Slimstat Analytics - Version 4.9

Version Description

  • [New] Browscap Library is now bundled with the main plugin, only definition files are downloaded dynamically.
  • [New] Support MaxMind License Key for GeoLite2 database downloads.
  • [New] Speedup Browscap version check when repository site is down.
  • [New] Delete plugin settings and stats only if explicitly enabled in settings
  • [Fix] Addressed a PHP warning of undefined variable when parsing a query string looking for search term keywords (thank you, inndesign).
  • [Fix] Fixed SQL error when Events Manager plugin is installed, 'Posts and Pages' is enabled, and no events are existing (thank you, lwangamaman.
  • [Fix] Starting with 4.9, PHP 7.4+ is required (thank you, stephanie-mitchell.
  • [Fix] Opt-Out cookie does not delete slimstat cookie.
Download this release

Release Info

Developer coolmann
Plugin Icon 128x128 Slimstat Analytics
Version 4.9
Comparing to
See all releases

Code changes from version 4.8.8.1 to 4.9

Files changed (199) hide show
  1. CHANGELOG.md +10 -88
  2. README.md +2 -2
  3. admin/config/index.php +29 -13
  4. admin/index.php +5 -2
  5. admin/view/index.php +1 -1
  6. admin/view/wp-slimstat-db.php +1 -1
  7. admin/view/wp-slimstat-reports.php +1 -1
  8. readme.txt +16 -94
  9. uninstall.php +6 -0
  10. vendor/browscap-php/LICENSE +674 -0
  11. vendor/browscap-php/browscap/browscap-php/src/Browscap.php +125 -0
  12. vendor/browscap-php/browscap/browscap-php/src/BrowscapInterface.php +56 -0
  13. vendor/browscap-php/browscap/browscap-php/src/BrowscapUpdater.php +415 -0
  14. vendor/browscap-php/browscap/browscap-php/src/BrowscapUpdaterInterface.php +88 -0
  15. vendor/browscap-php/browscap/browscap-php/src/Cache/BrowscapCache.php +226 -0
  16. vendor/browscap-php/browscap/browscap-php/src/Cache/BrowscapCacheInterface.php +92 -0
  17. vendor/browscap-php/browscap/browscap-php/src/Command/CheckUpdateCommand.php +117 -0
  18. vendor/browscap-php/browscap/browscap-php/src/Command/ConvertCommand.php +133 -0
  19. vendor/browscap-php/browscap/browscap-php/src/Command/FetchCommand.php +140 -0
  20. vendor/browscap-php/browscap/browscap-php/src/Command/ParserCommand.php +112 -0
  21. vendor/browscap-php/browscap/browscap-php/src/Command/UpdateCommand.php +125 -0
  22. vendor/browscap-php/browscap/browscap-php/src/Data/PropertyFormatter.php +36 -0
  23. vendor/browscap-php/browscap/browscap-php/src/Data/PropertyHolder.php +97 -0
  24. vendor/browscap-php/browscap/browscap-php/src/Exception.php +24 -0
  25. vendor/browscap-php/browscap/browscap-php/src/Exception/DomainException.php +11 -0
  26. vendor/browscap-php/browscap/browscap-php/src/Exception/ErrorCachedVersionException.php +9 -0
  27. vendor/browscap-php/browscap/browscap-php/src/Exception/ErrorReadingFileException.php +9 -0
  28. vendor/browscap-php/browscap/browscap-php/src/Exception/FetcherException.php +23 -0
  29. vendor/browscap-php/browscap/browscap-php/src/Exception/FileNameMissingException.php +11 -0
  30. vendor/browscap-php/browscap/browscap-php/src/Exception/FileNotFoundException.php +23 -0
  31. vendor/browscap-php/browscap/browscap-php/src/Exception/InvalidArgumentException.php +23 -0
  32. vendor/browscap-php/browscap/browscap-php/src/Exception/NoCachedVersionException.php +9 -0
  33. vendor/browscap-php/browscap/browscap-php/src/Exception/NoNewVersionException.php +9 -0
  34. vendor/browscap-php/browscap/browscap-php/src/Formatter/FormatterInterface.php +33 -0
  35. vendor/browscap-php/browscap/browscap-php/src/Formatter/LegacyFormatter.php +83 -0
  36. vendor/browscap-php/browscap/browscap-php/src/Formatter/PhpGetBrowser.php +125 -0
  37. vendor/browscap-php/browscap/browscap-php/src/Helper/Converter.php +236 -0
  38. vendor/browscap-php/browscap/browscap-php/src/Helper/ConverterInterface.php +71 -0
  39. vendor/browscap-php/browscap/browscap-php/src/Helper/Exception.php +14 -0
  40. vendor/browscap-php/browscap/browscap-php/src/Helper/Filesystem.php +68 -0
  41. vendor/browscap-php/browscap/browscap-php/src/Helper/IniLoader.php +76 -0
  42. vendor/browscap-php/browscap/browscap-php/src/Helper/IniLoaderInterface.php +53 -0
  43. vendor/browscap-php/browscap/browscap-php/src/Helper/Quoter.php +114 -0
  44. vendor/browscap-php/browscap/browscap-php/src/Helper/QuoterInterface.php +31 -0
  45. vendor/browscap-php/browscap/browscap-php/src/Helper/Support.php +83 -0
  46. vendor/browscap-php/browscap/browscap-php/src/Helper/SupportInterface.php +20 -0
  47. vendor/browscap-php/browscap/browscap-php/src/IniParser/IniParser.php +294 -0
  48. vendor/browscap-php/browscap/browscap-php/src/IniParser/ParserInterface.php +38 -0
  49. vendor/browscap-php/browscap/browscap-php/src/Parser/Helper/GetData.php +166 -0
  50. vendor/browscap-php/browscap/browscap-php/src/Parser/Helper/GetDataInterface.php +27 -0
  51. vendor/browscap-php/browscap/browscap-php/src/Parser/Helper/GetPattern.php +102 -0
  52. vendor/browscap-php/browscap/browscap-php/src/Parser/Helper/GetPatternInterface.php +26 -0
  53. vendor/browscap-php/browscap/browscap-php/src/Parser/Helper/Pattern.php +102 -0
  54. vendor/browscap-php/browscap/browscap-php/src/Parser/Helper/SubKey.php +82 -0
  55. vendor/browscap-php/browscap/browscap-php/src/Parser/Ini.php +103 -0
  56. vendor/browscap-php/browscap/browscap-php/src/Parser/ParserInterface.php +24 -0
  57. vendor/browscap-php/composer/ClassLoader.php +572 -0
  58. vendor/browscap-php/composer/autoload_classmap.php +16 -0
  59. vendor/browscap-php/composer/autoload_files.php +20 -0
  60. vendor/browscap-php/composer/autoload_namespaces.php +9 -0
  61. vendor/browscap-php/composer/autoload_psr4.php +33 -0
  62. vendor/browscap-php/composer/autoload_real.php +75 -0
  63. vendor/browscap-php/composer/autoload_static.php +185 -0
  64. vendor/browscap-php/league/flysystem/src/Config.php +43 -0
  65. vendor/browscap-php/league/flysystem/src/CorruptedPathDetected.php +13 -0
  66. vendor/browscap-php/league/flysystem/src/DirectoryAttributes.php +110 -0
  67. vendor/browscap-php/league/flysystem/src/DirectoryListing.php +84 -0
  68. vendor/browscap-php/league/flysystem/src/FileAttributes.php +139 -0
  69. vendor/browscap-php/league/flysystem/src/Filesystem.php +163 -0
  70. vendor/browscap-php/league/flysystem/src/FilesystemAdapter.php +108 -0
  71. vendor/browscap-php/league/flysystem/src/FilesystemException.php +11 -0
  72. vendor/browscap-php/league/flysystem/src/FilesystemOperationFailed.php +22 -0
  73. vendor/browscap-php/league/flysystem/src/FilesystemOperator.php +9 -0
  74. vendor/browscap-php/league/flysystem/src/FilesystemReader.php +66 -0
  75. vendor/browscap-php/league/flysystem/src/FilesystemWriter.php +58 -0
  76. vendor/browscap-php/league/flysystem/src/InvalidStreamProvided.php +11 -0
  77. vendor/browscap-php/league/flysystem/src/InvalidVisibilityProvided.php +20 -0
  78. vendor/browscap-php/league/flysystem/src/Local/LocalFilesystemAdapter.php +419 -0
  79. vendor/browscap-php/league/flysystem/src/MountManager.php +334 -0
  80. vendor/browscap-php/league/flysystem/src/PathNormalizer.php +10 -0
  81. vendor/browscap-php/league/flysystem/src/PathPrefixer.php +60 -0
  82. vendor/browscap-php/league/flysystem/src/PathTraversalDetected.php +28 -0
  83. vendor/browscap-php/league/flysystem/src/PortableVisibilityGuard.php +19 -0
  84. vendor/browscap-php/league/flysystem/src/ProxyArrayAccessToProperties.php +62 -0
  85. vendor/browscap-php/league/flysystem/src/StorageAttributes.php +40 -0
  86. vendor/browscap-php/league/flysystem/src/SymbolicLinkEncountered.php +28 -0
  87. vendor/browscap-php/league/flysystem/src/UnableToCheckFileExistence.php +21 -0
  88. vendor/browscap-php/league/flysystem/src/UnableToCopyFile.php +48 -0
  89. vendor/browscap-php/league/flysystem/src/UnableToCreateDirectory.php +44 -0
  90. vendor/browscap-php/league/flysystem/src/UnableToDeleteDirectory.php +48 -0
  91. vendor/browscap-php/league/flysystem/src/UnableToDeleteFile.php +45 -0
  92. vendor/browscap-php/league/flysystem/src/UnableToMountFilesystem.php +32 -0
  93. vendor/browscap-php/league/flysystem/src/UnableToMoveFile.php +48 -0
  94. vendor/browscap-php/league/flysystem/src/UnableToReadFile.php +45 -0
  95. vendor/browscap-php/league/flysystem/src/UnableToResolveFilesystemMount.php +20 -0
  96. vendor/browscap-php/league/flysystem/src/UnableToRetrieveMetadata.php +76 -0
  97. vendor/browscap-php/league/flysystem/src/UnableToSetVisibility.php +49 -0
  98. vendor/browscap-php/league/flysystem/src/UnableToWriteFile.php +45 -0
  99. vendor/browscap-php/league/flysystem/src/UnixVisibility/PortableVisibilityConverter.php +109 -0
  100. vendor/browscap-php/league/flysystem/src/UnixVisibility/VisibilityConverter.php +14 -0
  101. vendor/browscap-php/league/flysystem/src/UnreadableFileEncountered.php +28 -0
  102. vendor/browscap-php/league/flysystem/src/Visibility.php +11 -0
  103. vendor/browscap-php/league/flysystem/src/WhitespacePathNormalizer.php +49 -0
  104. vendor/browscap-php/league/mime-type-detection/src/EmptyExtensionToMimeTypeMap.php +13 -0
  105. vendor/browscap-php/league/mime-type-detection/src/ExtensionMimeTypeDetector.php +42 -0
  106. vendor/browscap-php/league/mime-type-detection/src/ExtensionToMimeTypeMap.php +10 -0
  107. vendor/browscap-php/league/mime-type-detection/src/FinfoMimeTypeDetector.php +79 -0
  108. vendor/browscap-php/league/mime-type-detection/src/GeneratedExtensionToMimeTypeMap.php +1220 -0
  109. vendor/browscap-php/league/mime-type-detection/src/MimeTypeDetector.php +19 -0
  110. vendor/browscap-php/league/mime-type-detection/src/OverridingExtensionToMimeTypeMap.php +30 -0
  111. vendor/browscap-php/matthiasmullie/scrapbook/src/Adapters/Apc.php +735 -0
  112. vendor/browscap-php/matthiasmullie/scrapbook/src/Adapters/Collections/Apc.php +46 -0
  113. vendor/browscap-php/matthiasmullie/scrapbook/src/Adapters/Collections/Couchbase.php +36 -0
  114. vendor/browscap-php/matthiasmullie/scrapbook/src/Adapters/Collections/Flysystem.php +73 -0
  115. vendor/browscap-php/matthiasmullie/scrapbook/src/Adapters/Collections/Memcached.php +29 -0
  116. vendor/browscap-php/matthiasmullie/scrapbook/src/Adapters/Collections/MemoryStore.php +54 -0
  117. vendor/browscap-php/matthiasmullie/scrapbook/src/Adapters/Collections/Redis.php +33 -0
  118. vendor/browscap-php/matthiasmullie/scrapbook/src/Adapters/Collections/SQL.php +54 -0
  119. vendor/browscap-php/matthiasmullie/scrapbook/src/Adapters/Collections/Utils/PrefixKeys.php +216 -0
  120. vendor/browscap-php/matthiasmullie/scrapbook/src/Adapters/Collections/Utils/PrefixReset.php +59 -0
  121. vendor/browscap-php/matthiasmullie/scrapbook/src/Adapters/Couchbase.php +501 -0
  122. vendor/browscap-php/matthiasmullie/scrapbook/src/Adapters/Flysystem.php +620 -0
  123. vendor/browscap-php/matthiasmullie/scrapbook/src/Adapters/Memcached.php +446 -0
  124. vendor/browscap-php/matthiasmullie/scrapbook/src/Adapters/MemoryStore.php +388 -0
  125. vendor/browscap-php/matthiasmullie/scrapbook/src/Adapters/MySQL.php +136 -0
  126. vendor/browscap-php/matthiasmullie/scrapbook/src/Adapters/PostgreSQL.php +126 -0
  127. vendor/browscap-php/matthiasmullie/scrapbook/src/Adapters/Redis.php +626 -0
  128. vendor/browscap-php/matthiasmullie/scrapbook/src/Adapters/SQL.php +513 -0
  129. vendor/browscap-php/matthiasmullie/scrapbook/src/Adapters/SQLite.php +98 -0
  130. vendor/browscap-php/matthiasmullie/scrapbook/src/Buffered/BufferedStore.php +229 -0
  131. vendor/browscap-php/matthiasmullie/scrapbook/src/Buffered/TransactionalStore.php +255 -0
  132. vendor/browscap-php/matthiasmullie/scrapbook/src/Buffered/Utils/Buffer.php +96 -0
  133. vendor/browscap-php/matthiasmullie/scrapbook/src/Buffered/Utils/BufferCollection.php +53 -0
  134. vendor/browscap-php/matthiasmullie/scrapbook/src/Buffered/Utils/Defer.php +576 -0
  135. vendor/browscap-php/matthiasmullie/scrapbook/src/Buffered/Utils/Transaction.php +523 -0
  136. vendor/browscap-php/matthiasmullie/scrapbook/src/Exception/Exception.php +12 -0
  137. vendor/browscap-php/matthiasmullie/scrapbook/src/Exception/InvalidCollection.php +12 -0
  138. vendor/browscap-php/matthiasmullie/scrapbook/src/Exception/InvalidKey.php +12 -0
  139. vendor/browscap-php/matthiasmullie/scrapbook/src/Exception/OperationFailed.php +12 -0
  140. vendor/browscap-php/matthiasmullie/scrapbook/src/Exception/ServerUnhealthy.php +12 -0
  141. vendor/browscap-php/matthiasmullie/scrapbook/src/Exception/UnbegunTransaction.php +12 -0
  142. vendor/browscap-php/matthiasmullie/scrapbook/src/Exception/UncommittedTransaction.php +12 -0
  143. vendor/browscap-php/matthiasmullie/scrapbook/src/KeyValueStore.php +253 -0
  144. vendor/browscap-php/matthiasmullie/scrapbook/src/Psr16/InvalidArgumentException.php +9 -0
  145. vendor/browscap-php/matthiasmullie/scrapbook/src/Psr16/SimpleCache.php +257 -0
  146. vendor/browscap-php/matthiasmullie/scrapbook/src/Psr6/InvalidArgumentException.php +9 -0
  147. vendor/browscap-php/matthiasmullie/scrapbook/src/Psr6/Item.php +234 -0
  148. vendor/browscap-php/matthiasmullie/scrapbook/src/Psr6/Pool.php +250 -0
  149. vendor/browscap-php/matthiasmullie/scrapbook/src/Psr6/Repository.php +125 -0
  150. vendor/browscap-php/matthiasmullie/scrapbook/src/Scale/Shard.php +257 -0
  151. vendor/browscap-php/matthiasmullie/scrapbook/src/Scale/StampedeProtector.php +298 -0
  152. vendor/browscap-php/psr/cache/src/CacheException.php +10 -0
  153. vendor/browscap-php/psr/cache/src/CacheItemInterface.php +105 -0
  154. vendor/browscap-php/psr/cache/src/CacheItemPoolInterface.php +138 -0
  155. vendor/browscap-php/psr/cache/src/InvalidArgumentException.php +13 -0
  156. vendor/browscap-php/psr/container/src/ContainerExceptionInterface.php +12 -0
  157. vendor/browscap-php/psr/container/src/ContainerInterface.php +36 -0
  158. vendor/browscap-php/psr/container/src/NotFoundExceptionInterface.php +10 -0
  159. vendor/browscap-php/psr/simple-cache/src/CacheException.php +10 -0
  160. vendor/browscap-php/psr/simple-cache/src/CacheInterface.php +114 -0
  161. vendor/browscap-php/psr/simple-cache/src/InvalidArgumentException.php +13 -0
  162. vendor/browscap-php/ralouphie/getallheaders/src/getallheaders.php +46 -0
  163. vendor/browscap-php/symfony/deprecation-contracts/function.php +27 -0
  164. vendor/browscap-php/symfony/polyfill-ctype/Ctype.php +232 -0
  165. vendor/browscap-php/symfony/polyfill-ctype/bootstrap.php +46 -0
  166. vendor/browscap-php/symfony/polyfill-intl-grapheme/Grapheme.php +247 -0
  167. vendor/browscap-php/symfony/polyfill-intl-grapheme/bootstrap.php +54 -0
  168. vendor/browscap-php/symfony/polyfill-intl-normalizer/Normalizer.php +310 -0
  169. vendor/browscap-php/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php +17 -0
  170. vendor/browscap-php/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalComposition.php +945 -0
  171. vendor/browscap-php/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalDecomposition.php +2065 -0
  172. vendor/browscap-php/symfony/polyfill-intl-normalizer/Resources/unidata/combiningClass.php +876 -0
  173. vendor/browscap-php/symfony/polyfill-intl-normalizer/Resources/unidata/compatibilityDecomposition.php +3695 -0
  174. vendor/browscap-php/symfony/polyfill-intl-normalizer/bootstrap.php +19 -0
  175. vendor/browscap-php/symfony/polyfill-mbstring/Mbstring.php +873 -0
  176. vendor/browscap-php/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php +1397 -0
  177. vendor/browscap-php/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php +5 -0
  178. vendor/browscap-php/symfony/polyfill-mbstring/Resources/unidata/upperCase.php +1489 -0
  179. vendor/browscap-php/symfony/polyfill-mbstring/bootstrap.php +143 -0
  180. vendor/browscap-php/symfony/polyfill-php73/Php73.php +43 -0
  181. vendor/browscap-php/symfony/polyfill-php73/Resources/stubs/JsonException.php +16 -0
  182. vendor/browscap-php/symfony/polyfill-php73/bootstrap.php +31 -0
  183. vendor/browscap-php/symfony/polyfill-php80/Php80.php +105 -0
  184. vendor/browscap-php/symfony/polyfill-php80/Resources/stubs/Attribute.php +22 -0
  185. vendor/browscap-php/symfony/polyfill-php80/Resources/stubs/Stringable.php +11 -0
  186. vendor/browscap-php/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php +7 -0
  187. vendor/browscap-php/symfony/polyfill-php80/Resources/stubs/ValueError.php +7 -0
  188. vendor/browscap-php/symfony/polyfill-php80/bootstrap.php +42 -0
  189. vendor/browscap-php/symfony/string/AbstractString.php +795 -0
  190. vendor/browscap-php/symfony/string/AbstractUnicodeString.php +620 -0
  191. vendor/browscap-php/symfony/string/ByteString.php +506 -0
  192. vendor/browscap-php/symfony/string/CodePointString.php +270 -0
  193. vendor/browscap-php/symfony/string/Exception/ExceptionInterface.php +16 -0
  194. vendor/browscap-php/symfony/string/Exception/InvalidArgumentException.php +16 -0
  195. vendor/browscap-php/symfony/string/Exception/RuntimeException.php +16 -0
  196. vendor/browscap-php/symfony/string/Inflector/EnglishInflector.php +511 -0
  197. vendor/browscap-php/symfony/string/Inflector/FrenchInflector.php +157 -0
  198. vendor/browscap-php/symfony/string/Inflector/InflectorInterface.php +33 -0
  199. vendor/browscap-php/symfony/string/LazyString.php +78 -0
CHANGELOG.md CHANGED
@@ -1,4 +1,14 @@
1
  ## Changelog ##
 
 
 
 
 
 
 
 
 
 
2
  ### 4.8.8.1 ###
3
  * [Update] The Privacy Mode option under Slimstat > Settings > Tracker now controls the fingerprint collection mechanism as well. If you have this option enabled to comply with European privacy laws, your visitors' IP addresses will be masked and they won't be fingerprinted (thank you, Peter).
4
  * [Update] Improved handling of our local DNS cache to store hostnames when the option to convert IP addresses is enabled in the settings.
@@ -69,91 +79,3 @@
69
  * [Update] Decrease the number of database requests needed to record a new pageview.
70
  * [Fix] Entries with a trailing slash and ones without were being listed as separate in Top Web Pages.
71
  * [Fix] Typo in one of the conditions definining the Top Bots report.
72
-
73
- ### 4.8.5.1 ###
74
- * [Fix] A bug was affecting the way shortcodes were being displayed on the website (thank you, [inndesign](https://wordpress.org/support/topic/crashes-avada-theme-in-chrome/)).
75
- * [Fix] Some icons in the Access Log were broken and not displayed as expected.
76
- * [Fix] Added extra code to make sure a callback function is defined for any given report.
77
- * [Fix] Top reports where displaying an incorrect percentage value on the WordPress dashboard (thank you, [scruffy1 and a305587](https://wordpress.org/support/topic/dashboard-widgets-showing-0)).
78
-
79
- ### 4.8.5 ###
80
- * [New] Introduced option to not track pageviews based on the ACCEPT-LANGUAGE header sent by the browser.
81
- * [New] Introduced option to display a pageview count instead of the percentage in Top reports.
82
- * [New] Introduced two new reports under the Audience tab: Tob Bots and Top Human Browsers.
83
- * [Update] Removed the option to hide reports on tabs, as it was confusing users who couldn't find them. Now you can simply use the Customizer to arrange your reports, and place the ones you don't need in the Inactive box.
84
- * [Update] Rewritten the code that manages which reports are displayed on which screen (Customizer), streamlined data structures and optimized their use. Please update all the add-ons to the latest version available. Don't hesitate to contact us if you have any questions!
85
- * [Fix] The HTML markup in the opt-out message field was being stripped out (thank you, [paulmcmanus](https://wordpress.org/support/topic/saving-settings-flips-opt-out-message-to-plain-text/)).
86
- * [Fix] Reports could not be properly deleted in the Customizer, if the Slimstat menu was displayed in the Admin Bar.
87
- * [Fix] A fatal error thrown by the Maxmind library when the data file is corrupted has been addressed.
88
- * [Fix] The icon filename for Windows 8.1 was incorrect (thank you, Dimitri).
89
-
90
- ### 4.8.4.1 ###
91
- * [Note] As anticipated a few weeks ago, this update drops the information about your visitors' browser plugins, which had been deprecated as not useful and oftentimes unreliable. Please make sure to backup your Slimstat tables if you need to preserve this information for some reason.
92
- * [Update] We received quite a few messages complaining about our decision to change the default position of the Slimstat menu from the sidebar to the admin bar. We are rolling back this change, and we apologize for any confusion this might have caused.
93
- * [Update] Added visitor's language to the Activity Log report.
94
- * [Update] Introduced code optimizations to improve performance when localizing strings related to operating systems, languages, countries, etc.
95
- * [Fix] Page URLs were not being displayed correctly if the option to display page titles was turned off.
96
- * [Fix] Not storing empty values in the database: leave as NULL. This will squeeze a few more bytes out of each row stored in the database.
97
-
98
- ### 4.8.4 ###
99
- * [Note] If you're using any of our premium add-ons, please make sure to update them to the latest version available (see Slimstat > Add-ons) as we've updated some references in our code.
100
- * [Note] We recently received an email from one of our users suggesting that we replace the line charts currently used to display reports over a timeline with **bar charts**, because 'the number of pageviews and IPs are discrete numbers, hence they should also be presented as discrete numbers', according to him. What do you think? Please let us know by [sending us a message](https://support.wp-slimstat.com/) on our support platform. Thank you.
101
- * [Update] Renamed a few files in the admin. If you're including Slimstat libraries in your custom code, please make sure to check that your references are up-to-date. Also, make sure to clear your cache if you page layout doesn't look right.
102
- * [Update] [AmCharts](https://www.amcharts.com/javascript-charts/), the library used to render all of our charts, has been updated to version 4.5.3.
103
- * [Update] When functioning in Client mode, the tracker will now not ignore bots, spiders and the like automatically. Please use the appropriate option under Settings > Exclusions if you would like to ignore bots. This solves an incompatibility issue with some caching plugins which "prefetch" the website, presenting themselves as bots.
104
- * [Update] Removed tracker notice field under Settings > Maintenance as it was confusing many people and generating extra work for our customer service team.
105
- * [Update] Removed option to not track "client properties" like screen resolution, etc. Also, removed option to not honor DNT headers, as we received complaints from privacy activists on this matter.
106
- * [Update] Removed option to change date/time formats and numeric separators: Slimstat will now use the WordPress settings to adjust its behavior.
107
- * [Update] Removed 'About Slimstat' report, given that some of the information in it has been moved to the Settings.
108
- * [Update] Removed unused strings, improved contextual descriptions and applied consistent naming conventions across our codebase (first pass).
109
- * [Update] The Slimstat admin menu is now added to the Admin Bar by default. Please go to Settings > Basic > WordPress Integration and change the corresponding option, if you prefer to use the side menu instead.
110
- * [Update] Enabled code editor in Settings.
111
- * [Update] Implemented a new optimized function to retrieve the post count on the Edit Posts/Pages/CPTs screens. Thank you, Lance.
112
- * [Update] Improved browser detection feature, which will now fallback to the heuristic function if the Browscap data file doesn't contain an exact match for a given browser. This usually happens whenever a new browser version is released, which is not yet included in the data file.
113
- * [Update] Option to track same-domain referrers is now deactivated by default on new installations.
114
- * [Update] Enabled wildcards on the exclusion rule by capability.
115
- * [Update] Improved the overall source code readability score. Now you don't have any other excuses to not contribute to this project!
116
- * [Update] Table indexes are now enabled by default in the database.
117
- * [Update] Added new WordPress filter to the Browscap Library, so that third-party tools can manipulate the data before it's returned to the tracker.
118
- * [Update] Added [nonce](https://wordpress.org/support/article/glossary/#nonce) to Settings page for improved security.
119
-
120
- ### 4.8.3 ###
121
- * [Note] Thank you for all the great feedback you provided to our unofficial survey about retiring the 'browser plugins' feature. The vast majority of those who replied confirmed what we already thought. Please consider backing up your database if you would like to preserve this information for future analysis. With this update, we removed the portion of code that tracks that information, but kept the existing data untouched. In a couple of releases, code will be added to actually drop this column from the database.
122
- * [New] If English is not your primary languge, Slimstat will now display a notice asking for your help to [translate our plugin](https://translate.wordpress.org/projects/wp-plugins/wp-slimstat/) in your language. Please consider volunteering for this great opportunity to help our community!
123
- * [Update] We are working with the GlotPress community to improve the way Slimstat speaks your language. We had to change the way certain strings are defined in our source code. Please let us know if you notice any unexpected behavior when analyzing languages, countries and operating systems.
124
- * [Update] Removed Facebook rankings metrics, as the API has been deprecated and the new one is not accessible without a private token.
125
- * [Update] MozRank has been deprecated, we have replaced it with the Domain Authority metric.
126
- * [Update] Spring cleaning in the 'admin notices' department: removed some obsolete CSS code, replaced by built-in WP classes and definitions.
127
- * [Fix] Changed the default minimum capability to access the reports from 'activate_plugins' to 'manage_options', so that regular administrators (a.k.a. non-super admins) in a multisite environment can still see their own reports (thank you, [homepageware](https://wordpress.org/support/topic/slimstat-and-multisite/)). This update does not affect existing installations: if you want regular admins to see their own stats, please go to Slimstat > Settings > Access Control and change the values in the corresponding fields.
128
- * [Fix] The autorefresh feature for the Access Log was not working as expected. Thank you to all the users who patiently worked with us to identify the issue.
129
- * [Fix] A conflict between the Async loader and AmCharts 4 was causing the Screen Options tab to not work as expected (thank you, [softfully](https://wordpress.org/support/topic/screen-options-doesnt-open/)).
130
- * [Fix] Removed unused setting 'Expand Reports'
131
-
132
- ### 4.8.2 ###
133
- * [Note] Our team has been contemplating the idea of deprecating the information collected about your visitors' *browser plugins* (Java, PDF reader, RealView player, Silverlight, etc). In this day and age, where browsers use either built-in functionality to provide those features, or extensions that cannot be tracked for privacy purposes, it feels anachronistic to continue collecting this outdated information. By getting rid of this specific feature, we can streamline our code, improve performance, and reduce the database size. However, we wanted to hear from our users before anything is actually implemented. Please do not hesitate [to let us know](https://support.wp-slimstat.com) if you are using the 'browser plugins' field for your reporting needs.
134
- * [New] Many CRM integration plugins rely mostly on the user emails, not usernames. For this reason, a new email field has been added to the database (thank you, [sandrodz](https://github.com/sandrodz)).
135
- * [Update] Changed the preset intervals in the date filter dropdown so that you can get a day over day comparison (Monday over Monday, etc) for improved accuracy.
136
- * [Update] [AmCharts](https://www.amcharts.com/javascript-charts/), the library used to render all of our charts, has been updated to version 4.4.9.
137
- * [Fix] The countdown timer on the Access Log was not working as expected (thank you, [anniest](https://wordpress.org/support/topic/no-refresh-2/)).
138
- * [Fix] The countdown timer was causing an warning message to appear on other screens.
139
- * [Fix] Minor aesthetic improvements.
140
-
141
- ### 4.8.1 ###
142
- * [Update] Async mode will now serialize concurrent requests to the backend to optimize performance and reduce server load.
143
- * [Fix] Addressed a remote XSS vulnerability disclosed by Sucuri/GoDaddy.
144
- * [Fix] Charts were displaying the wrong label for certain values (thank you, Alex).
145
-
146
- ### 4.8 ###
147
- * [Note] Now that we have a cleaner foundation to build on, it's time to start introducing new reports and new ways to segment your audience and the traffic they generate. While our users test the latest changes and updates (to confirm that the foundation is indeed solid and bug-free), we are hard at work implementing the first batch of new reports. Some of them will be made available in the free version, while others will be added to our premium add-on, [User Overview](https://www.wp-slimstat.com/downloads/user-overview/). And we need your help! If you think that a specific report should be added to Slimstat, please do not hesitate to let us know!
148
- * [Note] Worried about the recent [news regarding jQuery vulnerabilities](https://www.zdnet.com/article/popular-jquery-javascript-library-impacted-by-prototype-pollution-flaw/)? Slimstat doesn't use jQuery as a dependency, so you can sleep tight knowing that your website will not be affected.
149
- * [Update] [AmCharts](https://www.amcharts.com/javascript-charts/), the library used to render all of our charts, has been updated to version 4. This new release is not backward compatible, so the code to integrate it with Slimstat had to be completely rewritten. Please let us know if you notice any issues.
150
- * [Update] [Plugin Update Checker](https://github.com/YahnisElsts/plugin-update-checker), the library we use to check if a new version of our premium add-ons is available for download, has been update to version 4.6.
151
- * [Update] If you're using our partner's CDN functionality (JsDelivr) to load the tracker, their link is now always loaded over HTTPS for added security.
152
- * [Update] Switched the Add-on Update checker URL to HTTPS, for added security (thank you, Peter).
153
- * [Update] Changed the protocol of all the URLs used within Slimstat, including references to our documentation, to HTTPS.
154
- * [Update] Added icon to geolocate Originating IP addresses, when detected.
155
- * [Fix] The optout cookie path was not being set correctly (thank you, [ralfkerkhoff](https://wordpress.org/support/topic/opt-out-cookie-per-page/)).
156
- * [Fix] Google seems to be using a new User Agent string for its "mobile" crawler, which was causing Slimstat from incorrectly identifying visits as coming from mobile devices, instead of bots (thank you, Ron).
157
- * [Fix] An error was being returned if SVG elements were using the A tag on a page (thank you, [snaphappyme](https://wordpress.org/support/topic/uncaught-typeerror-all_linksn-href-indexof/)).
158
- * [Fix] A bug was causing Slimstat to incorrectly geolocate visits to websites behind a Cloudflare load balancer. Please update the IP Address Fix add-on as well.
159
- * [Fix] Tweaked the formula to determine your website bounce rate, and updated the associated description to better reflect the underlying calculations.
1
  ## Changelog ##
2
+ ### 4.9 ###
3
+ * [New] Browscap Library is now bundled with the main plugin, only definition files are downloaded dynamically.
4
+ * [New] Support MaxMind License Key for GeoLite2 database downloads.
5
+ * [New] Speedup Browscap version check when repository site is down.
6
+ * [New] Delete plugin settings and stats only if explicitly enabled in settings
7
+ * [Fix] Addressed a PHP warning of undefined variable when parsing a query string looking for search term keywords (thank you, [inndesign](https://wordpress.org/support/topic/line-747-and-line-1574-undefined)).
8
+ * [Fix] Fixed SQL error when Events Manager plugin is installed, 'Posts and Pages' is enabled, and no events are existing (thank you, [lwangamaman](https://wordpress.org/support/topic/you-have-an-error-in-your-sql-syntax-22/).
9
+ * [Fix] Starting with 4.9, PHP 7.4+ is required (thank you, [stephanie-mitchell](https://wordpress.org/support/topic/slimstats-4-8-8-1-fails-on-php-5-3-26-but-no-warning-during-installation/).
10
+ * [Fix] Opt-Out cookie does not delete slimstat cookie.
11
+
12
  ### 4.8.8.1 ###
13
  * [Update] The Privacy Mode option under Slimstat > Settings > Tracker now controls the fingerprint collection mechanism as well. If you have this option enabled to comply with European privacy laws, your visitors' IP addresses will be masked and they won't be fingerprinted (thank you, Peter).
14
  * [Update] Improved handling of our local DNS cache to store hostnames when the option to convert IP addresses is enabled in the settings.
79
  * [Update] Decrease the number of database requests needed to record a new pageview.
80
  * [Fix] Entries with a trailing slash and ones without were being listed as separate in Top Web Pages.
81
  * [Fix] Typo in one of the conditions definining the Top Bots report.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
README.md CHANGED
@@ -22,7 +22,7 @@ Try it out, you'll be amazed how good it feels! If you're on a tight budget, and
22
 
23
  ### Requirements ###
24
  * WordPress 4.9+
25
- * PHP 5.2+ (or 7.1+ if you use the Browscap data file)
26
  * MySQL 5.0.3+
27
  * At least 40 MB of free web space
28
  * At least 5 MB of free DB space
@@ -36,7 +36,7 @@ Try it out, you'll be amazed how good it feels! If you're on a tight budget, and
36
  5. If your `wp-admin` folder is not publicly accessible, make sure to check our [knowledge base](https://docs.wp-slimstat.com/) to see if there's anything else you need to do
37
 
38
  ## Please note ##
39
- * If you decide to uninstall Slimstat Analytics, all the stats will be **PERMANENTLY** deleted from your database. Make sure to setup a database backup (wp_slim_*) to avoid losing your data.
40
  * If you are upgrading from a version prior to 4.0, please [install version 4.0](https://downloads.wordpress.org/plugin/wp-slimstat.4.0.zip) first to upgrade the database structure and download the new Geolocation data.
41
 
42
  ## Frequently Asked Questions ##
22
 
23
  ### Requirements ###
24
  * WordPress 4.9+
25
+ * PHP 7.4+
26
  * MySQL 5.0.3+
27
  * At least 40 MB of free web space
28
  * At least 5 MB of free DB space
36
  5. If your `wp-admin` folder is not publicly accessible, make sure to check our [knowledge base](https://docs.wp-slimstat.com/) to see if there's anything else you need to do
37
 
38
  ## Please note ##
39
+ * If you decide to uninstall Slimstat Analytics, all the stats will be **PERMANENTLY** deleted from your database if you enable the 'Delete Data on Uninstall' setting. In this case, make sure to setup a database backup (wp_slim_*) to avoid losing your data.
40
  * If you are upgrading from a version prior to 4.0, please [install version 4.0](https://downloads.wordpress.org/plugin/wp-slimstat.4.0.zip) first to upgrade the database structure and download the new Geolocation data.
41
 
42
  ## Frequently Asked Questions ##
admin/config/index.php CHANGED
@@ -11,6 +11,15 @@ $current_tab = empty( $_GET[ 'tab' ] ) ? 1 : intval( $_GET[ 'tab' ] );
11
  // Retrieve any tracker errors for display
12
  $last_tracker_error = get_option( 'slimstat_tracker_error', array() );
13
 
 
 
 
 
 
 
 
 
 
14
  // Define all the options
15
  $settings = array(
16
  1 => array(
@@ -234,7 +243,7 @@ var SlimStatParams = { ajaxurl: "' . admin_url( 'admin-ajax.php' ) . '" };
234
  'ip_lookup_service' => array(
235
  'title' => __( 'IP Geolocation', 'wp-slimstat' ),
236
  'type'=> 'text',
237
- 'description'=> __( 'Customize the URL of the geolocation service to be used in the Access Log. Default value: <code>https://www.infosniper.net/?ip_address=</code>', 'wp-slimstat' )
238
  ),
239
  'comparison_chart' => array(
240
  'title' => __( 'Comparison Chart', 'wp-slimstat' ),
@@ -535,7 +544,12 @@ var SlimStatParams = { ajaxurl: "' . admin_url( 'admin-ajax.php' ) . '" };
535
  'enable_maxmind' => array(
536
  'title' => __( 'MaxMind Geolocation', 'wp-slimstat' ),
537
  'type'=> 'toggle',
538
- 'description'=> __( "The <a href='https://dev.maxmind.com/geoip/geoip2/geolite2/' target='_blank'>MaxMind GeoLite2 library</a>, which Slimstat uses to geolocate your visitors, is released under the Creative Commons BY-SA 4.0 license, and cannot be directly bundled with the plugin because of license incompatibility issues. If you're getting an error after enabling this option, please <a href='https://slimstat.freshdesk.com/solution/articles/12000039798-how-to-manually-install-the-maxmind-geolocation-data-file-' target='_blank'>take a look at our knowledge base</a> to learn how to install this file manually.", 'wp-slimstat' ) . ( !empty( $maxmind_last_modified ) ? ' ' . sprintf ( __( 'Your data file was last downloaded on <strong>%s</strong>', 'wp-slimstat' ), $maxmind_last_modified ) : '' )
 
 
 
 
 
539
  ),
540
 
541
  // Maintenance - Danger Zone
@@ -554,6 +568,11 @@ var SlimStatParams = { ajaxurl: "' . admin_url( 'admin-ajax.php' ) . '" };
554
  'type'=> 'plain-text',
555
  'after_input_field' => '<a class="button-primary" href="' . wp_slimstat_admin::$config_url . $current_tab . '&amp;action=reset-settings&amp;slimstat_update_settings=' . wp_create_nonce( 'slimstat_update_settings' ) . '" onclick="return( confirm( \'' . __( 'Please confirm that you want to RESET your settings.' ,'wp-slimstat' ) . '\' ) )">' . __( 'Factory Reset', 'wp-slimstat' ) . '</a>',
556
  'description'=> __( 'Restore all the settings to their default value. This action DOES NOT delete any records collected by the plugin.' ,'wp-slimstat' )
 
 
 
 
 
557
  )
558
  )
559
  ),
@@ -568,10 +587,10 @@ if ( version_compare( PHP_VERSION, '7.1', '>=' ) ) {
568
  $enable_browscap = array( 'enable_browscap' => array(
569
  'title' => __( 'Browscap Library', 'wp-slimstat' ),
570
  'type'=> 'toggle',
571
- 'description'=> __( "We are contributing to the <a href='https://browscap.org/' target='_blank'>Browscap Capabilities Project</a>, which we use to decode your visitors' user agent string into browser name and operating system. We use an <a href='https://github.com/slimstat/browscap-db' target='_blank'>optimized version of their data structure</a>, for improved performance. When enabled, Slimstat uses this library in addition to the built-in heuristic function, to determine your visitors' browser information. Updates are downloaded automatically every two weeks, when available.", 'wp-slimstat' ) . ( !empty( slim_browser::$browscap_local_version ) ? ' ' . sprintf( __( 'You are currently using version %s.' ), '<strong>' . slim_browser::$browscap_local_version . '</strong>' ) : '' )
572
  ) );
573
 
574
- $settings[ 6 ][ 'rows' ] = array_slice( $settings[ 6 ][ 'rows' ], 0, 6, true) + $enable_browscap + array_slice($settings[ 6 ][ 'rows' ], 6, NULL, true );
575
  }
576
 
577
  // Allow third-party tools to add their own settings
@@ -633,7 +652,9 @@ if ( !empty( $settings ) && !empty( $_REQUEST[ 'slimstat_update_settings' ] ) &&
633
 
634
  // MaxMind Data File
635
  if ( !empty( $_POST[ 'options' ][ 'enable_maxmind' ] ) ) {
636
- if ( $_POST[ 'options' ][ 'enable_maxmind' ] == 'on' && wp_slimstat::$settings[ 'enable_maxmind' ] == 'no' ) {
 
 
637
  include_once( plugin_dir_path( dirname( dirname( __FILE__ ) ) ) . 'vendor/maxmind.php' );
638
  $error = maxmind_geolite2_connector::download_maxmind_database();
639
 
@@ -683,7 +704,7 @@ if ( !empty( $settings ) && !empty( $_REQUEST[ 'slimstat_update_settings' ] ) &&
683
  else if ( $_POST[ 'options' ][ 'enable_browscap' ] == 'no' && wp_slimstat::$settings[ 'enable_browscap' ] == 'on' ) {
684
  WP_Filesystem();
685
 
686
- if ( $GLOBALS[ 'wp_filesystem' ]->rmdir( wp_slimstat::$upload_dir . '/browscap-db-master/', true ) ) {
687
  $save_messages[] = __( 'The Browscap data file has been uninstalled from your server.', 'wp-slimstat' );
688
  wp_slimstat::$settings[ 'enable_browscap' ] = 'no';
689
  }
@@ -733,12 +754,7 @@ if ( !empty( $settings ) && !empty( $_REQUEST[ 'slimstat_update_settings' ] ) &&
733
  }
734
  }
735
 
736
- $maxmind_last_modified = '';
737
- if ( file_exists( $maxmind_path ) && false !== ( $file_stat = @stat( $maxmind_path ) ) ) {
738
- $maxmind_last_modified = date_i18n( get_option( 'date_format' ), $file_stat[ 'mtime' ] );
739
- }
740
-
741
- $index_enabled = wp_slimstat::$wpdb->get_results(
742
  "SHOW INDEX FROM {$GLOBALS[ 'wpdb' ]->prefix}slim_stats WHERE Key_name = '{$GLOBALS[ 'wpdb' ]->prefix}stats_resource_idx'"
743
  );
744
 
@@ -898,4 +914,4 @@ foreach ( $settings as $a_tab_id => $a_tab_info ) {
898
  </form>
899
 
900
  <?php endif ?>
901
- </div>
11
  // Retrieve any tracker errors for display
12
  $last_tracker_error = get_option( 'slimstat_tracker_error', array() );
13
 
14
+ // Maxmind Data File
15
+ $maxmind_path = wp_slimstat::$upload_dir . '/maxmind.mmdb';
16
+
17
+ $maxmind_last_modified = '';
18
+ if ( file_exists( $maxmind_path ) && false !== ( $file_stat = @stat( $maxmind_path ) ) ) {
19
+ $maxmind_last_modified = date_i18n( get_option( 'date_format' ), $file_stat[ 'mtime' ] );
20
+ }
21
+
22
+
23
  // Define all the options
24
  $settings = array(
25
  1 => array(
243
  'ip_lookup_service' => array(
244
  'title' => __( 'IP Geolocation', 'wp-slimstat' ),
245
  'type'=> 'text',
246
+ 'description'=> __( 'Customize the URL of the geolocation service to be used in the Access Log. Default value: <code>https://whatismyipaddress.com/ip/</code>', 'wp-slimstat' )
247
  ),
248
  'comparison_chart' => array(
249
  'title' => __( 'Comparison Chart', 'wp-slimstat' ),
544
  'enable_maxmind' => array(
545
  'title' => __( 'MaxMind Geolocation', 'wp-slimstat' ),
546
  'type'=> 'toggle',
547
+ 'description'=> __( "The <a href='https://dev.maxmind.com/geoip/geoip2/geolite2/' target='_blank'>MaxMind GeoLite2 library</a>, which Slimstat uses to geolocate your visitors, is released under the Creative Commons BY-SA 4.0 license, and cannot be directly bundled with the plugin because of license incompatibility issues. You must obtain and set a GeoLite2 license key below. If you're getting an error after enabling this option, please <a href='https://slimstat.freshdesk.com/solution/articles/12000039798-how-to-manually-install-the-maxmind-geolocation-data-file-' target='_blank'>take a look at our knowledge base</a> to learn how to install this file manually.", 'wp-slimstat' ) . ' ' . __( 'Updates are downloaded automatically every 4 weeks, when available.', 'wp-slimstat' ) . ( !empty( $maxmind_last_modified ) ? ' ' . sprintf ( __( 'Your data file was last downloaded on <strong>%s</strong>.', 'wp-slimstat' ), $maxmind_last_modified ) : '' )
548
+ ),
549
+ 'maxmind_license_key' => array(
550
+ 'title' => __( 'MaxMind License Key', 'wp-slimstat' ),
551
+ 'type'=> 'text',
552
+ 'description'=> __( 'To be able to automatically download and update the MaxMind GeoLite2 database, you must sign up on <a href="https://dev.maxmind.com/geoip/geoip2/geolite2/" target="_blank">MaxMind GeoLite2</a> and create a license key. Then enter your license key in this field. Disable- and re-enable MaxMind Geolocation above to activate the license key. Note: It takes a couple of minutes after you created the license key to get it activated on the MaxMind website.', 'wp-slimstat' )
553
  ),
554
 
555
  // Maintenance - Danger Zone
568
  'type'=> 'plain-text',
569
  'after_input_field' => '<a class="button-primary" href="' . wp_slimstat_admin::$config_url . $current_tab . '&amp;action=reset-settings&amp;slimstat_update_settings=' . wp_create_nonce( 'slimstat_update_settings' ) . '" onclick="return( confirm( \'' . __( 'Please confirm that you want to RESET your settings.' ,'wp-slimstat' ) . '\' ) )">' . __( 'Factory Reset', 'wp-slimstat' ) . '</a>',
570
  'description'=> __( 'Restore all the settings to their default value. This action DOES NOT delete any records collected by the plugin.' ,'wp-slimstat' )
571
+ ),
572
+ 'delete_data_on_uninstall' => array(
573
+ 'title' => __( 'Delete Data on Uninstall', 'wp-slimstat' ),
574
+ 'type'=> 'toggle',
575
+ 'description'=> __( 'Delete all settings and statistics on plugin uninstall. Warning! If you enable this feature, all statistics and plugin settings will be permanently deleted from the database.','wp-slimstat' )
576
  )
577
  )
578
  ),
587
  $enable_browscap = array( 'enable_browscap' => array(
588
  'title' => __( 'Browscap Library', 'wp-slimstat' ),
589
  'type'=> 'toggle',
590
+ 'description'=> __( "We are contributing to the <a href='https://browscap.org/' target='_blank'>Browscap Capabilities Project</a>, which we use to decode your visitors' user agent string into browser name and operating system. We use an <a href='https://github.com/slimstat/browscap-cache' target='_blank'>optimized version of their data structure</a>, for improved performance. When enabled, Slimstat uses this library in addition to the built-in heuristic function, to determine your visitors' browser information. Updates are downloaded automatically every two weeks, when available.", 'wp-slimstat' ) . ( !empty( slim_browser::$browscap_local_version ) ? ' ' . sprintf( __( 'You are currently using version %s.', 'wp-slimstat' ), '<strong>' . slim_browser::$browscap_local_version . '</strong>' ) : '' )
591
  ) );
592
 
593
+ $settings[ 6 ][ 'rows' ] = array_slice( $settings[ 6 ][ 'rows' ], 0, 7, true) + $enable_browscap + array_slice($settings[ 6 ][ 'rows' ], 7, NULL, true );
594
  }
595
 
596
  // Allow third-party tools to add their own settings
652
 
653
  // MaxMind Data File
654
  if ( !empty( $_POST[ 'options' ][ 'enable_maxmind' ] ) ) {
655
+ if ( $_POST[ 'options' ][ 'enable_maxmind' ] == 'on'
656
+ && wp_slimstat::$settings[ 'enable_maxmind' ] == 'no'
657
+ && wp_slimstat::$settings[ 'maxmind_license_key' ] != '' ) {
658
  include_once( plugin_dir_path( dirname( dirname( __FILE__ ) ) ) . 'vendor/maxmind.php' );
659
  $error = maxmind_geolite2_connector::download_maxmind_database();
660
 
704
  else if ( $_POST[ 'options' ][ 'enable_browscap' ] == 'no' && wp_slimstat::$settings[ 'enable_browscap' ] == 'on' ) {
705
  WP_Filesystem();
706
 
707
+ if ( $GLOBALS[ 'wp_filesystem' ]->rmdir( wp_slimstat::$upload_dir . '/browscap-cache-master/', true ) ) {
708
  $save_messages[] = __( 'The Browscap data file has been uninstalled from your server.', 'wp-slimstat' );
709
  wp_slimstat::$settings[ 'enable_browscap' ] = 'no';
710
  }
754
  }
755
  }
756
 
757
+ $index_enabled = wp_slimstat::$wpdb->get_results(
 
 
 
 
 
758
  "SHOW INDEX FROM {$GLOBALS[ 'wpdb' ]->prefix}slim_stats WHERE Key_name = '{$GLOBALS[ 'wpdb' ]->prefix}stats_resource_idx'"
759
  );
760
 
914
  </form>
915
 
916
  <?php endif ?>
917
+ </div>
admin/index.php CHANGED
@@ -18,7 +18,7 @@ class wp_slimstat_admin {
18
  * Init -- Sets things up.
19
  */
20
  public static function init() {
21
- self::$admin_notice = "Just a quick reminder that, in our quest for improved performance, we are deprecating the two columns <em>type</em> and <em>event_description</em> in the events table, and consolidating that information in the <em>notes</em> field. Code will be added to Slimstat in a few released to actually drop these columns from the database. If you are using those two columns in your custom code, please feel free to contact our support team to discuss your options and how to update your code using the information collected by the new tracker.";
22
  // self::$admin_notice = "In this day and age where every single social media platform knows our individual whereabouts on the Interwebs, we have been doing some research on what <em>the techies</em> out there call <a href='https://amiunique.org/fp' target='_blank'>browser fingerprinting</a>. With this technique, it is not necessary to rely on cookies to identify a specific user. This version of Slimstat implements <a href='https://github.com/Valve/fingerprintjs2' target='_blank'>FingerprintJS2</a>, a library that enables our tracker to record your users' unique fingerprint and local timezone (isn't it nice to know what time it was for the user when s/he was visiting your website?) Of course, if you have Privacy Mode enabled, this feature will not be used, in compliance with GDPR and other international privacy laws. Your visitors' fingerprints are now available in the Access Log and in the Filter dropdown. In the next few months, we plan to introduce new reports and to leverage this new information to increase the plugin's overall accuracy.";
23
 
24
  // Load language files
@@ -734,7 +734,10 @@ class wp_slimstat_admin {
734
  self::$data_for_column[ 'sql' ][ $a_post->ID ] = self::$data_for_column[ 'url' ][ $a_post->ID ] . '%';
735
  }
736
 
737
- if ( empty( self::$data_for_column ) ) {
 
 
 
738
  return 0;
739
  }
740
 
18
  * Init -- Sets things up.
19
  */
20
  public static function init() {
21
+ self::$admin_notice = "I would like to thank you all for not losing faith in Slimstat. It's been an interesting couple of years, don't you think? Much has changed since the last update was released, but my passion for analytics hasn't. That's why I have decided to slowly reboot this project, which by the way is celebrating 16 years in a few days! I'm working on consolidating the list of available add-ons into just two or three packages. Sales are currently on hold, so if you need to purchase an add-on now, feel free to reach out through our <a href='https://www.wp-slimstat.com' target='_blank'>revamped website</a>. It's good to be back.<br><br>&mdash; Cheers, Jason";
22
  // self::$admin_notice = "In this day and age where every single social media platform knows our individual whereabouts on the Interwebs, we have been doing some research on what <em>the techies</em> out there call <a href='https://amiunique.org/fp' target='_blank'>browser fingerprinting</a>. With this technique, it is not necessary to rely on cookies to identify a specific user. This version of Slimstat implements <a href='https://github.com/Valve/fingerprintjs2' target='_blank'>FingerprintJS2</a>, a library that enables our tracker to record your users' unique fingerprint and local timezone (isn't it nice to know what time it was for the user when s/he was visiting your website?) Of course, if you have Privacy Mode enabled, this feature will not be used, in compliance with GDPR and other international privacy laws. Your visitors' fingerprints are now available in the Access Log and in the Filter dropdown. In the next few months, we plan to introduce new reports and to leverage this new information to increase the plugin's overall accuracy.";
23
 
24
  // Load language files
734
  self::$data_for_column[ 'sql' ][ $a_post->ID ] = self::$data_for_column[ 'url' ][ $a_post->ID ] . '%';
735
  }
736
 
737
+ /**
738
+ * https://wordpress.org/support/topic/you-have-an-error-in-your-sql-syntax-22/#post-12565619
739
+ */
740
+ if ( empty( self::$data_for_column ) || empty( self::$data_for_column[ 'url' ] ) ) {
741
  return 0;
742
  }
743
 
admin/view/index.php CHANGED
@@ -128,7 +128,7 @@
128
  wp_slimstat_admin::show_message( sprintf( __( "<a href='%s' class='noslimstat'>Install MaxMind's GeoLite DB</a> to identify your visitors' country of origin.", 'wp-slimstat' ), self::$config_url . '6#wp-slimstat-third-party-libraries' ), 'warning', 'geolite' );
129
  }
130
 
131
- if ( version_compare( PHP_VERSION, '7.1', '>=' ) && !file_exists( slim_browser::$browscap_autoload_path ) && wp_slimstat::$settings[ 'notice_browscap' ] == 'on' ) {
132
  wp_slimstat_admin::show_message( sprintf( __( "Install our <a href='%s' class='noslimstat'>Browscap Library</a> to identify your visitors' browser and operating system.", 'wp-slimstat' ), self::$config_url . '6#wp-slimstat-third-party-libraries' ), 'warning', 'browscap' );
133
  }
134
 
128
  wp_slimstat_admin::show_message( sprintf( __( "<a href='%s' class='noslimstat'>Install MaxMind's GeoLite DB</a> to identify your visitors' country of origin.", 'wp-slimstat' ), self::$config_url . '6#wp-slimstat-third-party-libraries' ), 'warning', 'geolite' );
129
  }
130
 
131
+ if ( version_compare( PHP_VERSION, '7.1', '>=' ) && !file_exists( wp_slimstat::$upload_dir . '/browscap-cache-master/version.txt' ) && wp_slimstat::$settings[ 'notice_browscap' ] == 'on' ) {
132
  wp_slimstat_admin::show_message( sprintf( __( "Install our <a href='%s' class='noslimstat'>Browscap Library</a> to identify your visitors' browser and operating system.", 'wp-slimstat' ), self::$config_url . '6#wp-slimstat-third-party-libraries' ), 'warning', 'browscap' );
133
  }
134
 
admin/view/wp-slimstat-db.php CHANGED
@@ -919,7 +919,7 @@ class wp_slimstat_db {
919
 
920
  public static function get_recent_events() {
921
  return self::get_results( "
922
- SELECT te.*, t1.resource
923
  FROM {$GLOBALS[ 'wpdb' ]->prefix}slim_events te INNER JOIN {$GLOBALS[ 'wpdb' ]->prefix}slim_stats t1 ON te.id = t1.id
924
  WHERE " . wp_slimstat_db::get_combined_where( 'te.notes NOT LIKE "_ype:click%"', 'te.notes', true, 't1' ) . "
925
  ORDER BY te.dt DESC",
919
 
920
  public static function get_recent_events() {
921
  return self::get_results( "
922
+ SELECT te.*, t1.ip, t1.resource
923
  FROM {$GLOBALS[ 'wpdb' ]->prefix}slim_events te INNER JOIN {$GLOBALS[ 'wpdb' ]->prefix}slim_stats t1 ON te.id = t1.id
924
  WHERE " . wp_slimstat_db::get_combined_where( 'te.notes NOT LIKE "_ype:click%"', 'te.notes', true, 't1' ) . "
925
  ORDER BY te.dt DESC",
admin/view/wp-slimstat-reports.php CHANGED
@@ -1364,7 +1364,7 @@ class wp_slimstat_reports {
1364
 
1365
  if ( !empty( $a_result[ 'dt' ] ) ) {
1366
  $date_time = date_i18n( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $a_result[ 'dt' ], true );
1367
- echo '<b class="slimstat-tooltip-content">' . __( 'Page', 'wp-slimstat' ) . ": <a href='{$blog_url}{$a_result[ 'resource' ]}'>{$blog_url}{$a_result[ 'resource' ]}</a><br>" . __( 'Coordinates', 'wp-slimstat' ) . ": {$a_result[ 'position' ]}<br>" . __( 'Date', 'wp-slimstat' ) . ": $date_time";
1368
  }
1369
 
1370
  echo "</b></p>";
1364
 
1365
  if ( !empty( $a_result[ 'dt' ] ) ) {
1366
  $date_time = date_i18n( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $a_result[ 'dt' ], true );
1367
+ echo '<b class="slimstat-tooltip-content">' . __( 'IP', 'wp-slimstat' ) . ": " . $a_result[ 'ip' ] . "<br/>" . __( 'Page', 'wp-slimstat' ) . ": <a href='{$blog_url}{$a_result[ 'resource' ]}'>{$blog_url}{$a_result[ 'resource' ]}</a><br>" . __( 'Coordinates', 'wp-slimstat' ) . ": {$a_result[ 'position' ]}<br>" . __( 'Date', 'wp-slimstat' ) . ": $date_time";
1368
  }
1369
 
1370
  echo "</b></p>";
readme.txt CHANGED
@@ -1,12 +1,12 @@
1
  === Slimstat Analytics ===
2
- Contributors: coolmann
3
  Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=BNJR5EZNY3W38
4
  Tags: analytics, statistics, counter, tracking, reports, wassup, geolocation, online users, spider, tracker, pageviews, stats, maxmind, statistics, statpress, power stats, hit
5
  Text Domain: wp-slimstat
6
  Requires at least: 4.9
7
- Requires PHP: 5.2
8
- Tested up to: 5.3
9
- Stable tag: 4.8.8.1
10
 
11
  == Description ==
12
  The leading web analytics plugin for WordPress. Track returning customers and registered users, monitor Javascript events, detect intrusions, analyze email campaigns. Thousands of WordPress sites are already using it.
@@ -31,8 +31,8 @@ is for those who would like to donate money - be it once, be it regularly, be it
31
  Try it out, you'll be amazed how good it feels! If you're on a tight budget, and coding is not your thing, please consider writing [a review](https://wordpress.org/support/plugin/wp-slimstat/reviews/#new-post) for Slimstat as a token of appreciation for our hard work!
32
 
33
  = Requirements =
34
- * WordPress 4.9+
35
- * PHP 5.2+ (or 7.1+ if you use the Browscap data file)
36
  * MySQL 5.0.3+
37
  * At least 40 MB of free web space
38
  * At least 5 MB of free DB space
@@ -60,6 +60,16 @@ Our knowledge base is available on our [support center](https://docs.wp-slimstat
60
  5. **Responsive layout** - Keep an eye on your reports on the go
61
 
62
  == Changelog ==
 
 
 
 
 
 
 
 
 
 
63
  = 4.8.8.1 =
64
  * [Update] The Privacy Mode option under Slimstat > Settings > Tracker now controls the fingerprint collection mechanism as well. If you have this option enabled to comply with European privacy laws, your visitors' IP addresses will be masked and they won't be fingerprinted (thank you, Peter).
65
  * [Update] Improved handling of our local DNS cache to store hostnames when the option to convert IP addresses is enabled in the settings.
@@ -130,91 +140,3 @@ Our knowledge base is available on our [support center](https://docs.wp-slimstat
130
  * [Update] Decrease the number of database requests needed to record a new pageview.
131
  * [Fix] Entries with a trailing slash and ones without were being listed as separate in Top Web Pages.
132
  * [Fix] Typo in one of the conditions definining the Top Bots report.
133
-
134
- = 4.8.5.1 =
135
- * [Fix] A bug was affecting the way shortcodes were being displayed on the website (thank you, [inndesign](https://wordpress.org/support/topic/crashes-avada-theme-in-chrome/)).
136
- * [Fix] Some icons in the Access Log were broken and not displayed as expected.
137
- * [Fix] Added extra code to make sure a callback function is defined for any given report.
138
- * [Fix] Top reports where displaying an incorrect percentage value on the WordPress dashboard (thank you, [scruffy1 and a305587](https://wordpress.org/support/topic/dashboard-widgets-showing-0)).
139
-
140
- = 4.8.5 =
141
- * [New] Introduced option to not track pageviews based on the ACCEPT-LANGUAGE header sent by the browser.
142
- * [New] Introduced option to display a pageview count instead of the percentage in Top reports.
143
- * [New] Introduced two new reports under the Audience tab: Tob Bots and Top Human Browsers.
144
- * [Update] Removed the option to hide reports on tabs, as it was confusing users who couldn't find them. Now you can simply use the Customizer to arrange your reports, and place the ones you don't need in the Inactive box.
145
- * [Update] Rewritten the code that manages which reports are displayed on which screen (Customizer), streamlined data structures and optimized their use. Please update all the add-ons to the latest version available. Don't hesitate to contact us if you have any questions!
146
- * [Fix] The HTML markup in the opt-out message field was being stripped out (thank you, [paulmcmanus](https://wordpress.org/support/topic/saving-settings-flips-opt-out-message-to-plain-text/)).
147
- * [Fix] Reports could not be properly deleted in the Customizer, if the Slimstat menu was displayed in the Admin Bar.
148
- * [Fix] A fatal error thrown by the Maxmind library when the data file is corrupted has been addressed.
149
- * [Fix] The icon filename for Windows 8.1 was incorrect (thank you, Dimitri).
150
-
151
- = 4.8.4.1 =
152
- * [Note] As anticipated a few weeks ago, this update drops the information about your visitors' browser plugins, which had been deprecated as not useful and oftentimes unreliable. Please make sure to backup your Slimstat tables if you need to preserve this information for some reason.
153
- * [Update] We received quite a few messages complaining about our decision to change the default position of the Slimstat menu from the sidebar to the admin bar. We are rolling back this change, and we apologize for any confusion this might have caused.
154
- * [Update] Added visitor's language to the Activity Log report.
155
- * [Update] Introduced code optimizations to improve performance when localizing strings related to operating systems, languages, countries, etc.
156
- * [Fix] Page URLs were not being displayed correctly if the option to display page titles was turned off.
157
- * [Fix] Not storing empty values in the database: leave as NULL. This will squeeze a few more bytes out of each row stored in the database.
158
-
159
- = 4.8.4 =
160
- * [Note] If you're using any of our premium add-ons, please make sure to update them to the latest version available (see Slimstat > Add-ons) as we've updated some references in our code.
161
- * [Note] We recently received an email from one of our users suggesting that we replace the line charts currently used to display reports over a timeline with **bar charts**, because 'the number of pageviews and IPs are discrete numbers, hence they should also be presented as discrete numbers', according to him. What do you think? Please let us know by [sending us a message](https://support.wp-slimstat.com/) on our support platform. Thank you.
162
- * [Update] Renamed a few files in the admin. If you're including Slimstat libraries in your custom code, please make sure to check that your references are up-to-date. Also, make sure to clear your cache if you page layout doesn't look right.
163
- * [Update] [AmCharts](https://www.amcharts.com/javascript-charts/), the library used to render all of our charts, has been updated to version 4.5.3.
164
- * [Update] When functioning in Client mode, the tracker will now not ignore bots, spiders and the like automatically. Please use the appropriate option under Settings > Exclusions if you would like to ignore bots. This solves an incompatibility issue with some caching plugins which "prefetch" the website, presenting themselves as bots.
165
- * [Update] Removed tracker notice field under Settings > Maintenance as it was confusing many people and generating extra work for our customer service team.
166
- * [Update] Removed option to not track "client properties" like screen resolution, etc. Also, removed option to not honor DNT headers, as we received complaints from privacy activists on this matter.
167
- * [Update] Removed option to change date/time formats and numeric separators: Slimstat will now use the WordPress settings to adjust its behavior.
168
- * [Update] Removed 'About Slimstat' report, given that some of the information in it has been moved to the Settings.
169
- * [Update] Removed unused strings, improved contextual descriptions and applied consistent naming conventions across our codebase (first pass).
170
- * [Update] The Slimstat admin menu is now added to the Admin Bar by default. Please go to Settings > Basic > WordPress Integration and change the corresponding option, if you prefer to use the side menu instead.
171
- * [Update] Enabled code editor in Settings.
172
- * [Update] Implemented a new optimized function to retrieve the post count on the Edit Posts/Pages/CPTs screens. Thank you, Lance.
173
- * [Update] Improved browser detection feature, which will now fallback to the heuristic function if the Browscap data file doesn't contain an exact match for a given browser. This usually happens whenever a new browser version is released, which is not yet included in the data file.
174
- * [Update] Option to track same-domain referrers is now deactivated by default on new installations.
175
- * [Update] Enabled wildcards on the exclusion rule by capability.
176
- * [Update] Improved the overall source code readability score. Now you don't have any other excuses to not contribute to this project!
177
- * [Update] Table indexes are now enabled by default in the database.
178
- * [Update] Added new WordPress filter to the Browscap Library, so that third-party tools can manipulate the data before it's returned to the tracker.
179
- * [Update] Added [nonce](https://wordpress.org/support/article/glossary/#nonce) to Settings page for improved security.
180
-
181
- = 4.8.3 =
182
- * [Note] Thank you for all the great feedback you provided to our unofficial survey about retiring the 'browser plugins' feature. The vast majority of those who replied confirmed what we already thought. Please consider backing up your database if you would like to preserve this information for future analysis. With this update, we removed the portion of code that tracks that information, but kept the existing data untouched. In a couple of releases, code will be added to actually drop this column from the database.
183
- * [New] If English is not your primary languge, Slimstat will now display a notice asking for your help to [translate our plugin](https://translate.wordpress.org/projects/wp-plugins/wp-slimstat/) in your language. Please consider volunteering for this great opportunity to help our community!
184
- * [Update] We are working with the GlotPress community to improve the way Slimstat speaks your language. We had to change the way certain strings are defined in our source code. Please let us know if you notice any unexpected behavior when analyzing languages, countries and operating systems.
185
- * [Update] Removed Facebook rankings metrics, as the API has been deprecated and the new one is not accessible without a private token.
186
- * [Update] MozRank has been deprecated, we have replaced it with the Domain Authority metric.
187
- * [Update] Spring cleaning in the 'admin notices' department: removed some obsolete CSS code, replaced by built-in WP classes and definitions.
188
- * [Fix] Changed the default minimum capability to access the reports from 'activate_plugins' to 'manage_options', so that regular administrators (a.k.a. non-super admins) in a multisite environment can still see their own reports (thank you, [homepageware](https://wordpress.org/support/topic/slimstat-and-multisite/)). This update does not affect existing installations: if you want regular admins to see their own stats, please go to Slimstat > Settings > Access Control and change the values in the corresponding fields.
189
- * [Fix] The autorefresh feature for the Access Log was not working as expected. Thank you to all the users who patiently worked with us to identify the issue.
190
- * [Fix] A conflict between the Async loader and AmCharts 4 was causing the Screen Options tab to not work as expected (thank you, [softfully](https://wordpress.org/support/topic/screen-options-doesnt-open/)).
191
- * [Fix] Removed unused setting 'Expand Reports'
192
-
193
- = 4.8.2 =
194
- * [Note] Our team has been contemplating the idea of deprecating the information collected about your visitors' *browser plugins* (Java, PDF reader, RealView player, Silverlight, etc). In this day and age, where browsers use either built-in functionality to provide those features, or extensions that cannot be tracked for privacy purposes, it feels anachronistic to continue collecting this outdated information. By getting rid of this specific feature, we can streamline our code, improve performance, and reduce the database size. However, we wanted to hear from our users before anything is actually implemented. Please do not hesitate [to let us know](https://support.wp-slimstat.com) if you are using the 'browser plugins' field for your reporting needs.
195
- * [New] Many CRM integration plugins rely mostly on the user emails, not usernames. For this reason, a new email field has been added to the database (thank you, [sandrodz](https://github.com/sandrodz)).
196
- * [Update] Changed the preset intervals in the date filter dropdown so that you can get a day over day comparison (Monday over Monday, etc) for improved accuracy.
197
- * [Update] [AmCharts](https://www.amcharts.com/javascript-charts/), the library used to render all of our charts, has been updated to version 4.4.9.
198
- * [Fix] The countdown timer on the Access Log was not working as expected (thank you, [anniest](https://wordpress.org/support/topic/no-refresh-2/)).
199
- * [Fix] The countdown timer was causing an warning message to appear on other screens.
200
- * [Fix] Minor aesthetic improvements.
201
-
202
- = 4.8.1 =
203
- * [Update] Async mode will now serialize concurrent requests to the backend to optimize performance and reduce server load.
204
- * [Fix] Addressed a remote XSS vulnerability disclosed by Sucuri/GoDaddy.
205
- * [Fix] Charts were displaying the wrong label for certain values (thank you, Alex).
206
-
207
- = 4.8 =
208
- * [Note] Now that we have a cleaner foundation to build on, it's time to start introducing new reports and new ways to segment your audience and the traffic they generate. While our users test the latest changes and updates (to confirm that the foundation is indeed solid and bug-free), we are hard at work implementing the first batch of new reports. Some of them will be made available in the free version, while others will be added to our premium add-on, [User Overview](https://www.wp-slimstat.com/downloads/user-overview/). And we need your help! If you think that a specific report should be added to Slimstat, please do not hesitate to let us know!
209
- * [Note] Worried about the recent [news regarding jQuery vulnerabilities](https://www.zdnet.com/article/popular-jquery-javascript-library-impacted-by-prototype-pollution-flaw/)? Slimstat doesn't use jQuery as a dependency, so you can sleep tight knowing that your website will not be affected.
210
- * [Update] [AmCharts](https://www.amcharts.com/javascript-charts/), the library used to render all of our charts, has been updated to version 4. This new release is not backward compatible, so the code to integrate it with Slimstat had to be completely rewritten. Please let us know if you notice any issues.
211
- * [Update] [Plugin Update Checker](https://github.com/YahnisElsts/plugin-update-checker), the library we use to check if a new version of our premium add-ons is available for download, has been update to version 4.6.
212
- * [Update] If you're using our partner's CDN functionality (JsDelivr) to load the tracker, their link is now always loaded over HTTPS for added security.
213
- * [Update] Switched the Add-on Update checker URL to HTTPS, for added security (thank you, Peter).
214
- * [Update] Changed the protocol of all the URLs used within Slimstat, including references to our documentation, to HTTPS.
215
- * [Update] Added icon to geolocate Originating IP addresses, when detected.
216
- * [Fix] The optout cookie path was not being set correctly (thank you, [ralfkerkhoff](https://wordpress.org/support/topic/opt-out-cookie-per-page/)).
217
- * [Fix] Google seems to be using a new User Agent string for its "mobile" crawler, which was causing Slimstat from incorrectly identifying visits as coming from mobile devices, instead of bots (thank you, Ron).
218
- * [Fix] An error was being returned if SVG elements were using the A tag on a page (thank you, [snaphappyme](https://wordpress.org/support/topic/uncaught-typeerror-all_linksn-href-indexof/)).
219
- * [Fix] A bug was causing Slimstat to incorrectly geolocate visits to websites behind a Cloudflare load balancer. Please update the IP Address Fix add-on as well.
220
- * [Fix] Tweaked the formula to determine your website bounce rate, and updated the associated description to better reflect the underlying calculations.
1
  === Slimstat Analytics ===
2
+ Contributors: coolmann, toxicum
3
  Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=BNJR5EZNY3W38
4
  Tags: analytics, statistics, counter, tracking, reports, wassup, geolocation, online users, spider, tracker, pageviews, stats, maxmind, statistics, statpress, power stats, hit
5
  Text Domain: wp-slimstat
6
  Requires at least: 4.9
7
+ Requires PHP: 7.4+
8
+ Tested up to: 5.9
9
+ Stable tag: 4.9
10
 
11
  == Description ==
12
  The leading web analytics plugin for WordPress. Track returning customers and registered users, monitor Javascript events, detect intrusions, analyze email campaigns. Thousands of WordPress sites are already using it.
31
  Try it out, you'll be amazed how good it feels! If you're on a tight budget, and coding is not your thing, please consider writing [a review](https://wordpress.org/support/plugin/wp-slimstat/reviews/#new-post) for Slimstat as a token of appreciation for our hard work!
32
 
33
  = Requirements =
34
+ * WordPress 5.0+
35
+ * PHP 7.4+
36
  * MySQL 5.0.3+
37
  * At least 40 MB of free web space
38
  * At least 5 MB of free DB space
60
  5. **Responsive layout** - Keep an eye on your reports on the go
61
 
62
  == Changelog ==
63
+ = 4.9 =
64
+ * [New] Browscap Library is now bundled with the main plugin, only definition files are downloaded dynamically.
65
+ * [New] Support MaxMind License Key for GeoLite2 database downloads.
66
+ * [New] Speedup Browscap version check when repository site is down.
67
+ * [New] Delete plugin settings and stats only if explicitly enabled in settings
68
+ * [Fix] Addressed a PHP warning of undefined variable when parsing a query string looking for search term keywords (thank you, [inndesign](https://wordpress.org/support/topic/line-747-and-line-1574-undefined)).
69
+ * [Fix] Fixed SQL error when Events Manager plugin is installed, 'Posts and Pages' is enabled, and no events are existing (thank you, [lwangamaman](https://wordpress.org/support/topic/you-have-an-error-in-your-sql-syntax-22/).
70
+ * [Fix] Starting with 4.9, PHP 7.4+ is required (thank you, [stephanie-mitchell](https://wordpress.org/support/topic/slimstats-4-8-8-1-fails-on-php-5-3-26-but-no-warning-during-installation/).
71
+ * [Fix] Opt-Out cookie does not delete slimstat cookie.
72
+
73
  = 4.8.8.1 =
74
  * [Update] The Privacy Mode option under Slimstat > Settings > Tracker now controls the fingerprint collection mechanism as well. If you have this option enabled to comply with European privacy laws, your visitors' IP addresses will be masked and they won't be fingerprinted (thank you, Peter).
75
  * [Update] Improved handling of our local DNS cache to store hostnames when the option to convert IP addresses is enabled in the settings.
140
  * [Update] Decrease the number of database requests needed to record a new pageview.
141
  * [Fix] Entries with a trailing slash and ones without were being listed as separate in Top Web Pages.
142
  * [Fix] Typo in one of the conditions definining the Top Bots report.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
uninstall.php CHANGED
@@ -5,6 +5,12 @@ if ( !defined( 'WP_UNINSTALL_PLUGIN' ) ) {
5
  }
6
 
7
  $slimstat_options = get_option( 'slimstat_options', array() );
 
 
 
 
 
 
8
  if ( !empty( $slimstat_options[ 'addon_custom_db_dbuser' ] ) && !empty( $slimstat_options[ 'addon_custom_db_dbpass' ] ) && !empty( $slimstat_options[ 'addon_custom_db_dbname' ] ) && !empty($slimstat_options[ 'addon_custom_db_dbhost' ] ) ) {
9
  $slimstat_wpdb = new wpdb( $slimstat_options[ 'addon_custom_db_dbuser' ], $slimstat_options[ 'addon_custom_db_dbpass' ], $slimstat_options[ 'addon_custom_db_dbname' ], $slimstat_options[ 'addon_custom_db_dbhost' ] );
10
  }
5
  }
6
 
7
  $slimstat_options = get_option( 'slimstat_options', array() );
8
+
9
+ if (isset($slimstat_options['delete_data_on_uninstall']) && $slimstat_options['delete_data_on_uninstall'] != 'on'){
10
+ // Do not delete db data and settings
11
+ return;
12
+ }
13
+
14
  if ( !empty( $slimstat_options[ 'addon_custom_db_dbuser' ] ) && !empty( $slimstat_options[ 'addon_custom_db_dbpass' ] ) && !empty( $slimstat_options[ 'addon_custom_db_dbname' ] ) && !empty($slimstat_options[ 'addon_custom_db_dbhost' ] ) ) {
15
  $slimstat_wpdb = new wpdb( $slimstat_options[ 'addon_custom_db_dbuser' ], $slimstat_options[ 'addon_custom_db_dbpass' ], $slimstat_options[ 'addon_custom_db_dbname' ], $slimstat_options[ 'addon_custom_db_dbhost' ] );
16
  }
vendor/browscap-php/LICENSE ADDED
@@ -0,0 +1,674 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ GNU GENERAL PUBLIC LICENSE
2
+ Version 3, 29 June 2007
3
+
4
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
5
+ Everyone is permitted to copy and distribute verbatim copies
6
+ of this license document, but changing it is not allowed.
7
+
8
+ Preamble
9
+
10
+ The GNU General Public License is a free, copyleft license for
11
+ software and other kinds of works.
12
+
13
+ The licenses for most software and other practical works are designed
14
+ to take away your freedom to share and change the works. By contrast,
15
+ the GNU General Public License is intended to guarantee your freedom to
16
+ share and change all versions of a program--to make sure it remains free
17
+ software for all its users. We, the Free Software Foundation, use the
18
+ GNU General Public License for most of our software; it applies also to
19
+ any other work released this way by its authors. You can apply it to
20
+ your programs, too.
21
+
22
+ When we speak of free software, we are referring to freedom, not
23
+ price. Our General Public Licenses are designed to make sure that you
24
+ have the freedom to distribute copies of free software (and charge for
25
+ them if you wish), that you receive source code or can get it if you
26
+ want it, that you can change the software or use pieces of it in new
27
+ free programs, and that you know you can do these things.
28
+
29
+ To protect your rights, we need to prevent others from denying you
30
+ these rights or asking you to surrender the rights. Therefore, you have
31
+ certain responsibilities if you distribute copies of the software, or if
32
+ you modify it: responsibilities to respect the freedom of others.
33
+
34
+ For example, if you distribute copies of such a program, whether
35
+ gratis or for a fee, you must pass on to the recipients the same
36
+ freedoms that you received. You must make sure that they, too, receive
37
+ or can get the source code. And you must show them these terms so they
38
+ know their rights.
39
+
40
+ Developers that use the GNU GPL protect your rights with two steps:
41
+ (1) assert copyright on the software, and (2) offer you this License
42
+ giving you legal permission to copy, distribute and/or modify it.
43
+
44
+ For the developers' and authors' protection, the GPL clearly explains
45
+ that there is no warranty for this free software. For both users' and
46
+ authors' sake, the GPL requires that modified versions be marked as
47
+ changed, so that their problems will not be attributed erroneously to
48
+ authors of previous versions.
49
+
50
+ Some devices are designed to deny users access to install or run
51
+ modified versions of the software inside them, although the manufacturer
52
+ can do so. This is fundamentally incompatible with the aim of
53
+ protecting users' freedom to change the software. The systematic
54
+ pattern of such abuse occurs in the area of products for individuals to
55
+ use, which is precisely where it is most unacceptable. Therefore, we
56
+ have designed this version of the GPL to prohibit the practice for those
57
+ products. If such problems arise substantially in other domains, we
58
+ stand ready to extend this provision to those domains in future versions
59
+ of the GPL, as needed to protect the freedom of users.
60
+
61
+ Finally, every program is threatened constantly by software patents.
62
+ States should not allow patents to restrict development and use of
63
+ software on general-purpose computers, but in those that do, we wish to
64
+ avoid the special danger that patents applied to a free program could
65
+ make it effectively proprietary. To prevent this, the GPL assures that
66
+ patents cannot be used to render the program non-free.
67
+
68
+ The precise terms and conditions for copying, distribution and
69
+ modification follow.
70
+
71
+ TERMS AND CONDITIONS
72
+
73
+ 0. Definitions.
74
+
75
+ "This License" refers to version 3 of the GNU General Public License.
76
+
77
+ "Copyright" also means copyright-like laws that apply to other kinds of
78
+ works, such as semiconductor masks.
79
+
80
+ "The Program" refers to any copyrightable work licensed under this
81
+ License. Each licensee is addressed as "you". "Licensees" and
82
+ "recipients" may be individuals or organizations.
83
+
84
+ To "modify" a work means to copy from or adapt all or part of the work
85
+ in a fashion requiring copyright permission, other than the making of an
86
+ exact copy. The resulting work is called a "modified version" of the
87
+ earlier work or a work "based on" the earlier work.
88
+
89
+ A "covered work" means either the unmodified Program or a work based
90
+ on the Program.
91
+
92
+ To "propagate" a work means to do anything with it that, without
93
+ permission, would make you directly or secondarily liable for
94
+ infringement under applicable copyright law, except executing it on a
95
+ computer or modifying a private copy. Propagation includes copying,
96
+ distribution (with or without modification), making available to the
97
+ public, and in some countries other activities as well.
98
+
99
+ To "convey" a work means any kind of propagation that enables other
100
+ parties to make or receive copies. Mere interaction with a user through
101
+ a computer network, with no transfer of a copy, is not conveying.
102
+
103
+ An interactive user interface displays "Appropriate Legal Notices"
104
+ to the extent that it includes a convenient and prominently visible
105
+ feature that (1) displays an appropriate copyright notice, and (2)
106
+ tells the user that there is no warranty for the work (except to the
107
+ extent that warranties are provided), that licensees may convey the
108
+ work under this License, and how to view a copy of this License. If
109
+ the interface presents a list of user commands or options, such as a
110
+ menu, a prominent item in the list meets this criterion.
111
+
112
+ 1. Source Code.
113
+
114
+ The "source code" for a work means the preferred form of the work
115
+ for making modifications to it. "Object code" means any non-source
116
+ form of a work.
117
+
118
+ A "Standard Interface" means an interface that either is an official
119
+ standard defined by a recognized standards body, or, in the case of
120
+ interfaces specified for a particular programming language, one that
121
+ is widely used among developers working in that language.
122
+
123
+ The "System Libraries" of an executable work include anything, other
124
+ than the work as a whole, that (a) is included in the normal form of
125
+ packaging a Major Component, but which is not part of that Major
126
+ Component, and (b) serves only to enable use of the work with that
127
+ Major Component, or to implement a Standard Interface for which an
128
+ implementation is available to the public in source code form. A
129
+ "Major Component", in this context, means a major essential component
130
+ (kernel, window system, and so on) of the specific operating system
131
+ (if any) on which the executable work runs, or a compiler used to
132
+ produce the work, or an object code interpreter used to run it.
133
+
134
+ The "Corresponding Source" for a work in object code form means all
135
+ the source code needed to generate, install, and (for an executable
136
+ work) run the object code and to modify the work, including scripts to
137
+ control those activities. However, it does not include the work's
138
+ System Libraries, or general-purpose tools or generally available free
139
+ programs which are used unmodified in performing those activities but
140
+ which are not part of the work. For example, Corresponding Source
141
+ includes interface definition files associated with source files for
142
+ the work, and the source code for shared libraries and dynamically
143
+ linked subprograms that the work is specifically designed to require,
144
+ such as by intimate data communication or control flow between those
145
+ subprograms and other parts of the work.
146
+
147
+ The Corresponding Source need not include anything that users
148
+ can regenerate automatically from other parts of the Corresponding
149
+ Source.
150
+
151
+ The Corresponding Source for a work in source code form is that
152
+ same work.
153
+
154
+ 2. Basic Permissions.
155
+
156
+ All rights granted under this License are granted for the term of
157
+ copyright on the Program, and are irrevocable provided the stated
158
+ conditions are met. This License explicitly affirms your unlimited
159
+ permission to run the unmodified Program. The output from running a
160
+ covered work is covered by this License only if the output, given its
161
+ content, constitutes a covered work. This License acknowledges your
162
+ rights of fair use or other equivalent, as provided by copyright law.
163
+
164
+ You may make, run and propagate covered works that you do not
165
+ convey, without conditions so long as your license otherwise remains
166
+ in force. You may convey covered works to others for the sole purpose
167
+ of having them make modifications exclusively for you, or provide you
168
+ with facilities for running those works, provided that you comply with
169
+ the terms of this License in conveying all material for which you do
170
+ not control copyright. Those thus making or running the covered works
171
+ for you must do so exclusively on your behalf, under your direction
172
+ and control, on terms that prohibit them from making any copies of
173
+ your copyrighted material outside their relationship with you.
174
+
175
+ Conveying under any other circumstances is permitted solely under
176
+ the conditions stated below. Sublicensing is not allowed; section 10
177
+ makes it unnecessary.
178
+
179
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180
+
181
+ No covered work shall be deemed part of an effective technological
182
+ measure under any applicable law fulfilling obligations under article
183
+ 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184
+ similar laws prohibiting or restricting circumvention of such
185
+ measures.
186
+
187
+ When you convey a covered work, you waive any legal power to forbid
188
+ circumvention of technological measures to the extent such circumvention
189
+ is effected by exercising rights under this License with respect to
190
+ the covered work, and you disclaim any intention to limit operation or
191
+ modification of the work as a means of enforcing, against the work's
192
+ users, your or third parties' legal rights to forbid circumvention of
193
+ technological measures.
194
+
195
+ 4. Conveying Verbatim Copies.
196
+
197
+ You may convey verbatim copies of the Program's source code as you
198
+ receive it, in any medium, provided that you conspicuously and
199
+ appropriately publish on each copy an appropriate copyright notice;
200
+ keep intact all notices stating that this License and any
201
+ non-permissive terms added in accord with section 7 apply to the code;
202
+ keep intact all notices of the absence of any warranty; and give all
203
+ recipients a copy of this License along with the Program.
204
+
205
+ You may charge any price or no price for each copy that you convey,
206
+ and you may offer support or warranty protection for a fee.
207
+
208
+ 5. Conveying Modified Source Versions.
209
+
210
+ You may convey a work based on the Program, or the modifications to
211
+ produce it from the Program, in the form of source code under the
212
+ terms of section 4, provided that you also meet all of these conditions:
213
+
214
+ a) The work must carry prominent notices stating that you modified
215
+ it, and giving a relevant date.
216
+
217
+ b) The work must carry prominent notices stating that it is
218
+ released under this License and any conditions added under section
219
+ 7. This requirement modifies the requirement in section 4 to
220
+ "keep intact all notices".
221
+
222
+ c) You must license the entire work, as a whole, under this
223
+ License to anyone who comes into possession of a copy. This
224
+ License will therefore apply, along with any applicable section 7
225
+ additional terms, to the whole of the work, and all its parts,
226
+ regardless of how they are packaged. This License gives no
227
+ permission to license the work in any other way, but it does not
228
+ invalidate such permission if you have separately received it.
229
+
230
+ d) If the work has interactive user interfaces, each must display
231
+ Appropriate Legal Notices; however, if the Program has interactive
232
+ interfaces that do not display Appropriate Legal Notices, your
233
+ work need not make them do so.
234
+
235
+ A compilation of a covered work with other separate and independent
236
+ works, which are not by their nature extensions of the covered work,
237
+ and which are not combined with it such as to form a larger program,
238
+ in or on a volume of a storage or distribution medium, is called an
239
+ "aggregate" if the compilation and its resulting copyright are not
240
+ used to limit the access or legal rights of the compilation's users
241
+ beyond what the individual works permit. Inclusion of a covered work
242
+ in an aggregate does not cause this License to apply to the other
243
+ parts of the aggregate.
244
+
245
+ 6. Conveying Non-Source Forms.
246
+
247
+ You may convey a covered work in object code form under the terms
248
+ of sections 4 and 5, provided that you also convey the
249
+ machine-readable Corresponding Source under the terms of this License,
250
+ in one of these ways:
251
+
252
+ a) Convey the object code in, or embodied in, a physical product
253
+ (including a physical distribution medium), accompanied by the
254
+ Corresponding Source fixed on a durable physical medium
255
+ customarily used for software interchange.
256
+
257
+ b) Convey the object code in, or embodied in, a physical product
258
+ (including a physical distribution medium), accompanied by a
259
+ written offer, valid for at least three years and valid for as
260
+ long as you offer spare parts or customer support for that product
261
+ model, to give anyone who possesses the object code either (1) a
262
+ copy of the Corresponding Source for all the software in the
263
+ product that is covered by this License, on a durable physical
264
+ medium customarily used for software interchange, for a price no
265
+ more than your reasonable cost of physically performing this
266
+ conveying of source, or (2) access to copy the
267
+ Corresponding Source from a network server at no charge.
268
+
269
+ c) Convey individual copies of the object code with a copy of the
270
+ written offer to provide the Corresponding Source. This
271
+ alternative is allowed only occasionally and noncommercially, and
272
+ only if you received the object code with such an offer, in accord
273
+ with subsection 6b.
274
+
275
+ d) Convey the object code by offering access from a designated
276
+ place (gratis or for a charge), and offer equivalent access to the
277
+ Corresponding Source in the same way through the same place at no
278
+ further charge. You need not require recipients to copy the
279
+ Corresponding Source along with the object code. If the place to
280
+ copy the object code is a network server, the Corresponding Source
281
+ may be on a different server (operated by you or a third party)
282
+ that supports equivalent copying facilities, provided you maintain
283
+ clear directions next to the object code saying where to find the
284
+ Corresponding Source. Regardless of what server hosts the
285
+ Corresponding Source, you remain obligated to ensure that it is
286
+ available for as long as needed to satisfy these requirements.
287
+
288
+ e) Convey the object code using peer-to-peer transmission, provided
289
+ you inform other peers where the object code and Corresponding
290
+ Source of the work are being offered to the general public at no
291
+ charge under subsection 6d.
292
+
293
+ A separable portion of the object code, whose source code is excluded
294
+ from the Corresponding Source as a System Library, need not be
295
+ included in conveying the object code work.
296
+
297
+ A "User Product" is either (1) a "consumer product", which means any
298
+ tangible personal property which is normally used for personal, family,
299
+ or household purposes, or (2) anything designed or sold for incorporation
300
+ into a dwelling. In determining whether a product is a consumer product,
301
+ doubtful cases shall be resolved in favor of coverage. For a particular
302
+ product received by a particular user, "normally used" refers to a
303
+ typical or common use of that class of product, regardless of the status
304
+ of the particular user or of the way in which the particular user
305
+ actually uses, or expects or is expected to use, the product. A product
306
+ is a consumer product regardless of whether the product has substantial
307
+ commercial, industrial or non-consumer uses, unless such uses represent
308
+ the only significant mode of use of the product.
309
+
310
+ "Installation Information" for a User Product means any methods,
311
+ procedures, authorization keys, or other information required to install
312
+ and execute modified versions of a covered work in that User Product from
313
+ a modified version of its Corresponding Source. The information must
314
+ suffice to ensure that the continued functioning of the modified object
315
+ code is in no case prevented or interfered with solely because
316
+ modification has been made.
317
+
318
+ If you convey an object code work under this section in, or with, or
319
+ specifically for use in, a User Product, and the conveying occurs as
320
+ part of a transaction in which the right of possession and use of the
321
+ User Product is transferred to the recipient in perpetuity or for a
322
+ fixed term (regardless of how the transaction is characterized), the
323
+ Corresponding Source conveyed under this section must be accompanied
324
+ by the Installation Information. But this requirement does not apply
325
+ if neither you nor any third party retains the ability to install
326
+ modified object code on the User Product (for example, the work has
327
+ been installed in ROM).
328
+
329
+ The requirement to provide Installation Information does not include a
330
+ requirement to continue to provide support service, warranty, or updates
331
+ for a work that has been modified or installed by the recipient, or for
332
+ the User Product in which it has been modified or installed. Access to a
333
+ network may be denied when the modification itself materially and
334
+ adversely affects the operation of the network or violates the rules and
335
+ protocols for communication across the network.
336
+
337
+ Corresponding Source conveyed, and Installation Information provided,
338
+ in accord with this section must be in a format that is publicly
339
+ documented (and with an implementation available to the public in
340
+ source code form), and must require no special password or key for
341
+ unpacking, reading or copying.
342
+
343
+ 7. Additional Terms.
344
+
345
+ "Additional permissions" are terms that supplement the terms of this
346
+ License by making exceptions from one or more of its conditions.
347
+ Additional permissions that are applicable to the entire Program shall
348
+ be treated as though they were included in this License, to the extent
349
+ that they are valid under applicable law. If additional permissions
350
+ apply only to part of the Program, that part may be used separately
351
+ under those permissions, but the entire Program remains governed by
352
+ this License without regard to the additional permissions.
353
+
354
+ When you convey a copy of a covered work, you may at your option
355
+ remove any additional permissions from that copy, or from any part of
356
+ it. (Additional permissions may be written to require their own
357
+ removal in certain cases when you modify the work.) You may place
358
+ additional permissions on material, added by you to a covered work,
359
+ for which you have or can give appropriate copyright permission.
360
+
361
+ Notwithstanding any other provision of this License, for material you
362
+ add to a covered work, you may (if authorized by the copyright holders of
363
+ that material) supplement the terms of this License with terms:
364
+
365
+ a) Disclaiming warranty or limiting liability differently from the
366
+ terms of sections 15 and 16 of this License; or
367
+
368
+ b) Requiring preservation of specified reasonable legal notices or
369
+ author attributions in that material or in the Appropriate Legal
370
+ Notices displayed by works containing it; or
371
+
372
+ c) Prohibiting misrepresentation of the origin of that material, or
373
+ requiring that modified versions of such material be marked in
374
+ reasonable ways as different from the original version; or
375
+
376
+ d) Limiting the use for publicity purposes of names of licensors or
377
+ authors of the material; or
378
+
379
+ e) Declining to grant rights under trademark law for use of some
380
+ trade names, trademarks, or service marks; or
381
+
382
+ f) Requiring indemnification of licensors and authors of that
383
+ material by anyone who conveys the material (or modified versions of
384
+ it) with contractual assumptions of liability to the recipient, for
385
+ any liability that these contractual assumptions directly impose on
386
+ those licensors and authors.
387
+
388
+ All other non-permissive additional terms are considered "further
389
+ restrictions" within the meaning of section 10. If the Program as you
390
+ received it, or any part of it, contains a notice stating that it is
391
+ governed by this License along with a term that is a further
392
+ restriction, you may remove that term. If a license document contains
393
+ a further restriction but permits relicensing or conveying under this
394
+ License, you may add to a covered work material governed by the terms
395
+ of that license document, provided that the further restriction does
396
+ not survive such relicensing or conveying.
397
+
398
+ If you add terms to a covered work in accord with this section, you
399
+ must place, in the relevant source files, a statement of the
400
+ additional terms that apply to those files, or a notice indicating
401
+ where to find the applicable terms.
402
+
403
+ Additional terms, permissive or non-permissive, may be stated in the
404
+ form of a separately written license, or stated as exceptions;
405
+ the above requirements apply either way.
406
+
407
+ 8. Termination.
408
+
409
+ You may not propagate or modify a covered work except as expressly
410
+ provided under this License. Any attempt otherwise to propagate or
411
+ modify it is void, and will automatically terminate your rights under
412
+ this License (including any patent licenses granted under the third
413
+ paragraph of section 11).
414
+
415
+ However, if you cease all violation of this License, then your
416
+ license from a particular copyright holder is reinstated (a)
417
+ provisionally, unless and until the copyright holder explicitly and
418
+ finally terminates your license, and (b) permanently, if the copyright
419
+ holder fails to notify you of the violation by some reasonable means
420
+ prior to 60 days after the cessation.
421
+
422
+ Moreover, your license from a particular copyright holder is
423
+ reinstated permanently if the copyright holder notifies you of the
424
+ violation by some reasonable means, this is the first time you have
425
+ received notice of violation of this License (for any work) from that
426
+ copyright holder, and you cure the violation prior to 30 days after
427
+ your receipt of the notice.
428
+
429
+ Termination of your rights under this section does not terminate the
430
+ licenses of parties who have received copies or rights from you under
431
+ this License. If your rights have been terminated and not permanently
432
+ reinstated, you do not qualify to receive new licenses for the same
433
+ material under section 10.
434
+
435
+ 9. Acceptance Not Required for Having Copies.
436
+
437
+ You are not required to accept this License in order to receive or
438
+ run a copy of the Program. Ancillary propagation of a covered work
439
+ occurring solely as a consequence of using peer-to-peer transmission
440
+ to receive a copy likewise does not require acceptance. However,
441
+ nothing other than this License grants you permission to propagate or
442
+ modify any covered work. These actions infringe copyright if you do
443
+ not accept this License. Therefore, by modifying or propagating a
444
+ covered work, you indicate your acceptance of this License to do so.
445
+
446
+ 10. Automatic Licensing of Downstream Recipients.
447
+
448
+ Each time you convey a covered work, the recipient automatically
449
+ receives a license from the original licensors, to run, modify and
450
+ propagate that work, subject to this License. You are not responsible
451
+ for enforcing compliance by third parties with this License.
452
+
453
+ An "entity transaction" is a transaction transferring control of an
454
+ organization, or substantially all assets of one, or subdividing an
455
+ organization, or merging organizations. If propagation of a covered
456
+ work results from an entity transaction, each party to that
457
+ transaction who receives a copy of the work also receives whatever
458
+ licenses to the work the party's predecessor in interest had or could
459
+ give under the previous paragraph, plus a right to possession of the
460
+ Corresponding Source of the work from the predecessor in interest, if
461
+ the predecessor has it or can get it with reasonable efforts.
462
+
463
+ You may not impose any further restrictions on the exercise of the
464
+ rights granted or affirmed under this License. For example, you may
465
+ not impose a license fee, royalty, or other charge for exercise of
466
+ rights granted under this License, and you may not initiate litigation
467
+ (including a cross-claim or counterclaim in a lawsuit) alleging that
468
+ any patent claim is infringed by making, using, selling, offering for
469
+ sale, or importing the Program or any portion of it.
470
+
471
+ 11. Patents.
472
+
473
+ A "contributor" is a copyright holder who authorizes use under this
474
+ License of the Program or a work on which the Program is based. The
475
+ work thus licensed is called the contributor's "contributor version".
476
+
477
+ A contributor's "essential patent claims" are all patent claims
478
+ owned or controlled by the contributor, whether already acquired or
479
+ hereafter acquired, that would be infringed by some manner, permitted
480
+ by this License, of making, using, or selling its contributor version,
481
+ but do not include claims that would be infringed only as a
482
+ consequence of further modification of the contributor version. For
483
+ purposes of this definition, "control" includes the right to grant
484
+ patent sublicenses in a manner consistent with the requirements of
485
+ this License.
486
+
487
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
488
+ patent license under the contributor's essential patent claims, to
489
+ make, use, sell, offer for sale, import and otherwise run, modify and
490
+ propagate the contents of its contributor version.
491
+
492
+ In the following three paragraphs, a "patent license" is any express
493
+ agreement or commitment, however denominated, not to enforce a patent
494
+ (such as an express permission to practice a patent or covenant not to
495
+ sue for patent infringement). To "grant" such a patent license to a
496
+ party means to make such an agreement or commitment not to enforce a
497
+ patent against the party.
498
+
499
+ If you convey a covered work, knowingly relying on a patent license,
500
+ and the Corresponding Source of the work is not available for anyone
501
+ to copy, free of charge and under the terms of this License, through a
502
+ publicly available network server or other readily accessible means,
503
+ then you must either (1) cause the Corresponding Source to be so
504
+ available, or (2) arrange to deprive yourself of the benefit of the
505
+ patent license for this particular work, or (3) arrange, in a manner
506
+ consistent with the requirements of this License, to extend the patent
507
+ license to downstream recipients. "Knowingly relying" means you have
508
+ actual knowledge that, but for the patent license, your conveying the
509
+ covered work in a country, or your recipient's use of the covered work
510
+ in a country, would infringe one or more identifiable patents in that
511
+ country that you have reason to believe are valid.
512
+
513
+ If, pursuant to or in connection with a single transaction or
514
+ arrangement, you convey, or propagate by procuring conveyance of, a
515
+ covered work, and grant a patent license to some of the parties
516
+ receiving the covered work authorizing them to use, propagate, modify
517
+ or convey a specific copy of the covered work, then the patent license
518
+ you grant is automatically extended to all recipients of the covered
519
+ work and works based on it.
520
+
521
+ A patent license is "discriminatory" if it does not include within
522
+ the scope of its coverage, prohibits the exercise of, or is
523
+ conditioned on the non-exercise of one or more of the rights that are
524
+ specifically granted under this License. You may not convey a covered
525
+ work if you are a party to an arrangement with a third party that is
526
+ in the business of distributing software, under which you make payment
527
+ to the third party based on the extent of your activity of conveying
528
+ the work, and under which the third party grants, to any of the
529
+ parties who would receive the covered work from you, a discriminatory
530
+ patent license (a) in connection with copies of the covered work
531
+ conveyed by you (or copies made from those copies), or (b) primarily
532
+ for and in connection with specific products or compilations that
533
+ contain the covered work, unless you entered into that arrangement,
534
+ or that patent license was granted, prior to 28 March 2007.
535
+
536
+ Nothing in this License shall be construed as excluding or limiting
537
+ any implied license or other defenses to infringement that may
538
+ otherwise be available to you under applicable patent law.
539
+
540
+ 12. No Surrender of Others' Freedom.
541
+
542
+ If conditions are imposed on you (whether by court order, agreement or
543
+ otherwise) that contradict the conditions of this License, they do not
544
+ excuse you from the conditions of this License. If you cannot convey a
545
+ covered work so as to satisfy simultaneously your obligations under this
546
+ License and any other pertinent obligations, then as a consequence you may
547
+ not convey it at all. For example, if you agree to terms that obligate you
548
+ to collect a royalty for further conveying from those to whom you convey
549
+ the Program, the only way you could satisfy both those terms and this
550
+ License would be to refrain entirely from conveying the Program.
551
+
552
+ 13. Use with the GNU Affero General Public License.
553
+
554
+ Notwithstanding any other provision of this License, you have
555
+ permission to link or combine any covered work with a work licensed
556
+ under version 3 of the GNU Affero General Public License into a single
557
+ combined work, and to convey the resulting work. The terms of this
558
+ License will continue to apply to the part which is the covered work,
559
+ but the special requirements of the GNU Affero General Public License,
560
+ section 13, concerning interaction through a network will apply to the
561
+ combination as such.
562
+
563
+ 14. Revised Versions of this License.
564
+
565
+ The Free Software Foundation may publish revised and/or new versions of
566
+ the GNU General Public License from time to time. Such new versions will
567
+ be similar in spirit to the present version, but may differ in detail to
568
+ address new problems or concerns.
569
+
570
+ Each version is given a distinguishing version number. If the
571
+ Program specifies that a certain numbered version of the GNU General
572
+ Public License "or any later version" applies to it, you have the
573
+ option of following the terms and conditions either of that numbered
574
+ version or of any later version published by the Free Software
575
+ Foundation. If the Program does not specify a version number of the
576
+ GNU General Public License, you may choose any version ever published
577
+ by the Free Software Foundation.
578
+
579
+ If the Program specifies that a proxy can decide which future
580
+ versions of the GNU General Public License can be used, that proxy's
581
+ public statement of acceptance of a version permanently authorizes you
582
+ to choose that version for the Program.
583
+
584
+ Later license versions may give you additional or different
585
+ permissions. However, no additional obligations are imposed on any
586
+ author or copyright holder as a result of your choosing to follow a
587
+ later version.
588
+
589
+ 15. Disclaimer of Warranty.
590
+
591
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592
+ APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593
+ HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594
+ OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595
+ THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596
+ PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597
+ IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598
+ ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599
+
600
+ 16. Limitation of Liability.
601
+
602
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603
+ WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604
+ THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605
+ GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606
+ USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607
+ DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608
+ PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609
+ EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610
+ SUCH DAMAGES.
611
+
612
+ 17. Interpretation of Sections 15 and 16.
613
+
614
+ If the disclaimer of warranty and limitation of liability provided
615
+ above cannot be given local legal effect according to their terms,
616
+ reviewing courts shall apply local law that most closely approximates
617
+ an absolute waiver of all civil liability in connection with the
618
+ Program, unless a warranty or assumption of liability accompanies a
619
+ copy of the Program in return for a fee.
620
+
621
+ END OF TERMS AND CONDITIONS
622
+
623
+ How to Apply These Terms to Your New Programs
624
+
625
+ If you develop a new program, and you want it to be of the greatest
626
+ possible use to the public, the best way to achieve this is to make it
627
+ free software which everyone can redistribute and change under these terms.
628
+
629
+ To do so, attach the following notices to the program. It is safest
630
+ to attach them to the start of each source file to most effectively
631
+ state the exclusion of warranty; and each file should have at least
632
+ the "copyright" line and a pointer to where the full notice is found.
633
+
634
+ {one line to give the program's name and a brief idea of what it does.}
635
+ Copyright (C) {year} {name of author}
636
+
637
+ This program is free software: you can redistribute it and/or modify
638
+ it under the terms of the GNU General Public License as published by
639
+ the Free Software Foundation, either version 3 of the License, or
640
+ (at your option) any later version.
641
+
642
+ This program is distributed in the hope that it will be useful,
643
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
644
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645
+ GNU General Public License for more details.
646
+
647
+ You should have received a copy of the GNU General Public License
648
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
649
+
650
+ Also add information on how to contact you by electronic and paper mail.
651
+
652
+ If the program does terminal interaction, make it output a short
653
+ notice like this when it starts in an interactive mode:
654
+
655
+ {project} Copyright (C) {year} {fullname}
656
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657
+ This is free software, and you are welcome to redistribute it
658
+ under certain conditions; type `show c' for details.
659
+
660
+ The hypothetical commands `show w' and `show c' should show the appropriate
661
+ parts of the General Public License. Of course, your program's commands
662
+ might be different; for a GUI interface, you would use an "about box".
663
+
664
+ You should also get your employer (if you work as a programmer) or school,
665
+ if any, to sign a "copyright disclaimer" for the program, if necessary.
666
+ For more information on this, and how to apply and follow the GNU GPL, see
667
+ <http://www.gnu.org/licenses/>.
668
+
669
+ The GNU General Public License does not permit incorporating your program
670
+ into proprietary programs. If your program is a subroutine library, you
671
+ may consider it more useful to permit linking proprietary applications with
672
+ the library. If this is what you want to do, use the GNU Lesser General
673
+ Public License instead of this License. But first, please read
674
+ <http://www.gnu.org/philosophy/why-not-lgpl.html>.
vendor/browscap-php/browscap/browscap-php/src/Browscap.php ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP;
6
+
7
+ use BrowscapPHP\Cache\BrowscapCache;
8
+ use BrowscapPHP\Cache\BrowscapCacheInterface;
9
+ use BrowscapPHP\Formatter\FormatterInterface;
10
+ use BrowscapPHP\Helper\Quoter;
11
+ use BrowscapPHP\Parser\ParserInterface;
12
+ use Psr\SimpleCache\CacheInterface;
13
+ use stdClass;
14
+ use UnexpectedValueException;
15
+
16
+ use function is_string;
17
+ use function sprintf;
18
+
19
+ /**
20
+ * Browscap.ini parsing class with caching and update capabilities
21
+ */
22
+ final class Browscap implements BrowscapInterface
23
+ {
24
+ /**
25
+ * Parser to use
26
+ */
27
+ private ?ParserInterface $parser = null;
28
+
29
+ /**
30
+ * Formatter to use
31
+ */
32
+ private FormatterInterface $formatter;
33
+
34
+ /**
35
+ * The cache instance
36
+ */
37
+ private BrowscapCacheInterface $cache;
38
+
39
+ /**
40
+ * @throws void
41
+ */
42
+ public function __construct(CacheInterface $cache)
43
+ {
44
+ $this->cache = new BrowscapCache($cache);
45
+
46
+ $this->formatter = new Formatter\PhpGetBrowser();
47
+ }
48
+
49
+ /**
50
+ * Set theformatter instance to use for the getBrowser() result
51
+ *
52
+ * @throws void
53
+ */
54
+ public function setFormatter(Formatter\FormatterInterface $formatter): void
55
+ {
56
+ $this->formatter = $formatter;
57
+ }
58
+
59
+ /**
60
+ * Sets the parser instance to use
61
+ *
62
+ * @throws void
63
+ */
64
+ public function setParser(ParserInterface $parser): void
65
+ {
66
+ $this->parser = $parser;
67
+ }
68
+
69
+ /**
70
+ * returns an instance of the used parser class
71
+ *
72
+ * @throws void
73
+ */
74
+ public function getParser(): ParserInterface
75
+ {
76
+ if ($this->parser === null) {
77
+ $patternHelper = new Parser\Helper\GetPattern($this->cache);
78
+ $dataHelper = new Parser\Helper\GetData($this->cache, new Quoter());
79
+
80
+ $this->parser = new Parser\Ini($patternHelper, $dataHelper, $this->formatter);
81
+ }
82
+
83
+ return $this->parser;
84
+ }
85
+
86
+ /**
87
+ * parses the given user agent to get the information about the browser
88
+ *
89
+ * if no user agent is given, it uses {@see \BrowscapPHP\Helper\Support} to get it
90
+ *
91
+ * @param string $userAgent the user agent string
92
+ *
93
+ * @return stdClass the object containing the browsers details.
94
+ *
95
+ * @throws Exception
96
+ */
97
+ public function getBrowser(?string $userAgent = null): stdClass
98
+ {
99
+ if ($this->cache->getVersion() === null) {
100
+ // there is no active/warm cache available
101
+ throw new Exception('there is no active cache available, please use the BrowscapUpdater and run the update command');
102
+ }
103
+
104
+ // Automatically detect the useragent
105
+ if (! is_string($userAgent)) {
106
+ $support = new Helper\Support($_SERVER);
107
+ $userAgent = $support->getUserAgent();
108
+ }
109
+
110
+ try {
111
+ // try to get browser data
112
+ $formatter = $this->getParser()->getBrowser($userAgent);
113
+ } catch (UnexpectedValueException $e) {
114
+ $formatter = null;
115
+ }
116
+
117
+ // if return is still NULL, updates are disabled... in this
118
+ // case we return an empty formatter instance
119
+ if ($formatter === null) {
120
+ $formatter = $this->formatter;
121
+ }
122
+
123
+ return $formatter->getData();
124
+ }
125
+ }
vendor/browscap-php/browscap/browscap-php/src/BrowscapInterface.php ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP;
6
+
7
+ use BrowscapPHP\Parser\ParserInterface;
8
+ use stdClass;
9
+
10
+ /**
11
+ * Browscap.ini parsing class with caching and update capabilities
12
+ */
13
+ interface BrowscapInterface
14
+ {
15
+ /**
16
+ * Set theformatter instance to use for the getBrowser() result
17
+ *
18
+ * @throws void
19
+ *
20
+ * @no-named-arguments
21
+ */
22
+ public function setFormatter(Formatter\FormatterInterface $formatter): void;
23
+
24
+ /**
25
+ * Sets the parser instance to use
26
+ *
27
+ * @throws void
28
+ *
29
+ * @no-named-arguments
30
+ */
31
+ public function setParser(ParserInterface $parser): void;
32
+
33
+ /**
34
+ * returns an instance of the used parser class
35
+ *
36
+ * @throws void
37
+ *
38
+ * @no-named-arguments
39
+ */
40
+ public function getParser(): ParserInterface;
41
+
42
+ /**
43
+ * parses the given user agent to get the information about the browser
44
+ *
45
+ * if no user agent is given, it uses {@see \BrowscapPHP\Helper\Support} to get it
46
+ *
47
+ * @param string $userAgent the user agent string
48
+ *
49
+ * @return stdClass the object containing the browsers details.
50
+ *
51
+ * @throws Exception
52
+ *
53
+ * @no-named-arguments
54
+ */
55
+ public function getBrowser(?string $userAgent = null): stdClass;
56
+ }
vendor/browscap-php/browscap/browscap-php/src/BrowscapUpdater.php ADDED
@@ -0,0 +1,415 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP;
6
+
7
+ use BrowscapPHP\Cache\BrowscapCache;
8
+ use BrowscapPHP\Cache\BrowscapCacheInterface;
9
+ use BrowscapPHP\Exception\ErrorCachedVersionException;
10
+ use BrowscapPHP\Exception\ErrorReadingFileException;
11
+ use BrowscapPHP\Exception\FetcherException;
12
+ use BrowscapPHP\Exception\FileNameMissingException;
13
+ use BrowscapPHP\Exception\FileNotFoundException;
14
+ use BrowscapPHP\Exception\NoCachedVersionException;
15
+ use BrowscapPHP\Exception\NoNewVersionException;
16
+ use BrowscapPHP\Helper\Converter;
17
+ use BrowscapPHP\Helper\ConverterInterface;
18
+ use BrowscapPHP\Helper\Exception;
19
+ use BrowscapPHP\Helper\Filesystem;
20
+ use BrowscapPHP\Helper\IniLoader;
21
+ use BrowscapPHP\Helper\IniLoaderInterface;
22
+ use GuzzleHttp\Client;
23
+ use GuzzleHttp\ClientInterface;
24
+ use GuzzleHttp\Exception\GuzzleException;
25
+ use Psr\Http\Message\ResponseInterface;
26
+ use Psr\Log\LoggerInterface;
27
+ use Psr\SimpleCache\CacheInterface;
28
+ use Psr\SimpleCache\InvalidArgumentException;
29
+ use Symfony\Component\Filesystem\Exception\IOException;
30
+ use Throwable;
31
+
32
+ use function assert;
33
+ use function error_get_last;
34
+ use function file_get_contents;
35
+ use function is_array;
36
+ use function is_int;
37
+ use function is_readable;
38
+ use function preg_replace;
39
+ use function sprintf;
40
+ use function str_replace;
41
+
42
+ /**
43
+ * Browscap.ini parsing class with caching and update capabilities
44
+ */
45
+ final class BrowscapUpdater implements BrowscapUpdaterInterface
46
+ {
47
+ public const DEFAULT_TIMEOUT = 5;
48
+
49
+ /**
50
+ * The cache instance
51
+ */
52
+ private BrowscapCacheInterface $cache;
53
+
54
+ private LoggerInterface $logger;
55
+
56
+ private ClientInterface $client;
57
+
58
+ /**
59
+ * Curl connect timeout in seconds
60
+ */
61
+ private int $connectTimeout;
62
+
63
+ /**
64
+ * @throws void
65
+ */
66
+ public function __construct(
67
+ CacheInterface $cache,
68
+ LoggerInterface $logger,
69
+ ?ClientInterface $client = null,
70
+ int $connectTimeout = self::DEFAULT_TIMEOUT
71
+ ) {
72
+ $this->cache = new BrowscapCache($cache, $logger);
73
+ $this->logger = $logger;
74
+
75
+ if ($client === null) {
76
+ $client = new Client();
77
+ }
78
+
79
+ $this->client = $client;
80
+ $this->connectTimeout = $connectTimeout;
81
+ }
82
+
83
+ /**
84
+ * reads and parses an ini file and writes the results into the cache
85
+ *
86
+ * @throws FileNameMissingException
87
+ * @throws FileNotFoundException
88
+ * @throws ErrorReadingFileException
89
+ */
90
+ public function convertFile(string $iniFile): void
91
+ {
92
+ if (empty($iniFile)) {
93
+ throw new FileNameMissingException('the file name can not be empty');
94
+ }
95
+
96
+ if (! is_readable($iniFile)) {
97
+ throw new FileNotFoundException(
98
+ sprintf('it was not possible to read the local file %s', $iniFile)
99
+ );
100
+ }
101
+
102
+ $iniString = file_get_contents($iniFile);
103
+
104
+ if ($iniString === false) {
105
+ throw new ErrorReadingFileException('an error occured while converting the local file into the cache');
106
+ }
107
+
108
+ $this->convertString($iniString);
109
+ }
110
+
111
+ /**
112
+ * reads and parses an ini string and writes the results into the cache
113
+ *
114
+ * @throws void
115
+ */
116
+ public function convertString(string $iniString): void
117
+ {
118
+ try {
119
+ $cachedVersion = $this->cache->getItem('browscap.version', false, $success);
120
+ } catch (InvalidArgumentException $e) {
121
+ $this->logger->error(new \InvalidArgumentException('an error occured while reading the data version from the cache', 0, $e));
122
+
123
+ return;
124
+ }
125
+
126
+ assert($cachedVersion === null || is_int($cachedVersion));
127
+
128
+ $converter = new Converter($this->logger, $this->cache);
129
+
130
+ $this->storeContent($converter, $iniString, $cachedVersion);
131
+ }
132
+
133
+ /**
134
+ * fetches a remote file and stores it into a local folder
135
+ *
136
+ * @param string $file The name of the file where to store the remote content
137
+ * @param string $remoteFile The code for the remote file to load
138
+ *
139
+ * @throws FetcherException
140
+ * @throws Exception
141
+ * @throws ErrorCachedVersionException
142
+ */
143
+ public function fetch(string $file, string $remoteFile = IniLoaderInterface::PHP_INI): void
144
+ {
145
+ try {
146
+ $cachedVersion = $this->checkUpdate();
147
+ } catch (NoNewVersionException $e) {
148
+ return;
149
+ } catch (NoCachedVersionException $e) {
150
+ $cachedVersion = 0;
151
+ }
152
+
153
+ $this->logger->debug('started fetching remote file');
154
+
155
+ $loader = new IniLoader();
156
+ $loader->setRemoteFilename($remoteFile);
157
+
158
+ $uri = $loader->getRemoteIniUrl();
159
+
160
+ try {
161
+ $response = $this->client->request('get', $uri, ['connect_timeout' => $this->connectTimeout]);
162
+ assert($response instanceof ResponseInterface);
163
+ } catch (GuzzleException $e) {
164
+ throw new FetcherException(
165
+ sprintf(
166
+ 'an error occured while fetching remote data from URI %s',
167
+ $uri
168
+ ),
169
+ 0,
170
+ $e
171
+ );
172
+ }
173
+
174
+ if ($response->getStatusCode() !== 200) {
175
+ throw new FetcherException(
176
+ sprintf(
177
+ 'an error occured while fetching remote data from URI %s: StatusCode was %d',
178
+ $uri,
179
+ $response->getStatusCode()
180
+ )
181
+ );
182
+ }
183
+
184
+ try {
185
+ $content = $response->getBody()->getContents();
186
+ } catch (Throwable $e) {
187
+ throw new FetcherException('an error occured while fetching remote data', 0, $e);
188
+ }
189
+
190
+ if (empty($content)) {
191
+ $error = error_get_last();
192
+
193
+ if (is_array($error)) {
194
+ throw FetcherException::httpError($uri, $error['message']);
195
+ }
196
+
197
+ throw FetcherException::httpError(
198
+ $uri,
199
+ 'an error occured while fetching remote data, but no error was raised'
200
+ );
201
+ }
202
+
203
+ $this->logger->debug('finished fetching remote file');
204
+ $this->logger->debug('started storing remote file into local file');
205
+
206
+ $content = $this->sanitizeContent($content);
207
+
208
+ $converter = new Converter($this->logger, $this->cache);
209
+ $iniVersion = $converter->getIniVersion($content);
210
+
211
+ if ($iniVersion > $cachedVersion) {
212
+ $fs = new Filesystem();
213
+
214
+ try {
215
+ $fs->dumpFile($file, $content);
216
+ } catch (IOException $exception) {
217
+ throw new FetcherException('an error occured while writing fetched data to local file', 0, $exception);
218
+ }
219
+ }
220
+
221
+ $this->logger->debug('finished storing remote file into local file');
222
+ }
223
+
224
+ /**
225
+ * fetches a remote file, parses it and writes the result into the cache
226
+ * if the local stored information are in the same version as the remote data no actions are
227
+ * taken
228
+ *
229
+ * @param string $remoteFile The code for the remote file to load
230
+ *
231
+ * @throws FetcherException
232
+ * @throws Exception
233
+ * @throws ErrorCachedVersionException
234
+ */
235
+ public function update(string $remoteFile = IniLoaderInterface::PHP_INI): void
236
+ {
237
+ $this->logger->debug('started fetching remote file');
238
+
239
+ try {
240
+ $cachedVersion = $this->checkUpdate();
241
+ } catch (NoNewVersionException $e) {
242
+ return;
243
+ } catch (NoCachedVersionException $e) {
244
+ $cachedVersion = 0;
245
+ }
246
+
247
+ $loader = new IniLoader();
248
+ $loader->setRemoteFilename($remoteFile);
249
+
250
+ $uri = $loader->getRemoteIniUrl();
251
+
252
+ try {
253
+ $response = $this->client->request('get', $uri, ['connect_timeout' => $this->connectTimeout]);
254
+ assert($response instanceof ResponseInterface);
255
+ } catch (GuzzleException $e) {
256
+ throw new FetcherException(
257
+ sprintf(
258
+ 'an error occured while fetching remote data from URI %s',
259
+ $uri
260
+ ),
261
+ 0,
262
+ $e
263
+ );
264
+ }
265
+
266
+ if ($response->getStatusCode() !== 200) {
267
+ throw new FetcherException(
268
+ sprintf(
269
+ 'an error occured while fetching remote data from URI %s: StatusCode was %d',
270
+ $uri,
271
+ $response->getStatusCode()
272
+ )
273
+ );
274
+ }
275
+
276
+ try {
277
+ $content = $response->getBody()->getContents();
278
+ } catch (Throwable $e) {
279
+ throw new FetcherException('an error occured while fetching remote data', 0, $e);
280
+ }
281
+
282
+ if (empty($content)) {
283
+ $error = error_get_last();
284
+
285
+ throw FetcherException::httpError($uri, $error['message'] ?? '');
286
+ }
287
+
288
+ $this->logger->debug('finished fetching remote file');
289
+ $this->logger->debug('started updating cache from remote file');
290
+
291
+ $converter = new Converter($this->logger, $this->cache);
292
+ $this->storeContent($converter, $content, $cachedVersion);
293
+
294
+ $this->logger->debug('finished updating cache from remote file');
295
+ }
296
+
297
+ /**
298
+ * checks if an update on a remote location for the local file or the cache
299
+ *
300
+ * @return int|null The actual cached version if a newer version is available, null otherwise
301
+ *
302
+ * @throws FetcherException
303
+ * @throws NoCachedVersionException
304
+ * @throws ErrorCachedVersionException
305
+ * @throws NoNewVersionException
306
+ */
307
+ public function checkUpdate(): ?int
308
+ {
309
+ $success = null;
310
+
311
+ try {
312
+ $cachedVersion = $this->cache->getItem('browscap.version', false, $success);
313
+ } catch (InvalidArgumentException $e) {
314
+ throw new ErrorCachedVersionException('an error occured while reading the data version from the cache', 0, $e);
315
+ }
316
+
317
+ assert($cachedVersion === null || is_int($cachedVersion));
318
+
319
+ if (! $cachedVersion) {
320
+ // could not load version from cache
321
+ throw new NoCachedVersionException('there is no cached version available, please update from remote');
322
+ }
323
+
324
+ $uri = (new IniLoader())->getRemoteVersionUrl();
325
+
326
+ try {
327
+ $response = $this->client->request('get', $uri, ['connect_timeout' => $this->connectTimeout]);
328
+ assert($response instanceof ResponseInterface);
329
+ } catch (GuzzleException $e) {
330
+ throw new FetcherException(
331
+ sprintf(
332
+ 'an error occured while fetching version data from URI %s',
333
+ $uri
334
+ ),
335
+ 0,
336
+ $e
337
+ );
338
+ }
339
+
340
+ if ($response->getStatusCode() !== 200) {
341
+ throw new FetcherException(
342
+ sprintf(
343
+ 'an error occured while fetching version data from URI %s: StatusCode was %d',
344
+ $uri,
345
+ $response->getStatusCode()
346
+ )
347
+ );
348
+ }
349
+
350
+ try {
351
+ $remoteVersion = $response->getBody()->getContents();
352
+ } catch (Throwable $e) {
353
+ throw new FetcherException(
354
+ sprintf(
355
+ 'an error occured while fetching version data from URI %s: StatusCode was %d',
356
+ $uri,
357
+ $response->getStatusCode()
358
+ ),
359
+ 0,
360
+ $e
361
+ );
362
+ }
363
+
364
+ if (! $remoteVersion) {
365
+ // could not load remote version
366
+ throw new FetcherException(
367
+ 'could not load version from remote location'
368
+ );
369
+ }
370
+
371
+ if ($remoteVersion <= $cachedVersion) {
372
+ throw new NoNewVersionException('there is no newer version available');
373
+ }
374
+
375
+ $this->logger->info(
376
+ sprintf(
377
+ 'a newer version is available, local version: %s, remote version: %s',
378
+ $cachedVersion,
379
+ $remoteVersion
380
+ )
381
+ );
382
+
383
+ return (int) $cachedVersion;
384
+ }
385
+
386
+ /**
387
+ * @throws void
388
+ */
389
+ private function sanitizeContent(string $content): string
390
+ {
391
+ // replace everything between opening and closing php and asp tags
392
+ $content = preg_replace('/<[?%].*[?%]>/', '', $content);
393
+
394
+ // replace opening and closing php and asp tags
395
+ return str_replace(['<?', '<%', '?>', '%>'], '', (string) $content);
396
+ }
397
+
398
+ /**
399
+ * reads and parses an ini string and writes the results into the cache
400
+ *
401
+ * @throws void
402
+ */
403
+ private function storeContent(ConverterInterface $converter, string $content, ?int $cachedVersion): void
404
+ {
405
+ $iniString = $this->sanitizeContent($content);
406
+ $iniVersion = $converter->getIniVersion($iniString);
407
+
408
+ if ($cachedVersion && $iniVersion <= $cachedVersion) {
409
+ return;
410
+ }
411
+
412
+ $converter->storeVersion();
413
+ $converter->convertString($iniString);
414
+ }
415
+ }
vendor/browscap-php/browscap/browscap-php/src/BrowscapUpdaterInterface.php ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP;
6
+
7
+ use BrowscapPHP\Exception\ErrorCachedVersionException;
8
+ use BrowscapPHP\Exception\ErrorReadingFileException;
9
+ use BrowscapPHP\Exception\FetcherException;
10
+ use BrowscapPHP\Exception\FileNameMissingException;
11
+ use BrowscapPHP\Exception\FileNotFoundException;
12
+ use BrowscapPHP\Exception\NoCachedVersionException;
13
+ use BrowscapPHP\Exception\NoNewVersionException;
14
+ use BrowscapPHP\Helper\Exception;
15
+ use BrowscapPHP\Helper\IniLoaderInterface;
16
+ use UnexpectedValueException;
17
+
18
+ /**
19
+ * Browscap.ini parsing class with caching and update capabilities
20
+ */
21
+ interface BrowscapUpdaterInterface
22
+ {
23
+ /**
24
+ * reads and parses an ini file and writes the results into the cache
25
+ *
26
+ * @throws FileNameMissingException
27
+ * @throws FileNotFoundException
28
+ * @throws ErrorReadingFileException
29
+ * @throws UnexpectedValueException
30
+ *
31
+ * @no-named-arguments
32
+ */
33
+ public function convertFile(string $iniFile): void;
34
+
35
+ /**
36
+ * reads and parses an ini string and writes the results into the cache
37
+ *
38
+ * @throws UnexpectedValueException
39
+ *
40
+ * @no-named-arguments
41
+ */
42
+ public function convertString(string $iniString): void;
43
+
44
+ /**
45
+ * fetches a remote file and stores it into a local folder
46
+ *
47
+ * @param string $file The name of the file where to store the remote content
48
+ * @param string $remoteFile The code for the remote file to load
49
+ *
50
+ * @throws FetcherException
51
+ * @throws Exception
52
+ * @throws ErrorCachedVersionException
53
+ *
54
+ * @no-named-arguments
55
+ */
56
+ public function fetch(string $file, string $remoteFile = IniLoaderInterface::PHP_INI): void;
57
+
58
+ /**
59
+ * fetches a remote file, parses it and writes the result into the cache
60
+ *
61
+ * if the local stored information are in the same version as the remote data no actions are
62
+ * taken
63
+ *
64
+ * @param string $remoteFile The code for the remote file to load
65
+ *
66
+ * @throws FetcherException
67
+ * @throws Exception
68
+ * @throws ErrorCachedVersionException
69
+ * @throws UnexpectedValueException
70
+ *
71
+ * @no-named-arguments
72
+ */
73
+ public function update(string $remoteFile = IniLoaderInterface::PHP_INI): void;
74
+
75
+ /**
76
+ * checks if an update on a remote location for the local file or the cache
77
+ *
78
+ * @return int|null The actual cached version if a newer version is available, null otherwise
79
+ *
80
+ * @throws FetcherException
81
+ * @throws NoCachedVersionException
82
+ * @throws ErrorCachedVersionException
83
+ * @throws NoNewVersionException
84
+ *
85
+ * @no-named-arguments
86
+ */
87
+ public function checkUpdate(): ?int;
88
+ }
vendor/browscap-php/browscap/browscap-php/src/Cache/BrowscapCache.php ADDED
@@ -0,0 +1,226 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP\Cache;
6
+
7
+ use Psr\SimpleCache\CacheInterface;
8
+ use Psr\SimpleCache\InvalidArgumentException;
9
+
10
+ use function array_key_exists;
11
+ use function assert;
12
+ use function is_array;
13
+ use function is_int;
14
+ use function is_string;
15
+ use function serialize;
16
+ use function unserialize;
17
+
18
+ /**
19
+ * A cache proxy to be able to use the cache adapters provided by the WurflCache package
20
+ */
21
+ final class BrowscapCache implements BrowscapCacheInterface
22
+ {
23
+ /**
24
+ * Path to the cache directory
25
+ */
26
+ private CacheInterface $cache;
27
+
28
+ /**
29
+ * Detected browscap version (read from INI file)
30
+ */
31
+ private ?int $version = null;
32
+
33
+ /**
34
+ * Release date of the Browscap data (read from INI file)
35
+ */
36
+ private ?string $releaseDate = null;
37
+
38
+ /**
39
+ * Type of the Browscap data (read from INI file)
40
+ */
41
+ private ?string $type = null;
42
+
43
+ /**
44
+ * Constructor class, checks for the existence of (and loads) the cache and
45
+ * if needed updated the definitions
46
+ *
47
+ * @throws void
48
+ */
49
+ public function __construct(CacheInterface $adapter)
50
+ {
51
+ $this->cache = $adapter;
52
+ }
53
+
54
+ /**
55
+ * Gets the version of the Browscap data
56
+ *
57
+ * @throws void
58
+ */
59
+ public function getVersion(): ?int
60
+ {
61
+ if ($this->version === null) {
62
+ $success = null;
63
+
64
+ try {
65
+ $cachedVersion = $this->getItem('browscap.version', false, $success);
66
+ } catch (InvalidArgumentException $e) {
67
+ $cachedVersion = null;
68
+ }
69
+
70
+ assert($cachedVersion === null || is_int($cachedVersion));
71
+
72
+ if ($cachedVersion !== null && $success) {
73
+ $this->version = (int) $cachedVersion;
74
+ }
75
+ }
76
+
77
+ return $this->version;
78
+ }
79
+
80
+ /**
81
+ * Gets the release date of the Browscap data
82
+ *
83
+ * @throws void
84
+ */
85
+ public function getReleaseDate(): ?string
86
+ {
87
+ if ($this->releaseDate === null) {
88
+ $success = null;
89
+
90
+ try {
91
+ $releaseDate = $this->getItem('browscap.releaseDate', false, $success);
92
+ } catch (InvalidArgumentException $e) {
93
+ $releaseDate = null;
94
+ }
95
+
96
+ assert($releaseDate === null || is_string($releaseDate));
97
+
98
+ if ($releaseDate !== null && $success) {
99
+ $this->releaseDate = $releaseDate;
100
+ }
101
+ }
102
+
103
+ return $this->releaseDate;
104
+ }
105
+
106
+ /**
107
+ * Gets the type of the Browscap data
108
+ *
109
+ * @throws void
110
+ */
111
+ public function getType(): ?string
112
+ {
113
+ if ($this->type === null) {
114
+ $success = null;
115
+
116
+ try {
117
+ $type = $this->getItem('browscap.type', false, $success);
118
+ } catch (InvalidArgumentException $e) {
119
+ $type = null;
120
+ }
121
+
122
+ assert($type === null || is_string($type));
123
+
124
+ if ($type !== null && $success) {
125
+ $this->type = $type;
126
+ }
127
+ }
128
+
129
+ return $this->type;
130
+ }
131
+
132
+ /**
133
+ * Get an item.
134
+ *
135
+ * @return mixed Data on success, null on failure
136
+ *
137
+ * @throws InvalidArgumentException
138
+ */
139
+ public function getItem(string $cacheId, bool $withVersion = true, ?bool &$success = null)
140
+ {
141
+ if ($withVersion) {
142
+ $cacheId .= '.' . $this->getVersion();
143
+ }
144
+
145
+ if (! $this->cache->has($cacheId)) {
146
+ $success = false;
147
+
148
+ return null;
149
+ }
150
+
151
+ $data = $this->cache->get($cacheId);
152
+
153
+ if (! is_array($data) || ! array_key_exists('content', $data)) {
154
+ $success = false;
155
+
156
+ return null;
157
+ }
158
+
159
+ $success = true;
160
+
161
+ return unserialize($data['content']);
162
+ }
163
+
164
+ /**
165
+ * save the content into an php file
166
+ *
167
+ * @param string $cacheId The cache id
168
+ * @param mixed $content The content to store
169
+ *
170
+ * @return bool whether the file was correctly written to the disk
171
+ *
172
+ * @throws InvalidArgumentException
173
+ */
174
+ public function setItem(string $cacheId, $content, bool $withVersion = true): bool
175
+ {
176
+ // Get the whole PHP code
177
+ $data = [
178
+ 'content' => serialize($content),
179
+ ];
180
+
181
+ if ($withVersion) {
182
+ $cacheId .= '.' . $this->getVersion();
183
+ }
184
+
185
+ // Save and return
186
+ return $this->cache->set($cacheId, $data);
187
+ }
188
+
189
+ /**
190
+ * Test if an item exists.
191
+ *
192
+ * @throws InvalidArgumentException
193
+ */
194
+ public function hasItem(string $cacheId, bool $withVersion = true): bool
195
+ {
196
+ if ($withVersion) {
197
+ $cacheId .= '.' . $this->getVersion();
198
+ }
199
+
200
+ return $this->cache->has($cacheId);
201
+ }
202
+
203
+ /**
204
+ * Remove an item.
205
+ *
206
+ * @throws InvalidArgumentException
207
+ */
208
+ public function removeItem(string $cacheId, bool $withVersion = true): bool
209
+ {
210
+ if ($withVersion) {
211
+ $cacheId .= '.' . $this->getVersion();
212
+ }
213
+
214
+ return $this->cache->delete($cacheId);
215
+ }
216
+
217
+ /**
218
+ * Flush the whole storage
219
+ *
220
+ * @throws void
221
+ */
222
+ public function flush(): bool
223
+ {
224
+ return $this->cache->clear();
225
+ }
226
+ }
vendor/browscap-php/browscap/browscap-php/src/Cache/BrowscapCacheInterface.php ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP\Cache;
6
+
7
+ use Psr\SimpleCache\InvalidArgumentException;
8
+
9
+ /**
10
+ * a cache proxy to be able to use the cache adapters provided by the WurflCache package
11
+ */
12
+ interface BrowscapCacheInterface
13
+ {
14
+ /**
15
+ * Gets the version of the Browscap data
16
+ *
17
+ * @throws void
18
+ *
19
+ * @no-named-arguments
20
+ */
21
+ public function getVersion(): ?int;
22
+
23
+ /**
24
+ * Gets the release date of the Browscap data
25
+ *
26
+ * @throws void
27
+ *
28
+ * @no-named-arguments
29
+ */
30
+ public function getReleaseDate(): ?string;
31
+
32
+ /**
33
+ * Gets the type of the Browscap data
34
+ *
35
+ * @throws void
36
+ *
37
+ * @no-named-arguments
38
+ */
39
+ public function getType(): ?string;
40
+
41
+ /**
42
+ * Get an item.
43
+ *
44
+ * @return mixed Data on success, null on failure
45
+ *
46
+ * @throws InvalidArgumentException
47
+ *
48
+ * @no-named-arguments
49
+ */
50
+ public function getItem(string $cacheId, bool $withVersion = true, ?bool &$success = null);
51
+
52
+ /**
53
+ * save the content into an php file
54
+ *
55
+ * @param string $cacheId The cache id
56
+ * @param mixed $content The content to store
57
+ *
58
+ * @return bool whether the file was correctly written to the disk
59
+ *
60
+ * @throws InvalidArgumentException
61
+ *
62
+ * @no-named-arguments
63
+ */
64
+ public function setItem(string $cacheId, $content, bool $withVersion = true): bool;
65
+
66
+ /**
67
+ * Test if an item exists.
68
+ *
69
+ * @throws InvalidArgumentException
70
+ *
71
+ * @no-named-arguments
72
+ */
73
+ public function hasItem(string $cacheId, bool $withVersion = true): bool;
74
+
75
+ /**
76
+ * Remove an item.
77
+ *
78
+ * @throws InvalidArgumentException
79
+ *
80
+ * @no-named-arguments
81
+ */
82
+ public function removeItem(string $cacheId, bool $withVersion = true): bool;
83
+
84
+ /**
85
+ * Flush the whole storage
86
+ *
87
+ * @throws void
88
+ *
89
+ * @no-named-arguments
90
+ */
91
+ public function flush(): bool;
92
+ }
vendor/browscap-php/browscap/browscap-php/src/Command/CheckUpdateCommand.php ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP\Command;
6
+
7
+ use BrowscapPHP\BrowscapUpdater;
8
+ use BrowscapPHP\Exception\ErrorCachedVersionException;
9
+ use BrowscapPHP\Exception\FetcherException;
10
+ use BrowscapPHP\Exception\NoCachedVersionException;
11
+ use BrowscapPHP\Exception\NoNewVersionException;
12
+ use BrowscapPHP\Helper\LoggerHelper;
13
+ use League\Flysystem\Filesystem;
14
+ use League\Flysystem\Local\LocalFilesystemAdapter;
15
+ use MatthiasMullie\Scrapbook\Adapters\Flysystem;
16
+ use MatthiasMullie\Scrapbook\Psr16\SimpleCache;
17
+ use Symfony\Component\Console\Command\Command;
18
+ use Symfony\Component\Console\Exception\InvalidArgumentException;
19
+ use Symfony\Component\Console\Exception\LogicException;
20
+ use Symfony\Component\Console\Input\InputInterface;
21
+ use Symfony\Component\Console\Input\InputOption;
22
+ use Symfony\Component\Console\Output\OutputInterface;
23
+ use Throwable;
24
+
25
+ use function assert;
26
+ use function is_string;
27
+
28
+ /**
29
+ * Command to fetch a browscap ini file from the remote host, convert it into an array and store the content in a local
30
+ * file
31
+ */
32
+ class CheckUpdateCommand extends Command
33
+ {
34
+ public const NO_CACHED_VERSION = 1;
35
+ public const NO_NEWER_VERSION = 2;
36
+ public const ERROR_READING_CACHE = 3;
37
+ public const ERROR_READING_REMOTE_FILE = 4;
38
+ public const GENERIC_ERROR = 5;
39
+
40
+ private ?string $defaultCacheFolder = null;
41
+
42
+ /**
43
+ * @throws LogicException
44
+ */
45
+ public function __construct(string $defaultCacheFolder)
46
+ {
47
+ $this->defaultCacheFolder = $defaultCacheFolder;
48
+
49
+ parent::__construct();
50
+ }
51
+
52
+ /**
53
+ * @throws InvalidArgumentException
54
+ */
55
+ protected function configure(): void
56
+ {
57
+ $this
58
+ ->setName('browscap:check-update')
59
+ ->setDescription('Checks if an updated INI file is available.')
60
+ ->addOption(
61
+ 'cache',
62
+ 'c',
63
+ InputOption::VALUE_OPTIONAL,
64
+ 'Where the cache files are located',
65
+ $this->defaultCacheFolder
66
+ );
67
+ }
68
+
69
+ /**
70
+ * @throws InvalidArgumentException
71
+ * @throws \InvalidArgumentException
72
+ */
73
+ protected function execute(InputInterface $input, OutputInterface $output): int
74
+ {
75
+ $logger = LoggerHelper::createDefaultLogger($output);
76
+
77
+ $cacheOption = $input->getOption('cache');
78
+ assert(is_string($cacheOption));
79
+
80
+ $adapter = new LocalFilesystemAdapter($cacheOption);
81
+ $filesystem = new Filesystem($adapter);
82
+ $cache = new SimpleCache(
83
+ new Flysystem($filesystem)
84
+ );
85
+
86
+ $logger->debug('started checking for new version of remote file');
87
+
88
+ $browscap = new BrowscapUpdater($cache, $logger);
89
+
90
+ try {
91
+ $browscap->checkUpdate();
92
+ } catch (NoCachedVersionException $e) {
93
+ return self::NO_CACHED_VERSION;
94
+ } catch (NoNewVersionException $e) {
95
+ // no newer version available
96
+ $logger->info('there is no newer version available');
97
+
98
+ return self::NO_NEWER_VERSION;
99
+ } catch (ErrorCachedVersionException $e) {
100
+ $logger->info($e);
101
+
102
+ return self::ERROR_READING_CACHE;
103
+ } catch (FetcherException $e) {
104
+ $logger->info($e);
105
+
106
+ return self::ERROR_READING_REMOTE_FILE;
107
+ } catch (Throwable $e) {
108
+ $logger->info($e);
109
+
110
+ return self::GENERIC_ERROR;
111
+ }
112
+
113
+ $logger->debug('finished checking for new version of remote file');
114
+
115
+ return self::SUCCESS;
116
+ }
117
+ }
vendor/browscap-php/browscap/browscap-php/src/Command/ConvertCommand.php ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP\Command;
6
+
7
+ use BrowscapPHP\BrowscapUpdater;
8
+ use BrowscapPHP\Exception;
9
+ use BrowscapPHP\Helper\LoggerHelper;
10
+ use League\Flysystem\Filesystem;
11
+ use League\Flysystem\Local\LocalFilesystemAdapter;
12
+ use MatthiasMullie\Scrapbook\Adapters\Flysystem;
13
+ use MatthiasMullie\Scrapbook\Psr16\SimpleCache;
14
+ use Symfony\Component\Console\Command\Command;
15
+ use Symfony\Component\Console\Exception\InvalidArgumentException;
16
+ use Symfony\Component\Console\Exception\LogicException;
17
+ use Symfony\Component\Console\Input\InputArgument;
18
+ use Symfony\Component\Console\Input\InputInterface;
19
+ use Symfony\Component\Console\Input\InputOption;
20
+ use Symfony\Component\Console\Output\OutputInterface;
21
+ use Throwable;
22
+
23
+ use function assert;
24
+ use function is_string;
25
+ use function sprintf;
26
+
27
+ /**
28
+ * Command to convert a downloaded Browscap ini file and write it to the cache
29
+ */
30
+ class ConvertCommand extends Command
31
+ {
32
+ public const FILENAME_MISSING = 6;
33
+ public const FILE_NOT_FOUND = 7;
34
+ public const ERROR_READING_FILE = 8;
35
+
36
+ private ?string $defaultIniFile = null;
37
+
38
+ private ?string $defaultCacheFolder = null;
39
+
40
+ /**
41
+ * @throws LogicException
42
+ */
43
+ public function __construct(string $defaultCacheFolder, string $defaultIniFile)
44
+ {
45
+ $this->defaultCacheFolder = $defaultCacheFolder;
46
+ $this->defaultIniFile = $defaultIniFile;
47
+
48
+ parent::__construct();
49
+ }
50
+
51
+ /**
52
+ * @throws InvalidArgumentException
53
+ */
54
+ protected function configure(): void
55
+ {
56
+ $this
57
+ ->setName('browscap:convert')
58
+ ->setDescription('Converts an existing browscap.ini file to a cache.php file.')
59
+ ->addArgument(
60
+ 'file',
61
+ InputArgument::OPTIONAL,
62
+ 'Path to the browscap.ini file',
63
+ $this->defaultIniFile
64
+ )
65
+ ->addOption(
66
+ 'cache',
67
+ 'c',
68
+ InputOption::VALUE_OPTIONAL,
69
+ 'Where the cache files are located',
70
+ $this->defaultCacheFolder
71
+ );
72
+ }
73
+
74
+ /**
75
+ * @throws InvalidArgumentException
76
+ * @throws \InvalidArgumentException
77
+ */
78
+ protected function execute(InputInterface $input, OutputInterface $output): int
79
+ {
80
+ $logger = LoggerHelper::createDefaultLogger($output);
81
+
82
+ $cacheOption = $input->getOption('cache');
83
+ assert(is_string($cacheOption));
84
+
85
+ $adapter = new LocalFilesystemAdapter($cacheOption);
86
+ $filesystem = new Filesystem($adapter);
87
+ $cache = new SimpleCache(
88
+ new Flysystem($filesystem)
89
+ );
90
+
91
+ $logger->info('initializing converting process');
92
+
93
+ $browscap = new BrowscapUpdater($cache, $logger);
94
+
95
+ $logger->info('started converting local file');
96
+
97
+ $file = $input->getArgument('file');
98
+ assert(is_string($file));
99
+ if (! $file) {
100
+ $file = $this->defaultIniFile;
101
+ }
102
+
103
+ if ($file === null) {
104
+ return self::FILENAME_MISSING;
105
+ }
106
+
107
+ $output->writeln(sprintf('converting file %s', $file));
108
+
109
+ try {
110
+ $browscap->convertFile($file);
111
+ } catch (Exception\FileNameMissingException $e) {
112
+ $logger->debug($e);
113
+
114
+ return self::FILENAME_MISSING;
115
+ } catch (Exception\FileNotFoundException $e) {
116
+ $logger->debug($e);
117
+
118
+ return self::FILE_NOT_FOUND;
119
+ } catch (Exception\ErrorReadingFileException $e) {
120
+ $logger->debug($e);
121
+
122
+ return self::ERROR_READING_FILE;
123
+ } catch (Throwable $e) {
124
+ $logger->info($e);
125
+
126
+ return CheckUpdateCommand::GENERIC_ERROR;
127
+ }
128
+
129
+ $logger->info('finished converting local file');
130
+
131
+ return self::SUCCESS;
132
+ }
133
+ }
vendor/browscap-php/browscap/browscap-php/src/Command/FetchCommand.php ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP\Command;
6
+
7
+ use BrowscapPHP\BrowscapUpdater;
8
+ use BrowscapPHP\Exception\ErrorCachedVersionException;
9
+ use BrowscapPHP\Exception\FetcherException;
10
+ use BrowscapPHP\Helper\IniLoaderInterface;
11
+ use BrowscapPHP\Helper\LoggerHelper;
12
+ use League\Flysystem\Filesystem;
13
+ use League\Flysystem\Local\LocalFilesystemAdapter;
14
+ use MatthiasMullie\Scrapbook\Adapters\Flysystem;
15
+ use MatthiasMullie\Scrapbook\Psr16\SimpleCache;
16
+ use Symfony\Component\Console\Command\Command;
17
+ use Symfony\Component\Console\Exception\InvalidArgumentException;
18
+ use Symfony\Component\Console\Exception\LogicException;
19
+ use Symfony\Component\Console\Input\InputArgument;
20
+ use Symfony\Component\Console\Input\InputInterface;
21
+ use Symfony\Component\Console\Input\InputOption;
22
+ use Symfony\Component\Console\Output\OutputInterface;
23
+ use Throwable;
24
+
25
+ use function assert;
26
+ use function is_string;
27
+ use function sprintf;
28
+
29
+ /**
30
+ * Command to fetch a browscap ini file from the remote host and store the content in a local file
31
+ */
32
+ class FetchCommand extends Command
33
+ {
34
+ private ?string $defaultIniFile = null;
35
+
36
+ private ?string $defaultCacheFolder = null;
37
+
38
+ /**
39
+ * @throws LogicException
40
+ */
41
+ public function __construct(string $defaultCacheFolder, string $defaultIniFile)
42
+ {
43
+ $this->defaultCacheFolder = $defaultCacheFolder;
44
+ $this->defaultIniFile = $defaultIniFile;
45
+
46
+ parent::__construct();
47
+ }
48
+
49
+ /**
50
+ * @throws InvalidArgumentException
51
+ */
52
+ protected function configure(): void
53
+ {
54
+ $this
55
+ ->setName('browscap:fetch')
56
+ ->setDescription('Fetches an updated INI file for Browscap.')
57
+ ->addArgument(
58
+ 'file',
59
+ InputArgument::OPTIONAL,
60
+ 'browscap.ini file',
61
+ $this->defaultIniFile
62
+ )
63
+ ->addOption(
64
+ 'remote-file',
65
+ 'r',
66
+ InputOption::VALUE_OPTIONAL,
67
+ sprintf(
68
+ 'browscap.ini file to download from remote location (possible values are: %s, %s, %s)',
69
+ IniLoaderInterface::PHP_INI_LITE,
70
+ IniLoaderInterface::PHP_INI,
71
+ IniLoaderInterface::PHP_INI_FULL
72
+ ),
73
+ IniLoaderInterface::PHP_INI
74
+ )
75
+ ->addOption(
76
+ 'cache',
77
+ 'c',
78
+ InputOption::VALUE_OPTIONAL,
79
+ 'Where the cache files are located',
80
+ $this->defaultCacheFolder
81
+ );
82
+ }
83
+
84
+ /**
85
+ * @throws InvalidArgumentException
86
+ * @throws \InvalidArgumentException
87
+ */
88
+ protected function execute(InputInterface $input, OutputInterface $output): int
89
+ {
90
+ $logger = LoggerHelper::createDefaultLogger($output);
91
+
92
+ $cacheOption = $input->getOption('cache');
93
+ assert(is_string($cacheOption));
94
+
95
+ $adapter = new LocalFilesystemAdapter($cacheOption);
96
+ $filesystem = new Filesystem($adapter);
97
+ $cache = new SimpleCache(
98
+ new Flysystem($filesystem)
99
+ );
100
+
101
+ $file = $input->getArgument('file');
102
+ assert(is_string($file));
103
+ if (! $file) {
104
+ $file = $this->defaultIniFile;
105
+ }
106
+
107
+ if ($file === null) {
108
+ return ConvertCommand::FILENAME_MISSING;
109
+ }
110
+
111
+ $output->writeln(sprintf('write fetched file to %s', $file));
112
+
113
+ $logger->info('started fetching remote file');
114
+
115
+ $browscap = new BrowscapUpdater($cache, $logger);
116
+
117
+ $remoteFileOption = $input->getOption('remote-file');
118
+ assert(is_string($remoteFileOption));
119
+
120
+ try {
121
+ $browscap->fetch($file, $remoteFileOption);
122
+ } catch (ErrorCachedVersionException $e) {
123
+ $logger->debug($e);
124
+
125
+ return CheckUpdateCommand::ERROR_READING_CACHE;
126
+ } catch (FetcherException $e) {
127
+ $logger->debug($e);
128
+
129
+ return CheckUpdateCommand::ERROR_READING_REMOTE_FILE;
130
+ } catch (Throwable $e) {
131
+ $logger->info($e);
132
+
133
+ return CheckUpdateCommand::GENERIC_ERROR;
134
+ }
135
+
136
+ $logger->info('finished fetching remote file');
137
+
138
+ return self::SUCCESS;
139
+ }
140
+ }
vendor/browscap-php/browscap/browscap-php/src/Command/ParserCommand.php ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP\Command;
6
+
7
+ use BrowscapPHP\Browscap;
8
+ use BrowscapPHP\Exception;
9
+ use BrowscapPHP\Helper\LoggerHelper;
10
+ use JsonException;
11
+ use League\Flysystem\Filesystem;
12
+ use League\Flysystem\Local\LocalFilesystemAdapter;
13
+ use MatthiasMullie\Scrapbook\Adapters\Flysystem;
14
+ use MatthiasMullie\Scrapbook\Psr16\SimpleCache;
15
+ use Symfony\Component\Console\Command\Command;
16
+ use Symfony\Component\Console\Exception\InvalidArgumentException;
17
+ use Symfony\Component\Console\Exception\LogicException;
18
+ use Symfony\Component\Console\Input\InputArgument;
19
+ use Symfony\Component\Console\Input\InputInterface;
20
+ use Symfony\Component\Console\Input\InputOption;
21
+ use Symfony\Component\Console\Output\OutputInterface;
22
+
23
+ use function assert;
24
+ use function is_string;
25
+ use function json_encode;
26
+
27
+ use const JSON_PRETTY_PRINT;
28
+ use const JSON_THROW_ON_ERROR;
29
+
30
+ /**
31
+ * commands to parse a given useragent
32
+ */
33
+ class ParserCommand extends Command
34
+ {
35
+ public const PARSER_ERROR = 11;
36
+
37
+ private ?string $defaultCacheFolder = null;
38
+
39
+ /**
40
+ * @throws LogicException
41
+ */
42
+ public function __construct(string $defaultCacheFolder)
43
+ {
44
+ $this->defaultCacheFolder = $defaultCacheFolder;
45
+
46
+ parent::__construct();
47
+ }
48
+
49
+ /**
50
+ * @throws InvalidArgumentException
51
+ */
52
+ protected function configure(): void
53
+ {
54
+ $this
55
+ ->setName('browscap:parse')
56
+ ->setDescription('Parses a user agent string and dumps the results.')
57
+ ->addArgument(
58
+ 'user-agent',
59
+ InputArgument::REQUIRED,
60
+ 'User agent string to analyze',
61
+ null
62
+ )
63
+ ->addOption(
64
+ 'cache',
65
+ 'c',
66
+ InputOption::VALUE_OPTIONAL,
67
+ 'Where the cache files are located',
68
+ $this->defaultCacheFolder
69
+ );
70
+ }
71
+
72
+ /**
73
+ * @throws InvalidArgumentException
74
+ * @throws \InvalidArgumentException
75
+ */
76
+ protected function execute(InputInterface $input, OutputInterface $output): int
77
+ {
78
+ $logger = LoggerHelper::createDefaultLogger($output);
79
+
80
+ $cacheOption = $input->getOption('cache');
81
+ assert(is_string($cacheOption));
82
+
83
+ $adapter = new LocalFilesystemAdapter($cacheOption);
84
+ $filesystem = new Filesystem($adapter);
85
+ $cache = new SimpleCache(
86
+ new Flysystem($filesystem)
87
+ );
88
+
89
+ $browscap = new Browscap($cache, $logger);
90
+
91
+ $uaArgument = $input->getArgument('user-agent');
92
+ assert(is_string($uaArgument));
93
+
94
+ try {
95
+ $result = $browscap->getBrowser($uaArgument);
96
+ } catch (Exception $e) {
97
+ $logger->debug($e);
98
+
99
+ return self::PARSER_ERROR;
100
+ }
101
+
102
+ try {
103
+ $output->writeln(json_encode($result, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
104
+ } catch (JsonException $e) {
105
+ $logger->error($e);
106
+
107
+ return self::PARSER_ERROR;
108
+ }
109
+
110
+ return self::SUCCESS;
111
+ }
112
+ }
vendor/browscap-php/browscap/browscap-php/src/Command/UpdateCommand.php ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP\Command;
6
+
7
+ use BrowscapPHP\BrowscapUpdater;
8
+ use BrowscapPHP\Exception\ErrorCachedVersionException;
9
+ use BrowscapPHP\Exception\FetcherException;
10
+ use BrowscapPHP\Helper\IniLoaderInterface;
11
+ use BrowscapPHP\Helper\LoggerHelper;
12
+ use League\Flysystem\Filesystem;
13
+ use League\Flysystem\Local\LocalFilesystemAdapter;
14
+ use MatthiasMullie\Scrapbook\Adapters\Flysystem;
15
+ use MatthiasMullie\Scrapbook\Psr16\SimpleCache;
16
+ use Symfony\Component\Console\Command\Command;
17
+ use Symfony\Component\Console\Exception\InvalidArgumentException;
18
+ use Symfony\Component\Console\Exception\LogicException;
19
+ use Symfony\Component\Console\Input\InputInterface;
20
+ use Symfony\Component\Console\Input\InputOption;
21
+ use Symfony\Component\Console\Output\OutputInterface;
22
+ use Throwable;
23
+
24
+ use function assert;
25
+ use function is_string;
26
+ use function sprintf;
27
+
28
+ /**
29
+ * Command to fetch a browscap ini file from the remote host, convert it into an array and store the content in a local
30
+ * file
31
+ */
32
+ class UpdateCommand extends Command
33
+ {
34
+ private ?string $defaultCacheFolder = null;
35
+
36
+ /**
37
+ * @throws LogicException
38
+ */
39
+ public function __construct(string $defaultCacheFolder)
40
+ {
41
+ $this->defaultCacheFolder = $defaultCacheFolder;
42
+
43
+ parent::__construct();
44
+ }
45
+
46
+ /**
47
+ * @throws InvalidArgumentException
48
+ */
49
+ protected function configure(): void
50
+ {
51
+ $this
52
+ ->setName('browscap:update')
53
+ ->setDescription('Fetches an updated INI file for Browscap and overwrites the current PHP file.')
54
+ ->addOption(
55
+ 'remote-file',
56
+ 'r',
57
+ InputOption::VALUE_OPTIONAL,
58
+ sprintf(
59
+ 'browscap.ini file to download from remote location (possible values are: %s, %s, %s)',
60
+ IniLoaderInterface::PHP_INI_LITE,
61
+ IniLoaderInterface::PHP_INI,
62
+ IniLoaderInterface::PHP_INI_FULL
63
+ ),
64
+ IniLoaderInterface::PHP_INI
65
+ )
66
+ ->addOption(
67
+ 'no-backup',
68
+ null,
69
+ InputOption::VALUE_NONE,
70
+ 'Do not backup the previously existing file'
71
+ )
72
+ ->addOption(
73
+ 'cache',
74
+ 'c',
75
+ InputOption::VALUE_OPTIONAL,
76
+ 'Where the cache files are located',
77
+ $this->defaultCacheFolder
78
+ );
79
+ }
80
+
81
+ /**
82
+ * @throws InvalidArgumentException
83
+ * @throws \InvalidArgumentException
84
+ */
85
+ protected function execute(InputInterface $input, OutputInterface $output): int
86
+ {
87
+ $logger = LoggerHelper::createDefaultLogger($output);
88
+
89
+ $cacheOption = $input->getOption('cache');
90
+ assert(is_string($cacheOption));
91
+
92
+ $adapter = new LocalFilesystemAdapter($cacheOption);
93
+ $filesystem = new Filesystem($adapter);
94
+ $cache = new SimpleCache(
95
+ new Flysystem($filesystem)
96
+ );
97
+
98
+ $logger->info('started updating cache with remote file');
99
+
100
+ $browscap = new BrowscapUpdater($cache, $logger);
101
+
102
+ $remoteFileOption = $input->getOption('remote-file');
103
+ assert(is_string($remoteFileOption));
104
+
105
+ try {
106
+ $browscap->update($remoteFileOption);
107
+ } catch (ErrorCachedVersionException $e) {
108
+ $logger->debug($e);
109
+
110
+ return CheckUpdateCommand::ERROR_READING_CACHE;
111
+ } catch (FetcherException $e) {
112
+ $logger->debug($e);
113
+
114
+ return CheckUpdateCommand::ERROR_READING_REMOTE_FILE;
115
+ } catch (Throwable $e) {
116
+ $logger->info($e);
117
+
118
+ return CheckUpdateCommand::GENERIC_ERROR;
119
+ }
120
+
121
+ $logger->info('finished updating cache with remote file');
122
+
123
+ return self::SUCCESS;
124
+ }
125
+ }
vendor/browscap-php/browscap/browscap-php/src/Data/PropertyFormatter.php ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP\Data;
6
+
7
+ final class PropertyFormatter
8
+ {
9
+ private PropertyHolder $propertyHolder;
10
+
11
+ /**
12
+ * @throws void
13
+ */
14
+ public function __construct(PropertyHolder $propertyHolder)
15
+ {
16
+ $this->propertyHolder = $propertyHolder;
17
+ }
18
+
19
+ /**
20
+ * formats the name of a property
21
+ *
22
+ * @param bool|string $value
23
+ *
24
+ * @return bool|string
25
+ *
26
+ * @throws void
27
+ */
28
+ public function formatPropertyValue($value, string $property)
29
+ {
30
+ if ($this->propertyHolder->getPropertyType($property) === PropertyHolder::TYPE_BOOLEAN) {
31
+ return $value === true || $value === 'true' || $value === '1';
32
+ }
33
+
34
+ return $value;
35
+ }
36
+ }
vendor/browscap-php/browscap/browscap-php/src/Data/PropertyHolder.php ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP\Data;
6
+
7
+ use function array_key_exists;
8
+
9
+ final class PropertyHolder
10
+ {
11
+ public const TYPE_STRING = 'string';
12
+ public const TYPE_GENERIC = 'generic';
13
+ public const TYPE_NUMBER = 'number';
14
+ public const TYPE_BOOLEAN = 'boolean';
15
+
16
+ /**
17
+ * Get the type of a property.
18
+ *
19
+ * @throws void
20
+ */
21
+ public function getPropertyType(string $propertyName): string
22
+ {
23
+ $stringProperties = [
24
+ 'Comment' => 1,
25
+ 'Browser' => 1,
26
+ 'Browser_Maker' => 1,
27
+ 'Browser_Modus' => 1,
28
+ 'Platform' => 1,
29
+ 'Platform_Name' => 1,
30
+ 'Platform_Description' => 1,
31
+ 'Device_Name' => 1,
32
+ 'Platform_Maker' => 1,
33
+ 'Device_Code_Name' => 1,
34
+ 'Device_Maker' => 1,
35
+ 'Device_Brand_Name' => 1,
36
+ 'RenderingEngine_Name' => 1,
37
+ 'RenderingEngine_Description' => 1,
38
+ 'RenderingEngine_Maker' => 1,
39
+ 'Parent' => 1,
40
+ 'PropertyName' => 1,
41
+ 'CDF' => 1,
42
+ 'PatternId' => 1,
43
+ ];
44
+
45
+ if (array_key_exists($propertyName, $stringProperties)) {
46
+ return self::TYPE_STRING;
47
+ }
48
+
49
+ $numericProperties = [
50
+ 'Version' => 1,
51
+ 'CssVersion' => 1,
52
+ 'AolVersion' => 1,
53
+ 'MajorVer' => 1,
54
+ 'MinorVer' => 1,
55
+ 'aolVersion' => 1,
56
+ ];
57
+
58
+ if (array_key_exists($propertyName, $numericProperties)) {
59
+ return self::TYPE_NUMBER;
60
+ }
61
+
62
+ $booleanProperties = [
63
+ 'Alpha' => 1,
64
+ 'Beta' => 1,
65
+ 'Win16' => 1,
66
+ 'Win32' => 1,
67
+ 'Win64' => 1,
68
+ 'Frames' => 1,
69
+ 'IFrames' => 1,
70
+ 'Tables' => 1,
71
+ 'Cookies' => 1,
72
+ 'BackgroundSounds' => 1,
73
+ 'JavaScript' => 1,
74
+ 'VBScript' => 1,
75
+ 'JavaApplets' => 1,
76
+ 'ActiveXControls' => 1,
77
+ 'isMobileDevice' => 1,
78
+ 'isTablet' => 1,
79
+ 'isSyndicationReader' => 1,
80
+ 'Crawler' => 1,
81
+ 'MasterParent' => 1,
82
+ 'LiteMode' => 1,
83
+ 'isFake' => 1,
84
+ 'isAnonymized' => 1,
85
+ 'isModified' => 1,
86
+ 'isBanned' => 1,
87
+ 'supportsCSS' => 1,
88
+ 'AOL' => 1,
89
+ ];
90
+
91
+ if (array_key_exists($propertyName, $booleanProperties)) {
92
+ return self::TYPE_BOOLEAN;
93
+ }
94
+
95
+ return self::TYPE_GENERIC;
96
+ }
97
+ }
vendor/browscap-php/browscap/browscap-php/src/Exception.php ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP;
6
+
7
+ /**
8
+ * Browscap.ini parsing class base exception
9
+ */
10
+ class Exception extends \Exception
11
+ {
12
+ public const LOCAL_FILE_MISSING = 100;
13
+ public const NO_RESULT_CLASS_RETURNED = 200;
14
+ public const STRING_VALUE_EXPECTED = 300;
15
+ public const CACHE_DIR_MISSING = 400;
16
+ public const CACHE_DIR_INVALID = 401;
17
+ public const CACHE_DIR_NOT_READABLE = 402;
18
+ public const CACHE_DIR_NOT_WRITABLE = 403;
19
+ public const CACHE_INCOMPATIBLE = 500;
20
+ public const INVALID_DATETIME = 600;
21
+ public const LOCAL_FILE_NOT_READABLE = 700;
22
+ public const REMOTE_UPDATE_NOT_POSSIBLE = 800;
23
+ public const INI_FILE_MISSING = 900;
24
+ }
vendor/browscap-php/browscap/browscap-php/src/Exception/DomainException.php ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP\Exception;
6
+
7
+ use DomainException as BaseDomainException;
8
+
9
+ abstract class DomainException extends BaseDomainException
10
+ {
11
+ }
vendor/browscap-php/browscap/browscap-php/src/Exception/ErrorCachedVersionException.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP\Exception;
6
+
7
+ final class ErrorCachedVersionException extends DomainException
8
+ {
9
+ }
vendor/browscap-php/browscap/browscap-php/src/Exception/ErrorReadingFileException.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP\Exception;
6
+
7
+ final class ErrorReadingFileException extends DomainException
8
+ {
9
+ }
vendor/browscap-php/browscap/browscap-php/src/Exception/FetcherException.php ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP\Exception;
6
+
7
+ use function sprintf;
8
+
9
+ /**
10
+ * Exception to handle errors while fetching a remote file
11
+ */
12
+ final class FetcherException extends DomainException
13
+ {
14
+ /**
15
+ * @return FetcherException
16
+ */
17
+ public static function httpError(string $resource, string $error): self
18
+ {
19
+ return new self(
20
+ sprintf('Could not fetch HTTP resource "%s": %s', $resource, $error)
21
+ );
22
+ }
23
+ }
vendor/browscap-php/browscap/browscap-php/src/Exception/FileNameMissingException.php ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP\Exception;
6
+
7
+ use Exception;
8
+
9
+ final class FileNameMissingException extends Exception
10
+ {
11
+ }
vendor/browscap-php/browscap/browscap-php/src/Exception/FileNotFoundException.php ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP\Exception;
6
+
7
+ use Exception;
8
+
9
+ use function sprintf;
10
+
11
+ /**
12
+ * Exception to handle errors because a file does not exist
13
+ */
14
+ final class FileNotFoundException extends Exception
15
+ {
16
+ /**
17
+ * @return FileNotFoundException
18
+ */
19
+ public static function fileNotFound(string $file): self
20
+ {
21
+ return new self(sprintf('File "%s" does not exist', $file));
22
+ }
23
+ }
vendor/browscap-php/browscap/browscap-php/src/Exception/InvalidArgumentException.php ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP\Exception;
6
+
7
+ use InvalidArgumentException as BaseInvalidArgumentException;
8
+
9
+ use function implode;
10
+ use function sprintf;
11
+
12
+ /**
13
+ * Exception to handle errors if one argument is required
14
+ */
15
+ final class InvalidArgumentException extends BaseInvalidArgumentException
16
+ {
17
+ public static function oneOfCommandArguments(string ...$requiredArguments): self
18
+ {
19
+ return new self(
20
+ sprintf('One of the command arguments "%s" is required', implode('", "', $requiredArguments))
21
+ );
22
+ }
23
+ }
vendor/browscap-php/browscap/browscap-php/src/Exception/NoCachedVersionException.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP\Exception;
6
+
7
+ final class NoCachedVersionException extends DomainException
8
+ {
9
+ }
vendor/browscap-php/browscap/browscap-php/src/Exception/NoNewVersionException.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP\Exception;
6
+
7
+ final class NoNewVersionException extends DomainException
8
+ {
9
+ }
vendor/browscap-php/browscap/browscap-php/src/Formatter/FormatterInterface.php ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP\Formatter;
6
+
7
+ use stdClass;
8
+
9
+ /**
10
+ * interface for formating the output
11
+ */
12
+ interface FormatterInterface
13
+ {
14
+ /**
15
+ * Sets the data (done by the parser)
16
+ *
17
+ * @param string[]|bool[] $settings
18
+ *
19
+ * @throws void
20
+ *
21
+ * @no-named-arguments
22
+ */
23
+ public function setData(array $settings): void;
24
+
25
+ /**
26
+ * Gets the data (in the preferred format)
27
+ *
28
+ * @throws void
29
+ *
30
+ * @no-named-arguments
31
+ */
32
+ public function getData(): stdClass;
33
+ }
vendor/browscap-php/browscap/browscap-php/src/Formatter/LegacyFormatter.php ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP\Formatter;
6
+
7
+ use stdClass;
8
+
9
+ use function array_key_exists;
10
+ use function array_merge;
11
+ use function strtolower;
12
+
13
+ /**
14
+ * formatter for backwards compatibility with 2.x
15
+ */
16
+ final class LegacyFormatter implements FormatterInterface
17
+ {
18
+ /**
19
+ * Options for the formatter
20
+ *
21
+ * @var bool[]
22
+ * @phpstan-var array{lowercase?: bool}
23
+ */
24
+ private array $options = [];
25
+
26
+ /**
27
+ * Default formatter options
28
+ *
29
+ * @var bool[]
30
+ * @phpstanvar array{lowercase: bool}
31
+ */
32
+ private array $defaultOptions = ['lowercase' => false];
33
+
34
+ /**
35
+ * Variable to save the settings in, type depends on implementation
36
+ *
37
+ * @var string[]|bool[]|null[]
38
+ */
39
+ private array $data = [];
40
+
41
+ /**
42
+ * @param bool[] $options Formatter options
43
+ * @phpstan-param array{lowercase?: bool} $options
44
+ *
45
+ * @throws void
46
+ */
47
+ public function __construct(array $options = [])
48
+ {
49
+ $this->options = array_merge($this->defaultOptions, $options);
50
+ }
51
+
52
+ /**
53
+ * Sets the data (done by the parser)
54
+ *
55
+ * @param string[]|bool[]|null[] $settings
56
+ *
57
+ * @throws void
58
+ */
59
+ public function setData(array $settings): void
60
+ {
61
+ $this->data = $settings;
62
+ }
63
+
64
+ /**
65
+ * Gets the data (in the preferred format)
66
+ *
67
+ * @throws void
68
+ */
69
+ public function getData(): stdClass
70
+ {
71
+ $output = new stdClass();
72
+
73
+ foreach ($this->data as $key => $property) {
74
+ if (array_key_exists('lowercase', $this->options) && $this->options['lowercase']) {
75
+ $key = strtolower($key);
76
+ }
77
+
78
+ $output->{$key} = $property;
79
+ }
80
+
81
+ return $output;
82
+ }
83
+ }
vendor/browscap-php/browscap/browscap-php/src/Formatter/PhpGetBrowser.php ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP\Formatter;
6
+
7
+ use stdClass;
8
+
9
+ use function array_key_exists;
10
+ use function array_keys;
11
+ use function strtolower;
12
+
13
+ /**
14
+ * Formatter to output the data like the native get_browser function
15
+ */
16
+ final class PhpGetBrowser implements FormatterInterface
17
+ {
18
+ /**
19
+ * Variable to save the settings in, type depends on implementation
20
+ *
21
+ * @var string[]|bool[]|null[]
22
+ */
23
+ private array $data = [];
24
+
25
+ /**
26
+ * a list of possible properties
27
+ *
28
+ * @var string[]|bool[]|null[]
29
+ */
30
+ private array $defaultProperties = [
31
+ 'browser_name_regex' => null,
32
+ 'browser_name_pattern' => null,
33
+ 'Parent' => null,
34
+ 'Comment' => 'Default Browser',
35
+ 'Browser' => 'Default Browser',
36
+ 'Browser_Type' => 'unknown',
37
+ 'Browser_Bits' => '0',
38
+ 'Browser_Maker' => 'unknown',
39
+ 'Browser_Modus' => 'unknown',
40
+ 'Version' => '0.0',
41
+ 'MajorVer' => '0',
42
+ 'MinorVer' => '0',
43
+ 'Platform' => 'unknown',
44
+ 'Platform_Version' => 'unknown',
45
+ 'Platform_Description' => 'unknown',
46
+ 'Platform_Bits' => '0',
47
+ 'Platform_Maker' => 'unknown',
48
+ 'Alpha' => false,
49
+ 'Beta' => false,
50
+ 'Win16' => false,
51
+ 'Win32' => false,
52
+ 'Win64' => false,
53
+ 'Frames' => false,
54
+ 'IFrames' => false,
55
+ 'Tables' => false,
56
+ 'Cookies' => false,
57
+ 'BackgroundSounds' => false,
58
+ 'JavaScript' => false,
59
+ 'VBScript' => false,
60
+ 'JavaApplets' => false,
61
+ 'ActiveXControls' => false,
62
+ 'isMobileDevice' => false,
63
+ 'isTablet' => false,
64
+ 'isSyndicationReader' => false,
65
+ 'Crawler' => false,
66
+ 'isFake' => false,
67
+ 'isAnonymized' => false,
68
+ 'isModified' => false,
69
+ 'CssVersion' => '0',
70
+ 'AolVersion' => '0',
71
+ 'Device_Name' => 'unknown',
72
+ 'Device_Maker' => 'unknown',
73
+ 'Device_Type' => 'unknown',
74
+ 'Device_Pointing_Method' => 'unknown',
75
+ 'Device_Code_Name' => 'unknown',
76
+ 'Device_Brand_Name' => 'unknown',
77
+ 'RenderingEngine_Name' => 'unknown',
78
+ 'RenderingEngine_Version' => 'unknown',
79
+ 'RenderingEngine_Description' => 'unknown',
80
+ 'RenderingEngine_Maker' => 'unknown',
81
+ ];
82
+
83
+ /**
84
+ * Sets the data (done by the parser)
85
+ *
86
+ * @param string[]|bool[]|null[] $settings
87
+ *
88
+ * @throws void
89
+ */
90
+ public function setData(array $settings): void
91
+ {
92
+ foreach ($settings as $key => $value) {
93
+ $this->data[strtolower($key)] = $value;
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Gets the data (in the preferred format)
99
+ *
100
+ * @throws void
101
+ */
102
+ public function getData(): stdClass
103
+ {
104
+ $output = new stdClass();
105
+
106
+ $propertyNames = array_keys($this->defaultProperties);
107
+ foreach ($propertyNames as $property) {
108
+ $key = strtolower($property);
109
+
110
+ if (array_key_exists($key, $this->data)) {
111
+ $output->{$key} = $this->data[$key];
112
+ } elseif ($key !== 'parent') {
113
+ $output->{$key} = $this->defaultProperties[$property];
114
+ }
115
+ }
116
+
117
+ // Don't want to normally do this, just if it exists in the data file
118
+ // for our test runs
119
+ if (array_key_exists('patternid', $this->data)) {
120
+ $output->patternid = $this->data['patternid'];
121
+ }
122
+
123
+ return $output;
124
+ }
125
+ }
vendor/browscap-php/browscap/browscap-php/src/Helper/Converter.php ADDED
@@ -0,0 +1,236 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP\Helper;
6
+
7
+ use BrowscapPHP\Cache\BrowscapCacheInterface;
8
+ use BrowscapPHP\Exception\ErrorReadingFileException;
9
+ use BrowscapPHP\Exception\FileNotFoundException;
10
+ use BrowscapPHP\IniParser\IniParser;
11
+ use JsonException;
12
+ use OutOfRangeException;
13
+ use Psr\Log\LoggerInterface;
14
+ use Psr\SimpleCache\InvalidArgumentException;
15
+ use UnexpectedValueException;
16
+
17
+ use function file_get_contents;
18
+ use function is_string;
19
+ use function preg_match;
20
+ use function sprintf;
21
+
22
+ /**
23
+ * patternHelper to convert the ini data, parses the data and stores them into the cache
24
+ */
25
+ final class Converter implements ConverterInterface
26
+ {
27
+ /**
28
+ * The key to search for in the INI file to find the browscap settings
29
+ */
30
+ private const BROWSCAP_VERSION_KEY = 'GJK_Browscap_Version';
31
+
32
+ private LoggerInterface $logger;
33
+
34
+ /**
35
+ * The cache instance
36
+ */
37
+ private BrowscapCacheInterface $cache;
38
+
39
+ /**
40
+ * a filesystem patternHelper instance
41
+ */
42
+ private Filesystem $filessystem;
43
+
44
+ /**
45
+ * version of the ini file
46
+ */
47
+ private int $iniVersion = 0;
48
+
49
+ /**
50
+ * @throws void
51
+ */
52
+ public function __construct(LoggerInterface $logger, BrowscapCacheInterface $cache)
53
+ {
54
+ $this->logger = $logger;
55
+ $this->cache = $cache;
56
+ $this->filessystem = new Filesystem();
57
+ }
58
+
59
+ /**
60
+ * Sets a filesystem instance
61
+ *
62
+ * @throws void
63
+ */
64
+ public function setFilesystem(Filesystem $file): void
65
+ {
66
+ $this->filessystem = $file;
67
+ }
68
+
69
+ /**
70
+ * converts a file
71
+ *
72
+ * @throws FileNotFoundException
73
+ * @throws ErrorReadingFileException
74
+ */
75
+ public function convertFile(string $iniFile): void
76
+ {
77
+ if (! $this->filessystem->exists($iniFile)) {
78
+ throw FileNotFoundException::fileNotFound($iniFile);
79
+ }
80
+
81
+ $this->logger->info('start reading file');
82
+
83
+ $iniString = file_get_contents($iniFile);
84
+
85
+ $this->logger->info('finished reading file');
86
+
87
+ if (! is_string($iniString)) {
88
+ throw new ErrorReadingFileException(sprintf('could not read file %s', $iniFile));
89
+ }
90
+
91
+ $this->convertString($iniString);
92
+ }
93
+
94
+ /**
95
+ * converts the string content
96
+ *
97
+ * @throws void
98
+ */
99
+ public function convertString(string $iniString): void
100
+ {
101
+ $iniParser = new IniParser();
102
+
103
+ $this->logger->info('start creating patterns from the ini data');
104
+
105
+ foreach ($iniParser->createPatterns($iniString) as $subkey => $content) {
106
+ if ($subkey === '') {
107
+ continue;
108
+ }
109
+
110
+ try {
111
+ if (! $this->cache->setItem('browscap.patterns.' . $subkey, $content, true)) {
112
+ $this->logger->error('could not write pattern data "' . $subkey . '" to the cache');
113
+ }
114
+ } catch (InvalidArgumentException $e) {
115
+ $this->logger->error(new \InvalidArgumentException('an error occured while writing pattern data into the cache', 0, $e));
116
+ }
117
+ }
118
+
119
+ $this->logger->info('finished creating patterns from the ini data');
120
+
121
+ $this->logger->info('start creating data from the ini data');
122
+
123
+ try {
124
+ foreach ($iniParser->createIniParts($iniString) as $subkey => $content) {
125
+ if ($subkey === '') {
126
+ continue;
127
+ }
128
+
129
+ try {
130
+ if (! $this->cache->setItem('browscap.iniparts.' . $subkey, $content, true)) {
131
+ $this->logger->error('could not write property data "' . $subkey . '" to the cache');
132
+ }
133
+ } catch (InvalidArgumentException $e) {
134
+ $this->logger->error(new \InvalidArgumentException('an error occured while writing property data into the cache', 0, $e));
135
+ }
136
+ }
137
+ } catch (OutOfRangeException | UnexpectedValueException | JsonException | \InvalidArgumentException $e) {
138
+ $this->logger->error(new \InvalidArgumentException('an error occured while writing property data into the cache', 0, $e));
139
+ }
140
+
141
+ try {
142
+ $this->cache->setItem('browscap.releaseDate', $this->getIniReleaseDate($iniString), false);
143
+ } catch (InvalidArgumentException $e) {
144
+ $this->logger->error(new \InvalidArgumentException('an error occured while writing data release date into the cache', 0, $e));
145
+ }
146
+
147
+ try {
148
+ $this->cache->setItem('browscap.type', $this->getIniType($iniString), false);
149
+ } catch (InvalidArgumentException $e) {
150
+ $this->logger->error(new \InvalidArgumentException('an error occured while writing the data type into the cache', 0, $e));
151
+ }
152
+
153
+ $this->logger->info('finished creating data from the ini data');
154
+ }
155
+
156
+ /**
157
+ * Parses the ini data to get the version of loaded ini file
158
+ *
159
+ * @param string $iniString The loaded ini data
160
+ *
161
+ * @throws void
162
+ */
163
+ public function getIniVersion(string $iniString): int
164
+ {
165
+ $quoterHelper = new Quoter();
166
+ $key = $quoterHelper->pregQuote(self::BROWSCAP_VERSION_KEY);
167
+
168
+ if (preg_match('/\.*\[' . $key . '\][^\[]*Version=(\d+)\D.*/', $iniString, $matches)) {
169
+ if (isset($matches[1])) {
170
+ $this->iniVersion = (int) $matches[1];
171
+ }
172
+ }
173
+
174
+ return $this->iniVersion;
175
+ }
176
+
177
+ /**
178
+ * sets the version
179
+ *
180
+ * @throws void
181
+ */
182
+ public function setVersion(int $version): void
183
+ {
184
+ $this->iniVersion = $version;
185
+ }
186
+
187
+ /**
188
+ * stores the version of the ini file into cache
189
+ *
190
+ * @throws void
191
+ */
192
+ public function storeVersion(): void
193
+ {
194
+ try {
195
+ $this->cache->setItem('browscap.version', $this->iniVersion, false);
196
+ } catch (InvalidArgumentException $e) {
197
+ $this->logger->error(new \InvalidArgumentException('an error occured while writing the data version into the cache', 0, $e));
198
+ }
199
+ }
200
+
201
+ /**
202
+ * Parses the ini data to get the releaseDate of loaded ini file
203
+ *
204
+ * @param string $iniString The loaded ini data
205
+ *
206
+ * @throws void
207
+ */
208
+ private function getIniReleaseDate(string $iniString): ?string
209
+ {
210
+ if (preg_match('/Released=(.*)/', $iniString, $matches)) {
211
+ if (isset($matches[1])) {
212
+ return $matches[1];
213
+ }
214
+ }
215
+
216
+ return null;
217
+ }
218
+
219
+ /**
220
+ * Parses the ini data to get the releaseDate of loaded ini file
221
+ *
222
+ * @param string $iniString The loaded ini data
223
+ *
224
+ * @throws void
225
+ */
226
+ private function getIniType(string $iniString): ?string
227
+ {
228
+ if (preg_match('/Type=(.*)/', $iniString, $matches)) {
229
+ if (isset($matches[1])) {
230
+ return $matches[1];
231
+ }
232
+ }
233
+
234
+ return null;
235
+ }
236
+ }
vendor/browscap-php/browscap/browscap-php/src/Helper/ConverterInterface.php ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP\Helper;
6
+
7
+ use BrowscapPHP\Exception\ErrorReadingFileException;
8
+ use BrowscapPHP\Exception\FileNotFoundException;
9
+
10
+ /**
11
+ * patternHelper to convert the ini data, parses the data and stores them into the cache
12
+ */
13
+ interface ConverterInterface
14
+ {
15
+ /**
16
+ * Sets a filesystem instance
17
+ *
18
+ * @throws void
19
+ *
20
+ * @no-named-arguments
21
+ */
22
+ public function setFilesystem(Filesystem $file): void;
23
+
24
+ /**
25
+ * converts a file
26
+ *
27
+ * @throws FileNotFoundException
28
+ * @throws ErrorReadingFileException
29
+ *
30
+ * @no-named-arguments
31
+ */
32
+ public function convertFile(string $iniFile): void;
33
+
34
+ /**
35
+ * converts the string content
36
+ *
37
+ * @throws void
38
+ *
39
+ * @no-named-arguments
40
+ */
41
+ public function convertString(string $iniString): void;
42
+
43
+ /**
44
+ * Parses the ini data to get the version of loaded ini file
45
+ *
46
+ * @param string $iniString The loaded ini data
47
+ *
48
+ * @throws void
49
+ *
50
+ * @no-named-arguments
51
+ */
52
+ public function getIniVersion(string $iniString): int;
53
+
54
+ /**
55
+ * sets the version
56
+ *
57
+ * @throws void
58
+ *
59
+ * @no-named-arguments
60
+ */
61
+ public function setVersion(int $version): void;
62
+
63
+ /**
64
+ * stores the version of the ini file into cache
65
+ *
66
+ * @throws void
67
+ *
68
+ * @no-named-arguments
69
+ */
70
+ public function storeVersion(): void;
71
+ }
vendor/browscap-php/browscap/browscap-php/src/Helper/Exception.php ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP\Helper;
6
+
7
+ use BrowscapPHP\Exception as BaseException;
8
+
9
+ /**
10
+ * Exception to handle errors inside the helpers.
11
+ */
12
+ class Exception extends BaseException
13
+ {
14
+ }
vendor/browscap-php/browscap/browscap-php/src/Helper/Filesystem.php ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP\Helper;
6
+
7
+ use Symfony\Component\Filesystem\Exception\IOException;
8
+ use Symfony\Component\Filesystem\Filesystem as BaseFilesystem;
9
+
10
+ use function basename;
11
+ use function dirname;
12
+ use function file_put_contents;
13
+ use function is_dir;
14
+ use function is_writable;
15
+ use function md5;
16
+ use function sprintf;
17
+ use function time;
18
+ use function unlink;
19
+
20
+ /**
21
+ * Provides basic utility to manipulate the file system.
22
+ */
23
+ class Filesystem extends BaseFilesystem
24
+ {
25
+ /**
26
+ * Atomically dumps content into a file.
27
+ *
28
+ * @param string $filename The file to be written to.
29
+ * @param string $content The data to write into the file.
30
+ * @param int|null $mode The file mode (octal). If null, file permissions are not modified
31
+ * Deprecated since version 2.3.12, to be removed in 3.0.
32
+ *
33
+ * @throws IOException If the file cannot be written to.
34
+ *
35
+ * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
36
+ */
37
+ public function dumpFile(string $filename, $content, ?int $mode = 0666): void
38
+ {
39
+ $dir = dirname($filename);
40
+
41
+ if (! is_dir($dir)) {
42
+ $this->mkdir($dir);
43
+ } elseif (! is_writable($dir)) {
44
+ throw new IOException(sprintf('Unable to write to the "%s" directory.', $dir), 0, null, $dir);
45
+ }
46
+
47
+ // "tempnam" did not work with VFSStream for tests
48
+ $tmpFile = dirname($filename) . '/temp_' . md5(time() . basename($filename));
49
+
50
+ if (@file_put_contents($tmpFile, $content) === false) {
51
+ throw new IOException(sprintf('Failed to write file "%s".', $filename), 0, null, $filename);
52
+ }
53
+
54
+ try {
55
+ $this->rename($tmpFile, $filename, true);
56
+ } catch (IOException $e) {
57
+ unlink($tmpFile);
58
+
59
+ throw $e;
60
+ }
61
+
62
+ if ($mode === null) {
63
+ return;
64
+ }
65
+
66
+ $this->chmod($filename, $mode);
67
+ }
68
+ }
vendor/browscap-php/browscap/browscap-php/src/Helper/IniLoader.php ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP\Helper;
6
+
7
+ use function str_replace;
8
+
9
+ /**
10
+ * class to load the browscap.ini
11
+ */
12
+ final class IniLoader implements IniLoaderInterface
13
+ {
14
+ /**
15
+ * The location from which download the ini file. The placeholder for the file should be represented by a %s.
16
+ */
17
+ private const REMOTE_INI_URI = 'http://browscap.org/stream?q=%q';
18
+
19
+ /**
20
+ * The location to use to check out if a new version of the browscap.ini file is available.
21
+ */
22
+ private const REMOTE_TIME_URI = 'http://browscap.org/version';
23
+
24
+ private const REMOTE_VERSION_URI = 'http://browscap.org/version-number';
25
+
26
+ private string $remoteFilename = IniLoaderInterface::PHP_INI;
27
+
28
+ /**
29
+ * sets the name of the local ini file
30
+ *
31
+ * @param string $name the file name
32
+ *
33
+ * @throws Exception
34
+ */
35
+ public function setRemoteFilename(string $name): void
36
+ {
37
+ if (empty($name)) {
38
+ throw new Exception(
39
+ 'the filename can not be empty',
40
+ Exception::INI_FILE_MISSING
41
+ );
42
+ }
43
+
44
+ $this->remoteFilename = $name;
45
+ }
46
+
47
+ /**
48
+ * returns the of the remote location for updating the ini file
49
+ *
50
+ * @throws void
51
+ */
52
+ public function getRemoteIniUrl(): string
53
+ {
54
+ return str_replace('%q', $this->remoteFilename, self::REMOTE_INI_URI);
55
+ }
56
+
57
+ /**
58
+ * returns the of the remote location for checking the version of the ini file
59
+ *
60
+ * @throws void
61
+ */
62
+ public function getRemoteTimeUrl(): string
63
+ {
64
+ return self::REMOTE_TIME_URI;
65
+ }
66
+
67
+ /**
68
+ * returns the of the remote location for checking the version of the ini file
69
+ *
70
+ * @throws void
71
+ */
72
+ public function getRemoteVersionUrl(): string
73
+ {
74
+ return self::REMOTE_VERSION_URI;
75
+ }
76
+ }
vendor/browscap-php/browscap/browscap-php/src/Helper/IniLoaderInterface.php ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP\Helper;
6
+
7
+ /**
8
+ * class to load the browscap.ini
9
+ */
10
+ interface IniLoaderInterface
11
+ {
12
+ public const PHP_INI_LITE = 'Lite_PHP_BrowscapINI';
13
+ public const PHP_INI_FULL = 'Full_PHP_BrowscapINI';
14
+ public const PHP_INI = 'PHP_BrowscapINI';
15
+
16
+ /**
17
+ * sets the name of the local ini file
18
+ *
19
+ * @param string $name the file name
20
+ *
21
+ * @throws Exception
22
+ *
23
+ * @no-named-arguments
24
+ */
25
+ public function setRemoteFilename(string $name): void;
26
+
27
+ /**
28
+ * returns the of the remote location for updating the ini file
29
+ *
30
+ * @throws void
31
+ *
32
+ * @no-named-arguments
33
+ */
34
+ public function getRemoteIniUrl(): string;
35
+
36
+ /**
37
+ * returns the of the remote location for checking the version of the ini file
38
+ *
39
+ * @throws void
40
+ *
41
+ * @no-named-arguments
42
+ */
43
+ public function getRemoteTimeUrl(): string;
44
+
45
+ /**
46
+ * returns the of the remote location for checking the version of the ini file
47
+ *
48
+ * @throws void
49
+ *
50
+ * @no-named-arguments
51
+ */
52
+ public function getRemoteVersionUrl(): string;
53
+ }
vendor/browscap-php/browscap/browscap-php/src/Helper/Quoter.php ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP\Helper;
6
+
7
+ use UnexpectedValueException;
8
+
9
+ use function preg_match;
10
+ use function preg_quote;
11
+ use function preg_replace;
12
+ use function sprintf;
13
+ use function str_replace;
14
+
15
+ /**
16
+ * class to help quoting strings for using a regex
17
+ */
18
+ final class Quoter implements QuoterInterface
19
+ {
20
+ /**
21
+ * Converts browscap match patterns into preg match patterns.
22
+ *
23
+ * @throws void
24
+ */
25
+ public function pregQuote(string $useragent, string $delimiter = '/'): string
26
+ {
27
+ $pattern = preg_quote($useragent, $delimiter);
28
+
29
+ // the \\x replacement is a fix for "Der gro\xdfe BilderSauger 2.00u" user agent match
30
+ return str_replace(['\*', '\?', '\\x'], ['.*', '.', '\\\\x'], $pattern);
31
+ }
32
+
33
+ /**
34
+ * Reverts the quoting of a pattern.
35
+ *
36
+ * @throws UnexpectedValueException
37
+ */
38
+ public function pregUnQuote(string $pattern): string
39
+ {
40
+ // Fast check, because most parent pattern like 'DefaultProperties' don't need a replacement
41
+ if (! preg_match('/[^a-z\s]/i', $pattern)) {
42
+ return $pattern;
43
+ }
44
+
45
+ $origPattern = $pattern;
46
+
47
+ // Undo the \\x replacement, that is a fix for "Der gro\xdfe BilderSauger 2.00u" user agent match
48
+ // @source https://github.com/browscap/browscap-php
49
+ $pattern = preg_replace(
50
+ ['/(?<!\\\\)\\.\\*/', '/(?<!\\\\)\\./', '/(?<!\\\\)\\\\x/'],
51
+ ['\\*', '\\?', '\\x'],
52
+ $pattern
53
+ );
54
+
55
+ if ($pattern === null) {
56
+ throw new UnexpectedValueException(
57
+ sprintf('an error occured while handling pattern %s', $origPattern)
58
+ );
59
+ }
60
+
61
+ // Undo preg_quote
62
+ return str_replace(
63
+ [
64
+ '\\\\',
65
+ '\\+',
66
+ '\\*',
67
+ '\\?',
68
+ '\\[',
69
+ '\\^',
70
+ '\\]',
71
+ '\\$',
72
+ '\\(',
73
+ '\\)',
74
+ '\\{',
75
+ '\\}',
76
+ '\\=',
77
+ '\\!',
78
+ '\\<',
79
+ '\\>',
80
+ '\\|',
81
+ '\\:',
82
+ '\\-',
83
+ '\\.',
84
+ '\\/',
85
+ '\\#',
86
+ ],
87
+ [
88
+ '\\',
89
+ '+',
90
+ '*',
91
+ '?',
92
+ '[',
93
+ '^',
94
+ ']',
95
+ '$',
96
+ '(',
97
+ ')',
98
+ '{',
99
+ '}',
100
+ '=',
101
+ '!',
102
+ '<',
103
+ '>',
104
+ '|',
105
+ ':',
106
+ '-',
107
+ '.',
108
+ '/',
109
+ '#',
110
+ ],
111
+ $pattern
112
+ );
113
+ }
114
+ }
vendor/browscap-php/browscap/browscap-php/src/Helper/QuoterInterface.php ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP\Helper;
6
+
7
+ use UnexpectedValueException;
8
+
9
+ /**
10
+ * class to help quoting strings for using a regex
11
+ */
12
+ interface QuoterInterface
13
+ {
14
+ /**
15
+ * Converts browscap match patterns into preg match patterns.
16
+ *
17
+ * @throws void
18
+ *
19
+ * @no-named-arguments
20
+ */
21
+ public function pregQuote(string $useragent, string $delimiter = '/'): string;
22
+
23
+ /**
24
+ * Reverts the quoting of a pattern.
25
+ *
26
+ * @throws UnexpectedValueException
27
+ *
28
+ * @no-named-arguments
29
+ */
30
+ public function pregUnQuote(string $pattern): string;
31
+ }
vendor/browscap-php/browscap/browscap-php/src/Helper/Support.php ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP\Helper;
6
+
7
+ use function array_key_exists;
8
+ use function strip_tags;
9
+ use function trim;
10
+ use function urldecode;
11
+
12
+ /**
13
+ * class to help getting the user agent
14
+ */
15
+ final class Support implements SupportInterface
16
+ {
17
+ /** @var string[] */
18
+ private array $source = [];
19
+
20
+ /**
21
+ * The HTTP Headers that this application will look through to find the best
22
+ * User Agent, if one is not specified
23
+ *
24
+ * @var string[]
25
+ */
26
+ private array $userAgentHeaders = [
27
+ 'HTTP_X_DEVICE_USER_AGENT',
28
+ 'HTTP_X_ORIGINAL_USER_AGENT',
29
+ 'HTTP_X_OPERAMINI_PHONE_UA',
30
+ 'HTTP_X_SKYFIRE_PHONE',
31
+ 'HTTP_X_BOLT_PHONE_UA',
32
+ 'HTTP_USER_AGENT',
33
+ ];
34
+
35
+ /**
36
+ * @param string[]|null $source
37
+ *
38
+ * @throws void
39
+ */
40
+ public function __construct(?array $source = null)
41
+ {
42
+ if ($source === null) {
43
+ $source = [];
44
+ }
45
+
46
+ $this->source = $source;
47
+ }
48
+
49
+ /**
50
+ * detect the useragent
51
+ *
52
+ * @throws void
53
+ */
54
+ public function getUserAgent(): string
55
+ {
56
+ $userAgent = '';
57
+
58
+ foreach ($this->userAgentHeaders as $header) {
59
+ if (
60
+ array_key_exists($header, $this->source)
61
+ && $this->source[$header]
62
+ ) {
63
+ $userAgent = $this->cleanParam($this->source[$header]);
64
+
65
+ break;
66
+ }
67
+ }
68
+
69
+ return $userAgent;
70
+ }
71
+
72
+ /**
73
+ * clean Parameters taken from GET or POST Variables
74
+ *
75
+ * @param string $param the value to be cleaned
76
+ *
77
+ * @throws void
78
+ */
79
+ private function cleanParam(string $param): string
80
+ {
81
+ return strip_tags(trim(urldecode($param)));
82
+ }
83
+ }
vendor/browscap-php/browscap/browscap-php/src/Helper/SupportInterface.php ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP\Helper;
6
+
7
+ /**
8
+ * class to help getting the user agent
9
+ */
10
+ interface SupportInterface
11
+ {
12
+ /**
13
+ * detect the useragent
14
+ *
15
+ * @throws void
16
+ *
17
+ * @no-named-arguments
18
+ */
19
+ public function getUserAgent(): string;
20
+ }
vendor/browscap-php/browscap/browscap-php/src/IniParser/IniParser.php ADDED
@@ -0,0 +1,294 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP\IniParser;
6
+
7
+ use BrowscapPHP\Data\PropertyFormatter;
8
+ use BrowscapPHP\Data\PropertyHolder;
9
+ use BrowscapPHP\Helper\Quoter;
10
+ use BrowscapPHP\Parser\Helper\Pattern;
11
+ use BrowscapPHP\Parser\Helper\SubKey;
12
+ use Generator;
13
+ use JsonException;
14
+ use OutOfRangeException;
15
+ use UnexpectedValueException;
16
+
17
+ use function array_chunk;
18
+ use function array_flip;
19
+ use function array_key_exists;
20
+ use function array_keys;
21
+ use function implode;
22
+ use function in_array;
23
+ use function is_array;
24
+ use function json_encode;
25
+ use function krsort;
26
+ use function ksort;
27
+ use function parse_ini_string;
28
+ use function preg_match_all;
29
+ use function preg_replace;
30
+ use function preg_split;
31
+ use function sprintf;
32
+ use function str_repeat;
33
+ use function str_replace;
34
+ use function strlen;
35
+ use function strpbrk;
36
+ use function strtolower;
37
+ use function usort;
38
+
39
+ use const INI_SCANNER_RAW;
40
+ use const JSON_HEX_AMP;
41
+ use const JSON_HEX_APOS;
42
+ use const JSON_HEX_QUOT;
43
+ use const JSON_HEX_TAG;
44
+ use const JSON_THROW_ON_ERROR;
45
+
46
+ /**
47
+ * Ini parser class (compatible with PHP 5.3+)
48
+ */
49
+ final class IniParser implements ParserInterface
50
+ {
51
+ /**
52
+ * Number of pattern to combine for a faster regular expression search.
53
+ *
54
+ * @important The number of patterns that can be processed in one step
55
+ * is limited by the internal regular expression limits.
56
+ */
57
+ private const COUNT_PATTERN = 50;
58
+
59
+ /**
60
+ * Creates new ini part cache files
61
+ *
62
+ * @throws OutOfRangeException
63
+ * @throws UnexpectedValueException
64
+ */
65
+ public function createIniParts(string $content): Generator
66
+ {
67
+ // get all patterns from the ini file in the correct order,
68
+ // so that we can calculate with index number of the resulting array,
69
+ // which part to use when the ini file is splitted into its sections.
70
+ preg_match_all('/(?<=\[)(?:[^\r\n]+)(?=\])/m', $content, $patternPositions);
71
+ $patternPositions = $patternPositions[0];
72
+
73
+ // split the ini file into sections and save the data in one line with a hash of the beloging
74
+ // pattern (filtered in the previous step)
75
+ $iniParts = preg_split('/\[[^\r\n]+\]/', $content);
76
+ if ($iniParts === false) {
77
+ throw new UnexpectedValueException('an error occured while splitting content into parts');
78
+ }
79
+
80
+ $contents = [];
81
+
82
+ $propertyFormatter = new PropertyFormatter(new PropertyHolder());
83
+
84
+ foreach ($patternPositions as $position => $pattern) {
85
+ $pattern = strtolower($pattern);
86
+ $patternhash = Pattern::getHashForParts($pattern);
87
+ $subkey = SubKey::getIniPartCacheSubKey($patternhash);
88
+
89
+ if (! isset($contents[$subkey])) {
90
+ $contents[$subkey] = [];
91
+ }
92
+
93
+ if (! array_key_exists($position + 1, $iniParts)) {
94
+ throw new OutOfRangeException(sprintf('could not find position %d inside iniparts', $position + 1));
95
+ }
96
+
97
+ $browserProperties = parse_ini_string($iniParts[$position + 1], false, INI_SCANNER_RAW);
98
+
99
+ if ($browserProperties === false) {
100
+ throw new UnexpectedValueException(sprintf('could ini parse position %d inside iniparts', $position + 1));
101
+ }
102
+
103
+ foreach (array_keys($browserProperties) as $property) {
104
+ $browserProperties[$property] = $propertyFormatter->formatPropertyValue(
105
+ $browserProperties[$property],
106
+ $property
107
+ );
108
+ }
109
+
110
+ try {
111
+ // the position has to be moved by one, because the header of the ini file
112
+ // is also returned as a part
113
+ $contents[$subkey][] = $patternhash . "\t" . json_encode(
114
+ $browserProperties,
115
+ JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP | JSON_THROW_ON_ERROR
116
+ );
117
+ } catch (JsonException $e) {
118
+ throw new UnexpectedValueException('json encoding content failed', 0, $e);
119
+ }
120
+ }
121
+
122
+ unset($patternPositions, $iniParts);
123
+
124
+ $subkeys = array_flip(SubKey::getAllIniPartCacheSubKeys());
125
+ foreach ($contents as $subkey => $cacheContent) {
126
+ yield $subkey => $cacheContent;
127
+
128
+ unset($subkeys[$subkey]);
129
+ }
130
+
131
+ foreach (array_keys($subkeys) as $subkey) {
132
+ $subkey = (string) $subkey;
133
+
134
+ yield $subkey => '';
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Creates new pattern cache files
140
+ *
141
+ * @throws void
142
+ */
143
+ public function createPatterns(string $content): Generator
144
+ {
145
+ // get all relevant patterns from the INI file
146
+ // - containing "*" or "?"
147
+ // - not containing "*" or "?", but not having a comment
148
+ preg_match_all(
149
+ '/(?<=\[)(?:[^\r\n]*[?*][^\r\n]*)(?=\])|(?<=\[)(?:[^\r\n*?]+)(?=\])(?![^\[]*Comment=)/m',
150
+ $content,
151
+ $matches
152
+ );
153
+
154
+ if (empty($matches[0]) || ! is_array($matches[0])) {
155
+ yield '' => '';
156
+
157
+ return;
158
+ }
159
+
160
+ $quoterHelper = new Quoter();
161
+ $matches = $matches[0];
162
+ usort($matches, [$this, 'compareBcStrings']);
163
+
164
+ // build an array to structure the data. this requires some memory, but we need this step to be able to
165
+ // sort the data in the way we need it (see below).
166
+ $data = [];
167
+
168
+ foreach ($matches as $pattern) {
169
+ if ($pattern === 'GJK_Browscap_Version') {
170
+ continue;
171
+ }
172
+
173
+ $pattern = strtolower($pattern);
174
+ $patternhash = Pattern::getHashForPattern($pattern, false)[0];
175
+ $tmpLength = Pattern::getPatternLength($pattern);
176
+
177
+ // special handling of default entry
178
+ if ($tmpLength === 0) {
179
+ $patternhash = str_repeat('z', 32);
180
+ }
181
+
182
+ if (! isset($data[$patternhash])) {
183
+ $data[$patternhash] = [];
184
+ }
185
+
186
+ if (! isset($data[$patternhash][$tmpLength])) {
187
+ $data[$patternhash][$tmpLength] = [];
188
+ }
189
+
190
+ $pattern = $quoterHelper->pregQuote($pattern);
191
+
192
+ // Check if the pattern contains digits - in this case we replace them with a digit regular expression,
193
+ // so that very similar patterns (e.g. only with different browser version numbers) can be compressed.
194
+ // This helps to speed up the first (and most expensive) part of the pattern search a lot.
195
+ if (strpbrk($pattern, '0123456789') !== false) {
196
+ $compressedPattern = preg_replace('/\d/', '[\d]', $pattern);
197
+
198
+ if (! in_array($compressedPattern, $data[$patternhash][$tmpLength])) {
199
+ $data[$patternhash][$tmpLength][] = $compressedPattern;
200
+ }
201
+ } else {
202
+ $data[$patternhash][$tmpLength][] = $pattern;
203
+ }
204
+ }
205
+
206
+ unset($matches);
207
+
208
+ // sorting of the data is important to check the patterns later in the correct order, because
209
+ // we need to check the most specific (=longest) patterns first, and the least specific
210
+ // (".*" for "Default Browser") last.
211
+ //
212
+ // sort by pattern start to group them
213
+ ksort($data);
214
+ // and then by pattern length (longest first)
215
+ foreach (array_keys($data) as $key) {
216
+ krsort($data[$key]);
217
+ }
218
+
219
+ // write optimized file (grouped by the first character of the has, generated from the pattern
220
+ // start) with multiple patterns joined by tabs. this is to speed up loading of the data (small
221
+ // array with pattern strings instead of an large array with single patterns) and also enables
222
+ // us to search for multiple patterns in one preg_match call for a fast first search
223
+ // (3-10 faster), followed by a detailed search for each single pattern.
224
+ $contents = [];
225
+ foreach ($data as $patternhash => $tmpEntries) {
226
+ if (empty($tmpEntries)) {
227
+ continue;
228
+ }
229
+
230
+ $subkey = SubKey::getPatternCacheSubkey($patternhash);
231
+
232
+ if (! isset($contents[$subkey])) {
233
+ $contents[$subkey] = [];
234
+ }
235
+
236
+ foreach ($tmpEntries as $tmpLength => $tmpPatterns) {
237
+ if (empty($tmpPatterns)) {
238
+ continue;
239
+ }
240
+
241
+ $chunks = array_chunk($tmpPatterns, self::COUNT_PATTERN);
242
+
243
+ foreach ($chunks as $chunk) {
244
+ $contents[$subkey][] = $patternhash . "\t" . $tmpLength . "\t" . implode("\t", $chunk);
245
+ }
246
+ }
247
+ }
248
+
249
+ unset($data);
250
+
251
+ $subkeys = SubKey::getAllPatternCacheSubkeys();
252
+ foreach ($contents as $subkey => $content) {
253
+ yield $subkey => $content;
254
+
255
+ unset($subkeys[$subkey]);
256
+ }
257
+
258
+ foreach (array_keys($subkeys) as $subkey) {
259
+ $subkey = (string) $subkey;
260
+
261
+ yield $subkey => '';
262
+ }
263
+ }
264
+
265
+ /**
266
+ * @throws void
267
+ */
268
+ private function compareBcStrings(string $a, string $b): int
269
+ {
270
+ $aLength = strlen($a);
271
+ $bLength = strlen($b);
272
+
273
+ if ($aLength > $bLength) {
274
+ return -1;
275
+ }
276
+
277
+ if ($aLength < $bLength) {
278
+ return 1;
279
+ }
280
+
281
+ $aLength = strlen(str_replace(['*', '?'], '', $a));
282
+ $bLength = strlen(str_replace(['*', '?'], '', $b));
283
+
284
+ if ($aLength > $bLength) {
285
+ return -1;
286
+ }
287
+
288
+ if ($aLength < $bLength) {
289
+ return 1;
290
+ }
291
+
292
+ return 0;
293
+ }
294
+ }
vendor/browscap-php/browscap/browscap-php/src/IniParser/ParserInterface.php ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP\IniParser;
6
+
7
+ use Generator;
8
+ use InvalidArgumentException;
9
+ use JsonException;
10
+ use OutOfRangeException;
11
+ use UnexpectedValueException;
12
+
13
+ /**
14
+ * Ini parser class (compatible with PHP 5.3+)
15
+ */
16
+ interface ParserInterface
17
+ {
18
+ /**
19
+ * Creates new ini part cache files
20
+ *
21
+ * @throws OutOfRangeException
22
+ * @throws UnexpectedValueException
23
+ * @throws InvalidArgumentException
24
+ * @throws JsonException
25
+ *
26
+ * @no-named-arguments
27
+ */
28
+ public function createIniParts(string $content): Generator;
29
+
30
+ /**
31
+ * Creates new pattern cache files
32
+ *
33
+ * @throws void
34
+ *
35
+ * @no-named-arguments
36
+ */
37
+ public function createPatterns(string $content): Generator;
38
+ }
vendor/browscap-php/browscap/browscap-php/src/Parser/Helper/GetData.php ADDED
@@ -0,0 +1,166 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP\Parser\Helper;
6
+
7
+ use BrowscapPHP\Cache\BrowscapCacheInterface;
8
+ use BrowscapPHP\Data\PropertyFormatter;
9
+ use BrowscapPHP\Data\PropertyHolder;
10
+ use BrowscapPHP\Helper\QuoterInterface;
11
+ use JsonException;
12
+ use Psr\SimpleCache\InvalidArgumentException;
13
+ use UnexpectedValueException;
14
+
15
+ use function array_keys;
16
+ use function assert;
17
+ use function count;
18
+ use function explode;
19
+ use function is_array;
20
+ use function is_string;
21
+ use function json_decode;
22
+ use function sprintf;
23
+ use function strtolower;
24
+
25
+ use const JSON_THROW_ON_ERROR;
26
+
27
+ /**
28
+ * extracts the data and the data for theses pattern from the ini content, optimized for PHP 5.5+
29
+ */
30
+ final class GetData implements GetDataInterface
31
+ {
32
+ /**
33
+ * The cache instance
34
+ */
35
+ private BrowscapCacheInterface $cache;
36
+
37
+ private QuoterInterface $quoter;
38
+
39
+ /**
40
+ * @throws void
41
+ */
42
+ public function __construct(BrowscapCacheInterface $cache, QuoterInterface $quoter)
43
+ {
44
+ $this->cache = $cache;
45
+ $this->quoter = $quoter;
46
+ }
47
+
48
+ /**
49
+ * Gets the settings for a given pattern (method calls itself to
50
+ * get the data from the parent patterns)
51
+ *
52
+ * @param string[] $settings
53
+ *
54
+ * @return string[]
55
+ *
56
+ * @throws UnexpectedValueException
57
+ */
58
+ public function getSettings(string $pattern, array $settings = []): array
59
+ {
60
+ // The pattern has been pre-quoted on generation to speed up the pattern search,
61
+ // but for this check we need the unquoted version
62
+ $unquotedPattern = $this->quoter->pregUnQuote($pattern);
63
+
64
+ // Try to get settings for the pattern
65
+ $addedSettings = $this->getIniPart($unquotedPattern);
66
+
67
+ // set some additional data
68
+ if (count($settings) === 0) {
69
+ // The optimization with replaced digits get can now result in setting searches, for which we
70
+ // won't find a result - so only add the pattern information, is settings have been found.
71
+ //
72
+ // If not an empty array will be returned and the calling function can easily check if a pattern
73
+ // has been found.
74
+ if (0 < count($addedSettings)) {
75
+ $settings['browser_name_regex'] = '/^' . $pattern . '$/';
76
+ $settings['browser_name_pattern'] = $unquotedPattern;
77
+ }
78
+ }
79
+
80
+ // check if parent pattern set, only keep the first one
81
+ $parentPattern = null;
82
+
83
+ if (isset($addedSettings['Parent'])) {
84
+ $parentPattern = $addedSettings['Parent'];
85
+
86
+ if (isset($settings['Parent'])) {
87
+ unset($addedSettings['Parent']);
88
+ }
89
+ }
90
+
91
+ // merge settings
92
+ $settings += $addedSettings;
93
+
94
+ if (is_string($parentPattern)) {
95
+ return $this->getSettings($this->quoter->pregQuote($parentPattern), $settings);
96
+ }
97
+
98
+ return $settings;
99
+ }
100
+
101
+ /**
102
+ * Gets the relevant part (array of settings) of the ini file for a given pattern.
103
+ *
104
+ * @return string[]
105
+ *
106
+ * @throws void
107
+ */
108
+ private function getIniPart(string $pattern): array
109
+ {
110
+ $pattern = strtolower($pattern);
111
+ $patternhash = Pattern::getHashForParts($pattern);
112
+ $subkey = SubKey::getIniPartCacheSubKey($patternhash);
113
+
114
+ try {
115
+ if (! $this->cache->hasItem('browscap.iniparts.' . $subkey, true)) {
116
+ return [];
117
+ }
118
+ } catch (InvalidArgumentException $e) {
119
+ return [];
120
+ }
121
+
122
+ $success = null;
123
+
124
+ try {
125
+ $file = $this->cache->getItem('browscap.iniparts.' . $subkey, true, $success);
126
+ } catch (InvalidArgumentException $e) {
127
+ return [];
128
+ }
129
+
130
+ if (! $success) {
131
+ return [];
132
+ }
133
+
134
+ if (! is_array($file) || ! count($file)) {
135
+ return [];
136
+ }
137
+
138
+ $propertyFormatter = new PropertyFormatter(new PropertyHolder());
139
+ $return = [];
140
+
141
+ foreach ($file as $buffer) {
142
+ [$tmpBuffer, $patterns] = explode("\t", $buffer, 2);
143
+
144
+ if ($tmpBuffer === $patternhash) {
145
+ try {
146
+ $return = json_decode($patterns, true, 512, JSON_THROW_ON_ERROR);
147
+ } catch (JsonException $e) {
148
+ return [];
149
+ }
150
+
151
+ assert(is_array($return));
152
+
153
+ foreach (array_keys($return) as $property) {
154
+ $return[$property] = $propertyFormatter->formatPropertyValue(
155
+ $return[$property],
156
+ (string) $property
157
+ );
158
+ }
159
+
160
+ break;
161
+ }
162
+ }
163
+
164
+ return $return;
165
+ }
166
+ }
vendor/browscap-php/browscap/browscap-php/src/Parser/Helper/GetDataInterface.php ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP\Parser\Helper;
6
+
7
+ use UnexpectedValueException;
8
+
9
+ /**
10
+ * interface for the parser dataHelper
11
+ */
12
+ interface GetDataInterface
13
+ {
14
+ /**
15
+ * Gets the settings for a given pattern (method calls itself to
16
+ * get the data from the parent patterns)
17
+ *
18
+ * @param string[] $settings
19
+ *
20
+ * @return string[]
21
+ *
22
+ * @throws UnexpectedValueException
23
+ *
24
+ * @no-named-arguments
25
+ */
26
+ public function getSettings(string $pattern, array $settings = []): array;
27
+ }
vendor/browscap-php/browscap/browscap-php/src/Parser/Helper/GetPattern.php ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP\Parser\Helper;
6
+
7
+ use BrowscapPHP\Cache\BrowscapCacheInterface;
8
+ use Generator;
9
+ use Psr\SimpleCache\InvalidArgumentException;
10
+
11
+ use function count;
12
+ use function explode;
13
+ use function is_array;
14
+ use function sprintf;
15
+ use function str_repeat;
16
+ use function strlen;
17
+ use function trim;
18
+
19
+ /**
20
+ * extracts the pattern and the data for theses pattern from the ini content, optimized for PHP 5.5+
21
+ */
22
+ class GetPattern implements GetPatternInterface
23
+ {
24
+ /**
25
+ * The cache instance
26
+ */
27
+ private BrowscapCacheInterface $cache;
28
+
29
+ /**
30
+ * @throws void
31
+ */
32
+ public function __construct(BrowscapCacheInterface $cache)
33
+ {
34
+ $this->cache = $cache;
35
+ }
36
+
37
+ /**
38
+ * Gets some possible patterns that have to be matched against the user agent. With the given
39
+ * user agent string, we can optimize the search for potential patterns:
40
+ * - We check the first characters of the user agent (or better: a hash, generated from it)
41
+ * - We compare the length of the pattern with the length of the user agent
42
+ * (the pattern cannot be longer than the user agent!)
43
+ *
44
+ * @throws void
45
+ */
46
+ public function getPatterns(string $userAgent): Generator
47
+ {
48
+ $starts = Pattern::getHashForPattern($userAgent, true);
49
+ $length = strlen($userAgent);
50
+
51
+ // add special key to fall back to the default browser
52
+ $starts[] = str_repeat('z', 32);
53
+
54
+ // get patterns, first for the given browser and if that is not found,
55
+ // for the default browser (with a special key)
56
+ foreach ($starts as $tmpStart) {
57
+ $tmpSubkey = SubKey::getPatternCacheSubkey($tmpStart);
58
+
59
+ try {
60
+ if (! $this->cache->hasItem('browscap.patterns.' . $tmpSubkey, true)) {
61
+ continue;
62
+ }
63
+ } catch (InvalidArgumentException $e) {
64
+ continue;
65
+ }
66
+
67
+ $success = null;
68
+
69
+ try {
70
+ $file = $this->cache->getItem('browscap.patterns.' . $tmpSubkey, true, $success);
71
+ } catch (InvalidArgumentException $e) {
72
+ continue;
73
+ }
74
+
75
+ if (! $success) {
76
+ continue;
77
+ }
78
+
79
+ if (! is_array($file) || ! count($file)) {
80
+ continue;
81
+ }
82
+
83
+ $found = false;
84
+
85
+ foreach ($file as $buffer) {
86
+ [$tmpBuffer, $len, $patterns] = explode("\t", $buffer, 3);
87
+
88
+ if ($tmpBuffer === $tmpStart) {
89
+ if ($len <= $length) {
90
+ yield trim($patterns);
91
+ }
92
+
93
+ $found = true;
94
+ } elseif ($found === true) {
95
+ break;
96
+ }
97
+ }
98
+ }
99
+
100
+ yield '';
101
+ }
102
+ }
vendor/browscap-php/browscap/browscap-php/src/Parser/Helper/GetPatternInterface.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP\Parser\Helper;
6
+
7
+ use Generator;
8
+
9
+ /**
10
+ * interface for the parser patternHelper
11
+ */
12
+ interface GetPatternInterface
13
+ {
14
+ /**
15
+ * Gets some possible patterns that have to be matched against the user agent. With the given
16
+ * user agent string, we can optimize the search for potential patterns:
17
+ * - We check the first characters of the user agent (or better: a hash, generated from it)
18
+ * - We compare the length of the pattern with the length of the user agent
19
+ * (the pattern cannot be longer than the user agent!)
20
+ *
21
+ * @throws void
22
+ *
23
+ * @no-named-arguments
24
+ */
25
+ public function getPatterns(string $userAgent): Generator;
26
+ }
vendor/browscap-php/browscap/browscap-php/src/Parser/Helper/Pattern.php ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP\Parser\Helper;
6
+
7
+ use function md5;
8
+ use function preg_match;
9
+ use function str_replace;
10
+ use function strlen;
11
+ use function substr;
12
+
13
+ /**
14
+ * includes general functions for the work with patterns
15
+ */
16
+ final class Pattern
17
+ {
18
+ /**
19
+ * @throws void
20
+ */
21
+ private function __construct()
22
+ {
23
+ }
24
+
25
+ /**
26
+ * Gets a hash or an array of hashes from the first characters of a pattern/user agent, that can
27
+ * be used for a fast comparison, by comparing only the hashes, without having to match the
28
+ * complete pattern against the user agent.
29
+ *
30
+ * With the variants options, all variants from the maximum number of pattern characters to one
31
+ * character will be returned. This is required in some cases, the a placeholder is used very
32
+ * early in the pattern.
33
+ *
34
+ * Example:
35
+ *
36
+ * Pattern: "Mozilla/* (Nintendo 3DS; *) Version/*"
37
+ * User agent: "Mozilla/5.0 (Nintendo 3DS; U; ; en) Version/1.7567.US"
38
+ *
39
+ * In this case the has for the pattern is created for "Mozilla/" while the pattern
40
+ * for the hash for user agent is created for "Mozilla/5.0". The variants option
41
+ * results in an array with hashes for "Mozilla/5.0", "Mozilla/5.", "Mozilla/5",
42
+ * "Mozilla/" ... "M", so that the pattern hash is included.
43
+ *
44
+ * @return string[]
45
+ *
46
+ * @throws void
47
+ */
48
+ public static function getHashForPattern(string $pattern, bool $variants = false): array
49
+ {
50
+ $regex = '/^([^\.\*\?\s\r\n\\\\]+).*$/';
51
+ $pattern = substr($pattern, 0, 32);
52
+ $matches = [];
53
+
54
+ if (! preg_match($regex, $pattern, $matches)) {
55
+ return [md5('')];
56
+ }
57
+
58
+ if (! isset($matches[1])) {
59
+ return [md5('')];
60
+ }
61
+
62
+ $string = $matches[1];
63
+
64
+ if ($variants === true) {
65
+ $patternStarts = [];
66
+
67
+ for ($i = strlen($string); 1 <= $i; --$i) {
68
+ $string = substr($string, 0, $i);
69
+ $patternStarts[] = md5($string);
70
+ }
71
+
72
+ // Add empty pattern start to include patterns that start with "*",
73
+ // e.g. "*FAST Enterprise Crawler*"
74
+ $patternStarts[] = md5('');
75
+
76
+ return $patternStarts;
77
+ }
78
+
79
+ return [md5($string)];
80
+ }
81
+
82
+ /**
83
+ * returns a hash for one pattern
84
+ *
85
+ * @throws void
86
+ */
87
+ public static function getHashForParts(string $pattern): string
88
+ {
89
+ return md5($pattern);
90
+ }
91
+
92
+ /**
93
+ * Gets the minimum length of the patern (used in the getPatterns() method to
94
+ * check against the user agent length)
95
+ *
96
+ * @throws void
97
+ */
98
+ public static function getPatternLength(string $pattern): int
99
+ {
100
+ return strlen(str_replace('*', '', $pattern));
101
+ }
102
+ }
vendor/browscap-php/browscap/browscap-php/src/Parser/Helper/SubKey.php ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP\Parser\Helper;
6
+
7
+ /**
8
+ * includes general functions for the work with patterns
9
+ */
10
+ final class SubKey
11
+ {
12
+ /**
13
+ * @throws void
14
+ */
15
+ private function __construct()
16
+ {
17
+ }
18
+
19
+ /**
20
+ * Gets the subkey for the pattern cache file, generated from the given string
21
+ *
22
+ * @throws void
23
+ */
24
+ public static function getPatternCacheSubkey(string $string): string
25
+ {
26
+ return $string[0] . $string[1];
27
+ }
28
+
29
+ /**
30
+ * Gets all subkeys for the pattern cache files
31
+ *
32
+ * @return string[]
33
+ *
34
+ * @throws void
35
+ */
36
+ public static function getAllPatternCacheSubkeys(): array
37
+ {
38
+ $subkeys = [];
39
+ $chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
40
+
41
+ foreach ($chars as $charOne) {
42
+ foreach ($chars as $charTwo) {
43
+ $subkeys[$charOne . $charTwo] = '';
44
+ }
45
+ }
46
+
47
+ return $subkeys;
48
+ }
49
+
50
+ /**
51
+ * Gets the sub key for the ini parts cache file, generated from the given string
52
+ *
53
+ * @throws void
54
+ */
55
+ public static function getIniPartCacheSubKey(string $string): string
56
+ {
57
+ return $string[0] . $string[1] . $string[2];
58
+ }
59
+
60
+ /**
61
+ * Gets all sub keys for the inipart cache files
62
+ *
63
+ * @return string[]
64
+ *
65
+ * @throws void
66
+ */
67
+ public static function getAllIniPartCacheSubKeys(): array
68
+ {
69
+ $subKeys = [];
70
+ $chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
71
+
72
+ foreach ($chars as $charOne) {
73
+ foreach ($chars as $charTwo) {
74
+ foreach ($chars as $charThree) {
75
+ $subKeys[] = $charOne . $charTwo . $charThree;
76
+ }
77
+ }
78
+ }
79
+
80
+ return $subKeys;
81
+ }
82
+ }
vendor/browscap-php/browscap/browscap-php/src/Parser/Ini.php ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP\Parser;
6
+
7
+ use BrowscapPHP\Formatter\FormatterInterface;
8
+ use BrowscapPHP\Parser\Helper\GetDataInterface;
9
+ use BrowscapPHP\Parser\Helper\GetPatternInterface;
10
+ use UnexpectedValueException;
11
+
12
+ use function array_shift;
13
+ use function count;
14
+ use function preg_match;
15
+ use function str_replace;
16
+ use function strpos;
17
+ use function strtok;
18
+ use function strtolower;
19
+ use function substr_replace;
20
+
21
+ /**
22
+ * Ini parser class (compatible with PHP 5.3+)
23
+ */
24
+ final class Ini implements ParserInterface
25
+ {
26
+ private Helper\GetPatternInterface $patternHelper;
27
+
28
+ private Helper\GetDataInterface $dataHelper;
29
+
30
+ /**
31
+ * Formatter to use
32
+ */
33
+ private FormatterInterface $formatter;
34
+
35
+ /**
36
+ * @throws void
37
+ */
38
+ public function __construct(
39
+ GetPatternInterface $patternHelper,
40
+ GetDataInterface $dataHelper,
41
+ FormatterInterface $formatter
42
+ ) {
43
+ $this->patternHelper = $patternHelper;
44
+ $this->dataHelper = $dataHelper;
45
+ $this->formatter = $formatter;
46
+ }
47
+
48
+ /**
49
+ * Gets the browser data formatr for the given user agent
50
+ * (or null if no data avaailble, no even the default browser)
51
+ *
52
+ * @throws UnexpectedValueException
53
+ */
54
+ public function getBrowser(string $userAgent): ?FormatterInterface
55
+ {
56
+ $userAgent = strtolower($userAgent);
57
+ $formatter = null;
58
+
59
+ foreach ($this->patternHelper->getPatterns($userAgent) as $patterns) {
60
+ $patternToMatch = '/^(?:' . str_replace("\t", ')|(?:', $patterns) . ')$/i';
61
+
62
+ if (! preg_match($patternToMatch, $userAgent)) {
63
+ continue;
64
+ }
65
+
66
+ // strtok() requires less memory than explode()
67
+ $pattern = strtok($patterns, "\t");
68
+
69
+ while ($pattern !== false) {
70
+ $pattern = str_replace('[\d]', '(\d)', $pattern);
71
+ $quotedPattern = '/^' . $pattern . '$/i';
72
+ $matches = [];
73
+
74
+ if (preg_match($quotedPattern, $userAgent, $matches)) {
75
+ // Insert the digits back into the pattern, so that we can search the settings for it
76
+ if (1 < count($matches)) {
77
+ array_shift($matches);
78
+ foreach ($matches as $oneMatch) {
79
+ $numPos = (int) strpos($pattern, '(\d)');
80
+ $pattern = substr_replace($pattern, $oneMatch, $numPos, 4);
81
+ }
82
+ }
83
+
84
+ // Try to get settings - as digits have been replaced to speed up the pattern search (up to 90 faster),
85
+ // we won't always find the data in the first step - so check if settings have been found and if not,
86
+ // search for the next pattern.
87
+ $settings = $this->dataHelper->getSettings($pattern);
88
+
89
+ if (0 < count($settings)) {
90
+ $formatter = $this->formatter;
91
+ $formatter->setData($settings);
92
+
93
+ break 2;
94
+ }
95
+ }
96
+
97
+ $pattern = strtok("\t");
98
+ }
99
+ }
100
+
101
+ return $formatter;
102
+ }
103
+ }
vendor/browscap-php/browscap/browscap-php/src/Parser/ParserInterface.php ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace BrowscapPHP\Parser;
6
+
7
+ use BrowscapPHP\Formatter\FormatterInterface;
8
+ use UnexpectedValueException;
9
+
10
+ /**
11
+ * the interface for the ini parser class
12
+ */
13
+ interface ParserInterface
14
+ {
15
+ /**
16
+ * Gets the browser data formatter for the given user agent
17
+ * (or null if no data available, no even the default browser)
18
+ *
19
+ * @throws UnexpectedValueException
20
+ *
21
+ * @no-named-arguments
22
+ */
23
+ public function getBrowser(string $userAgent): ?FormatterInterface;
24
+ }
vendor/browscap-php/composer/ClassLoader.php ADDED
@@ -0,0 +1,572 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Composer.
5
+ *
6
+ * (c) Nils Adermann <naderman@naderman.de>
7
+ * Jordi Boggiano <j.boggiano@seld.be>
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+
13
+ namespace Composer\Autoload;
14
+
15
+ /**
16
+ * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
17
+ *
18
+ * $loader = new \Composer\Autoload\ClassLoader();
19
+ *
20
+ * // register classes with namespaces
21
+ * $loader->add('Symfony\Component', __DIR__.'/component');
22
+ * $loader->add('Symfony', __DIR__.'/framework');
23
+ *
24
+ * // activate the autoloader
25
+ * $loader->register();
26
+ *
27
+ * // to enable searching the include path (eg. for PEAR packages)
28
+ * $loader->setUseIncludePath(true);
29
+ *
30
+ * In this example, if you try to use a class in the Symfony\Component
31
+ * namespace or one of its children (Symfony\Component\Console for instance),
32
+ * the autoloader will first look for the class under the component/
33
+ * directory, and it will then fallback to the framework/ directory if not
34
+ * found before giving up.
35
+ *
36
+ * This class is loosely based on the Symfony UniversalClassLoader.
37
+ *
38
+ * @author Fabien Potencier <fabien@symfony.com>
39
+ * @author Jordi Boggiano <j.boggiano@seld.be>
40
+ * @see https://www.php-fig.org/psr/psr-0/
41
+ * @see https://www.php-fig.org/psr/psr-4/
42
+ */
43
+ class ClassLoader
44
+ {
45
+ /** @var ?string */
46
+ private $vendorDir;
47
+
48
+ // PSR-4
49
+ /**
50
+ * @var array[]
51
+ * @psalm-var array<string, array<string, int>>
52
+ */
53
+ private $prefixLengthsPsr4 = array();
54
+ /**
55
+ * @var array[]
56
+ * @psalm-var array<string, array<int, string>>
57
+ */
58
+ private $prefixDirsPsr4 = array();
59
+ /**
60
+ * @var array[]
61
+ * @psalm-var array<string, string>
62
+ */
63
+ private $fallbackDirsPsr4 = array();
64
+
65
+ // PSR-0
66
+ /**
67
+ * @var array[]
68
+ * @psalm-var array<string, array<string, string[]>>
69
+ */
70
+ private $prefixesPsr0 = array();
71
+ /**
72
+ * @var array[]
73
+ * @psalm-var array<string, string>
74
+ */
75
+ private $fallbackDirsPsr0 = array();
76
+
77
+ /** @var bool */
78
+ private $useIncludePath = false;
79
+
80
+ /**
81
+ * @var string[]
82
+ * @psalm-var array<string, string>
83
+ */
84
+ private $classMap = array();
85
+
86
+ /** @var bool */
87
+ private $classMapAuthoritative = false;
88
+
89
+ /**
90
+ * @var bool[]
91
+ * @psalm-var array<string, bool>
92
+ */
93
+ private $missingClasses = array();
94
+
95
+ /** @var ?string */
96
+ private $apcuPrefix;
97
+
98
+ /**
99
+ * @var self[]
100
+ */
101
+ private static $registeredLoaders = array();
102
+
103
+ /**
104
+ * @param ?string $vendorDir
105
+ */
106
+ public function __construct($vendorDir = null)
107
+ {
108
+ $this->vendorDir = $vendorDir;
109
+ }
110
+
111
+ /**
112
+ * @return string[]
113
+ */
114
+ public function getPrefixes()
115
+ {
116
+ if (!empty($this->prefixesPsr0)) {
117
+ return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
118
+ }
119
+
120
+ return array();
121
+ }
122
+
123
+ /**
124
+ * @return array[]
125
+ * @psalm-return array<string, array<int, string>>
126
+ */
127
+ public function getPrefixesPsr4()
128
+ {
129
+ return $this->prefixDirsPsr4;
130
+ }
131
+
132
+ /**
133
+ * @return array[]
134
+ * @psalm-return array<string, string>
135
+ */
136
+ public function getFallbackDirs()
137
+ {
138
+ return $this->fallbackDirsPsr0;
139
+ }
140
+
141
+ /**
142
+ * @return array[]
143
+ * @psalm-return array<string, string>
144
+ */
145
+ public function getFallbackDirsPsr4()
146
+ {
147
+ return $this->fallbackDirsPsr4;
148
+ }
149
+
150
+ /**
151
+ * @return string[] Array of classname => path
152
+ * @psalm-var array<string, string>
153
+ */
154
+ public function getClassMap()
155
+ {
156
+ return $this->classMap;
157
+ }
158
+
159
+ /**
160
+ * @param string[] $classMap Class to filename map
161
+ * @psalm-param array<string, string> $classMap
162
+ *
163
+ * @return void
164
+ */
165
+ public function addClassMap(array $classMap)
166
+ {
167
+ if ($this->classMap) {
168
+ $this->classMap = array_merge($this->classMap, $classMap);
169
+ } else {
170
+ $this->classMap = $classMap;
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Registers a set of PSR-0 directories for a given prefix, either
176
+ * appending or prepending to the ones previously set for this prefix.
177
+ *
178
+ * @param string $prefix The prefix
179
+ * @param string[]|string $paths The PSR-0 root directories
180
+ * @param bool $prepend Whether to prepend the directories
181
+ *
182
+ * @return void
183
+ */
184
+ public function add($prefix, $paths, $prepend = false)
185
+ {
186
+ if (!$prefix) {
187
+ if ($prepend) {
188
+ $this->fallbackDirsPsr0 = array_merge(
189
+ (array) $paths,
190
+ $this->fallbackDirsPsr0
191
+ );
192
+ } else {
193
+ $this->fallbackDirsPsr0 = array_merge(
194
+ $this->fallbackDirsPsr0,
195
+ (array) $paths
196
+ );
197
+ }
198
+
199
+ return;
200
+ }
201
+
202
+ $first = $prefix[0];
203
+ if (!isset($this->prefixesPsr0[$first][$prefix])) {
204
+ $this->prefixesPsr0[$first][$prefix] = (array) $paths;
205
+
206
+ return;
207
+ }
208
+ if ($prepend) {
209
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
210
+ (array) $paths,
211
+ $this->prefixesPsr0[$first][$prefix]
212
+ );
213
+ } else {
214
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
215
+ $this->prefixesPsr0[$first][$prefix],
216
+ (array) $paths
217
+ );
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Registers a set of PSR-4 directories for a given namespace, either
223
+ * appending or prepending to the ones previously set for this namespace.
224
+ *
225
+ * @param string $prefix The prefix/namespace, with trailing '\\'
226
+ * @param string[]|string $paths The PSR-4 base directories
227
+ * @param bool $prepend Whether to prepend the directories
228
+ *
229
+ * @throws \InvalidArgumentException
230
+ *
231
+ * @return void
232
+ */
233
+ public function addPsr4($prefix, $paths, $prepend = false)
234
+ {
235
+ if (!$prefix) {
236
+ // Register directories for the root namespace.
237
+ if ($prepend) {
238
+ $this->fallbackDirsPsr4 = array_merge(
239
+ (array) $paths,
240
+ $this->fallbackDirsPsr4
241
+ );
242
+ } else {
243
+ $this->fallbackDirsPsr4 = array_merge(
244
+ $this->fallbackDirsPsr4,
245
+ (array) $paths
246
+ );
247
+ }
248
+ } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
249
+ // Register directories for a new namespace.
250
+ $length = strlen($prefix);
251
+ if ('\\' !== $prefix[$length - 1]) {
252
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
253
+ }
254
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
255
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
256
+ } elseif ($prepend) {
257
+ // Prepend directories for an already registered namespace.
258
+ $this->prefixDirsPsr4[$prefix] = array_merge(
259
+ (array) $paths,
260
+ $this->prefixDirsPsr4[$prefix]
261
+ );
262
+ } else {
263
+ // Append directories for an already registered namespace.
264
+ $this->prefixDirsPsr4[$prefix] = array_merge(
265
+ $this->prefixDirsPsr4[$prefix],
266
+ (array) $paths
267
+ );
268
+ }
269
+ }
270
+
271
+ /**
272
+ * Registers a set of PSR-0 directories for a given prefix,
273
+ * replacing any others previously set for this prefix.
274
+ *
275
+ * @param string $prefix The prefix
276
+ * @param string[]|string $paths The PSR-0 base directories
277
+ *
278
+ * @return void
279
+ */
280
+ public function set($prefix, $paths)
281
+ {
282
+ if (!$prefix) {
283
+ $this->fallbackDirsPsr0 = (array) $paths;
284
+ } else {
285
+ $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
286
+ }
287
+ }
288
+
289
+ /**
290
+ * Registers a set of PSR-4 directories for a given namespace,
291
+ * replacing any others previously set for this namespace.
292
+ *
293
+ * @param string $prefix The prefix/namespace, with trailing '\\'
294
+ * @param string[]|string $paths The PSR-4 base directories
295
+ *
296
+ * @throws \InvalidArgumentException
297
+ *
298
+ * @return void
299
+ */
300
+ public function setPsr4($prefix, $paths)
301
+ {
302
+ if (!$prefix) {
303
+ $this->fallbackDirsPsr4 = (array) $paths;
304
+ } else {
305
+ $length = strlen($prefix);
306
+ if ('\\' !== $prefix[$length - 1]) {
307
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
308
+ }
309
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
310
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
311
+ }
312
+ }
313
+
314
+ /**
315
+ * Turns on searching the include path for class files.
316
+ *
317
+ * @param bool $useIncludePath
318
+ *
319
+ * @return void
320
+ */
321
+ public function setUseIncludePath($useIncludePath)
322
+ {
323
+ $this->useIncludePath = $useIncludePath;
324
+ }
325
+
326
+ /**
327
+ * Can be used to check if the autoloader uses the include path to check
328
+ * for classes.
329
+ *
330
+ * @return bool
331
+ */
332
+ public function getUseIncludePath()
333
+ {
334
+ return $this->useIncludePath;
335
+ }
336
+
337
+ /**
338
+ * Turns off searching the prefix and fallback directories for classes
339
+ * that have not been registered with the class map.
340
+ *
341
+ * @param bool $classMapAuthoritative
342
+ *
343
+ * @return void
344
+ */
345
+ public function setClassMapAuthoritative($classMapAuthoritative)
346
+ {
347
+ $this->classMapAuthoritative = $classMapAuthoritative;
348
+ }
349
+
350
+ /**
351
+ * Should class lookup fail if not found in the current class map?
352
+ *
353
+ * @return bool
354
+ */
355
+ public function isClassMapAuthoritative()
356
+ {
357
+ return $this->classMapAuthoritative;
358
+ }
359
+
360
+ /**
361
+ * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
362
+ *
363
+ * @param string|null $apcuPrefix
364
+ *
365
+ * @return void
366
+ */
367
+ public function setApcuPrefix($apcuPrefix)
368
+ {
369
+ $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
370
+ }
371
+
372
+ /**
373
+ * The APCu prefix in use, or null if APCu caching is not enabled.
374
+ *
375
+ * @return string|null
376
+ */
377
+ public function getApcuPrefix()
378
+ {
379
+ return $this->apcuPrefix;
380
+ }
381
+
382
+ /**
383
+ * Registers this instance as an autoloader.
384
+ *
385
+ * @param bool $prepend Whether to prepend the autoloader or not
386
+ *
387
+ * @return void
388
+ */
389
+ public function register($prepend = false)
390
+ {
391
+ spl_autoload_register(array($this, 'loadClass'), true, $prepend);
392
+
393
+ if (null === $this->vendorDir) {
394
+ return;
395
+ }
396
+
397
+ if ($prepend) {
398
+ self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
399
+ } else {
400
+ unset(self::$registeredLoaders[$this->vendorDir]);
401
+ self::$registeredLoaders[$this->vendorDir] = $this;
402
+ }
403
+ }
404
+
405
+ /**
406
+ * Unregisters this instance as an autoloader.
407
+ *
408
+ * @return void
409
+ */
410
+ public function unregister()
411
+ {
412
+ spl_autoload_unregister(array($this, 'loadClass'));
413
+
414
+ if (null !== $this->vendorDir) {
415
+ unset(self::$registeredLoaders[$this->vendorDir]);
416
+ }
417
+ }
418
+
419
+ /**
420
+ * Loads the given class or interface.
421
+ *
422
+ * @param string $class The name of the class
423
+ * @return true|null True if loaded, null otherwise
424
+ */
425
+ public function loadClass($class)
426
+ {
427
+ if ($file = $this->findFile($class)) {
428
+ includeFile($file);
429
+
430
+ return true;
431
+ }
432
+
433
+ return null;
434
+ }
435
+
436
+ /**
437
+ * Finds the path to the file where the class is defined.
438
+ *
439
+ * @param string $class The name of the class
440
+ *
441
+ * @return string|false The path if found, false otherwise
442
+ */
443
+ public function findFile($class)
444
+ {
445
+ // class map lookup
446
+ if (isset($this->classMap[$class])) {
447
+ return $this->classMap[$class];
448
+ }
449
+ if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
450
+ return false;
451
+ }
452
+ if (null !== $this->apcuPrefix) {
453
+ $file = apcu_fetch($this->apcuPrefix.$class, $hit);
454
+ if ($hit) {
455
+ return $file;
456
+ }
457
+ }
458
+
459
+ $file = $this->findFileWithExtension($class, '.php');
460
+
461
+ // Search for Hack files if we are running on HHVM
462
+ if (false === $file && defined('HHVM_VERSION')) {
463
+ $file = $this->findFileWithExtension($class, '.hh');
464
+ }
465
+
466
+ if (null !== $this->apcuPrefix) {
467
+ apcu_add($this->apcuPrefix.$class, $file);
468
+ }
469
+
470
+ if (false === $file) {
471
+ // Remember that this class does not exist.
472
+ $this->missingClasses[$class] = true;
473
+ }
474
+
475
+ return $file;
476
+ }
477
+
478
+ /**
479
+ * Returns the currently registered loaders indexed by their corresponding vendor directories.
480
+ *
481
+ * @return self[]
482
+ */
483
+ public static function getRegisteredLoaders()
484
+ {
485
+ return self::$registeredLoaders;
486
+ }
487
+
488
+ /**
489
+ * @param string $class
490
+ * @param string $ext
491
+ * @return string|false
492
+ */
493
+ private function findFileWithExtension($class, $ext)
494
+ {
495
+ // PSR-4 lookup
496
+ $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
497
+
498
+ $first = $class[0];
499
+ if (isset($this->prefixLengthsPsr4[$first])) {
500
+ $subPath = $class;
501
+ while (false !== $lastPos = strrpos($subPath, '\\')) {
502
+ $subPath = substr($subPath, 0, $lastPos);
503
+ $search = $subPath . '\\';
504
+ if (isset($this->prefixDirsPsr4[$search])) {
505
+ $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
506
+ foreach ($this->prefixDirsPsr4[$search] as $dir) {
507
+ if (file_exists($file = $dir . $pathEnd)) {
508
+ return $file;
509
+ }
510
+ }
511
+ }
512
+ }
513
+ }
514
+
515
+ // PSR-4 fallback dirs
516
+ foreach ($this->fallbackDirsPsr4 as $dir) {
517
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
518
+ return $file;
519
+ }
520
+ }
521
+
522
+ // PSR-0 lookup
523
+ if (false !== $pos = strrpos($class, '\\')) {
524
+ // namespaced class name
525
+ $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
526
+ . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
527
+ } else {
528
+ // PEAR-like class name
529
+ $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
530
+ }
531
+
532
+ if (isset($this->prefixesPsr0[$first])) {
533
+ foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
534
+ if (0 === strpos($class, $prefix)) {
535
+ foreach ($dirs as $dir) {
536
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
537
+ return $file;
538
+ }
539
+ }
540
+ }
541
+ }
542
+ }
543
+
544
+ // PSR-0 fallback dirs
545
+ foreach ($this->fallbackDirsPsr0 as $dir) {
546
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
547
+ return $file;
548
+ }
549
+ }
550
+
551
+ // PSR-0 include paths.
552
+ if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
553
+ return $file;
554
+ }
555
+
556
+ return false;
557
+ }
558
+ }
559
+
560
+ /**
561
+ * Scope isolated include.
562
+ *
563
+ * Prevents access to $this/self from included files.
564
+ *
565
+ * @param string $file
566
+ * @return void
567
+ * @private
568
+ */
569
+ function includeFile($file)
570
+ {
571
+ include $file;
572
+ }
vendor/browscap-php/composer/autoload_classmap.php ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_classmap.php @generated by Composer
4
+
5
+ $vendorDir = dirname(dirname(__FILE__));
6
+ $baseDir = dirname($vendorDir);
7
+
8
+ return array(
9
+ 'Attribute' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
10
+ 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
11
+ 'JsonException' => $vendorDir . '/symfony/polyfill-php73/Resources/stubs/JsonException.php',
12
+ 'Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php',
13
+ 'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
14
+ 'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
15
+ 'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
16
+ );
vendor/browscap-php/composer/autoload_files.php ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_files.php @generated by Composer
4
+
5
+ $vendorDir = dirname(dirname(__FILE__));
6
+ $baseDir = dirname($vendorDir);
7
+
8
+ return array(
9
+ 'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
10
+ '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
11
+ '6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
12
+ '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
13
+ 'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php',
14
+ '8825ede83f2f289127722d4e842cf7e8' => $vendorDir . '/symfony/polyfill-intl-grapheme/bootstrap.php',
15
+ '7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
16
+ 'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php',
17
+ '0d59ee240a4cd96ddbb4ff164fccea4d' => $vendorDir . '/symfony/polyfill-php73/bootstrap.php',
18
+ // 'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php',
19
+ // '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
20
+ );
vendor/browscap-php/composer/autoload_namespaces.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_namespaces.php @generated by Composer
4
+
5
+ $vendorDir = dirname(dirname(__FILE__));
6
+ $baseDir = dirname($vendorDir);
7
+
8
+ return array(
9
+ );
vendor/browscap-php/composer/autoload_psr4.php ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_psr4.php @generated by Composer
4
+
5
+ $vendorDir = dirname(dirname(__FILE__));
6
+ $baseDir = dirname($vendorDir);
7
+
8
+ return array(
9
+ 'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'),
10
+ 'Symfony\\Polyfill\\Php73\\' => array($vendorDir . '/symfony/polyfill-php73'),
11
+ 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
12
+ 'Symfony\\Polyfill\\Intl\\Normalizer\\' => array($vendorDir . '/symfony/polyfill-intl-normalizer'),
13
+ 'Symfony\\Polyfill\\Intl\\Grapheme\\' => array($vendorDir . '/symfony/polyfill-intl-grapheme'),
14
+ 'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'),
15
+ 'Symfony\\Contracts\\Service\\' => array($vendorDir . '/symfony/service-contracts'),
16
+ 'Symfony\\Component\\String\\' => array($vendorDir . '/symfony/string'),
17
+ 'Symfony\\Component\\Filesystem\\' => array($vendorDir . '/symfony/filesystem'),
18
+ 'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'),
19
+ 'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'),
20
+ 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
21
+ 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src', $vendorDir . '/psr/http-factory/src'),
22
+ 'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'),
23
+ 'Psr\\Container\\' => array($vendorDir . '/psr/container/src'),
24
+ 'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'),
25
+ 'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'),
26
+ 'MatthiasMullie\\Scrapbook\\' => array($vendorDir . '/matthiasmullie/scrapbook/src'),
27
+ 'League\\MimeTypeDetection\\' => array($vendorDir . '/league/mime-type-detection/src'),
28
+ 'League\\Flysystem\\' => array($vendorDir . '/league/flysystem/src'),
29
+ 'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'),
30
+ 'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'),
31
+ 'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'),
32
+ 'BrowscapPHP\\' => array($vendorDir . '/browscap/browscap-php/src'),
33
+ );
vendor/browscap-php/composer/autoload_real.php ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_real.php @generated by Composer
4
+
5
+ return ComposerAutoloaderInit7f72c92fc9a0a0a884f5e33f3edfd4e9::getLoader();
6
+
7
+ class ComposerAutoloaderInit7f72c92fc9a0a0a884f5e33f3edfd4e9
8
+ {
9
+ private static $loader;
10
+
11
+ public static function loadClassLoader($class)
12
+ {
13
+ if ('Composer\Autoload\ClassLoader' === $class) {
14
+ require __DIR__ . '/ClassLoader.php';
15
+ }
16
+ }
17
+
18
+ /**
19
+ * @return \Composer\Autoload\ClassLoader
20
+ */
21
+ public static function getLoader()
22
+ {
23
+ if (null !== self::$loader) {
24
+ return self::$loader;
25
+ }
26
+
27
+ spl_autoload_register(array('ComposerAutoloaderInit7f72c92fc9a0a0a884f5e33f3edfd4e9', 'loadClassLoader'), true, true);
28
+ self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
29
+ spl_autoload_unregister(array('ComposerAutoloaderInit7f72c92fc9a0a0a884f5e33f3edfd4e9', 'loadClassLoader'));
30
+
31
+ $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
32
+ if ($useStaticLoader) {
33
+ require __DIR__ . '/autoload_static.php';
34
+
35
+ call_user_func(\Composer\Autoload\ComposerStaticInit7f72c92fc9a0a0a884f5e33f3edfd4e9::getInitializer($loader));
36
+ } else {
37
+ $map = require __DIR__ . '/autoload_namespaces.php';
38
+ foreach ($map as $namespace => $path) {
39
+ $loader->set($namespace, $path);
40
+ }
41
+
42
+ $map = require __DIR__ . '/autoload_psr4.php';
43
+ foreach ($map as $namespace => $path) {
44
+ $loader->setPsr4($namespace, $path);
45
+ }
46
+
47
+ $classMap = require __DIR__ . '/autoload_classmap.php';
48
+ if ($classMap) {
49
+ $loader->addClassMap($classMap);
50
+ }
51
+ }
52
+
53
+ $loader->register(true);
54
+
55
+ if ($useStaticLoader) {
56
+ $includeFiles = Composer\Autoload\ComposerStaticInit7f72c92fc9a0a0a884f5e33f3edfd4e9::$files;
57
+ } else {
58
+ $includeFiles = require __DIR__ . '/autoload_files.php';
59
+ }
60
+ foreach ($includeFiles as $fileIdentifier => $file) {
61
+ composerRequire7f72c92fc9a0a0a884f5e33f3edfd4e9($fileIdentifier, $file);
62
+ }
63
+
64
+ return $loader;
65
+ }
66
+ }
67
+
68
+ function composerRequire7f72c92fc9a0a0a884f5e33f3edfd4e9($fileIdentifier, $file)
69
+ {
70
+ if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
71
+ require $file;
72
+
73
+ $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
74
+ }
75
+ }
vendor/browscap-php/composer/autoload_static.php ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_static.php @generated by Composer
4
+
5
+ namespace Composer\Autoload;
6
+
7
+ class ComposerStaticInit7f72c92fc9a0a0a884f5e33f3edfd4e9
8
+ {
9
+ public static $files = array (
10
+ 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
11
+ '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
12
+ '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
13
+ '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
14
+ 'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php',
15
+ '8825ede83f2f289127722d4e842cf7e8' => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme/bootstrap.php',
16
+ '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php',
17
+ 'b6b991a57620e2fb6b2f66f03fe9ddc2' => __DIR__ . '/..' . '/symfony/string/Resources/functions.php',
18
+ '0d59ee240a4cd96ddbb4ff164fccea4d' => __DIR__ . '/..' . '/symfony/polyfill-php73/bootstrap.php',
19
+ );
20
+
21
+ public static $prefixLengthsPsr4 = array (
22
+ 'S' =>
23
+ array (
24
+ 'Symfony\\Polyfill\\Php80\\' => 23,
25
+ 'Symfony\\Polyfill\\Php73\\' => 23,
26
+ 'Symfony\\Polyfill\\Mbstring\\' => 26,
27
+ 'Symfony\\Polyfill\\Intl\\Normalizer\\' => 33,
28
+ 'Symfony\\Polyfill\\Intl\\Grapheme\\' => 31,
29
+ 'Symfony\\Polyfill\\Ctype\\' => 23,
30
+ 'Symfony\\Contracts\\Service\\' => 26,
31
+ 'Symfony\\Component\\String\\' => 25,
32
+ 'Symfony\\Component\\Filesystem\\' => 29,
33
+ 'Symfony\\Component\\Console\\' => 26,
34
+ ),
35
+ 'P' =>
36
+ array (
37
+ 'Psr\\SimpleCache\\' => 16,
38
+ 'Psr\\Log\\' => 8,
39
+ 'Psr\\Http\\Message\\' => 17,
40
+ 'Psr\\Http\\Client\\' => 16,
41
+ 'Psr\\Container\\' => 14,
42
+ 'Psr\\Cache\\' => 10,
43
+ ),
44
+ 'M' =>
45
+ array (
46
+ 'Monolog\\' => 8,
47
+ 'MatthiasMullie\\Scrapbook\\' => 25,
48
+ ),
49
+ 'L' =>
50
+ array (
51
+ 'League\\MimeTypeDetection\\' => 25,
52
+ 'League\\Flysystem\\' => 17,
53
+ ),
54
+ 'G' =>
55
+ array (
56
+ 'GuzzleHttp\\Psr7\\' => 16,
57
+ 'GuzzleHttp\\Promise\\' => 19,
58
+ 'GuzzleHttp\\' => 11,
59
+ ),
60
+ 'B' =>
61
+ array (
62
+ 'BrowscapPHP\\' => 12,
63
+ ),
64
+ );
65
+
66
+ public static $prefixDirsPsr4 = array (
67
+ 'Symfony\\Polyfill\\Php80\\' =>
68
+ array (
69
+ 0 => __DIR__ . '/..' . '/symfony/polyfill-php80',
70
+ ),
71
+ 'Symfony\\Polyfill\\Php73\\' =>
72
+ array (
73
+ 0 => __DIR__ . '/..' . '/symfony/polyfill-php73',
74
+ ),
75
+ 'Symfony\\Polyfill\\Mbstring\\' =>
76
+ array (
77
+ 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
78
+ ),
79
+ 'Symfony\\Polyfill\\Intl\\Normalizer\\' =>
80
+ array (
81
+ 0 => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer',
82
+ ),
83
+ 'Symfony\\Polyfill\\Intl\\Grapheme\\' =>
84
+ array (
85
+ 0 => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme',
86
+ ),
87
+ 'Symfony\\Polyfill\\Ctype\\' =>
88
+ array (
89
+ 0 => __DIR__ . '/..' . '/symfony/polyfill-ctype',
90
+ ),
91
+ 'Symfony\\Contracts\\Service\\' =>
92
+ array (
93
+ 0 => __DIR__ . '/..' . '/symfony/service-contracts',
94
+ ),
95
+ 'Symfony\\Component\\String\\' =>
96
+ array (
97
+ 0 => __DIR__ . '/..' . '/symfony/string',
98
+ ),
99
+ 'Symfony\\Component\\Filesystem\\' =>
100
+ array (
101
+ 0 => __DIR__ . '/..' . '/symfony/filesystem',
102
+ ),
103
+ 'Symfony\\Component\\Console\\' =>
104
+ array (
105
+ 0 => __DIR__ . '/..' . '/symfony/console',
106
+ ),
107
+ 'Psr\\SimpleCache\\' =>
108
+ array (
109
+ 0 => __DIR__ . '/..' . '/psr/simple-cache/src',
110
+ ),
111
+ 'Psr\\Log\\' =>
112
+ array (
113
+ 0 => __DIR__ . '/..' . '/psr/log/Psr/Log',
114
+ ),
115
+ 'Psr\\Http\\Message\\' =>
116
+ array (
117
+ 0 => __DIR__ . '/..' . '/psr/http-message/src',
118
+ 1 => __DIR__ . '/..' . '/psr/http-factory/src',
119
+ ),
120
+ 'Psr\\Http\\Client\\' =>
121
+ array (
122
+ 0 => __DIR__ . '/..' . '/psr/http-client/src',
123
+ ),
124
+ 'Psr\\Container\\' =>
125
+ array (
126
+ 0 => __DIR__ . '/..' . '/psr/container/src',
127
+ ),
128
+ 'Psr\\Cache\\' =>
129
+ array (
130
+ 0 => __DIR__ . '/..' . '/psr/cache/src',
131
+ ),
132
+ 'Monolog\\' =>
133
+ array (
134
+ 0 => __DIR__ . '/..' . '/monolog/monolog/src/Monolog',
135
+ ),
136
+ 'MatthiasMullie\\Scrapbook\\' =>
137
+ array (
138
+ 0 => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src',
139
+ ),
140
+ 'League\\MimeTypeDetection\\' =>
141
+ array (
142
+ 0 => __DIR__ . '/..' . '/league/mime-type-detection/src',
143
+ ),
144
+ 'League\\Flysystem\\' =>
145
+ array (
146
+ 0 => __DIR__ . '/..' . '/league/flysystem/src',
147
+ ),
148
+ 'GuzzleHttp\\Psr7\\' =>
149
+ array (
150
+ 0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src',
151
+ ),
152
+ 'GuzzleHttp\\Promise\\' =>
153
+ array (
154
+ 0 => __DIR__ . '/..' . '/guzzlehttp/promises/src',
155
+ ),
156
+ 'GuzzleHttp\\' =>
157
+ array (
158
+ 0 => __DIR__ . '/..' . '/guzzlehttp/guzzle/src',
159
+ ),
160
+ 'BrowscapPHP\\' =>
161
+ array (
162
+ 0 => __DIR__ . '/..' . '/browscap/browscap-php/src',
163
+ ),
164
+ );
165
+
166
+ public static $classMap = array (
167
+ 'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
168
+ 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
169
+ 'JsonException' => __DIR__ . '/..' . '/symfony/polyfill-php73/Resources/stubs/JsonException.php',
170
+ 'Normalizer' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php',
171
+ 'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
172
+ 'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
173
+ 'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
174
+ );
175
+
176
+ public static function getInitializer(ClassLoader $loader)
177
+ {
178
+ return \Closure::bind(function () use ($loader) {
179
+ $loader->prefixLengthsPsr4 = ComposerStaticInit7f72c92fc9a0a0a884f5e33f3edfd4e9::$prefixLengthsPsr4;
180
+ $loader->prefixDirsPsr4 = ComposerStaticInit7f72c92fc9a0a0a884f5e33f3edfd4e9::$prefixDirsPsr4;
181
+ $loader->classMap = ComposerStaticInit7f72c92fc9a0a0a884f5e33f3edfd4e9::$classMap;
182
+
183
+ }, null, ClassLoader::class);
184
+ }
185
+ }
vendor/browscap-php/league/flysystem/src/Config.php ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\Flysystem;
6
+
7
+ use function array_merge;
8
+
9
+ class Config
10
+ {
11
+ public const OPTION_VISIBILITY = 'visibility';
12
+ public const OPTION_DIRECTORY_VISIBILITY = 'directory_visibility';
13
+
14
+ /**
15
+ * @var array
16
+ */
17
+ private $options;
18
+
19
+ public function __construct(array $options = [])
20
+ {
21
+ $this->options = $options;
22
+ }
23
+
24
+ /**
25
+ * @param mixed $default
26
+ *
27
+ * @return mixed
28
+ */
29
+ public function get(string $property, $default = null)
30
+ {
31
+ return $this->options[$property] ?? $default;
32
+ }
33
+
34
+ public function extend(array $options): Config
35
+ {
36
+ return new Config(array_merge($this->options, $options));
37
+ }
38
+
39
+ public function withDefaults(array $defaults): Config
40
+ {
41
+ return new Config($this->options + $defaults);
42
+ }
43
+ }
vendor/browscap-php/league/flysystem/src/CorruptedPathDetected.php ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace League\Flysystem;
4
+
5
+ use RuntimeException;
6
+
7
+ final class CorruptedPathDetected extends RuntimeException implements FilesystemException
8
+ {
9
+ public static function forPath(string $path): CorruptedPathDetected
10
+ {
11
+ return new CorruptedPathDetected("Corrupted path detected: " . $path);
12
+ }
13
+ }
vendor/browscap-php/league/flysystem/src/DirectoryAttributes.php ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\Flysystem;
6
+
7
+ class DirectoryAttributes implements StorageAttributes
8
+ {
9
+ use ProxyArrayAccessToProperties;
10
+
11
+ /**
12
+ * @var string
13
+ */
14
+ private $type = StorageAttributes::TYPE_DIRECTORY;
15
+
16
+ /**
17
+ * @var string
18
+ */
19
+ private $path;
20
+
21
+ /**
22
+ * @var string|null
23
+ */
24
+ private $visibility;
25
+
26
+ /**
27
+ * @var int|null
28
+ */
29
+ private $lastModified;
30
+
31
+ /**
32
+ * @var array
33
+ */
34
+ private $extraMetadata;
35
+
36
+ public function __construct(string $path, ?string $visibility = null, ?int $lastModified = null, array $extraMetadata = [])
37
+ {
38
+ $this->path = $path;
39
+ $this->visibility = $visibility;
40
+ $this->lastModified = $lastModified;
41
+ $this->extraMetadata = $extraMetadata;
42
+ }
43
+
44
+ public function path(): string
45
+ {
46
+ return $this->path;
47
+ }
48
+
49
+ public function type(): string
50
+ {
51
+ return StorageAttributes::TYPE_DIRECTORY;
52
+ }
53
+
54
+ public function visibility(): ?string
55
+ {
56
+ return $this->visibility;
57
+ }
58
+
59
+ public function lastModified(): ?int
60
+ {
61
+ return $this->lastModified;
62
+ }
63
+
64
+ public function extraMetadata(): array
65
+ {
66
+ return $this->extraMetadata;
67
+ }
68
+
69
+ public function isFile(): bool
70
+ {
71
+ return false;
72
+ }
73
+
74
+ public function isDir(): bool
75
+ {
76
+ return true;
77
+ }
78
+
79
+ public function withPath(string $path): StorageAttributes
80
+ {
81
+ $clone = clone $this;
82
+ $clone->path = $path;
83
+
84
+ return $clone;
85
+ }
86
+
87
+ public static function fromArray(array $attributes): StorageAttributes
88
+ {
89
+ return new DirectoryAttributes(
90
+ $attributes[StorageAttributes::ATTRIBUTE_PATH],
91
+ $attributes[StorageAttributes::ATTRIBUTE_VISIBILITY] ?? null,
92
+ $attributes[StorageAttributes::ATTRIBUTE_LAST_MODIFIED] ?? null,
93
+ $attributes[StorageAttributes::ATTRIBUTE_EXTRA_METADATA] ?? []
94
+ );
95
+ }
96
+
97
+ /**
98
+ * @inheritDoc
99
+ */
100
+ public function jsonSerialize(): array
101
+ {
102
+ return [
103
+ StorageAttributes::ATTRIBUTE_TYPE => $this->type,
104
+ StorageAttributes::ATTRIBUTE_PATH => $this->path,
105
+ StorageAttributes::ATTRIBUTE_VISIBILITY => $this->visibility,
106
+ StorageAttributes::ATTRIBUTE_LAST_MODIFIED => $this->lastModified,
107
+ StorageAttributes::ATTRIBUTE_EXTRA_METADATA => $this->extraMetadata,
108
+ ];
109
+ }
110
+ }
vendor/browscap-php/league/flysystem/src/DirectoryListing.php ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\Flysystem;
6
+
7
+ use ArrayIterator;
8
+ use Generator;
9
+ use IteratorAggregate;
10
+ use Traversable;
11
+
12
+ /**
13
+ * @template T
14
+ */
15
+ class DirectoryListing implements IteratorAggregate
16
+ {
17
+ /**
18
+ * @var iterable<T>
19
+ */
20
+ private $listing;
21
+
22
+ /**
23
+ * @param iterable<T> $listing
24
+ */
25
+ public function __construct(iterable $listing)
26
+ {
27
+ $this->listing = $listing;
28
+ }
29
+
30
+ public function filter(callable $filter): DirectoryListing
31
+ {
32
+ $generator = (static function (iterable $listing) use ($filter): Generator {
33
+ foreach ($listing as $item) {
34
+ if ($filter($item)) {
35
+ yield $item;
36
+ }
37
+ }
38
+ })($this->listing);
39
+
40
+ return new DirectoryListing($generator);
41
+ }
42
+
43
+ public function map(callable $mapper): DirectoryListing
44
+ {
45
+ $generator = (static function (iterable $listing) use ($mapper): Generator {
46
+ foreach ($listing as $item) {
47
+ yield $mapper($item);
48
+ }
49
+ })($this->listing);
50
+
51
+ return new DirectoryListing($generator);
52
+ }
53
+
54
+ public function sortByPath(): DirectoryListing
55
+ {
56
+ $listing = $this->toArray();
57
+
58
+ usort($listing, function (StorageAttributes $a, StorageAttributes $b) {
59
+ return $a->path() <=> $b->path();
60
+ });
61
+
62
+ return new DirectoryListing($listing);
63
+ }
64
+
65
+ /**
66
+ * @return Traversable<T>
67
+ */
68
+ public function getIterator(): Traversable
69
+ {
70
+ return $this->listing instanceof Traversable
71
+ ? $this->listing
72
+ : new ArrayIterator($this->listing);
73
+ }
74
+
75
+ /**
76
+ * @return T[]
77
+ */
78
+ public function toArray(): array
79
+ {
80
+ return $this->listing instanceof Traversable
81
+ ? iterator_to_array($this->listing, false)
82
+ : (array) $this->listing;
83
+ }
84
+ }
vendor/browscap-php/league/flysystem/src/FileAttributes.php ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\Flysystem;
6
+
7
+ class FileAttributes implements StorageAttributes
8
+ {
9
+ use ProxyArrayAccessToProperties;
10
+
11
+ /**
12
+ * @var string
13
+ */
14
+ private $type = StorageAttributes::TYPE_FILE;
15
+
16
+ /**
17
+ * @var string
18
+ */
19
+ private $path;
20
+
21
+ /**
22
+ * @var int|null
23
+ */
24
+ private $fileSize;
25
+
26
+ /**
27
+ * @var string|null
28
+ */
29
+ private $visibility;
30
+
31
+ /**
32
+ * @var int|null
33
+ */
34
+ private $lastModified;
35
+
36
+ /**
37
+ * @var string|null
38
+ */
39
+ private $mimeType;
40
+
41
+ /**
42
+ * @var array
43
+ */
44
+ private $extraMetadata;
45
+
46
+ public function __construct(
47
+ string $path,
48
+ ?int $fileSize = null,
49
+ ?string $visibility = null,
50
+ ?int $lastModified = null,
51
+ ?string $mimeType = null,
52
+ array $extraMetadata = []
53
+ ) {
54
+ $this->path = $path;
55
+ $this->fileSize = $fileSize;
56
+ $this->visibility = $visibility;
57
+ $this->lastModified = $lastModified;
58
+ $this->mimeType = $mimeType;
59
+ $this->extraMetadata = $extraMetadata;
60
+ }
61
+
62
+ public function type(): string
63
+ {
64
+ return $this->type;
65
+ }
66
+
67
+ public function path(): string
68
+ {
69
+ return $this->path;
70
+ }
71
+
72
+ public function fileSize(): ?int
73
+ {
74
+ return $this->fileSize;
75
+ }
76
+
77
+ public function visibility(): ?string
78
+ {
79
+ return $this->visibility;
80
+ }
81
+
82
+ public function lastModified(): ?int
83
+ {
84
+ return $this->lastModified;
85
+ }
86
+
87
+ public function mimeType(): ?string
88
+ {
89
+ return $this->mimeType;
90
+ }
91
+
92
+ public function extraMetadata(): array
93
+ {
94
+ return $this->extraMetadata;
95
+ }
96
+
97
+ public function isFile(): bool
98
+ {
99
+ return true;
100
+ }
101
+
102
+ public function isDir(): bool
103
+ {
104
+ return false;
105
+ }
106
+
107
+ public function withPath(string $path): StorageAttributes
108
+ {
109
+ $clone = clone $this;
110
+ $clone->path = $path;
111
+
112
+ return $clone;
113
+ }
114
+
115
+ public static function fromArray(array $attributes): StorageAttributes
116
+ {
117
+ return new FileAttributes(
118
+ $attributes[StorageAttributes::ATTRIBUTE_PATH],
119
+ $attributes[StorageAttributes::ATTRIBUTE_FILE_SIZE] ?? null,
120
+ $attributes[StorageAttributes::ATTRIBUTE_VISIBILITY] ?? null,
121
+ $attributes[StorageAttributes::ATTRIBUTE_LAST_MODIFIED] ?? null,
122
+ $attributes[StorageAttributes::ATTRIBUTE_MIME_TYPE] ?? null,
123
+ $attributes[StorageAttributes::ATTRIBUTE_EXTRA_METADATA] ?? []
124
+ );
125
+ }
126
+
127
+ public function jsonSerialize(): array
128
+ {
129
+ return [
130
+ StorageAttributes::ATTRIBUTE_TYPE => self::TYPE_FILE,
131
+ StorageAttributes::ATTRIBUTE_PATH => $this->path,
132
+ StorageAttributes::ATTRIBUTE_FILE_SIZE => $this->fileSize,
133
+ StorageAttributes::ATTRIBUTE_VISIBILITY => $this->visibility,
134
+ StorageAttributes::ATTRIBUTE_LAST_MODIFIED => $this->lastModified,
135
+ StorageAttributes::ATTRIBUTE_MIME_TYPE => $this->mimeType,
136
+ StorageAttributes::ATTRIBUTE_EXTRA_METADATA => $this->extraMetadata,
137
+ ];
138
+ }
139
+ }
vendor/browscap-php/league/flysystem/src/Filesystem.php ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\Flysystem;
6
+
7
+ class Filesystem implements FilesystemOperator
8
+ {
9
+ /**
10
+ * @var FilesystemAdapter
11
+ */
12
+ private $adapter;
13
+
14
+ /**
15
+ * @var Config
16
+ */
17
+ private $config;
18
+
19
+ /**
20
+ * @var PathNormalizer
21
+ */
22
+ private $pathNormalizer;
23
+
24
+ public function __construct(
25
+ FilesystemAdapter $adapter,
26
+ array $config = [],
27
+ PathNormalizer $pathNormalizer = null
28
+ ) {
29
+ $this->adapter = $adapter;
30
+ $this->config = new Config($config);
31
+ $this->pathNormalizer = $pathNormalizer ?: new WhitespacePathNormalizer();
32
+ }
33
+
34
+ public function fileExists(string $location): bool
35
+ {
36
+ return $this->adapter->fileExists($this->pathNormalizer->normalizePath($location));
37
+ }
38
+
39
+ public function write(string $location, string $contents, array $config = []): void
40
+ {
41
+ $this->adapter->write(
42
+ $this->pathNormalizer->normalizePath($location),
43
+ $contents,
44
+ $this->config->extend($config)
45
+ );
46
+ }
47
+
48
+ public function writeStream(string $location, $contents, array $config = []): void
49
+ {
50
+ /* @var resource $contents */
51
+ $this->assertIsResource($contents);
52
+ $this->rewindStream($contents);
53
+ $this->adapter->writeStream(
54
+ $this->pathNormalizer->normalizePath($location),
55
+ $contents,
56
+ $this->config->extend($config)
57
+ );
58
+ }
59
+
60
+ public function read(string $location): string
61
+ {
62
+ return $this->adapter->read($this->pathNormalizer->normalizePath($location));
63
+ }
64
+
65
+ public function readStream(string $location)
66
+ {
67
+ return $this->adapter->readStream($this->pathNormalizer->normalizePath($location));
68
+ }
69
+
70
+ public function delete(string $location): void
71
+ {
72
+ $this->adapter->delete($this->pathNormalizer->normalizePath($location));
73
+ }
74
+
75
+ public function deleteDirectory(string $location): void
76
+ {
77
+ $this->adapter->deleteDirectory($this->pathNormalizer->normalizePath($location));
78
+ }
79
+
80
+ public function createDirectory(string $location, array $config = []): void
81
+ {
82
+ $this->adapter->createDirectory(
83
+ $this->pathNormalizer->normalizePath($location),
84
+ $this->config->extend($config)
85
+ );
86
+ }
87
+
88
+ public function listContents(string $location, bool $deep = self::LIST_SHALLOW): DirectoryListing
89
+ {
90
+ $path = $this->pathNormalizer->normalizePath($location);
91
+
92
+ return new DirectoryListing($this->adapter->listContents($path, $deep));
93
+ }
94
+
95
+ public function move(string $source, string $destination, array $config = []): void
96
+ {
97
+ $this->adapter->move(
98
+ $this->pathNormalizer->normalizePath($source),
99
+ $this->pathNormalizer->normalizePath($destination),
100
+ $this->config->extend($config)
101
+ );
102
+ }
103
+
104
+ public function copy(string $source, string $destination, array $config = []): void
105
+ {
106
+ $this->adapter->copy(
107
+ $this->pathNormalizer->normalizePath($source),
108
+ $this->pathNormalizer->normalizePath($destination),
109
+ $this->config->extend($config)
110
+ );
111
+ }
112
+
113
+ public function lastModified(string $path): int
114
+ {
115
+ return $this->adapter->lastModified($this->pathNormalizer->normalizePath($path))->lastModified();
116
+ }
117
+
118
+ public function fileSize(string $path): int
119
+ {
120
+ return $this->adapter->fileSize($this->pathNormalizer->normalizePath($path))->fileSize();
121
+ }
122
+
123
+ public function mimeType(string $path): string
124
+ {
125
+ return $this->adapter->mimeType($this->pathNormalizer->normalizePath($path))->mimeType();
126
+ }
127
+
128
+ public function setVisibility(string $path, string $visibility): void
129
+ {
130
+ $this->adapter->setVisibility($this->pathNormalizer->normalizePath($path), $visibility);
131
+ }
132
+
133
+ public function visibility(string $path): string
134
+ {
135
+ return $this->adapter->visibility($this->pathNormalizer->normalizePath($path))->visibility();
136
+ }
137
+
138
+ /**
139
+ * @param mixed $contents
140
+ */
141
+ private function assertIsResource($contents): void
142
+ {
143
+ if (is_resource($contents) === false) {
144
+ throw new InvalidStreamProvided(
145
+ "Invalid stream provided, expected stream resource, received " . gettype($contents)
146
+ );
147
+ } elseif ($type = get_resource_type($contents) !== 'stream') {
148
+ throw new InvalidStreamProvided(
149
+ "Invalid stream provided, expected stream resource, received resource of type " . $type
150
+ );
151
+ }
152
+ }
153
+
154
+ /**
155
+ * @param resource $resource
156
+ */
157
+ private function rewindStream($resource): void
158
+ {
159
+ if (ftell($resource) !== 0 && stream_get_meta_data($resource)['seekable']) {
160
+ rewind($resource);
161
+ }
162
+ }
163
+ }
vendor/browscap-php/league/flysystem/src/FilesystemAdapter.php ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\Flysystem;
6
+
7
+ interface FilesystemAdapter
8
+ {
9
+ /**
10
+ * @throws FilesystemException
11
+ */
12
+ public function fileExists(string $path): bool;
13
+
14
+ /**
15
+ * @throws UnableToWriteFile
16
+ * @throws FilesystemException
17
+ */
18
+ public function write(string $path, string $contents, Config $config): void;
19
+
20
+ /**
21
+ * @param resource $contents
22
+ *
23
+ * @throws UnableToWriteFile
24
+ * @throws FilesystemException
25
+ */
26
+ public function writeStream(string $path, $contents, Config $config): void;
27
+
28
+ /**
29
+ * @throws UnableToReadFile
30
+ * @throws FilesystemException
31
+ */
32
+ public function read(string $path): string;
33
+
34
+ /**
35
+ * @return resource
36
+ *
37
+ * @throws UnableToReadFile
38
+ * @throws FilesystemException
39
+ */
40
+ public function readStream(string $path);
41
+
42
+ /**
43
+ * @throws UnableToDeleteFile
44
+ * @throws FilesystemException
45
+ */
46
+ public function delete(string $path): void;
47
+
48
+ /**
49
+ * @throws UnableToDeleteDirectory
50
+ * @throws FilesystemException
51
+ */
52
+ public function deleteDirectory(string $path): void;
53
+
54
+ /**
55
+ * @throws UnableToCreateDirectory
56
+ * @throws FilesystemException
57
+ */
58
+ public function createDirectory(string $path, Config $config): void;
59
+
60
+ /**
61
+ * @throws InvalidVisibilityProvided
62
+ * @throws FilesystemException
63
+ */
64
+ public function setVisibility(string $path, string $visibility): void;
65
+
66
+ /**
67
+ * @throws UnableToRetrieveMetadata
68
+ * @throws FilesystemException
69
+ */
70
+ public function visibility(string $path): FileAttributes;
71
+
72
+ /**
73
+ * @throws UnableToRetrieveMetadata
74
+ * @throws FilesystemException
75
+ */
76
+ public function mimeType(string $path): FileAttributes;
77
+
78
+ /**
79
+ * @throws UnableToRetrieveMetadata
80
+ * @throws FilesystemException
81
+ */
82
+ public function lastModified(string $path): FileAttributes;
83
+
84
+ /**
85
+ * @throws UnableToRetrieveMetadata
86
+ * @throws FilesystemException
87
+ */
88
+ public function fileSize(string $path): FileAttributes;
89
+
90
+ /**
91
+ * @return iterable<StorageAttributes>
92
+ *
93
+ * @throws FilesystemException
94
+ */
95
+ public function listContents(string $path, bool $deep): iterable;
96
+
97
+ /**
98
+ * @throws UnableToMoveFile
99
+ * @throws FilesystemException
100
+ */
101
+ public function move(string $source, string $destination, Config $config): void;
102
+
103
+ /**
104
+ * @throws UnableToCopyFile
105
+ * @throws FilesystemException
106
+ */
107
+ public function copy(string $source, string $destination, Config $config): void;
108
+ }
vendor/browscap-php/league/flysystem/src/FilesystemException.php ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\Flysystem;
6
+
7
+ use Throwable;
8
+
9
+ interface FilesystemException extends Throwable
10
+ {
11
+ }
vendor/browscap-php/league/flysystem/src/FilesystemOperationFailed.php ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\Flysystem;
6
+
7
+ interface FilesystemOperationFailed extends FilesystemException
8
+ {
9
+ public const OPERATION_WRITE = 'WRITE';
10
+ public const OPERATION_UPDATE = 'UPDATE';
11
+ public const OPERATION_FILE_EXISTS = 'FILE_EXISTS';
12
+ public const OPERATION_CREATE_DIRECTORY = 'CREATE_DIRECTORY';
13
+ public const OPERATION_DELETE = 'DELETE';
14
+ public const OPERATION_DELETE_DIRECTORY = 'DELETE_DIRECTORY';
15
+ public const OPERATION_MOVE = 'MOVE';
16
+ public const OPERATION_RETRIEVE_METADATA = 'RETRIEVE_METADATA';
17
+ public const OPERATION_COPY = 'COPY';
18
+ public const OPERATION_READ = 'READ';
19
+ public const OPERATION_SET_VISIBILITY = 'SET_VISIBILITY';
20
+
21
+ public function operation(): string;
22
+ }
vendor/browscap-php/league/flysystem/src/FilesystemOperator.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\Flysystem;
6
+
7
+ interface FilesystemOperator extends FilesystemReader, FilesystemWriter
8
+ {
9
+ }
vendor/browscap-php/league/flysystem/src/FilesystemReader.php ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\Flysystem;
6
+
7
+ /**
8
+ * This interface contains everything to read from and inspect
9
+ * a filesystem. All methods containing are non-destructive.
10
+ */
11
+ interface FilesystemReader
12
+ {
13
+ public const LIST_SHALLOW = false;
14
+ public const LIST_DEEP = true;
15
+
16
+ /**
17
+ * @throws FilesystemException
18
+ * @throws UnableToCheckFileExistence
19
+ */
20
+ public function fileExists(string $location): bool;
21
+
22
+ /**
23
+ * @throws UnableToReadFile
24
+ * @throws FilesystemException
25
+ */
26
+ public function read(string $location): string;
27
+
28
+ /**
29
+ * @return resource
30
+ *
31
+ * @throws UnableToReadFile
32
+ * @throws FilesystemException
33
+ */
34
+ public function readStream(string $location);
35
+
36
+ /**
37
+ * @return DirectoryListing<StorageAttributes>
38
+ *
39
+ * @throws FilesystemException
40
+ */
41
+ public function listContents(string $location, bool $deep = self::LIST_SHALLOW): DirectoryListing;
42
+
43
+ /**
44
+ * @throws UnableToRetrieveMetadata
45
+ * @throws FilesystemException
46
+ */
47
+ public function lastModified(string $path): int;
48
+
49
+ /**
50
+ * @throws UnableToRetrieveMetadata
51
+ * @throws FilesystemException
52
+ */
53
+ public function fileSize(string $path): int;
54
+
55
+ /**
56
+ * @throws UnableToRetrieveMetadata
57
+ * @throws FilesystemException
58
+ */
59
+ public function mimeType(string $path): string;
60
+
61
+ /**
62
+ * @throws UnableToRetrieveMetadata
63
+ * @throws FilesystemException
64
+ */
65
+ public function visibility(string $path): string;
66
+ }
vendor/browscap-php/league/flysystem/src/FilesystemWriter.php ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\Flysystem;
6
+
7
+ interface FilesystemWriter
8
+ {
9
+ /**
10
+ * @throws UnableToWriteFile
11
+ * @throws FilesystemException
12
+ */
13
+ public function write(string $location, string $contents, array $config = []): void;
14
+
15
+ /**
16
+ * @param mixed $contents
17
+ *
18
+ * @throws UnableToWriteFile
19
+ * @throws FilesystemException
20
+ */
21
+ public function writeStream(string $location, $contents, array $config = []): void;
22
+
23
+ /**
24
+ * @throws UnableToSetVisibility
25
+ * @throws FilesystemException
26
+ */
27
+ public function setVisibility(string $path, string $visibility): void;
28
+
29
+ /**
30
+ * @throws UnableToDeleteFile
31
+ * @throws FilesystemException
32
+ */
33
+ public function delete(string $location): void;
34
+
35
+ /**
36
+ * @throws UnableToDeleteDirectory
37
+ * @throws FilesystemException
38
+ */
39
+ public function deleteDirectory(string $location): void;
40
+
41
+ /**
42
+ * @throws UnableToCreateDirectory
43
+ * @throws FilesystemException
44
+ */
45
+ public function createDirectory(string $location, array $config = []): void;
46
+
47
+ /**
48
+ * @throws UnableToMoveFile
49
+ * @throws FilesystemException
50
+ */
51
+ public function move(string $source, string $destination, array $config = []): void;
52
+
53
+ /**
54
+ * @throws UnableToCopyFile
55
+ * @throws FilesystemException
56
+ */
57
+ public function copy(string $source, string $destination, array $config = []): void;
58
+ }
vendor/browscap-php/league/flysystem/src/InvalidStreamProvided.php ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\Flysystem;
6
+
7
+ use InvalidArgumentException as BaseInvalidArgumentException;
8
+
9
+ class InvalidStreamProvided extends BaseInvalidArgumentException implements FilesystemException
10
+ {
11
+ }
vendor/browscap-php/league/flysystem/src/InvalidVisibilityProvided.php ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\Flysystem;
6
+
7
+ use InvalidArgumentException;
8
+
9
+ use function var_export;
10
+
11
+ class InvalidVisibilityProvided extends InvalidArgumentException implements FilesystemException
12
+ {
13
+ public static function withVisibility(string $visibility, string $expectedMessage): InvalidVisibilityProvided
14
+ {
15
+ $provided = var_export($visibility, true);
16
+ $message = "Invalid visibility provided. Expected {$expectedMessage}, received {$provided}";
17
+
18
+ throw new InvalidVisibilityProvided($message);
19
+ }
20
+ }
vendor/browscap-php/league/flysystem/src/Local/LocalFilesystemAdapter.php ADDED
@@ -0,0 +1,419 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\Flysystem\Local;
6
+
7
+ use function file_put_contents;
8
+ use const DIRECTORY_SEPARATOR;
9
+ use const LOCK_EX;
10
+ use DirectoryIterator;
11
+ use FilesystemIterator;
12
+ use Generator;
13
+ use League\Flysystem\Config;
14
+ use League\Flysystem\DirectoryAttributes;
15
+ use League\Flysystem\FileAttributes;
16
+ use League\Flysystem\FilesystemAdapter;
17
+ use League\Flysystem\PathPrefixer;
18
+ use League\Flysystem\SymbolicLinkEncountered;
19
+ use League\Flysystem\UnableToCopyFile;
20
+ use League\Flysystem\UnableToCreateDirectory;
21
+ use League\Flysystem\UnableToDeleteDirectory;
22
+ use League\Flysystem\UnableToDeleteFile;
23
+ use League\Flysystem\UnableToMoveFile;
24
+ use League\Flysystem\UnableToReadFile;
25
+ use League\Flysystem\UnableToRetrieveMetadata;
26
+ use League\Flysystem\UnableToSetVisibility;
27
+ use League\Flysystem\UnableToWriteFile;
28
+ use League\Flysystem\UnixVisibility\PortableVisibilityConverter;
29
+ use League\Flysystem\UnixVisibility\VisibilityConverter;
30
+ use League\MimeTypeDetection\FinfoMimeTypeDetector;
31
+ use League\MimeTypeDetection\MimeTypeDetector;
32
+ use RecursiveDirectoryIterator;
33
+ use RecursiveIteratorIterator;
34
+ use SplFileInfo;
35
+ use function chmod;
36
+ use function clearstatcache;
37
+ use function dirname;
38
+ use function error_clear_last;
39
+ use function error_get_last;
40
+ use function file_exists;
41
+ use function is_dir;
42
+ use function is_file;
43
+ use function mkdir;
44
+ use function rename;
45
+ use function stream_copy_to_stream;
46
+
47
+ class LocalFilesystemAdapter implements FilesystemAdapter
48
+ {
49
+ /**
50
+ * @var int
51
+ */
52
+ public const SKIP_LINKS = 0001;
53
+
54
+ /**
55
+ * @var int
56
+ */
57
+ public const DISALLOW_LINKS = 0002;
58
+
59
+ /**
60
+ * @var PathPrefixer
61
+ */
62
+ private $prefixer;
63
+
64
+ /**
65
+ * @var int
66
+ */
67
+ private $writeFlags;
68
+
69
+ /**
70
+ * @var int
71
+ */
72
+ private $linkHandling;
73
+
74
+ /**
75
+ * @var VisibilityConverter
76
+ */
77
+ private $visibility;
78
+
79
+ /**
80
+ * @var MimeTypeDetector
81
+ */
82
+ private $mimeTypeDetector;
83
+
84
+ public function __construct(
85
+ string $location,
86
+ VisibilityConverter $visibility = null,
87
+ int $writeFlags = LOCK_EX,
88
+ int $linkHandling = self::DISALLOW_LINKS,
89
+ MimeTypeDetector $mimeTypeDetector = null
90
+ ) {
91
+ $this->prefixer = new PathPrefixer($location, DIRECTORY_SEPARATOR);
92
+ $this->writeFlags = $writeFlags;
93
+ $this->linkHandling = $linkHandling;
94
+ $this->visibility = $visibility ?: new PortableVisibilityConverter();
95
+ $this->ensureDirectoryExists($location, $this->visibility->defaultForDirectories());
96
+ $this->mimeTypeDetector = $mimeTypeDetector ?: new FinfoMimeTypeDetector();
97
+ }
98
+
99
+ public function write(string $path, string $contents, Config $config): void
100
+ {
101
+ $this->writeToFile($path, $contents, $config);
102
+ }
103
+
104
+ public function writeStream(string $path, $contents, Config $config): void
105
+ {
106
+ $this->writeToFile($path, $contents, $config);
107
+ }
108
+
109
+ /**
110
+ * @param resource|string $contents
111
+ */
112
+ private function writeToFile(string $path, $contents, Config $config): void
113
+ {
114
+ $prefixedLocation = $this->prefixer->prefixPath($path);
115
+ $this->ensureDirectoryExists(
116
+ dirname($prefixedLocation),
117
+ $this->resolveDirectoryVisibility($config->get(Config::OPTION_DIRECTORY_VISIBILITY))
118
+ );
119
+ error_clear_last();
120
+
121
+ if (@file_put_contents($prefixedLocation, $contents, $this->writeFlags) === false) {
122
+ throw UnableToWriteFile::atLocation($path, error_get_last()['message'] ?? '');
123
+ }
124
+
125
+ if ($visibility = $config->get(Config::OPTION_VISIBILITY)) {
126
+ $this->setVisibility($path, (string) $visibility);
127
+ }
128
+ }
129
+
130
+ public function delete(string $path): void
131
+ {
132
+ $location = $this->prefixer->prefixPath($path);
133
+
134
+ if ( ! file_exists($location)) {
135
+ return;
136
+ }
137
+
138
+ error_clear_last();
139
+
140
+ if ( ! @unlink($location)) {
141
+ throw UnableToDeleteFile::atLocation($location, error_get_last()['message'] ?? '');
142
+ }
143
+ }
144
+
145
+ public function deleteDirectory(string $prefix): void
146
+ {
147
+ $location = $this->prefixer->prefixPath($prefix);
148
+
149
+ if ( ! is_dir($location)) {
150
+ return;
151
+ }
152
+
153
+ $contents = $this->listDirectoryRecursively($location, RecursiveIteratorIterator::CHILD_FIRST);
154
+
155
+ /** @var SplFileInfo $file */
156
+ foreach ($contents as $file) {
157
+ if ( ! $this->deleteFileInfoObject($file)) {
158
+ throw UnableToDeleteDirectory::atLocation($prefix, "Unable to delete file at " . $file->getPathname());
159
+ }
160
+ }
161
+
162
+ unset($contents);
163
+
164
+ if ( ! @rmdir($location)) {
165
+ throw UnableToDeleteDirectory::atLocation($prefix, error_get_last()['message'] ?? '');
166
+ }
167
+ }
168
+
169
+ private function listDirectoryRecursively(
170
+ string $path,
171
+ int $mode = RecursiveIteratorIterator::SELF_FIRST
172
+ ): Generator {
173
+ yield from new RecursiveIteratorIterator(
174
+ new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS),
175
+ $mode
176
+ );
177
+ }
178
+
179
+ protected function deleteFileInfoObject(SplFileInfo $file): bool
180
+ {
181
+ switch ($file->getType()) {
182
+ case 'dir':
183
+ return @rmdir((string) $file->getRealPath());
184
+ case 'link':
185
+ return @unlink((string) $file->getPathname());
186
+ default:
187
+ return @unlink((string) $file->getRealPath());
188
+ }
189
+ }
190
+
191
+ public function listContents(string $path, bool $deep): iterable
192
+ {
193
+ $location = $this->prefixer->prefixPath($path);
194
+
195
+ if ( ! is_dir($location)) {
196
+ return;
197
+ }
198
+
199
+ /** @var SplFileInfo[] $iterator */
200
+ $iterator = $deep ? $this->listDirectoryRecursively($location) : $this->listDirectory($location);
201
+
202
+ foreach ($iterator as $fileInfo) {
203
+ if ($fileInfo->isLink()) {
204
+ if ($this->linkHandling & self::SKIP_LINKS) {
205
+ continue;
206
+ }
207
+ throw SymbolicLinkEncountered::atLocation($fileInfo->getPathname());
208
+ }
209
+
210
+ $path = $this->prefixer->stripPrefix($fileInfo->getPathname());
211
+ $lastModified = $fileInfo->getMTime();
212
+ $isDirectory = $fileInfo->isDir();
213
+ $permissions = octdec(substr(sprintf('%o', $fileInfo->getPerms()), -4));
214
+ $visibility = $isDirectory ? $this->visibility->inverseForDirectory($permissions) : $this->visibility->inverseForFile($permissions);
215
+
216
+ yield $isDirectory ? new DirectoryAttributes($path, $visibility, $lastModified) : new FileAttributes(
217
+ str_replace('\\', '/', $path),
218
+ $fileInfo->getSize(),
219
+ $visibility,
220
+ $lastModified
221
+ );
222
+ }
223
+ }
224
+
225
+ public function move(string $source, string $destination, Config $config): void
226
+ {
227
+ $sourcePath = $this->prefixer->prefixPath($source);
228
+ $destinationPath = $this->prefixer->prefixPath($destination);
229
+ $this->ensureDirectoryExists(
230
+ dirname($destinationPath),
231
+ $this->resolveDirectoryVisibility($config->get(Config::OPTION_DIRECTORY_VISIBILITY))
232
+ );
233
+
234
+ if ( ! @rename($sourcePath, $destinationPath)) {
235
+ throw UnableToMoveFile::fromLocationTo($sourcePath, $destinationPath);
236
+ }
237
+ }
238
+
239
+ public function copy(string $source, string $destination, Config $config): void
240
+ {
241
+ $sourcePath = $this->prefixer->prefixPath($source);
242
+ $destinationPath = $this->prefixer->prefixPath($destination);
243
+ $this->ensureDirectoryExists(
244
+ dirname($destinationPath),
245
+ $this->resolveDirectoryVisibility($config->get(Config::OPTION_DIRECTORY_VISIBILITY))
246
+ );
247
+
248
+ if ( ! @copy($sourcePath, $destinationPath)) {
249
+ throw UnableToCopyFile::fromLocationTo($sourcePath, $destinationPath);
250
+ }
251
+ }
252
+
253
+ public function read(string $path): string
254
+ {
255
+ $location = $this->prefixer->prefixPath($path);
256
+ error_clear_last();
257
+ $contents = @file_get_contents($location);
258
+
259
+ if ($contents === false) {
260
+ throw UnableToReadFile::fromLocation($path, error_get_last()['message'] ?? '');
261
+ }
262
+
263
+ return $contents;
264
+ }
265
+
266
+ public function readStream(string $path)
267
+ {
268
+ $location = $this->prefixer->prefixPath($path);
269
+ error_clear_last();
270
+ $contents = @fopen($location, 'rb');
271
+
272
+ if ($contents === false) {
273
+ throw UnableToReadFile::fromLocation($path, error_get_last()['message'] ?? '');
274
+ }
275
+
276
+ return $contents;
277
+ }
278
+
279
+ protected function ensureDirectoryExists(string $dirname, int $visibility): void
280
+ {
281
+ if (is_dir($dirname)) {
282
+ return;
283
+ }
284
+
285
+ error_clear_last();
286
+
287
+ if ( ! @mkdir($dirname, $visibility, true)) {
288
+ $mkdirError = error_get_last();
289
+ }
290
+
291
+ clearstatcache(true, $dirname);
292
+
293
+ if ( ! is_dir($dirname)) {
294
+ $errorMessage = isset($mkdirError['message']) ? $mkdirError['message'] : '';
295
+
296
+ throw UnableToCreateDirectory::atLocation($dirname, $errorMessage);
297
+ }
298
+ }
299
+
300
+ public function fileExists(string $location): bool
301
+ {
302
+ $location = $this->prefixer->prefixPath($location);
303
+
304
+ return is_file($location);
305
+ }
306
+
307
+ public function createDirectory(string $path, Config $config): void
308
+ {
309
+ $location = $this->prefixer->prefixPath($path);
310
+ $visibility = $config->get(Config::OPTION_VISIBILITY, $config->get(Config::OPTION_DIRECTORY_VISIBILITY));
311
+ $permissions = $this->resolveDirectoryVisibility($visibility);
312
+
313
+ if (is_dir($location)) {
314
+ $this->setPermissions($location, $permissions);
315
+
316
+ return;
317
+ }
318
+
319
+ error_clear_last();
320
+
321
+ if ( ! @mkdir($location, $permissions, true)) {
322
+ throw UnableToCreateDirectory::atLocation($path, error_get_last()['message'] ?? '');
323
+ }
324
+ }
325
+
326
+ public function setVisibility(string $path, string $visibility): void
327
+ {
328
+ $path = $this->prefixer->prefixPath($path);
329
+ $visibility = is_dir($path) ? $this->visibility->forDirectory($visibility) : $this->visibility->forFile(
330
+ $visibility
331
+ );
332
+
333
+ $this->setPermissions($path, $visibility);
334
+ }
335
+
336
+ public function visibility(string $path): FileAttributes
337
+ {
338
+ $location = $this->prefixer->prefixPath($path);
339
+ clearstatcache(false, $location);
340
+ error_clear_last();
341
+ $fileperms = @fileperms($location);
342
+
343
+ if ($fileperms === false) {
344
+ throw UnableToRetrieveMetadata::visibility($path, error_get_last()['message'] ?? '');
345
+ }
346
+
347
+ $permissions = $fileperms & 0777;
348
+ $visibility = $this->visibility->inverseForFile($permissions);
349
+
350
+ return new FileAttributes($path, null, $visibility);
351
+ }
352
+
353
+ private function resolveDirectoryVisibility(?string $visibility): int
354
+ {
355
+ return $visibility === null ? $this->visibility->defaultForDirectories() : $this->visibility->forDirectory(
356
+ $visibility
357
+ );
358
+ }
359
+
360
+ public function mimeType(string $path): FileAttributes
361
+ {
362
+ $location = $this->prefixer->prefixPath($path);
363
+ error_clear_last();
364
+ $mimeType = $this->mimeTypeDetector->detectMimeTypeFromFile($location);
365
+
366
+ if ($mimeType === null) {
367
+ throw UnableToRetrieveMetadata::mimeType($path, error_get_last()['message'] ?? '');
368
+ }
369
+
370
+ return new FileAttributes($path, null, null, null, $mimeType);
371
+ }
372
+
373
+ public function lastModified(string $path): FileAttributes
374
+ {
375
+ $location = $this->prefixer->prefixPath($path);
376
+ error_clear_last();
377
+ $lastModified = @filemtime($location);
378
+
379
+ if ($lastModified === false) {
380
+ throw UnableToRetrieveMetadata::lastModified($path, error_get_last()['message'] ?? '');
381
+ }
382
+
383
+ return new FileAttributes($path, null, null, $lastModified);
384
+ }
385
+
386
+ public function fileSize(string $path): FileAttributes
387
+ {
388
+ $location = $this->prefixer->prefixPath($path);
389
+ error_clear_last();
390
+
391
+ if (is_file($location) && ($fileSize = @filesize($location)) !== false) {
392
+ return new FileAttributes($path, $fileSize);
393
+ }
394
+
395
+ throw UnableToRetrieveMetadata::fileSize($path, error_get_last()['message'] ?? '');
396
+ }
397
+
398
+ private function listDirectory(string $location): Generator
399
+ {
400
+ $iterator = new DirectoryIterator($location);
401
+
402
+ foreach ($iterator as $item) {
403
+ if ($item->isDot()) {
404
+ continue;
405
+ }
406
+
407
+ yield $item;
408
+ }
409
+ }
410
+
411
+ private function setPermissions(string $location, int $visibility): void
412
+ {
413
+ error_clear_last();
414
+ if ( ! @chmod($location, $visibility)) {
415
+ $extraMessage = error_get_last()['message'] ?? '';
416
+ throw UnableToSetVisibility::atLocation($this->prefixer->stripPrefix($location), $extraMessage);
417
+ }
418
+ }
419
+ }
vendor/browscap-php/league/flysystem/src/MountManager.php ADDED
@@ -0,0 +1,334 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\Flysystem;
6
+
7
+ use function sprintf;
8
+
9
+ class MountManager implements FilesystemOperator
10
+ {
11
+ /**
12
+ * @var array<string, FilesystemOperator>
13
+ */
14
+ private $filesystems = [];
15
+
16
+ /**
17
+ * MountManager constructor.
18
+ *
19
+ * @param array<string,FilesystemOperator> $filesystems
20
+ */
21
+ public function __construct(array $filesystems = [])
22
+ {
23
+ $this->mountFilesystems($filesystems);
24
+ }
25
+
26
+ public function fileExists(string $location): bool
27
+ {
28
+ /** @var FilesystemOperator $filesystem */
29
+ [$filesystem, $path] = $this->determineFilesystemAndPath($location);
30
+
31
+ try {
32
+ return $filesystem->fileExists($path);
33
+ } catch (UnableToCheckFileExistence $exception) {
34
+ throw UnableToCheckFileExistence::forLocation($location, $exception);
35
+ }
36
+ }
37
+
38
+ public function read(string $location): string
39
+ {
40
+ /** @var FilesystemOperator $filesystem */
41
+ [$filesystem, $path] = $this->determineFilesystemAndPath($location);
42
+
43
+ try {
44
+ return $filesystem->read($path);
45
+ } catch (UnableToReadFile $exception) {
46
+ throw UnableToReadFile::fromLocation($location, $exception->reason(), $exception);
47
+ }
48
+ }
49
+
50
+ public function readStream(string $location)
51
+ {
52
+ /** @var FilesystemOperator $filesystem */
53
+ [$filesystem, $path] = $this->determineFilesystemAndPath($location);
54
+
55
+ try {
56
+ return $filesystem->readStream($path);
57
+ } catch (UnableToReadFile $exception) {
58
+ throw UnableToReadFile::fromLocation($location, $exception->reason(), $exception);
59
+ }
60
+ }
61
+
62
+ public function listContents(string $location, bool $deep = self::LIST_SHALLOW): DirectoryListing
63
+ {
64
+ /** @var FilesystemOperator $filesystem */
65
+ [$filesystem, $path, $mountIdentifier] = $this->determineFilesystemAndPath($location);
66
+
67
+ return
68
+ $filesystem
69
+ ->listContents($path, $deep)
70
+ ->map(
71
+ function (StorageAttributes $attributes) use ($mountIdentifier) {
72
+ return $attributes->withPath(sprintf('%s://%s', $mountIdentifier, $attributes->path()));
73
+ }
74
+ );
75
+ }
76
+
77
+ public function lastModified(string $location): int
78
+ {
79
+ /** @var FilesystemOperator $filesystem */
80
+ [$filesystem, $path] = $this->determineFilesystemAndPath($location);
81
+
82
+ try {
83
+ return $filesystem->lastModified($path);
84
+ } catch (UnableToRetrieveMetadata $exception) {
85
+ throw UnableToRetrieveMetadata::lastModified($location, $exception->reason(), $exception);
86
+ }
87
+ }
88
+
89
+ public function fileSize(string $location): int
90
+ {
91
+ /** @var FilesystemOperator $filesystem */
92
+ [$filesystem, $path] = $this->determineFilesystemAndPath($location);
93
+
94
+ try {
95
+ return $filesystem->fileSize($path);
96
+ } catch (UnableToRetrieveMetadata $exception) {
97
+ throw UnableToRetrieveMetadata::fileSize($location, $exception->reason(), $exception);
98
+ }
99
+ }
100
+
101
+ public function mimeType(string $location): string
102
+ {
103
+ /** @var FilesystemOperator $filesystem */
104
+ [$filesystem, $path] = $this->determineFilesystemAndPath($location);
105
+
106
+ try {
107
+ return $filesystem->mimeType($path);
108
+ } catch (UnableToRetrieveMetadata $exception) {
109
+ throw UnableToRetrieveMetadata::mimeType($location, $exception->reason(), $exception);
110
+ }
111
+ }
112
+
113
+ public function visibility(string $location): string
114
+ {
115
+ /** @var FilesystemOperator $filesystem */
116
+ [$filesystem, $path] = $this->determineFilesystemAndPath($location);
117
+
118
+ try {
119
+ return $filesystem->visibility($path);
120
+ } catch (UnableToRetrieveMetadata $exception) {
121
+ throw UnableToRetrieveMetadata::visibility($location, $exception->reason(), $exception);
122
+ }
123
+ }
124
+
125
+ public function write(string $location, string $contents, array $config = []): void
126
+ {
127
+ /** @var FilesystemOperator $filesystem */
128
+ [$filesystem, $path] = $this->determineFilesystemAndPath($location);
129
+
130
+ try {
131
+ $filesystem->write($path, $contents, $config);
132
+ } catch (UnableToWriteFile $exception) {
133
+ throw UnableToWriteFile::atLocation($location, $exception->reason(), $exception);
134
+ }
135
+ }
136
+
137
+ public function writeStream(string $location, $contents, array $config = []): void
138
+ {
139
+ /** @var FilesystemOperator $filesystem */
140
+ [$filesystem, $path] = $this->determineFilesystemAndPath($location);
141
+ $filesystem->writeStream($path, $contents, $config);
142
+ }
143
+
144
+ public function setVisibility(string $path, string $visibility): void
145
+ {
146
+ /** @var FilesystemOperator $filesystem */
147
+ [$filesystem, $path] = $this->determineFilesystemAndPath($path);
148
+ $filesystem->setVisibility($path, $visibility);
149
+ }
150
+
151
+ public function delete(string $location): void
152
+ {
153
+ /** @var FilesystemOperator $filesystem */
154
+ [$filesystem, $path] = $this->determineFilesystemAndPath($location);
155
+
156
+ try {
157
+ $filesystem->delete($path);
158
+ } catch (UnableToDeleteFile $exception) {
159
+ throw UnableToDeleteFile::atLocation($location, '', $exception);
160
+ }
161
+ }
162
+
163
+ public function deleteDirectory(string $location): void
164
+ {
165
+ /** @var FilesystemOperator $filesystem */
166
+ [$filesystem, $path] = $this->determineFilesystemAndPath($location);
167
+
168
+ try {
169
+ $filesystem->deleteDirectory($path);
170
+ } catch (UnableToDeleteDirectory $exception) {
171
+ throw UnableToDeleteDirectory::atLocation($location, '', $exception);
172
+ }
173
+ }
174
+
175
+ public function createDirectory(string $location, array $config = []): void
176
+ {
177
+ /** @var FilesystemOperator $filesystem */
178
+ [$filesystem, $path] = $this->determineFilesystemAndPath($location);
179
+
180
+ try {
181
+ $filesystem->createDirectory($path, $config);
182
+ } catch (UnableToCreateDirectory $exception) {
183
+ throw UnableToCreateDirectory::dueToFailure($location, $exception);
184
+ }
185
+ }
186
+
187
+ public function move(string $source, string $destination, array $config = []): void
188
+ {
189
+ /** @var FilesystemOperator $sourceFilesystem */
190
+ /* @var FilesystemOperator $destinationFilesystem */
191
+ [$sourceFilesystem, $sourcePath] = $this->determineFilesystemAndPath($source);
192
+ [$destinationFilesystem, $destinationPath] = $this->determineFilesystemAndPath($destination);
193
+
194
+ $sourceFilesystem === $destinationFilesystem ? $this->moveInTheSameFilesystem(
195
+ $sourceFilesystem,
196
+ $sourcePath,
197
+ $destinationPath,
198
+ $source,
199
+ $destination
200
+ ) : $this->moveAcrossFilesystems($source, $destination);
201
+ }
202
+
203
+ public function copy(string $source, string $destination, array $config = []): void
204
+ {
205
+ /** @var FilesystemOperator $sourceFilesystem */
206
+ /* @var FilesystemOperator $destinationFilesystem */
207
+ [$sourceFilesystem, $sourcePath] = $this->determineFilesystemAndPath($source);
208
+ [$destinationFilesystem, $destinationPath] = $this->determineFilesystemAndPath($destination);
209
+
210
+ $sourceFilesystem === $destinationFilesystem ? $this->copyInSameFilesystem(
211
+ $sourceFilesystem,
212
+ $sourcePath,
213
+ $destinationPath,
214
+ $source,
215
+ $destination
216
+ ) : $this->copyAcrossFilesystem(
217
+ $config['visibility'] ?? null,
218
+ $sourceFilesystem,
219
+ $sourcePath,
220
+ $destinationFilesystem,
221
+ $destinationPath,
222
+ $source,
223
+ $destination
224
+ );
225
+ }
226
+
227
+ private function mountFilesystems(array $filesystems): void
228
+ {
229
+ foreach ($filesystems as $key => $filesystem) {
230
+ $this->guardAgainstInvalidMount($key, $filesystem);
231
+ /* @var string $key */
232
+ /* @var FilesystemOperator $filesystem */
233
+ $this->mountFilesystem($key, $filesystem);
234
+ }
235
+ }
236
+
237
+ /**
238
+ * @param mixed $key
239
+ * @param mixed $filesystem
240
+ */
241
+ private function guardAgainstInvalidMount($key, $filesystem): void
242
+ {
243
+ if ( ! is_string($key)) {
244
+ throw UnableToMountFilesystem::becauseTheKeyIsNotValid($key);
245
+ }
246
+
247
+ if ( ! $filesystem instanceof FilesystemOperator) {
248
+ throw UnableToMountFilesystem::becauseTheFilesystemWasNotValid($filesystem);
249
+ }
250
+ }
251
+
252
+ private function mountFilesystem(string $key, FilesystemOperator $filesystem): void
253
+ {
254
+ $this->filesystems[$key] = $filesystem;
255
+ }
256
+
257
+ /**
258
+ * @param string $path
259
+ *
260
+ * @return array{0:FilesystemOperator, 1:string}
261
+ */
262
+ private function determineFilesystemAndPath(string $path): array
263
+ {
264
+ if (strpos($path, '://') < 1) {
265
+ throw UnableToResolveFilesystemMount::becauseTheSeparatorIsMissing($path);
266
+ }
267
+
268
+ /** @var string $mountIdentifier */
269
+ /** @var string $mountPath */
270
+ [$mountIdentifier, $mountPath] = explode('://', $path, 2);
271
+
272
+ if ( ! array_key_exists($mountIdentifier, $this->filesystems)) {
273
+ throw UnableToResolveFilesystemMount::becauseTheMountWasNotRegistered($mountIdentifier);
274
+ }
275
+
276
+ return [$this->filesystems[$mountIdentifier], $mountPath, $mountIdentifier];
277
+ }
278
+
279
+ private function copyInSameFilesystem(
280
+ FilesystemOperator $sourceFilesystem,
281
+ string $sourcePath,
282
+ string $destinationPath,
283
+ string $source,
284
+ string $destination
285
+ ): void {
286
+ try {
287
+ $sourceFilesystem->copy($sourcePath, $destinationPath);
288
+ } catch (UnableToCopyFile $exception) {
289
+ throw UnableToCopyFile::fromLocationTo($source, $destination, $exception);
290
+ }
291
+ }
292
+
293
+ private function copyAcrossFilesystem(
294
+ ?string $visibility,
295
+ FilesystemOperator $sourceFilesystem,
296
+ string $sourcePath,
297
+ FilesystemOperator $destinationFilesystem,
298
+ string $destinationPath,
299
+ string $source,
300
+ string $destination
301
+ ): void {
302
+ try {
303
+ $visibility = $visibility ?? $sourceFilesystem->visibility($sourcePath);
304
+ $stream = $sourceFilesystem->readStream($sourcePath);
305
+ $destinationFilesystem->writeStream($destinationPath, $stream, compact('visibility'));
306
+ } catch (UnableToRetrieveMetadata | UnableToReadFile | UnableToWriteFile $exception) {
307
+ throw UnableToCopyFile::fromLocationTo($source, $destination, $exception);
308
+ }
309
+ }
310
+
311
+ private function moveInTheSameFilesystem(
312
+ FilesystemOperator $sourceFilesystem,
313
+ string $sourcePath,
314
+ string $destinationPath,
315
+ string $source,
316
+ string $destination
317
+ ): void {
318
+ try {
319
+ $sourceFilesystem->move($sourcePath, $destinationPath);
320
+ } catch (UnableToMoveFile $exception) {
321
+ throw UnableToMoveFile::fromLocationTo($source, $destination, $exception);
322
+ }
323
+ }
324
+
325
+ private function moveAcrossFilesystems(string $source, string $destination): void
326
+ {
327
+ try {
328
+ $this->copy($source, $destination);
329
+ $this->delete($source);
330
+ } catch (UnableToCopyFile | UnableToDeleteFile $exception) {
331
+ throw UnableToMoveFile::fromLocationTo($source, $destination, $exception);
332
+ }
333
+ }
334
+ }
vendor/browscap-php/league/flysystem/src/PathNormalizer.php ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\Flysystem;
6
+
7
+ interface PathNormalizer
8
+ {
9
+ public function normalizePath(string $path): string;
10
+ }
vendor/browscap-php/league/flysystem/src/PathPrefixer.php ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\Flysystem;
6
+
7
+ use function rtrim;
8
+ use function strlen;
9
+ use function substr;
10
+
11
+ final class PathPrefixer
12
+ {
13
+ /**
14
+ * @var string
15
+ */
16
+ private $prefix = '';
17
+
18
+ /**
19
+ * @var string
20
+ */
21
+ private $separator = '/';
22
+
23
+ public function __construct(string $prefix, string $separator = '/')
24
+ {
25
+ $this->prefix = rtrim($prefix, '\\/');
26
+
27
+ if ($this->prefix !== '' || $prefix === $separator) {
28
+ $this->prefix .= $separator;
29
+ }
30
+
31
+ $this->separator = $separator;
32
+ }
33
+
34
+ public function prefixPath(string $path): string
35
+ {
36
+ return $this->prefix . ltrim($path, '\\/');
37
+ }
38
+
39
+ public function stripPrefix(string $path): string
40
+ {
41
+ /* @var string */
42
+ return substr($path, strlen($this->prefix));
43
+ }
44
+
45
+ public function stripDirectoryPrefix(string $path): string
46
+ {
47
+ return rtrim($this->stripPrefix($path), '\\/');
48
+ }
49
+
50
+ public function prefixDirectoryPath(string $path): string
51
+ {
52
+ $prefixedPath = $this->prefixPath(rtrim($path, '\\/'));
53
+
54
+ if ((substr($prefixedPath, -1) === $this->separator) || $prefixedPath === '') {
55
+ return $prefixedPath;
56
+ }
57
+
58
+ return $prefixedPath . $this->separator;
59
+ }
60
+ }
vendor/browscap-php/league/flysystem/src/PathTraversalDetected.php ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\Flysystem;
6
+
7
+ use RuntimeException;
8
+
9
+ class PathTraversalDetected extends RuntimeException implements FilesystemException
10
+ {
11
+ /**
12
+ * @var string
13
+ */
14
+ private $path;
15
+
16
+ public function path(): string
17
+ {
18
+ return $this->path;
19
+ }
20
+
21
+ public static function forPath(string $path): PathTraversalDetected
22
+ {
23
+ $e = new PathTraversalDetected("Path traversal detected: {$path}");
24
+ $e->path = $path;
25
+
26
+ return $e;
27
+ }
28
+ }
vendor/browscap-php/league/flysystem/src/PortableVisibilityGuard.php ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\Flysystem;
6
+
7
+ final class PortableVisibilityGuard
8
+ {
9
+ public static function guardAgainstInvalidInput(string $visibility): void
10
+ {
11
+ if ($visibility !== Visibility::PUBLIC && $visibility !== Visibility::PRIVATE) {
12
+ $className = Visibility::class;
13
+ throw InvalidVisibilityProvided::withVisibility(
14
+ $visibility,
15
+ "either {$className}::PUBLIC or {$className}::PRIVATE"
16
+ );
17
+ }
18
+ }
19
+ }
vendor/browscap-php/league/flysystem/src/ProxyArrayAccessToProperties.php ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\Flysystem;
6
+
7
+ use RuntimeException;
8
+
9
+ /**
10
+ * @internal
11
+ */
12
+ trait ProxyArrayAccessToProperties
13
+ {
14
+ private function formatPropertyName(string $offset): string
15
+ {
16
+ return str_replace('_', '', lcfirst(ucwords($offset, '_')));
17
+ }
18
+
19
+ /**
20
+ * @param mixed $offset
21
+ *
22
+ * @return bool
23
+ */
24
+ public function offsetExists($offset): bool
25
+ {
26
+ $property = $this->formatPropertyName((string) $offset);
27
+
28
+ return isset($this->{$property});
29
+ }
30
+
31
+ /**
32
+ * @param mixed $offset
33
+ *
34
+ * @return mixed
35
+ */
36
+ #[\ReturnTypeWillChange]
37
+ public function offsetGet($offset)
38
+ {
39
+ $property = $this->formatPropertyName((string) $offset);
40
+
41
+ return $this->{$property};
42
+ }
43
+
44
+ /**
45
+ * @param mixed $offset
46
+ * @param mixed $value
47
+ */
48
+ #[\ReturnTypeWillChange]
49
+ public function offsetSet($offset, $value): void
50
+ {
51
+ throw new RuntimeException('Properties can not be manipulated');
52
+ }
53
+
54
+ /**
55
+ * @param mixed $offset
56
+ */
57
+ #[\ReturnTypeWillChange]
58
+ public function offsetUnset($offset): void
59
+ {
60
+ throw new RuntimeException('Properties can not be manipulated');
61
+ }
62
+ }
vendor/browscap-php/league/flysystem/src/StorageAttributes.php ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\Flysystem;
6
+
7
+ use ArrayAccess;
8
+ use JsonSerializable;
9
+
10
+ interface StorageAttributes extends JsonSerializable, ArrayAccess
11
+ {
12
+ public const ATTRIBUTE_PATH = 'path';
13
+ public const ATTRIBUTE_TYPE = 'type';
14
+ public const ATTRIBUTE_FILE_SIZE = 'file_size';
15
+ public const ATTRIBUTE_VISIBILITY = 'visibility';
16
+ public const ATTRIBUTE_LAST_MODIFIED = 'last_modified';
17
+ public const ATTRIBUTE_MIME_TYPE = 'mime_type';
18
+ public const ATTRIBUTE_EXTRA_METADATA = 'extra_metadata';
19
+
20
+ public const TYPE_FILE = 'file';
21
+ public const TYPE_DIRECTORY = 'dir';
22
+
23
+ public function path(): string;
24
+
25
+ public function type(): string;
26
+
27
+ public function visibility(): ?string;
28
+
29
+ public function lastModified(): ?int;
30
+
31
+ public static function fromArray(array $attributes): StorageAttributes;
32
+
33
+ public function isFile(): bool;
34
+
35
+ public function isDir(): bool;
36
+
37
+ public function withPath(string $path): StorageAttributes;
38
+
39
+ public function extraMetadata(): array;
40
+ }
vendor/browscap-php/league/flysystem/src/SymbolicLinkEncountered.php ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\Flysystem;
6
+
7
+ use RuntimeException;
8
+
9
+ final class SymbolicLinkEncountered extends RuntimeException implements FilesystemException
10
+ {
11
+ /**
12
+ * @var string
13
+ */
14
+ private $location;
15
+
16
+ public function location(): string
17
+ {
18
+ return $this->location;
19
+ }
20
+
21
+ public static function atLocation(string $pathName): SymbolicLinkEncountered
22
+ {
23
+ $e = new static("Unsupported symbolic link encountered at location $pathName");
24
+ $e->location = $pathName;
25
+
26
+ return $e;
27
+ }
28
+ }
vendor/browscap-php/league/flysystem/src/UnableToCheckFileExistence.php ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\Flysystem;
6
+
7
+ use RuntimeException;
8
+ use Throwable;
9
+
10
+ class UnableToCheckFileExistence extends RuntimeException implements FilesystemOperationFailed
11
+ {
12
+ public static function forLocation(string $path, Throwable $exception = null): UnableToCheckFileExistence
13
+ {
14
+ return new UnableToCheckFileExistence("Unable to check file existence for: ${path}", 0, $exception);
15
+ }
16
+
17
+ public function operation(): string
18
+ {
19
+ return FilesystemOperationFailed::OPERATION_FILE_EXISTS;
20
+ }
21
+ }
vendor/browscap-php/league/flysystem/src/UnableToCopyFile.php ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\Flysystem;
6
+
7
+ use RuntimeException;
8
+ use Throwable;
9
+
10
+ final class UnableToCopyFile extends RuntimeException implements FilesystemOperationFailed
11
+ {
12
+ /**
13
+ * @var string
14
+ */
15
+ private $source;
16
+
17
+ /**
18
+ * @var string
19
+ */
20
+ private $destination;
21
+
22
+ public function source(): string
23
+ {
24
+ return $this->source;
25
+ }
26
+
27
+ public function destination(): string
28
+ {
29
+ return $this->destination;
30
+ }
31
+
32
+ public static function fromLocationTo(
33
+ string $sourcePath,
34
+ string $destinationPath,
35
+ Throwable $previous = null
36
+ ): UnableToCopyFile {
37
+ $e = new static("Unable to copy file from $sourcePath to $destinationPath", 0 , $previous);
38
+ $e->source = $sourcePath;
39
+ $e->destination = $destinationPath;
40
+
41
+ return $e;
42
+ }
43
+
44
+ public function operation(): string
45
+ {
46
+ return FilesystemOperationFailed::OPERATION_COPY;
47
+ }
48
+ }
vendor/browscap-php/league/flysystem/src/UnableToCreateDirectory.php ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\Flysystem;
6
+
7
+ use RuntimeException;
8
+ use Throwable;
9
+
10
+ final class UnableToCreateDirectory extends RuntimeException implements FilesystemOperationFailed
11
+ {
12
+ /**
13
+ * @var string
14
+ */
15
+ private $location;
16
+
17
+ public static function atLocation(string $dirname, string $errorMessage = ''): UnableToCreateDirectory
18
+ {
19
+ $message = "Unable to create a directory at {$dirname}. ${errorMessage}";
20
+ $e = new static(rtrim($message));
21
+ $e->location = $dirname;
22
+
23
+ return $e;
24
+ }
25
+
26
+ public static function dueToFailure(string $dirname, Throwable $previous): UnableToCreateDirectory
27
+ {
28
+ $message = "Unable to create a directory at {$dirname}";
29
+ $e = new static($message, 0, $previous);
30
+ $e->location = $dirname;
31
+
32
+ return $e;
33
+ }
34
+
35
+ public function operation(): string
36
+ {
37
+ return FilesystemOperationFailed::OPERATION_CREATE_DIRECTORY;
38
+ }
39
+
40
+ public function location(): string
41
+ {
42
+ return $this->location;
43
+ }
44
+ }
vendor/browscap-php/league/flysystem/src/UnableToDeleteDirectory.php ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\Flysystem;
6
+
7
+ use RuntimeException;
8
+ use Throwable;
9
+
10
+ final class UnableToDeleteDirectory extends RuntimeException implements FilesystemOperationFailed
11
+ {
12
+ /**
13
+ * @var string
14
+ */
15
+ private $location = '';
16
+
17
+ /**
18
+ * @var string
19
+ */
20
+ private $reason;
21
+
22
+ public static function atLocation(
23
+ string $location,
24
+ string $reason = '',
25
+ Throwable $previous = null
26
+ ): UnableToDeleteDirectory {
27
+ $e = new static(rtrim("Unable to delete directory located at: {$location}. {$reason}"), 0, $previous);
28
+ $e->location = $location;
29
+ $e->reason = $reason;
30
+
31
+ return $e;
32
+ }
33
+
34
+ public function operation(): string
35
+ {
36
+ return FilesystemOperationFailed::OPERATION_DELETE_DIRECTORY;
37
+ }
38
+
39
+ public function reason(): string
40
+ {
41
+ return $this->reason;
42
+ }
43
+
44
+ public function location(): string
45
+ {
46
+ return $this->location;
47
+ }
48
+ }
vendor/browscap-php/league/flysystem/src/UnableToDeleteFile.php ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\Flysystem;
6
+
7
+ use RuntimeException;
8
+ use Throwable;
9
+
10
+ final class UnableToDeleteFile extends RuntimeException implements FilesystemOperationFailed
11
+ {
12
+ /**
13
+ * @var string
14
+ */
15
+ private $location = '';
16
+
17
+ /**
18
+ * @var string
19
+ */
20
+ private $reason;
21
+
22
+ public static function atLocation(string $location, string $reason = '', Throwable $previous = null): UnableToDeleteFile
23
+ {
24
+ $e = new static(rtrim("Unable to delete file located at: {$location}. {$reason}"), 0, $previous);
25
+ $e->location = $location;
26
+ $e->reason = $reason;
27
+
28
+ return $e;
29
+ }
30
+
31
+ public function operation(): string
32
+ {
33
+ return FilesystemOperationFailed::OPERATION_DELETE;
34
+ }
35
+
36
+ public function reason(): string
37
+ {
38
+ return $this->reason;
39
+ }
40
+
41
+ public function location(): string
42
+ {
43
+ return $this->location;
44
+ }
45
+ }
vendor/browscap-php/league/flysystem/src/UnableToMountFilesystem.php ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\Flysystem;
6
+
7
+ use LogicException;
8
+
9
+ class UnableToMountFilesystem extends LogicException implements FilesystemException
10
+ {
11
+ /**
12
+ * @param mixed $key
13
+ */
14
+ public static function becauseTheKeyIsNotValid($key): UnableToMountFilesystem
15
+ {
16
+ return new UnableToMountFilesystem(
17
+ 'Unable to mount filesystem, key was invalid. String expected, received: ' . gettype($key)
18
+ );
19
+ }
20
+
21
+ /**
22
+ * @param mixed $filesystem
23
+ */
24
+ public static function becauseTheFilesystemWasNotValid($filesystem): UnableToMountFilesystem
25
+ {
26
+ $received = is_object($filesystem) ? get_class($filesystem) : gettype($filesystem);
27
+
28
+ return new UnableToMountFilesystem(
29
+ 'Unable to mount filesystem, filesystem was invalid. Instance of ' . FilesystemOperator::class . ' expected, received: ' . $received
30
+ );
31
+ }
32
+ }
vendor/browscap-php/league/flysystem/src/UnableToMoveFile.php ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\Flysystem;
6
+
7
+ use RuntimeException;
8
+ use Throwable;
9
+
10
+ final class UnableToMoveFile extends RuntimeException implements FilesystemOperationFailed
11
+ {
12
+ /**
13
+ * @var string
14
+ */
15
+ private $source;
16
+
17
+ /**
18
+ * @var string
19
+ */
20
+ private $destination;
21
+
22
+ public function source(): string
23
+ {
24
+ return $this->source;
25
+ }
26
+
27
+ public function destination(): string
28
+ {
29
+ return $this->destination;
30
+ }
31
+
32
+ public static function fromLocationTo(
33
+ string $sourcePath,
34
+ string $destinationPath,
35
+ Throwable $previous = null
36
+ ): UnableToMoveFile {
37
+ $e = new static("Unable to move file from $sourcePath to $destinationPath", 0, $previous);
38
+ $e->source = $sourcePath;
39
+ $e->destination = $destinationPath;
40
+
41
+ return $e;
42
+ }
43
+
44
+ public function operation(): string
45
+ {
46
+ return FilesystemOperationFailed::OPERATION_MOVE;
47
+ }
48
+ }
vendor/browscap-php/league/flysystem/src/UnableToReadFile.php ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\Flysystem;
6
+
7
+ use RuntimeException;
8
+ use Throwable;
9
+
10
+ final class UnableToReadFile extends RuntimeException implements FilesystemOperationFailed
11
+ {
12
+ /**
13
+ * @var string
14
+ */
15
+ private $location = '';
16
+
17
+ /**
18
+ * @var string
19
+ */
20
+ private $reason = '';
21
+
22
+ public static function fromLocation(string $location, string $reason = '', Throwable $previous = null): UnableToReadFile
23
+ {
24
+ $e = new static(rtrim("Unable to read file from location: {$location}. {$reason}"), 0, $previous);
25
+ $e->location = $location;
26
+ $e->reason = $reason;
27
+
28
+ return $e;
29
+ }
30
+
31
+ public function operation(): string
32
+ {
33
+ return FilesystemOperationFailed::OPERATION_READ;
34
+ }
35
+
36
+ public function reason(): string
37
+ {
38
+ return $this->reason;
39
+ }
40
+
41
+ public function location(): string
42
+ {
43
+ return $this->location;
44
+ }
45
+ }
vendor/browscap-php/league/flysystem/src/UnableToResolveFilesystemMount.php ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\Flysystem;
6
+
7
+ use RuntimeException;
8
+
9
+ class UnableToResolveFilesystemMount extends RuntimeException implements FilesystemException
10
+ {
11
+ public static function becauseTheSeparatorIsMissing(string $path): UnableToResolveFilesystemMount
12
+ {
13
+ return new UnableToResolveFilesystemMount("Unable to resolve the filesystem mount because the path ($path) is missing a separator (://).");
14
+ }
15
+
16
+ public static function becauseTheMountWasNotRegistered(string $mountIdentifier): UnableToResolveFilesystemMount
17
+ {
18
+ return new UnableToResolveFilesystemMount("Unable to resolve the filesystem mount because the mount ($mountIdentifier) was not registered.");
19
+ }
20
+ }
vendor/browscap-php/league/flysystem/src/UnableToRetrieveMetadata.php ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\Flysystem;
6
+
7
+ use RuntimeException;
8
+ use Throwable;
9
+
10
+ final class UnableToRetrieveMetadata extends RuntimeException implements FilesystemOperationFailed
11
+ {
12
+ /**
13
+ * @var string
14
+ */
15
+ private $location;
16
+
17
+ /**
18
+ * @var string
19
+ */
20
+ private $metadataType;
21
+
22
+ /**
23
+ * @var string
24
+ */
25
+ private $reason;
26
+
27
+ public static function lastModified(string $location, string $reason = '', Throwable $previous = null): self
28
+ {
29
+ return static::create($location, FileAttributes::ATTRIBUTE_LAST_MODIFIED, $reason, $previous);
30
+ }
31
+
32
+ public static function visibility(string $location, string $reason = '', Throwable $previous = null): self
33
+ {
34
+ return static::create($location, FileAttributes::ATTRIBUTE_VISIBILITY, $reason, $previous);
35
+ }
36
+
37
+ public static function fileSize(string $location, string $reason = '', Throwable $previous = null): self
38
+ {
39
+ return static::create($location, FileAttributes::ATTRIBUTE_FILE_SIZE, $reason, $previous);
40
+ }
41
+
42
+ public static function mimeType(string $location, string $reason = '', Throwable $previous = null): self
43
+ {
44
+ return static::create($location, FileAttributes::ATTRIBUTE_MIME_TYPE, $reason, $previous);
45
+ }
46
+
47
+ public static function create(string $location, string $type, string $reason = '', Throwable $previous = null): self
48
+ {
49
+ $e = new static("Unable to retrieve the $type for file at location: $location. {$reason}", 0, $previous);
50
+ $e->reason = $reason;
51
+ $e->location = $location;
52
+ $e->metadataType = $type;
53
+
54
+ return $e;
55
+ }
56
+
57
+ public function reason(): string
58
+ {
59
+ return $this->reason;
60
+ }
61
+
62
+ public function location(): string
63
+ {
64
+ return $this->location;
65
+ }
66
+
67
+ public function metadataType(): string
68
+ {
69
+ return $this->metadataType;
70
+ }
71
+
72
+ public function operation(): string
73
+ {
74
+ return FilesystemOperationFailed::OPERATION_RETRIEVE_METADATA;
75
+ }
76
+ }
vendor/browscap-php/league/flysystem/src/UnableToSetVisibility.php ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\Flysystem;
6
+
7
+ use RuntimeException;
8
+
9
+ use Throwable;
10
+
11
+ use function rtrim;
12
+
13
+ final class UnableToSetVisibility extends RuntimeException implements FilesystemOperationFailed
14
+ {
15
+ /**
16
+ * @var string
17
+ */
18
+ private $location;
19
+
20
+ /**
21
+ * @var string
22
+ */
23
+ private $reason;
24
+
25
+ public function reason(): string
26
+ {
27
+ return $this->reason;
28
+ }
29
+
30
+ public static function atLocation(string $filename, string $extraMessage = '', Throwable $previous = null): self
31
+ {
32
+ $message = "Unable to set visibility for file {$filename}. $extraMessage";
33
+ $e = new static(rtrim($message), 0, $previous);
34
+ $e->reason = $extraMessage;
35
+ $e->location = $filename;
36
+
37
+ return $e;
38
+ }
39
+
40
+ public function operation(): string
41
+ {
42
+ return FilesystemOperationFailed::OPERATION_SET_VISIBILITY;
43
+ }
44
+
45
+ public function location(): string
46
+ {
47
+ return $this->location;
48
+ }
49
+ }
vendor/browscap-php/league/flysystem/src/UnableToWriteFile.php ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\Flysystem;
6
+
7
+ use RuntimeException;
8
+ use Throwable;
9
+
10
+ final class UnableToWriteFile extends RuntimeException implements FilesystemOperationFailed
11
+ {
12
+ /**
13
+ * @var string
14
+ */
15
+ private $location = '';
16
+
17
+ /**
18
+ * @var string
19
+ */
20
+ private $reason;
21
+
22
+ public static function atLocation(string $location, string $reason = '', Throwable $previous = null): UnableToWriteFile
23
+ {
24
+ $e = new static(rtrim("Unable to write file at location: {$location}. {$reason}"), 0, $previous);
25
+ $e->location = $location;
26
+ $e->reason = $reason;
27
+
28
+ return $e;
29
+ }
30
+
31
+ public function operation(): string
32
+ {
33
+ return FilesystemOperationFailed::OPERATION_WRITE;
34
+ }
35
+
36
+ public function reason(): string
37
+ {
38
+ return $this->reason;
39
+ }
40
+
41
+ public function location(): string
42
+ {
43
+ return $this->location;
44
+ }
45
+ }
vendor/browscap-php/league/flysystem/src/UnixVisibility/PortableVisibilityConverter.php ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\Flysystem\UnixVisibility;
6
+
7
+ use League\Flysystem\PortableVisibilityGuard;
8
+ use League\Flysystem\Visibility;
9
+
10
+ class PortableVisibilityConverter implements VisibilityConverter
11
+ {
12
+ /**
13
+ * @var int
14
+ */
15
+ private $filePublic;
16
+
17
+ /**
18
+ * @var int
19
+ */
20
+ private $filePrivate;
21
+
22
+ /**
23
+ * @var int
24
+ */
25
+ private $directoryPublic;
26
+
27
+ /**
28
+ * @var int
29
+ */
30
+ private $directoryPrivate;
31
+
32
+ /**
33
+ * @var string
34
+ */
35
+ private $defaultForDirectories;
36
+
37
+ public function __construct(
38
+ int $filePublic = 0644,
39
+ int $filePrivate = 0600,
40
+ int $directoryPublic = 0755,
41
+ int $directoryPrivate = 0700,
42
+ string $defaultForDirectories = Visibility::PRIVATE
43
+ ) {
44
+ $this->filePublic = $filePublic;
45
+ $this->filePrivate = $filePrivate;
46
+ $this->directoryPublic = $directoryPublic;
47
+ $this->directoryPrivate = $directoryPrivate;
48
+ $this->defaultForDirectories = $defaultForDirectories;
49
+ }
50
+
51
+ public function forFile(string $visibility): int
52
+ {
53
+ PortableVisibilityGuard::guardAgainstInvalidInput($visibility);
54
+
55
+ return $visibility === Visibility::PUBLIC
56
+ ? $this->filePublic
57
+ : $this->filePrivate;
58
+ }
59
+
60
+ public function forDirectory(string $visibility): int
61
+ {
62
+ PortableVisibilityGuard::guardAgainstInvalidInput($visibility);
63
+
64
+ return $visibility === Visibility::PUBLIC
65
+ ? $this->directoryPublic
66
+ : $this->directoryPrivate;
67
+ }
68
+
69
+ public function inverseForFile(int $visibility): string
70
+ {
71
+ if ($visibility === $this->filePublic) {
72
+ return Visibility::PUBLIC;
73
+ } elseif ($visibility === $this->filePrivate) {
74
+ return Visibility::PRIVATE;
75
+ }
76
+
77
+ return Visibility::PUBLIC; // default
78
+ }
79
+
80
+ public function inverseForDirectory(int $visibility): string
81
+ {
82
+ if ($visibility === $this->directoryPublic) {
83
+ return Visibility::PUBLIC;
84
+ } elseif ($visibility === $this->directoryPrivate) {
85
+ return Visibility::PRIVATE;
86
+ }
87
+
88
+ return Visibility::PUBLIC; // default
89
+ }
90
+
91
+ public function defaultForDirectories(): int
92
+ {
93
+ return $this->defaultForDirectories === Visibility::PUBLIC ? $this->directoryPublic : $this->directoryPrivate;
94
+ }
95
+
96
+ /**
97
+ * @param array<mixed> $permissionMap
98
+ */
99
+ public static function fromArray(array $permissionMap, string $defaultForDirectories = Visibility::PRIVATE): PortableVisibilityConverter
100
+ {
101
+ return new PortableVisibilityConverter(
102
+ $permissionMap['file']['public'] ?? 0644,
103
+ $permissionMap['file']['private'] ?? 0600,
104
+ $permissionMap['dir']['public'] ?? 0755,
105
+ $permissionMap['dir']['private'] ?? 0700,
106
+ $defaultForDirectories
107
+ );
108
+ }
109
+ }
vendor/browscap-php/league/flysystem/src/UnixVisibility/VisibilityConverter.php ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\Flysystem\UnixVisibility;
6
+
7
+ interface VisibilityConverter
8
+ {
9
+ public function forFile(string $visibility): int;
10
+ public function forDirectory(string $visibility): int;
11
+ public function inverseForFile(int $visibility): string;
12
+ public function inverseForDirectory(int $visibility): string;
13
+ public function defaultForDirectories(): int;
14
+ }
vendor/browscap-php/league/flysystem/src/UnreadableFileEncountered.php ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\Flysystem;
6
+
7
+ use RuntimeException;
8
+
9
+ final class UnreadableFileEncountered extends RuntimeException implements FilesystemException
10
+ {
11
+ /**
12
+ * @var string
13
+ */
14
+ private $location;
15
+
16
+ public function location(): string
17
+ {
18
+ return $this->location;
19
+ }
20
+
21
+ public static function atLocation(string $location): UnreadableFileEncountered
22
+ {
23
+ $e = new static("Unreadable file encountered at location {$location}.");
24
+ $e->location = $location;
25
+
26
+ return $e;
27
+ }
28
+ }
vendor/browscap-php/league/flysystem/src/Visibility.php ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\Flysystem;
6
+
7
+ final class Visibility
8
+ {
9
+ public const PUBLIC = 'public';
10
+ public const PRIVATE = 'private';
11
+ }
vendor/browscap-php/league/flysystem/src/WhitespacePathNormalizer.php ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\Flysystem;
6
+
7
+ class WhitespacePathNormalizer implements PathNormalizer
8
+ {
9
+ public function normalizePath(string $path): string
10
+ {
11
+ $path = str_replace('\\', '/', $path);
12
+ $this->rejectFunkyWhiteSpace($path);
13
+
14
+ return $this->normalizeRelativePath($path);
15
+ }
16
+
17
+ private function rejectFunkyWhiteSpace(string $path): void
18
+ {
19
+ if (preg_match('#\p{C}+#u', $path)) {
20
+ throw CorruptedPathDetected::forPath($path);
21
+ }
22
+ }
23
+
24
+ private function normalizeRelativePath(string $path): string
25
+ {
26
+ $parts = [];
27
+
28
+ foreach (explode('/', $path) as $part) {
29
+ switch ($part) {
30
+ case '':
31
+ case '.':
32
+ break;
33
+
34
+ case '..':
35
+ if (empty($parts)) {
36
+ throw PathTraversalDetected::forPath($path);
37
+ }
38
+ array_pop($parts);
39
+ break;
40
+
41
+ default:
42
+ $parts[] = $part;
43
+ break;
44
+ }
45
+ }
46
+
47
+ return implode('/', $parts);
48
+ }
49
+ }
vendor/browscap-php/league/mime-type-detection/src/EmptyExtensionToMimeTypeMap.php ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\MimeTypeDetection;
6
+
7
+ class EmptyExtensionToMimeTypeMap implements ExtensionToMimeTypeMap
8
+ {
9
+ public function lookupMimeType(string $extension): ?string
10
+ {
11
+ return null;
12
+ }
13
+ }
vendor/browscap-php/league/mime-type-detection/src/ExtensionMimeTypeDetector.php ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\MimeTypeDetection;
6
+
7
+ use const PATHINFO_EXTENSION;
8
+
9
+ class ExtensionMimeTypeDetector implements MimeTypeDetector
10
+ {
11
+ /**
12
+ * @var ExtensionToMimeTypeMap
13
+ */
14
+ private $extensions;
15
+
16
+ public function __construct(ExtensionToMimeTypeMap $extensions = null)
17
+ {
18
+ $this->extensions = $extensions ?: new GeneratedExtensionToMimeTypeMap();
19
+ }
20
+
21
+ public function detectMimeType(string $path, $contents): ?string
22
+ {
23
+ return $this->detectMimeTypeFromPath($path);
24
+ }
25
+
26
+ public function detectMimeTypeFromPath(string $path): ?string
27
+ {
28
+ $extension = strtolower(pathinfo($path, PATHINFO_EXTENSION));
29
+
30
+ return $this->extensions->lookupMimeType($extension);
31
+ }
32
+
33
+ public function detectMimeTypeFromFile(string $path): ?string
34
+ {
35
+ return $this->detectMimeTypeFromPath($path);
36
+ }
37
+
38
+ public function detectMimeTypeFromBuffer(string $contents): ?string
39
+ {
40
+ return null;
41
+ }
42
+ }
vendor/browscap-php/league/mime-type-detection/src/ExtensionToMimeTypeMap.php ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\MimeTypeDetection;
6
+
7
+ interface ExtensionToMimeTypeMap
8
+ {
9
+ public function lookupMimeType(string $extension): ?string;
10
+ }
vendor/browscap-php/league/mime-type-detection/src/FinfoMimeTypeDetector.php ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\MimeTypeDetection;
6
+
7
+ use const FILEINFO_MIME_TYPE;
8
+
9
+ use const PATHINFO_EXTENSION;
10
+ use finfo;
11
+
12
+ class FinfoMimeTypeDetector implements MimeTypeDetector
13
+ {
14
+ private const INCONCLUSIVE_MIME_TYPES = ['application/x-empty', 'text/plain', 'text/x-asm'];
15
+
16
+ /**
17
+ * @var finfo
18
+ */
19
+ private $finfo;
20
+
21
+ /**
22
+ * @var ExtensionToMimeTypeMap
23
+ */
24
+ private $extensionMap;
25
+
26
+ /**
27
+ * @var int|null
28
+ */
29
+ private $bufferSampleSize;
30
+
31
+ public function __construct(
32
+ string $magicFile = '',
33
+ ExtensionToMimeTypeMap $extensionMap = null,
34
+ ?int $bufferSampleSize = null
35
+ ) {
36
+ $this->finfo = new finfo(FILEINFO_MIME_TYPE, $magicFile);
37
+ $this->extensionMap = $extensionMap ?: new GeneratedExtensionToMimeTypeMap();
38
+ $this->bufferSampleSize = $bufferSampleSize;
39
+ }
40
+
41
+ public function detectMimeType(string $path, $contents): ?string
42
+ {
43
+ $mimeType = is_string($contents)
44
+ ? (@$this->finfo->buffer($this->takeSample($contents)) ?: null)
45
+ : null;
46
+
47
+ if ($mimeType !== null && ! in_array($mimeType, self::INCONCLUSIVE_MIME_TYPES)) {
48
+ return $mimeType;
49
+ }
50
+
51
+ return $this->detectMimeTypeFromPath($path);
52
+ }
53
+
54
+ public function detectMimeTypeFromPath(string $path): ?string
55
+ {
56
+ $extension = strtolower(pathinfo($path, PATHINFO_EXTENSION));
57
+
58
+ return $this->extensionMap->lookupMimeType($extension);
59
+ }
60
+
61
+ public function detectMimeTypeFromFile(string $path): ?string
62
+ {
63
+ return @$this->finfo->file($path) ?: null;
64
+ }
65
+
66
+ public function detectMimeTypeFromBuffer(string $contents): ?string
67
+ {
68
+ return @$this->finfo->buffer($this->takeSample($contents)) ?: null;
69
+ }
70
+
71
+ private function takeSample(string $contents): string
72
+ {
73
+ if ($this->bufferSampleSize === null) {
74
+ return $contents;
75
+ }
76
+
77
+ return (string) substr($contents, 0, $this->bufferSampleSize);
78
+ }
79
+ }
vendor/browscap-php/league/mime-type-detection/src/GeneratedExtensionToMimeTypeMap.php ADDED
@@ -0,0 +1,1220 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\MimeTypeDetection;
6
+
7
+ class GeneratedExtensionToMimeTypeMap implements ExtensionToMimeTypeMap
8
+ {
9
+ /**
10
+ * @var string[]
11
+ *
12
+ * @internal
13
+ */
14
+ public const MIME_TYPES_FOR_EXTENSIONS = [
15
+ '1km' => 'application/vnd.1000minds.decision-model+xml',
16
+ '3dml' => 'text/vnd.in3d.3dml',
17
+ '3ds' => 'image/x-3ds',
18
+ '3g2' => 'video/3gpp2',
19
+ '3gp' => 'video/3gp',
20
+ '3gpp' => 'video/3gpp',
21
+ '3mf' => 'model/3mf',
22
+ '7z' => 'application/x-7z-compressed',
23
+ '7zip' => 'application/x-7z-compressed',
24
+ '123' => 'application/vnd.lotus-1-2-3',
25
+ 'aab' => 'application/x-authorware-bin',
26
+ 'aac' => 'audio/x-acc',
27
+ 'aam' => 'application/x-authorware-map',
28
+ 'aas' => 'application/x-authorware-seg',
29
+ 'abw' => 'application/x-abiword',
30
+ 'ac' => 'application/vnd.nokia.n-gage.ac+xml',
31
+ 'ac3' => 'audio/ac3',
32
+ 'acc' => 'application/vnd.americandynamics.acc',
33
+ 'ace' => 'application/x-ace-compressed',
34
+ 'acu' => 'application/vnd.acucobol',
35
+ 'acutc' => 'application/vnd.acucorp',
36
+ 'adp' => 'audio/adpcm',
37
+ 'aep' => 'application/vnd.audiograph',
38
+ 'afm' => 'application/x-font-type1',
39
+ 'afp' => 'application/vnd.ibm.modcap',
40
+ 'age' => 'application/vnd.age',
41
+ 'ahead' => 'application/vnd.ahead.space',
42
+ 'ai' => 'application/pdf',
43
+ 'aif' => 'audio/x-aiff',
44
+ 'aifc' => 'audio/x-aiff',
45
+ 'aiff' => 'audio/x-aiff',
46
+ 'air' => 'application/vnd.adobe.air-application-installer-package+zip',
47
+ 'ait' => 'application/vnd.dvb.ait',
48
+ 'ami' => 'application/vnd.amiga.ami',
49
+ 'amr' => 'audio/amr',
50
+ 'apk' => 'application/vnd.android.package-archive',
51
+ 'apng' => 'image/apng',
52
+ 'appcache' => 'text/cache-manifest',
53
+ 'application' => 'application/x-ms-application',
54
+ 'apr' => 'application/vnd.lotus-approach',
55
+ 'arc' => 'application/x-freearc',
56
+ 'arj' => 'application/x-arj',
57
+ 'asc' => 'application/pgp-signature',
58
+ 'asf' => 'video/x-ms-asf',
59
+ 'asm' => 'text/x-asm',
60
+ 'aso' => 'application/vnd.accpac.simply.aso',
61
+ 'asx' => 'video/x-ms-asf',
62
+ 'atc' => 'application/vnd.acucorp',
63
+ 'atom' => 'application/atom+xml',
64
+ 'atomcat' => 'application/atomcat+xml',
65
+ 'atomdeleted' => 'application/atomdeleted+xml',
66
+ 'atomsvc' => 'application/atomsvc+xml',
67
+ 'atx' => 'application/vnd.antix.game-component',
68
+ 'au' => 'audio/x-au',
69
+ 'avi' => 'video/x-msvideo',
70
+ 'avif' => 'image/avif',
71
+ 'aw' => 'application/applixware',
72
+ 'azf' => 'application/vnd.airzip.filesecure.azf',
73
+ 'azs' => 'application/vnd.airzip.filesecure.azs',
74
+ 'azv' => 'image/vnd.airzip.accelerator.azv',
75
+ 'azw' => 'application/vnd.amazon.ebook',
76
+ 'b16' => 'image/vnd.pco.b16',
77
+ 'bat' => 'application/x-msdownload',
78
+ 'bcpio' => 'application/x-bcpio',
79
+ 'bdf' => 'application/x-font-bdf',
80
+ 'bdm' => 'application/vnd.syncml.dm+wbxml',
81
+ 'bdoc' => 'application/x-bdoc',
82
+ 'bed' => 'application/vnd.realvnc.bed',
83
+ 'bh2' => 'application/vnd.fujitsu.oasysprs',
84
+ 'bin' => 'application/octet-stream',
85
+ 'blb' => 'application/x-blorb',
86
+ 'blorb' => 'application/x-blorb',
87
+ 'bmi' => 'application/vnd.bmi',
88
+ 'bmml' => 'application/vnd.balsamiq.bmml+xml',
89
+ 'bmp' => 'image/bmp',
90
+ 'book' => 'application/vnd.framemaker',
91
+ 'box' => 'application/vnd.previewsystems.box',
92
+ 'boz' => 'application/x-bzip2',
93
+ 'bpk' => 'application/octet-stream',
94
+ 'bpmn' => 'application/octet-stream',
95
+ 'bsp' => 'model/vnd.valve.source.compiled-map',
96
+ 'btif' => 'image/prs.btif',
97
+ 'buffer' => 'application/octet-stream',
98
+ 'bz' => 'application/x-bzip',
99
+ 'bz2' => 'application/x-bzip2',
100
+ 'c' => 'text/x-c',
101
+ 'c4d' => 'application/vnd.clonk.c4group',
102
+ 'c4f' => 'application/vnd.clonk.c4group',
103
+ 'c4g' => 'application/vnd.clonk.c4group',
104
+ 'c4p' => 'application/vnd.clonk.c4group',
105
+ 'c4u' => 'application/vnd.clonk.c4group',
106
+ 'c11amc' => 'application/vnd.cluetrust.cartomobile-config',
107
+ 'c11amz' => 'application/vnd.cluetrust.cartomobile-config-pkg',
108
+ 'cab' => 'application/vnd.ms-cab-compressed',
109
+ 'caf' => 'audio/x-caf',
110
+ 'cap' => 'application/vnd.tcpdump.pcap',
111
+ 'car' => 'application/vnd.curl.car',
112
+ 'cat' => 'application/vnd.ms-pki.seccat',
113
+ 'cb7' => 'application/x-cbr',
114
+ 'cba' => 'application/x-cbr',
115
+ 'cbr' => 'application/x-cbr',
116
+ 'cbt' => 'application/x-cbr',
117
+ 'cbz' => 'application/x-cbr',
118
+ 'cc' => 'text/x-c',
119
+ 'cco' => 'application/x-cocoa',
120
+ 'cct' => 'application/x-director',
121
+ 'ccxml' => 'application/ccxml+xml',
122
+ 'cdbcmsg' => 'application/vnd.contact.cmsg',
123
+ 'cdf' => 'application/x-netcdf',
124
+ 'cdfx' => 'application/cdfx+xml',
125
+ 'cdkey' => 'application/vnd.mediastation.cdkey',
126
+ 'cdmia' => 'application/cdmi-capability',
127
+ 'cdmic' => 'application/cdmi-container',
128
+ 'cdmid' => 'application/cdmi-domain',
129
+ 'cdmio' => 'application/cdmi-object',
130
+ 'cdmiq' => 'application/cdmi-queue',
131
+ 'cdr' => 'application/cdr',
132
+ 'cdx' => 'chemical/x-cdx',
133
+ 'cdxml' => 'application/vnd.chemdraw+xml',
134
+ 'cdy' => 'application/vnd.cinderella',
135
+ 'cer' => 'application/pkix-cert',
136
+ 'cfs' => 'application/x-cfs-compressed',
137
+ 'cgm' => 'image/cgm',
138
+ 'chat' => 'application/x-chat',
139
+ 'chm' => 'application/vnd.ms-htmlhelp',
140
+ 'chrt' => 'application/vnd.kde.kchart',
141
+ 'cif' => 'chemical/x-cif',
142
+ 'cii' => 'application/vnd.anser-web-certificate-issue-initiation',
143
+ 'cil' => 'application/vnd.ms-artgalry',
144
+ 'cjs' => 'application/node',
145
+ 'cla' => 'application/vnd.claymore',
146
+ 'class' => 'application/octet-stream',
147
+ 'clkk' => 'application/vnd.crick.clicker.keyboard',
148
+ 'clkp' => 'application/vnd.crick.clicker.palette',
149
+ 'clkt' => 'application/vnd.crick.clicker.template',
150
+ 'clkw' => 'application/vnd.crick.clicker.wordbank',
151
+ 'clkx' => 'application/vnd.crick.clicker',
152
+ 'clp' => 'application/x-msclip',
153
+ 'cmc' => 'application/vnd.cosmocaller',
154
+ 'cmdf' => 'chemical/x-cmdf',
155
+ 'cml' => 'chemical/x-cml',
156
+ 'cmp' => 'application/vnd.yellowriver-custom-menu',
157
+ 'cmx' => 'image/x-cmx',
158
+ 'cod' => 'application/vnd.rim.cod',
159
+ 'coffee' => 'text/coffeescript',
160
+ 'com' => 'application/x-msdownload',
161
+ 'conf' => 'text/plain',
162
+ 'cpio' => 'application/x-cpio',
163
+ 'cpp' => 'text/x-c',
164
+ 'cpt' => 'application/mac-compactpro',
165
+ 'crd' => 'application/x-mscardfile',
166
+ 'crl' => 'application/pkix-crl',
167
+ 'crt' => 'application/x-x509-ca-cert',
168
+ 'crx' => 'application/x-chrome-extension',
169
+ 'cryptonote' => 'application/vnd.rig.cryptonote',
170
+ 'csh' => 'application/x-csh',
171
+ 'csl' => 'application/vnd.citationstyles.style+xml',
172
+ 'csml' => 'chemical/x-csml',
173
+ 'csp' => 'application/vnd.commonspace',
174
+ 'csr' => 'application/octet-stream',
175
+ 'css' => 'text/css',
176
+ 'cst' => 'application/x-director',
177
+ 'csv' => 'text/csv',
178
+ 'cu' => 'application/cu-seeme',
179
+ 'curl' => 'text/vnd.curl',
180
+ 'cww' => 'application/prs.cww',
181
+ 'cxt' => 'application/x-director',
182
+ 'cxx' => 'text/x-c',
183
+ 'dae' => 'model/vnd.collada+xml',
184
+ 'daf' => 'application/vnd.mobius.daf',
185
+ 'dart' => 'application/vnd.dart',
186
+ 'dataless' => 'application/vnd.fdsn.seed',
187
+ 'davmount' => 'application/davmount+xml',
188
+ 'dbf' => 'application/vnd.dbf',
189
+ 'dbk' => 'application/docbook+xml',
190
+ 'dcr' => 'application/x-director',
191
+ 'dcurl' => 'text/vnd.curl.dcurl',
192
+ 'dd2' => 'application/vnd.oma.dd2+xml',
193
+ 'ddd' => 'application/vnd.fujixerox.ddd',
194
+ 'ddf' => 'application/vnd.syncml.dmddf+xml',
195
+ 'dds' => 'image/vnd.ms-dds',
196
+ 'deb' => 'application/x-debian-package',
197
+ 'def' => 'text/plain',
198
+ 'deploy' => 'application/octet-stream',
199
+ 'der' => 'application/x-x509-ca-cert',
200
+ 'dfac' => 'application/vnd.dreamfactory',
201
+ 'dgc' => 'application/x-dgc-compressed',
202
+ 'dic' => 'text/x-c',
203
+ 'dir' => 'application/x-director',
204
+ 'dis' => 'application/vnd.mobius.dis',
205
+ 'disposition-notification' => 'message/disposition-notification',
206
+ 'dist' => 'application/octet-stream',
207
+ 'distz' => 'application/octet-stream',
208
+ 'djv' => 'image/vnd.djvu',
209
+ 'djvu' => 'image/vnd.djvu',
210
+ 'dll' => 'application/octet-stream',
211
+ 'dmg' => 'application/x-apple-diskimage',
212
+ 'dmn' => 'application/octet-stream',
213
+ 'dmp' => 'application/vnd.tcpdump.pcap',
214
+ 'dms' => 'application/octet-stream',
215
+ 'dna' => 'application/vnd.dna',
216
+ 'doc' => 'application/msword',
217
+ 'docm' => 'application/vnd.ms-word.template.macroEnabled.12',
218
+ 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
219
+ 'dot' => 'application/msword',
220
+ 'dotm' => 'application/vnd.ms-word.template.macroEnabled.12',
221
+ 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
222
+ 'dp' => 'application/vnd.osgi.dp',
223
+ 'dpg' => 'application/vnd.dpgraph',
224
+ 'dra' => 'audio/vnd.dra',
225
+ 'drle' => 'image/dicom-rle',
226
+ 'dsc' => 'text/prs.lines.tag',
227
+ 'dssc' => 'application/dssc+der',
228
+ 'dtb' => 'application/x-dtbook+xml',
229
+ 'dtd' => 'application/xml-dtd',
230
+ 'dts' => 'audio/vnd.dts',
231
+ 'dtshd' => 'audio/vnd.dts.hd',
232
+ 'dump' => 'application/octet-stream',
233
+ 'dvb' => 'video/vnd.dvb.file',
234
+ 'dvi' => 'application/x-dvi',
235
+ 'dwd' => 'application/atsc-dwd+xml',
236
+ 'dwf' => 'model/vnd.dwf',
237
+ 'dwg' => 'image/vnd.dwg',
238
+ 'dxf' => 'image/vnd.dxf',
239
+ 'dxp' => 'application/vnd.spotfire.dxp',
240
+ 'dxr' => 'application/x-director',
241
+ 'ear' => 'application/java-archive',
242
+ 'ecelp4800' => 'audio/vnd.nuera.ecelp4800',
243
+ 'ecelp7470' => 'audio/vnd.nuera.ecelp7470',
244
+ 'ecelp9600' => 'audio/vnd.nuera.ecelp9600',
245
+ 'ecma' => 'application/ecmascript',
246
+ 'edm' => 'application/vnd.novadigm.edm',
247
+ 'edx' => 'application/vnd.novadigm.edx',
248
+ 'efif' => 'application/vnd.picsel',
249
+ 'ei6' => 'application/vnd.pg.osasli',
250
+ 'elc' => 'application/octet-stream',
251
+ 'emf' => 'image/emf',
252
+ 'eml' => 'message/rfc822',
253
+ 'emma' => 'application/emma+xml',
254
+ 'emotionml' => 'application/emotionml+xml',
255
+ 'emz' => 'application/x-msmetafile',
256
+ 'eol' => 'audio/vnd.digital-winds',
257
+ 'eot' => 'application/vnd.ms-fontobject',
258
+ 'eps' => 'application/postscript',
259
+ 'epub' => 'application/epub+zip',
260
+ 'es' => 'application/ecmascript',
261
+ 'es3' => 'application/vnd.eszigno3+xml',
262
+ 'esa' => 'application/vnd.osgi.subsystem',
263
+ 'esf' => 'application/vnd.epson.esf',
264
+ 'et3' => 'application/vnd.eszigno3+xml',
265
+ 'etx' => 'text/x-setext',
266
+ 'eva' => 'application/x-eva',
267
+ 'evy' => 'application/x-envoy',
268
+ 'exe' => 'application/octet-stream',
269
+ 'exi' => 'application/exi',
270
+ 'exp' => 'application/express',
271
+ 'exr' => 'image/aces',
272
+ 'ext' => 'application/vnd.novadigm.ext',
273
+ 'ez' => 'application/andrew-inset',
274
+ 'ez2' => 'application/vnd.ezpix-album',
275
+ 'ez3' => 'application/vnd.ezpix-package',
276
+ 'f' => 'text/x-fortran',
277
+ 'f4v' => 'video/mp4',
278
+ 'f77' => 'text/x-fortran',
279
+ 'f90' => 'text/x-fortran',
280
+ 'fbs' => 'image/vnd.fastbidsheet',
281
+ 'fcdt' => 'application/vnd.adobe.formscentral.fcdt',
282
+ 'fcs' => 'application/vnd.isac.fcs',
283
+ 'fdf' => 'application/vnd.fdf',
284
+ 'fdt' => 'application/fdt+xml',
285
+ 'fe_launch' => 'application/vnd.denovo.fcselayout-link',
286
+ 'fg5' => 'application/vnd.fujitsu.oasysgp',
287
+ 'fgd' => 'application/x-director',
288
+ 'fh' => 'image/x-freehand',
289
+ 'fh4' => 'image/x-freehand',
290
+ 'fh5' => 'image/x-freehand',
291
+ 'fh7' => 'image/x-freehand',
292
+ 'fhc' => 'image/x-freehand',
293
+ 'fig' => 'application/x-xfig',
294
+ 'fits' => 'image/fits',
295
+ 'flac' => 'audio/x-flac',
296
+ 'fli' => 'video/x-fli',
297
+ 'flo' => 'application/vnd.micrografx.flo',
298
+ 'flv' => 'video/x-flv',
299
+ 'flw' => 'application/vnd.kde.kivio',
300
+ 'flx' => 'text/vnd.fmi.flexstor',
301
+ 'fly' => 'text/vnd.fly',
302
+ 'fm' => 'application/vnd.framemaker',
303
+ 'fnc' => 'application/vnd.frogans.fnc',
304
+ 'fo' => 'application/vnd.software602.filler.form+xml',
305
+ 'for' => 'text/x-fortran',
306
+ 'fpx' => 'image/vnd.fpx',
307
+ 'frame' => 'application/vnd.framemaker',
308
+ 'fsc' => 'application/vnd.fsc.weblaunch',
309
+ 'fst' => 'image/vnd.fst',
310
+ 'ftc' => 'application/vnd.fluxtime.clip',
311
+ 'fti' => 'application/vnd.anser-web-funds-transfer-initiation',
312
+ 'fvt' => 'video/vnd.fvt',
313
+ 'fxp' => 'application/vnd.adobe.fxp',
314
+ 'fxpl' => 'application/vnd.adobe.fxp',
315
+ 'fzs' => 'application/vnd.fuzzysheet',
316
+ 'g2w' => 'application/vnd.geoplan',
317
+ 'g3' => 'image/g3fax',
318
+ 'g3w' => 'application/vnd.geospace',
319
+ 'gac' => 'application/vnd.groove-account',
320
+ 'gam' => 'application/x-tads',
321
+ 'gbr' => 'application/rpki-ghostbusters',
322
+ 'gca' => 'application/x-gca-compressed',
323
+ 'gdl' => 'model/vnd.gdl',
324
+ 'gdoc' => 'application/vnd.google-apps.document',
325
+ 'ged' => 'text/vnd.familysearch.gedcom',
326
+ 'geo' => 'application/vnd.dynageo',
327
+ 'geojson' => 'application/geo+json',
328
+ 'gex' => 'application/vnd.geometry-explorer',
329
+ 'ggb' => 'application/vnd.geogebra.file',
330
+ 'ggt' => 'application/vnd.geogebra.tool',
331
+ 'ghf' => 'application/vnd.groove-help',
332
+ 'gif' => 'image/gif',
333
+ 'gim' => 'application/vnd.groove-identity-message',
334
+ 'glb' => 'model/gltf-binary',
335
+ 'gltf' => 'model/gltf+json',
336
+ 'gml' => 'application/gml+xml',
337
+ 'gmx' => 'application/vnd.gmx',
338
+ 'gnumeric' => 'application/x-gnumeric',
339
+ 'gpg' => 'application/gpg-keys',
340
+ 'gph' => 'application/vnd.flographit',
341
+ 'gpx' => 'application/gpx+xml',
342
+ 'gqf' => 'application/vnd.grafeq',
343
+ 'gqs' => 'application/vnd.grafeq',
344
+ 'gram' => 'application/srgs',
345
+ 'gramps' => 'application/x-gramps-xml',
346
+ 'gre' => 'application/vnd.geometry-explorer',
347
+ 'grv' => 'application/vnd.groove-injector',
348
+ 'grxml' => 'application/srgs+xml',
349
+ 'gsf' => 'application/x-font-ghostscript',
350
+ 'gsheet' => 'application/vnd.google-apps.spreadsheet',
351
+ 'gslides' => 'application/vnd.google-apps.presentation',
352
+ 'gtar' => 'application/x-gtar',
353
+ 'gtm' => 'application/vnd.groove-tool-message',
354
+ 'gtw' => 'model/vnd.gtw',
355
+ 'gv' => 'text/vnd.graphviz',
356
+ 'gxf' => 'application/gxf',
357
+ 'gxt' => 'application/vnd.geonext',
358
+ 'gz' => 'application/gzip',
359
+ 'gzip' => 'application/gzip',
360
+ 'h' => 'text/x-c',
361
+ 'h261' => 'video/h261',
362
+ 'h263' => 'video/h263',
363
+ 'h264' => 'video/h264',
364
+ 'hal' => 'application/vnd.hal+xml',
365
+ 'hbci' => 'application/vnd.hbci',
366
+ 'hbs' => 'text/x-handlebars-template',
367
+ 'hdd' => 'application/x-virtualbox-hdd',
368
+ 'hdf' => 'application/x-hdf',
369
+ 'heic' => 'image/heic',
370
+ 'heics' => 'image/heic-sequence',
371
+ 'heif' => 'image/heif',
372
+ 'heifs' => 'image/heif-sequence',
373
+ 'hej2' => 'image/hej2k',
374
+ 'held' => 'application/atsc-held+xml',
375
+ 'hh' => 'text/x-c',
376
+ 'hjson' => 'application/hjson',
377
+ 'hlp' => 'application/winhlp',
378
+ 'hpgl' => 'application/vnd.hp-hpgl',
379
+ 'hpid' => 'application/vnd.hp-hpid',
380
+ 'hps' => 'application/vnd.hp-hps',
381
+ 'hqx' => 'application/mac-binhex40',
382
+ 'hsj2' => 'image/hsj2',
383
+ 'htc' => 'text/x-component',
384
+ 'htke' => 'application/vnd.kenameaapp',
385
+ 'htm' => 'text/html',
386
+ 'html' => 'text/html',
387
+ 'hvd' => 'application/vnd.yamaha.hv-dic',
388
+ 'hvp' => 'application/vnd.yamaha.hv-voice',
389
+ 'hvs' => 'application/vnd.yamaha.hv-script',
390
+ 'i2g' => 'application/vnd.intergeo',
391
+ 'icc' => 'application/vnd.iccprofile',
392
+ 'ice' => 'x-conference/x-cooltalk',
393
+ 'icm' => 'application/vnd.iccprofile',
394
+ 'ico' => 'image/x-icon',
395
+ 'ics' => 'text/calendar',
396
+ 'ief' => 'image/ief',
397
+ 'ifb' => 'text/calendar',
398
+ 'ifm' => 'application/vnd.shana.informed.formdata',
399
+ 'iges' => 'model/iges',
400
+ 'igl' => 'application/vnd.igloader',
401
+ 'igm' => 'application/vnd.insors.igm',
402
+ 'igs' => 'model/iges',
403
+ 'igx' => 'application/vnd.micrografx.igx',
404
+ 'iif' => 'application/vnd.shana.informed.interchange',
405
+ 'img' => 'application/octet-stream',
406
+ 'imp' => 'application/vnd.accpac.simply.imp',
407
+ 'ims' => 'application/vnd.ms-ims',
408
+ 'in' => 'text/plain',
409
+ 'ini' => 'text/plain',
410
+ 'ink' => 'application/inkml+xml',
411
+ 'inkml' => 'application/inkml+xml',
412
+ 'install' => 'application/x-install-instructions',
413
+ 'iota' => 'application/vnd.astraea-software.iota',
414
+ 'ipfix' => 'application/ipfix',
415
+ 'ipk' => 'application/vnd.shana.informed.package',
416
+ 'irm' => 'application/vnd.ibm.rights-management',
417
+ 'irp' => 'application/vnd.irepository.package+xml',
418
+ 'iso' => 'application/x-iso9660-image',
419
+ 'itp' => 'application/vnd.shana.informed.formtemplate',
420
+ 'its' => 'application/its+xml',
421
+ 'ivp' => 'application/vnd.immervision-ivp',
422
+ 'ivu' => 'application/vnd.immervision-ivu',
423
+ 'jad' => 'text/vnd.sun.j2me.app-descriptor',
424
+ 'jade' => 'text/jade',
425
+ 'jam' => 'application/vnd.jam',
426
+ 'jar' => 'application/java-archive',
427
+ 'jardiff' => 'application/x-java-archive-diff',
428
+ 'java' => 'text/x-java-source',
429
+ 'jhc' => 'image/jphc',
430
+ 'jisp' => 'application/vnd.jisp',
431
+ 'jls' => 'image/jls',
432
+ 'jlt' => 'application/vnd.hp-jlyt',
433
+ 'jng' => 'image/x-jng',
434
+ 'jnlp' => 'application/x-java-jnlp-file',
435
+ 'joda' => 'application/vnd.joost.joda-archive',
436
+ 'jp2' => 'image/jp2',
437
+ 'jpe' => 'image/jpeg',
438
+ 'jpeg' => 'image/jpeg',
439
+ 'jpf' => 'image/jpx',
440
+ 'jpg' => 'image/jpeg',
441
+ 'jpg2' => 'image/jp2',
442
+ 'jpgm' => 'video/jpm',
443
+ 'jpgv' => 'video/jpeg',
444
+ 'jph' => 'image/jph',
445
+ 'jpm' => 'video/jpm',
446
+ 'jpx' => 'image/jpx',
447
+ 'js' => 'application/javascript',
448
+ 'json' => 'application/json',
449
+ 'json5' => 'application/json5',
450
+ 'jsonld' => 'application/ld+json',
451
+ 'jsonml' => 'application/jsonml+json',
452
+ 'jsx' => 'text/jsx',
453
+ 'jxr' => 'image/jxr',
454
+ 'jxra' => 'image/jxra',
455
+ 'jxrs' => 'image/jxrs',
456
+ 'jxs' => 'image/jxs',
457
+ 'jxsc' => 'image/jxsc',
458
+ 'jxsi' => 'image/jxsi',
459
+ 'jxss' => 'image/jxss',
460
+ 'kar' => 'audio/midi',
461
+ 'karbon' => 'application/vnd.kde.karbon',
462
+ 'kdb' => 'application/octet-stream',
463
+ 'kdbx' => 'application/x-keepass2',
464
+ 'key' => 'application/x-iwork-keynote-sffkey',
465
+ 'kfo' => 'application/vnd.kde.kformula',
466
+ 'kia' => 'application/vnd.kidspiration',
467
+ 'kml' => 'application/vnd.google-earth.kml+xml',
468
+ 'kmz' => 'application/vnd.google-earth.kmz',
469
+ 'kne' => 'application/vnd.kinar',
470
+ 'knp' => 'application/vnd.kinar',
471
+ 'kon' => 'application/vnd.kde.kontour',
472
+ 'kpr' => 'application/vnd.kde.kpresenter',
473
+ 'kpt' => 'application/vnd.kde.kpresenter',
474
+ 'kpxx' => 'application/vnd.ds-keypoint',
475
+ 'ksp' => 'application/vnd.kde.kspread',
476
+ 'ktr' => 'application/vnd.kahootz',
477
+ 'ktx' => 'image/ktx',
478
+ 'ktx2' => 'image/ktx2',
479
+ 'ktz' => 'application/vnd.kahootz',
480
+ 'kwd' => 'application/vnd.kde.kword',
481
+ 'kwt' => 'application/vnd.kde.kword',
482
+ 'lasxml' => 'application/vnd.las.las+xml',
483
+ 'latex' => 'application/x-latex',
484
+ 'lbd' => 'application/vnd.llamagraphics.life-balance.desktop',
485
+ 'lbe' => 'application/vnd.llamagraphics.life-balance.exchange+xml',
486
+ 'les' => 'application/vnd.hhe.lesson-player',
487
+ 'less' => 'text/less',
488
+ 'lgr' => 'application/lgr+xml',
489
+ 'lha' => 'application/octet-stream',
490
+ 'link66' => 'application/vnd.route66.link66+xml',
491
+ 'list' => 'text/plain',
492
+ 'list3820' => 'application/vnd.ibm.modcap',
493
+ 'listafp' => 'application/vnd.ibm.modcap',
494
+ 'litcoffee' => 'text/coffeescript',
495
+ 'lnk' => 'application/x-ms-shortcut',
496
+ 'log' => 'text/plain',
497
+ 'lostxml' => 'application/lost+xml',
498
+ 'lrf' => 'application/octet-stream',
499
+ 'lrm' => 'application/vnd.ms-lrm',
500
+ 'ltf' => 'application/vnd.frogans.ltf',
501
+ 'lua' => 'text/x-lua',
502
+ 'luac' => 'application/x-lua-bytecode',
503
+ 'lvp' => 'audio/vnd.lucent.voice',
504
+ 'lwp' => 'application/vnd.lotus-wordpro',
505
+ 'lzh' => 'application/octet-stream',
506
+ 'm1v' => 'video/mpeg',
507
+ 'm2a' => 'audio/mpeg',
508
+ 'm2v' => 'video/mpeg',
509
+ 'm3a' => 'audio/mpeg',
510
+ 'm3u' => 'text/plain',
511
+ 'm3u8' => 'application/vnd.apple.mpegurl',
512
+ 'm4a' => 'audio/x-m4a',
513
+ 'm4p' => 'application/mp4',
514
+ 'm4s' => 'video/iso.segment',
515
+ 'm4u' => 'application/vnd.mpegurl',
516
+ 'm4v' => 'video/x-m4v',
517
+ 'm13' => 'application/x-msmediaview',
518
+ 'm14' => 'application/x-msmediaview',
519
+ 'm21' => 'application/mp21',
520
+ 'ma' => 'application/mathematica',
521
+ 'mads' => 'application/mads+xml',
522
+ 'maei' => 'application/mmt-aei+xml',
523
+ 'mag' => 'application/vnd.ecowin.chart',
524
+ 'maker' => 'application/vnd.framemaker',
525
+ 'man' => 'text/troff',
526
+ 'manifest' => 'text/cache-manifest',
527
+ 'map' => 'application/json',
528
+ 'mar' => 'application/octet-stream',
529
+ 'markdown' => 'text/markdown',
530
+ 'mathml' => 'application/mathml+xml',
531
+ 'mb' => 'application/mathematica',
532
+ 'mbk' => 'application/vnd.mobius.mbk',
533
+ 'mbox' => 'application/mbox',
534
+ 'mc1' => 'application/vnd.medcalcdata',
535
+ 'mcd' => 'application/vnd.mcd',
536
+ 'mcurl' => 'text/vnd.curl.mcurl',
537
+ 'md' => 'text/markdown',
538
+ 'mdb' => 'application/x-msaccess',
539
+ 'mdi' => 'image/vnd.ms-modi',
540
+ 'mdx' => 'text/mdx',
541
+ 'me' => 'text/troff',
542
+ 'mesh' => 'model/mesh',
543
+ 'meta4' => 'application/metalink4+xml',
544
+ 'metalink' => 'application/metalink+xml',
545
+ 'mets' => 'application/mets+xml',
546
+ 'mfm' => 'application/vnd.mfmp',
547
+ 'mft' => 'application/rpki-manifest',
548
+ 'mgp' => 'application/vnd.osgeo.mapguide.package',
549
+ 'mgz' => 'application/vnd.proteus.magazine',
550
+ 'mid' => 'audio/midi',
551
+ 'midi' => 'audio/midi',
552
+ 'mie' => 'application/x-mie',
553
+ 'mif' => 'application/vnd.mif',
554
+ 'mime' => 'message/rfc822',
555
+ 'mj2' => 'video/mj2',
556
+ 'mjp2' => 'video/mj2',
557
+ 'mjs' => 'application/javascript',
558
+ 'mk3d' => 'video/x-matroska',
559
+ 'mka' => 'audio/x-matroska',
560
+ 'mkd' => 'text/x-markdown',
561
+ 'mks' => 'video/x-matroska',
562
+ 'mkv' => 'video/x-matroska',
563
+ 'mlp' => 'application/vnd.dolby.mlp',
564
+ 'mmd' => 'application/vnd.chipnuts.karaoke-mmd',
565
+ 'mmf' => 'application/vnd.smaf',
566
+ 'mml' => 'text/mathml',
567
+ 'mmr' => 'image/vnd.fujixerox.edmics-mmr',
568
+ 'mng' => 'video/x-mng',
569
+ 'mny' => 'application/x-msmoney',
570
+ 'mobi' => 'application/x-mobipocket-ebook',
571
+ 'mods' => 'application/mods+xml',
572
+ 'mov' => 'video/quicktime',
573
+ 'movie' => 'video/x-sgi-movie',
574
+ 'mp2' => 'audio/mpeg',
575
+ 'mp2a' => 'audio/mpeg',
576
+ 'mp3' => 'audio/mpeg',
577
+ 'mp4' => 'video/mp4',
578
+ 'mp4a' => 'audio/mp4',
579
+ 'mp4s' => 'application/mp4',
580
+ 'mp4v' => 'video/mp4',
581
+ 'mp21' => 'application/mp21',
582
+ 'mpc' => 'application/vnd.mophun.certificate',
583
+ 'mpd' => 'application/dash+xml',
584
+ 'mpe' => 'video/mpeg',
585
+ 'mpeg' => 'video/mpeg',
586
+ 'mpg' => 'video/mpeg',
587
+ 'mpg4' => 'video/mp4',
588
+ 'mpga' => 'audio/mpeg',
589
+ 'mpkg' => 'application/vnd.apple.installer+xml',
590
+ 'mpm' => 'application/vnd.blueice.multipass',
591
+ 'mpn' => 'application/vnd.mophun.application',
592
+ 'mpp' => 'application/vnd.ms-project',
593
+ 'mpt' => 'application/vnd.ms-project',
594
+ 'mpy' => 'application/vnd.ibm.minipay',
595
+ 'mqy' => 'application/vnd.mobius.mqy',
596
+ 'mrc' => 'application/marc',
597
+ 'mrcx' => 'application/marcxml+xml',
598
+ 'ms' => 'text/troff',
599
+ 'mscml' => 'application/mediaservercontrol+xml',
600
+ 'mseed' => 'application/vnd.fdsn.mseed',
601
+ 'mseq' => 'application/vnd.mseq',
602
+ 'msf' => 'application/vnd.epson.msf',
603
+ 'msg' => 'application/vnd.ms-outlook',
604
+ 'msh' => 'model/mesh',
605
+ 'msi' => 'application/x-msdownload',
606
+ 'msl' => 'application/vnd.mobius.msl',
607
+ 'msm' => 'application/octet-stream',
608
+ 'msp' => 'application/octet-stream',
609
+ 'msty' => 'application/vnd.muvee.style',
610
+ 'mtl' => 'model/mtl',
611
+ 'mts' => 'model/vnd.mts',
612
+ 'mus' => 'application/vnd.musician',
613
+ 'musd' => 'application/mmt-usd+xml',
614
+ 'musicxml' => 'application/vnd.recordare.musicxml+xml',
615
+ 'mvb' => 'application/x-msmediaview',
616
+ 'mvt' => 'application/vnd.mapbox-vector-tile',
617
+ 'mwf' => 'application/vnd.mfer',
618
+ 'mxf' => 'application/mxf',
619
+ 'mxl' => 'application/vnd.recordare.musicxml',
620
+ 'mxmf' => 'audio/mobile-xmf',
621
+ 'mxml' => 'application/xv+xml',
622
+ 'mxs' => 'application/vnd.triscape.mxs',
623
+ 'mxu' => 'video/vnd.mpegurl',
624
+ 'n-gage' => 'application/vnd.nokia.n-gage.symbian.install',
625
+ 'n3' => 'text/n3',
626
+ 'nb' => 'application/mathematica',
627
+ 'nbp' => 'application/vnd.wolfram.player',
628
+ 'nc' => 'application/x-netcdf',
629
+ 'ncx' => 'application/x-dtbncx+xml',
630
+ 'nfo' => 'text/x-nfo',
631
+ 'ngdat' => 'application/vnd.nokia.n-gage.data',
632
+ 'nitf' => 'application/vnd.nitf',
633
+ 'nlu' => 'application/vnd.neurolanguage.nlu',
634
+ 'nml' => 'application/vnd.enliven',
635
+ 'nnd' => 'application/vnd.noblenet-directory',
636
+ 'nns' => 'application/vnd.noblenet-sealer',
637
+ 'nnw' => 'application/vnd.noblenet-web',
638
+ 'npx' => 'image/vnd.net-fpx',
639
+ 'nq' => 'application/n-quads',
640
+ 'nsc' => 'application/x-conference',
641
+ 'nsf' => 'application/vnd.lotus-notes',
642
+ 'nt' => 'application/n-triples',
643
+ 'ntf' => 'application/vnd.nitf',
644
+ 'numbers' => 'application/x-iwork-numbers-sffnumbers',
645
+ 'nzb' => 'application/x-nzb',
646
+ 'oa2' => 'application/vnd.fujitsu.oasys2',
647
+ 'oa3' => 'application/vnd.fujitsu.oasys3',
648
+ 'oas' => 'application/vnd.fujitsu.oasys',
649
+ 'obd' => 'application/x-msbinder',
650
+ 'obgx' => 'application/vnd.openblox.game+xml',
651
+ 'obj' => 'model/obj',
652
+ 'oda' => 'application/oda',
653
+ 'odb' => 'application/vnd.oasis.opendocument.database',
654
+ 'odc' => 'application/vnd.oasis.opendocument.chart',
655
+ 'odf' => 'application/vnd.oasis.opendocument.formula',
656
+ 'odft' => 'application/vnd.oasis.opendocument.formula-template',
657
+ 'odg' => 'application/vnd.oasis.opendocument.graphics',
658
+ 'odi' => 'application/vnd.oasis.opendocument.image',
659
+ 'odm' => 'application/vnd.oasis.opendocument.text-master',
660
+ 'odp' => 'application/vnd.oasis.opendocument.presentation',
661
+ 'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
662
+ 'odt' => 'application/vnd.oasis.opendocument.text',
663
+ 'oga' => 'audio/ogg',
664
+ 'ogex' => 'model/vnd.opengex',
665
+ 'ogg' => 'audio/ogg',
666
+ 'ogv' => 'video/ogg',
667
+ 'ogx' => 'application/ogg',
668
+ 'omdoc' => 'application/omdoc+xml',
669
+ 'onepkg' => 'application/onenote',
670
+ 'onetmp' => 'application/onenote',
671
+ 'onetoc' => 'application/onenote',
672
+ 'onetoc2' => 'application/onenote',
673
+ 'opf' => 'application/oebps-package+xml',
674
+ 'opml' => 'text/x-opml',
675
+ 'oprc' => 'application/vnd.palm',
676
+ 'opus' => 'audio/ogg',
677
+ 'org' => 'text/x-org',
678
+ 'osf' => 'application/vnd.yamaha.openscoreformat',
679
+ 'osfpvg' => 'application/vnd.yamaha.openscoreformat.osfpvg+xml',
680
+ 'osm' => 'application/vnd.openstreetmap.data+xml',
681
+ 'otc' => 'application/vnd.oasis.opendocument.chart-template',
682
+ 'otf' => 'font/otf',
683
+ 'otg' => 'application/vnd.oasis.opendocument.graphics-template',
684
+ 'oth' => 'application/vnd.oasis.opendocument.text-web',
685
+ 'oti' => 'application/vnd.oasis.opendocument.image-template',
686
+ 'otp' => 'application/vnd.oasis.opendocument.presentation-template',
687
+ 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template',
688
+ 'ott' => 'application/vnd.oasis.opendocument.text-template',
689
+ 'ova' => 'application/x-virtualbox-ova',
690
+ 'ovf' => 'application/x-virtualbox-ovf',
691
+ 'owl' => 'application/rdf+xml',
692
+ 'oxps' => 'application/oxps',
693
+ 'oxt' => 'application/vnd.openofficeorg.extension',
694
+ 'p' => 'text/x-pascal',
695
+ 'p7a' => 'application/x-pkcs7-signature',
696
+ 'p7b' => 'application/x-pkcs7-certificates',
697
+ 'p7c' => 'application/pkcs7-mime',
698
+ 'p7m' => 'application/pkcs7-mime',
699
+ 'p7r' => 'application/x-pkcs7-certreqresp',
700
+ 'p7s' => 'application/pkcs7-signature',
701
+ 'p8' => 'application/pkcs8',
702
+ 'p10' => 'application/x-pkcs10',
703
+ 'p12' => 'application/x-pkcs12',
704
+ 'pac' => 'application/x-ns-proxy-autoconfig',
705
+ 'pages' => 'application/x-iwork-pages-sffpages',
706
+ 'pas' => 'text/x-pascal',
707
+ 'paw' => 'application/vnd.pawaafile',
708
+ 'pbd' => 'application/vnd.powerbuilder6',
709
+ 'pbm' => 'image/x-portable-bitmap',
710
+ 'pcap' => 'application/vnd.tcpdump.pcap',
711
+ 'pcf' => 'application/x-font-pcf',
712
+ 'pcl' => 'application/vnd.hp-pcl',
713
+ 'pclxl' => 'application/vnd.hp-pclxl',
714
+ 'pct' => 'image/x-pict',
715
+ 'pcurl' => 'application/vnd.curl.pcurl',
716
+ 'pcx' => 'image/x-pcx',
717
+ 'pdb' => 'application/x-pilot',
718
+ 'pde' => 'text/x-processing',
719
+ 'pdf' => 'application/pdf',
720
+ 'pem' => 'application/x-x509-user-cert',
721
+ 'pfa' => 'application/x-font-type1',
722
+ 'pfb' => 'application/x-font-type1',
723
+ 'pfm' => 'application/x-font-type1',
724
+ 'pfr' => 'application/font-tdpfr',
725
+ 'pfx' => 'application/x-pkcs12',
726
+ 'pgm' => 'image/x-portable-graymap',
727
+ 'pgn' => 'application/x-chess-pgn',
728
+ 'pgp' => 'application/pgp',
729
+ 'php' => 'application/x-httpd-php',
730
+ 'php3' => 'application/x-httpd-php',
731
+ 'php4' => 'application/x-httpd-php',
732
+ 'phps' => 'application/x-httpd-php-source',
733
+ 'phtml' => 'application/x-httpd-php',
734
+ 'pic' => 'image/x-pict',
735
+ 'pkg' => 'application/octet-stream',
736
+ 'pki' => 'application/pkixcmp',
737
+ 'pkipath' => 'application/pkix-pkipath',
738
+ 'pkpass' => 'application/vnd.apple.pkpass',
739
+ 'pl' => 'application/x-perl',
740
+ 'plb' => 'application/vnd.3gpp.pic-bw-large',
741
+ 'plc' => 'application/vnd.mobius.plc',
742
+ 'plf' => 'application/vnd.pocketlearn',
743
+ 'pls' => 'application/pls+xml',
744
+ 'pm' => 'application/x-perl',
745
+ 'pml' => 'application/vnd.ctc-posml',
746
+ 'png' => 'image/png',
747
+ 'pnm' => 'image/x-portable-anymap',
748
+ 'portpkg' => 'application/vnd.macports.portpkg',
749
+ 'pot' => 'application/vnd.ms-powerpoint',
750
+ 'potm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
751
+ 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
752
+ 'ppa' => 'application/vnd.ms-powerpoint',
753
+ 'ppam' => 'application/vnd.ms-powerpoint.addin.macroEnabled.12',
754
+ 'ppd' => 'application/vnd.cups-ppd',
755
+ 'ppm' => 'image/x-portable-pixmap',
756
+ 'pps' => 'application/vnd.ms-powerpoint',
757
+ 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12',
758
+ 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
759
+ 'ppt' => 'application/powerpoint',
760
+ 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
761
+ 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
762
+ 'pqa' => 'application/vnd.palm',
763
+ 'prc' => 'application/x-pilot',
764
+ 'pre' => 'application/vnd.lotus-freelance',
765
+ 'prf' => 'application/pics-rules',
766
+ 'provx' => 'application/provenance+xml',
767
+ 'ps' => 'application/postscript',
768
+ 'psb' => 'application/vnd.3gpp.pic-bw-small',
769
+ 'psd' => 'application/x-photoshop',
770
+ 'psf' => 'application/x-font-linux-psf',
771
+ 'pskcxml' => 'application/pskc+xml',
772
+ 'pti' => 'image/prs.pti',
773
+ 'ptid' => 'application/vnd.pvi.ptid1',
774
+ 'pub' => 'application/x-mspublisher',
775
+ 'pvb' => 'application/vnd.3gpp.pic-bw-var',
776
+ 'pwn' => 'application/vnd.3m.post-it-notes',
777
+ 'pya' => 'audio/vnd.ms-playready.media.pya',
778
+ 'pyv' => 'video/vnd.ms-playready.media.pyv',
779
+ 'qam' => 'application/vnd.epson.quickanime',
780
+ 'qbo' => 'application/vnd.intu.qbo',
781
+ 'qfx' => 'application/vnd.intu.qfx',
782
+ 'qps' => 'application/vnd.publishare-delta-tree',
783
+ 'qt' => 'video/quicktime',
784
+ 'qwd' => 'application/vnd.quark.quarkxpress',
785
+ 'qwt' => 'application/vnd.quark.quarkxpress',
786
+ 'qxb' => 'application/vnd.quark.quarkxpress',
787
+ 'qxd' => 'application/vnd.quark.quarkxpress',
788
+ 'qxl' => 'application/vnd.quark.quarkxpress',
789
+ 'qxt' => 'application/vnd.quark.quarkxpress',
790
+ 'ra' => 'audio/x-realaudio',
791
+ 'ram' => 'audio/x-pn-realaudio',
792
+ 'raml' => 'application/raml+yaml',
793
+ 'rapd' => 'application/route-apd+xml',
794
+ 'rar' => 'application/x-rar',
795
+ 'ras' => 'image/x-cmu-raster',
796
+ 'rcprofile' => 'application/vnd.ipunplugged.rcprofile',
797
+ 'rdf' => 'application/rdf+xml',
798
+ 'rdz' => 'application/vnd.data-vision.rdz',
799
+ 'relo' => 'application/p2p-overlay+xml',
800
+ 'rep' => 'application/vnd.businessobjects',
801
+ 'res' => 'application/x-dtbresource+xml',
802
+ 'rgb' => 'image/x-rgb',
803
+ 'rif' => 'application/reginfo+xml',
804
+ 'rip' => 'audio/vnd.rip',
805
+ 'ris' => 'application/x-research-info-systems',
806
+ 'rl' => 'application/resource-lists+xml',
807
+ 'rlc' => 'image/vnd.fujixerox.edmics-rlc',
808
+ 'rld' => 'application/resource-lists-diff+xml',
809
+ 'rm' => 'audio/x-pn-realaudio',
810
+ 'rmi' => 'audio/midi',
811
+ 'rmp' => 'audio/x-pn-realaudio-plugin',
812
+ 'rms' => 'application/vnd.jcp.javame.midlet-rms',
813
+ 'rmvb' => 'application/vnd.rn-realmedia-vbr',
814
+ 'rnc' => 'application/relax-ng-compact-syntax',
815
+ 'rng' => 'application/xml',
816
+ 'roa' => 'application/rpki-roa',
817
+ 'roff' => 'text/troff',
818
+ 'rp9' => 'application/vnd.cloanto.rp9',
819
+ 'rpm' => 'audio/x-pn-realaudio-plugin',
820
+ 'rpss' => 'application/vnd.nokia.radio-presets',
821
+ 'rpst' => 'application/vnd.nokia.radio-preset',
822
+ 'rq' => 'application/sparql-query',
823
+ 'rs' => 'application/rls-services+xml',
824
+ 'rsa' => 'application/x-pkcs7',
825
+ 'rsat' => 'application/atsc-rsat+xml',
826
+ 'rsd' => 'application/rsd+xml',
827
+ 'rsheet' => 'application/urc-ressheet+xml',
828
+ 'rss' => 'application/rss+xml',
829
+ 'rtf' => 'text/rtf',
830
+ 'rtx' => 'text/richtext',
831
+ 'run' => 'application/x-makeself',
832
+ 'rusd' => 'application/route-usd+xml',
833
+ 'rv' => 'video/vnd.rn-realvideo',
834
+ 's' => 'text/x-asm',
835
+ 's3m' => 'audio/s3m',
836
+ 'saf' => 'application/vnd.yamaha.smaf-audio',
837
+ 'sass' => 'text/x-sass',
838
+ 'sbml' => 'application/sbml+xml',
839
+ 'sc' => 'application/vnd.ibm.secure-container',
840
+ 'scd' => 'application/x-msschedule',
841
+ 'scm' => 'application/vnd.lotus-screencam',
842
+ 'scq' => 'application/scvp-cv-request',
843
+ 'scs' => 'application/scvp-cv-response',
844
+ 'scss' => 'text/x-scss',
845
+ 'scurl' => 'text/vnd.curl.scurl',
846
+ 'sda' => 'application/vnd.stardivision.draw',
847
+ 'sdc' => 'application/vnd.stardivision.calc',
848
+ 'sdd' => 'application/vnd.stardivision.impress',
849
+ 'sdkd' => 'application/vnd.solent.sdkm+xml',
850
+ 'sdkm' => 'application/vnd.solent.sdkm+xml',
851
+ 'sdp' => 'application/sdp',
852
+ 'sdw' => 'application/vnd.stardivision.writer',
853
+ 'sea' => 'application/octet-stream',
854
+ 'see' => 'application/vnd.seemail',
855
+ 'seed' => 'application/vnd.fdsn.seed',
856
+ 'sema' => 'application/vnd.sema',
857
+ 'semd' => 'application/vnd.semd',
858
+ 'semf' => 'application/vnd.semf',
859
+ 'senmlx' => 'application/senml+xml',
860
+ 'sensmlx' => 'application/sensml+xml',
861
+ 'ser' => 'application/java-serialized-object',
862
+ 'setpay' => 'application/set-payment-initiation',
863
+ 'setreg' => 'application/set-registration-initiation',
864
+ 'sfd-hdstx' => 'application/vnd.hydrostatix.sof-data',
865
+ 'sfs' => 'application/vnd.spotfire.sfs',
866
+ 'sfv' => 'text/x-sfv',
867
+ 'sgi' => 'image/sgi',
868
+ 'sgl' => 'application/vnd.stardivision.writer-global',
869
+ 'sgm' => 'text/sgml',
870
+ 'sgml' => 'text/sgml',
871
+ 'sh' => 'application/x-sh',
872
+ 'shar' => 'application/x-shar',
873
+ 'shex' => 'text/shex',
874
+ 'shf' => 'application/shf+xml',
875
+ 'shtml' => 'text/html',
876
+ 'sid' => 'image/x-mrsid-image',
877
+ 'sieve' => 'application/sieve',
878
+ 'sig' => 'application/pgp-signature',
879
+ 'sil' => 'audio/silk',
880
+ 'silo' => 'model/mesh',
881
+ 'sis' => 'application/vnd.symbian.install',
882
+ 'sisx' => 'application/vnd.symbian.install',
883
+ 'sit' => 'application/x-stuffit',
884
+ 'sitx' => 'application/x-stuffitx',
885
+ 'siv' => 'application/sieve',
886
+ 'skd' => 'application/vnd.koan',
887
+ 'skm' => 'application/vnd.koan',
888
+ 'skp' => 'application/vnd.koan',
889
+ 'skt' => 'application/vnd.koan',
890
+ 'sldm' => 'application/vnd.ms-powerpoint.slide.macroenabled.12',
891
+ 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
892
+ 'slim' => 'text/slim',
893
+ 'slm' => 'text/slim',
894
+ 'sls' => 'application/route-s-tsid+xml',
895
+ 'slt' => 'application/vnd.epson.salt',
896
+ 'sm' => 'application/vnd.stepmania.stepchart',
897
+ 'smf' => 'application/vnd.stardivision.math',
898
+ 'smi' => 'application/smil',
899
+ 'smil' => 'application/smil',
900
+ 'smv' => 'video/x-smv',
901
+ 'smzip' => 'application/vnd.stepmania.package',
902
+ 'snd' => 'audio/basic',
903
+ 'snf' => 'application/x-font-snf',
904
+ 'so' => 'application/octet-stream',
905
+ 'spc' => 'application/x-pkcs7-certificates',
906
+ 'spdx' => 'text/spdx',
907
+ 'spf' => 'application/vnd.yamaha.smaf-phrase',
908
+ 'spl' => 'application/x-futuresplash',
909
+ 'spot' => 'text/vnd.in3d.spot',
910
+ 'spp' => 'application/scvp-vp-response',
911
+ 'spq' => 'application/scvp-vp-request',
912
+ 'spx' => 'audio/ogg',
913
+ 'sql' => 'application/x-sql',
914
+ 'src' => 'application/x-wais-source',
915
+ 'srt' => 'application/x-subrip',
916
+ 'sru' => 'application/sru+xml',
917
+ 'srx' => 'application/sparql-results+xml',
918
+ 'ssdl' => 'application/ssdl+xml',
919
+ 'sse' => 'application/vnd.kodak-descriptor',
920
+ 'ssf' => 'application/vnd.epson.ssf',
921
+ 'ssml' => 'application/ssml+xml',
922
+ 'sst' => 'application/octet-stream',
923
+ 'st' => 'application/vnd.sailingtracker.track',
924
+ 'stc' => 'application/vnd.sun.xml.calc.template',
925
+ 'std' => 'application/vnd.sun.xml.draw.template',
926
+ 'stf' => 'application/vnd.wt.stf',
927
+ 'sti' => 'application/vnd.sun.xml.impress.template',
928
+ 'stk' => 'application/hyperstudio',
929
+ 'stl' => 'model/stl',
930
+ 'stpx' => 'model/step+xml',
931
+ 'stpxz' => 'model/step-xml+zip',
932
+ 'stpz' => 'model/step+zip',
933
+ 'str' => 'application/vnd.pg.format',
934
+ 'stw' => 'application/vnd.sun.xml.writer.template',
935
+ 'styl' => 'text/stylus',
936
+ 'stylus' => 'text/stylus',
937
+ 'sub' => 'text/vnd.dvb.subtitle',
938
+ 'sus' => 'application/vnd.sus-calendar',
939
+ 'susp' => 'application/vnd.sus-calendar',
940
+ 'sv4cpio' => 'application/x-sv4cpio',
941
+ 'sv4crc' => 'application/x-sv4crc',
942
+ 'svc' => 'application/vnd.dvb.service',
943
+ 'svd' => 'application/vnd.svd',
944
+ 'svg' => 'image/svg+xml',
945
+ 'svgz' => 'image/svg+xml',
946
+ 'swa' => 'application/x-director',
947
+ 'swf' => 'application/x-shockwave-flash',
948
+ 'swi' => 'application/vnd.aristanetworks.swi',
949
+ 'swidtag' => 'application/swid+xml',
950
+ 'sxc' => 'application/vnd.sun.xml.calc',
951
+ 'sxd' => 'application/vnd.sun.xml.draw',
952
+ 'sxg' => 'application/vnd.sun.xml.writer.global',
953
+ 'sxi' => 'application/vnd.sun.xml.impress',
954
+ 'sxm' => 'application/vnd.sun.xml.math',
955
+ 'sxw' => 'application/vnd.sun.xml.writer',
956
+ 't' => 'text/troff',
957
+ 't3' => 'application/x-t3vm-image',
958
+ 't38' => 'image/t38',
959
+ 'taglet' => 'application/vnd.mynfc',
960
+ 'tao' => 'application/vnd.tao.intent-module-archive',
961
+ 'tap' => 'image/vnd.tencent.tap',
962
+ 'tar' => 'application/x-tar',
963
+ 'tcap' => 'application/vnd.3gpp2.tcap',
964
+ 'tcl' => 'application/x-tcl',
965
+ 'td' => 'application/urc-targetdesc+xml',
966
+ 'teacher' => 'application/vnd.smart.teacher',
967
+ 'tei' => 'application/tei+xml',
968
+ 'teicorpus' => 'application/tei+xml',
969
+ 'tex' => 'application/x-tex',
970
+ 'texi' => 'application/x-texinfo',
971
+ 'texinfo' => 'application/x-texinfo',
972
+ 'text' => 'text/plain',
973
+ 'tfi' => 'application/thraud+xml',
974
+ 'tfm' => 'application/x-tex-tfm',
975
+ 'tfx' => 'image/tiff-fx',
976
+ 'tga' => 'image/x-tga',
977
+ 'tgz' => 'application/x-tar',
978
+ 'thmx' => 'application/vnd.ms-officetheme',
979
+ 'tif' => 'image/tiff',
980
+ 'tiff' => 'image/tiff',
981
+ 'tk' => 'application/x-tcl',
982
+ 'tmo' => 'application/vnd.tmobile-livetv',
983
+ 'toml' => 'application/toml',
984
+ 'torrent' => 'application/x-bittorrent',
985
+ 'tpl' => 'application/vnd.groove-tool-template',
986
+ 'tpt' => 'application/vnd.trid.tpt',
987
+ 'tr' => 'text/troff',
988
+ 'tra' => 'application/vnd.trueapp',
989
+ 'trig' => 'application/trig',
990
+ 'trm' => 'application/x-msterminal',
991
+ 'ts' => 'video/mp2t',
992
+ 'tsd' => 'application/timestamped-data',
993
+ 'tsv' => 'text/tab-separated-values',
994
+ 'ttc' => 'font/collection',
995
+ 'ttf' => 'font/ttf',
996
+ 'ttl' => 'text/turtle',
997
+ 'ttml' => 'application/ttml+xml',
998
+ 'twd' => 'application/vnd.simtech-mindmapper',
999
+ 'twds' => 'application/vnd.simtech-mindmapper',
1000
+ 'txd' => 'application/vnd.genomatix.tuxedo',
1001
+ 'txf' => 'application/vnd.mobius.txf',
1002
+ 'txt' => 'text/plain',
1003
+ 'u8dsn' => 'message/global-delivery-status',
1004
+ 'u8hdr' => 'message/global-headers',
1005
+ 'u8mdn' => 'message/global-disposition-notification',
1006
+ 'u8msg' => 'message/global',
1007
+ 'u32' => 'application/x-authorware-bin',
1008
+ 'ubj' => 'application/ubjson',
1009
+ 'udeb' => 'application/x-debian-package',
1010
+ 'ufd' => 'application/vnd.ufdl',
1011
+ 'ufdl' => 'application/vnd.ufdl',
1012
+ 'ulx' => 'application/x-glulx',
1013
+ 'umj' => 'application/vnd.umajin',
1014
+ 'unityweb' => 'application/vnd.unity',
1015
+ 'uoml' => 'application/vnd.uoml+xml',
1016
+ 'uri' => 'text/uri-list',
1017
+ 'uris' => 'text/uri-list',
1018
+ 'urls' => 'text/uri-list',
1019
+ 'usdz' => 'model/vnd.usdz+zip',
1020
+ 'ustar' => 'application/x-ustar',
1021
+ 'utz' => 'application/vnd.uiq.theme',
1022
+ 'uu' => 'text/x-uuencode',
1023
+ 'uva' => 'audio/vnd.dece.audio',
1024
+ 'uvd' => 'application/vnd.dece.data',
1025
+ 'uvf' => 'application/vnd.dece.data',
1026
+ 'uvg' => 'image/vnd.dece.graphic',
1027
+ 'uvh' => 'video/vnd.dece.hd',
1028
+ 'uvi' => 'image/vnd.dece.graphic',
1029
+ 'uvm' => 'video/vnd.dece.mobile',
1030
+ 'uvp' => 'video/vnd.dece.pd',
1031
+ 'uvs' => 'video/vnd.dece.sd',
1032
+ 'uvt' => 'application/vnd.dece.ttml+xml',
1033
+ 'uvu' => 'video/vnd.uvvu.mp4',
1034
+ 'uvv' => 'video/vnd.dece.video',
1035
+ 'uvva' => 'audio/vnd.dece.audio',
1036
+ 'uvvd' => 'application/vnd.dece.data',
1037
+ 'uvvf' => 'application/vnd.dece.data',
1038
+ 'uvvg' => 'image/vnd.dece.graphic',
1039
+ 'uvvh' => 'video/vnd.dece.hd',
1040
+ 'uvvi' => 'image/vnd.dece.graphic',
1041
+ 'uvvm' => 'video/vnd.dece.mobile',
1042
+ 'uvvp' => 'video/vnd.dece.pd',
1043
+ 'uvvs' => 'video/vnd.dece.sd',
1044
+ 'uvvt' => 'application/vnd.dece.ttml+xml',
1045
+ 'uvvu' => 'video/vnd.uvvu.mp4',
1046
+ 'uvvv' => 'video/vnd.dece.video',
1047
+ 'uvvx' => 'application/vnd.dece.unspecified',
1048
+ 'uvvz' => 'application/vnd.dece.zip',
1049
+ 'uvx' => 'application/vnd.dece.unspecified',
1050
+ 'uvz' => 'application/vnd.dece.zip',
1051
+ 'vbox' => 'application/x-virtualbox-vbox',
1052
+ 'vbox-extpack' => 'application/x-virtualbox-vbox-extpack',
1053
+ 'vcard' => 'text/vcard',
1054
+ 'vcd' => 'application/x-cdlink',
1055
+ 'vcf' => 'text/x-vcard',
1056
+ 'vcg' => 'application/vnd.groove-vcard',
1057
+ 'vcs' => 'text/x-vcalendar',
1058
+ 'vcx' => 'application/vnd.vcx',
1059
+ 'vdi' => 'application/x-virtualbox-vdi',
1060
+ 'vds' => 'model/vnd.sap.vds',
1061
+ 'vhd' => 'application/x-virtualbox-vhd',
1062
+ 'vis' => 'application/vnd.visionary',
1063
+ 'viv' => 'video/vnd.vivo',
1064
+ 'vlc' => 'application/videolan',
1065
+ 'vmdk' => 'application/x-virtualbox-vmdk',
1066
+ 'vob' => 'video/x-ms-vob',
1067
+ 'vor' => 'application/vnd.stardivision.writer',
1068
+ 'vox' => 'application/x-authorware-bin',
1069
+ 'vrml' => 'model/vrml',
1070
+ 'vsd' => 'application/vnd.visio',
1071
+ 'vsf' => 'application/vnd.vsf',
1072
+ 'vss' => 'application/vnd.visio',
1073
+ 'vst' => 'application/vnd.visio',
1074
+ 'vsw' => 'application/vnd.visio',
1075
+ 'vtf' => 'image/vnd.valve.source.texture',
1076
+ 'vtt' => 'text/vtt',
1077
+ 'vtu' => 'model/vnd.vtu',
1078
+ 'vxml' => 'application/voicexml+xml',
1079
+ 'w3d' => 'application/x-director',
1080
+ 'wad' => 'application/x-doom',
1081
+ 'wadl' => 'application/vnd.sun.wadl+xml',
1082
+ 'war' => 'application/java-archive',
1083
+ 'wasm' => 'application/wasm',
1084
+ 'wav' => 'audio/x-wav',
1085
+ 'wax' => 'audio/x-ms-wax',
1086
+ 'wbmp' => 'image/vnd.wap.wbmp',
1087
+ 'wbs' => 'application/vnd.criticaltools.wbs+xml',
1088
+ 'wbxml' => 'application/wbxml',
1089
+ 'wcm' => 'application/vnd.ms-works',
1090
+ 'wdb' => 'application/vnd.ms-works',
1091
+ 'wdp' => 'image/vnd.ms-photo',
1092
+ 'weba' => 'audio/webm',
1093
+ 'webapp' => 'application/x-web-app-manifest+json',
1094
+ 'webm' => 'video/webm',
1095
+ 'webmanifest' => 'application/manifest+json',
1096
+ 'webp' => 'image/webp',
1097
+ 'wg' => 'application/vnd.pmi.widget',
1098
+ 'wgt' => 'application/widget',
1099
+ 'wks' => 'application/vnd.ms-works',
1100
+ 'wm' => 'video/x-ms-wm',
1101
+ 'wma' => 'audio/x-ms-wma',
1102
+ 'wmd' => 'application/x-ms-wmd',
1103
+ 'wmf' => 'image/wmf',
1104
+ 'wml' => 'text/vnd.wap.wml',
1105
+ 'wmlc' => 'application/wmlc',
1106
+ 'wmls' => 'text/vnd.wap.wmlscript',
1107
+ 'wmlsc' => 'application/vnd.wap.wmlscriptc',
1108
+ 'wmv' => 'video/x-ms-wmv',
1109
+ 'wmx' => 'video/x-ms-wmx',
1110
+ 'wmz' => 'application/x-msmetafile',
1111
+ 'woff' => 'font/woff',
1112
+ 'woff2' => 'font/woff2',
1113
+ 'word' => 'application/msword',
1114
+ 'wpd' => 'application/vnd.wordperfect',
1115
+ 'wpl' => 'application/vnd.ms-wpl',
1116
+ 'wps' => 'application/vnd.ms-works',
1117
+ 'wqd' => 'application/vnd.wqd',
1118
+ 'wri' => 'application/x-mswrite',
1119
+ 'wrl' => 'model/vrml',
1120
+ 'wsc' => 'message/vnd.wfa.wsc',
1121
+ 'wsdl' => 'application/wsdl+xml',
1122
+ 'wspolicy' => 'application/wspolicy+xml',
1123
+ 'wtb' => 'application/vnd.webturbo',
1124
+ 'wvx' => 'video/x-ms-wvx',
1125
+ 'x3d' => 'model/x3d+xml',
1126
+ 'x3db' => 'model/x3d+fastinfoset',
1127
+ 'x3dbz' => 'model/x3d+binary',
1128
+ 'x3dv' => 'model/x3d-vrml',
1129
+ 'x3dvz' => 'model/x3d+vrml',
1130
+ 'x3dz' => 'model/x3d+xml',
1131
+ 'x32' => 'application/x-authorware-bin',
1132
+ 'x_b' => 'model/vnd.parasolid.transmit.binary',
1133
+ 'x_t' => 'model/vnd.parasolid.transmit.text',
1134
+ 'xaml' => 'application/xaml+xml',
1135
+ 'xap' => 'application/x-silverlight-app',
1136
+ 'xar' => 'application/vnd.xara',
1137
+ 'xav' => 'application/xcap-att+xml',
1138
+ 'xbap' => 'application/x-ms-xbap',
1139
+ 'xbd' => 'application/vnd.fujixerox.docuworks.binder',
1140
+ 'xbm' => 'image/x-xbitmap',
1141
+ 'xca' => 'application/xcap-caps+xml',
1142
+ 'xcs' => 'application/calendar+xml',
1143
+ 'xdf' => 'application/xcap-diff+xml',
1144
+ 'xdm' => 'application/vnd.syncml.dm+xml',
1145
+ 'xdp' => 'application/vnd.adobe.xdp+xml',
1146
+ 'xdssc' => 'application/dssc+xml',
1147
+ 'xdw' => 'application/vnd.fujixerox.docuworks',
1148
+ 'xel' => 'application/xcap-el+xml',
1149
+ 'xenc' => 'application/xenc+xml',
1150
+ 'xer' => 'application/patch-ops-error+xml',
1151
+ 'xfdf' => 'application/vnd.adobe.xfdf',
1152
+ 'xfdl' => 'application/vnd.xfdl',
1153
+ 'xht' => 'application/xhtml+xml',
1154
+ 'xhtml' => 'application/xhtml+xml',
1155
+ 'xhvml' => 'application/xv+xml',
1156
+ 'xif' => 'image/vnd.xiff',
1157
+ 'xl' => 'application/excel',
1158
+ 'xla' => 'application/vnd.ms-excel',
1159
+ 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
1160
+ 'xlc' => 'application/vnd.ms-excel',
1161
+ 'xlf' => 'application/xliff+xml',
1162
+ 'xlm' => 'application/vnd.ms-excel',
1163
+ 'xls' => 'application/vnd.ms-excel',
1164
+ 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
1165
+ 'xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12',
1166
+ 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
1167
+ 'xlt' => 'application/vnd.ms-excel',
1168
+ 'xltm' => 'application/vnd.ms-excel.template.macroEnabled.12',
1169
+ 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
1170
+ 'xlw' => 'application/vnd.ms-excel',
1171
+ 'xm' => 'audio/xm',
1172
+ 'xml' => 'application/xml',
1173
+ 'xns' => 'application/xcap-ns+xml',
1174
+ 'xo' => 'application/vnd.olpc-sugar',
1175
+ 'xop' => 'application/xop+xml',
1176
+ 'xpi' => 'application/x-xpinstall',
1177
+ 'xpl' => 'application/xproc+xml',
1178
+ 'xpm' => 'image/x-xpixmap',
1179
+ 'xpr' => 'application/vnd.is-xpr',
1180
+ 'xps' => 'application/vnd.ms-xpsdocument',
1181
+ 'xpw' => 'application/vnd.intercon.formnet',
1182
+ 'xpx' => 'application/vnd.intercon.formnet',
1183
+ 'xsd' => 'application/xml',
1184
+ 'xsl' => 'application/xml',
1185
+ 'xslt' => 'application/xslt+xml',
1186
+ 'xsm' => 'application/vnd.syncml+xml',
1187
+ 'xspf' => 'application/xspf+xml',
1188
+ 'xul' => 'application/vnd.mozilla.xul+xml',
1189
+ 'xvm' => 'application/xv+xml',
1190
+ 'xvml' => 'application/xv+xml',
1191
+ 'xwd' => 'image/x-xwindowdump',
1192
+ 'xyz' => 'chemical/x-xyz',
1193
+ 'xz' => 'application/x-xz',
1194
+ 'yaml' => 'text/yaml',
1195
+ 'yang' => 'application/yang',
1196
+ 'yin' => 'application/yin+xml',
1197
+ 'yml' => 'text/yaml',
1198
+ 'ymp' => 'text/x-suse-ymp',
1199
+ 'z' => 'application/x-compress',
1200
+ 'z1' => 'application/x-zmachine',
1201
+ 'z2' => 'application/x-zmachine',
1202
+ 'z3' => 'application/x-zmachine',
1203
+ 'z4' => 'application/x-zmachine',
1204
+ 'z5' => 'application/x-zmachine',
1205
+ 'z6' => 'application/x-zmachine',
1206
+ 'z7' => 'application/x-zmachine',
1207
+ 'z8' => 'application/x-zmachine',
1208
+ 'zaz' => 'application/vnd.zzazz.deck+xml',
1209
+ 'zip' => 'application/zip',
1210
+ 'zir' => 'application/vnd.zul',
1211
+ 'zirz' => 'application/vnd.zul',
1212
+ 'zmm' => 'application/vnd.handheld-entertainment+xml',
1213
+ 'zsh' => 'text/x-scriptzsh',
1214
+ ];
1215
+
1216
+ public function lookupMimeType(string $extension): ?string
1217
+ {
1218
+ return self::MIME_TYPES_FOR_EXTENSIONS[$extension] ?? null;
1219
+ }
1220
+ }
vendor/browscap-php/league/mime-type-detection/src/MimeTypeDetector.php ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace League\MimeTypeDetection;
6
+
7
+ interface MimeTypeDetector
8
+ {
9
+ /**
10
+ * @param string|resource $contents
11
+ */
12
+ public function detectMimeType(string $path, $contents): ?string;
13
+
14
+ public function detectMimeTypeFromBuffer(string $contents): ?string;
15
+
16
+ public function detectMimeTypeFromPath(string $path): ?string;
17
+
18
+ public function detectMimeTypeFromFile(string $path): ?string;
19
+ }
vendor/browscap-php/league/mime-type-detection/src/OverridingExtensionToMimeTypeMap.php ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace League\MimeTypeDetection;
4
+
5
+ class OverridingExtensionToMimeTypeMap implements ExtensionToMimeTypeMap
6
+ {
7
+ /**
8
+ * @var ExtensionToMimeTypeMap
9
+ */
10
+ private $innerMap;
11
+
12
+ /**
13
+ * @var string[]
14
+ */
15
+ private $overrides;
16
+
17
+ /**
18
+ * @param array<string, string> $overrides
19
+ */
20
+ public function __construct(ExtensionToMimeTypeMap $innerMap, array $overrides)
21
+ {
22
+ $this->innerMap = $innerMap;
23
+ $this->overrides = $overrides;
24
+ }
25
+
26
+ public function lookupMimeType(string $extension): ?string
27
+ {
28
+ return $this->overrides[$extension] ?? $this->innerMap->lookupMimeType($extension);
29
+ }
30
+ }
vendor/browscap-php/matthiasmullie/scrapbook/src/Adapters/Apc.php ADDED
@@ -0,0 +1,735 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace MatthiasMullie\Scrapbook\Adapters;
4
+
5
+ use APCIterator;
6
+ use APCuIterator;
7
+ use MatthiasMullie\Scrapbook\Adapters\Collections\Apc as Collection;
8
+ use MatthiasMullie\Scrapbook\Exception\Exception;
9
+ use MatthiasMullie\Scrapbook\KeyValueStore;
10
+
11
+ /**
12
+ * APC adapter. Basically just a wrapper over apc_* functions, but in an
13
+ * exchangeable (KeyValueStore) interface.
14
+ *
15
+ * @author Matthias Mullie <scrapbook@mullie.eu>
16
+ * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
17
+ * @license LICENSE MIT
18
+ */
19
+ class Apc implements KeyValueStore
20
+ {
21
+ /**
22
+ * APC does this crazy thing of only deleting expired data on every new
23
+ * (page) request, not checking it when you actually retrieve the value
24
+ * (which you may just have set in the same request)
25
+ * Since it's totally possible to store values that expire in the same
26
+ * request, we'll keep track of those expiration times here & filter them
27
+ * out in case they did expire.
28
+ *
29
+ * @see http://stackoverflow.com/questions/11750223/apc-user-cache-entries-not-expiring
30
+ *
31
+ * @var array
32
+ */
33
+ protected $expires = array();
34
+
35
+ public function __construct()
36
+ {
37
+ if (!function_exists('apcu_fetch') && !function_exists('apc_fetch')) {
38
+ throw new Exception('ext-apc(u) is not installed.');
39
+ }
40
+ }
41
+
42
+ /**
43
+ * {@inheritdoc}
44
+ */
45
+ public function get($key, &$token = null)
46
+ {
47
+ // check for values that were just stored in this request but have
48
+ // actually expired by now
49
+ if (isset($this->expires[$key]) && $this->expires[$key] < time()) {
50
+ return false;
51
+ }
52
+
53
+ $value = $this->apcu_fetch($key, $success);
54
+ if (false === $success) {
55
+ $token = null;
56
+
57
+ return false;
58
+ }
59
+
60
+ $token = serialize($value);
61
+
62
+ return $value;
63
+ }
64
+
65
+ /**
66
+ * {@inheritdoc}
67
+ */
68
+ public function getMulti(array $keys, array &$tokens = null)
69
+ {
70
+ $tokens = array();
71
+ if (empty($keys)) {
72
+ return array();
73
+ }
74
+
75
+ // check for values that were just stored in this request but have
76
+ // actually expired by now
77
+ foreach ($keys as $i => $key) {
78
+ if (isset($this->expires[$key]) && $this->expires[$key] < time()) {
79
+ unset($keys[$i]);
80
+ }
81
+ }
82
+
83
+ $values = $this->apcu_fetch($keys);
84
+ if (false === $values) {
85
+ return array();
86
+ }
87
+
88
+ $tokens = array();
89
+ foreach ($values as $key => $value) {
90
+ $tokens[$key] = serialize($value);
91
+ }
92
+
93
+ return $values;
94
+ }
95
+
96
+ /**
97
+ * {@inheritdoc}
98
+ */
99
+ public function set($key, $value, $expire = 0)
100
+ {
101
+ $ttl = $this->ttl($expire);
102
+
103
+ // negative TTLs don't always seem to properly treat the key as deleted
104
+ if ($ttl < 0) {
105
+ $this->delete($key);
106
+
107
+ return true;
108
+ }
109
+
110
+ // lock required for CAS
111
+ if (!$this->lock($key)) {
112
+ return false;
113
+ }
114
+
115
+ $success = $this->apcu_store($key, $value, $ttl);
116
+ $this->expire($key, $ttl);
117
+ $this->unlock($key);
118
+
119
+ return $success;
120
+ }
121
+
122
+ /**
123
+ * {@inheritdoc}
124
+ */
125
+ public function setMulti(array $items, $expire = 0)
126
+ {
127
+ if (empty($items)) {
128
+ return array();
129
+ }
130
+
131
+ $ttl = $this->ttl($expire);
132
+
133
+ // negative TTLs don't always seem to properly treat the key as deleted
134
+ if ($ttl < 0) {
135
+ $this->deleteMulti(array_keys($items));
136
+
137
+ return array_fill_keys(array_keys($items), true);
138
+ }
139
+
140
+ // attempt to get locks for all items
141
+ $locked = $this->lock(array_keys($items));
142
+ $locked = array_fill_keys($locked, null);
143
+ $failed = array_diff_key($items, $locked);
144
+ $items = array_intersect_key($items, $locked);
145
+
146
+ if ($items) {
147
+ // only write to those where lock was acquired
148
+ $this->apcu_store($items, null, $ttl);
149
+ $this->expire(array_keys($items), $ttl);
150
+ $this->unlock(array_keys($items));
151
+ }
152
+
153
+ $return = array();
154
+ foreach ($items as $key => $value) {
155
+ $return[$key] = !array_key_exists($key, $failed);
156
+ }
157
+
158
+ return $return;
159
+ }
160
+
161
+ /**
162
+ * {@inheritdoc}
163
+ */
164
+ public function delete($key)
165
+ {
166
+ // lock required for CAS
167
+ if (!$this->lock($key)) {
168
+ return false;
169
+ }
170
+
171
+ $success = $this->apcu_delete($key);
172
+ unset($this->expires[$key]);
173
+ $this->unlock($key);
174
+
175
+ return $success;
176
+ }
177
+
178
+ /**
179
+ * {@inheritdoc}
180
+ */
181
+ public function deleteMulti(array $keys)
182
+ {
183
+ if (empty($keys)) {
184
+ return array();
185
+ }
186
+
187
+ // attempt to get locks for all items
188
+ $locked = $this->lock($keys);
189
+ $failed = array_diff($keys, $locked);
190
+ $keys = array_intersect($keys, $locked);
191
+
192
+ // only delete those where lock was acquired
193
+ if ($keys) {
194
+ /**
195
+ * Contrary to the docs, apc_delete also accepts an array of
196
+ * multiple keys to be deleted. Docs for apcu_delete are ok in this
197
+ * regard.
198
+ * But both are flawed in terms of return value in this case: an
199
+ * array with failed keys is returned.
200
+ *
201
+ * @see http://php.net/manual/en/function.apc-delete.php
202
+ *
203
+ * @var string[]
204
+ */
205
+ $result = $this->apcu_delete($keys);
206
+ $failed = array_merge($failed, $result);
207
+ $this->unlock($keys);
208
+ }
209
+
210
+ $return = array();
211
+ foreach ($keys as $key) {
212
+ $return[$key] = !in_array($key, $failed);
213
+ unset($this->expires[$key]);
214
+ }
215
+
216
+ return $return;
217
+ }
218
+
219
+ /**
220
+ * {@inheritdoc}
221
+ */
222
+ public function add($key, $value, $expire = 0)
223
+ {
224
+ $ttl = $this->ttl($expire);
225
+
226
+ // negative TTLs don't always seem to properly treat the key as deleted
227
+ if ($ttl < 0) {
228
+ // don't add - it's expired already; just check if it already
229
+ // existed to return true/false as expected from add()
230
+ return false === $this->get($key);
231
+ }
232
+
233
+ // lock required for CAS
234
+ if (!$this->lock($key)) {
235
+ return false;
236
+ }
237
+
238
+ $success = $this->apcu_add($key, $value, $ttl);
239
+ $this->expire($key, $ttl);
240
+ $this->unlock($key);
241
+
242
+ return $success;
243
+ }
244
+
245
+ /**
246
+ * {@inheritdoc}
247
+ */
248
+ public function replace($key, $value, $expire = 0)
249
+ {
250
+ $ttl = $this->ttl($expire);
251
+
252
+ // APC doesn't support replace; I'll use get to check key existence,
253
+ // then safely replace with cas
254
+ $current = $this->get($key, $token);
255
+ if (false === $current) {
256
+ return false;
257
+ }
258
+
259
+ // negative TTLs don't always seem to properly treat the key as deleted
260
+ if ($ttl < 0) {
261
+ $this->delete($key);
262
+
263
+ return true;
264
+ }
265
+
266
+ // no need for locking - cas will do that
267
+ return $this->cas($token, $key, $value, $ttl);
268
+ }
269
+
270
+ /**
271
+ * {@inheritdoc}
272
+ */
273
+ public function cas($token, $key, $value, $expire = 0)
274
+ {
275
+ $ttl = $this->ttl($expire);
276
+
277
+ // lock required because we can't perform an atomic CAS
278
+ if (!$this->lock($key)) {
279
+ return false;
280
+ }
281
+
282
+ // check for values that were just stored in this request but have
283
+ // actually expired by now
284
+ if (isset($this->expires[$key]) && $this->expires[$key] < time()) {
285
+ return false;
286
+ }
287
+
288
+ // get current value, to compare with token
289
+ $compare = $this->apcu_fetch($key);
290
+
291
+ if (false === $compare) {
292
+ $this->unlock($key);
293
+
294
+ return false;
295
+ }
296
+
297
+ if ($token !== serialize($compare)) {
298
+ $this->unlock($key);
299
+
300
+ return false;
301
+ }
302
+
303
+ // negative TTLs don't always seem to properly treat the key as deleted
304
+ if ($ttl < 0) {
305
+ $this->apcu_delete($key);
306
+ unset($this->expires[$key]);
307
+ $this->unlock($key);
308
+
309
+ return true;
310
+ }
311
+
312
+ $success = $this->apcu_store($key, $value, $ttl);
313
+ $this->expire($key, $ttl);
314
+ $this->unlock($key);
315
+
316
+ return $success;
317
+ }
318
+
319
+ /**
320
+ * {@inheritdoc}
321
+ */
322
+ public function increment($key, $offset = 1, $initial = 0, $expire = 0)
323
+ {
324
+ if ($offset <= 0 || $initial < 0) {
325
+ return false;
326
+ }
327
+
328
+ // not doing apc_inc because that one it doesn't let us set an initial
329
+ // value or TTL
330
+ return $this->doIncrement($key, $offset, $initial, $expire);
331
+ }
332
+
333
+ /**
334
+ * {@inheritdoc}
335
+ */
336
+ public function decrement($key, $offset = 1, $initial = 0, $expire = 0)
337
+ {
338
+ if ($offset <= 0 || $initial < 0) {
339
+ return false;
340
+ }
341
+
342
+ // not doing apc_dec because that one it doesn't let us set an initial
343
+ // value or TTL
344
+ return $this->doIncrement($key, -$offset, $initial, $expire);
345
+ }
346
+
347
+ /**
348
+ * {@inheritdoc}
349
+ */
350
+ public function touch($key, $expire)
351
+ {
352
+ $ttl = $this->ttl($expire);
353
+
354
+ // shortcut - expiring is similar to deleting, but the former has no
355
+ // 1-operation equivalent
356
+ if ($ttl < 0) {
357
+ return $this->delete($key);
358
+ }
359
+
360
+ // get existing TTL & quit early if it's that one already
361
+ $iterator = $this->APCuIterator('/^'.preg_quote($key, '/').'$/', \APC_ITER_VALUE | \APC_ITER_TTL, 1, \APC_LIST_ACTIVE);
362
+ $current = $iterator->current();
363
+ if (!$current) {
364
+ // doesn't exist
365
+ return false;
366
+ }
367
+ if ($current['ttl'] === $ttl) {
368
+ // that's the TTL already, no need to reset it
369
+ return true;
370
+ }
371
+
372
+ // generate CAS token to safely CAS existing value with new TTL
373
+ $value = $current['value'];
374
+ $token = serialize($value);
375
+
376
+ return $this->cas($token, $key, $value, $ttl);
377
+ }
378
+
379
+ /**
380
+ * {@inheritdoc}
381
+ */
382
+ public function flush()
383
+ {
384
+ $this->expires = array();
385
+
386
+ return $this->apcu_clear_cache();
387
+ }
388
+
389
+ /**
390
+ * {@inheritdoc}
391
+ */
392
+ public function getCollection($name)
393
+ {
394
+ return new Collection($this, $name);
395
+ }
396
+
397
+ /**
398
+ * Shared between increment/decrement: both have mostly the same logic
399
+ * (decrement just increments a negative value), but need their validation
400
+ * split up (increment won't accept negative values).
401
+ *
402
+ * @param string $key
403
+ * @param int $offset
404
+ * @param int $initial
405
+ * @param int $expire
406
+ *
407
+ * @return int|bool
408
+ */
409
+ protected function doIncrement($key, $offset, $initial, $expire)
410
+ {
411
+ $ttl = $this->ttl($expire);
412
+
413
+ /*
414
+ * APC has apc_inc & apc_dec, which work great. However, they don't
415
+ * allow for a TTL to be set.
416
+ * I could use apc_inc & apc_dec & then do a touch, but touch also
417
+ * doesn't have an APC implementation & needs a get & cas. That would
418
+ * be 2 operations + CAS.
419
+ * Instead, I'll just do a get, implement the increase or decrease in
420
+ * PHP, then CAS the new value = 1 operation + CAS.
421
+ */
422
+ $value = $this->get($key, $token);
423
+ if (false === $value) {
424
+ // don't even set initial value, it's already expired...
425
+ if ($ttl < 0) {
426
+ return $initial;
427
+ }
428
+
429
+ // no need for locking - set will do that
430
+ $success = $this->add($key, $initial, $ttl);
431
+
432
+ return $success ? $initial : false;
433
+ }
434
+
435
+ if (!is_numeric($value) || $value < 0) {
436
+ return false;
437
+ }
438
+
439
+ $value += $offset;
440
+ // value can never be lower than 0
441
+ $value = max(0, $value);
442
+
443
+ // negative TTLs don't always seem to properly treat the key as deleted
444
+ if ($ttl < 0) {
445
+ $success = $this->delete($key);
446
+
447
+ return $success ? $value : false;
448
+ }
449
+
450
+ // no need for locking - cas will do that
451
+ $success = $this->cas($token, $key, $value, $ttl);
452
+
453
+ return $success ? $value : false;
454
+ }
455
+
456
+ /**
457
+ * APC expects true TTL, not expiration timestamp.
458
+ *
459
+ * @param int $expire
460
+ *
461
+ * @return int TTL in seconds
462
+ */
463
+ protected function ttl($expire)
464
+ {
465
+ // relative time in seconds, <30 days
466
+ if ($expire < 30 * 24 * 60 * 60) {
467
+ $expire += time();
468
+ }
469
+
470
+ return $expire ? $expire - time() : 0;
471
+ }
472
+
473
+ /**
474
+ * Acquire a lock. If we failed to acquire a lock, it'll automatically try
475
+ * again in 1ms, for a maximum of 10 times.
476
+ *
477
+ * APC provides nothing that would allow us to do CAS. To "emulate" CAS,
478
+ * we'll work with locks: all cache writes also briefly create a lock
479
+ * cache entry (yup: #writes * 3, for lock & unlock - luckily, they're
480
+ * not over the network)
481
+ * Writes are disallows when a lock can't be obtained (= locked by
482
+ * another write), which makes it possible for us to first retrieve,
483
+ * compare & then set in a nob-atomic way.
484
+ * However, there's a possibility for interference with direct APC
485
+ * access touching the same keys - e.g. other scripts, not using this
486
+ * class. If CAS is of importance, make sure the only things touching
487
+ * APC on your server is using these classes!
488
+ *
489
+ * @param string|string[] $keys
490
+ *
491
+ * @return array Array of successfully locked keys
492
+ */
493
+ protected function lock($keys)
494
+ {
495
+ // both string (single key) and array (multiple) are accepted
496
+ $keys = (array) $keys;
497
+
498
+ $locked = array();
499
+ for ($i = 0; $i < 10; ++$i) {
500
+ $locked += $this->acquire($keys);
501
+ $keys = array_diff($keys, $locked);
502
+
503
+ if (empty($keys)) {
504
+ break;
505
+ }
506
+
507
+ usleep(1);
508
+ }
509
+
510
+ return $locked;
511
+ }
512
+
513
+ /**
514
+ * Acquire a lock - required to provide CAS functionality.
515
+ *
516
+ * @param string|string[] $keys
517
+ *
518
+ * @return string[] Array of successfully locked keys
519
+ */
520
+ protected function acquire($keys)
521
+ {
522
+ $keys = (array) $keys;
523
+
524
+ $values = array();
525
+ foreach ($keys as $key) {
526
+ $values["scrapbook.lock.$key"] = null;
527
+ }
528
+
529
+ // there's no point in locking longer than max allowed execution time
530
+ // for this script
531
+ $ttl = ini_get('max_execution_time');
532
+
533
+ // lock these keys, then compile a list of successfully locked keys
534
+ // (using the returned failure array)
535
+ $result = (array) $this->apcu_add($values, null, $ttl);
536
+ $failed = array();
537
+ foreach ($result as $key => $err) {
538
+ $failed[] = substr($key, strlen('scrapbook.lock.'));
539
+ }
540
+
541
+ return array_diff($keys, $failed);
542
+ }
543
+
544
+ /**
545
+ * Release a lock.
546
+ *
547
+ * @param string|string[] $keys
548
+ *
549
+ * @return bool
550
+ */
551
+ protected function unlock($keys)
552
+ {
553
+ $keys = (array) $keys;
554
+ foreach ($keys as $i => $key) {
555
+ $keys[$i] = "scrapbook.lock.$key";
556
+ }
557
+
558
+ $this->apcu_delete($keys);
559
+
560
+ return true;
561
+ }
562
+
563
+ /**
564
+ * Store the expiration time for items we're setting in this request, to
565
+ * work around APC's behavior of only clearing expires per page request.
566
+ *
567
+ * @see static::$expires
568
+ *
569
+ * @param array|string $key
570
+ * @param int $ttl
571
+ */
572
+ protected function expire($key = array(), $ttl = 0)
573
+ {
574
+ if (0 === $ttl) {
575
+ // when storing indefinitely, there's no point in keeping it around,
576
+ // it won't just expire
577
+ return;
578
+ }
579
+
580
+ // $key can be both string (1 key) or array (multiple)
581
+ $keys = (array) $key;
582
+
583
+ $time = time() + $ttl;
584
+ foreach ($keys as $key) {
585
+ $this->expires[$key] = $time;
586
+ }
587
+ }
588
+
589
+ /**
590
+ * @param string|string[] $key
591
+ * @param bool $success
592
+ *
593
+ * @return mixed|false
594
+ */
595
+ protected function apcu_fetch($key, &$success = null)
596
+ {
597
+ /*
598
+ * $key can also be numeric, in which case APC is able to retrieve it,
599
+ * but will have an invalid $key in the results array, and trying to
600
+ * locate it by its $key in that array will fail with `undefined index`.
601
+ * I'll work around this by requesting those values 1 by 1.
602
+ */
603
+ if (is_array($key)) {
604
+ $nums = array_filter($key, 'is_numeric');
605
+ if ($nums) {
606
+ $values = array();
607
+ foreach ($nums as $k) {
608
+ $values[$k] = $this->apcu_fetch((string) $k, $success);
609
+ }
610
+
611
+ $remaining = array_diff($key, $nums);
612
+ if ($remaining) {
613
+ $values += $this->apcu_fetch($remaining, $success2);
614
+ $success &= $success2;
615
+ }
616
+
617
+ return $values;
618
+ }
619
+ }
620
+
621
+ if (function_exists('apcu_fetch')) {
622
+ return apcu_fetch($key, $success);
623
+ } else {
624
+ return apc_fetch($key, $success);
625
+ }
626
+ }
627
+
628
+ /**
629
+ * @param string|string[] $key
630
+ * @param mixed $var
631
+ * @param int $ttl
632
+ *
633
+ * @return bool|bool[]
634
+ */
635
+ protected function apcu_store($key, $var, $ttl = 0)
636
+ {
637
+ /*
638
+ * $key can also be a [$key => $value] array, where key is numeric,
639
+ * but got cast to int by PHP. APC doesn't seem to store such numerical
640
+ * key, so we'll have to take care of those one by one.
641
+ */
642
+ if (is_array($key)) {
643
+ $nums = array_filter(array_keys($key), 'is_numeric');
644
+ if ($nums) {
645
+ $success = array();
646
+ $nums = array_intersect_key($key, array_fill_keys($nums, null));
647
+ foreach ($nums as $k => $v) {
648
+ $success[$k] = $this->apcu_store((string) $k, $v, $ttl);
649
+ }
650
+
651
+ $remaining = array_diff_key($key, $nums);
652
+ if ($remaining) {
653
+ $success += $this->apcu_store($remaining, $var, $ttl);
654
+ }
655
+
656
+ return $success;
657
+ }
658
+ }
659
+
660
+ if (function_exists('apcu_store')) {
661
+ return apcu_store($key, $var, $ttl);
662
+ } else {
663
+ return apc_store($key, $var, $ttl);
664
+ }
665
+ }
666
+
667
+ /**
668
+ * @param string|string[]|APCIterator|APCuIterator $key
669
+ *
670
+ * @return bool|string[]
671
+ */
672
+ protected function apcu_delete($key)
673
+ {
674
+ if (function_exists('apcu_delete')) {
675
+ return apcu_delete($key);
676
+ } else {
677
+ return apc_delete($key);
678
+ }
679
+ }
680
+
681
+ /**
682
+ * @param string|string[] $key
683
+ * @param mixed $var
684
+ * @param int $ttl
685
+ *
686
+ * @return bool|bool[]
687
+ */
688
+ protected function apcu_add($key, $var, $ttl = 0)
689
+ {
690
+ if (function_exists('apcu_add')) {
691
+ return apcu_add($key, $var, $ttl);
692
+ } else {
693
+ return apc_add($key, $var, $ttl);
694
+ }
695
+ }
696
+
697
+ /**
698
+ * @return bool
699
+ */
700
+ protected function apcu_clear_cache()
701
+ {
702
+ if (function_exists('apcu_clear_cache')) {
703
+ return apcu_clear_cache();
704
+ } else {
705
+ return apc_clear_cache('user');
706
+ }
707
+ }
708
+
709
+ /**
710
+ * @param string|string[]|null $search
711
+ * @param int $format
712
+ * @param int $chunk_size
713
+ * @param int $list
714
+ *
715
+ * @return APCIterator|APCuIterator
716
+ */
717
+ protected function APCuIterator($search = null, $format = null, $chunk_size = null, $list = null)
718
+ {
719
+ $arguments = func_get_args();
720
+
721
+ if (class_exists('APCuIterator', false)) {
722
+ // I can't set the defaults parameter values because the APC_ or
723
+ // APCU_ constants may not exist, so I'll just initialize from
724
+ // func_get_args, not passing those params that haven't been set
725
+ $reflect = new \ReflectionClass('APCuIterator');
726
+
727
+ return $reflect->newInstanceArgs($arguments);
728
+ } else {
729
+ array_unshift($arguments, 'user');
730
+ $reflect = new \ReflectionClass('APCIterator');
731
+
732
+ return $reflect->newInstanceArgs($arguments);
733
+ }
734
+ }
735
+ }
vendor/browscap-php/matthiasmullie/scrapbook/src/Adapters/Collections/Apc.php ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace MatthiasMullie\Scrapbook\Adapters\Collections;
4
+
5
+ use MatthiasMullie\Scrapbook\Adapters\Apc as Adapter;
6
+ use MatthiasMullie\Scrapbook\Adapters\Collections\Utils\PrefixKeys;
7
+
8
+ /**
9
+ * APC adapter for a subset of data, accomplished by prefixing keys.
10
+ *
11
+ * @author Matthias Mullie <scrapbook@mullie.eu>
12
+ * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
13
+ * @license LICENSE MIT
14
+ */
15
+ class Apc extends PrefixKeys
16
+ {
17
+ /**
18
+ * @param string $name
19
+ */
20
+ public function __construct(Adapter $cache, $name)
21
+ {
22
+ parent::__construct($cache, 'collection:'.$name.':');
23
+ }
24
+
25
+ /**
26
+ * {@inheritdoc}
27
+ */
28
+ public function flush()
29
+ {
30
+ /*
31
+ * Both of these utility methods are protected in parent, because I just
32
+ * don't want to expose them to users. But I really want to use them
33
+ * here... I'll use reflection to access them - I shouldn't, but I have
34
+ * a decent test suite to protect me, should I forget about this and
35
+ * change the implementation of these methods.
36
+ */
37
+ $reflection = new \ReflectionMethod($this->cache, 'APCuIterator');
38
+ $reflection->setAccessible(true);
39
+ $iterator = $reflection->invoke($this->cache, '/^'.preg_quote($this->prefix, '/').'/', \APC_ITER_KEY);
40
+
41
+ $reflection = new \ReflectionMethod($this->cache, 'apcu_delete');
42
+ $reflection->setAccessible(true);
43
+
44
+ return $reflection->invoke($this->cache, $iterator);
45
+ }
46
+ }
vendor/browscap-php/matthiasmullie/scrapbook/src/Adapters/Collections/Couchbase.php ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace MatthiasMullie\Scrapbook\Adapters\Collections;
4
+
5
+ use MatthiasMullie\Scrapbook\Adapters\Collections\Utils\PrefixReset;
6
+ use MatthiasMullie\Scrapbook\Adapters\Couchbase as Adapter;
7
+
8
+ /**
9
+ * Couchbase adapter for a subset of data, accomplished by prefixing keys.
10
+ *
11
+ * Couchbase supports multiple buckets. However, there's no overarching "server"
12
+ * that can flush all the buckets (apart from looping all of them)
13
+ * It may also not be possible to get into another bucket: they may have
14
+ * different credentials.
15
+ * And it's less trivial to "create" a new bucket. It could be done (although
16
+ * not from the `CouchbaseBucket` we have in the adapter), but needs config.
17
+ *
18
+ * I'll implement collections similar to how they've been implemented for
19
+ * Memcached: prefix keys & a reference value that can be changed to "flush"
20
+ * the cache. If people want multiple different buckets, they can easily create
21
+ * multiple Couchbase adapter objects.
22
+ *
23
+ * @author Matthias Mullie <scrapbook@mullie.eu>
24
+ * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
25
+ * @license LICENSE MIT
26
+ */
27
+ class Couchbase extends PrefixReset
28
+ {
29
+ /**
30
+ * @param string $name
31
+ */
32
+ public function __construct(Adapter $cache, $name)
33
+ {
34
+ parent::__construct($cache, $name);
35
+ }
36
+ }
vendor/browscap-php/matthiasmullie/scrapbook/src/Adapters/Collections/Flysystem.php ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace MatthiasMullie\Scrapbook\Adapters\Collections;
4
+
5
+ use League\Flysystem\FileNotFoundException;
6
+ use League\Flysystem\Filesystem;
7
+ use League\Flysystem\UnableToDeleteFile;
8
+ use MatthiasMullie\Scrapbook\Adapters\Flysystem as Adapter;
9
+
10
+ /**
11
+ * Flysystem 1.x and 2.x adapter for a subset of data, in a subfolder.
12
+ *
13
+ * @author Matthias Mullie <scrapbook@mullie.eu>
14
+ * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
15
+ * @license LICENSE MIT
16
+ */
17
+ class Flysystem extends Adapter
18
+ {
19
+ /**
20
+ * @var string
21
+ */
22
+ protected $collection;
23
+
24
+ /**
25
+ * @param string $collection
26
+ */
27
+ public function __construct(Filesystem $filesystem, $collection)
28
+ {
29
+ parent::__construct($filesystem);
30
+ $this->collection = $collection;
31
+ }
32
+
33
+ /**
34
+ * {@inheritdoc}
35
+ */
36
+ public function flush()
37
+ {
38
+ $files = $this->filesystem->listContents($this->collection);
39
+ foreach ($files as $file) {
40
+ try {
41
+ if ('dir' === $file['type']) {
42
+ if (1 === $this->version) {
43
+ $this->filesystem->deleteDir($file['path']);
44
+ } else {
45
+ $this->filesystem->deleteDirectory($file['path']);
46
+ }
47
+ } else {
48
+ $this->filesystem->delete($file['path']);
49
+ }
50
+ } catch (FileNotFoundException $e) {
51
+ // v1.x
52
+ // don't care if we failed to unlink something, might have
53
+ // been deleted by another process in the meantime...
54
+ } catch (UnableToDeleteFile $e) {
55
+ // v2.x
56
+ // don't care if we failed to unlink something, might have
57
+ // been deleted by another process in the meantime...
58
+ }
59
+ }
60
+
61
+ return true;
62
+ }
63
+
64
+ /**
65
+ * @param string $key
66
+ *
67
+ * @return string
68
+ */
69
+ protected function path($key)
70
+ {
71
+ return $this->collection.'/'.parent::path($key);
72
+ }
73
+ }
vendor/browscap-php/matthiasmullie/scrapbook/src/Adapters/Collections/Memcached.php ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace MatthiasMullie\Scrapbook\Adapters\Collections;
4
+
5
+ use MatthiasMullie\Scrapbook\Adapters\Collections\Utils\PrefixReset;
6
+ use MatthiasMullie\Scrapbook\Adapters\Memcached as Adapter;
7
+
8
+ /**
9
+ * Memcached adapter for a subset of data, accomplished by prefixing keys.
10
+ *
11
+ * I would like to OPT_PREFIX_KEY on the client, but since $client is a
12
+ * reference to the one in parent, that prefix would also be applied
13
+ * there. Instead, I'll manually apply the prefix to all keys prior to
14
+ * them going out to the server - this will have the exact same result!
15
+ *
16
+ * @author Matthias Mullie <scrapbook@mullie.eu>
17
+ * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
18
+ * @license LICENSE MIT
19
+ */
20
+ class Memcached extends PrefixReset
21
+ {
22
+ /**
23
+ * @param string $name
24
+ */
25
+ public function __construct(Adapter $cache, $name)
26
+ {
27
+ parent::__construct($cache, $name);
28
+ }
29
+ }
vendor/browscap-php/matthiasmullie/scrapbook/src/Adapters/Collections/MemoryStore.php ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace MatthiasMullie\Scrapbook\Adapters\Collections;
4
+
5
+ use MatthiasMullie\Scrapbook\Adapters\Collections\Utils\PrefixKeys;
6
+ use MatthiasMullie\Scrapbook\Adapters\MemoryStore as Adapter;
7
+ use ReflectionObject;
8
+
9
+ /**
10
+ * MemoryStore adapter for a subset of data.
11
+ *
12
+ * @author Matthias Mullie <scrapbook@mullie.eu>
13
+ * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
14
+ * @license LICENSE MIT
15
+ */
16
+ class MemoryStore extends PrefixKeys
17
+ {
18
+ /**
19
+ * @param string $name
20
+ */
21
+ public function __construct(Adapter $cache, $name)
22
+ {
23
+ parent::__construct($cache, $name.':');
24
+ }
25
+
26
+ /**
27
+ * {@inheritdoc}
28
+ */
29
+ public function flush()
30
+ {
31
+ /*
32
+ * It's not done to use ReflectionObject, but:
33
+ * - I *really* don't want to expose $cache->items publicly
34
+ * - This is very specific to MemoryStore implementation, it can assume
35
+ * these kind of implementation details (like how it's ok for a child
36
+ * to use protected methods - this just can't be a subclass for
37
+ * practical reasons, but it mostly acts like one)
38
+ * - Reflection is not the most optimized thing, but that doesn't matter
39
+ * too much for MemoryStore, which is not a *real* cache
40
+ */
41
+ $object = new ReflectionObject($this->cache);
42
+ $property = $object->getProperty('items');
43
+ $property->setAccessible(true);
44
+ $items = $property->getValue($this->cache);
45
+
46
+ foreach ($items as $key => $value) {
47
+ if (0 === strpos($key, $this->prefix)) {
48
+ $this->cache->delete($key);
49
+ }
50
+ }
51
+
52
+ return true;
53
+ }
54
+ }
vendor/browscap-php/matthiasmullie/scrapbook/src/Adapters/Collections/Redis.php ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace MatthiasMullie\Scrapbook\Adapters\Collections;
4
+
5
+ use MatthiasMullie\Scrapbook\Adapters\Redis as Adapter;
6
+
7
+ /**
8
+ * Redis adapter for a subset of data, in a different database.
9
+ *
10
+ * @author Matthias Mullie <scrapbook@mullie.eu>
11
+ * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
12
+ * @license LICENSE MIT
13
+ */
14
+ class Redis extends Adapter
15
+ {
16
+ /**
17
+ * @param \Redis $client
18
+ * @param int $database
19
+ */
20
+ public function __construct($client, $database)
21
+ {
22
+ parent::__construct($client);
23
+ $this->client->select($database);
24
+ }
25
+
26
+ /**
27
+ * {@inheritdoc}
28
+ */
29
+ public function flush()
30
+ {
31
+ return $this->client->flushDB();
32
+ }
33
+ }
vendor/browscap-php/matthiasmullie/scrapbook/src/Adapters/Collections/SQL.php ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace MatthiasMullie\Scrapbook\Adapters\Collections;
4
+
5
+ use MatthiasMullie\Scrapbook\Adapters\Collections\Utils\PrefixKeys;
6
+ use MatthiasMullie\Scrapbook\Adapters\SQL as Adapter;
7
+ use PDO;
8
+
9
+ /**
10
+ * SQL adapter for a subset of data, accomplished by prefixing keys.
11
+ *
12
+ * @author Matthias Mullie <scrapbook@mullie.eu>
13
+ * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
14
+ * @license LICENSE MIT
15
+ */
16
+ class SQL extends PrefixKeys
17
+ {
18
+ /**
19
+ * @var PDO
20
+ */
21
+ protected $client;
22
+
23
+ /**
24
+ * @var string
25
+ */
26
+ protected $table;
27
+
28
+ /**
29
+ * @param string $table
30
+ * @param string $name
31
+ */
32
+ public function __construct(Adapter $cache, PDO $client, $table, $name)
33
+ {
34
+ parent::__construct($cache, 'collection:'.$name.':');
35
+ $this->client = $client;
36
+ $this->table = $table;
37
+ }
38
+
39
+ /**
40
+ * {@inheritdoc}
41
+ */
42
+ public function flush()
43
+ {
44
+ // deleting key with a prefixed LIKE should be fast, they're indexed
45
+ $statement = $this->client->prepare(
46
+ "DELETE FROM $this->table
47
+ WHERE k LIKE :key"
48
+ );
49
+
50
+ return $statement->execute(array(
51
+ ':key' => $this->prefix.'%',
52
+ ));
53
+ }
54
+ }
vendor/browscap-php/matthiasmullie/scrapbook/src/Adapters/Collections/Utils/PrefixKeys.php ADDED
@@ -0,0 +1,216 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace MatthiasMullie\Scrapbook\Adapters\Collections\Utils;
4
+
5
+ use MatthiasMullie\Scrapbook\KeyValueStore;
6
+
7
+ /**
8
+ * @author Matthias Mullie <scrapbook@mullie.eu>
9
+ * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
10
+ * @license LICENSE MIT
11
+ */
12
+ class PrefixKeys implements KeyValueStore
13
+ {
14
+ /**
15
+ * @var KeyValueStore
16
+ */
17
+ protected $cache;
18
+
19
+ /**
20
+ * @var string
21
+ */
22
+ protected $prefix;
23
+
24
+ /**
25
+ * @param string $prefix
26
+ */
27
+ public function __construct(KeyValueStore $cache, $prefix)
28
+ {
29
+ $this->cache = $cache;
30
+ $this->setPrefix($prefix);
31
+ }
32
+
33
+ /**
34
+ * {@inheritdoc}
35
+ */
36
+ public function get($key, &$token = null)
37
+ {
38
+ $key = $this->prefix($key);
39
+
40
+ return $this->cache->get($key, $token);
41
+ }
42
+
43
+ /**
44
+ * {@inheritdoc}
45
+ */
46
+ public function getMulti(array $keys, array &$tokens = null)
47
+ {
48
+ $keys = array_map(array($this, 'prefix'), $keys);
49
+ $results = $this->cache->getMulti($keys, $tokens);
50
+ $keys = array_map(array($this, 'unfix'), array_keys($results));
51
+ $tokens = array_combine($keys, $tokens);
52
+
53
+ return array_combine($keys, $results);
54
+ }
55
+
56
+ /**
57
+ * {@inheritdoc}
58
+ */
59
+ public function set($key, $value, $expire = 0)
60
+ {
61
+ $key = $this->prefix($key);
62
+
63
+ // Note: I have no idea why, but it seems to happen in some cases that
64
+ // `$value` is `null`, but func_get_arg(1) returns the correct value.
65
+ // Makes no sense, probably a very obscure edge case, but it happens.
66
+ // (it didn't seem to happen if `$value` was another variable name...)
67
+ return $this->cache->set($key, func_get_arg(1), $expire);
68
+ }
69
+
70
+ /**
71
+ * {@inheritdoc}
72
+ */
73
+ public function setMulti(array $items, $expire = 0)
74
+ {
75
+ $keys = array_map(array($this, 'prefix'), array_keys($items));
76
+ $items = array_combine($keys, $items);
77
+ $results = $this->cache->setMulti($items, $expire);
78
+ $keys = array_map(array($this, 'unfix'), array_keys($results));
79
+
80
+ return array_combine($keys, $results);
81
+ }
82
+
83
+ /**
84
+ * {@inheritdoc}
85
+ */
86
+ public function delete($key)
87
+ {
88
+ $key = $this->prefix($key);
89
+
90
+ return $this->cache->delete($key);
91
+ }
92
+
93
+ /**
94
+ * {@inheritdoc}
95
+ */
96
+ public function deleteMulti(array $keys)
97
+ {
98
+ $keys = array_map(array($this, 'prefix'), $keys);
99
+ $results = $this->cache->deleteMulti($keys);
100
+ $keys = array_map(array($this, 'unfix'), array_keys($results));
101
+
102
+ return array_combine($keys, $results);
103
+ }
104
+
105
+ /**
106
+ * {@inheritdoc}
107
+ */
108
+ public function add($key, $value, $expire = 0)
109
+ {
110
+ $key = $this->prefix($key);
111
+
112
+ // Note: I have no idea why, but it seems to happen in some cases that
113
+ // `$value` is `null`, but func_get_arg(1) returns the correct value.
114
+ // Makes no sense, probably a very obscure edge case, but it happens.
115
+ // (it didn't seem to happen if `$value` was another variable name...)
116
+ return $this->cache->add($key, func_get_arg(1), $expire);
117
+ }
118
+
119
+ /**
120
+ * {@inheritdoc}
121
+ */
122
+ public function replace($key, $value, $expire = 0)
123
+ {
124
+ $key = $this->prefix($key);
125
+
126
+ // Note: I have no idea why, but it seems to happen in some cases that
127
+ // `$value` is `null`, but func_get_arg(1) returns the correct value.
128
+ // Makes no sense, probably a very obscure edge case, but it happens.
129
+ // (it didn't seem to happen if `$value` was another variable name...)
130
+ return $this->cache->replace($key, func_get_arg(1), $expire);
131
+ }
132
+
133
+ /**
134
+ * {@inheritdoc}
135
+ */
136
+ public function cas($token, $key, $value, $expire = 0)
137
+ {
138
+ $key = $this->prefix($key);
139
+
140
+ // Note: I have no idea why, but it seems to happen in some cases that
141
+ // `$value` is `null`, but func_get_arg(2) returns the correct value.
142
+ // Makes no sense, probably a very obscure edge case, but it happens.
143
+ // (it didn't seem to happen if `$value` was another variable name...)
144
+ return $this->cache->cas($token, $key, func_get_arg(2), $expire);
145
+ }
146
+
147
+ /**
148
+ * {@inheritdoc}
149
+ */
150
+ public function increment($key, $offset = 1, $initial = 0, $expire = 0)
151
+ {
152
+ $key = $this->prefix($key);
153
+
154
+ return $this->cache->increment($key, $offset, $initial, $expire);
155
+ }
156
+
157
+ /**
158
+ * {@inheritdoc}
159
+ */
160
+ public function decrement($key, $offset = 1, $initial = 0, $expire = 0)
161
+ {
162
+ $key = $this->prefix($key);
163
+
164
+ return $this->cache->decrement($key, $offset, $initial, $expire);
165
+ }
166
+
167
+ /**
168
+ * {@inheritdoc}
169
+ */
170
+ public function touch($key, $expire)
171
+ {
172
+ $key = $this->prefix($key);
173
+
174
+ return $this->cache->touch($key, $expire);
175
+ }
176
+
177
+ /**
178
+ * {@inheritdoc}
179
+ */
180
+ public function flush()
181
+ {
182
+ return $this->cache->flush();
183
+ }
184
+
185
+ /**
186
+ * {@inheritdoc}
187
+ */
188
+ public function getCollection($name)
189
+ {
190
+ return $this->cache->getCollection($name);
191
+ }
192
+
193
+ /**
194
+ * @param string $prefix
195
+ */
196
+ protected function setPrefix($prefix)
197
+ {
198
+ $this->prefix = $prefix;
199
+ }
200
+
201
+ /**
202
+ * {@inheritdoc}
203
+ */
204
+ protected function prefix($key)
205
+ {
206
+ return $this->prefix.$key;
207
+ }
208
+
209
+ /**
210
+ * {@inheritdoc}
211
+ */
212
+ protected function unfix($key)
213
+ {
214
+ return preg_replace('/^'.preg_quote($this->prefix, '/').'/', '', $key);
215
+ }
216
+ }
vendor/browscap-php/matthiasmullie/scrapbook/src/Adapters/Collections/Utils/PrefixReset.php ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace MatthiasMullie\Scrapbook\Adapters\Collections\Utils;
4
+
5
+ use MatthiasMullie\Scrapbook\KeyValueStore;
6
+
7
+ /**
8
+ * @author Matthias Mullie <scrapbook@mullie.eu>
9
+ * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
10
+ * @license LICENSE MIT
11
+ */
12
+ class PrefixReset extends PrefixKeys
13
+ {
14
+ /**
15
+ * @var string
16
+ */
17
+ protected $collection;
18
+
19
+ /**
20
+ * @param string $name
21
+ */
22
+ public function __construct(KeyValueStore $cache, $name)
23
+ {
24
+ $this->cache = $cache;
25
+ $this->collection = $name;
26
+ parent::__construct($cache, $this->getPrefix());
27
+ }
28
+
29
+ /**
30
+ * {@inheritdoc}
31
+ */
32
+ public function flush()
33
+ {
34
+ $index = $this->cache->increment($this->collection);
35
+ $this->setPrefix($this->collection.':'.$index.':');
36
+
37
+ return false !== $index;
38
+ }
39
+
40
+ /**
41
+ * @return string
42
+ */
43
+ protected function getPrefix()
44
+ {
45
+ /*
46
+ * It's easy enough to just set a prefix to be used, but we can not
47
+ * flush only a prefix!
48
+ * Instead, we'll generate a unique prefix key, based on some name.
49
+ * If we want to flush, we just create a new prefix and use that one.
50
+ */
51
+ $index = $this->cache->get($this->collection);
52
+
53
+ if (false === $index) {
54
+ $index = $this->cache->set($this->collection, 1);
55
+ }
56
+
57
+ return $this->collection.':'.$index.':';
58
+ }
59
+ }
vendor/browscap-php/matthiasmullie/scrapbook/src/Adapters/Couchbase.php ADDED
@@ -0,0 +1,501 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace MatthiasMullie\Scrapbook\Adapters;
4
+
5
+ use MatthiasMullie\Scrapbook\Adapters\Collections\Couchbase as Collection;
6
+ use MatthiasMullie\Scrapbook\Exception\InvalidKey;
7
+ use MatthiasMullie\Scrapbook\Exception\ServerUnhealthy;
8
+ use MatthiasMullie\Scrapbook\KeyValueStore;
9
+
10
+ /**
11
+ * Couchbase adapter. Basically just a wrapper over \CouchbaseBucket, but in an
12
+ * exchangeable (KeyValueStore) interface.
13
+ *
14
+ * @see http://developer.couchbase.com/documentation/server/4.0/sdks/php-2.0/php-intro.html
15
+ * @see http://docs.couchbase.com/sdk-api/couchbase-php-client-2.1.0/
16
+ * @see http://docs.couchbase.com/sdk-api/couchbase-php-client-2.6.2/
17
+ *
18
+ * @author Matthias Mullie <scrapbook@mullie.eu>
19
+ * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
20
+ * @license LICENSE MIT
21
+ */
22
+ class Couchbase implements KeyValueStore
23
+ {
24
+ /**
25
+ * @var \CouchbaseBucket|\Couchbase|Bucket
26
+ */
27
+ protected $client;
28
+
29
+ /**
30
+ * @param bool $assertServerHealthy
31
+ *
32
+ * @throws ServerUnhealthy
33
+ */
34
+ public function __construct(/* \CouchbaseBucket|\Couchbase|Bucket */ $client, $assertServerHealthy = true)
35
+ {
36
+ $this->client = $client;
37
+
38
+ if ($assertServerHealthy) {
39
+ $this->assertServerHealhy();
40
+ }
41
+ }
42
+
43
+ /**
44
+ * {@inheritdoc}
45
+ */
46
+ public function get($key, &$token = null)
47
+ {
48
+ $this->assertValidKey($key);
49
+
50
+ try {
51
+ $result = $this->client->get($key);
52
+ } catch (\CouchbaseException $e) {
53
+ $token = null;
54
+
55
+ return false;
56
+ }
57
+
58
+ $token = $result->cas;
59
+
60
+ return $result->error ? false : $this->unserialize($result->value);
61
+ }
62
+
63
+ /**
64
+ * {@inheritdoc}
65
+ */
66
+ public function getMulti(array $keys, array &$tokens = null)
67
+ {
68
+ array_map(array($this, 'assertValidKey'), $keys);
69
+
70
+ $tokens = array();
71
+ if (empty($keys)) {
72
+ return array();
73
+ }
74
+
75
+ try {
76
+ $results = $this->client->get($keys);
77
+ } catch (\CouchbaseException $e) {
78
+ return array();
79
+ }
80
+
81
+ $values = array();
82
+ $tokens = array();
83
+
84
+ foreach ($results as $key => $value) {
85
+ if (!in_array($key, $keys) || $value->error) {
86
+ continue;
87
+ }
88
+
89
+ $values[$key] = $this->unserialize($value->value);
90
+ $tokens[$key] = $value->cas;
91
+ }
92
+
93
+ return $values;
94
+ }
95
+
96
+ /**
97
+ * {@inheritdoc}
98
+ */
99
+ public function set($key, $value, $expire = 0)
100
+ {
101
+ $this->assertValidKey($key);
102
+
103
+ if ($this->deleteIfExpired($key, $expire)) {
104
+ return true;
105
+ }
106
+
107
+ $value = $this->serialize($value);
108
+ try {
109
+ $result = $this->client->upsert($key, $value, array('expiry' => $expire));
110
+ } catch (\CouchbaseException $e) {
111
+ return false;
112
+ }
113
+
114
+ return !$result->error;
115
+ }
116
+
117
+ /**
118
+ * {@inheritdoc}
119
+ */
120
+ public function setMulti(array $items, $expire = 0)
121
+ {
122
+ array_map(array($this, 'assertValidKey'), array_keys($items));
123
+
124
+ if (empty($items)) {
125
+ return array();
126
+ }
127
+
128
+ $keys = array_keys($items);
129
+ if ($this->deleteIfExpired($keys, $expire)) {
130
+ return array_fill_keys($keys, true);
131
+ }
132
+
133
+ // attempting to insert integer keys (e.g. '0' as key is automatically
134
+ // cast to int, if it's an array key) fails with a segfault, so we'll
135
+ // have to do those piecemeal
136
+ $integers = array_filter(array_keys($items), 'is_int');
137
+ if ($integers) {
138
+ $success = array();
139
+ $integers = array_intersect_key($items, array_fill_keys($integers, null));
140
+ foreach ($integers as $k => $v) {
141
+ $success[$k] = $this->set((string) $k, $v, $expire);
142
+ }
143
+
144
+ $items = array_diff_key($items, $integers);
145
+
146
+ return array_merge($success, $this->setMulti($items, $expire));
147
+ }
148
+
149
+ foreach ($items as $key => $value) {
150
+ $items[$key] = array(
151
+ 'value' => $this->serialize($value),
152
+ 'expiry' => $expire,
153
+ );
154
+ }
155
+
156
+ try {
157
+ $results = $this->client->upsert($items);
158
+ } catch (\CouchbaseException $e) {
159
+ return array_fill_keys(array_keys($items), false);
160
+ }
161
+
162
+ $success = array();
163
+ foreach ($results as $key => $result) {
164
+ $success[$key] = !$result->error;
165
+ }
166
+
167
+ return $success;
168
+ }
169
+
170
+ /**
171
+ * {@inheritdoc}
172
+ */
173
+ public function delete($key)
174
+ {
175
+ $this->assertValidKey($key);
176
+
177
+ try {
178
+ $result = $this->client->remove($key);
179
+ } catch (\CouchbaseException $e) {
180
+ return false;
181
+ }
182
+
183
+ return !$result->error;
184
+ }
185
+
186
+ /**
187
+ * {@inheritdoc}
188
+ */
189
+ public function deleteMulti(array $keys)
190
+ {
191
+ array_map(array($this, 'assertValidKey'), $keys);
192
+
193
+ if (empty($keys)) {
194
+ return array();
195
+ }
196
+
197
+ try {
198
+ $results = $this->client->remove($keys);
199
+ } catch (\CouchbaseException $e) {
200
+ return array_fill_keys($keys, false);
201
+ }
202
+
203
+ $success = array();
204
+ foreach ($results as $key => $result) {
205
+ $success[$key] = !$result->error;
206
+ }
207
+
208
+ return $success;
209
+ }
210
+
211
+ /**
212
+ * {@inheritdoc}
213
+ */
214
+ public function add($key, $value, $expire = 0)
215
+ {
216
+ $this->assertValidKey($key);
217
+
218
+ $value = $this->serialize($value);
219
+ try {
220
+ $result = $this->client->insert($key, $value, array('expiry' => $expire));
221
+ } catch (\CouchbaseException $e) {
222
+ return false;
223
+ }
224
+
225
+ $success = !$result->error;
226
+
227
+ // Couchbase is imprecise in its expiration handling, so we can clean up
228
+ // stuff that is already expired (assuming the `add` succeeded)
229
+ if ($success && $this->deleteIfExpired($key, $expire)) {
230
+ return true;
231
+ }
232
+
233
+ return $success;
234
+ }
235
+
236
+ /**
237
+ * {@inheritdoc}
238
+ */
239
+ public function replace($key, $value, $expire = 0)
240
+ {
241
+ $this->assertValidKey($key);
242
+
243
+ $value = $this->serialize($value);
244
+ try {
245
+ $result = $this->client->replace($key, $value, array('expiry' => $expire));
246
+ } catch (\CouchbaseException $e) {
247
+ return false;
248
+ }
249
+
250
+ $success = !$result->error;
251
+
252
+ // Couchbase is imprecise in its expiration handling, so we can clean up
253
+ // stuff that is already expired (assuming the `replace` succeeded)
254
+ if ($success && $this->deleteIfExpired($key, $expire)) {
255
+ return true;
256
+ }
257
+
258
+ return $success;
259
+ }
260
+
261
+ /**
262
+ * {@inheritdoc}
263
+ */
264
+ public function cas($token, $key, $value, $expire = 0)
265
+ {
266
+ $this->assertValidKey($key);
267
+
268
+ $value = $this->serialize($value);
269
+ try {
270
+ $result = $this->client->replace($key, $value, array('expiry' => $expire, 'cas' => $token));
271
+ } catch (\CouchbaseException $e) {
272
+ return false;
273
+ }
274
+
275
+ $success = !$result->error;
276
+
277
+ // Couchbase is imprecise in its expiration handling, so we can clean up
278
+ // stuff that is already expired (assuming the `cas` succeeded)
279
+ if ($success && $this->deleteIfExpired($key, $expire)) {
280
+ return true;
281
+ }
282
+
283
+ return $success;
284
+ }
285
+
286
+ /**
287
+ * {@inheritdoc}
288
+ */
289
+ public function increment($key, $offset = 1, $initial = 0, $expire = 0)
290
+ {
291
+ $this->assertValidKey($key);
292
+
293
+ if ($offset <= 0 || $initial < 0) {
294
+ return false;
295
+ }
296
+
297
+ return $this->doIncrement($key, $offset, $initial, $expire);
298
+ }
299
+
300
+ /**
301
+ * {@inheritdoc}
302
+ */
303
+ public function decrement($key, $offset = 1, $initial = 0, $expire = 0)
304
+ {
305
+ $this->assertValidKey($key);
306
+
307
+ if ($offset <= 0 || $initial < 0) {
308
+ return false;
309
+ }
310
+
311
+ return $this->doIncrement($key, -$offset, $initial, $expire);
312
+ }
313
+
314
+ /**
315
+ * {@inheritdoc}
316
+ */
317
+ public function touch($key, $expire)
318
+ {
319
+ $this->assertValidKey($key);
320
+
321
+ if ($this->deleteIfExpired($key, $expire)) {
322
+ return true;
323
+ }
324
+
325
+ try {
326
+ $result = $this->client->getAndTouch($key, $expire);
327
+ } catch (\CouchbaseException $e) {
328
+ return false;
329
+ }
330
+
331
+ return !$result->error;
332
+ }
333
+
334
+ /**
335
+ * {@inheritdoc}
336
+ */
337
+ public function flush()
338
+ {
339
+ // depending on config & client version, flush may not be available
340
+ try {
341
+ /*
342
+ * Flush wasn't always properly implemented[1] in the client, plus
343
+ * it depends on server config[2] to be enabled. Return status has
344
+ * been null in both success & failure cases.
345
+ * Flush is a very pervasive function that's likely not called
346
+ * lightly. Since it's probably more important to know whether or
347
+ * not it succeeded, than having it execute as fast as possible, I'm
348
+ * going to add some calls and test if flush succeeded.
349
+ *
350
+ * 1: https://forums.couchbase.com/t/php-flush-isnt-doing-anything/1886/8
351
+ * 2: http://docs.couchbase.com/admin/admin/CLI/CBcli/cbcli-bucket-flush.html
352
+ */
353
+ $this->client->upsert('cb-flush-tester', '');
354
+
355
+ $manager = $this->client->manager();
356
+ if (method_exists($manager, 'flush')) {
357
+ // ext-couchbase >= 2.0.6
358
+ $manager->flush();
359
+ } elseif (method_exists($this->client, 'flush')) {
360
+ // ext-couchbase < 2.0.6
361
+ $this->client->flush();
362
+ } else {
363
+ return false;
364
+ }
365
+ } catch (\CouchbaseException $e) {
366
+ return false;
367
+ }
368
+
369
+ try {
370
+ // cleanup in case flush didn't go through; but if it did, we won't
371
+ // be able to remove it and know flush succeeded
372
+ $result = $this->client->remove('cb-flush-tester');
373
+
374
+ return (bool) $result->error;
375
+ } catch (\CouchbaseException $e) {
376
+ // exception: "The key does not exist on the server"
377
+ return true;
378
+ }
379
+ }
380
+
381
+ /**
382
+ * {@inheritdoc}
383
+ */
384
+ public function getCollection($name)
385
+ {
386
+ return new Collection($this, $name);
387
+ }
388
+
389
+ /**
390
+ * We could use `$this->client->counter()`, but it doesn't seem to respect
391
+ * data types and stores the values as strings instead of integers.
392
+ *
393
+ * Shared between increment/decrement: both have mostly the same logic
394
+ * (decrement just increments a negative value), but need their validation
395
+ * split up (increment won't accept negative values).
396
+ *
397
+ * @param string $key
398
+ * @param int $offset
399
+ * @param int $initial
400
+ * @param int $expire
401
+ *
402
+ * @return int|bool
403
+ */
404
+ protected function doIncrement($key, $offset, $initial, $expire)
405
+ {
406
+ $this->assertValidKey($key);
407
+
408
+ $value = $this->get($key, $token);
409
+ if (false === $value) {
410
+ $success = $this->add($key, $initial, $expire);
411
+
412
+ return $success ? $initial : false;
413
+ }
414
+
415
+ if (!is_numeric($value) || $value < 0) {
416
+ return false;
417
+ }
418
+
419
+ $value += $offset;
420
+ // value can never be lower than 0
421
+ $value = max(0, $value);
422
+ $success = $this->cas($token, $key, $value, $expire);
423
+
424
+ return $success ? $value : false;
425
+ }
426
+
427
+ /**
428
+ * Couchbase doesn't properly remember the data type being stored:
429
+ * arrays and objects are turned into stdClass instances.
430
+ *
431
+ * @param mixed $value
432
+ *
433
+ * @return string|mixed
434
+ */
435
+ protected function serialize($value)
436
+ {
437
+ return (is_array($value) || is_object($value)) ? serialize($value) : $value;
438
+ }
439
+
440
+ /**
441
+ * Restore serialized data.
442
+ *
443
+ * @param mixed $value
444
+ *
445
+ * @return mixed|int|float
446
+ */
447
+ protected function unserialize($value)
448
+ {
449
+ $unserialized = @unserialize($value);
450
+
451
+ return false === $unserialized ? $value : $unserialized;
452
+ }
453
+
454
+ /**
455
+ * Couchbase seems to not timely purge items the way it should when
456
+ * storing it with an expired timestamp, so we'll detect that and
457
+ * delete it (instead of performing the already expired operation).
458
+ *
459
+ * @param string|string[] $key
460
+ * @param int $expire
461
+ *
462
+ * @return int TTL in seconds
463
+ */
464
+ protected function deleteIfExpired($key, $expire)
465
+ {
466
+ if ($expire < 0 || ($expire > 2592000 && $expire < time())) {
467
+ $this->deleteMulti((array) $key);
468
+
469
+ return true;
470
+ }
471
+
472
+ return false;
473
+ }
474
+
475
+ /**
476
+ * @param string $key
477
+ *
478
+ * @throws InvalidKey
479
+ */
480
+ protected function assertValidKey($key)
481
+ {
482
+ if (strlen($key) > 255) {
483
+ throw new InvalidKey("Invalid key: $key. Couchbase keys can not exceed 255 chars.");
484
+ }
485
+ }
486
+
487
+ /**
488
+ * Verify that the server is healthy.
489
+ *
490
+ * @throws ServerUnhealthy
491
+ */
492
+ protected function assertServerHealhy()
493
+ {
494
+ $info = $this->client->manager()->info();
495
+ foreach ($info['nodes'] as $node) {
496
+ if ('healthy' !== $node['status']) {
497
+ throw new ServerUnhealthy('Server isn\'t ready yet');
498
+ }
499
+ }
500
+ }
501
+ }
vendor/browscap-php/matthiasmullie/scrapbook/src/Adapters/Flysystem.php ADDED
@@ -0,0 +1,620 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace MatthiasMullie\Scrapbook\Adapters;
4
+
5
+ use League\Flysystem\FileExistsException;
6
+ use League\Flysystem\FileNotFoundException;
7
+ use League\Flysystem\Filesystem;
8
+ use League\Flysystem\UnableToDeleteFile;
9
+ use League\Flysystem\UnableToReadFile;
10
+ use League\Flysystem\UnableToWriteFile;
11
+ use MatthiasMullie\Scrapbook\Adapters\Collections\Flysystem as Collection;
12
+ use MatthiasMullie\Scrapbook\KeyValueStore;
13
+
14
+ /**
15
+ * Flysystem 1.x and 2.x adapter. Data will be written to League\Flysystem\Filesystem.
16
+ *
17
+ * Flysystem doesn't allow locking files, though. To guarantee interference from
18
+ * other processes, we'll create separate lock-files to flag a cache key in use.
19
+ *
20
+ * @author Matthias Mullie <scrapbook@mullie.eu>
21
+ * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
22
+ * @license LICENSE MIT
23
+ */
24
+ class Flysystem implements KeyValueStore
25
+ {
26
+ /**
27
+ * @var Filesystem
28
+ */
29
+ protected $filesystem;
30
+
31
+ /**
32
+ * @var int
33
+ */
34
+ protected $version;
35
+
36
+ public function __construct(Filesystem $filesystem)
37
+ {
38
+ $this->filesystem = $filesystem;
39
+ $this->version = class_exists('League\Flysystem\Local\LocalFilesystemAdapter') ? 2 : 1;
40
+ }
41
+
42
+ /**
43
+ * {@inheritdoc}
44
+ */
45
+ public function get($key, &$token = null)
46
+ {
47
+ $token = null;
48
+
49
+ // let expired-but-not-yet-deleted files be deleted first
50
+ if (!$this->exists($key)) {
51
+ return false;
52
+ }
53
+
54
+ $data = $this->read($key);
55
+ if (false === $data) {
56
+ return false;
57
+ }
58
+
59
+ $value = unserialize($data[1]);
60
+ $token = $data[1];
61
+
62
+ return $value;
63
+ }
64
+
65
+ /**
66
+ * {@inheritdoc}
67
+ */
68
+ public function getMulti(array $keys, array &$tokens = null)
69
+ {
70
+ $results = array();
71
+ $tokens = array();
72
+ foreach ($keys as $key) {
73
+ $token = null;
74
+ $value = $this->get($key, $token);
75
+
76
+ if (null !== $token) {
77
+ $results[$key] = $value;
78
+ $tokens[$key] = $token;
79
+ }
80
+ }
81
+
82
+ return $results;
83
+ }
84
+
85
+ /**
86
+ * {@inheritdoc}
87
+ */
88
+ public function set($key, $value, $expire = 0)
89
+ {
90
+ // we don't really need a lock for this operation, but we need to make
91
+ // sure it's not locked by another operation, which we could overwrite
92
+ if (!$this->lock($key)) {
93
+ return false;
94
+ }
95
+
96
+ $expire = $this->normalizeTime($expire);
97
+ if (0 !== $expire && $expire < time()) {
98
+ $this->unlock($key);
99
+
100
+ // don't waste time storing (and later comparing expiration
101
+ // timestamp) data that is already expired; just delete it already
102
+ return !$this->exists($key) || $this->delete($key);
103
+ }
104
+
105
+ $path = $this->path($key);
106
+ $data = $this->wrap($value, $expire);
107
+ try {
108
+ if (1 === $this->version) {
109
+ $this->filesystem->put($path, $data);
110
+ } else {
111
+ $this->filesystem->write($path, $data);
112
+ }
113
+ } catch (FileExistsException $e) {
114
+ // v1.x
115
+ $this->unlock($key);
116
+
117
+ return false;
118
+ } catch (UnableToWriteFile $e) {
119
+ // v2.x
120
+ $this->unlock($key);
121
+
122
+ return false;
123
+ }
124
+
125
+ return $this->unlock($key);
126
+ }
127
+
128
+ /**
129
+ * {@inheritdoc}
130
+ */
131
+ public function setMulti(array $items, $expire = 0)
132
+ {
133
+ $success = array();
134
+ foreach ($items as $key => $value) {
135
+ $success[$key] = $this->set($key, $value, $expire);
136
+ }
137
+
138
+ return $success;
139
+ }
140
+
141
+ /**
142
+ * {@inheritdoc}
143
+ */
144
+ public function delete($key)
145
+ {
146
+ if (!$this->exists($key)) {
147
+ return false;
148
+ }
149
+
150
+ if (!$this->lock($key)) {
151
+ return false;
152
+ }
153
+
154
+ $path = $this->path($key);
155
+
156
+ try {
157
+ $this->filesystem->delete($path);
158
+
159
+ return $this->unlock($key);
160
+ } catch (FileNotFoundException $e) {
161
+ // v1.x
162
+ $this->unlock($key);
163
+
164
+ return false;
165
+ } catch (UnableToDeleteFile $e) {
166
+ // v2.x
167
+ $this->unlock($key);
168
+
169
+ return false;
170
+ }
171
+ }
172
+
173
+ /**
174
+ * {@inheritdoc}
175
+ */
176
+ public function deleteMulti(array $keys)
177
+ {
178
+ $success = array();
179
+ foreach ($keys as $key) {
180
+ $success[$key] = $this->delete($key);
181
+ }
182
+
183
+ return $success;
184
+ }
185
+
186
+ /**
187
+ * {@inheritdoc}
188
+ */
189
+ public function add($key, $value, $expire = 0)
190
+ {
191
+ if (!$this->lock($key)) {
192
+ return false;
193
+ }
194
+
195
+ if ($this->exists($key)) {
196
+ $this->unlock($key);
197
+
198
+ return false;
199
+ }
200
+
201
+ $path = $this->path($key);
202
+ $data = $this->wrap($value, $expire);
203
+
204
+ try {
205
+ $this->filesystem->write($path, $data);
206
+
207
+ return $this->unlock($key);
208
+ } catch (FileExistsException $e) {
209
+ // v1.x
210
+ $this->unlock($key);
211
+
212
+ return false;
213
+ } catch (UnableToWriteFile $e) {
214
+ // v2.x
215
+ $this->unlock($key);
216
+
217
+ return false;
218
+ }
219
+ }
220
+
221
+ /**
222
+ * {@inheritdoc}
223
+ */
224
+ public function replace($key, $value, $expire = 0)
225
+ {
226
+ if (!$this->lock($key)) {
227
+ return false;
228
+ }
229
+
230
+ if (!$this->exists($key)) {
231
+ $this->unlock($key);
232
+
233
+ return false;
234
+ }
235
+
236
+ $path = $this->path($key);
237
+ $data = $this->wrap($value, $expire);
238
+
239
+ try {
240
+ if (1 === $this->version) {
241
+ $this->filesystem->update($path, $data);
242
+ } else {
243
+ $this->filesystem->write($path, $data);
244
+ }
245
+
246
+ return $this->unlock($key);
247
+ } catch (FileNotFoundException $e) {
248
+ // v1.x
249
+ $this->unlock($key);
250
+
251
+ return false;
252
+ } catch (UnableToWriteFile $e) {
253
+ // v2.x
254
+ $this->unlock($key);
255
+
256
+ return false;
257
+ }
258
+ }
259
+
260
+ /**
261
+ * {@inheritdoc}
262
+ */
263
+ public function cas($token, $key, $value, $expire = 0)
264
+ {
265
+ if (!$this->lock($key)) {
266
+ return false;
267
+ }
268
+
269
+ $current = $this->get($key);
270
+ if ($token !== serialize($current)) {
271
+ $this->unlock($key);
272
+
273
+ return false;
274
+ }
275
+
276
+ $path = $this->path($key);
277
+ $data = $this->wrap($value, $expire);
278
+
279
+ try {
280
+ if (1 === $this->version) {
281
+ $this->filesystem->update($path, $data);
282
+ } else {
283
+ $this->filesystem->write($path, $data);
284
+ }
285
+
286
+ return $this->unlock($key);
287
+ } catch (FileNotFoundException $e) {
288
+ // v1.x
289
+ $this->unlock($key);
290
+
291
+ return false;
292
+ } catch (UnableToWriteFile $e) {
293
+ // v2.x
294
+ $this->unlock($key);
295
+
296
+ return false;
297
+ }
298
+ }
299
+
300
+ /**
301
+ * {@inheritdoc}
302
+ */
303
+ public function increment($key, $offset = 1, $initial = 0, $expire = 0)
304
+ {
305
+ if ($offset <= 0 || $initial < 0) {
306
+ return false;
307
+ }
308
+
309
+ return $this->doIncrement($key, $offset, $initial, $expire);
310
+ }
311
+
312
+ /**
313
+ * {@inheritdoc}
314
+ */
315
+ public function decrement($key, $offset = 1, $initial = 0, $expire = 0)
316
+ {
317
+ if ($offset <= 0 || $initial < 0) {
318
+ return false;
319
+ }
320
+
321
+ return $this->doIncrement($key, -$offset, $initial, $expire);
322
+ }
323
+
324
+ /**
325
+ * {@inheritdoc}
326
+ */
327
+ public function touch($key, $expire)
328
+ {
329
+ if (!$this->lock($key)) {
330
+ return false;
331
+ }
332
+
333
+ $value = $this->get($key);
334
+ if (false === $value) {
335
+ $this->unlock($key);
336
+
337
+ return false;
338
+ }
339
+
340
+ $path = $this->path($key);
341
+ $data = $this->wrap($value, $expire);
342
+
343
+ try {
344
+ if (1 === $this->version) {
345
+ $this->filesystem->update($path, $data);
346
+ } else {
347
+ $this->filesystem->write($path, $data);
348
+ }
349
+
350
+ return $this->unlock($key);
351
+ } catch (FileNotFoundException $e) {
352
+ // v1.x
353
+ $this->unlock($key);
354
+
355
+ return false;
356
+ } catch (UnableToWriteFile $e) {
357
+ // v2.x
358
+ $this->unlock($key);
359
+
360
+ return false;
361
+ }
362
+ }
363
+
364
+ /**
365
+ * {@inheritdoc}
366
+ */
367
+ public function flush()
368
+ {
369
+ $files = $this->filesystem->listContents('.');
370
+ foreach ($files as $file) {
371
+ try {
372
+ if ('dir' === $file['type']) {
373
+ if (1 === $this->version) {
374
+ $this->filesystem->deleteDir($file['path']);
375
+ } else {
376
+ $this->filesystem->deleteDirectory($file['path']);
377
+ }
378
+ } else {
379
+ $this->filesystem->delete($file['path']);
380
+ }
381
+ } catch (FileNotFoundException $e) {
382
+ // v1.x
383
+ // don't care if we failed to unlink something, might have
384
+ // been deleted by another process in the meantime...
385
+ } catch (UnableToDeleteFile $e) {
386
+ // v2.x
387
+ // don't care if we failed to unlink something, might have
388
+ // been deleted by another process in the meantime...
389
+ }
390
+ }
391
+
392
+ return true;
393
+ }
394
+
395
+ /**
396
+ * {@inheritdoc}
397
+ */
398
+ public function getCollection($name)
399
+ {
400
+ /*
401
+ * A better solution could be to simply construct a new object for a
402
+ * subfolder, but we can't reliably create a new
403
+ * `League\Flysystem\Filesystem` object for a subfolder from the
404
+ * `$this->filesystem` object we have. I could `->getAdapter` and fetch
405
+ * the path from there, but only if we can assume that the adapter is
406
+ * `League\Flysystem\Adapter\Local` (1.x) or
407
+ * `League\Flysystem\Local\LocalFilesystemAdapter` (2.x), which it may not be.
408
+ * But I can just create a new object that changes the path to write at,
409
+ * by prefixing it with a subfolder!
410
+ */
411
+ if (1 === $this->version) {
412
+ $this->filesystem->createDir($name);
413
+ } else {
414
+ $this->filesystem->createDirectory($name);
415
+ }
416
+
417
+ return new Collection($this->filesystem, $name);
418
+ }
419
+
420
+ /**
421
+ * Shared between increment/decrement: both have mostly the same logic
422
+ * (decrement just increments a negative value), but need their validation
423
+ * split up (increment won't accept negative values).
424
+ *
425
+ * @param string $key
426
+ * @param int $offset
427
+ * @param int $initial
428
+ * @param int $expire
429
+ *
430
+ * @return int|bool
431
+ */
432
+ protected function doIncrement($key, $offset, $initial, $expire)
433
+ {
434
+ $current = $this->get($key, $token);
435
+ if (false === $current) {
436
+ $success = $this->add($key, $initial, $expire);
437
+
438
+ return $success ? $initial : false;
439
+ }
440
+
441
+ // NaN, doesn't compute
442
+ if (!is_numeric($current)) {
443
+ return false;
444
+ }
445
+
446
+ $value = $current + $offset;
447
+ $value = max(0, $value);
448
+
449
+ $success = $this->cas($token, $key, $value, $expire);
450
+
451
+ return $success ? $value : false;
452
+ }
453
+
454
+ /**
455
+ * @param string $key
456
+ *
457
+ * @return bool
458
+ */
459
+ protected function exists($key)
460
+ {
461
+ $data = $this->read($key);
462
+ if (false === $data) {
463
+ return false;
464
+ }
465
+
466
+ $expire = $data[0];
467
+ if (0 !== $expire && $expire < time()) {
468
+ // expired, don't keep it around
469
+ $path = $this->path($key);
470
+ $this->filesystem->delete($path);
471
+
472
+ return false;
473
+ }
474
+
475
+ return true;
476
+ }
477
+
478
+ /**
479
+ * Obtain a lock for a given key.
480
+ * It'll try to get a lock for a couple of times, but ultimately give up if
481
+ * no lock can be obtained in a reasonable time.
482
+ *
483
+ * @param string $key
484
+ *
485
+ * @return bool
486
+ */
487
+ protected function lock($key)
488
+ {
489
+ $path = md5($key).'.lock';
490
+
491
+ for ($i = 0; $i < 25; ++$i) {
492
+ try {
493
+ $this->filesystem->write($path, '');
494
+
495
+ return true;
496
+ } catch (FileExistsException $e) {
497
+ // v1.x
498
+ usleep(200);
499
+ } catch (UnableToWriteFile $e) {
500
+ // v2.x
501
+ usleep(200);
502
+ }
503
+ }
504
+
505
+ return false;
506
+ }
507
+
508
+ /**
509
+ * Release the lock for a given key.
510
+ *
511
+ * @param string $key
512
+ *
513
+ * @return bool
514
+ */
515
+ protected function unlock($key)
516
+ {
517
+ $path = md5($key).'.lock';
518
+ try {
519
+ $this->filesystem->delete($path);
520
+ } catch (FileNotFoundException $e) {
521
+ // v1.x
522
+ return false;
523
+ } catch (UnableToDeleteFile $e) {
524
+ // v2.x
525
+ return false;
526
+ }
527
+
528
+ return true;
529
+ }
530
+
531
+ /**
532
+ * Times can be:
533
+ * * relative (in seconds) to current time, within 30 days
534
+ * * absolute unix timestamp
535
+ * * 0, for infinity.
536
+ *
537
+ * The first case (relative time) will be normalized into a fixed absolute
538
+ * timestamp.
539
+ *
540
+ * @param int $time
541
+ *
542
+ * @return int
543
+ */
544
+ protected function normalizeTime($time)
545
+ {
546
+ // 0 = infinity
547
+ if (!$time) {
548
+ return 0;
549
+ }
550
+
551
+ // relative time in seconds, <30 days
552
+ if ($time < 30 * 24 * 60 * 60) {
553
+ $time += time();
554
+ }
555
+
556
+ return $time;
557
+ }
558
+
559
+ /**
560
+ * Build value, token & expiration time to be stored in cache file.
561
+ *
562
+ * @param string $value
563
+ * @param int $expire
564
+ *
565
+ * @return string
566
+ */
567
+ protected function wrap($value, $expire)
568
+ {
569
+ $expire = $this->normalizeTime($expire);
570
+
571
+ return $expire."\n".serialize($value);
572
+ }
573
+
574
+ /**
575
+ * Fetch stored data from cache file.
576
+ *
577
+ * @param string $key
578
+ *
579
+ * @return bool|array
580
+ */
581
+ protected function read($key)
582
+ {
583
+ $path = $this->path($key);
584
+ try {
585
+ $data = $this->filesystem->read($path);
586
+ } catch (FileNotFoundException $e) {
587
+ // v1.x
588
+ // unlikely given previous 'exists' check, but let's play safe...
589
+ // (outside process may have removed it since)
590
+ return false;
591
+ } catch (UnableToReadFile $e) {
592
+ // v2.x
593
+ // unlikely given previous 'exists' check, but let's play safe...
594
+ // (outside process may have removed it since)
595
+ return false;
596
+ }
597
+
598
+ if (false === $data) {
599
+ // in theory, a file could still be deleted between Flysystem's
600
+ // assertPresent & the time it actually fetched the content
601
+ // extremely unlikely though
602
+ return false;
603
+ }
604
+
605
+ $data = explode("\n", $data, 2);
606
+ $data[0] = (int) $data[0];
607
+
608
+ return $data;
609
+ }
610
+
611
+ /**
612
+ * @param string $key
613
+ *
614
+ * @return string
615
+ */
616
+ protected function path($key)
617
+ {
618
+ return md5($key).'.cache';
619
+ }
620
+ }
vendor/browscap-php/matthiasmullie/scrapbook/src/Adapters/Memcached.php ADDED
@@ -0,0 +1,446 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace MatthiasMullie\Scrapbook\Adapters;
4
+
5
+ use MatthiasMullie\Scrapbook\Adapters\Collections\Memcached as Collection;
6
+ use MatthiasMullie\Scrapbook\Exception\InvalidKey;
7
+ use MatthiasMullie\Scrapbook\Exception\OperationFailed;
8
+ use MatthiasMullie\Scrapbook\KeyValueStore;
9
+
10
+ /**
11
+ * Memcached adapter. Basically just a wrapper over \Memcached, but in an
12
+ * exchangeable (KeyValueStore) interface.
13
+ *
14
+ * @author Matthias Mullie <scrapbook@mullie.eu>
15
+ * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
16
+ * @license LICENSE MIT
17
+ */
18
+ class Memcached implements KeyValueStore
19
+ {
20
+ /**
21
+ * @var \Memcached
22
+ */
23
+ protected $client;
24
+
25
+ public function __construct(\Memcached $client)
26
+ {
27
+ $this->client = $client;
28
+ }
29
+
30
+ /**
31
+ * {@inheritdoc}
32
+ */
33
+ public function get($key, &$token = null)
34
+ {
35
+ /**
36
+ * Wouldn't it be awesome if I just used the obvious method?
37
+ *
38
+ * I'm going to use getMulti() instead of get() because the latter is
39
+ * flawed in earlier versions, where it was known to mess up some
40
+ * operations that are followed by it (increment/decrement have been
41
+ * reported, also seen it make CAS return result unreliable)
42
+ *
43
+ * @see https://github.com/php-memcached-dev/php-memcached/issues/21
44
+ */
45
+ $values = $this->getMulti(array($key), $tokens);
46
+
47
+ if (!isset($values[$key])) {
48
+ $token = null;
49
+
50
+ return false;
51
+ }
52
+
53
+ $token = $tokens[$key];
54
+
55
+ return $values[$key];
56
+ }
57
+
58
+ /**
59
+ * {@inheritdoc}
60
+ */
61
+ public function getMulti(array $keys, array &$tokens = null)
62
+ {
63
+ $tokens = array();
64
+ if (empty($keys)) {
65
+ return array();
66
+ }
67
+
68
+ $keys = array_map(array($this, 'encode'), $keys);
69
+
70
+ if (defined('\Memcached::GET_EXTENDED')) {
71
+ $return = $this->client->getMulti($keys, \Memcached::GET_EXTENDED);
72
+ $this->throwExceptionOnClientCallFailure($return);
73
+ foreach ($return as $key => $value) {
74
+ // once PHP<5.5 support is dropped, just use array_column
75
+ $tokens[$key] = $value['cas'];
76
+ $return[$key] = $value['value'];
77
+ }
78
+ } else {
79
+ $return = $this->client->getMulti($keys, $tokens);
80
+ $this->throwExceptionOnClientCallFailure($return);
81
+ }
82
+
83
+ $keys = array_map(array($this, 'decode'), array_keys($return));
84
+ $return = array_combine($keys, $return);
85
+
86
+ // HHVMs getMulti() returns null instead of empty array for no results,
87
+ // so normalize that
88
+ $tokens = $tokens ?: array();
89
+ $tokens = array_combine($keys, $tokens);
90
+
91
+ return $return ?: array();
92
+ }
93
+
94
+ /**
95
+ * {@inheritdoc}
96
+ */
97
+ public function set($key, $value, $expire = 0)
98
+ {
99
+ // Memcached seems to not timely purge items the way it should when
100
+ // storing it with an expired timestamp
101
+ if ($expire < 0 || ($expire > 2592000 && $expire < time())) {
102
+ $this->delete($key);
103
+
104
+ return true;
105
+ }
106
+
107
+ $key = $this->encode($key);
108
+
109
+ return $this->client->set($key, $value, $expire);
110
+ }
111
+
112
+ /**
113
+ * {@inheritdoc}
114
+ */
115
+ public function setMulti(array $items, $expire = 0)
116
+ {
117
+ if (empty($items)) {
118
+ return array();
119
+ }
120
+
121
+ // Memcached seems to not timely purge items the way it should when
122
+ // storing it with an expired timestamp
123
+ if ($expire < 0 || ($expire > 2592000 && $expire < time())) {
124
+ $keys = array_keys($items);
125
+ $this->deleteMulti($keys);
126
+
127
+ return array_fill_keys($keys, true);
128
+ }
129
+
130
+ if (defined('HHVM_VERSION')) {
131
+ $nums = array_filter(array_keys($items), 'is_numeric');
132
+ if (!empty($nums)) {
133
+ return $this->setMultiNumericItemsForHHVM($items, $nums, $expire);
134
+ }
135
+ }
136
+
137
+ $keys = array_map(array($this, 'encode'), array_keys($items));
138
+ $items = array_combine($keys, $items);
139
+ $success = $this->client->setMulti($items, $expire);
140
+ $keys = array_map(array($this, 'decode'), array_keys($items));
141
+
142
+ return array_fill_keys($keys, $success);
143
+ }
144
+
145
+ /**
146
+ * {@inheritdoc}
147
+ */
148
+ public function delete($key)
149
+ {
150
+ $key = $this->encode($key);
151
+
152
+ return $this->client->delete($key);
153
+ }
154
+
155
+ /**
156
+ * {@inheritdoc}
157
+ */
158
+ public function deleteMulti(array $keys)
159
+ {
160
+ if (empty($keys)) {
161
+ return array();
162
+ }
163
+
164
+ if (!method_exists($this->client, 'deleteMulti')) {
165
+ /**
166
+ * HHVM didn't always support deleteMulti, so I'll hack around it by
167
+ * setting all items expired.
168
+ * I could also delete() all items one by one, but that would
169
+ * probably take more network requests (this version always takes 2).
170
+ *
171
+ * @see http://docs.hhvm.com/manual/en/memcached.deletemulti.php
172
+ */
173
+ $values = $this->getMulti($keys);
174
+
175
+ $keys = array_map(array($this, 'encode'), array_keys($values));
176
+ $this->client->setMulti(array_fill_keys($keys, ''), time() - 1);
177
+
178
+ $return = array();
179
+ foreach ($keys as $key) {
180
+ $key = $this->decode($key);
181
+ $return[$key] = array_key_exists($key, $values);
182
+ }
183
+
184
+ return $return;
185
+ }
186
+
187
+ $keys = array_map(array($this, 'encode'), $keys);
188
+ $result = (array) $this->client->deleteMulti($keys);
189
+ $keys = array_map(array($this, 'decode'), array_keys($result));
190
+ $result = array_combine($keys, $result);
191
+
192
+ /*
193
+ * Contrary to docs (http://php.net/manual/en/memcached.deletemulti.php)
194
+ * deleteMulti returns an array of [key => true] (for successfully
195
+ * deleted values) and [key => error code] (for failures)
196
+ * Pretty good because I want an array of true/false, so I'll just have
197
+ * to replace the error codes by falses.
198
+ */
199
+ foreach ($result as $key => $status) {
200
+ $result[$key] = true === $status;
201
+ }
202
+
203
+ return $result;
204
+ }
205
+
206
+ /**
207
+ * {@inheritdoc}
208
+ */
209
+ public function add($key, $value, $expire = 0)
210
+ {
211
+ $key = $this->encode($key);
212
+
213
+ return $this->client->add($key, $value, $expire);
214
+ }
215
+
216
+ /**
217
+ * {@inheritdoc}
218
+ */
219
+ public function replace($key, $value, $expire = 0)
220
+ {
221
+ $key = $this->encode($key);
222
+
223
+ return $this->client->replace($key, $value, $expire);
224
+ }
225
+
226
+ /**
227
+ * {@inheritdoc}
228
+ */
229
+ public function cas($token, $key, $value, $expire = 0)
230
+ {
231
+ if (!is_float($token) && !is_int($token)) {
232
+ return false;
233
+ }
234
+
235
+ $key = $this->encode($key);
236
+
237
+ return $this->client->cas($token, $key, $value, $expire);
238
+ }
239
+
240
+ /**
241
+ * {@inheritdoc}
242
+ */
243
+ public function increment($key, $offset = 1, $initial = 0, $expire = 0)
244
+ {
245
+ if ($offset <= 0 || $initial < 0) {
246
+ return false;
247
+ }
248
+
249
+ /*
250
+ * Not doing \Memcached::increment because that one:
251
+ * * needs \Memcached::OPT_BINARY_PROTOCOL == true
252
+ * * is prone to errors after a flush ("merges" with pruned data) in at
253
+ * least some particular versions of Memcached
254
+ */
255
+ return $this->doIncrement($key, $offset, $initial, $expire);
256
+ }
257
+
258
+ /**
259
+ * {@inheritdoc}
260
+ */
261
+ public function decrement($key, $offset = 1, $initial = 0, $expire = 0)
262
+ {
263
+ if ($offset <= 0 || $initial < 0) {
264
+ return false;
265
+ }
266
+
267
+ /*
268
+ * Not doing \Memcached::decrement for the reasons described in:
269
+ * @see increment()
270
+ */
271
+ return $this->doIncrement($key, -$offset, $initial, $expire);
272
+ }
273
+
274
+ /**
275
+ * {@inheritdoc}
276
+ */
277
+ public function touch($key, $expire)
278
+ {
279
+ /*
280
+ * Since \Memcached has no reliable touch(), we might as well take an
281
+ * easy approach where we can. If TTL is expired already, just delete
282
+ * the key - this only needs 1 request.
283
+ */
284
+ if ($expire < 0 || ($expire > 2592000 && $expire < time())) {
285
+ return $this->delete($key);
286
+ }
287
+
288
+ /**
289
+ * HHVM doesn't support touch.
290
+ *
291
+ * @see http://docs.hhvm.com/manual/en/memcached.touch.php
292
+ *
293
+ * PHP does, but only with \Memcached::OPT_BINARY_PROTOCOL == true,
294
+ * and even then, it appears to be buggy on particular versions of
295
+ * Memcached.
296
+ *
297
+ * I'll just work around it!
298
+ */
299
+ $value = $this->get($key, $token);
300
+
301
+ return $this->cas($token, $key, $value, $expire);
302
+ }
303
+
304
+ /**
305
+ * {@inheritdoc}
306
+ */
307
+ public function flush()
308
+ {
309
+ return $this->client->flush();
310
+ }
311
+
312
+ /**
313
+ * {@inheritdoc}
314
+ */
315
+ public function getCollection($name)
316
+ {
317
+ return new Collection($this, $name);
318
+ }
319
+
320
+ /**
321
+ * Shared between increment/decrement: both have mostly the same logic
322
+ * (decrement just increments a negative value), but need their validation
323
+ * split up (increment won't accept negative values).
324
+ *
325
+ * @param string $key
326
+ * @param int $offset
327
+ * @param int $initial
328
+ * @param int $expire
329
+ *
330
+ * @return int|bool
331
+ */
332
+ protected function doIncrement($key, $offset, $initial, $expire)
333
+ {
334
+ $value = $this->get($key, $token);
335
+ if (false === $value) {
336
+ $success = $this->add($key, $initial, $expire);
337
+
338
+ return $success ? $initial : false;
339
+ }
340
+
341
+ if (!is_numeric($value) || $value < 0) {
342
+ return false;
343
+ }
344
+
345
+ $value += $offset;
346
+ // value can never be lower than 0
347
+ $value = max(0, $value);
348
+ $key = $this->encode($key);
349
+ $success = $this->client->cas($token, $key, $value, $expire);
350
+
351
+ return $success ? $value : false;
352
+ }
353
+
354
+ /**
355
+ * Encode a key for use on the wire inside the memcached protocol.
356
+ *
357
+ * We encode spaces and line breaks to avoid protocol errors. We encode
358
+ * the other control characters for compatibility with libmemcached
359
+ * verify_key. We leave other punctuation alone, to maximise backwards
360
+ * compatibility.
361
+ *
362
+ * @see https://github.com/wikimedia/mediawiki/commit/be76d869#diff-75b7c03970b5e43de95ff95f5faa6ef1R100
363
+ * @see https://github.com/wikimedia/mediawiki/blob/master/includes/libs/objectcache/MemcachedBagOStuff.php#L116
364
+ *
365
+ * @param string $key
366
+ *
367
+ * @return string
368
+ *
369
+ * @throws InvalidKey
370
+ */
371
+ protected function encode($key)
372
+ {
373
+ $regex = '/[^\x21\x22\x24\x26-\x39\x3b-\x7e]+/';
374
+ $key = preg_replace_callback($regex, function ($match) {
375
+ return rawurlencode($match[0]);
376
+ }, $key);
377
+
378
+ if (strlen($key) > 255) {
379
+ throw new InvalidKey("Invalid key: $key. Encoded Memcached keys can not exceed 255 chars.");
380
+ }
381
+
382
+ return $key;
383
+ }
384
+
385
+ /**
386
+ * Decode a key encoded with encode().
387
+ *
388
+ * @param string $key
389
+ *
390
+ * @return string
391
+ */
392
+ protected function decode($key)
393
+ {
394
+ // matches %20, %7F, ... but not %21, %22, ...
395
+ // (=the encoded versions for those encoded in encode)
396
+ $regex = '/%(?!2[1246789]|3[0-9]|3[B-F]|[4-6][0-9A-F]|5[0-9A-E])[0-9A-Z]{2}/i';
397
+
398
+ return preg_replace_callback($regex, function ($match) {
399
+ return rawurldecode($match[0]);
400
+ }, $key);
401
+ }
402
+
403
+ /**
404
+ * Numerical strings turn into integers when used as array keys, and
405
+ * HHVM (used to) reject(s) such cache keys.
406
+ *
407
+ * @see https://github.com/facebook/hhvm/pull/7654
408
+ *
409
+ * @param int $expire
410
+ *
411
+ * @return array
412
+ */
413
+ protected function setMultiNumericItemsForHHVM(array $items, array $nums, $expire = 0)
414
+ {
415
+ $success = array();
416
+ $nums = array_intersect_key($items, array_fill_keys($nums, null));
417
+ foreach ($nums as $k => $v) {
418
+ $success[$k] = $this->set((string) $k, $v, $expire);
419
+ }
420
+
421
+ $remaining = array_diff_key($items, $nums);
422
+ if ($remaining) {
423
+ $success += $this->setMulti($remaining, $expire);
424
+ }
425
+
426
+ return $success;
427
+ }
428
+
429
+ /**
430
+ * Will throw an exception if the returned result from a Memcached call
431
+ * indicates a failure in the operation.
432
+ * The exception will contain debug information about the failure.
433
+ *
434
+ * @param mixed $result
435
+ *
436
+ * @throws OperationFailed
437
+ */
438
+ protected function throwExceptionOnClientCallFailure($result)
439
+ {
440
+ if (false !== $result) {
441
+ return;
442
+ }
443
+
444
+ throw new OperationFailed($this->client->getResultMessage(), $this->client->getResultCode());
445
+ }
446
+ }
vendor/browscap-php/matthiasmullie/scrapbook/src/Adapters/MemoryStore.php ADDED
@@ -0,0 +1,388 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace MatthiasMullie\Scrapbook\Adapters;
4
+
5
+ use MatthiasMullie\Scrapbook\Adapters\Collections\MemoryStore as Collection;
6
+ use MatthiasMullie\Scrapbook\KeyValueStore;
7
+
8
+ /**
9
+ * No-storage cache: all values will be "cached" in memory, in a simple PHP
10
+ * array. Values will only be valid for 1 request: whatever is in memory at the
11
+ * end of the request just dies. Other requests will start from a blank slate.
12
+ *
13
+ * This is mainly useful for testing purposes, where this class can let you test
14
+ * application logic against cache, without having to run a cache server.
15
+ *
16
+ * @author Matthias Mullie <scrapbook@mullie.eu>
17
+ * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
18
+ * @license LICENSE MIT
19
+ */
20
+ class MemoryStore implements KeyValueStore
21
+ {
22
+ /**
23
+ * @var array
24
+ */
25
+ protected $items = array();
26
+
27
+ /**
28
+ * @var int
29
+ */
30
+ protected $limit = 0;
31
+
32
+ /**
33
+ * @var int
34
+ */
35
+ protected $size = 0;
36
+
37
+ /**
38
+ * @param int|string $limit Memory limit in bytes (defaults to 10% of memory_limit)
39
+ */
40
+ public function __construct($limit = null)
41
+ {
42
+ if (null === $limit) {
43
+ $phpLimit = ini_get('memory_limit');
44
+ if ($phpLimit <= 0) {
45
+ $this->limit = PHP_INT_MAX;
46
+ } else {
47
+ $this->limit = (int) ($this->shorthandToBytes($phpLimit) / 10);
48
+ }
49
+ } else {
50
+ $this->limit = $this->shorthandToBytes($limit);
51
+ }
52
+ }
53
+
54
+ /**
55
+ * {@inheritdoc}
56
+ */
57
+ public function get($key, &$token = null)
58
+ {
59
+ if (!$this->exists($key)) {
60
+ $token = null;
61
+
62
+ return false;
63
+ }
64
+
65
+ $value = $this->items[$key][0];
66
+
67
+ // use serialized version of stored value as CAS token
68
+ $token = $value;
69
+
70
+ return unserialize($value);
71
+ }
72
+
73
+ /**
74
+ * {@inheritdoc}
75
+ */
76
+ public function getMulti(array $keys, array &$tokens = null)
77
+ {
78
+ $items = array();
79
+ $tokens = array();
80
+
81
+ foreach ($keys as $key) {
82
+ if (!$this->exists($key)) {
83
+ // omit missing keys from return array
84
+ continue;
85
+ }
86
+
87
+ $items[$key] = $this->get($key, $token);
88
+ $tokens[$key] = $token;
89
+ }
90
+
91
+ return $items;
92
+ }
93
+
94
+ /**
95
+ * {@inheritdoc}
96
+ */
97
+ public function set($key, $value, $expire = 0)
98
+ {
99
+ $this->size -= isset($this->items[$key]) ? strlen($this->items[$key][0]) : 0;
100
+
101
+ $value = serialize($value);
102
+ $expire = $this->normalizeTime($expire);
103
+ $this->items[$key] = array($value, $expire);
104
+
105
+ $this->size += strlen($value);
106
+ $this->lru($key);
107
+ $this->evict();
108
+
109
+ return true;
110
+ }
111
+
112
+ /**
113
+ * {@inheritdoc}
114
+ */
115
+ public function setMulti(array $items, $expire = 0)
116
+ {
117
+ $success = array();
118
+ foreach ($items as $key => $value) {
119
+ $success[$key] = $this->set($key, $value, $expire);
120
+ }
121
+
122
+ return $success;
123
+ }
124
+
125
+ /**
126
+ * {@inheritdoc}
127
+ */
128
+ public function delete($key)
129
+ {
130
+ $exists = $this->exists($key);
131
+
132
+ if ($exists) {
133
+ $this->size -= strlen($this->items[$key][0]);
134
+ unset($this->items[$key]);
135
+ }
136
+
137
+ return $exists;
138
+ }
139
+
140
+ /**
141
+ * {@inheritdoc}
142
+ */
143
+ public function deleteMulti(array $keys)
144
+ {
145
+ $success = array();
146
+
147
+ foreach ($keys as $key) {
148
+ $success[$key] = $this->delete($key);
149
+ }
150
+
151
+ return $success;
152
+ }
153
+
154
+ /**
155
+ * {@inheritdoc}
156
+ */
157
+ public function add($key, $value, $expire = 0)
158
+ {
159
+ if ($this->exists($key)) {
160
+ return false;
161
+ }
162
+
163
+ return $this->set($key, $value, $expire);
164
+ }
165
+
166
+ /**
167
+ * {@inheritdoc}
168
+ */
169
+ public function replace($key, $value, $expire = 0)
170
+ {
171
+ if (!$this->exists($key)) {
172
+ return false;
173
+ }
174
+
175
+ return $this->set($key, $value, $expire);
176
+ }
177
+
178
+ /**
179
+ * {@inheritdoc}
180
+ */
181
+ public function cas($token, $key, $value, $expire = 0)
182
+ {
183
+ if (!$this->exists($key)) {
184
+ return false;
185
+ }
186
+
187
+ $this->get($key, $comparison);
188
+ if ($comparison !== $token) {
189
+ return false;
190
+ }
191
+
192
+ return $this->set($key, $value, $expire);
193
+ }
194
+
195
+ /**
196
+ * {@inheritdoc}
197
+ */
198
+ public function increment($key, $offset = 1, $initial = 0, $expire = 0)
199
+ {
200
+ if ($offset <= 0 || $initial < 0) {
201
+ return false;
202
+ }
203
+
204
+ return $this->doIncrement($key, $offset, $initial, $expire);
205
+ }
206
+
207
+ /**
208
+ * {@inheritdoc}
209
+ */
210
+ public function decrement($key, $offset = 1, $initial = 0, $expire = 0)
211
+ {
212
+ if ($offset <= 0 || $initial < 0) {
213
+ return false;
214
+ }
215
+
216
+ return $this->doIncrement($key, -$offset, $initial, $expire);
217
+ }
218
+
219
+ /**
220
+ * {@inheritdoc}
221
+ */
222
+ public function touch($key, $expire)
223
+ {
224
+ $expire = $this->normalizeTime($expire);
225
+
226
+ // get current value & re-save it, with new expiration
227
+ $value = $this->get($key, $token);
228
+
229
+ return $this->cas($token, $key, $value, $expire);
230
+ }
231
+
232
+ /**
233
+ * {@inheritdoc}
234
+ */
235
+ public function flush()
236
+ {
237
+ $this->items = array();
238
+ $this->size = 0;
239
+
240
+ return true;
241
+ }
242
+
243
+ /**
244
+ * {@inheritdoc}
245
+ */
246
+ public function getCollection($name)
247
+ {
248
+ return new Collection($this, $name);
249
+ }
250
+
251
+ /**
252
+ * Checks if a value exists in cache and is not yet expired.
253
+ *
254
+ * @param string $key
255
+ *
256
+ * @return bool
257
+ */
258
+ protected function exists($key)
259
+ {
260
+ if (!array_key_exists($key, $this->items)) {
261
+ // key not in cache
262
+ return false;
263
+ }
264
+
265
+ $expire = $this->items[$key][1];
266
+ if (0 !== $expire && $expire < time()) {
267
+ // not permanent & already expired
268
+ $this->size -= strlen($this->items[$key][0]);
269
+ unset($this->items[$key]);
270
+
271
+ return false;
272
+ }
273
+
274
+ $this->lru($key);
275
+
276
+ return true;
277
+ }
278
+
279
+ /**
280
+ * Shared between increment/decrement: both have mostly the same logic
281
+ * (decrement just increments a negative value), but need their validation
282
+ * split up (increment won't accept negative values).
283
+ *
284
+ * @param string $key
285
+ * @param int $offset
286
+ * @param int $initial
287
+ * @param int $expire
288
+ *
289
+ * @return int|bool
290
+ */
291
+ protected function doIncrement($key, $offset, $initial, $expire)
292
+ {
293
+ if (!$this->exists($key)) {
294
+ $this->set($key, $initial, $expire);
295
+
296
+ return $initial;
297
+ }
298
+
299
+ $value = $this->get($key);
300
+ if (!is_numeric($value) || $value < 0) {
301
+ return false;
302
+ }
303
+
304
+ $value += $offset;
305
+ // value can never be lower than 0
306
+ $value = max(0, $value);
307
+ $this->set($key, $value, $expire);
308
+
309
+ return $value;
310
+ }
311
+
312
+ /**
313
+ * Times can be:
314
+ * * relative (in seconds) to current time, within 30 days
315
+ * * absolute unix timestamp
316
+ * * 0, for infinity.
317
+ *
318
+ * The first case (relative time) will be normalized into a fixed absolute
319
+ * timestamp.
320
+ *
321
+ * @param int $time
322
+ *
323
+ * @return int
324
+ */
325
+ protected function normalizeTime($time)
326
+ {
327
+ // 0 = infinity
328
+ if (!$time) {
329
+ return 0;
330
+ }
331
+
332
+ // relative time in seconds, <30 days
333
+ if ($time < 30 * 24 * 60 * 60) {
334
+ $time += time();
335
+ }
336
+
337
+ return $time;
338
+ }
339
+
340
+ /**
341
+ * This cache uses least recently used algorithm. This is to be called
342
+ * with the key to be marked as just used.
343
+ */
344
+ protected function lru($key)
345
+ {
346
+ // move key that has just been used to last position in the array
347
+ $value = $this->items[$key];
348
+ unset($this->items[$key]);
349
+ $this->items[$key] = $value;
350
+ }
351
+
352
+ /**
353
+ * Least recently used cache values will be evicted from cache should
354
+ * it fill up too much.
355
+ */
356
+ protected function evict()
357
+ {
358
+ while ($this->size > $this->limit && !empty($this->items)) {
359
+ $item = array_shift($this->items);
360
+ $this->size -= strlen($item[0]);
361
+ }
362
+ }
363
+
364
+ /**
365
+ * Understands shorthand byte values (as used in e.g. memory_limit ini
366
+ * setting) and converts them into bytes.
367
+ *
368
+ * @see http://php.net/manual/en/faq.using.php#faq.using.shorthandbytes
369
+ *
370
+ * @param string|int $shorthand Amount of bytes (int) or shorthand value (e.g. 512M)
371
+ *
372
+ * @return int
373
+ */
374
+ protected function shorthandToBytes($shorthand)
375
+ {
376
+ if (is_numeric($shorthand)) {
377
+ // make sure that when float(1.234E17) is passed in, it doesn't get
378
+ // cast to string('1.234E17'), then to int(1)
379
+ return $shorthand;
380
+ }
381
+
382
+ $units = array('B' => 1024, 'M' => pow(1024, 2), 'G' => pow(1024, 3));
383
+
384
+ return (int) preg_replace_callback('/^([0-9]+)('.implode('|', array_keys($units)).')$/', function ($match) use ($units) {
385
+ return $match[1] * $units[$match[2]];
386
+ }, $shorthand);
387
+ }
388
+ }
vendor/browscap-php/matthiasmullie/scrapbook/src/Adapters/MySQL.php ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace MatthiasMullie\Scrapbook\Adapters;
4
+
5
+ /**
6
+ * MySQL adapter. Basically just a wrapper over \PDO, but in an exchangeable
7
+ * (KeyValueStore) interface.
8
+ *
9
+ * @author Matthias Mullie <scrapbook@mullie.eu>
10
+ * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
11
+ * @license LICENSE MIT
12
+ */
13
+ class MySQL extends SQL
14
+ {
15
+ /**
16
+ * {@inheritdoc}
17
+ */
18
+ public function set($key, $value, $expire = 0)
19
+ {
20
+ $value = $this->serialize($value);
21
+ $expire = $this->expire($expire);
22
+
23
+ $this->clearExpired();
24
+
25
+ $statement = $this->client->prepare(
26
+ "REPLACE INTO $this->table (k, v, e)
27
+ VALUES (:key, :value, :expire)"
28
+ );
29
+
30
+ $statement->execute(array(
31
+ ':key' => $key,
32
+ ':value' => $value,
33
+ ':expire' => $expire,
34
+ ));
35
+
36
+ // 1 = insert; 2 = update
37
+ return 1 === $statement->rowCount() || 2 === $statement->rowCount();
38
+ }
39
+
40
+ /**
41
+ * {@inheritdoc}
42
+ */
43
+ public function setMulti(array $items, $expire = 0)
44
+ {
45
+ if (empty($items)) {
46
+ return array();
47
+ }
48
+
49
+ $i = 1;
50
+ $query = array();
51
+ $params = array();
52
+ $expire = $this->expire($expire);
53
+
54
+ $this->clearExpired();
55
+
56
+ foreach ($items as $key => $value) {
57
+ $value = $this->serialize($value);
58
+
59
+ $query[] = "(:key$i, :value$i, :expire$i)";
60
+ $params += array(
61
+ ":key$i" => $key,
62
+ ":value$i" => $value,
63
+ ":expire$i" => $expire,
64
+ );
65
+
66
+ ++$i;
67
+ }
68
+
69
+ $statement = $this->client->prepare(
70
+ "REPLACE INTO $this->table (k, v, e)
71
+ VALUES ".implode(',', $query)
72
+ );
73
+
74
+ $statement->execute($params);
75
+
76
+ /*
77
+ * As far as I can tell, there are no conditions under which this can go
78
+ * wrong (if item exists or not, REPLACE INTO will work either way),
79
+ * except for connection problems, in which case all or none will be
80
+ * stored.
81
+ * Can't compare with count($items) because rowCount could be 1 or 2,
82
+ * depending on if REPLACE was an INSERT or UPDATE.
83
+ */
84
+ $success = $statement->rowCount() > 0;
85
+
86
+ return array_fill_keys(array_keys($items), $success);
87
+ }
88
+
89
+ /**
90
+ * {@inheritdoc}
91
+ */
92
+ public function add($key, $value, $expire = 0)
93
+ {
94
+ $value = $this->serialize($value);
95
+ $expire = $this->expire($expire);
96
+
97
+ $this->clearExpired();
98
+
99
+ // MySQL-specific way to ignore insert-on-duplicate errors
100
+ $statement = $this->client->prepare(
101
+ "INSERT IGNORE INTO $this->table (k, v, e)
102
+ VALUES (:key, :value, :expire)"
103
+ );
104
+
105
+ $statement->execute(array(
106
+ ':key' => $key,
107
+ ':value' => $value,
108
+ ':expire' => $expire,
109
+ ));
110
+
111
+ return 1 === $statement->rowCount();
112
+ }
113
+
114
+ /**
115
+ * {@inheritdoc}
116
+ */
117
+ public function flush()
118
+ {
119
+ return false !== $this->client->exec("TRUNCATE TABLE $this->table");
120
+ }
121
+
122
+ /**
123
+ * {@inheritdoc}
124
+ */
125
+ protected function init()
126
+ {
127
+ $this->client->exec(
128
+ "CREATE TABLE IF NOT EXISTS $this->table (
129
+ k VARBINARY(1000) NOT NULL PRIMARY KEY,
130
+ v LONGBLOB,
131
+ e TIMESTAMP NULL DEFAULT NULL,
132
+ KEY e (e)
133
+ )"
134
+ );
135
+ }
136
+ }
vendor/browscap-php/matthiasmullie/scrapbook/src/Adapters/PostgreSQL.php ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace MatthiasMullie\Scrapbook\Adapters;
4
+
5
+ use PDO;
6
+
7
+ /**
8
+ * PostgreSQL adapter. Basically just a wrapper over \PDO, but in an
9
+ * exchangeable (KeyValueStore) interface.
10
+ *
11
+ * @author Matthias Mullie <scrapbook@mullie.eu>
12
+ * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
13
+ * @license LICENSE MIT
14
+ */
15
+ class PostgreSQL extends SQL
16
+ {
17
+ /**
18
+ * @var bool
19
+ */
20
+ protected $conflictSupport = true;
21
+
22
+ /**
23
+ * {@inheritdoc}
24
+ */
25
+ public function flush()
26
+ {
27
+ return false !== $this->client->exec("TRUNCATE TABLE $this->table");
28
+ }
29
+
30
+ /**
31
+ * {@inheritdoc}
32
+ */
33
+ public function set($key, $value, $expire = 0)
34
+ {
35
+ if (!$this->conflictSupport) {
36
+ return parent::set($key, $value, $expire);
37
+ }
38
+
39
+ $this->clearExpired();
40
+
41
+ $serialized = $this->serialize($value);
42
+ $expiration = $this->expire($expire);
43
+
44
+ $statement = $this->client->prepare(
45
+ "INSERT INTO $this->table (k, v, e)
46
+ VALUES (:key, :value, :expire)
47
+ ON CONFLICT (k) DO UPDATE SET v=EXCLUDED.v, e=EXCLUDED.e"
48
+ );
49
+
50
+ $statement->bindParam(':key', $key);
51
+ $statement->bindParam(':value', $serialized, PDO::PARAM_LOB, strlen($serialized));
52
+ $statement->bindParam(':expire', $expiration);
53
+ $statement->execute();
54
+
55
+ // ON CONFLICT is not supported in versions < 9.5, in which case we'll
56
+ // have to fall back on add/replace
57
+ if ('42601' === $statement->errorCode()) {
58
+ $this->conflictSupport = false;
59
+
60
+ return $this->set($key, $value, $expire);
61
+ }
62
+
63
+ return 1 === $statement->rowCount();
64
+ }
65
+
66
+ /**
67
+ * {@inheritdoc}
68
+ */
69
+ public function get($key, &$token = null)
70
+ {
71
+ $return = parent::get($key, $token);
72
+
73
+ if (null !== $token) {
74
+ // BYTEA data return streams - we actually need the data in
75
+ // serialized format, not some silly stream
76
+ $token = $this->serialize($return);
77
+ }
78
+
79
+ return $return;
80
+ }
81
+
82
+ /**
83
+ * {@inheritdoc}
84
+ */
85
+ public function getMulti(array $keys, array &$tokens = null)
86
+ {
87
+ $return = parent::getMulti($keys, $tokens);
88
+
89
+ foreach ($return as $key => $value) {
90
+ // BYTEA data return streams - we actually need the data in
91
+ // serialized format, not some silly stream
92
+ $tokens[$key] = $this->serialize($value);
93
+ }
94
+
95
+ return $return;
96
+ }
97
+
98
+ /**
99
+ * {@inheritdoc}
100
+ */
101
+ protected function init()
102
+ {
103
+ $this->client->exec(
104
+ "CREATE TABLE IF NOT EXISTS $this->table (
105
+ k VARCHAR NOT NULL PRIMARY KEY,
106
+ v BYTEA,
107
+ e TIMESTAMP NULL DEFAULT NULL
108
+ )"
109
+ );
110
+ $this->client->exec("CREATE INDEX IF NOT EXISTS e_index ON $this->table (e)");
111
+ }
112
+
113
+ /**
114
+ * {@inheritdoc}
115
+ */
116
+ protected function unserialize($value)
117
+ {
118
+ // BYTEA data return streams. Even though it's not how init() will
119
+ // configure the DB by default, it could be used instead!
120
+ if (is_resource($value)) {
121
+ $value = stream_get_contents($value);
122
+ }
123
+
124
+ return parent::unserialize($value);
125
+ }
126
+ }
vendor/browscap-php/matthiasmullie/scrapbook/src/Adapters/Redis.php ADDED
@@ -0,0 +1,626 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace MatthiasMullie\Scrapbook\Adapters;
4
+
5
+ use MatthiasMullie\Scrapbook\Adapters\Collections\Redis as Collection;
6
+ use MatthiasMullie\Scrapbook\Exception\InvalidCollection;
7
+ use MatthiasMullie\Scrapbook\KeyValueStore;
8
+
9
+ /**
10
+ * Redis adapter. Basically just a wrapper over \Redis, but in an exchangeable
11
+ * (KeyValueStore) interface.
12
+ *
13
+ * @author Matthias Mullie <scrapbook@mullie.eu>
14
+ * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
15
+ * @license LICENSE MIT
16
+ */
17
+ class Redis implements KeyValueStore
18
+ {
19
+ /**
20
+ * @var \Redis
21
+ */
22
+ protected $client;
23
+
24
+ /**
25
+ * @var string|null
26
+ */
27
+ protected $version;
28
+
29
+ public function __construct(\Redis $client)
30
+ {
31
+ $this->client = $client;
32
+
33
+ // set a serializer if none is set already
34
+ if (\Redis::SERIALIZER_NONE == $this->client->getOption(\Redis::OPT_SERIALIZER)) {
35
+ $this->client->setOption(\Redis::OPT_SERIALIZER, \Redis::SERIALIZER_PHP);
36
+ }
37
+ }
38
+
39
+ /**
40
+ * {@inheritdoc}
41
+ */
42
+ public function get($key, &$token = null)
43
+ {
44
+ $this->client->multi();
45
+
46
+ $this->client->get($key);
47
+ $this->client->exists($key);
48
+
49
+ /** @var array $return */
50
+ $return = $this->client->exec();
51
+ if (false === $return) {
52
+ return false;
53
+ }
54
+
55
+ $value = $return[0];
56
+ $exists = $return[1];
57
+
58
+ // no value = quit early, don't generate a useless token
59
+ if (!$exists) {
60
+ $token = null;
61
+
62
+ return false;
63
+ }
64
+
65
+ // serializing to make sure we don't pass objects (by-reference) ;)
66
+ $token = serialize($value);
67
+
68
+ return $value;
69
+ }
70
+
71
+ /**
72
+ * {@inheritdoc}
73
+ */
74
+ public function getMulti(array $keys, array &$tokens = null)
75
+ {
76
+ $tokens = array();
77
+ if (empty($keys)) {
78
+ return array();
79
+ }
80
+
81
+ $this->client->multi();
82
+
83
+ $this->client->mget($keys);
84
+ foreach ($keys as $key) {
85
+ $this->client->exists($key);
86
+ }
87
+
88
+ /** @var array $return */
89
+ $return = $this->client->exec();
90
+ if (false === $return) {
91
+ return array();
92
+ }
93
+
94
+ $values = array_shift($return);
95
+ $exists = $return;
96
+
97
+ if (false === $values) {
98
+ $values = array_fill_keys($keys, false);
99
+ }
100
+ $values = array_combine($keys, $values);
101
+ $exists = array_combine($keys, $exists);
102
+
103
+ $tokens = array();
104
+ foreach ($values as $key => $value) {
105
+ // filter out non-existing values
106
+ if (!$exists[$key]) {
107
+ unset($values[$key]);
108
+ continue;
109
+ }
110
+
111
+ // serializing to make sure we don't pass objects (by-reference) ;)
112
+ $tokens[$key] = serialize($value);
113
+ }
114
+
115
+ return $values;
116
+ }
117
+
118
+ /**
119
+ * {@inheritdoc}
120
+ */
121
+ public function set($key, $value, $expire = 0)
122
+ {
123
+ $ttl = $this->ttl($expire);
124
+
125
+ /*
126
+ * Negative ttl behavior isn't properly documented & doesn't always
127
+ * appear to treat the value as non-existing. Let's play safe and just
128
+ * delete it right away!
129
+ */
130
+ if ($ttl < 0) {
131
+ $this->delete($key);
132
+
133
+ return true;
134
+ }
135
+
136
+ /*
137
+ * phpredis advises: "Calling setex() is preferred if you want a Time To
138
+ * Live". It seems that setex it what set will fall back to if you pass
139
+ * it a TTL anyway.
140
+ * Redis advises: "Note: Since the SET command options can replace
141
+ * SETNX, SETEX, PSETEX, it is possible that in future versions of Redis
142
+ * these three commands will be deprecated and finally removed."
143
+ * I'll just go with set() - it works and seems the desired path for the
144
+ * future.
145
+ *
146
+ * @see https://github.com/ukko/phpredis-phpdoc/blob/master/src/Redis.php#L190
147
+ * @see https://github.com/phpredis/phpredis#set
148
+ * @see http://redis.io/commands/SET
149
+ */
150
+ return $this->client->set($key, $value, $ttl);
151
+ }
152
+
153
+ /**
154
+ * {@inheritdoc}
155
+ */
156
+ public function setMulti(array $items, $expire = 0)
157
+ {
158
+ if (empty($items)) {
159
+ return array();
160
+ }
161
+
162
+ $ttl = $this->ttl($expire);
163
+
164
+ /*
165
+ * Negative ttl behavior isn't properly documented & doesn't always
166
+ * appear to treat the value as non-existing. Let's play safe and just
167
+ * delete it right away!
168
+ */
169
+ if ($ttl < 0) {
170
+ $this->deleteMulti(array_keys($items));
171
+
172
+ return array_fill_keys(array_keys($items), true);
173
+ }
174
+
175
+ if (null === $ttl) {
176
+ $success = $this->client->mset($items);
177
+
178
+ return array_fill_keys(array_keys($items), $success);
179
+ }
180
+
181
+ $this->client->multi();
182
+ $this->client->mset($items);
183
+
184
+ // Redis has no convenient multi-expire method
185
+ foreach ($items as $key => $value) {
186
+ $this->client->expire($key, $ttl);
187
+ }
188
+
189
+ /* @var bool[] $return */
190
+ $result = (array) $this->client->exec();
191
+
192
+ $return = array();
193
+ $keys = array_keys($items);
194
+ $success = array_shift($result);
195
+ foreach ($result as $i => $value) {
196
+ $key = $keys[$i];
197
+ $return[$key] = $success && $value;
198
+ }
199
+
200
+ return $return;
201
+ }
202
+
203
+ /**
204
+ * {@inheritdoc}
205
+ */
206
+ public function delete($key)
207
+ {
208
+ return (bool) $this->client->del($key);
209
+ }
210
+
211
+ /**
212
+ * {@inheritdoc}
213
+ */
214
+ public function deleteMulti(array $keys)
215
+ {
216
+ if (empty($keys)) {
217
+ return array();
218
+ }
219
+
220
+ /*
221
+ * del will only return the amount of deleted entries, but we also want
222
+ * to know which failed. Deletes will only fail for items that don't
223
+ * exist, so we'll just ask for those and see which are missing.
224
+ */
225
+ $items = $this->getMulti($keys);
226
+
227
+ $this->client->del($keys);
228
+
229
+ $return = array();
230
+ foreach ($keys as $key) {
231
+ $return[$key] = array_key_exists($key, $items);
232
+ }
233
+
234
+ return $return;
235
+ }
236
+
237
+ /**
238
+ * {@inheritdoc}
239
+ */
240
+ public function add($key, $value, $expire = 0)
241
+ {
242
+ $ttl = $this->ttl($expire);
243
+
244
+ /*
245
+ * Negative ttl behavior isn't properly documented & doesn't always
246
+ * appear to treat the value as non-existing. Let's play safe and not
247
+ * even create the value (also saving a request)
248
+ */
249
+ if ($ttl < 0) {
250
+ return true;
251
+ }
252
+
253
+ if (null === $ttl) {
254
+ return $this->client->setnx($key, $value);
255
+ }
256
+
257
+ /*
258
+ * I could use Redis 2.6.12-style options array:
259
+ * $this->client->set($key, $value, array('xx', 'ex' => $ttl));
260
+ * However, this one should be pretty fast already, compared to the
261
+ * replace-workaround below.
262
+ */
263
+ $this->client->multi();
264
+ $this->client->setnx($key, $value);
265
+ $this->client->expire($key, $ttl);
266
+
267
+ /** @var bool[] $return */
268
+ $return = (array) $this->client->exec();
269
+
270
+ return !in_array(false, $return);
271
+ }
272
+
273
+ /**
274
+ * {@inheritdoc}
275
+ */
276
+ public function replace($key, $value, $expire = 0)
277
+ {
278
+ $ttl = $this->ttl($expire);
279
+
280
+ /*
281
+ * Negative ttl behavior isn't properly documented & doesn't always
282
+ * appear to treat the value as non-existing. Let's play safe and just
283
+ * delete it right away!
284
+ */
285
+ if ($ttl < 0) {
286
+ return $this->delete($key);
287
+ }
288
+
289
+ /*
290
+ * Redis supports passing set() an extended options array since >=2.6.12
291
+ * which allows for an easy and 1-request way to replace a value.
292
+ * That version already comes with Ubuntu 14.04. Ubuntu 12.04 (still
293
+ * widely used and in LTS) comes with an older version, however, so I
294
+ * want to support that too.
295
+ * Supporting both versions comes at a cost.
296
+ * I'll optimize for recent versions, which will get (in case of replace
297
+ * failure) 1 additional network request (for version info). Older
298
+ * versions will get 2 additional network requests: a failed replace
299
+ * (because the options are unknown) & a version check.
300
+ */
301
+ if (null === $this->version || $this->supportsOptionsArray()) {
302
+ $options = array('xx');
303
+ if ($ttl > 0) {
304
+ /*
305
+ * Not adding 0 TTL to options:
306
+ * * HHVM (used to) interpret(s) wrongly & throw an exception
307
+ * * it's not needed anyway, for 0...
308
+ * @see https://github.com/facebook/hhvm/pull/4833
309
+ */
310
+ $options['ex'] = $ttl;
311
+ }
312
+
313
+ // either we support options array or we haven't yet checked, in
314
+ // which case I'll assume a recent server is running
315
+ $result = $this->client->set($key, $value, $options);
316
+ if (false !== $result) {
317
+ return $result;
318
+ }
319
+
320
+ if ($this->supportsOptionsArray()) {
321
+ // failed execution, but not because our Redis version is too old
322
+ return false;
323
+ }
324
+ }
325
+
326
+ // workaround for old Redis versions
327
+ $this->client->watch($key);
328
+
329
+ $exists = $this->client->exists('key');
330
+ if (!$exists) {
331
+ /*
332
+ * HHVM Redis only got unwatch recently
333
+ * @see https://github.com/asgrim/hhvm/commit/bf5a259cece5df8a7617133c85043608d1ad5316
334
+ */
335
+ if (method_exists($this->client, 'unwatch')) {
336
+ $this->client->unwatch();
337
+ } else {
338
+ // this should also kill the watch...
339
+ $this->client->multi()->discard();
340
+ }
341
+
342
+ return false;
343
+ }
344
+
345
+ // since we're watching the key, this will fail should it change in the
346
+ // meantime
347
+ $this->client->multi();
348
+
349
+ $this->client->set($key, $value, $ttl);
350
+
351
+ /** @var bool[] $return */
352
+ $return = (array) $this->client->exec();
353
+
354
+ return !in_array(false, $return);
355
+ }
356
+
357
+ /**
358
+ * {@inheritdoc}
359
+ */
360
+ public function cas($token, $key, $value, $expire = 0)
361
+ {
362
+ $this->client->watch($key);
363
+
364
+ // check if the value still matches CAS token
365
+ $comparison = $this->client->get($key);
366
+ if (serialize($comparison) !== $token) {
367
+ /*
368
+ * HHVM Redis only got unwatch recently
369
+ * @see https://github.com/asgrim/hhvm/commit/bf5a259cece5df8a7617133c85043608d1ad5316
370
+ */
371
+ if (method_exists($this->client, 'unwatch')) {
372
+ $this->client->unwatch();
373
+ } else {
374
+ // this should also kill the watch...
375
+ $this->client->multi()->discard();
376
+ }
377
+
378
+ return false;
379
+ }
380
+
381
+ $ttl = $this->ttl($expire);
382
+
383
+ // since we're watching the key, this will fail should it change in the
384
+ // meantime
385
+ $this->client->multi();
386
+
387
+ /*
388
+ * Negative ttl behavior isn't properly documented & doesn't always
389
+ * appear to treat the value as non-existing. Let's play safe and just
390
+ * delete it right away!
391
+ */
392
+ if ($ttl < 0) {
393
+ $this->client->del($key);
394
+ } else {
395
+ $this->client->set($key, $value, $ttl);
396
+ }
397
+
398
+ /** @var bool[] $return */
399
+ $return = (array) $this->client->exec();
400
+
401
+ return !in_array(false, $return);
402
+ }
403
+
404
+ /**
405
+ * {@inheritdoc}
406
+ */
407
+ public function increment($key, $offset = 1, $initial = 0, $expire = 0)
408
+ {
409
+ if ($offset <= 0 || $initial < 0) {
410
+ return false;
411
+ }
412
+
413
+ // INCRBY initializes (at 0) & immediately increments, whereas we
414
+ // only do initialization if the value does not yet exist
415
+ if (0 === $initial + $offset && 0 === $expire) {
416
+ return $this->client->incrBy($key, $offset);
417
+ }
418
+
419
+ return $this->doIncrement($key, $offset, $initial, $expire);
420
+ }
421
+
422
+ /**
423
+ * {@inheritdoc}
424
+ */
425
+ public function decrement($key, $offset = 1, $initial = 0, $expire = 0)
426
+ {
427
+ if ($offset <= 0 || $initial < 0) {
428
+ return false;
429
+ }
430
+
431
+ // DECRBY can't be used. Not even if we don't need an initial
432
+ // value (it auto-initializes at 0) or expire. Problem is it
433
+ // will decrement below 0, which is something we don't support.
434
+
435
+ return $this->doIncrement($key, -$offset, $initial, $expire);
436
+ }
437
+
438
+ /**
439
+ * {@inheritdoc}
440
+ */
441
+ public function touch($key, $expire)
442
+ {
443
+ $ttl = $this->ttl($expire);
444
+
445
+ if ($ttl < 0) {
446
+ // Redis can't set expired, so just remove in that case ;)
447
+ return (bool) $this->client->del($key);
448
+ }
449
+
450
+ return $this->client->expire($key, $ttl);
451
+ }
452
+
453
+ /**
454
+ * {@inheritdoc}
455
+ */
456
+ public function flush()
457
+ {
458
+ return $this->client->flushAll();
459
+ }
460
+
461
+ /**
462
+ * {@inheritdoc}
463
+ */
464
+ public function getCollection($name)
465
+ {
466
+ if (!is_numeric($name)) {
467
+ throw new InvalidCollection('Redis database names must be numeric. '.serialize($name).' given.');
468
+ }
469
+
470
+ // we can't reuse $this->client in a different object, because it'll
471
+ // operate on a different database
472
+ $client = new \Redis();
473
+
474
+ if (null !== $this->client->getPersistentID()) {
475
+ $client->pconnect(
476
+ $this->client->getHost(),
477
+ $this->client->getPort(),
478
+ $this->client->getTimeout()
479
+ );
480
+ } else {
481
+ $client->connect(
482
+ $this->client->getHost(),
483
+ $this->client->getPort(),
484
+ $this->client->getTimeout()
485
+ );
486
+ }
487
+
488
+ $auth = $this->client->getAuth();
489
+ if (null !== $auth) {
490
+ $client->auth($auth);
491
+ }
492
+
493
+ $readTimeout = $this->client->getReadTimeout();
494
+ if ($readTimeout) {
495
+ $client->setOption(\Redis::OPT_READ_TIMEOUT, $this->client->getReadTimeout());
496
+ }
497
+
498
+ return new Collection($client, $name);
499
+ }
500
+
501
+ /**
502
+ * Redis expects true TTL, not expiration timestamp.
503
+ *
504
+ * @param int $expire
505
+ *
506
+ * @return int|null TTL in seconds, or `null` for no expiration
507
+ */
508
+ protected function ttl($expire)
509
+ {
510
+ if (0 === $expire) {
511
+ return null;
512
+ }
513
+
514
+ // relative time in seconds, <30 days
515
+ if ($expire > 30 * 24 * 60 * 60) {
516
+ return $expire - time();
517
+ }
518
+
519
+ return $expire;
520
+ }
521
+
522
+ /**
523
+ * Shared between increment/decrement: both have mostly the same logic
524
+ * (decrement just increments a negative value), but need their validation
525
+ * & use of non-ttl native methods split up.
526
+ *
527
+ * @param string $key
528
+ * @param int $offset
529
+ * @param int $initial
530
+ * @param int $expire
531
+ *
532
+ * @return int|bool
533
+ */
534
+ protected function doIncrement($key, $offset, $initial, $expire)
535
+ {
536
+ $ttl = $this->ttl($expire);
537
+
538
+ $this->client->watch($key);
539
+
540
+ $value = $this->client->get($key);
541
+
542
+ if (false === $value) {
543
+ /*
544
+ * Negative ttl behavior isn't properly documented & doesn't always
545
+ * appear to treat the value as non-existing. Let's play safe and not
546
+ * even create the value (also saving a request)
547
+ */
548
+ if ($ttl < 0) {
549
+ return true;
550
+ }
551
+
552
+ // value is not yet set, store initial value!
553
+ $this->client->multi();
554
+ $this->client->set($key, $initial, $ttl);
555
+
556
+ /** @var bool[] $return */
557
+ $return = (array) $this->client->exec();
558
+
559
+ return !in_array(false, $return) ? $initial : false;
560
+ }
561
+
562
+ // can't increment if a non-numeric value is set
563
+ if (!is_numeric($value) || $value < 0) {
564
+ /*
565
+ * HHVM Redis only got unwatch recently.
566
+ * @see https://github.com/asgrim/hhvm/commit/bf5a259cece5df8a7617133c85043608d1ad5316
567
+ */
568
+ if (method_exists($this->client, 'unwatch')) {
569
+ $this->client->unwatch();
570
+ } else {
571
+ // this should also kill the watch...
572
+ $this->client->multi()->discard();
573
+ }
574
+
575
+ return false;
576
+ }
577
+
578
+ $value += $offset;
579
+ // value can never be lower than 0
580
+ $value = max(0, $value);
581
+
582
+ $this->client->multi();
583
+
584
+ /*
585
+ * Negative ttl behavior isn't properly documented & doesn't always
586
+ * appear to treat the value as non-existing. Let's play safe and just
587
+ * delete it right away!
588
+ */
589
+ if ($ttl < 0) {
590
+ $this->client->del($key);
591
+ } else {
592
+ $this->client->set($key, $value, $ttl);
593
+ }
594
+
595
+ /** @var bool[] $return */
596
+ $return = (array) $this->client->exec();
597
+
598
+ return !in_array(false, $return) ? $value : false;
599
+ }
600
+
601
+ /**
602
+ * Returns the version of the Redis server we're connecting to.
603
+ *
604
+ * @return string
605
+ */
606
+ protected function getVersion()
607
+ {
608
+ if (null === $this->version) {
609
+ $info = $this->client->info();
610
+ $this->version = $info['redis_version'];
611
+ }
612
+
613
+ return $this->version;
614
+ }
615
+
616
+ /**
617
+ * Version-based check to test if passing an options array to set() is
618
+ * supported.
619
+ *
620
+ * @return bool
621
+ */
622
+ protected function supportsOptionsArray()
623
+ {
624
+ return version_compare($this->getVersion(), '2.6.12') >= 0;
625
+ }
626
+ }
vendor/browscap-php/matthiasmullie/scrapbook/src/Adapters/SQL.php ADDED
@@ -0,0 +1,513 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace MatthiasMullie\Scrapbook\Adapters;
4
+
5
+ use MatthiasMullie\Scrapbook\Adapters\Collections\SQL as Collection;
6
+ use MatthiasMullie\Scrapbook\KeyValueStore;
7
+ use PDO;
8
+
9
+ /**
10
+ * SQL adapter. Basically just a wrapper over \PDO, but in an exchangeable
11
+ * (KeyValueStore) interface.
12
+ *
13
+ * This abstract class should be a "fits all DB engines" normalization. It's up
14
+ * to extending classes to optimize for that specific engine.
15
+ *
16
+ * @author Matthias Mullie <scrapbook@mullie.eu>
17
+ * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
18
+ * @license LICENSE MIT
19
+ */
20
+ abstract class SQL implements KeyValueStore
21
+ {
22
+ /**
23
+ * @var PDO
24
+ */
25
+ protected $client;
26
+
27
+ /**
28
+ * @var string
29
+ */
30
+ protected $table;
31
+
32
+ /**
33
+ * Create the database/indices if it does not already exist.
34
+ */
35
+ abstract protected function init();
36
+
37
+ /**
38
+ * @param string $table
39
+ */
40
+ public function __construct(PDO $client, $table = 'cache')
41
+ {
42
+ $this->client = $client;
43
+ $this->table = $table;
44
+
45
+ // don't throw exceptions - it's ok to fail, as long as the return value
46
+ // reflects that!
47
+ $this->client->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
48
+
49
+ // make sure the database exists (or just "fail" silently)
50
+ $this->init();
51
+
52
+ // now's a great time to clean up all expired items
53
+ $this->clearExpired();
54
+ }
55
+
56
+ /**
57
+ * {@inheritdoc}
58
+ */
59
+ public function get($key, &$token = null)
60
+ {
61
+ $statement = $this->client->prepare(
62
+ "SELECT v
63
+ FROM $this->table
64
+ WHERE k = :key AND (e IS NULL OR e > :expire)"
65
+ );
66
+ $statement->execute(array(
67
+ ':key' => $key,
68
+ ':expire' => date('Y-m-d H:i:s'), // right now!
69
+ ));
70
+
71
+ $result = $statement->fetch(PDO::FETCH_ASSOC);
72
+
73
+ if (!isset($result['v'])) {
74
+ $token = null;
75
+
76
+ return false;
77
+ }
78
+
79
+ $token = $result['v'];
80
+
81
+ return $this->unserialize($result['v']);
82
+ }
83
+
84
+ /**
85
+ * {@inheritdoc}
86
+ */
87
+ public function getMulti(array $keys, array &$tokens = null)
88
+ {
89
+ $tokens = array();
90
+ if (empty($keys)) {
91
+ return array();
92
+ }
93
+
94
+ // escape input, can't bind multiple params for IN()
95
+ $quoted = array();
96
+ foreach ($keys as $key) {
97
+ $quoted[] = $this->client->quote($key);
98
+ }
99
+
100
+ $statement = $this->client->prepare(
101
+ "SELECT k, v
102
+ FROM $this->table
103
+ WHERE
104
+ k IN (".implode(',', $quoted).') AND
105
+ (e IS NULL OR e > :expire)'
106
+ );
107
+ $statement->execute(array(':expire' => date('Y-m-d H:i:s')));
108
+ $values = $statement->fetchAll(PDO::FETCH_ASSOC);
109
+
110
+ $result = array();
111
+ $tokens = array();
112
+ foreach ($values as $value) {
113
+ $tokens[$value['k']] = $value['v'];
114
+ $result[$value['k']] = $this->unserialize($value['v']);
115
+ }
116
+
117
+ return $result;
118
+ }
119
+
120
+ /**
121
+ * {@inheritdoc}
122
+ */
123
+ public function set($key, $value, $expire = 0)
124
+ {
125
+ // PostgreSQL doesn't have a decent UPSERT (like REPLACE or even INSERT
126
+ // ... ON DUPLICATE KEY UPDATE ...); here's a "works for all" downgrade
127
+ $success = $this->add($key, $value, $expire);
128
+ if ($success) {
129
+ return true;
130
+ }
131
+
132
+ $success = $this->replace($key, $value, $expire);
133
+ if ($success) {
134
+ return true;
135
+ }
136
+
137
+ return false;
138
+ }
139
+
140
+ /**
141
+ * {@inheritdoc}
142
+ */
143
+ public function setMulti(array $items, $expire = 0)
144
+ {
145
+ $success = array();
146
+
147
+ // PostgreSQL's lack of a decent UPSERT is even worse for multiple
148
+ // values - we can only do them one at a time...
149
+ foreach ($items as $key => $value) {
150
+ $success[$key] = $this->set($key, $value, $expire);
151
+ }
152
+
153
+ return $success;
154
+ }
155
+
156
+ /**
157
+ * {@inheritdoc}
158
+ */
159
+ public function delete($key)
160
+ {
161
+ $statement = $this->client->prepare(
162
+ "DELETE FROM $this->table
163
+ WHERE k = :key"
164
+ );
165
+
166
+ $statement->execute(array(':key' => $key));
167
+
168
+ return 1 === $statement->rowCount();
169
+ }
170
+
171
+ /**
172
+ * {@inheritdoc}
173
+ */
174
+ public function deleteMulti(array $keys)
175
+ {
176
+ if (empty($keys)) {
177
+ return array();
178
+ }
179
+
180
+ // we'll need these to figure out which could not be deleted...
181
+ $items = $this->getMulti($keys);
182
+
183
+ // escape input, can't bind multiple params for IN()
184
+ $quoted = array();
185
+ foreach ($keys as $key) {
186
+ $quoted[] = $this->client->quote($key);
187
+ }
188
+
189
+ $statement = $this->client->query(
190
+ "DELETE FROM $this->table
191
+ WHERE k IN (".implode(',', $quoted).')'
192
+ );
193
+
194
+ /*
195
+ * In case of connection problems, we may not have been able to delete
196
+ * any. Otherwise, we'll use the getMulti() results to figure out which
197
+ * couldn't be deleted because they didn't exist at that time.
198
+ */
199
+ $success = 0 !== $statement->rowCount();
200
+ $success = array_fill_keys($keys, $success);
201
+ foreach ($keys as $key) {
202
+ if (!array_key_exists($key, $items)) {
203
+ $success[$key] = false;
204
+ }
205
+ }
206
+
207
+ return $success;
208
+ }
209
+
210
+ /**
211
+ * {@inheritdoc}
212
+ */
213
+ public function add($key, $value, $expire = 0)
214
+ {
215
+ $value = $this->serialize($value);
216
+ $expire = $this->expire($expire);
217
+
218
+ $this->clearExpired();
219
+
220
+ $statement = $this->client->prepare(
221
+ "INSERT INTO $this->table (k, v, e)
222
+ VALUES (:key, :value, :expire)"
223
+ );
224
+
225
+ $statement->execute(array(
226
+ ':key' => $key,
227
+ ':value' => $value,
228
+ ':expire' => $expire,
229
+ ));
230
+
231
+ return 1 === $statement->rowCount();
232
+ }
233
+
234
+ /**
235
+ * {@inheritdoc}
236
+ */
237
+ public function replace($key, $value, $expire = 0)
238
+ {
239
+ $value = $this->serialize($value);
240
+ $expire = $this->expire($expire);
241
+
242
+ $this->clearExpired();
243
+
244
+ $statement = $this->client->prepare(
245
+ "UPDATE $this->table
246
+ SET v = :value, e = :expire
247
+ WHERE k = :key"
248
+ );
249
+
250
+ $statement->execute(array(
251
+ ':key' => $key,
252
+ ':value' => $value,
253
+ ':expire' => $expire,
254
+ ));
255
+
256
+ if (1 === $statement->rowCount()) {
257
+ return true;
258
+ }
259
+
260
+ // if the value we've just replaced was the same as the replacement, as
261
+ // well as the same expiration time, rowCount will have been 0, but the
262
+ // operation was still a success
263
+ $statement = $this->client->prepare(
264
+ "SELECT e
265
+ FROM $this->table
266
+ WHERE k = :key AND v = :value"
267
+ );
268
+ $statement->execute(array(
269
+ ':key' => $key,
270
+ ':value' => $value,
271
+ ));
272
+
273
+ return $statement->fetchColumn(0) === $expire;
274
+ }
275
+
276
+ /**
277
+ * {@inheritdoc}
278
+ */
279
+ public function cas($token, $key, $value, $expire = 0)
280
+ {
281
+ $value = $this->serialize($value);
282
+ $expire = $this->expire($expire);
283
+
284
+ $this->clearExpired();
285
+
286
+ $statement = $this->client->prepare(
287
+ "UPDATE $this->table
288
+ SET v = :value, e = :expire
289
+ WHERE k = :key AND v = :token"
290
+ );
291
+
292
+ $statement->execute(array(
293
+ ':key' => $key,
294
+ ':value' => $value,
295
+ ':expire' => $expire,
296
+ ':token' => $token,
297
+ ));
298
+
299
+ if (1 === $statement->rowCount()) {
300
+ return true;
301
+ }
302
+
303
+ // if the value we've just cas'ed was the same as the replacement, as
304
+ // well as the same expiration time, rowCount will have been 0, but the
305
+ // operation was still a success
306
+ $statement = $this->client->prepare(
307
+ "SELECT e
308
+ FROM $this->table
309
+ WHERE k = :key AND v = :value AND v = :token"
310
+ );
311
+ $statement->execute(array(
312
+ ':key' => $key,
313
+ ':value' => $value,
314
+ ':token' => $token,
315
+ ));
316
+
317
+ return $statement->fetchColumn(0) === $expire;
318
+ }
319
+
320
+ /**
321
+ * {@inheritdoc}
322
+ */
323
+ public function increment($key, $offset = 1, $initial = 0, $expire = 0)
324
+ {
325
+ if ($offset <= 0 || $initial < 0) {
326
+ return false;
327
+ }
328
+
329
+ return $this->doIncrement($key, $offset, $initial, $expire);
330
+ }
331
+
332
+ /**
333
+ * {@inheritdoc}
334
+ */
335
+ public function decrement($key, $offset = 1, $initial = 0, $expire = 0)
336
+ {
337
+ if ($offset <= 0 || $initial < 0) {
338
+ return false;
339
+ }
340
+
341
+ return $this->doIncrement($key, -$offset, $initial, $expire);
342
+ }
343
+
344
+ /**
345
+ * {@inheritdoc}
346
+ */
347
+ public function touch($key, $expire)
348
+ {
349
+ $expire = $this->expire($expire);
350
+
351
+ $this->clearExpired();
352
+
353
+ $statement = $this->client->prepare(
354
+ "UPDATE $this->table
355
+ SET e = :expire
356
+ WHERE k = :key"
357
+ );
358
+
359
+ $statement->execute(array(
360
+ ':key' => $key,
361
+ ':expire' => $expire,
362
+ ));
363
+
364
+ return 1 === $statement->rowCount();
365
+ }
366
+
367
+ /**
368
+ * {@inheritdoc}
369
+ */
370
+ public function flush()
371
+ {
372
+ // TRUNCATE doesn't work on SQLite - DELETE works for all
373
+ return false !== $this->client->exec("DELETE FROM $this->table");
374
+ }
375
+
376
+ /**
377
+ * {@inheritdoc}
378
+ */
379
+ public function getCollection($name)
380
+ {
381
+ return new Collection($this, $this->client, $this->table, $name);
382
+ }
383
+
384
+ /**
385
+ * Shared between increment/decrement: both have mostly the same logic
386
+ * (decrement just increments a negative value), but need their validation
387
+ * & use of non-ttl native methods split up.
388
+ *
389
+ * @param string $key
390
+ * @param int $offset
391
+ * @param int $initial
392
+ * @param int $expire
393
+ *
394
+ * @return int|bool
395
+ */
396
+ protected function doIncrement($key, $offset, $initial, $expire)
397
+ {
398
+ /*
399
+ * I used to have all this logic in a huge & ugly query, but getting
400
+ * that right on multiple SQL engines proved challenging (SQLite doesn't
401
+ * do INSERT ... ON DUPLICATE KEY UPDATE ..., for example)
402
+ * I'll just stuff it in a transaction & leverage existing methods.
403
+ */
404
+ $this->client->beginTransaction();
405
+ $this->clearExpired();
406
+
407
+ $value = $this->get($key);
408
+ if (false === $value) {
409
+ $return = $this->add($key, $initial, $expire);
410
+
411
+ if ($return) {
412
+ $this->client->commit();
413
+
414
+ return $initial;
415
+ }
416
+ } elseif (is_numeric($value)) {
417
+ $value += $offset;
418
+ // < 0 is never possible
419
+ $value = max(0, $value);
420
+ $return = $this->replace($key, $value, $expire);
421
+
422
+ if ($return) {
423
+ $this->client->commit();
424
+
425
+ return (int) $value;
426
+ }
427
+ }
428
+
429
+ $this->client->rollBack();
430
+
431
+ return false;
432
+ }
433
+
434
+ /**
435
+ * Expired entries shouldn't keep filling up the database. Additionally,
436
+ * we will want to remove those in order to properly rely on INSERT (for
437
+ * add) and UPDATE (for replace), which assume a column exists or not, not
438
+ * taking the expiration status into consideration.
439
+ * An expired column should simply not exist.
440
+ */
441
+ protected function clearExpired()
442
+ {
443
+ $statement = $this->client->prepare(
444
+ "DELETE FROM $this->table
445
+ WHERE e < :expire"
446
+ );
447
+
448
+ $statement->execute(array(':expire' => date('Y-m-d H:i:s')));
449
+ }
450
+
451
+ /**
452
+ * Transforms expiration times into TIMESTAMP (Y-m-d H:i:s) format, which DB
453
+ * will understand and be able to compare with other dates.
454
+ *
455
+ * @param int $expire
456
+ *
457
+ * @return string|null
458
+ */
459
+ protected function expire($expire)
460
+ {
461
+ if (0 === $expire) {
462
+ return;
463
+ }
464
+
465
+ // relative time in seconds, <30 days
466
+ if ($expire < 30 * 24 * 60 * 60) {
467
+ $expire += time();
468
+ }
469
+
470
+ return date('Y-m-d H:i:s', $expire);
471
+ }
472
+
473
+ /**
474
+ * I originally didn't want to serialize numeric values because I planned
475
+ * on incrementing them in the DB, but revisited that idea.
476
+ * However, not serializing numbers still causes some small DB storage gains
477
+ * and it's safe (serialized data can never be confused for an int).
478
+ *
479
+ * @param mixed $value
480
+ *
481
+ * @return string|int
482
+ */
483
+ protected function serialize($value)
484
+ {
485
+ return is_int($value) || is_float($value) ? $value : serialize($value);
486
+ }
487
+
488
+ /**
489
+ * Numbers aren't serialized for storage size purposes.
490
+ *
491
+ * @param mixed $value
492
+ *
493
+ * @return mixed|int|float
494
+ */
495
+ protected function unserialize($value)
496
+ {
497
+ if (is_numeric($value)) {
498
+ $int = (int) $value;
499
+ if ((string) $int === $value) {
500
+ return $int;
501
+ }
502
+
503
+ $float = (float) $value;
504
+ if ((string) $float === $value) {
505
+ return $float;
506
+ }
507
+
508
+ return $value;
509
+ }
510
+
511
+ return unserialize($value);
512
+ }
513
+ }
vendor/browscap-php/matthiasmullie/scrapbook/src/Adapters/SQLite.php ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace MatthiasMullie\Scrapbook\Adapters;
4
+
5
+ /**
6
+ * SQLite adapter. Basically just a wrapper over \PDO, but in an exchangeable
7
+ * (KeyValueStore) interface.
8
+ *
9
+ * @author Matthias Mullie <scrapbook@mullie.eu>
10
+ * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
11
+ * @license LICENSE MIT
12
+ */
13
+ class SQLite extends MySQL
14
+ {
15
+ /**
16
+ * {@inheritdoc}
17
+ */
18
+ public function setMulti(array $items, $expire = 0)
19
+ {
20
+ if (empty($items)) {
21
+ return array();
22
+ }
23
+
24
+ $expire = $this->expire($expire);
25
+
26
+ $this->clearExpired();
27
+
28
+ // SQLite < 3.7.11 doesn't support multi-insert/replace!
29
+
30
+ $statement = $this->client->prepare(
31
+ "REPLACE INTO $this->table (k, v, e)
32
+ VALUES (:key, :value, :expire)"
33
+ );
34
+
35
+ $success = array();
36
+ foreach ($items as $key => $value) {
37
+ $value = $this->serialize($value);
38
+
39
+ $statement->execute(array(
40
+ ':key' => $key,
41
+ ':value' => $value,
42
+ ':expire' => $expire,
43
+ ));
44
+
45
+ $success[$key] = (bool) $statement->rowCount();
46
+ }
47
+
48
+ return $success;
49
+ }
50
+
51
+ /**
52
+ * {@inheritdoc}
53
+ */
54
+ public function add($key, $value, $expire = 0)
55
+ {
56
+ $value = $this->serialize($value);
57
+ $expire = $this->expire($expire);
58
+
59
+ $this->clearExpired();
60
+
61
+ // SQLite-specific way to ignore insert-on-duplicate errors
62
+ $statement = $this->client->prepare(
63
+ "INSERT OR IGNORE INTO $this->table (k, v, e)
64
+ VALUES (:key, :value, :expire)"
65
+ );
66
+
67
+ $statement->execute(array(
68
+ ':key' => $key,
69
+ ':value' => $value,
70
+ ':expire' => $expire,
71
+ ));
72
+
73
+ return 1 === $statement->rowCount();
74
+ }
75
+
76
+ /**
77
+ * {@inheritdoc}
78
+ */
79
+ public function flush()
80
+ {
81
+ return false !== $this->client->exec("DELETE FROM $this->table");
82
+ }
83
+
84
+ /**
85
+ * {@inheritdoc}
86
+ */
87
+ protected function init()
88
+ {
89
+ $this->client->exec(
90
+ "CREATE TABLE IF NOT EXISTS $this->table (
91
+ k VARCHAR(255) NOT NULL PRIMARY KEY,
92
+ v BLOB,
93
+ e TIMESTAMP NULL DEFAULT NULL,
94
+ KEY e
95
+ )"
96
+ );
97
+ }
98
+ }
vendor/browscap-php/matthiasmullie/scrapbook/src/Buffered/BufferedStore.php ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace MatthiasMullie\Scrapbook\Buffered;
4
+
5
+ use MatthiasMullie\Scrapbook\Buffered\Utils\Buffer;
6
+ use MatthiasMullie\Scrapbook\Buffered\Utils\Transaction;
7
+ use MatthiasMullie\Scrapbook\KeyValueStore;
8
+
9
+ /**
10
+ * This class will serve as a local buffer to the real cache: anything read from
11
+ * & written to the real cache will be stored in memory, so if any of those keys
12
+ * is again requested in the same request, we can just grab it from memory
13
+ * instead of having to get it over the wire.
14
+ *
15
+ * @author Matthias Mullie <scrapbook@mullie.eu>
16
+ * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
17
+ * @license LICENSE MIT
18
+ */
19
+ class BufferedStore implements KeyValueStore
20
+ {
21
+ /**
22
+ * Transaction will already buffer all writes (until the transaction
23
+ * has been committed/rolled back). As long as we immediately commit
24
+ * to real store, it'll look as if no transaction is in progress &
25
+ * all we'll be left with is the local copy of all data that can act
26
+ * as buffer for follow-up requests.
27
+ * All we'll need to add is also buffering non-write results.
28
+ *
29
+ * @var Transaction
30
+ */
31
+ protected $transaction;
32
+
33
+ /**
34
+ * Local in-memory storage, for the data we've already requested from
35
+ * or written to the real cache.
36
+ *
37
+ * @var Buffer
38
+ */
39
+ protected $local;
40
+
41
+ /**
42
+ * @var BufferedStore[]
43
+ */
44
+ protected $collections = array();
45
+
46
+ /**
47
+ * @param KeyValueStore $cache The real cache we'll buffer for
48
+ */
49
+ public function __construct(KeyValueStore $cache)
50
+ {
51
+ $this->local = new Buffer();
52
+ $this->transaction = new Transaction($this->local, $cache);
53
+ }
54
+
55
+ /**
56
+ * In addition to all writes being stored to $local, we'll also
57
+ * keep get() values around ;).
58
+ *
59
+ * {@inheritdoc}
60
+ */
61
+ public function get($key, &$token = null)
62
+ {
63
+ $value = $this->transaction->get($key, $token);
64
+
65
+ // only store if we managed to retrieve a value (valid token) and it's
66
+ // not already in cache (or we may mess up tokens)
67
+ if (false !== $value && false === $this->local->get($key, $localToken) && null === $localToken) {
68
+ $this->local->set($key, $value);
69
+ }
70
+
71
+ return $value;
72
+ }
73
+
74
+ /**
75
+ * In addition to all writes being stored to $local, we'll also
76
+ * keep get() values around ;).
77
+ *
78
+ * {@inheritdoc}
79
+ */
80
+ public function getMulti(array $keys, array &$tokens = null)
81
+ {
82
+ $values = $this->transaction->getMulti($keys, $tokens);
83
+
84
+ $missing = array_diff_key($values, $this->local->getMulti($keys));
85
+ if (!empty($missing)) {
86
+ $this->local->setMulti($missing);
87
+ }
88
+
89
+ return $values;
90
+ }
91
+
92
+ /**
93
+ * {@inheritdoc}
94
+ */
95
+ public function set($key, $value, $expire = 0)
96
+ {
97
+ $result = $this->transaction->set($key, $value, $expire);
98
+ $this->transaction->commit();
99
+
100
+ return $result;
101
+ }
102
+
103
+ /**
104
+ * {@inheritdoc}
105
+ */
106
+ public function setMulti(array $items, $expire = 0)
107
+ {
108
+ $result = $this->transaction->setMulti($items, $expire);
109
+ $this->transaction->commit();
110
+
111
+ return $result;
112
+ }
113
+
114
+ /**
115
+ * {@inheritdoc}
116
+ */
117
+ public function delete($key)
118
+ {
119
+ $result = $this->transaction->delete($key);
120
+ $this->transaction->commit();
121
+
122
+ return $result;
123
+ }
124
+
125
+ /**
126
+ * {@inheritdoc}
127
+ */
128
+ public function deleteMulti(array $keys)
129
+ {
130
+ $result = $this->transaction->deleteMulti($keys);
131
+ $this->transaction->commit();
132
+
133
+ return $result;
134
+ }
135
+
136
+ /**
137
+ * {@inheritdoc}
138
+ */
139
+ public function add($key, $value, $expire = 0)
140
+ {
141
+ $result = $this->transaction->add($key, $value, $expire);
142
+ $this->transaction->commit();
143
+
144
+ return $result;
145
+ }
146
+
147
+ /**
148
+ * {@inheritdoc}
149
+ */
150
+ public function replace($key, $value, $expire = 0)
151
+ {
152
+ $result = $this->transaction->replace($key, $value, $expire);
153
+ $this->transaction->commit();
154
+
155
+ return $result;
156
+ }
157
+
158
+ /**
159
+ * {@inheritdoc}
160
+ */
161
+ public function cas($token, $key, $value, $expire = 0)
162
+ {
163
+ $result = $this->transaction->cas($token, $key, $value, $expire);
164
+ $this->transaction->commit();
165
+
166
+ return $result;
167
+ }
168
+
169
+ /**
170
+ * {@inheritdoc}
171
+ */
172
+ public function increment($key, $offset = 1, $initial = 0, $expire = 0)
173
+ {
174
+ $result = $this->transaction->increment($key, $offset, $initial, $expire);
175
+ $this->transaction->commit();
176
+
177
+ return $result;
178
+ }
179
+
180
+ /**
181
+ * {@inheritdoc}
182
+ */
183
+ public function decrement($key, $offset = 1, $initial = 0, $expire = 0)
184
+ {
185
+ $result = $this->transaction->decrement($key, $offset, $initial, $expire);
186
+ $this->transaction->commit();
187
+
188
+ return $result;
189
+ }
190
+
191
+ /**
192
+ * {@inheritdoc}
193
+ */
194
+ public function touch($key, $expire)
195
+ {
196
+ $result = $this->transaction->touch($key, $expire);
197
+ $this->transaction->commit();
198
+
199
+ return $result;
200
+ }
201
+
202
+ /**
203
+ * {@inheritdoc}
204
+ */
205
+ public function flush()
206
+ {
207
+ foreach ($this->collections as $collection) {
208
+ $collection->flush();
209
+ }
210
+
211
+ $result = $this->transaction->flush();
212
+ $this->transaction->commit();
213
+
214
+ return $result;
215
+ }
216
+
217
+ /**
218
+ * {@inheritdoc}
219
+ */
220
+ public function getCollection($name)
221
+ {
222
+ if (!isset($this->collections[$name])) {
223
+ $collection = $this->transaction->getCollection($name);
224
+ $this->collections[$name] = new static($collection);
225
+ }
226
+
227
+ return $this->collections[$name];
228
+ }
229
+ }
vendor/browscap-php/matthiasmullie/scrapbook/src/Buffered/TransactionalStore.php ADDED
@@ -0,0 +1,255 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace MatthiasMullie\Scrapbook\Buffered;
4
+
5
+ use MatthiasMullie\Scrapbook\Buffered\Utils\Buffer;
6
+ use MatthiasMullie\Scrapbook\Buffered\Utils\Transaction;
7
+ use MatthiasMullie\Scrapbook\Exception\UnbegunTransaction;
8
+ use MatthiasMullie\Scrapbook\KeyValueStore;
9
+
10
+ /**
11
+ * In addition to buffering cache data in memory (see BufferedStore), this class
12
+ * will add transactional capabilities. Writes can be deferred by starting a
13
+ * transaction & all of them will only go out when you commit them.
14
+ * This makes it possible to defer cache updates until we can guarantee it's
15
+ * safe (e.g. until we successfully committed everything to persistent storage).
16
+ *
17
+ * There will be some trickery to make sure that, after we've made changes to
18
+ * cache (but not yet committed), we don't read from the real cache anymore, but
19
+ * instead serve the in-memory equivalent that we'll be writing to real cache
20
+ * when all goes well.
21
+ *
22
+ * If a commit fails, all keys affected will be deleted to ensure no corrupt
23
+ * data stays behind.
24
+ *
25
+ * @author Matthias Mullie <scrapbook@mullie.eu>
26
+ * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
27
+ * @license LICENSE MIT
28
+ */
29
+ class TransactionalStore implements KeyValueStore
30
+ {
31
+ /**
32
+ * Array of KeyValueStore objects. Every cache action will be executed
33
+ * on the last item in this array, so transactions can be nested.
34
+ *
35
+ * @var KeyValueStore[]
36
+ */
37
+ protected $transactions = array();
38
+
39
+ /**
40
+ * @param KeyValueStore $cache The real cache we'll buffer for
41
+ */
42
+ public function __construct(KeyValueStore $cache)
43
+ {
44
+ $this->transactions[] = $cache;
45
+ }
46
+
47
+ /**
48
+ * Roll back uncommitted transactions.
49
+ */
50
+ public function __destruct()
51
+ {
52
+ while (count($this->transactions) > 1) {
53
+ /** @var Transaction $transaction */
54
+ $transaction = array_pop($this->transactions);
55
+ $transaction->rollback();
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Initiate a transaction: this will defer all writes to real cache until
61
+ * commit() is called.
62
+ */
63
+ public function begin()
64
+ {
65
+ // we'll rely on buffer to respond data that has not yet committed, so
66
+ // it must never evict from cache - I'd even rather see the app crash
67
+ $buffer = new Buffer(ini_get('memory_limit'));
68
+
69
+ // transactions can be nested: the previous transaction will serve as
70
+ // cache backend for the new cache (so when committing a nested
71
+ // transaction, it will commit to the parent transaction)
72
+ $cache = end($this->transactions);
73
+ $this->transactions[] = new Transaction($buffer, $cache);
74
+ }
75
+
76
+ /**
77
+ * Commits all deferred updates to real cache.
78
+ * If the any write fails, all subsequent writes will be aborted & all keys
79
+ * that had already been written to will be deleted.
80
+ *
81
+ * @return bool
82
+ *
83
+ * @throws UnbegunTransaction
84
+ */
85
+ public function commit()
86
+ {
87
+ if (count($this->transactions) <= 1) {
88
+ throw new UnbegunTransaction('Attempted to commit without having begun a transaction.');
89
+ }
90
+
91
+ /** @var Transaction $transaction */
92
+ $transaction = array_pop($this->transactions);
93
+
94
+ return $transaction->commit();
95
+ }
96
+
97
+ /**
98
+ * Roll back all scheduled changes.
99
+ *
100
+ * @return bool
101
+ *
102
+ * @throws UnbegunTransaction
103
+ */
104
+ public function rollback()
105
+ {
106
+ if (count($this->transactions) <= 1) {
107
+ throw new UnbegunTransaction('Attempted to rollback without having begun a transaction.');
108
+ }
109
+
110
+ /** @var Transaction $transaction */
111
+ $transaction = array_pop($this->transactions);
112
+
113
+ return $transaction->rollback();
114
+ }
115
+
116
+ /**
117
+ * {@inheritdoc}
118
+ */
119
+ public function get($key, &$token = null)
120
+ {
121
+ $cache = end($this->transactions);
122
+
123
+ return $cache->get($key, $token);
124
+ }
125
+
126
+ /**
127
+ * {@inheritdoc}
128
+ */
129
+ public function getMulti(array $keys, array &$tokens = null)
130
+ {
131
+ $cache = end($this->transactions);
132
+
133
+ return $cache->getMulti($keys, $tokens);
134
+ }
135
+
136
+ /**
137
+ * {@inheritdoc}
138
+ */
139
+ public function set($key, $value, $expire = 0)
140
+ {
141
+ $cache = end($this->transactions);
142
+
143
+ return $cache->set($key, $value, $expire);
144
+ }
145
+
146
+ /**
147
+ * {@inheritdoc}
148
+ */
149
+ public function setMulti(array $items, $expire = 0)
150
+ {
151
+ $cache = end($this->transactions);
152
+
153
+ return $cache->setMulti($items, $expire);
154
+ }
155
+
156
+ /**
157
+ * {@inheritdoc}
158
+ */
159
+ public function delete($key)
160
+ {
161
+ $cache = end($this->transactions);
162
+
163
+ return $cache->delete($key);
164
+ }
165
+
166
+ /**
167
+ * {@inheritdoc}
168
+ */
169
+ public function deleteMulti(array $keys)
170
+ {
171
+ $cache = end($this->transactions);
172
+
173
+ return $cache->deleteMulti($keys);
174
+ }
175
+
176
+ /**
177
+ * {@inheritdoc}
178
+ */
179
+ public function add($key, $value, $expire = 0)
180
+ {
181
+ $cache = end($this->transactions);
182
+
183
+ return $cache->add($key, $value, $expire);
184
+ }
185
+
186
+ /**
187
+ * {@inheritdoc}
188
+ */
189
+ public function replace($key, $value, $expire = 0)
190
+ {
191
+ $cache = end($this->transactions);
192
+
193
+ return $cache->replace($key, $value, $expire);
194
+ }
195
+
196
+ /**
197
+ * {@inheritdoc}
198
+ */
199
+ public function cas($token, $key, $value, $expire = 0)
200
+ {
201
+ $cache = end($this->transactions);
202
+
203
+ return $cache->cas($token, $key, $value, $expire);
204
+ }
205
+
206
+ /**
207
+ * {@inheritdoc}
208
+ */
209
+ public function increment($key, $offset = 1, $initial = 0, $expire = 0)
210
+ {
211
+ $cache = end($this->transactions);
212
+
213
+ return $cache->increment($key, $offset, $initial, $expire);
214
+ }
215
+
216
+ /**
217
+ * {@inheritdoc}
218
+ */
219
+ public function decrement($key, $offset = 1, $initial = 0, $expire = 0)
220
+ {
221
+ $cache = end($this->transactions);
222
+
223
+ return $cache->decrement($key, $offset, $initial, $expire);
224
+ }
225
+
226
+ /**
227
+ * {@inheritdoc}
228
+ */
229
+ public function touch($key, $expire)
230
+ {
231
+ $cache = end($this->transactions);
232
+
233
+ return $cache->touch($key, $expire);
234
+ }
235
+
236
+ /**
237
+ * {@inheritdoc}
238
+ */
239
+ public function flush()
240
+ {
241
+ $cache = end($this->transactions);
242
+
243
+ return $cache->flush();
244
+ }
245
+
246
+ /**
247
+ * {@inheritdoc}
248
+ */
249
+ public function getCollection($name)
250
+ {
251
+ $cache = end($this->transactions);
252
+
253
+ return new static($cache->getCollection($name));
254
+ }
255
+ }
vendor/browscap-php/matthiasmullie/scrapbook/src/Buffered/Utils/Buffer.php ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace MatthiasMullie\Scrapbook\Buffered\Utils;
4
+
5
+ use MatthiasMullie\Scrapbook\Adapters\MemoryStore;
6
+
7
+ /**
8
+ * This is a helper class for BufferedStore & TransactionalStore, which buffer
9
+ * real cache requests in memory.
10
+ * The memory-part can easily be handled by MemoryStore. There's just 1 gotcha:
11
+ * when an item is to be deleted (but not yet committed), it needs to be deleted
12
+ * from the MemoryStore too, but we need to be able to make a distinction
13
+ * between "this is deleted" and "this value is not known in this memory cache,
14
+ * fall back to real cache".
15
+ *
16
+ * This is where this class comes in to play: we'll add an additional "expired"
17
+ * method, which allows BufferedStore to just expire the keys that are supposed
18
+ * to be deleted (instead of deleting them) - then we can keep track of when
19
+ * a key is just not known, or known-but-deleted (=expired)
20
+ *
21
+ * @author Matthias Mullie <scrapbook@mullie.eu>
22
+ * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
23
+ * @license LICENSE MIT
24
+ */
25
+ class Buffer extends MemoryStore
26
+ {
27
+ /**
28
+ * Make items publicly available - if we create a collection from this,
29
+ * that collection will need to be able to access these items to determine
30
+ * if something has expired.
31
+ *
32
+ * @var array
33
+ */
34
+ public $items = array();
35
+
36
+ /**
37
+ * Checks if a value exists in cache and is not yet expired.
38
+ * Contrary to default MemoryStore, expired items must *not* be deleted
39
+ * from memory: we need to remember that they were expired, so we don't
40
+ * reach out to real cache (only to get nothing, since it's expired...).
41
+ *
42
+ * @param string $key
43
+ *
44
+ * @return bool
45
+ */
46
+ protected function exists($key)
47
+ {
48
+ if (!array_key_exists($key, $this->items)) {
49
+ // key not in cache
50
+ return false;
51
+ }
52
+
53
+ $expire = $this->items[$key][1];
54
+ if (0 !== $expire && $expire < time()) {
55
+ // not permanent & already expired
56
+ return false;
57
+ }
58
+
59
+ $this->lru($key);
60
+
61
+ return true;
62
+ }
63
+
64
+ /**
65
+ * Check if a key existed in local storage, but is now expired.
66
+ *
67
+ * Because our local buffer is also just a real cache, expired items will
68
+ * just return nothing, which will lead us to believe no such item exists in
69
+ * that local cache, and we'll reach out to the real cache (where the value
70
+ * may not yet have been expired because that may have been part of an
71
+ * uncommitted write)
72
+ * So we'll want to know when a value is in local cache, but expired!
73
+ *
74
+ * @param string $key
75
+ *
76
+ * @return bool
77
+ */
78
+ public function expired($key)
79
+ {
80
+ if (false !== $this->get($key)) {
81
+ // returned a value, clearly not yet expired
82
+ return false;
83
+ }
84
+
85
+ // a known item, not returned by get, is expired
86
+ return array_key_exists($key, $this->items);
87
+ }
88
+
89
+ /**
90
+ * {@inheritdoc}
91
+ */
92
+ public function getCollection($name)
93
+ {
94
+ return new BufferCollection($this, $name);
95
+ }
96
+ }
vendor/browscap-php/matthiasmullie/scrapbook/src/Buffered/Utils/BufferCollection.php ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace MatthiasMullie\Scrapbook\Buffered\Utils;
4
+
5
+ use MatthiasMullie\Scrapbook\Adapters\Collections\MemoryStore as MemoryStoreCollection;
6
+
7
+ /**
8
+ * A collection implementation for Buffer.
9
+ *
10
+ * @author Matthias Mullie <scrapbook@mullie.eu>
11
+ * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
12
+ * @license LICENSE MIT
13
+ */
14
+ class BufferCollection extends MemoryStoreCollection
15
+ {
16
+ /**
17
+ * @var Buffer
18
+ */
19
+ protected $cache;
20
+
21
+ /**
22
+ * @param string $name
23
+ */
24
+ public function __construct(Buffer $cache, $name)
25
+ {
26
+ parent::__construct($cache, $name);
27
+ }
28
+
29
+ /**
30
+ * Check if a key existed in local storage, but is now expired.
31
+ *
32
+ * Because our local buffer is also just a real cache, expired items will
33
+ * just return nothing, which will lead us to believe no such item exists in
34
+ * that local cache, and we'll reach out to the real cache (where the value
35
+ * may not yet have been expired because that may have been part of an
36
+ * uncommitted write)
37
+ * So we'll want to know when a value is in local cache, but expired!
38
+ *
39
+ * @param string $key
40
+ *
41
+ * @return bool
42
+ */
43
+ public function expired($key)
44
+ {
45
+ if (false !== $this->get($key)) {
46
+ // returned a value, clearly not yet expired
47
+ return false;
48
+ }
49
+
50
+ // a known item, not returned by get, is expired
51
+ return array_key_exists($key, $this->cache->items);
52
+ }
53
+ }
vendor/browscap-php/matthiasmullie/scrapbook/src/Buffered/Utils/Defer.php ADDED
@@ -0,0 +1,576 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace MatthiasMullie\Scrapbook\Buffered\Utils;
4
+
5
+ use MatthiasMullie\Scrapbook\Exception\UncommittedTransaction;
6
+ use MatthiasMullie\Scrapbook\KeyValueStore;
7
+
8
+ /**
9
+ * This is a helper class for transactions. It will optimize the write going
10
+ * out and take care of rolling back.
11
+ *
12
+ * Optimizations will be:
13
+ * * multiple set() values (with the same expiration) will be applied in a
14
+ * single setMulti()
15
+ * * for a set() followed by another set() on the same key, only the latter
16
+ * one will be applied
17
+ * * same for an replace() followed by an increment(), or whatever operation
18
+ * happens on the same key: if we can pre-calculate the end result, we'll
19
+ * only execute 1 operation with the end result
20
+ * * operations before a flush() will not be executed, they'll just be lost
21
+ *
22
+ * Rollback strategy includes:
23
+ * * fetching the original value of operations prone to fail (add, replace &
24
+ * cas) prior to executing them
25
+ * * executing said operations before the others, to minimize changes of
26
+ * interfering concurrent writes
27
+ * * if the commit fails, said original values will be restored in case the
28
+ * new value had already been stored
29
+ *
30
+ * This class must never receive invalid data. E.g. a "replace" can never
31
+ * follow a "delete" of the same key. This should be guaranteed by whatever
32
+ * uses this class: there is no point in re-implementing these checks here.
33
+ * The only acceptable conflicts are when cache values have changed outside,
34
+ * from another process. Those will be handled by this class.
35
+ *
36
+ * @author Matthias Mullie <scrapbook@mullie.eu>
37
+ * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
38
+ * @license LICENSE MIT
39
+ */
40
+ class Defer
41
+ {
42
+ /**
43
+ * Cache to write to.
44
+ *
45
+ * @var KeyValueStore
46
+ */
47
+ protected $cache;
48
+
49
+ /**
50
+ * All updates will be scheduled by key. If there are multiple updates
51
+ * for a key, they can just be folded together.
52
+ * E.g. 2 sets, the later will override the former.
53
+ * E.g. set + increment, might as well set incremented value immediately.
54
+ *
55
+ * This is going to be an array that holds horrible arrays of update data,
56
+ * being:
57
+ * * 0: the operation name (set, add, ...) so we're able to sort them
58
+ * * 1: a callable, to apply the update to cache
59
+ * * 2: the array of data to supply to the callable
60
+ *
61
+ * @var array[]
62
+ */
63
+ protected $keys = array();
64
+
65
+ /**
66
+ * Flush is special - it's not specific to (a) key(s), so we can't store
67
+ * it to $keys.
68
+ *
69
+ * @var bool
70
+ */
71
+ protected $flush = false;
72
+
73
+ public function __construct(KeyValueStore $cache)
74
+ {
75
+ $this->cache = $cache;
76
+ }
77
+
78
+ /**
79
+ * @throws UncommittedTransaction
80
+ */
81
+ public function __destruct()
82
+ {
83
+ if (!empty($this->keys)) {
84
+ throw new UncommittedTransaction('Transaction is about to be destroyed without having been '.'committed or rolled back.');
85
+ }
86
+ }
87
+
88
+ /**
89
+ * @param string $key
90
+ * @param mixed $value
91
+ * @param int $expire
92
+ */
93
+ public function set($key, $value, $expire)
94
+ {
95
+ $args = array(
96
+ 'key' => $key,
97
+ 'value' => $value,
98
+ 'expire' => $expire,
99
+ );
100
+ $this->keys[$key] = array(__FUNCTION__, array($this->cache, __FUNCTION__), $args);
101
+ }
102
+
103
+ /**
104
+ * @param mixed[] $items
105
+ * @param int $expire
106
+ */
107
+ public function setMulti(array $items, $expire)
108
+ {
109
+ foreach ($items as $key => $value) {
110
+ $this->set($key, $value, $expire);
111
+ }
112
+ }
113
+
114
+ /**
115
+ * @param string $key
116
+ */
117
+ public function delete($key)
118
+ {
119
+ $args = array('key' => $key);
120
+ $this->keys[$key] = array(__FUNCTION__, array($this->cache, __FUNCTION__), $args);
121
+ }
122
+
123
+ /**
124
+ * @param string[] $keys
125
+ */
126
+ public function deleteMulti(array $keys)
127
+ {
128
+ foreach ($keys as $key) {
129
+ $this->delete($key);
130
+ }
131
+ }
132
+
133
+ /**
134
+ * @param string $key
135
+ * @param mixed $value
136
+ * @param int $expire
137
+ */
138
+ public function add($key, $value, $expire)
139
+ {
140
+ $args = array(
141
+ 'key' => $key,
142
+ 'value' => $value,
143
+ 'expire' => $expire,
144
+ );
145
+ $this->keys[$key] = array(__FUNCTION__, array($this->cache, __FUNCTION__), $args);
146
+ }
147
+
148
+ /**
149
+ * @param string $key
150
+ * @param mixed $value
151
+ * @param int $expire
152
+ */
153
+ public function replace($key, $value, $expire)
154
+ {
155
+ $args = array(
156
+ 'key' => $key,
157
+ 'value' => $value,
158
+ 'expire' => $expire,
159
+ );
160
+ $this->keys[$key] = array(__FUNCTION__, array($this->cache, __FUNCTION__), $args);
161
+ }
162
+
163
+ /**
164
+ * @param mixed $originalValue No real CAS token, but the original value for this key
165
+ * @param string $key
166
+ * @param mixed $value
167
+ * @param int $expire
168
+ */
169
+ public function cas($originalValue, $key, $value, $expire)
170
+ {
171
+ /*
172
+ * If we made it here, we're sure that logically, the CAS applies with
173
+ * respect to other operations in this transaction. That means we don't
174
+ * have to verify the token here: whatever has already been set/add/
175
+ * replace/cas will have taken care of that and we already know this one
176
+ * applies on top op that change. We can just fold it in there & update
177
+ * the value we set initially.
178
+ */
179
+ if (isset($this->keys[$key]) && in_array($this->keys[$key][0], array('set', 'add', 'replace', 'cas'))) {
180
+ $this->keys[$key][2]['value'] = $value;
181
+ $this->keys[$key][2]['expire'] = $expire;
182
+
183
+ return;
184
+ }
185
+
186
+ /*
187
+ * @param mixed $originalValue
188
+ * @param string $key
189
+ * @param mixed $value
190
+ * @param int $expire
191
+ * @return bool
192
+ */
193
+ $cache = $this->cache;
194
+ $callback = function ($originalValue, $key, $value, $expire) use ($cache) {
195
+ // check if given (local) CAS token was known
196
+ if (null === $originalValue) {
197
+ return false;
198
+ }
199
+
200
+ // fetch data from real cache, getting new valid CAS token
201
+ $current = $cache->get($key, $token);
202
+
203
+ // check if the value we just read from real cache is still the same
204
+ // as the one we saved when doing the original fetch
205
+ if (serialize($current) === $originalValue) {
206
+ // everything still checked out, CAS the value for real now
207
+ return $cache->cas($token, $key, $value, $expire);
208
+ }
209
+
210
+ return false;
211
+ };
212
+
213
+ $args = array(
214
+ 'originalValue' => $originalValue,
215
+ 'key' => $key,
216
+ 'value' => $value,
217
+ 'expire' => $expire,
218
+ );
219
+ $this->keys[$key] = array(__FUNCTION__, $callback, $args);
220
+ }
221
+
222
+ /**
223
+ * @param string $key
224
+ * @param int $offset
225
+ * @param int $initial
226
+ * @param int $expire
227
+ */
228
+ public function increment($key, $offset, $initial, $expire)
229
+ {
230
+ $this->doIncrement(__FUNCTION__, $key, $offset, $initial, $expire);
231
+ }
232
+
233
+ /**
234
+ * @param string $key
235
+ * @param int $offset
236
+ * @param int $initial
237
+ * @param int $expire
238
+ */
239
+ public function decrement($key, $offset, $initial, $expire)
240
+ {
241
+ $this->doIncrement(__FUNCTION__, $key, $offset, $initial, $expire);
242
+ }
243
+
244
+ /**
245
+ * @param string $operation
246
+ * @param string $key
247
+ * @param int $offset
248
+ * @param int $initial
249
+ * @param int $expire
250
+ */
251
+ protected function doIncrement($operation, $key, $offset, $initial, $expire)
252
+ {
253
+ if (isset($this->keys[$key])) {
254
+ if (in_array($this->keys[$key][0], array('set', 'add', 'replace', 'cas'))) {
255
+ // we're trying to increment a key that's only just being stored
256
+ // in this transaction - might as well combine those
257
+ $symbol = 'increment' === $this->keys[$key][1] ? 1 : -1;
258
+ $this->keys[$key][2]['value'] += $symbol * $offset;
259
+ $this->keys[$key][2]['expire'] = $expire;
260
+ } elseif (in_array($this->keys[$key][0], array('increment', 'decrement'))) {
261
+ // we're trying to increment a key that's already being incremented
262
+ // or decremented in this transaction - might as well combine those
263
+
264
+ // we may be combining an increment with a decrement
265
+ // we must carefully figure out how these 2 apply against each other
266
+ $symbol = 'increment' === $this->keys[$key][0] ? 1 : -1;
267
+ $previous = $symbol * $this->keys[$key][2]['offset'];
268
+
269
+ $symbol = 'increment' === $operation ? 1 : -1;
270
+ $current = $symbol * $offset;
271
+
272
+ $offset = $previous + $current;
273
+
274
+ $this->keys[$key][2]['offset'] = abs($offset);
275
+ // initial value must also be adjusted to include the new offset
276
+ $this->keys[$key][2]['initial'] += $current;
277
+ $this->keys[$key][2]['expire'] = $expire;
278
+
279
+ // adjust operation - it might just have switched from increment to
280
+ // decrement or vice versa
281
+ $operation = $offset >= 0 ? 'increment' : 'decrement';
282
+ $this->keys[$key][0] = $operation;
283
+ $this->keys[$key][1] = array($this->cache, $operation);
284
+ } else {
285
+ // touch & delete become useless if incrementing/decrementing after
286
+ unset($this->keys[$key]);
287
+ }
288
+ }
289
+
290
+ if (!isset($this->keys[$key])) {
291
+ $args = array(
292
+ 'key' => $key,
293
+ 'offset' => $offset,
294
+ 'initial' => $initial,
295
+ 'expire' => $expire,
296
+ );
297
+ $this->keys[$key] = array($operation, array($this->cache, $operation), $args);
298
+ }
299
+ }
300
+
301
+ /**
302
+ * @param string $key
303
+ * @param int $expire
304
+ */
305
+ public function touch($key, $expire)
306
+ {
307
+ if (isset($this->keys[$key]) && isset($this->keys[$key][2]['expire'])) {
308
+ // changing expiration time of a value we're already storing in
309
+ // this transaction - might as well just set new expiration time
310
+ // right away
311
+ $this->keys[$key][2]['expire'] = $expire;
312
+ } else {
313
+ $args = array(
314
+ 'key' => $key,
315
+ 'expire' => $expire,
316
+ );
317
+ $this->keys[$key] = array(__FUNCTION__, array($this->cache, __FUNCTION__), $args);
318
+ }
319
+ }
320
+
321
+ public function flush()
322
+ {
323
+ // clear all scheduled updates, they'll be wiped out after this anyway
324
+ $this->keys = array();
325
+ $this->flush = true;
326
+ }
327
+
328
+ /**
329
+ * Clears all scheduled writes.
330
+ */
331
+ public function clear()
332
+ {
333
+ $this->keys = array();
334
+ $this->flush = false;
335
+ }
336
+
337
+ /**
338
+ * Commit all deferred writes to cache.
339
+ *
340
+ * When the commit fails, no changes in this transaction will be applied
341
+ * (and those that had already been applied will be undone). False will
342
+ * be returned in that case.
343
+ *
344
+ * @return bool
345
+ */
346
+ public function commit()
347
+ {
348
+ list($old, $new) = $this->generateRollback();
349
+ $updates = $this->generateUpdates();
350
+ $updates = $this->combineUpdates($updates);
351
+ usort($updates, array($this, 'sortUpdates'));
352
+
353
+ foreach ($updates as $update) {
354
+ // apply update to cache & receive a simple bool to indicate
355
+ // success (true) or failure (false)
356
+ $success = call_user_func_array($update[1], $update[2]);
357
+ if (false === $success) {
358
+ $this->rollback($old, $new);
359
+
360
+ return false;
361
+ }
362
+ }
363
+
364
+ $this->clear();
365
+
366
+ return true;
367
+ }
368
+
369
+ /**
370
+ * Roll the cache back to pre-transaction state by comparing the current
371
+ * cache values with what we planned to set them to.
372
+ */
373
+ protected function rollback(array $old, array $new)
374
+ {
375
+ foreach ($old as $key => $value) {
376
+ $current = $this->cache->get($key, $token);
377
+
378
+ /*
379
+ * If the value right now equals the one we planned to write, it
380
+ * should be restored to what it was before. If it's yet something
381
+ * else, another process must've stored it and we should leave it
382
+ * alone.
383
+ */
384
+ if ($current === $new) {
385
+ /*
386
+ * CAS the rollback. If it fails, that means another process
387
+ * has stored in the meantime and we can just leave it alone.
388
+ * Note that we can't know the original expiration time!
389
+ */
390
+ $this->cas($token, $key, $value, 0);
391
+ }
392
+ }
393
+
394
+ $this->clear();
395
+ }
396
+
397
+ /**
398
+ * Since we can't perform true atomic transactions, we'll fake it.
399
+ * Most of the operations (set, touch, ...) can't fail. We'll do those last.
400
+ * We'll first schedule the operations that can fail (cas, replace, add)
401
+ * to minimize chances of another process overwriting those values in the
402
+ * meantime.
403
+ * But it could still happen, so we should fetch the current values for all
404
+ * unsafe operations. If the transaction fails, we can then restore them.
405
+ *
406
+ * @return array[] Array of 2 [key => value] maps: current & scheduled data
407
+ */
408
+ protected function generateRollback()
409
+ {
410
+ $keys = array();
411
+ $new = array();
412
+
413
+ foreach ($this->keys as $key => $data) {
414
+ $operation = $data[0];
415
+
416
+ // we only need values for cas & replace - recovering from an 'add'
417
+ // is just deleting the value...
418
+ if (in_array($operation, array('cas', 'replace'))) {
419
+ $keys[] = $key;
420
+ $new[$key] = $data[2]['value'];
421
+ }
422
+ }
423
+
424
+ if (empty($keys)) {
425
+ return array(array(), array());
426
+ }
427
+
428
+ // fetch the existing data & return the planned new data as well
429
+ $current = $this->cache->getMulti($keys);
430
+
431
+ return array($current, $new);
432
+ }
433
+
434
+ /**
435
+ * By storing all updates by key, we've already made sure we don't perform
436
+ * redundant operations on a per-key basis. Now we'll turn those into
437
+ * actual updates.
438
+ *
439
+ * @return array
440
+ */
441
+ protected function generateUpdates()
442
+ {
443
+ $updates = array();
444
+
445
+ if ($this->flush) {
446
+ $updates[] = array('flush', array($this->cache, 'flush'), array());
447
+ }
448
+
449
+ foreach ($this->keys as $key => $data) {
450
+ $updates[] = $data;
451
+ }
452
+
453
+ return $updates;
454
+ }
455
+
456
+ /**
457
+ * We may have multiple sets & deletes, which can be combined into a single
458
+ * setMulti or deleteMulti operation.
459
+ *
460
+ * @param array $updates
461
+ *
462
+ * @return array
463
+ */
464
+ protected function combineUpdates($updates)
465
+ {
466
+ $setMulti = array();
467
+ $deleteMulti = array();
468
+
469
+ foreach ($updates as $i => $update) {
470
+ $operation = $update[0];
471
+ $args = $update[2];
472
+
473
+ switch ($operation) {
474
+ // all set & delete operations can be grouped into setMulti & deleteMulti
475
+ case 'set':
476
+ unset($updates[$i]);
477
+
478
+ // only group sets with same expiration
479
+ $setMulti[$args['expire']][$args['key']] = $args['value'];
480
+ break;
481
+ case 'delete':
482
+ unset($updates[$i]);
483
+
484
+ $deleteMulti[] = $args['key'];
485
+ break;
486
+ default:
487
+ break;
488
+ }
489
+ }
490
+
491
+ if (!empty($setMulti)) {
492
+ $cache = $this->cache;
493
+
494
+ /*
495
+ * We'll use the return value of all deferred writes to check if they
496
+ * should be rolled back.
497
+ * commit() expects a single bool, not a per-key array of success bools.
498
+ *
499
+ * @param mixed[] $items
500
+ * @param int $expire
501
+ * @return bool
502
+ */
503
+ $callback = function ($items, $expire) use ($cache) {
504
+ $success = $cache->setMulti($items, $expire);
505
+
506
+ return !in_array(false, $success);
507
+ };
508
+
509
+ foreach ($setMulti as $expire => $items) {
510
+ $updates[] = array('setMulti', $callback, array($items, $expire));
511
+ }
512
+ }
513
+
514
+ if (!empty($deleteMulti)) {
515
+ $cache = $this->cache;
516
+
517
+ /*
518
+ * commit() expected a single bool, not an array of success bools.
519
+ * Besides, deleteMulti() is never cause for failure here: if the
520
+ * key didn't exist because it has been deleted elsewhere already,
521
+ * the data isn't corrupt, it's still as we'd expect it.
522
+ *
523
+ * @param string[] $keys
524
+ * @return bool
525
+ */
526
+ $callback = function ($keys) use ($cache) {
527
+ $cache->deleteMulti($keys);
528
+
529
+ return true;
530
+ };
531
+
532
+ $updates[] = array('deleteMulti', $callback, array($deleteMulti));
533
+ }
534
+
535
+ return $updates;
536
+ }
537
+
538
+ /**
539
+ * Change the order of the updates in this transaction to ensure we have those
540
+ * most likely to fail first. That'll decrease odds of having to roll back, and
541
+ * make rolling back easier.
542
+ *
543
+ * @param array $a Update, where index 0 is the operation name
544
+ * @param array $b Update, where index 0 is the operation name
545
+ *
546
+ * @return int
547
+ */
548
+ protected function sortUpdates(array $a, array $b)
549
+ {
550
+ $updateOrder = array(
551
+ // there's no point in applying this after doing the below updates
552
+ // we also shouldn't really worry about cas/replace failing after this,
553
+ // there won't be any after cache having been flushed
554
+ 'flush',
555
+
556
+ // prone to fail: they depend on certain conditions (token must match
557
+ // or value must (not) exist)
558
+ 'cas',
559
+ 'replace',
560
+ 'add',
561
+
562
+ // unlikely/impossible to fail, assuming the input is valid
563
+ 'touch',
564
+ 'increment',
565
+ 'decrement',
566
+ 'set', 'setMulti',
567
+ 'delete', 'deleteMulti',
568
+ );
569
+
570
+ if ($a[0] === $b[0]) {
571
+ return 0;
572
+ }
573
+
574
+ return array_search($a[0], $updateOrder) < array_search($b[0], $updateOrder) ? -1 : 1;
575
+ }
576
+ }
vendor/browscap-php/matthiasmullie/scrapbook/src/Buffered/Utils/Transaction.php ADDED
@@ -0,0 +1,523 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace MatthiasMullie\Scrapbook\Buffered\Utils;
4
+
5
+ use MatthiasMullie\Scrapbook\Adapters\Collections\MemoryStore as BufferCollection;
6
+ use MatthiasMullie\Scrapbook\KeyValueStore;
7
+
8
+ /**
9
+ * This is a helper class for BufferedStore & TransactionalStore, which buffer
10
+ * real cache requests in memory.
11
+ *
12
+ * This class accepts 2 caches: a KeyValueStore object (the real cache) and a
13
+ * Buffer instance (to read data from as long as it hasn't been committed)
14
+ *
15
+ * Every write action will first store the data in the Buffer instance, and
16
+ * then pas update along to $defer.
17
+ * Once commit() is called, $defer will execute all these updates against the
18
+ * real cache. All deferred writes that fail to apply will cause that cache key
19
+ * to be deleted, to ensure cache consistency.
20
+ * Until commit() is called, all data is read from the temporary Buffer instance.
21
+ *
22
+ * @author Matthias Mullie <scrapbook@mullie.eu>
23
+ * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
24
+ * @license LICENSE MIT
25
+ */
26
+ class Transaction implements KeyValueStore
27
+ {
28
+ /**
29
+ * @var KeyValueStore
30
+ */
31
+ protected $cache;
32
+
33
+ /**
34
+ * @var Buffer
35
+ */
36
+ protected $local;
37
+
38
+ /**
39
+ * We'll return stub CAS tokens in order to reliably replay the CAS actions
40
+ * to the real cache. This will hold a map of stub token => value, used to
41
+ * verify when we do the actual CAS.
42
+ *
43
+ * @see cas()
44
+ *
45
+ * @var mixed[]
46
+ */
47
+ protected $tokens = array();
48
+
49
+ /**
50
+ * Deferred updates to be committed to real cache.
51
+ *
52
+ * @var Defer
53
+ */
54
+ protected $defer;
55
+
56
+ /**
57
+ * Suspend reads from real cache. This is used when a flush is issued but it
58
+ * has not yet been committed. In that case, we don't want to fall back to
59
+ * real cache values, because they're about to be flushed.
60
+ *
61
+ * @var bool
62
+ */
63
+ protected $suspend = false;
64
+
65
+ /**
66
+ * @var Transaction[]
67
+ */
68
+ protected $collections = array();
69
+
70
+ /**
71
+ * @param Buffer|BufferCollection $local
72
+ */
73
+ public function __construct(/* Buffer|BufferCollection */ $local, KeyValueStore $cache)
74
+ {
75
+ // can't do double typehint, so let's manually check the type
76
+ if (!$local instanceof Buffer && !$local instanceof BufferCollection) {
77
+ $error = 'Invalid class for $local: '.get_class($local);
78
+ if (class_exists('\TypeError')) {
79
+ throw new \TypeError($error);
80
+ }
81
+ trigger_error($error, E_USER_ERROR);
82
+ }
83
+
84
+ $this->cache = $cache;
85
+
86
+ // (uncommitted) writes must never be evicted (even if that means
87
+ // crashing because we run out of memory)
88
+ $this->local = $local;
89
+
90
+ $this->defer = new Defer($this->cache);
91
+ }
92
+
93
+ /**
94
+ * {@inheritdoc}
95
+ */
96
+ public function get($key, &$token = null)
97
+ {
98
+ $value = $this->local->get($key, $token);
99
+
100
+ // short-circuit reading from real cache if we have an uncommitted flush
101
+ if ($this->suspend && null === $token) {
102
+ // flush hasn't been committed yet, don't read from real cache!
103
+ return false;
104
+ }
105
+
106
+ if (false === $value) {
107
+ if ($this->local->expired($key)) {
108
+ /*
109
+ * Item used to exist in local cache, but is now expired. This
110
+ * is used when values are to be deleted: we don't want to reach
111
+ * out to real storage because that would respond with the not-
112
+ * yet-deleted value.
113
+ */
114
+
115
+ return false;
116
+ }
117
+
118
+ // unknown in local cache = fetch from source cache
119
+ $value = $this->cache->get($key, $token);
120
+ }
121
+
122
+ // no value = quit early, don't generate a useless token
123
+ if (false === $value) {
124
+ return false;
125
+ }
126
+
127
+ /*
128
+ * $token will be unreliable to the deferred updates so generate
129
+ * a custom one and keep the associated value around.
130
+ * Read more details in PHPDoc for function cas().
131
+ * uniqid is ok here. Doesn't really have to be unique across
132
+ * servers, just has to be unique every time it's called in this
133
+ * one particular request - which it is.
134
+ */
135
+ $token = uniqid();
136
+ $this->tokens[$token] = serialize($value);
137
+
138
+ return $value;
139
+ }
140
+
141
+ /**
142
+ * {@inheritdoc}
143
+ */
144
+ public function getMulti(array $keys, array &$tokens = null)
145
+ {
146
+ // retrieve all that we can from local cache
147
+ $values = $this->local->getMulti($keys);
148
+ $tokens = array();
149
+
150
+ // short-circuit reading from real cache if we have an uncommitted flush
151
+ if (!$this->suspend) {
152
+ // figure out which missing key we need to get from real cache
153
+ $keys = array_diff($keys, array_keys($values));
154
+ foreach ($keys as $i => $key) {
155
+ // don't reach out to real cache for keys that are about to be gone
156
+ if ($this->local->expired($key)) {
157
+ unset($keys[$i]);
158
+ }
159
+ }
160
+
161
+ // fetch missing values from real cache
162
+ if ($keys) {
163
+ $missing = $this->cache->getMulti($keys);
164
+ $values += $missing;
165
+ }
166
+ }
167
+
168
+ // any tokens we get will be unreliable, so generate some replacements
169
+ // (more elaborate explanation in get())
170
+ foreach ($values as $key => $value) {
171
+ $token = uniqid();
172
+ $tokens[$key] = $token;
173
+ $this->tokens[$token] = serialize($value);
174
+ }
175
+
176
+ return $values;
177
+ }
178
+
179
+ /**
180
+ * {@inheritdoc}
181
+ */
182
+ public function set($key, $value, $expire = 0)
183
+ {
184
+ // store the value in memory, so that when we ask for it again later in
185
+ // this same request, we get the value we just set
186
+ $success = $this->local->set($key, $value, $expire);
187
+ if (false === $success) {
188
+ return false;
189
+ }
190
+
191
+ $this->defer->set($key, $value, $expire);
192
+
193
+ return true;
194
+ }
195
+
196
+ /**
197
+ * {@inheritdoc}
198
+ */
199
+ public function setMulti(array $items, $expire = 0)
200
+ {
201
+ // store the values in memory, so that when we ask for it again later in
202
+ // this same request, we get the value we just set
203
+ $success = $this->local->setMulti($items, $expire);
204
+
205
+ // only attempt to store those that we've set successfully to local
206
+ $successful = array_intersect_key($items, $success);
207
+ if (!empty($successful)) {
208
+ $this->defer->setMulti($successful, $expire);
209
+ }
210
+
211
+ return $success;
212
+ }
213
+
214
+ /**
215
+ * {@inheritdoc}
216
+ */
217
+ public function delete($key)
218
+ {
219
+ // check the current value to see if it currently exists, so we can
220
+ // properly return true/false as would be expected from KeyValueStore
221
+ $value = $this->get($key);
222
+ if (false === $value) {
223
+ return false;
224
+ }
225
+
226
+ /*
227
+ * To make sure that subsequent get() calls for this key don't return
228
+ * a value (it's supposed to be deleted), we'll make it expired in our
229
+ * temporary bag (as opposed to deleting it from out bag, in which case
230
+ * we'd fall back to fetching it from real store, where the transaction
231
+ * might not yet be committed)
232
+ */
233
+ $this->local->set($key, $value, -1);
234
+
235
+ $this->defer->delete($key);
236
+
237
+ return true;
238
+ }
239
+
240
+ /**
241
+ * {@inheritdoc}
242
+ */
243
+ public function deleteMulti(array $keys)
244
+ {
245
+ // check the current values to see if they currently exists, so we can
246
+ // properly return true/false as would be expected from KeyValueStore
247
+ $items = $this->getMulti($keys);
248
+ $success = array();
249
+ foreach ($keys as $key) {
250
+ $success[$key] = array_key_exists($key, $items);
251
+ }
252
+
253
+ // only attempt to store those that we've deleted successfully to local
254
+ $values = array_intersect_key($success, array_flip($keys));
255
+ if (empty($values)) {
256
+ return array();
257
+ }
258
+
259
+ // mark all as expired in local cache (see comment in delete())
260
+ $this->local->setMulti($values, -1);
261
+
262
+ $this->defer->deleteMulti(array_keys($values));
263
+
264
+ return $success;
265
+ }
266
+
267
+ /**
268
+ * {@inheritdoc}
269
+ */
270
+ public function add($key, $value, $expire = 0)
271
+ {
272
+ // before adding, make sure the value doesn't yet exist (in real cache,
273
+ // nor in memory)
274
+ if (false !== $this->get($key)) {
275
+ return false;
276
+ }
277
+
278
+ // store the value in memory, so that when we ask for it again later
279
+ // in this same request, we get the value we just set
280
+ $success = $this->local->set($key, $value, $expire);
281
+ if (false === $success) {
282
+ return false;
283
+ }
284
+
285
+ $this->defer->add($key, $value, $expire);
286
+
287
+ return true;
288
+ }
289
+
290
+ /**
291
+ * {@inheritdoc}
292
+ */
293
+ public function replace($key, $value, $expire = 0)
294
+ {
295
+ // before replacing, make sure the value actually exists (in real cache,
296
+ // or already created in memory)
297
+ if (false === $this->get($key)) {
298
+ return false;
299
+ }
300
+
301
+ // store the value in memory, so that when we ask for it again later
302
+ // in this same request, we get the value we just set
303
+ $success = $this->local->set($key, $value, $expire);
304
+ if (false === $success) {
305
+ return false;
306
+ }
307
+
308
+ $this->defer->replace($key, $value, $expire);
309
+
310
+ return true;
311
+ }
312
+
313
+ /**
314
+ * Since our CAS is deferred, the CAS token we got from our original
315
+ * get() will likely not be valid by the time we want to store it to
316
+ * the real cache. Imagine this scenario:
317
+ * * a value is fetched from (real) cache
318
+ * * an new value key is CAS'ed (into temp cache - real CAS is deferred)
319
+ * * this key's value is fetched again (this time from temp cache)
320
+ * * and a new value is CAS'ed again (into temp cache...).
321
+ *
322
+ * In this scenario, when we finally want to replay the write actions
323
+ * onto the real cache, the first 3 actions would likely work fine.
324
+ * The last (second CAS) however would not, since it never got a real
325
+ * updated $token from the real cache.
326
+ *
327
+ * To work around this problem, all get() calls will return a unique
328
+ * CAS token and store the value-at-that-time associated with that
329
+ * token. All we have to do when we want to write the data to real cache
330
+ * is, right before was CAS for real, get the value & (real) cas token
331
+ * from storage & compare that value to the one we had stored. If that
332
+ * checks out, we can safely resume the CAS with the real token we just
333
+ * received.
334
+ *
335
+ * {@inheritdoc}
336
+ */
337
+ public function cas($token, $key, $value, $expire = 0)
338
+ {
339
+ $originalValue = isset($this->tokens[$token]) ? $this->tokens[$token] : null;
340
+
341
+ // value is no longer the same as what we used for token
342
+ if (serialize($this->get($key)) !== $originalValue) {
343
+ return false;
344
+ }
345
+
346
+ // "CAS" value to local cache/memory
347
+ $success = $this->local->set($key, $value, $expire);
348
+ if (false === $success) {
349
+ return false;
350
+ }
351
+
352
+ // only schedule the CAS to be performed on real cache if it was OK on
353
+ // local cache
354
+ $this->defer->cas($originalValue, $key, $value, $expire);
355
+
356
+ return true;
357
+ }
358
+
359
+ /**
360
+ * {@inheritdoc}
361
+ */
362
+ public function increment($key, $offset = 1, $initial = 0, $expire = 0)
363
+ {
364
+ if ($offset <= 0 || $initial < 0) {
365
+ return false;
366
+ }
367
+
368
+ // get existing value (from real cache or memory) so we know what to
369
+ // increment in memory (where we may not have anything yet, so we should
370
+ // adjust our initial value to what's already in real cache)
371
+ $value = $this->get($key);
372
+ if (false === $value) {
373
+ $value = $initial - $offset;
374
+ }
375
+
376
+ if (!is_numeric($value) || !is_numeric($offset)) {
377
+ return false;
378
+ }
379
+
380
+ // store the value in memory, so that when we ask for it again later
381
+ // in this same request, we get the value we just set
382
+ $value = max(0, $value + $offset);
383
+ $success = $this->local->set($key, $value, $expire);
384
+ if (false === $success) {
385
+ return false;
386
+ }
387
+
388
+ $this->defer->increment($key, $offset, $initial, $expire);
389
+
390
+ return $value;
391
+ }
392
+
393
+ /**
394
+ * {@inheritdoc}
395
+ */
396
+ public function decrement($key, $offset = 1, $initial = 0, $expire = 0)
397
+ {
398
+ if ($offset <= 0 || $initial < 0) {
399
+ return false;
400
+ }
401
+
402
+ // get existing value (from real cache or memory) so we know what to
403
+ // increment in memory (where we may not have anything yet, so we should
404
+ // adjust our initial value to what's already in real cache)
405
+ $value = $this->get($key);
406
+ if (false === $value) {
407
+ $value = $initial + $offset;
408
+ }
409
+
410
+ if (!is_numeric($value) || !is_numeric($offset)) {
411
+ return false;
412
+ }
413
+
414
+ // store the value in memory, so that when we ask for it again later
415
+ // in this same request, we get the value we just set
416
+ $value = max(0, $value - $offset);
417
+ $success = $this->local->set($key, $value, $expire);
418
+ if (false === $success) {
419
+ return false;
420
+ }
421
+
422
+ $this->defer->decrement($key, $offset, $initial, $expire);
423
+
424
+ return $value;
425
+ }
426
+
427
+ /**
428
+ * {@inheritdoc}
429
+ */
430
+ public function touch($key, $expire)
431
+ {
432
+ // grab existing value (from real cache or memory) and re-save (to
433
+ // memory) with updated expiration time
434
+ $value = $this->get($key);
435
+ if (false === $value) {
436
+ return false;
437
+ }
438
+
439
+ $success = $this->local->set($key, $value, $expire);
440
+ if (false === $success) {
441
+ return false;
442
+ }
443
+
444
+ $this->defer->touch($key, $expire);
445
+
446
+ return true;
447
+ }
448
+
449
+ /**
450
+ * {@inheritdoc}
451
+ */
452
+ public function flush()
453
+ {
454
+ foreach ($this->collections as $collection) {
455
+ $collection->flush();
456
+ }
457
+
458
+ $success = $this->local->flush();
459
+ if (false === $success) {
460
+ return false;
461
+ }
462
+
463
+ // clear all buffered writes, flush wipes them out anyway
464
+ $this->clear();
465
+
466
+ // make sure that reads, from now on until commit, don't read from cache
467
+ $this->suspend = true;
468
+
469
+ $this->defer->flush();
470
+
471
+ return true;
472
+ }
473
+
474
+ /**
475
+ * {@inheritdoc}
476
+ */
477
+ public function getCollection($name)
478
+ {
479
+ if (!isset($this->collections[$name])) {
480
+ $this->collections[$name] = new static(
481
+ $this->local->getCollection($name),
482
+ $this->cache->getCollection($name)
483
+ );
484
+ }
485
+
486
+ return $this->collections[$name];
487
+ }
488
+
489
+ /**
490
+ * Commits all deferred updates to real cache.
491
+ * that had already been written to will be deleted.
492
+ *
493
+ * @return bool
494
+ */
495
+ public function commit()
496
+ {
497
+ $this->clear();
498
+
499
+ return $this->defer->commit();
500
+ }
501
+
502
+ /**
503
+ * Roll back all scheduled changes.
504
+ *
505
+ * @return bool
506
+ */
507
+ public function rollback()
508
+ {
509
+ $this->clear();
510
+ $this->defer->clear();
511
+
512
+ return true;
513
+ }
514
+
515
+ /**
516
+ * Clears all transaction-related data stored in memory.
517
+ */
518
+ protected function clear()
519
+ {
520
+ $this->tokens = array();
521
+ $this->suspend = false;
522
+ }
523
+ }
vendor/browscap-php/matthiasmullie/scrapbook/src/Exception/Exception.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace MatthiasMullie\Scrapbook\Exception;
4
+
5
+ /**
6
+ * @author Matthias Mullie <scrapbook@mullie.eu>
7
+ * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
8
+ * @license LICENSE MIT
9
+ */
10
+ class Exception extends \Exception
11
+ {
12
+ }
vendor/browscap-php/matthiasmullie/scrapbook/src/Exception/InvalidCollection.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace MatthiasMullie\Scrapbook\Exception;
4
+
5
+ /**
6
+ * @author Matthias Mullie <scrapbook@mullie.eu>
7
+ * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
8
+ * @license LICENSE MIT
9
+ */
10
+ class InvalidCollection extends Exception
11
+ {
12
+ }
vendor/browscap-php/matthiasmullie/scrapbook/src/Exception/InvalidKey.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace MatthiasMullie\Scrapbook\Exception;
4
+
5
+ /**
6
+ * @author Matthias Mullie <scrapbook@mullie.eu>
7
+ * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
8
+ * @license LICENSE MIT
9
+ */
10
+ class InvalidKey extends Exception
11
+ {
12
+ }
vendor/browscap-php/matthiasmullie/scrapbook/src/Exception/OperationFailed.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace MatthiasMullie\Scrapbook\Exception;
4
+
5
+ /**
6
+ * @author Martin Georgiev <martin.georgiev@gmail.com>
7
+ * @copyright Copyright (c) 2017, Martin Georgiev. All rights reserved
8
+ * @license LICENSE MIT
9
+ */
10
+ class OperationFailed extends Exception
11
+ {
12
+ }
vendor/browscap-php/matthiasmullie/scrapbook/src/Exception/ServerUnhealthy.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace MatthiasMullie\Scrapbook\Exception;
4
+
5
+ /**
6
+ * @author Matthias Mullie <scrapbook@mullie.eu>
7
+ * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
8
+ * @license LICENSE MIT
9
+ */
10
+ class ServerUnhealthy extends Exception
11
+ {
12
+ }
vendor/browscap-php/matthiasmullie/scrapbook/src/Exception/UnbegunTransaction.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace MatthiasMullie\Scrapbook\Exception;
4
+
5
+ /**
6
+ * @author Matthias Mullie <scrapbook@mullie.eu>
7
+ * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
8
+ * @license LICENSE MIT
9
+ */
10
+ class UnbegunTransaction extends Exception
11
+ {
12
+ }
vendor/browscap-php/matthiasmullie/scrapbook/src/Exception/UncommittedTransaction.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace MatthiasMullie\Scrapbook\Exception;
4
+
5
+ /**
6
+ * @author Matthias Mullie <scrapbook@mullie.eu>
7
+ * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
8
+ * @license LICENSE MIT
9
+ */
10
+ class UncommittedTransaction extends Exception
11
+ {
12
+ }
vendor/browscap-php/matthiasmullie/scrapbook/src/KeyValueStore.php ADDED
@@ -0,0 +1,253 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace MatthiasMullie\Scrapbook;
4
+
5
+ /**
6
+ * Interface for key-value storage engines.
7
+ *
8
+ * @author Matthias Mullie <scrapbook@mullie.eu>
9
+ * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
10
+ * @license LICENSE MIT
11
+ */
12
+ interface KeyValueStore
13
+ {
14
+ /**
15
+ * Retrieves an item from the cache.
16
+ *
17
+ * Optionally, an 2nd variable can be passed to this function. It will be
18
+ * filled with a value that can be used for cas()
19
+ *
20
+ * @param string $key
21
+ * @param mixed $token Will be filled with the CAS token
22
+ *
23
+ * @return mixed|bool Value, or false on failure
24
+ */
25
+ public function get($key, &$token = null);
26
+
27
+ /**
28
+ * Retrieves multiple items at once.
29
+ *
30
+ * Return value will be an associative array in [key => value] format. Keys
31
+ * missing in cache will be omitted from the array.
32
+ *
33
+ * Optionally, an 2nd variable can be passed to this function. It will be
34
+ * filled with values that can be used for cas(), in an associative array in
35
+ * [key => token] format. Keys missing in cache will be omitted from the
36
+ * array.
37
+ *
38
+ * getMulti is preferred over multiple individual get operations as you'll
39
+ * get them all in 1 request.
40
+ *
41
+ * @param mixed[] $tokens Will be filled with the CAS tokens, in [key => token] format
42
+ *
43
+ * @return mixed[] [key => value]
44
+ */
45
+ public function getMulti(array $keys, array &$tokens = null);
46
+
47
+ /**
48
+ * Stores a value, regardless of whether or not the key already exists (in
49
+ * which case it will overwrite the existing value for that key).
50
+ *
51
+ * Return value is a boolean true when the operation succeeds, or false on
52
+ * failure.
53
+ *
54
+ * @param string $key
55
+ * @param mixed $value
56
+ * @param int $expire Time when item falls out of the cache:
57
+ * 0 = permanent (doesn't expires);
58
+ * under 2592000 (30 days) = relative time, in seconds from now;
59
+ * over 2592000 = absolute time, unix timestamp
60
+ *
61
+ * @return bool
62
+ */
63
+ public function set($key, $value, $expire = 0);
64
+
65
+ /**
66
+ * Store multiple values at once.
67
+ *
68
+ * Return value will be an associative array in [key => status] form, where
69
+ * status is a boolean true for success, or false for failure.
70
+ *
71
+ * setMulti is preferred over multiple individual set operations as you'll
72
+ * set them all in 1 request.
73
+ *
74
+ * @param mixed[] $items [key => value]
75
+ * @param int $expire Time when item falls out of the cache:
76
+ * 0 = permanent (doesn't expires);
77
+ * under 2592000 (30 days) = relative time, in seconds from now;
78
+ * over 2592000 = absolute time, unix timestamp
79
+ *
80
+ * @return bool[]
81
+ */
82
+ public function setMulti(array $items, $expire = 0);
83
+
84
+ /**
85
+ * Deletes an item from the cache.
86
+ * Returns true if item existed & was successfully deleted, false otherwise.
87
+ *
88
+ * Return value is a boolean true when the operation succeeds, or false on
89
+ * failure.
90
+ *
91
+ * @param string $key
92
+ *
93
+ * @return bool
94
+ */
95
+ public function delete($key);
96
+
97
+ /**
98
+ * Deletes multiple items at once (reduced network traffic compared to
99
+ * individual operations).
100
+ *
101
+ * Return value will be an associative array in [key => status] form, where
102
+ * status is a boolean true for success, or false for failure.
103
+ *
104
+ * @param string[] $keys
105
+ *
106
+ * @return bool[]
107
+ */
108
+ public function deleteMulti(array $keys);
109
+
110
+ /**
111
+ * Adds an item under new key.
112
+ *
113
+ * This operation fails (returns false) if the key already exists in cache.
114
+ * If the operation succeeds, true will be returned.
115
+ *
116
+ * @param string $key
117
+ * @param mixed $value
118
+ * @param int $expire Time when item falls out of the cache:
119
+ * 0 = permanent (doesn't expires);
120
+ * under 2592000 (30 days) = relative time, in seconds from now;
121
+ * over 2592000 = absolute time, unix timestamp
122
+ *
123
+ * @return bool
124
+ */
125
+ public function add($key, $value, $expire = 0);
126
+
127
+ /**
128
+ * Replaces an item.
129
+ *
130
+ * This operation fails (returns false) if the key does not yet exist in
131
+ * cache. If the operation succeeds, true will be returned.
132
+ *
133
+ * @param string $key
134
+ * @param mixed $value
135
+ * @param int $expire Time when item falls out of the cache:
136
+ * 0 = permanent (doesn't expires);
137
+ * under 2592000 (30 days) = relative time, in seconds from now;
138
+ * over 2592000 = absolute time, unix timestamp
139
+ *
140
+ * @return bool
141
+ */
142
+ public function replace($key, $value, $expire = 0);
143
+
144
+ /**
145
+ * Replaces an item in 1 atomic operation, to ensure it didn't change since
146
+ * it was originally read, when the CAS token was issued.
147
+ *
148
+ * This operation fails (returns false) if the CAS token didn't match with
149
+ * what's currently in cache, when a new value has been written to cache
150
+ * after we've fetched it. If the operation succeeds, true will be returned.
151
+ *
152
+ * @param mixed $token Token received from get() or getMulti()
153
+ * @param string $key
154
+ * @param mixed $value
155
+ * @param int $expire Time when item falls out of the cache:
156
+ * 0 = permanent (doesn't expires);
157
+ * under 2592000 (30 days) = relative time, in seconds from now;
158
+ * over 2592000 = absolute time, unix timestamp
159
+ *
160
+ * @return bool
161
+ */
162
+ public function cas($token, $key, $value, $expire = 0);
163
+
164
+ /**
165
+ * Increments a counter value, or sets an initial value if it does not yet
166
+ * exist.
167
+ *
168
+ * The new counter value will be returned if this operation succeeds, or
169
+ * false for failure (e.g. when the value currently in cache is not a
170
+ * number, in which case it can't be incremented)
171
+ *
172
+ * @param string $key
173
+ * @param int $offset Value to increment with
174
+ * @param int $initial Initial value (if item doesn't yet exist)
175
+ * @param int $expire Time when item falls out of the cache:
176
+ * 0 = permanent (doesn't expires);
177
+ * under 2592000 (30 days) = relative time, in seconds from now;
178
+ * over 2592000 = absolute time, unix timestamp
179
+ *
180
+ * @return int|bool New value or false on failure
181
+ */
182
+ public function increment($key, $offset = 1, $initial = 0, $expire = 0);
183
+
184
+ /**
185
+ * Decrements a counter value, or sets an initial value if it does not yet
186
+ * exist.
187
+ *
188
+ * The new counter value will be returned if this operation succeeds, or
189
+ * false for failure (e.g. when the value currently in cache is not a
190
+ * number, in which case it can't be decremented)
191
+ *
192
+ * @param string $key
193
+ * @param int $offset Value to decrement with
194
+ * @param int $initial Initial value (if item doesn't yet exist)
195
+ * @param int $expire Time when item falls out of the cache:
196
+ * 0 = permanent (doesn't expires);
197
+ * under 2592000 (30 days) = relative time, in seconds from now;
198
+ * over 2592000 = absolute time, unix timestamp
199
+ *
200
+ * @return int|bool New value or false on failure
201
+ */
202
+ public function decrement($key, $offset = 1, $initial = 0, $expire = 0);
203
+
204
+ /**
205
+ * Updates an item's expiration time without altering the stored value.
206
+ *
207
+ * Return value is a boolean true when the operation succeeds, or false on
208
+ * failure.
209
+ *
210
+ * @param string $key
211
+ * @param int $expire Time when item falls out of the cache:
212
+ * 0 = permanent (doesn't expires);
213
+ * under 2592000 (30 days) = relative time, in seconds from now;
214
+ * over 2592000 = absolute time, unix timestamp
215
+ *
216
+ * @return bool
217
+ */
218
+ public function touch($key, $expire);
219
+
220
+ /**
221
+ * Clears the entire cache (or the everything for the given collection).
222
+ *
223
+ * Return value is a boolean true when the operation succeeds, or false on
224
+ * failure.
225
+ *
226
+ * @return bool
227
+ */
228
+ public function flush();
229
+
230
+ /**
231
+ * Returns an isolated subset (collection) in which to store or fetch data
232
+ * from.
233
+ *
234
+ * A new KeyValueStore object will be returned, one that will only have
235
+ * access to this particular subset of data. Exact implementation can vary
236
+ * between adapters (e.g. separate database, prefixed keys, ...), but it
237
+ * will only ever provide access to data within this collection.
238
+ *
239
+ * It is not possible to set/fetch data across collections.
240
+ * Setting the same key in 2 different collections will store 2 different
241
+ * values, that can only be retrieved from their respective collections.
242
+ * Flushing a collection will only flush those specific keys and will leave
243
+ * keys in other collections untouched.
244
+ * Flushing the server, however, will wipe out everything, including data in
245
+ * any of the collections on that server.
246
+ *
247
+ * @param string $name
248
+ *
249
+ * @return KeyValueStore A new KeyValueStore instance representing only a
250
+ * subset of data on this server
251
+ */
252
+ public function getCollection($name);
253
+ }
vendor/browscap-php/matthiasmullie/scrapbook/src/Psr16/InvalidArgumentException.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace MatthiasMullie\Scrapbook\Psr16;
4
+
5
+ use MatthiasMullie\Scrapbook\Exception\Exception;
6
+
7
+ class InvalidArgumentException extends Exception implements \Psr\SimpleCache\InvalidArgumentException
8
+ {
9
+ }
vendor/browscap-php/matthiasmullie/scrapbook/src/Psr16/SimpleCache.php ADDED
@@ -0,0 +1,257 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace MatthiasMullie\Scrapbook\Psr16;
4
+
5
+ use DateInterval;
6
+ use DateTime;
7
+ use MatthiasMullie\Scrapbook\KeyValueStore;
8
+ use Psr\SimpleCache\CacheInterface;
9
+ use Traversable;
10
+
11
+ /**
12
+ * @author Matthias Mullie <scrapbook@mullie.eu>
13
+ * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
14
+ * @license LICENSE MIT
15
+ */
16
+ class SimpleCache implements CacheInterface
17
+ {
18
+ /**
19
+ * List of invalid (or reserved) key characters.
20
+ *
21
+ * @var string
22
+ */
23
+ const KEY_INVALID_CHARACTERS = '{}()/\@:';
24
+
25
+ /**
26
+ * @var KeyValueStore
27
+ */
28
+ protected $store;
29
+
30
+ public function __construct(KeyValueStore $store)
31
+ {
32
+ $this->store = $store;
33
+ }
34
+
35
+ /**
36
+ * {@inheritdoc}
37
+ */
38
+ public function get($key, $default = null)
39
+ {
40
+ $this->assertValidKey($key);
41
+
42
+ // KeyValueStore::get returns false for cache misses (which could also
43
+ // be confused for a `false` value), so we'll check existence with getMulti
44
+ $multi = $this->store->getMulti(array($key));
45
+
46
+ return isset($multi[$key]) ? $multi[$key] : $default;
47
+ }
48
+
49
+ /**
50
+ * {@inheritdoc}
51
+ */
52
+ public function set($key, $value, $ttl = null)
53
+ {
54
+ $this->assertValidKey($key);
55
+ $ttl = $this->ttl($ttl);
56
+
57
+ return $this->store->set($key, $value, $ttl);
58
+ }
59
+
60
+ /**
61
+ * {@inheritdoc}
62
+ */
63
+ public function delete($key)
64
+ {
65
+ $this->assertValidKey($key);
66
+
67
+ $this->store->delete($key);
68
+
69
+ // as long as the item is gone from the cache (even if it never existed
70
+ // and delete failed because of that), we should return `true`
71
+ return true;
72
+ }
73
+
74
+ /**
75
+ * {@inheritdoc}
76
+ */
77
+ public function clear()
78
+ {
79
+ return $this->store->flush();
80
+ }
81
+
82
+ /**
83
+ * {@inheritdoc}
84
+ */
85
+ public function getMultiple($keys, $default = null)
86
+ {
87
+ if ($keys instanceof Traversable) {
88
+ $keys = iterator_to_array($keys, false);
89
+ }
90
+
91
+ if (!is_array($keys)) {
92
+ throw new InvalidArgumentException('Invalid keys: '.var_export($keys, true).'. Keys should be an array or Traversable of strings.');
93
+ }
94
+ array_map(array($this, 'assertValidKey'), $keys);
95
+
96
+ $results = $this->store->getMulti($keys);
97
+
98
+ // KeyValueStore omits values that are not in cache, while PSR-16 will
99
+ // have them with a default value
100
+ $nulls = array_fill_keys($keys, $default);
101
+ $results = array_merge($nulls, $results);
102
+
103
+ return $results;
104
+ }
105
+
106
+ /**
107
+ * {@inheritdoc}
108
+ */
109
+ public function setMultiple($values, $ttl = null)
110
+ {
111
+ if ($values instanceof Traversable) {
112
+ // we also need the keys, and an array is stricter about what it can
113
+ // have as keys than a Traversable is, so we can't use
114
+ // iterator_to_array...
115
+ $array = array();
116
+ foreach ($values as $key => $value) {
117
+ if (!is_string($key) && !is_int($key)) {
118
+ throw new InvalidArgumentException('Invalid values: '.var_export($values, true).'. Only strings are allowed as keys.');
119
+ }
120
+ $array[$key] = $value;
121
+ }
122
+ $values = $array;
123
+ }
124
+
125
+ if (!is_array($values)) {
126
+ throw new InvalidArgumentException('Invalid values: '.var_export($values, true).'. Values should be an array or Traversable with strings as keys.');
127
+ }
128
+
129
+ foreach ($values as $key => $value) {
130
+ // $key is also allowed to be an integer, since ['0' => ...] will
131
+ // automatically convert to [0 => ...]
132
+ $key = is_int($key) ? (string) $key : $key;
133
+ $this->assertValidKey($key);
134
+ }
135
+
136
+ $ttl = $this->ttl($ttl);
137
+ $success = $this->store->setMulti($values, $ttl);
138
+
139
+ return !in_array(false, $success);
140
+ }
141
+
142
+ /**
143
+ * {@inheritdoc}
144
+ */
145
+ public function deleteMultiple($keys)
146
+ {
147
+ if ($keys instanceof Traversable) {
148
+ $keys = iterator_to_array($keys, false);
149
+ }
150
+
151
+ if (!is_array($keys)) {
152
+ throw new InvalidArgumentException('Invalid keys: '.var_export($keys, true).'. Keys should be an array or Traversable of strings.');
153
+ }
154
+ array_map(array($this, 'assertValidKey'), $keys);
155
+
156
+ $this->store->deleteMulti($keys);
157
+
158
+ // as long as the item is gone from the cache (even if it never existed
159
+ // and delete failed because of that), we should return `true`
160
+ return true;
161
+ }
162
+
163
+ /**
164
+ * {@inheritdoc}
165
+ */
166
+ public function has($key)
167
+ {
168
+ $this->assertValidKey($key);
169
+
170
+ // KeyValueStore::get returns false for cache misses (which could also
171
+ // be confused for a `false` value), so we'll check existence with getMulti
172
+ $multi = $this->store->getMulti(array($key));
173
+
174
+ return isset($multi[$key]);
175
+ }
176
+
177
+ /**
178
+ * Throws an exception if $key is invalid.
179
+ *
180
+ * @param string $key
181
+ *
182
+ * @throws InvalidArgumentException
183
+ */
184
+ protected function assertValidKey($key)
185
+ {
186
+ if (!is_string($key)) {
187
+ throw new InvalidArgumentException('Invalid key: '.var_export($key, true).'. Key should be a string.');
188
+ }
189
+
190
+ if ('' === $key) {
191
+ throw new InvalidArgumentException('Invalid key. Key should not be empty.');
192
+ }
193
+
194
+ // valid key according to PSR-16 rules
195
+ $invalid = preg_quote(static::KEY_INVALID_CHARACTERS, '/');
196
+ if (preg_match('/['.$invalid.']/', $key)) {
197
+ throw new InvalidArgumentException('Invalid key: '.$key.'. Contains (a) character(s) reserved '.'for future extension: '.static::KEY_INVALID_CHARACTERS);
198
+ }
199
+ }
200
+
201
+ /**
202
+ * Accepts all TTL inputs valid in PSR-16 (null|int|DateInterval) and
203
+ * converts them into TTL for KeyValueStore (int).
204
+ *
205
+ * @param int|DateInterval|null $ttl
206
+ *
207
+ * @return int
208
+ *
209
+ * @throws \TypeError
210
+ */
211
+ protected function ttl($ttl)
212
+ {
213
+ if (null === $ttl) {
214
+ return 0;
215
+ } elseif (is_int($ttl)) {
216
+ /*
217
+ * PSR-16 specifies that if `0` is provided, it must be treated as
218
+ * expired, whereas KeyValueStore will interpret 0 to mean "never
219
+ * expire".
220
+ */
221
+ if (0 === $ttl) {
222
+ return -1;
223
+ }
224
+
225
+ /*
226
+ * PSR-16 only accepts relative timestamps, whereas KeyValueStore
227
+ * accepts both relative & absolute, depending on what the timestamp
228
+ * is. We'll convert all timestamps > 30 days into absolute
229
+ * timestamps; the others can remain relative, as KeyValueStore will
230
+ * already treat those values as such.
231
+ * @see https://github.com/dragoonis/psr-simplecache/issues/3
232
+ */
233
+ if ($ttl > 30 * 24 * 60 * 60) {
234
+ return $ttl + time();
235
+ }
236
+
237
+ return $ttl;
238
+ } elseif ($ttl instanceof DateInterval) {
239
+ // convert DateInterval to integer by adding it to a 0 DateTime
240
+ $datetime = new DateTime();
241
+ $datetime->setTimestamp(0);
242
+ $datetime->add($ttl);
243
+
244
+ return time() + (int) $datetime->format('U');
245
+ }
246
+
247
+ $error = 'Invalid time: '.serialize($ttl).'. Must be integer or '.
248
+ 'instance of DateInterval.';
249
+
250
+ if (class_exists('\TypeError')) {
251
+ throw new \TypeError($error);
252
+ }
253
+ trigger_error($error, E_USER_ERROR);
254
+
255
+ return 0;
256
+ }
257
+ }
vendor/browscap-php/matthiasmullie/scrapbook/src/Psr6/InvalidArgumentException.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace MatthiasMullie\Scrapbook\Psr6;
4
+
5
+ use MatthiasMullie\Scrapbook\Exception\Exception;
6
+
7
+ class InvalidArgumentException extends Exception implements \Psr\Cache\InvalidArgumentException
8
+ {
9
+ }
vendor/browscap-php/matthiasmullie/scrapbook/src/Psr6/Item.php ADDED
@@ -0,0 +1,234 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace MatthiasMullie\Scrapbook\Psr6;
4
+
5
+ use DateInterval;
6
+ use DateTime;
7
+ use DateTimeInterface;
8
+ use Psr\Cache\CacheItemInterface;
9
+
10
+ /**
11
+ * Representation of a cache item, both existing & non-existing (to be created).
12
+ *
13
+ * @author Matthias Mullie <scrapbook@mullie.eu>
14
+ * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
15
+ * @license LICENSE MIT
16
+ */
17
+ class Item implements CacheItemInterface
18
+ {
19
+ /**
20
+ * @var string
21
+ */
22
+ protected $hash;
23
+
24
+ /**
25
+ * @var string
26
+ */
27
+ protected $key;
28
+
29
+ /**
30
+ * @var Repository
31
+ */
32
+ protected $repository;
33
+
34
+ /**
35
+ * @var mixed
36
+ */
37
+ protected $value;
38
+
39
+ /**
40
+ * @var int
41
+ */
42
+ protected $expire = 0;
43
+
44
+ /**
45
+ * @var bool
46
+ */
47
+ protected $isHit = null;
48
+
49
+ /**
50
+ * @var bool
51
+ */
52
+ protected $changed = false;
53
+
54
+ /**
55
+ * @param string $key
56
+ */
57
+ public function __construct($key, Repository $repository)
58
+ {
59
+ $this->key = $key;
60
+
61
+ /*
62
+ * Register this key (tied to this particular object) to the value
63
+ * repository.
64
+ *
65
+ * If 1 key is requested multiple times, the value could be an object
66
+ * that could be altered (by reference) and if all objects-for-same-key
67
+ * reference that same value, all would've been changed (because all
68
+ * would be that exact same value.)
69
+ *
70
+ * I'm using spl_object_hash to get a unique identifier linking $key to
71
+ * this particular object, without using this object itself (I could use
72
+ * SplObjectStorage.) If I stored this object, it wouldn't be destructed
73
+ * when it's no longer needed, and I want it to destruct so I can free
74
+ * up this value in the repository when it's no longer needed.
75
+ */
76
+ $this->repository = $repository;
77
+ $this->hash = spl_object_hash($this);
78
+ $this->repository->add($this->hash, $this->key);
79
+ }
80
+
81
+ /**
82
+ * When this item is being killed, we should no longer keep its value around
83
+ * in the repository. Free up some memory!
84
+ */
85
+ public function __destruct()
86
+ {
87
+ $this->repository->remove($this->hash);
88
+ }
89
+
90
+ /**
91
+ * {@inheritdoc}
92
+ */
93
+ public function getKey()
94
+ {
95
+ return $this->key;
96
+ }
97
+
98
+ /**
99
+ * {@inheritdoc}
100
+ */
101
+ public function get()
102
+ {
103
+ // value was already set on this object, return that one!
104
+ if (null !== $this->value) {
105
+ return $this->value;
106
+ }
107
+
108
+ // sanity check
109
+ if (!$this->isHit()) {
110
+ return;
111
+ }
112
+
113
+ return $this->repository->get($this->hash);
114
+ }
115
+
116
+ /**
117
+ * {@inheritdoc}
118
+ */
119
+ public function set($value)
120
+ {
121
+ $this->value = $value;
122
+ $this->changed = true;
123
+
124
+ return $this;
125
+ }
126
+
127
+ /**
128
+ * {@inheritdoc}
129
+ */
130
+ public function isHit()
131
+ {
132
+ if (null !== $this->isHit) {
133
+ return $this->isHit;
134
+ }
135
+
136
+ return $this->repository->exists($this->hash);
137
+ }
138
+
139
+ /**
140
+ * {@inheritdoc}
141
+ */
142
+ public function expiresAt($expiration)
143
+ {
144
+ // DateTimeInterface only exists since PHP>=5.5, also accept DateTime
145
+ if ($expiration instanceof DateTimeInterface || $expiration instanceof DateTime) {
146
+ // convert datetime to unix timestamp
147
+ $this->expire = (int) $expiration->format('U');
148
+ $this->changed = true;
149
+ } elseif (null === $expiration) {
150
+ $this->expire = 0;
151
+ $this->changed = true;
152
+ } else {
153
+ $class = get_class($this);
154
+ $type = gettype($expiration);
155
+ $error = "Argument 1 passed to $class::expiresAt() must be an ".
156
+ "instance of DateTime or DateTimeImmutable, $type given";
157
+
158
+ if (class_exists('\TypeError')) {
159
+ throw new \TypeError($error);
160
+ }
161
+ trigger_error($error, E_USER_ERROR);
162
+ }
163
+
164
+ return $this;
165
+ }
166
+
167
+ /**
168
+ * {@inheritdoc}
169
+ */
170
+ public function expiresAfter($time)
171
+ {
172
+ if ($time instanceof DateInterval) {
173
+ $expire = new DateTime();
174
+ $expire->add($time);
175
+ // convert datetime to unix timestamp
176
+ $this->expire = (int) $expire->format('U');
177
+ } elseif (is_int($time)) {
178
+ $this->expire = time() + $time;
179
+ } elseif (is_null($time)) {
180
+ // this is allowed, but just defaults to infinite
181
+ $this->expire = 0;
182
+ } else {
183
+ throw new InvalidArgumentException('Invalid time: '.serialize($time).'. Must be integer or '.'instance of DateInterval.');
184
+ }
185
+ $this->changed = true;
186
+
187
+ return $this;
188
+ }
189
+
190
+ /**
191
+ * Returns the set expiration time in integer form (as it's what
192
+ * KeyValueStore expects).
193
+ *
194
+ * @return int
195
+ */
196
+ public function getExpiration()
197
+ {
198
+ return $this->expire;
199
+ }
200
+
201
+ /**
202
+ * Returns true if the item is already expired, false otherwise.
203
+ *
204
+ * @return bool
205
+ */
206
+ public function isExpired()
207
+ {
208
+ $expire = $this->getExpiration();
209
+
210
+ return 0 !== $expire && $expire < time();
211
+ }
212
+
213
+ /**
214
+ * We'll want to know if this Item was altered (value or expiration date)
215
+ * once we'll want to store it.
216
+ *
217
+ * @return bool
218
+ */
219
+ public function hasChanged()
220
+ {
221
+ return $this->changed;
222
+ }
223
+
224
+ /**
225
+ * Allow isHit to be override, in case it's a value that is returned from
226
+ * memory, when a value is being saved deferred.
227
+ *
228
+ * @param bool $isHit
229
+ */
230
+ public function overrideIsHit($isHit)
231
+ {
232
+ $this->isHit = $isHit;
233
+ }
234
+ }
vendor/browscap-php/matthiasmullie/scrapbook/src/Psr6/Pool.php ADDED
@@ -0,0 +1,250 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace MatthiasMullie\Scrapbook\Psr6;
4
+
5
+ use MatthiasMullie\Scrapbook\KeyValueStore;
6
+ use Psr\Cache\CacheItemInterface;
7
+ use Psr\Cache\CacheItemPoolInterface;
8
+
9
+ /**
10
+ * Representation of the cache storage, which lets you read items from, and add
11
+ * values to the cache.
12
+ *
13
+ * @author Matthias Mullie <scrapbook@mullie.eu>
14
+ * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
15
+ * @license LICENSE MIT
16
+ */
17
+ class Pool implements CacheItemPoolInterface
18
+ {
19
+ /**
20
+ * List of invalid (or reserved) key characters.
21
+ *
22
+ * @var string
23
+ */
24
+ const KEY_INVALID_CHARACTERS = '{}()/\@:';
25
+
26
+ /**
27
+ * @var KeyValueStore
28
+ */
29
+ protected $store;
30
+
31
+ /**
32
+ * @var Repository
33
+ */
34
+ protected $repository;
35
+
36
+ /**
37
+ * @var Item[]
38
+ */
39
+ protected $deferred = array();
40
+
41
+ public function __construct(KeyValueStore $store)
42
+ {
43
+ $this->store = $store;
44
+ $this->repository = new Repository($store);
45
+ }
46
+
47
+ public function __destruct()
48
+ {
49
+ // make sure all deferred items are actually saved
50
+ $this->commit();
51
+ }
52
+
53
+ /**
54
+ * {@inheritdoc}
55
+ */
56
+ public function getItem($key)
57
+ {
58
+ $this->assertValidKey($key);
59
+ if (array_key_exists($key, $this->deferred)) {
60
+ /*
61
+ * In theory, we could request & change a deferred value. In the
62
+ * case of objects, we'll clone them to make sure that when the
63
+ * value for 1 item is manipulated, it doesn't affect the value of
64
+ * the one about to be stored to cache (because those objects would
65
+ * be passed by-ref without the cloning)
66
+ */
67
+ $value = $this->deferred[$key];
68
+ $item = is_object($value) ? clone $value : $value;
69
+
70
+ /*
71
+ * Deferred items should identify as being hit, unless if expired:
72
+ * @see https://groups.google.com/forum/?fromgroups#!topic/php-fig/pxy_VYgm2sU
73
+ */
74
+ $item->overrideIsHit(!$item->isExpired());
75
+
76
+ return $item;
77
+ }
78
+
79
+ // return a stub object - the real call to the cache store will only be
80
+ // done once we actually want to access data from this object
81
+ return new Item($key, $this->repository);
82
+ }
83
+
84
+ /**
85
+ * {@inheritdoc}
86
+ *
87
+ * @return Item[]
88
+ */
89
+ public function getItems(array $keys = array())
90
+ {
91
+ $items = array();
92
+ foreach ($keys as $key) {
93
+ $this->assertValidKey($key);
94
+
95
+ $items[$key] = $this->getItem($key);
96
+ }
97
+
98
+ return $items;
99
+ }
100
+
101
+ /**
102
+ * {@inheritdoc}
103
+ */
104
+ public function hasItem($key)
105
+ {
106
+ $this->assertValidKey($key);
107
+
108
+ $item = $this->getItem($key);
109
+
110
+ return $item->isHit();
111
+ }
112
+
113
+ /**
114
+ * {@inheritdoc}
115
+ */
116
+ public function clear()
117
+ {
118
+ $this->deferred = array();
119
+
120
+ return $this->store->flush();
121
+ }
122
+
123
+ /**
124
+ * {@inheritdoc}
125
+ */
126
+ public function deleteItem($key)
127
+ {
128
+ $this->assertValidKey($key);
129
+
130
+ $this->store->delete($key);
131
+ unset($this->deferred[$key]);
132
+
133
+ // as long as the item is gone from the cache (even if it never existed
134
+ // and delete failed because of that), we should return `true`
135
+ return true;
136
+ }
137
+
138
+ /**
139
+ * {@inheritdoc}
140
+ */
141
+ public function deleteItems(array $keys)
142
+ {
143
+ foreach ($keys as $key) {
144
+ $this->assertValidKey($key);
145
+
146
+ unset($this->deferred[$key]);
147
+ }
148
+
149
+ $this->store->deleteMulti($keys);
150
+
151
+ // as long as the item is gone from the cache (even if it never existed
152
+ // and delete failed because of that), we should return `true`
153
+ return true;
154
+ }
155
+
156
+ /**
157
+ * {@inheritdoc}
158
+ */
159
+ public function save(CacheItemInterface $item)
160
+ {
161
+ if (!$item instanceof Item) {
162
+ throw new InvalidArgumentException('MatthiasMullie\Scrapbook\Psr6\Pool can only save
163
+ MatthiasMullie\Scrapbook\Psr6\Item objects');
164
+ }
165
+
166
+ if (!$item->hasChanged()) {
167
+ /*
168
+ * If the item didn't change, we don't have to re-save it. We do,
169
+ * however, need to check if the item actually holds a value: if it
170
+ * does, it should be considered "saved" (even though nothing has
171
+ * changed, the value for this key is in cache) and if it doesn't,
172
+ * well then nothing is in cache.
173
+ */
174
+ return null !== $item->get();
175
+ }
176
+
177
+ $expire = $item->getExpiration();
178
+
179
+ return $this->store->set($item->getKey(), $item->get(), $expire);
180
+ }
181
+
182
+ /**
183
+ * {@inheritdoc}
184
+ */
185
+ public function saveDeferred(CacheItemInterface $item)
186
+ {
187
+ if (!$item instanceof Item) {
188
+ throw new InvalidArgumentException('MatthiasMullie\Scrapbook\Psr6\Pool can only save
189
+ MatthiasMullie\Scrapbook\Psr6\Item objects');
190
+ }
191
+
192
+ $this->deferred[$item->getKey()] = $item;
193
+ // let's pretend that this actually comes from cache (we'll store it
194
+ // there soon), unless if it's already expired (in which case it will
195
+ // never reach cache...)
196
+ $item->overrideIsHit(!$item->isExpired());
197
+
198
+ return true;
199
+ }
200
+
201
+ /**
202
+ * {@inheritdoc}
203
+ */
204
+ public function commit()
205
+ {
206
+ $deferred = array();
207
+ foreach ($this->deferred as $key => $item) {
208
+ if ($item->isExpired()) {
209
+ // already expired: don't even save it
210
+ continue;
211
+ }
212
+
213
+ // setMulti doesn't allow to set expiration times on a per-item basis,
214
+ // so we'll have to group our requests per expiration date
215
+ $expire = $item->getExpiration();
216
+ $deferred[$expire][$item->getKey()] = $item->get();
217
+ }
218
+
219
+ // setMulti doesn't allow to set expiration times on a per-item basis,
220
+ // so we'll have to group our requests per expiration date
221
+ $success = true;
222
+ foreach ($deferred as $expire => $items) {
223
+ $status = $this->store->setMulti($items, $expire);
224
+ $success &= !in_array(false, $status);
225
+ unset($deferred[$expire]);
226
+ }
227
+
228
+ return (bool) $success;
229
+ }
230
+
231
+ /**
232
+ * Throws an exception if $key is invalid.
233
+ *
234
+ * @param string $key
235
+ *
236
+ * @throws InvalidArgumentException
237
+ */
238
+ protected function assertValidKey($key)
239
+ {
240
+ if (!is_string($key)) {
241
+ throw new InvalidArgumentException('Invalid key: '.var_export($key, true).'. Key should be a string.');
242
+ }
243
+
244
+ // valid key according to PSR-6 rules
245
+ $invalid = preg_quote(static::KEY_INVALID_CHARACTERS, '/');
246
+ if (preg_match('/['.$invalid.']/', $key)) {
247
+ throw new InvalidArgumentException('Invalid key: '.$key.'. Contains (a) character(s) reserved '.'for future extension: '.static::KEY_INVALID_CHARACTERS);
248
+ }
249
+ }
250
+ }
vendor/browscap-php/matthiasmullie/scrapbook/src/Psr6/Repository.php ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace MatthiasMullie\Scrapbook\Psr6;
4
+
5
+ use MatthiasMullie\Scrapbook\KeyValueStore;
6
+
7
+ /**
8
+ * Helper object to serve as glue between pool & item.
9
+ *
10
+ * New items are created by first get()-ing them from the pool. It would be
11
+ * inefficient to let such a get() immediately query the real cache (because it
12
+ * may not be meant to retrieve real data, but just to set a new value)
13
+ *
14
+ * Instead, every Item returned by get() will be a "placeholder", and once the
15
+ * values are actually needed, this object will be called to go do that (along
16
+ * with every other value that has not yet been resolved, while we're at it)
17
+ *
18
+ * @author Matthias Mullie <scrapbook@mullie.eu>
19
+ * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
20
+ * @license LICENSE MIT
21
+ */
22
+ class Repository
23
+ {
24
+ /**
25
+ * @var KeyValueStore
26
+ */
27
+ protected $store;
28
+
29
+ /**
30
+ * Array of resolved items.
31
+ *
32
+ * @var mixed[] [unique => value]
33
+ */
34
+ protected $resolved = array();
35
+
36
+ /**
37
+ * Array of unresolved items.
38
+ *
39
+ * @var string[] [unique => key]
40
+ */
41
+ protected $unresolved = array();
42
+
43
+ public function __construct(KeyValueStore $store)
44
+ {
45
+ $this->store = $store;
46
+ }
47
+
48
+ /**
49
+ * Add a to-be-resolved cache key.
50
+ *
51
+ * @param string $unique
52
+ * @param string $key
53
+ */
54
+ public function add($unique, $key)
55
+ {
56
+ $this->unresolved[$unique] = $key;
57
+ }
58
+
59
+ /**
60
+ * This repository holds the real values for all Item objects. However, if
61
+ * such an item gets garbage collected, there is no point in wasting any
62
+ * more memory storing that value.
63
+ * In that case, this method can be called to remove those values.
64
+ *
65
+ * @param string $unique
66
+ */
67
+ public function remove($unique)
68
+ {
69
+ unset($this->unresolved[$unique], $this->resolved[$unique]);
70
+ }
71
+
72
+ /**
73
+ * @param string $unique
74
+ *
75
+ * @return mixed|null Value, of null if non-existent
76
+ */
77
+ public function get($unique)
78
+ {
79
+ return $this->exists($unique) ? $this->resolved[$unique] : null;
80
+ }
81
+
82
+ /**
83
+ * @param string $unique
84
+ *
85
+ * @return bool
86
+ */
87
+ public function exists($unique)
88
+ {
89
+ if (array_key_exists($unique, $this->unresolved)) {
90
+ $this->resolve();
91
+ }
92
+
93
+ return array_key_exists($unique, $this->resolved);
94
+ }
95
+
96
+ /**
97
+ * Resolve all unresolved keys at once.
98
+ */
99
+ protected function resolve()
100
+ {
101
+ $keys = array_unique(array_values($this->unresolved));
102
+ $values = $this->store->getMulti($keys);
103
+
104
+ foreach ($this->unresolved as $unique => $key) {
105
+ if (!array_key_exists($key, $values)) {
106
+ // key doesn't exist in cache
107
+ continue;
108
+ }
109
+
110
+ /*
111
+ * In theory, there could've been multiple unresolved requests for
112
+ * the same cache key. In the case of objects, we'll clone them
113
+ * to make sure that when the value for 1 item is manipulated, it
114
+ * doesn't affect the value of the other item (because those objects
115
+ * would be passed by-ref without the cloning)
116
+ */
117
+ $value = $values[$key];
118
+ $value = is_object($value) ? clone $value : $value;
119
+
120
+ $this->resolved[$unique] = $value;
121
+ }
122
+
123
+ $this->unresolved = array();
124
+ }
125
+ }
vendor/browscap-php/matthiasmullie/scrapbook/src/Scale/Shard.php ADDED
@@ -0,0 +1,257 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace MatthiasMullie\Scrapbook\Scale;
4
+
5
+ use MatthiasMullie\Scrapbook\KeyValueStore;
6
+ use SplObjectStorage;
7
+
8
+ /**
9
+ * This class lets you scale your cache cluster by sharding the data across
10
+ * multiple cache servers.
11
+ *
12
+ * Pass the individual KeyValueStore objects that compose the cache server pool
13
+ * into this constructor how you want the data to be sharded. The cache data
14
+ * will be sharded over them according to the order they were in when they were
15
+ * passed into this constructor (so make sure to always keep the order the same)
16
+ *
17
+ * The sharding is spread evenly and all cache servers will roughly receive the
18
+ * same amount of cache keys. If some servers are bigger than others, you can
19
+ * offset this by adding the KeyValueStore object more than once.
20
+ *
21
+ * Data can even be sharded among different adapters: one server in the shard
22
+ * pool can be Redis while another can be Memcached. Not sure why you would even
23
+ * want that, but you could!
24
+ *
25
+ * @author Matthias Mullie <scrapbook@mullie.eu>
26
+ * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
27
+ * @license LICENSE MIT
28
+ */
29
+ class Shard implements KeyValueStore
30
+ {
31
+ /**
32
+ * @var KeyValueStore[]
33
+ */
34
+ protected $caches = array();
35
+
36
+ /**
37
+ * Overloadable with multiple KeyValueStore objects.
38
+ */
39
+ public function __construct(KeyValueStore $cache1, KeyValueStore $cache2 = null /* , [KeyValueStore $cache3, [...]] */)
40
+ {
41
+ $caches = func_get_args();
42
+ $caches = array_filter($caches);
43
+ $this->caches = $caches;
44
+ }
45
+
46
+ public function addCache(KeyValueStore $cache)
47
+ {
48
+ $this->caches[] = $cache;
49
+ }
50
+
51
+ /**
52
+ * {@inheritdoc}
53
+ */
54
+ public function get($key, &$token = null)
55
+ {
56
+ return $this->getShard($key)->get($key, $token);
57
+ }
58
+
59
+ /**
60
+ * {@inheritdoc}
61
+ */
62
+ public function getMulti(array $keys, array &$tokens = null)
63
+ {
64
+ $shards = $this->getShards($keys);
65
+ $results = array();
66
+ $tokens = array();
67
+
68
+ /** @var KeyValueStore $shard */
69
+ foreach ($shards as $shard) {
70
+ $keysOnShard = $shards[$shard];
71
+ $results += $shard->getMulti($keysOnShard, $shardTokens);
72
+ $tokens += $shardTokens ?: array();
73
+ }
74
+
75
+ return $results;
76
+ }
77
+
78
+ /**
79
+ * {@inheritdoc}
80
+ */
81
+ public function set($key, $value, $expire = 0)
82
+ {
83
+ return $this->getShard($key)->set($key, $value, $expire);
84
+ }
85
+
86
+ /**
87
+ * {@inheritdoc}
88
+ */
89
+ public function setMulti(array $items, $expire = 0)
90
+ {
91
+ $shards = $this->getShards(array_keys($items));
92
+ $results = array();
93
+
94
+ /** @var KeyValueStore $shard */
95
+ foreach ($shards as $shard) {
96
+ $keysOnShard = $shards[$shard];
97
+ $itemsOnShard = array_intersect_key($items, array_flip($keysOnShard));
98
+ $results += $shard->setMulti($itemsOnShard, $expire);
99
+ }
100
+
101
+ return $results;
102
+ }
103
+
104
+ /**
105
+ * {@inheritdoc}
106
+ */
107
+ public function delete($key)
108
+ {
109
+ return $this->getShard($key)->delete($key);
110
+ }
111
+
112
+ /**
113
+ * {@inheritdoc}
114
+ */
115
+ public function deleteMulti(array $keys)
116
+ {
117
+ $shards = $this->getShards($keys);
118
+ $results = array();
119
+
120
+ /** @var KeyValueStore $shard */
121
+ foreach ($shards as $shard) {
122
+ $keysOnShard = $shards[$shard];
123
+ $results += $shard->deleteMulti($keysOnShard);
124
+ }
125
+
126
+ return $results;
127
+ }
128
+
129
+ /**
130
+ * {@inheritdoc}
131
+ */
132
+ public function add($key, $value, $expire = 0)
133
+ {
134
+ return $this->getShard($key)->add($key, $value, $expire);
135
+ }
136
+
137
+ /**
138
+ * {@inheritdoc}
139
+ */
140
+ public function replace($key, $value, $expire = 0)
141
+ {
142
+ return $this->getShard($key)->replace($key, $value, $expire);
143
+ }
144
+
145
+ /**
146
+ * {@inheritdoc}
147
+ */
148
+ public function cas($token, $key, $value, $expire = 0)
149
+ {
150
+ return $this->getShard($key)->cas($token, $key, $value, $expire);
151
+ }
152
+
153
+ /**
154
+ * {@inheritdoc}
155
+ */
156
+ public function increment($key, $offset = 1, $initial = 0, $expire = 0)
157
+ {
158
+ return $this->getShard($key)->increment($key, $offset, $initial, $expire);
159
+ }
160
+
161
+ /**
162
+ * {@inheritdoc}
163
+ */
164
+ public function decrement($key, $offset = 1, $initial = 0, $expire = 0)
165
+ {
166
+ return $this->getShard($key)->decrement($key, $offset, $initial, $expire);
167
+ }
168
+
169
+ /**
170
+ * {@inheritdoc}
171
+ */
172
+ public function touch($key, $expire)
173
+ {
174
+ return $this->getShard($key)->touch($key, $expire);
175
+ }
176
+
177
+ /**
178
+ * {@inheritdoc}
179
+ */
180
+ public function flush()
181
+ {
182
+ $result = true;
183
+
184
+ foreach ($this->caches as $cache) {
185
+ $result &= $cache->flush();
186
+ }
187
+
188
+ return (bool) $result;
189
+ }
190
+
191
+ /**
192
+ * {@inheritdoc}
193
+ */
194
+ public function getCollection($name)
195
+ {
196
+ $shard = new static($this->caches[0]->getCollection($name));
197
+
198
+ $count = count($this->caches);
199
+ for ($i = 1; $i < $count; ++$i) {
200
+ $shard->addCache($this->caches[$i]->getCollection($name));
201
+ }
202
+
203
+ return $shard;
204
+ }
205
+
206
+ /**
207
+ * Get the shard (KeyValueStore object) that corresponds to a particular
208
+ * cache key.
209
+ *
210
+ * @param string $key
211
+ *
212
+ * @return KeyValueStore
213
+ */
214
+ protected function getShard($key)
215
+ {
216
+ /*
217
+ * The hash is so we can deterministically randomize the spread of keys
218
+ * over servers: if we were to just spread them based on key name, we
219
+ * may end up with a large chunk of similarly prefixed keys on the same
220
+ * server. Hashing the key will ensure similar looking keys can still
221
+ * result in very different values, yet they result will be the same
222
+ * every time it's repeated for the same key.
223
+ * Since we don't use the hash for encryption, the fastest algorithm
224
+ * will do just fine here.
225
+ */
226
+ $hash = crc32($key);
227
+
228
+ // crc32 on 32-bit machines can produce a negative int
229
+ $hash = abs($hash);
230
+
231
+ $index = $hash % count($this->caches);
232
+
233
+ return $this->caches[$index];
234
+ }
235
+
236
+ /**
237
+ * Get a [KeyValueStore => array of cache keys] map (SplObjectStorage) for
238
+ * multiple cache keys.
239
+ *
240
+ * @return SplObjectStorage
241
+ */
242
+ protected function getShards(array $keys)
243
+ {
244
+ $shards = new SplObjectStorage();
245
+
246
+ foreach ($keys as $key) {
247
+ $shard = $this->getShard($key);
248
+ if (!isset($shards[$shard])) {
249
+ $shards[$shard] = array();
250
+ }
251
+
252
+ $shards[$shard] = array_merge($shards[$shard], array($key));
253
+ }
254
+
255
+ return $shards;
256
+ }
257
+ }
vendor/browscap-php/matthiasmullie/scrapbook/src/Scale/StampedeProtector.php ADDED
@@ -0,0 +1,298 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace MatthiasMullie\Scrapbook\Scale;
4
+
5
+ use MatthiasMullie\Scrapbook\Exception\InvalidKey;
6
+ use MatthiasMullie\Scrapbook\KeyValueStore;
7
+
8
+ /**
9
+ * Cache is usually used to reduce performing a complex operation. In case of a
10
+ * cache miss, that operation is executed & the result is stored.
11
+ *
12
+ * A cache stampede happens when there are a lot of requests for data that is
13
+ * not currently in cache. Examples could be:
14
+ * * cache expires for something that is often under very heavy load
15
+ * * sudden unexpected high load on something that is likely to not be in cache
16
+ * In those cases, this huge amount of requests for data that is not at that
17
+ * time in cache, causes that expensive operation to be executed a lot of times,
18
+ * all at once.
19
+ *
20
+ * This class is designed to counteract that: if a value can't be found in cache
21
+ * we'll write something else to cache for a short period of time, to indicate
22
+ * that another process has already requested this same key (and is probably
23
+ * already performing that complex operation that will result in the key being
24
+ * filled)
25
+ *
26
+ * All of the follow-up requests (that find that the "stampede indicator" has
27
+ * already been set) will just wait (usleep): instead of crippling the servers
28
+ * by all having to execute the same operation, these processes will just idle
29
+ * to give the first process the chance to fill in the cache. Periodically,
30
+ * these processes will poll the cache to see if the value has already been
31
+ * stored in the meantime.
32
+ *
33
+ * The stampede protection will only be temporary, for $sla milliseconds. We
34
+ * need to limit it because the first process (tasked with filling the cache
35
+ * after executing the expensive operation) may fail/crash/... If the expensive
36
+ * operation fails to conclude in < $sla milliseconds.
37
+ * This class guarantees that the stampede will hold off for $sla amount of time
38
+ * but after that, all follow-up requests will go through without cached values
39
+ * and cause a stampede after all, if the initial process fails to complete
40
+ * within that time.
41
+ *
42
+ * @author Matthias Mullie <scrapbook@mullie.eu>
43
+ * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
44
+ * @license LICENSE MIT
45
+ */
46
+ class StampedeProtector implements KeyValueStore
47
+ {
48
+ /**
49
+ * @var KeyValueStore
50
+ */
51
+ protected $cache = array();
52
+
53
+ /**
54
+ * Amount of time, in milliseconds, this class guarantees protection.
55
+ *
56
+ * @var int
57
+ */
58
+ protected $sla = 1000;
59
+
60
+ /**
61
+ * Amount of times every process will poll within $sla time.
62
+ *
63
+ * @var int
64
+ */
65
+ protected $attempts = 10;
66
+
67
+ /**
68
+ * @param KeyValueStore $cache The real cache we'll buffer for
69
+ * @param int $sla Stampede protection time, in milliseconds
70
+ */
71
+ public function __construct(KeyValueStore $cache, $sla = 1000)
72
+ {
73
+ $this->cache = $cache;
74
+ $this->sla = $sla;
75
+ }
76
+
77
+ /**
78
+ * {@inheritdoc}
79
+ */
80
+ public function get($key, &$token = null)
81
+ {
82
+ $values = $this->getMulti(array($key), $tokens);
83
+ $token = isset($tokens[$key]) ? $tokens[$key] : null;
84
+
85
+ return isset($values[$key]) ? $values[$key] : false;
86
+ }
87
+
88
+ /**
89
+ * {@inheritdoc}
90
+ */
91
+ public function getMulti(array $keys, array &$tokens = null)
92
+ {
93
+ // fetch both requested keys + stampede protection indicators at once
94
+ $stampedeKeys = array_combine($keys, array_map(array($this, 'stampedeKey'), $keys));
95
+ $values = $this->cache->getMulti(array_merge($keys, $stampedeKeys), $tokens);
96
+
97
+ // figure out which of the requested keys are protected, and which need
98
+ // protection (=currently empty & not yet protected)
99
+ $protected = array_keys(array_intersect($stampedeKeys, array_keys($values)));
100
+ $protect = array_diff($keys, array_keys($values), $protected);
101
+
102
+ // protect keys that we couldn't find, and remove them from the list of
103
+ // keys we want results from, because we'll keep fetching empty keys
104
+ // (that are currently protected)
105
+ $done = $this->protect($protect);
106
+ $keys = array_diff($keys, $done);
107
+
108
+ // we may have failed to protect some keys after all (race condition
109
+ // with another process), in which case we also have to keep polling
110
+ // those keys (which the other process is likely working on already)
111
+ $protected += array_diff($protect, $done);
112
+
113
+ // we over-fetched (to include stampede indicators), now limit the
114
+ // results to only the keys we requested
115
+ $results = array_intersect_key($values, array_flip($keys));
116
+ $tokens = array_intersect_key($tokens, $results);
117
+
118
+ // we may not have been able to retrieve all keys yet: some may have
119
+ // been "protected" (and are being regenerated in another process) in
120
+ // which case we'll retry a couple of times, hoping the other process
121
+ // stores the new value in the meantime
122
+ $attempts = $this->attempts;
123
+ while (--$attempts > 0 && !empty($protected) && $this->sleep()) {
124
+ $values = $this->cache->getMulti($protected, $tokens2);
125
+
126
+ $results += array_intersect_key($values, array_flip($keys));
127
+ $tokens += array_intersect_key($tokens2, array_flip($keys));
128
+
129
+ // don't keep polling for values we just fetched...
130
+ $protected = array_diff($protected, array_keys($values));
131
+ }
132
+
133
+ return $results;
134
+ }
135
+
136
+ /**
137
+ * {@inheritdoc}
138
+ */
139
+ public function set($key, $value, $expire = 0)
140
+ {
141
+ return $this->cache->set($key, $value, $expire);
142
+ }
143
+
144
+ /**
145
+ * {@inheritdoc}
146
+ */
147
+ public function setMulti(array $items, $expire = 0)
148
+ {
149
+ return $this->cache->setMulti($items, $expire);
150
+ }
151
+
152
+ /**
153
+ * {@inheritdoc}
154
+ */
155
+ public function delete($key)
156
+ {
157
+ return $this->cache->delete($key);
158
+ }
159
+
160
+ /**
161
+ * {@inheritdoc}
162
+ */
163
+ public function deleteMulti(array $keys)
164
+ {
165
+ return $this->cache->deleteMulti($keys);
166
+ }
167
+
168
+ /**
169
+ * {@inheritdoc}
170
+ */
171
+ public function add($key, $value, $expire = 0)
172
+ {
173
+ return $this->cache->add($key, $value, $expire);
174
+ }
175
+
176
+ /**
177
+ * {@inheritdoc}
178
+ */
179
+ public function replace($key, $value, $expire = 0)
180
+ {
181
+ return $this->cache->replace($key, $value, $expire);
182
+ }
183
+
184
+ /**
185
+ * {@inheritdoc}
186
+ */
187
+ public function cas($token, $key, $value, $expire = 0)
188
+ {
189
+ return $this->cache->cas($token, $key, $value, $expire);
190
+ }
191
+
192
+ /**
193
+ * {@inheritdoc}
194
+ */
195
+ public function increment($key, $offset = 1, $initial = 0, $expire = 0)
196
+ {
197
+ return $this->cache->increment($key, $offset, $initial, $expire);
198
+ }
199
+
200
+ /**
201
+ * {@inheritdoc}
202
+ */
203
+ public function decrement($key, $offset = 1, $initial = 0, $expire = 0)
204
+ {
205
+ return $this->cache->decrement($key, $offset, $initial, $expire);
206
+ }
207
+
208
+ /**
209
+ * {@inheritdoc}
210
+ */
211
+ public function touch($key, $expire)
212
+ {
213
+ return $this->cache->touch($key, $expire);
214
+ }
215
+
216
+ /**
217
+ * {@inheritdoc}
218
+ */
219
+ public function flush()
220
+ {
221
+ return $this->cache->flush();
222
+ }
223
+
224
+ /**
225
+ * {@inheritdoc}
226
+ */
227
+ public function getCollection($name)
228
+ {
229
+ $collection = $this->cache->getCollection($name);
230
+
231
+ return new static($collection);
232
+ }
233
+
234
+ /**
235
+ * As soon as a key turns up empty (doesn't yet exist in cache), we'll
236
+ * "protect" it for some time. This will be done by writing to a key similar
237
+ * to the original key name. If this key is present (which it will only be
238
+ * for a short amount of time) we'll know it's protected.
239
+ *
240
+ * @return string[] Array of keys that were successfully protected
241
+ */
242
+ protected function protect(array $keys)
243
+ {
244
+ if (empty($keys)) {
245
+ return array();
246
+ }
247
+
248
+ $success = array();
249
+ foreach ($keys as $key) {
250
+ /*
251
+ * Key is add()ed because there may be multiple concurrent processes
252
+ * that are both in the process of protecting - first one to add()
253
+ * wins (and those are returned by the function, so those that are
254
+ * failed to protect can be considered protected)
255
+ */
256
+ $success[$key] = $this->cache->add($this->stampedeKey($key), '', $this->sla);
257
+ }
258
+
259
+ return array_keys(array_filter($success));
260
+ }
261
+
262
+ /**
263
+ * When waiting for stampede-protected keys, we'll just sleep, not using
264
+ * much resources.
265
+ *
266
+ * @return bool
267
+ */
268
+ protected function sleep()
269
+ {
270
+ $break = $this->sla / $this->attempts;
271
+ usleep(1000 * $break);
272
+
273
+ return true;
274
+ }
275
+
276
+ /**
277
+ * To figure out if something has recently been requested already (and is
278
+ * likely in the process of being recalculated), we'll temporarily write to
279
+ * another key, so follow-up requests know another process is likely already
280
+ * re-processing the value.
281
+ *
282
+ * @param string $key
283
+ *
284
+ * @return string
285
+ *
286
+ * @throws InvalidKey
287
+ */
288
+ protected function stampedeKey($key)
289
+ {
290
+ $suffix = '.stampede';
291
+
292
+ if (substr($key, -strlen($suffix)) === $suffix) {
293
+ throw new InvalidKey("Invalid key: $key. Keys with suffix '$suffix' are reserved.");
294
+ }
295
+
296
+ return $key.$suffix;
297
+ }
298
+ }
vendor/browscap-php/psr/cache/src/CacheException.php ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Psr\Cache;
4
+
5
+ /**
6
+ * Exception interface for all exceptions thrown by an Implementing Library.
7
+ */
8
+ interface CacheException
9
+ {
10
+ }
vendor/browscap-php/psr/cache/src/CacheItemInterface.php ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Psr\Cache;
4
+
5
+ /**
6
+ * CacheItemInterface defines an interface for interacting with objects inside a cache.
7
+ *
8
+ * Each Item object MUST be associated with a specific key, which can be set
9
+ * according to the implementing system and is typically passed by the
10
+ * Cache\CacheItemPoolInterface object.
11
+ *
12
+ * The Cache\CacheItemInterface object encapsulates the storage and retrieval of
13
+ * cache items. Each Cache\CacheItemInterface is generated by a
14
+ * Cache\CacheItemPoolInterface object, which is responsible for any required
15
+ * setup as well as associating the object with a unique Key.
16
+ * Cache\CacheItemInterface objects MUST be able to store and retrieve any type
17
+ * of PHP value defined in the Data section of the specification.
18
+ *
19
+ * Calling Libraries MUST NOT instantiate Item objects themselves. They may only
20
+ * be requested from a Pool object via the getItem() method. Calling Libraries
21
+ * SHOULD NOT assume that an Item created by one Implementing Library is
22
+ * compatible with a Pool from another Implementing Library.
23
+ */
24
+ interface CacheItemInterface
25
+ {
26
+ /**
27
+ * Returns the key for the current cache item.
28
+ *
29
+ * The key is loaded by the Implementing Library, but should be available to
30
+ * the higher level callers when needed.
31
+ *
32
+ * @return string
33
+ * The key string for this cache item.
34
+ */
35
+ public function getKey();
36
+
37
+ /**
38
+ * Retrieves the value of the item from the cache associated with this object's key.
39
+ *
40
+ * The value returned must be identical to the value originally stored by set().
41
+ *
42
+ * If isHit() returns false, this method MUST return null. Note that null
43
+ * is a legitimate cached value, so the isHit() method SHOULD be used to
44
+ * differentiate between "null value was found" and "no value was found."
45
+ *
46
+ * @return mixed
47
+ * The value corresponding to this cache item's key, or null if not found.
48
+ */
49
+ public function get();
50
+
51
+ /**
52
+ * Confirms if the cache item lookup resulted in a cache hit.
53
+ *
54
+ * Note: This method MUST NOT have a race condition between calling isHit()
55
+ * and calling get().
56
+ *
57
+ * @return bool
58
+ * True if the request resulted in a cache hit. False otherwise.
59
+ */
60
+ public function isHit();
61
+
62
+ /**
63
+ * Sets the value represented by this cache item.
64
+ *
65
+ * The $value argument may be any item that can be serialized by PHP,
66
+ * although the method of serialization is left up to the Implementing
67
+ * Library.
68
+ *
69
+ * @param mixed $value
70
+ * The serializable value to be stored.
71
+ *
72
+ * @return static
73
+ * The invoked object.
74
+ */
75
+ public function set($value);
76
+
77
+ /**
78
+ * Sets the expiration time for this cache item.
79
+ *
80
+ * @param \DateTimeInterface|null $expiration
81
+ * The point in time after which the item MUST be considered expired.
82
+ * If null is passed explicitly, a default value MAY be used. If none is set,
83
+ * the value should be stored permanently or for as long as the
84
+ * implementation allows.
85
+ *
86
+ * @return static
87
+ * The called object.
88
+ */
89
+ public function expiresAt($expiration);
90
+
91
+ /**
92
+ * Sets the expiration time for this cache item.
93
+ *
94
+ * @param int|\DateInterval|null $time
95
+ * The period of time from the present after which the item MUST be considered
96
+ * expired. An integer parameter is understood to be the time in seconds until
97
+ * expiration. If null is passed explicitly, a default value MAY be used.
98
+ * If none is set, the value should be stored permanently or for as long as the
99
+ * implementation allows.
100
+ *
101
+ * @return static
102
+ * The called object.
103
+ */
104
+ public function expiresAfter($time);
105
+ }
vendor/browscap-php/psr/cache/src/CacheItemPoolInterface.php ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Psr\Cache;
4
+
5
+ /**
6
+ * CacheItemPoolInterface generates CacheItemInterface objects.
7
+ *
8
+ * The primary purpose of Cache\CacheItemPoolInterface is to accept a key from
9
+ * the Calling Library and return the associated Cache\CacheItemInterface object.
10
+ * It is also the primary point of interaction with the entire cache collection.
11
+ * All configuration and initialization of the Pool is left up to an
12
+ * Implementing Library.
13
+ */
14
+ interface CacheItemPoolInterface
15
+ {
16
+ /**
17
+ * Returns a Cache Item representing the specified key.
18
+ *
19
+ * This method must always return a CacheItemInterface object, even in case of
20
+ * a cache miss. It MUST NOT return null.
21
+ *
22
+ * @param string $key
23
+ * The key for which to return the corresponding Cache Item.
24
+ *
25
+ * @throws InvalidArgumentException
26
+ * If the $key string is not a legal value a \Psr\Cache\InvalidArgumentException
27
+ * MUST be thrown.
28
+ *
29
+ * @return CacheItemInterface
30
+ * The corresponding Cache Item.
31
+ */
32
+ public function getItem($key);
33
+
34
+ /**
35
+ * Returns a traversable set of cache items.
36
+ *
37
+ * @param string[] $keys
38
+ * An indexed array of keys of items to retrieve.
39
+ *
40
+ * @throws InvalidArgumentException
41
+ * If any of the keys in $keys are not a legal value a \Psr\Cache\InvalidArgumentException
42
+ * MUST be thrown.
43
+ *
44
+ * @return array|\Traversable
45
+ * A traversable collection of Cache Items keyed by the cache keys of
46
+ * each item. A Cache item will be returned for each key, even if that
47
+ * key is not found. However, if no keys are specified then an empty
48
+ * traversable MUST be returned instead.
49
+ */
50
+ public function getItems(array $keys = array());
51
+
52
+ /**
53
+ * Confirms if the cache contains specified cache item.
54
+ *
55
+ * Note: This method MAY avoid retrieving the cached value for performance reasons.
56
+ * This could result in a race condition with CacheItemInterface::get(). To avoid
57
+ * such situation use CacheItemInterface::isHit() instead.
58
+ *
59
+ * @param string $key
60
+ * The key for which to check existence.
61
+ *
62
+ * @throws InvalidArgumentException
63
+ * If the $key string is not a legal value a \Psr\Cache\InvalidArgumentException
64
+ * MUST be thrown.
65
+ *
66
+ * @return bool
67
+ * True if item exists in the cache, false otherwise.
68
+ */
69
+ public function hasItem($key);
70
+
71
+ /**
72
+ * Deletes all items in the pool.
73
+ *
74
+ * @return bool
75
+ * True if the pool was successfully cleared. False if there was an error.
76
+ */
77
+ public function clear();
78
+
79
+ /**
80
+ * Removes the item from the pool.
81
+ *
82
+ * @param string $key
83
+ * The key to delete.
84
+ *
85
+ * @throws InvalidArgumentException
86
+ * If the $key string is not a legal value a \Psr\Cache\InvalidArgumentException
87
+ * MUST be thrown.
88
+ *
89
+ * @return bool
90
+ * True if the item was successfully removed. False if there was an error.
91
+ */
92
+ public function deleteItem($key);
93
+
94
+ /**
95
+ * Removes multiple items from the pool.
96
+ *
97
+ * @param string[] $keys
98
+ * An array of keys that should be removed from the pool.
99
+
100
+ * @throws InvalidArgumentException
101
+ * If any of the keys in $keys are not a legal value a \Psr\Cache\InvalidArgumentException
102
+ * MUST be thrown.
103
+ *
104
+ * @return bool
105
+ * True if the items were successfully removed. False if there was an error.
106
+ */
107
+ public function deleteItems(array $keys);
108
+
109
+ /**
110
+ * Persists a cache item immediately.
111
+ *
112
+ * @param CacheItemInterface $item
113
+ * The cache item to save.
114
+ *
115
+ * @return bool
116
+ * True if the item was successfully persisted. False if there was an error.
117
+ */
118
+ public function save(CacheItemInterface $item);
119
+
120
+ /**
121
+ * Sets a cache item to be persisted later.
122
+ *
123
+ * @param CacheItemInterface $item
124
+ * The cache item to save.
125
+ *
126
+ * @return bool
127
+ * False if the item could not be queued or if a commit was attempted and failed. True otherwise.
128
+ */
129
+ public function saveDeferred(CacheItemInterface $item);
130
+
131
+ /**
132
+ * Persists any deferred cache items.
133
+ *
134
+ * @return bool
135
+ * True if all not-yet-saved items were successfully saved or there were none. False otherwise.
136
+ */
137
+ public function commit();
138
+ }
vendor/browscap-php/psr/cache/src/InvalidArgumentException.php ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Psr\Cache;
4
+
5
+ /**
6
+ * Exception interface for invalid cache arguments.
7
+ *
8
+ * Any time an invalid argument is passed into a method it must throw an
9
+ * exception class which implements Psr\Cache\InvalidArgumentException.
10
+ */
11
+ interface InvalidArgumentException extends CacheException
12
+ {
13
+ }
vendor/browscap-php/psr/container/src/ContainerExceptionInterface.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Psr\Container;
4
+
5
+ use Throwable;
6
+
7
+ /**
8
+ * Base interface representing a generic exception in a container.
9
+ */
10
+ interface ContainerExceptionInterface extends Throwable
11
+ {
12
+ }
vendor/browscap-php/psr/container/src/ContainerInterface.php ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Psr\Container;
6
+
7
+ /**
8
+ * Describes the interface of a container that exposes methods to read its entries.
9
+ */
10
+ interface ContainerInterface
11
+ {
12
+ /**
13
+ * Finds an entry of the container by its identifier and returns it.
14
+ *
15
+ * @param string $id Identifier of the entry to look for.
16
+ *
17
+ * @throws NotFoundExceptionInterface No entry was found for **this** identifier.
18
+ * @throws ContainerExceptionInterface Error while retrieving the entry.
19
+ *
20
+ * @return mixed Entry.
21
+ */
22
+ public function get(string $id);
23
+
24
+ /**
25
+ * Returns true if the container can return an entry for the given identifier.
26
+ * Returns false otherwise.
27
+ *
28
+ * `has($id)` returning true does not mean that `get($id)` will not throw an exception.
29
+ * It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
30
+ *
31
+ * @param string $id Identifier of the entry to look for.
32
+ *
33
+ * @return bool
34
+ */
35
+ public function has(string $id);
36
+ }
vendor/browscap-php/psr/container/src/NotFoundExceptionInterface.php ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Psr\Container;
4
+
5
+ /**
6
+ * No entry was found in the container.
7
+ */
8
+ interface NotFoundExceptionInterface extends ContainerExceptionInterface
9
+ {
10
+ }
vendor/browscap-php/psr/simple-cache/src/CacheException.php ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Psr\SimpleCache;
4
+
5
+ /**
6
+ * Interface used for all types of exceptions thrown by the implementing library.
7
+ */
8
+ interface CacheException
9
+ {
10
+ }
vendor/browscap-php/psr/simple-cache/src/CacheInterface.php ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Psr\SimpleCache;
4
+
5
+ interface CacheInterface
6
+ {
7
+ /**
8
+ * Fetches a value from the cache.
9
+ *
10
+ * @param string $key The unique key of this item in the cache.
11
+ * @param mixed $default Default value to return if the key does not exist.
12
+ *
13
+ * @return mixed The value of the item from the cache, or $default in case of cache miss.
14
+ *
15
+ * @throws \Psr\SimpleCache\InvalidArgumentException
16
+ * MUST be thrown if the $key string is not a legal value.
17
+ */
18
+ public function get($key, $default = null);
19
+
20
+ /**
21
+ * Persists data in the cache, uniquely referenced by a key with an optional expiration TTL time.
22
+ *
23
+ * @param string $key The key of the item to store.
24
+ * @param mixed $value The value of the item to store, must be serializable.
25
+ * @param null|int|\DateInterval $ttl Optional. The TTL value of this item. If no value is sent and
26
+ * the driver supports TTL then the library may set a default value
27
+ * for it or let the driver take care of that.
28
+ *
29
+ * @return bool True on success and false on failure.
30
+ *
31
+ * @throws \Psr\SimpleCache\InvalidArgumentException
32
+ * MUST be thrown if the $key string is not a legal value.
33
+ */
34
+ public function set($key, $value, $ttl = null);
35
+
36
+ /**
37
+ * Delete an item from the cache by its unique key.
38
+ *
39
+ * @param string $key The unique cache key of the item to delete.
40
+ *
41
+ * @return bool True if the item was successfully removed. False if there was an error.
42
+ *
43
+ * @throws \Psr\SimpleCache\InvalidArgumentException
44
+ * MUST be thrown if the $key string is not a legal value.
45
+ */
46
+ public function delete($key);
47
+
48
+ /**
49
+ * Wipes clean the entire cache's keys.
50
+ *
51
+ * @return bool True on success and false on failure.
52
+ */
53
+ public function clear();
54
+
55
+ /**
56
+ * Obtains multiple cache items by their unique keys.
57
+ *
58
+ * @param iterable $keys A list of keys that can obtained in a single operation.
59
+ * @param mixed $default Default value to return for keys that do not exist.
60
+ *
61
+ * @return iterable A list of key => value pairs. Cache keys that do not exist or are stale will have $default as value.
62
+ *
63
+ * @throws \Psr\SimpleCache\InvalidArgumentException
64
+ * MUST be thrown if $keys is neither an array nor a Traversable,
65
+ * or if any of the $keys are not a legal value.
66
+ */
67
+ public function getMultiple($keys, $default = null);
68
+
69
+ /**
70
+ * Persists a set of key => value pairs in the cache, with an optional TTL.
71
+ *
72
+ * @param iterable $values A list of key => value pairs for a multiple-set operation.
73
+ * @param null|int|\DateInterval $ttl Optional. The TTL value of this item. If no value is sent and
74
+ * the driver supports TTL then the library may set a default value
75
+ * for it or let the driver take care of that.
76
+ *
77
+ * @return bool True on success and false on failure.
78
+ *
79
+ * @throws \Psr\SimpleCache\InvalidArgumentException
80
+ * MUST be thrown if $values is neither an array nor a Traversable,
81
+ * or if any of the $values are not a legal value.
82
+ */
83
+ public function setMultiple($values, $ttl = null);
84
+
85
+ /**
86
+ * Deletes multiple cache items in a single operation.
87
+ *
88
+ * @param iterable $keys A list of string-based keys to be deleted.
89
+ *
90
+ * @return bool True if the items were successfully removed. False if there was an error.
91
+ *
92
+ * @throws \Psr\SimpleCache\InvalidArgumentException
93
+ * MUST be thrown if $keys is neither an array nor a Traversable,
94
+ * or if any of the $keys are not a legal value.
95
+ */
96
+ public function deleteMultiple($keys);
97
+
98
+ /**
99
+ * Determines whether an item is present in the cache.
100
+ *
101
+ * NOTE: It is recommended that has() is only to be used for cache warming type purposes
102
+ * and not to be used within your live applications operations for get/set, as this method
103
+ * is subject to a race condition where your has() will return true and immediately after,
104
+ * another script can remove it making the state of your app out of date.
105
+ *
106
+ * @param string $key The cache item key.
107
+ *
108
+ * @return bool
109
+ *
110
+ * @throws \Psr\SimpleCache\InvalidArgumentException
111
+ * MUST be thrown if the $key string is not a legal value.
112
+ */
113
+ public function has($key);
114
+ }
vendor/browscap-php/psr/simple-cache/src/InvalidArgumentException.php ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Psr\SimpleCache;
4
+
5
+ /**
6
+ * Exception interface for invalid cache arguments.
7
+ *
8
+ * When an invalid argument is passed it must throw an exception which implements
9
+ * this interface
10
+ */
11
+ interface InvalidArgumentException extends CacheException
12
+ {
13
+ }
vendor/browscap-php/ralouphie/getallheaders/src/getallheaders.php ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if (!function_exists('getallheaders')) {
4
+
5
+ /**
6
+ * Get all HTTP header key/values as an associative array for the current request.
7
+ *
8
+ * @return string[string] The HTTP header key/value pairs.
9
+ */
10
+ function getallheaders()
11
+ {
12
+ $headers = array();
13
+
14
+ $copy_server = array(
15
+ 'CONTENT_TYPE' => 'Content-Type',
16
+ 'CONTENT_LENGTH' => 'Content-Length',
17
+ 'CONTENT_MD5' => 'Content-Md5',
18
+ );
19
+
20
+ foreach ($_SERVER as $key => $value) {
21
+ if (substr($key, 0, 5) === 'HTTP_') {
22
+ $key = substr($key, 5);
23
+ if (!isset($copy_server[$key]) || !isset($_SERVER[$key])) {
24
+ $key = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $key))));
25
+ $headers[$key] = $value;
26
+ }
27
+ } elseif (isset($copy_server[$key])) {
28
+ $headers[$copy_server[$key]] = $value;
29
+ }
30
+ }
31
+
32
+ if (!isset($headers['Authorization'])) {
33
+ if (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) {
34
+ $headers['Authorization'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION'];
35
+ } elseif (isset($_SERVER['PHP_AUTH_USER'])) {
36
+ $basic_pass = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : '';
37
+ $headers['Authorization'] = 'Basic ' . base64_encode($_SERVER['PHP_AUTH_USER'] . ':' . $basic_pass);
38
+ } elseif (isset($_SERVER['PHP_AUTH_DIGEST'])) {
39
+ $headers['Authorization'] = $_SERVER['PHP_AUTH_DIGEST'];
40
+ }
41
+ }
42
+
43
+ return $headers;
44
+ }
45
+
46
+ }
vendor/browscap-php/symfony/deprecation-contracts/function.php ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of the Symfony package.
5
+ *
6
+ * (c) Fabien Potencier <fabien@symfony.com>
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ if (!function_exists('trigger_deprecation')) {
13
+ /**
14
+ * Triggers a silenced deprecation notice.
15
+ *
16
+ * @param string $package The name of the Composer package that is triggering the deprecation
17
+ * @param string $version The version of the package that introduced the deprecation
18
+ * @param string $message The message of the deprecation
19
+ * @param mixed ...$args Values to insert in the message using printf() formatting
20
+ *
21
+ * @author Nicolas Grekas <p@tchwork.com>
22
+ */
23
+ function trigger_deprecation(string $package, string $version, string $message, ...$args): void
24
+ {
25
+ @trigger_error(($package || $version ? "Since $package $version: " : '').($args ? vsprintf($message, $args) : $message), \E_USER_DEPRECATED);
26
+ }
27
+ }
vendor/browscap-php/symfony/polyfill-ctype/Ctype.php ADDED
@@ -0,0 +1,232 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of the Symfony package.
5
+ *
6
+ * (c) Fabien Potencier <fabien@symfony.com>
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ namespace Symfony\Polyfill\Ctype;
13
+
14
+ /**
15
+ * Ctype implementation through regex.
16
+ *
17
+ * @internal
18
+ *
19
+ * @author Gert de Pagter <BackEndTea@gmail.com>
20
+ */
21
+ final class Ctype
22
+ {
23
+ /**
24
+ * Returns TRUE if every character in text is either a letter or a digit, FALSE otherwise.
25
+ *
26
+ * @see https://php.net/ctype-alnum
27
+ *
28
+ * @param mixed $text
29
+ *
30
+ * @return bool
31
+ */
32
+ public static function ctype_alnum($text)
33
+ {
34
+ $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);
35
+
36
+ return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z0-9]/', $text);
37
+ }
38
+
39
+ /**
40
+ * Returns TRUE if every character in text is a letter, FALSE otherwise.
41
+ *
42
+ * @see https://php.net/ctype-alpha
43
+ *
44
+ * @param mixed $text
45
+ *
46
+ * @return bool
47
+ */
48
+ public static function ctype_alpha($text)
49
+ {
50
+ $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);
51
+
52
+ return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z]/', $text);
53
+ }
54
+
55
+ /**
56
+ * Returns TRUE if every character in text is a control character from the current locale, FALSE otherwise.
57
+ *
58
+ * @see https://php.net/ctype-cntrl
59
+ *
60
+ * @param mixed $text
61
+ *
62
+ * @return bool
63
+ */
64
+ public static function ctype_cntrl($text)
65
+ {
66
+ $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);
67
+
68
+ return \is_string($text) && '' !== $text && !preg_match('/[^\x00-\x1f\x7f]/', $text);
69
+ }
70
+
71
+ /**
72
+ * Returns TRUE if every character in the string text is a decimal digit, FALSE otherwise.
73
+ *
74
+ * @see https://php.net/ctype-digit
75
+ *
76
+ * @param mixed $text
77
+ *
78
+ * @return bool
79
+ */
80
+ public static function ctype_digit($text)
81
+ {
82
+ $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);
83
+
84
+ return \is_string($text) && '' !== $text && !preg_match('/[^0-9]/', $text);
85
+ }
86
+
87
+ /**
88
+ * Returns TRUE if every character in text is printable and actually creates visible output (no white space), FALSE otherwise.
89
+ *
90
+ * @see https://php.net/ctype-graph
91
+ *
92
+ * @param mixed $text
93
+ *
94
+ * @return bool
95
+ */
96
+ public static function ctype_graph($text)
97
+ {
98
+ $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);
99
+
100
+ return \is_string($text) && '' !== $text && !preg_match('/[^!-~]/', $text);
101
+ }
102
+
103
+ /**
104
+ * Returns TRUE if every character in text is a lowercase letter.
105
+ *
106
+ * @see https://php.net/ctype-lower
107
+ *
108
+ * @param mixed $text
109
+ *
110
+ * @return bool
111
+ */
112
+ public static function ctype_lower($text)
113
+ {
114
+ $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);
115
+
116
+ return \is_string($text) && '' !== $text && !preg_match('/[^a-z]/', $text);
117
+ }
118
+
119
+ /**
120
+ * 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.
121
+ *
122
+ * @see https://php.net/ctype-print
123
+ *
124
+ * @param mixed $text
125
+ *
126
+ * @return bool
127
+ */
128
+ public static function ctype_print($text)
129
+ {
130
+ $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);
131
+
132
+ return \is_string($text) && '' !== $text && !preg_match('/[^ -~]/', $text);
133
+ }
134
+
135
+ /**
136
+ * Returns TRUE if every character in text is printable, but neither letter, digit or blank, FALSE otherwise.
137
+ *
138
+ * @see https://php.net/ctype-punct
139
+ *
140
+ * @param mixed $text
141
+ *
142
+ * @return bool
143
+ */
144
+ public static function ctype_punct($text)
145
+ {
146
+ $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);
147
+
148
+ return \is_string($text) && '' !== $text && !preg_match('/[^!-\/\:-@\[-`\{-~]/', $text);
149
+ }
150
+
151
+ /**
152
+ * 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.
153
+ *
154
+ * @see https://php.net/ctype-space
155
+ *
156
+ * @param mixed $text
157
+ *
158
+ * @return bool
159
+ */
160
+ public static function ctype_space($text)
161
+ {
162
+ $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);
163
+
164
+ return \is_string($text) && '' !== $text && !preg_match('/[^\s]/', $text);
165
+ }
166
+
167
+ /**
168
+ * Returns TRUE if every character in text is an uppercase letter.
169
+ *
170
+ * @see https://php.net/ctype-upper
171
+ *
172
+ * @param mixed $text
173
+ *
174
+ * @return bool
175
+ */
176
+ public static function ctype_upper($text)
177
+ {
178
+ $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);
179
+
180
+ return \is_string($text) && '' !== $text && !preg_match('/[^A-Z]/', $text);
181
+ }
182
+
183
+ /**
184
+ * 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.
185
+ *
186
+ * @see https://php.net/ctype-xdigit
187
+ *
188
+ * @param mixed $text
189
+ *
190
+ * @return bool
191
+ */
192
+ public static function ctype_xdigit($text)
193
+ {
194
+ $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);
195
+
196
+ return \is_string($text) && '' !== $text && !preg_match('/[^A-Fa-f0-9]/', $text);
197
+ }
198
+
199
+ /**
200
+ * Converts integers to their char versions according to normal ctype behaviour, if needed.
201
+ *
202
+ * If an integer between -128 and 255 inclusive is provided,
203
+ * it is interpreted as the ASCII value of a single character
204
+ * (negative values have 256 added in order to allow characters in the Extended ASCII range).
205
+ * Any other integer is interpreted as a string containing the decimal digits of the integer.
206
+ *
207
+ * @param mixed $int
208
+ * @param string $function
209
+ *
210
+ * @return mixed
211
+ */
212
+ private static function convert_int_to_char_for_ctype($int, $function)
213
+ {
214
+ if (!\is_int($int)) {
215
+ return $int;
216
+ }
217
+
218
+ if ($int < -128 || $int > 255) {
219
+ return (string) $int;
220
+ }
221
+
222
+ if (\PHP_VERSION_ID >= 80100) {
223
+ @trigger_error($function.'(): Argument of type int will be interpreted as string in the future', \E_USER_DEPRECATED);
224
+ }
225
+
226
+ if ($int < 0) {
227
+ $int += 256;
228
+ }
229
+
230
+ return \chr($int);
231
+ }
232
+ }
vendor/browscap-php/symfony/polyfill-ctype/bootstrap.php ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of the Symfony package.
5
+ *
6
+ * (c) Fabien Potencier <fabien@symfony.com>
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ use Symfony\Polyfill\Ctype as p;
13
+
14
+ if (!function_exists('ctype_alnum')) {
15
+ function ctype_alnum($text) { return p\Ctype::ctype_alnum($text); }
16
+ }
17
+ if (!function_exists('ctype_alpha')) {
18
+ function ctype_alpha($text) { return p\Ctype::ctype_alpha($text); }
19
+ }
20
+ if (!function_exists('ctype_cntrl')) {
21
+ function ctype_cntrl($text) { return p\Ctype::ctype_cntrl($text); }
22
+ }
23
+ if (!function_exists('ctype_digit')) {
24
+ function ctype_digit($text) { return p\Ctype::ctype_digit($text); }
25
+ }
26
+ if (!function_exists('ctype_graph')) {
27
+ function ctype_graph($text) { return p\Ctype::ctype_graph($text); }
28
+ }
29
+ if (!function_exists('ctype_lower')) {
30
+ function ctype_lower($text) { return p\Ctype::ctype_lower($text); }
31
+ }
32
+ if (!function_exists('ctype_print')) {
33
+ function ctype_print($text) { return p\Ctype::ctype_print($text); }
34
+ }
35
+ if (!function_exists('ctype_punct')) {
36
+ function ctype_punct($text) { return p\Ctype::ctype_punct($text); }
37
+ }
38
+ if (!function_exists('ctype_space')) {
39
+ function ctype_space($text) { return p\Ctype::ctype_space($text); }
40
+ }
41
+ if (!function_exists('ctype_upper')) {
42
+ function ctype_upper($text) { return p\Ctype::ctype_upper($text); }
43
+ }
44
+ if (!function_exists('ctype_xdigit')) {
45
+ function ctype_xdigit($text) { return p\Ctype::ctype_xdigit($text); }
46
+ }
vendor/browscap-php/symfony/polyfill-intl-grapheme/Grapheme.php ADDED
@@ -0,0 +1,247 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of the Symfony package.
5
+ *
6
+ * (c) Fabien Potencier <fabien@symfony.com>
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ namespace Symfony\Polyfill\Intl\Grapheme;
13
+
14
+ \define('SYMFONY_GRAPHEME_CLUSTER_RX', ((float) \PCRE_VERSION < 10 ? (float) \PCRE_VERSION >= 8.32 : (float) \PCRE_VERSION >= 10.39) ? '\X' : Grapheme::GRAPHEME_CLUSTER_RX);
15
+
16
+ /**
17
+ * Partial intl implementation in pure PHP.
18
+ *
19
+ * Implemented:
20
+ * - grapheme_extract - Extract a sequence of grapheme clusters from a text buffer, which must be encoded in UTF-8
21
+ * - grapheme_stripos - Find position (in grapheme units) of first occurrence of a case-insensitive string
22
+ * - grapheme_stristr - Returns part of haystack string from the first occurrence of case-insensitive needle to the end of haystack
23
+ * - grapheme_strlen - Get string length in grapheme units
24
+ * - grapheme_strpos - Find position (in grapheme units) of first occurrence of a string
25
+ * - grapheme_strripos - Find position (in grapheme units) of last occurrence of a case-insensitive string
26
+ * - grapheme_strrpos - Find position (in grapheme units) of last occurrence of a string
27
+ * - grapheme_strstr - Returns part of haystack string from the first occurrence of needle to the end of haystack
28
+ * - grapheme_substr - Return part of a string
29
+ *
30
+ * @author Nicolas Grekas <p@tchwork.com>
31
+ *
32
+ * @internal
33
+ */
34
+ final class Grapheme
35
+ {
36
+ // (CRLF|([ZWNJ-ZWJ]|T+|L*(LV?V+|LV|LVT)T*|L+|[^Control])[Extend]*|[Control])
37
+ // This regular expression is a work around for http://bugs.exim.org/1279
38
+ 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}])';
39
+
40
+ private const CASE_FOLD = [
41
+ ['µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"],
42
+ ['μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1", 'ι'],
43
+ ];
44
+
45
+ public static function grapheme_extract($s, $size, $type = \GRAPHEME_EXTR_COUNT, $start = 0, &$next = 0)
46
+ {
47
+ if (0 > $start) {
48
+ $start = \strlen($s) + $start;
49
+ }
50
+
51
+ if (!is_scalar($s)) {
52
+ $hasError = false;
53
+ set_error_handler(function () use (&$hasError) { $hasError = true; });
54
+ $next = substr($s, $start);
55
+ restore_error_handler();
56
+ if ($hasError) {
57
+ substr($s, $start);
58
+ $s = '';
59
+ } else {
60
+ $s = $next;
61
+ }
62
+ } else {
63
+ $s = substr($s, $start);
64
+ }
65
+ $size = (int) $size;
66
+ $type = (int) $type;
67
+ $start = (int) $start;
68
+
69
+ if (\GRAPHEME_EXTR_COUNT !== $type && \GRAPHEME_EXTR_MAXBYTES !== $type && \GRAPHEME_EXTR_MAXCHARS !== $type) {
70
+ if (80000 > \PHP_VERSION_ID) {
71
+ return false;
72
+ }
73
+
74
+ throw new \ValueError('grapheme_extract(): Argument #3 ($type) must be one of GRAPHEME_EXTR_COUNT, GRAPHEME_EXTR_MAXBYTES, or GRAPHEME_EXTR_MAXCHARS');
75
+ }
76
+
77
+ if (!isset($s[0]) || 0 > $size || 0 > $start) {
78
+ return false;
79
+ }
80
+ if (0 === $size) {
81
+ return '';
82
+ }
83
+
84
+ $next = $start;
85
+
86
+ $s = preg_split('/('.SYMFONY_GRAPHEME_CLUSTER_RX.')/u', "\r\n".$s, $size + 1, \PREG_SPLIT_NO_EMPTY | \PREG_SPLIT_DELIM_CAPTURE);
87
+
88
+ if (!isset($s[1])) {
89
+ return false;
90
+ }
91
+
92
+ $i = 1;
93
+ $ret = '';
94
+
95
+ do {
96
+ if (\GRAPHEME_EXTR_COUNT === $type) {
97
+ --$size;
98
+ } elseif (\GRAPHEME_EXTR_MAXBYTES === $type) {
99
+ $size -= \strlen($s[$i]);
100
+ } else {
101
+ $size -= iconv_strlen($s[$i], 'UTF-8//IGNORE');
102
+ }
103
+
104
+ if ($size >= 0) {
105
+ $ret .= $s[$i];
106
+ }
107
+ } while (isset($s[++$i]) && $size > 0);
108
+
109
+ $next += \strlen($ret);
110
+
111
+ return $ret;
112
+ }
113
+
114
+ public static function grapheme_strlen($s)
115
+ {
116
+ preg_replace('/'.SYMFONY_GRAPHEME_CLUSTER_RX.'/u', '', $s, -1, $len);
117
+
118
+ return 0 === $len && '' !== $s ? null : $len;
119
+ }
120
+
121
+ public static function grapheme_substr($s, $start, $len = null)
122
+ {
123
+ if (null === $len) {
124
+ $len = 2147483647;
125
+ }
126
+
127
+ preg_match_all('/'.SYMFONY_GRAPHEME_CLUSTER_RX.'/u', $s, $s);
128
+
129
+ $slen = \count($s[0]);
130
+ $start = (int) $start;
131
+
132
+ if (0 > $start) {
133
+ $start += $slen;
134
+ }
135
+ if (0 > $start) {
136
+ if (\PHP_VERSION_ID < 80000) {
137
+ return false;
138
+ }
139
+
140
+ $start = 0;
141
+ }
142
+ if ($start >= $slen) {
143
+ return \PHP_VERSION_ID >= 80000 ? '' : false;
144
+ }
145
+
146
+ $rem = $slen - $start;
147
+
148
+ if (0 > $len) {
149
+ $len += $rem;
150
+ }
151
+ if (0 === $len) {
152
+ return '';
153
+ }
154
+ if (0 > $len) {
155
+ return \PHP_VERSION_ID >= 80000 ? '' : false;
156
+ }
157
+ if ($len > $rem) {
158
+ $len = $rem;
159
+ }
160
+
161
+ return implode('', \array_slice($s[0], $start, $len));
162
+ }
163
+
164
+ public static function grapheme_strpos($s, $needle, $offset = 0)
165
+ {
166
+ return self::grapheme_position($s, $needle, $offset, 0);
167
+ }
168
+
169
+ public static function grapheme_stripos($s, $needle, $offset = 0)
170
+ {
171
+ return self::grapheme_position($s, $needle, $offset, 1);
172
+ }
173
+
174
+ public static function grapheme_strrpos($s, $needle, $offset = 0)
175
+ {
176
+ return self::grapheme_position($s, $needle, $offset, 2);
177
+ }
178
+
179
+ public static function grapheme_strripos($s, $needle, $offset = 0)
180
+ {
181
+ return self::grapheme_position($s, $needle, $offset, 3);
182
+ }
183
+
184
+ public static function grapheme_stristr($s, $needle, $beforeNeedle = false)
185
+ {
186
+ return mb_stristr($s, $needle, $beforeNeedle, 'UTF-8');
187
+ }
188
+
189
+ public static function grapheme_strstr($s, $needle, $beforeNeedle = false)
190
+ {
191
+ return mb_strstr($s, $needle, $beforeNeedle, 'UTF-8');
192
+ }
193
+
194
+ private static function grapheme_position($s, $needle, $offset, $mode)
195
+ {
196
+ $needle = (string) $needle;
197
+ if (80000 > \PHP_VERSION_ID && !preg_match('/./us', $needle)) {
198
+ return false;
199
+ }
200
+ $s = (string) $s;
201
+ if (!preg_match('/./us', $s)) {
202
+ return false;
203
+ }
204
+ if ($offset > 0) {
205
+ $s = self::grapheme_substr($s, $offset);
206
+ } elseif ($offset < 0) {
207
+ if (2 > $mode) {
208
+ $offset += self::grapheme_strlen($s);
209
+ $s = self::grapheme_substr($s, $offset);
210
+ if (0 > $offset) {
211
+ $offset = 0;
212
+ }
213
+ } elseif (0 > $offset += self::grapheme_strlen($needle)) {
214
+ $s = self::grapheme_substr($s, 0, $offset);
215
+ $offset = 0;
216
+ } else {
217
+ $offset = 0;
218
+ }
219
+ }
220
+
221
+ // As UTF-8 is self-synchronizing, and we have ensured the strings are valid UTF-8,
222
+ // we can use normal binary string functions here. For case-insensitive searches,
223
+ // case fold the strings first.
224
+ $caseInsensitive = $mode & 1;
225
+ $reverse = $mode & 2;
226
+ if ($caseInsensitive) {
227
+ // Use the same case folding mode as mbstring does for mb_stripos().
228
+ // Stick to SIMPLE case folding to avoid changing the length of the string, which
229
+ // might result in offsets being shifted.
230
+ $mode = \defined('MB_CASE_FOLD_SIMPLE') ? \MB_CASE_FOLD_SIMPLE : \MB_CASE_LOWER;
231
+ $s = mb_convert_case($s, $mode, 'UTF-8');
232
+ $needle = mb_convert_case($needle, $mode, 'UTF-8');
233
+
234
+ if (!\defined('MB_CASE_FOLD_SIMPLE')) {
235
+ $s = str_replace(self::CASE_FOLD[0], self::CASE_FOLD[1], $s);
236
+ $needle = str_replace(self::CASE_FOLD[0], self::CASE_FOLD[1], $needle);
237
+ }
238
+ }
239
+ if ($reverse) {
240
+ $needlePos = strrpos($s, $needle);
241
+ } else {
242
+ $needlePos = strpos($s, $needle);
243
+ }
244
+
245
+ return false !== $needlePos ? self::grapheme_strlen(substr($s, 0, $needlePos)) + $offset : false;
246
+ }
247
+ }
vendor/browscap-php/symfony/polyfill-intl-grapheme/bootstrap.php ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of the Symfony package.
5
+ *
6
+ * (c) Fabien Potencier <fabien@symfony.com>
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ use Symfony\Polyfill\Intl\Grapheme as p;
13
+
14
+ if (extension_loaded('intl')) {
15
+ return;
16
+ }
17
+
18
+ if (!defined('GRAPHEME_EXTR_COUNT')) {
19
+ define('GRAPHEME_EXTR_COUNT', 0);
20
+ }
21
+ if (!defined('GRAPHEME_EXTR_MAXBYTES')) {
22
+ define('GRAPHEME_EXTR_MAXBYTES', 1);
23
+ }
24
+ if (!defined('GRAPHEME_EXTR_MAXCHARS')) {
25
+ define('GRAPHEME_EXTR_MAXCHARS', 2);
26
+ }
27
+
28
+ if (!function_exists('grapheme_extract')) {
29
+ function grapheme_extract($haystack, $size, $type = 0, $start = 0, &$next = 0) { return p\Grapheme::grapheme_extract($haystack, $size, $type, $start, $next); }
30
+ }
31
+ if (!function_exists('grapheme_stripos')) {
32
+ function grapheme_stripos($haystack, $needle, $offset = 0) { return p\Grapheme::grapheme_stripos($haystack, $needle, $offset); }
33
+ }
34
+ if (!function_exists('grapheme_stristr')) {
35
+ function grapheme_stristr($haystack, $needle, $beforeNeedle = false) { return p\Grapheme::grapheme_stristr($haystack, $needle, $beforeNeedle); }
36
+ }
37
+ if (!function_exists('grapheme_strlen')) {
38
+ function grapheme_strlen($input) { return p\Grapheme::grapheme_strlen($input); }
39
+ }
40
+ if (!function_exists('grapheme_strpos')) {
41
+ function grapheme_strpos($haystack, $needle, $offset = 0) { return p\Grapheme::grapheme_strpos($haystack, $needle, $offset); }
42
+ }
43
+ if (!function_exists('grapheme_strripos')) {
44
+ function grapheme_strripos($haystack, $needle, $offset = 0) { return p\Grapheme::grapheme_strripos($haystack, $needle, $offset); }
45
+ }
46
+ if (!function_exists('grapheme_strrpos')) {
47
+ function grapheme_strrpos($haystack, $needle, $offset = 0) { return p\Grapheme::grapheme_strrpos($haystack, $needle, $offset); }
48
+ }
49
+ if (!function_exists('grapheme_strstr')) {
50
+ function grapheme_strstr($haystack, $needle, $beforeNeedle = false) { return p\Grapheme::grapheme_strstr($haystack, $needle, $beforeNeedle); }
51
+ }
52
+ if (!function_exists('grapheme_substr')) {
53
+ function grapheme_substr($string, $offset, $length = null) { return p\Grapheme::grapheme_substr($string, $offset, $length); }
54
+ }
vendor/browscap-php/symfony/polyfill-intl-normalizer/Normalizer.php ADDED
@@ -0,0 +1,310 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of the Symfony package.
5
+ *
6
+ * (c) Fabien Potencier <fabien@symfony.com>
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ namespace Symfony\Polyfill\Intl\Normalizer;
13
+
14
+ /**
15
+ * Normalizer is a PHP fallback implementation of the Normalizer class provided by the intl extension.
16
+ *
17
+ * It has been validated with Unicode 6.3 Normalization Conformance Test.
18
+ * See http://www.unicode.org/reports/tr15/ for detailed info about Unicode normalizations.
19
+ *
20
+ * @author Nicolas Grekas <p@tchwork.com>
21
+ *
22
+ * @internal
23
+ */
24
+ class Normalizer
25
+ {
26
+ public const FORM_D = \Normalizer::FORM_D;
27
+ public const FORM_KD = \Normalizer::FORM_KD;
28
+ public const FORM_C = \Normalizer::FORM_C;
29
+ public const FORM_KC = \Normalizer::FORM_KC;
30
+ public const NFD = \Normalizer::NFD;
31
+ public const NFKD = \Normalizer::NFKD;
32
+ public const NFC = \Normalizer::NFC;
33
+ public const NFKC = \Normalizer::NFKC;
34
+
35
+ private static $C;
36
+ private static $D;
37
+ private static $KD;
38
+ private static $cC;
39
+ private static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4];
40
+ private static $ASCII = "\x20\x65\x69\x61\x73\x6E\x74\x72\x6F\x6C\x75\x64\x5D\x5B\x63\x6D\x70\x27\x0A\x67\x7C\x68\x76\x2E\x66\x62\x2C\x3A\x3D\x2D\x71\x31\x30\x43\x32\x2A\x79\x78\x29\x28\x4C\x39\x41\x53\x2F\x50\x22\x45\x6A\x4D\x49\x6B\x33\x3E\x35\x54\x3C\x44\x34\x7D\x42\x7B\x38\x46\x77\x52\x36\x37\x55\x47\x4E\x3B\x4A\x7A\x56\x23\x48\x4F\x57\x5F\x26\x21\x4B\x3F\x58\x51\x25\x59\x5C\x09\x5A\x2B\x7E\x5E\x24\x40\x60\x7F\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F";
41
+
42
+ public static function isNormalized(string $s, int $form = self::FORM_C)
43
+ {
44
+ if (!\in_array($form, [self::NFD, self::NFKD, self::NFC, self::NFKC])) {
45
+ return false;
46
+ }
47
+ if (!isset($s[strspn($s, self::$ASCII)])) {
48
+ return true;
49
+ }
50
+ if (self::NFC == $form && preg_match('//u', $s) && !preg_match('/[^\x00-\x{2FF}]/u', $s)) {
51
+ return true;
52
+ }
53
+
54
+ return self::normalize($s, $form) === $s;
55
+ }
56
+
57
+ public static function normalize(string $s, int $form = self::FORM_C)
58
+ {
59
+ if (!preg_match('//u', $s)) {
60
+ return false;
61
+ }
62
+
63
+ switch ($form) {
64
+ case self::NFC: $C = true; $K = false; break;
65
+ case self::NFD: $C = false; $K = false; break;
66
+ case self::NFKC: $C = true; $K = true; break;
67
+ case self::NFKD: $C = false; $K = true; break;
68
+ default:
69
+ if (\defined('Normalizer::NONE') && \Normalizer::NONE == $form) {
70
+ return $s;
71
+ }
72
+
73
+ if (80000 > \PHP_VERSION_ID) {
74
+ return false;
75
+ }
76
+
77
+ throw new \ValueError('normalizer_normalize(): Argument #2 ($form) must be a a valid normalization form');
78
+ }
79
+
80
+ if ('' === $s) {
81
+ return '';
82
+ }
83
+
84
+ if ($K && null === self::$KD) {
85
+ self::$KD = self::getData('compatibilityDecomposition');
86
+ }
87
+
88
+ if (null === self::$D) {
89
+ self::$D = self::getData('canonicalDecomposition');
90
+ self::$cC = self::getData('combiningClass');
91
+ }
92
+
93
+ if (null !== $mbEncoding = (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) ? mb_internal_encoding() : null) {
94
+ mb_internal_encoding('8bit');
95
+ }
96
+
97
+ $r = self::decompose($s, $K);
98
+
99
+ if ($C) {
100
+ if (null === self::$C) {
101
+ self::$C = self::getData('canonicalComposition');
102
+ }
103
+
104
+ $r = self::recompose($r);
105
+ }
106
+ if (null !== $mbEncoding) {
107
+ mb_internal_encoding($mbEncoding);
108
+ }
109
+
110
+ return $r;
111
+ }
112
+
113
+ private static function recompose($s)
114
+ {
115
+ $ASCII = self::$ASCII;
116
+ $compMap = self::$C;
117
+ $combClass = self::$cC;
118
+ $ulenMask = self::$ulenMask;
119
+
120
+ $result = $tail = '';
121
+
122
+ $i = $s[0] < "\x80" ? 1 : $ulenMask[$s[0] & "\xF0"];
123
+ $len = \strlen($s);
124
+
125
+ $lastUchr = substr($s, 0, $i);
126
+ $lastUcls = isset($combClass[$lastUchr]) ? 256 : 0;
127
+
128
+ while ($i < $len) {
129
+ if ($s[$i] < "\x80") {
130
+ // ASCII chars
131
+
132
+ if ($tail) {
133
+ $lastUchr .= $tail;
134
+ $tail = '';
135
+ }
136
+
137
+ if ($j = strspn($s, $ASCII, $i + 1)) {
138
+ $lastUchr .= substr($s, $i, $j);
139
+ $i += $j;
140
+ }
141
+
142
+ $result .= $lastUchr;
143
+ $lastUchr = $s[$i];
144
+ $lastUcls = 0;
145
+ ++$i;
146
+ continue;
147
+ }
148
+
149
+ $ulen = $ulenMask[$s[$i] & "\xF0"];
150
+ $uchr = substr($s, $i, $ulen);
151
+
152
+ if ($lastUchr < "\xE1\x84\x80" || "\xE1\x84\x92" < $lastUchr
153
+ || $uchr < "\xE1\x85\xA1" || "\xE1\x85\xB5" < $uchr
154
+ || $lastUcls) {
155
+ // Table lookup and combining chars composition
156
+
157
+ $ucls = $combClass[$uchr] ?? 0;
158
+
159
+ if (isset($compMap[$lastUchr.$uchr]) && (!$lastUcls || $lastUcls < $ucls)) {
160
+ $lastUchr = $compMap[$lastUchr.$uchr];
161
+ } elseif ($lastUcls = $ucls) {
162
+ $tail .= $uchr;
163
+ } else {
164
+ if ($tail) {
165
+ $lastUchr .= $tail;
166
+ $tail = '';
167
+ }
168
+
169
+ $result .= $lastUchr;
170
+ $lastUchr = $uchr;
171
+ }
172
+ } else {
173
+ // Hangul chars
174
+
175
+ $L = \ord($lastUchr[2]) - 0x80;
176
+ $V = \ord($uchr[2]) - 0xA1;
177
+ $T = 0;
178
+
179
+ $uchr = substr($s, $i + $ulen, 3);
180
+
181
+ if ("\xE1\x86\xA7" <= $uchr && $uchr <= "\xE1\x87\x82") {
182
+ $T = \ord($uchr[2]) - 0xA7;
183
+ 0 > $T && $T += 0x40;
184
+ $ulen += 3;
185
+ }
186
+
187
+ $L = 0xAC00 + ($L * 21 + $V) * 28 + $T;
188
+ $lastUchr = \chr(0xE0 | $L >> 12).\chr(0x80 | $L >> 6 & 0x3F).\chr(0x80 | $L & 0x3F);
189
+ }
190
+
191
+ $i += $ulen;
192
+ }
193
+
194
+ return $result.$lastUchr.$tail;
195
+ }
196
+
197
+ private static function decompose($s, $c)
198
+ {
199
+ $result = '';
200
+
201
+ $ASCII = self::$ASCII;
202
+ $decompMap = self::$D;
203
+ $combClass = self::$cC;
204
+ $ulenMask = self::$ulenMask;
205
+ if ($c) {
206
+ $compatMap = self::$KD;
207
+ }
208
+
209
+ $c = [];
210
+ $i = 0;
211
+ $len = \strlen($s);
212
+
213
+ while ($i < $len) {
214
+ if ($s[$i] < "\x80") {
215
+ // ASCII chars
216
+
217
+ if ($c) {
218
+ ksort($c);
219
+ $result .= implode('', $c);
220
+ $c = [];
221
+ }
222
+
223
+ $j = 1 + strspn($s, $ASCII, $i + 1);
224
+ $result .= substr($s, $i, $j);
225
+ $i += $j;
226
+ continue;
227
+ }
228
+
229
+ $ulen = $ulenMask[$s[$i] & "\xF0"];
230
+ $uchr = substr($s, $i, $ulen);
231
+ $i += $ulen;
232
+
233
+ if ($uchr < "\xEA\xB0\x80" || "\xED\x9E\xA3" < $uchr) {
234
+ // Table lookup
235
+
236
+ if ($uchr !== $j = $compatMap[$uchr] ?? ($decompMap[$uchr] ?? $uchr)) {
237
+ $uchr = $j;
238
+
239
+ $j = \strlen($uchr);
240
+ $ulen = $uchr[0] < "\x80" ? 1 : $ulenMask[$uchr[0] & "\xF0"];
241
+
242
+ if ($ulen != $j) {
243
+ // Put trailing chars in $s
244
+
245
+ $j -= $ulen;
246
+ $i -= $j;
247
+
248
+ if (0 > $i) {
249
+ $s = str_repeat(' ', -$i).$s;
250
+ $len -= $i;
251
+ $i = 0;
252
+ }
253
+
254
+ while ($j--) {
255
+ $s[$i + $j] = $uchr[$ulen + $j];
256
+ }
257
+
258
+ $uchr = substr($uchr, 0, $ulen);
259
+ }
260
+ }
261
+ if (isset($combClass[$uchr])) {
262
+ // Combining chars, for sorting
263
+
264
+ if (!isset($c[$combClass[$uchr]])) {
265
+ $c[$combClass[$uchr]] = '';
266
+ }
267
+ $c[$combClass[$uchr]] .= $uchr;
268
+ continue;
269
+ }
270
+ } else {
271
+ // Hangul chars
272
+
273
+ $uchr = unpack('C*', $uchr);
274
+ $j = (($uchr[1] - 224) << 12) + (($uchr[2] - 128) << 6) + $uchr[3] - 0xAC80;
275
+
276
+ $uchr = "\xE1\x84".\chr(0x80 + (int) ($j / 588))
277
+ ."\xE1\x85".\chr(0xA1 + (int) (($j % 588) / 28));
278
+
279
+ if ($j %= 28) {
280
+ $uchr .= $j < 25
281
+ ? ("\xE1\x86".\chr(0xA7 + $j))
282
+ : ("\xE1\x87".\chr(0x67 + $j));
283
+ }
284
+ }
285
+ if ($c) {
286
+ ksort($c);
287
+ $result .= implode('', $c);
288
+ $c = [];
289
+ }
290
+
291
+ $result .= $uchr;
292
+ }
293
+
294
+ if ($c) {
295
+ ksort($c);
296
+ $result .= implode('', $c);
297
+ }
298
+
299
+ return $result;
300
+ }
301
+
302
+ private static function getData($file)
303
+ {
304
+ if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) {
305
+ return require $file;
306
+ }
307
+
308
+ return false;
309
+ }
310
+ }
vendor/browscap-php/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Normalizer extends Symfony\Polyfill\Intl\Normalizer\Normalizer
4
+ {
5
+ /**
6
+ * @deprecated since ICU 56 and removed in PHP 8
7
+ */
8
+ public const NONE = 2;
9
+ public const FORM_D = 4;
10
+ public const FORM_KD = 8;
11
+ public const FORM_C = 16;
12
+ public const FORM_KC = 32;
13
+ public const NFD = 4;
14
+ public const NFKD = 8;
15
+ public const NFC = 16;
16
+ public const NFKC = 32;
17
+ }
vendor/browscap-php/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalComposition.php ADDED
@@ -0,0 +1,945 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ return array (
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
+ 'á' => 'á',
32
+ 'â' => 'â',
33
+ 'ã' => 'ã',
34
+ 'ä' => 'ä',
35
+ 'å' => 'å',
36
+ 'ç' => 'ç',
37
+ 'è' => 'è',
38
+ 'é' => 'é',
39
+ 'ê' => 'ê',
40
+ 'ë' => 'ë',
41
+ 'ì' => 'ì',
42
+ 'í' => 'í',
43
+ 'î' => 'î',
44
+ 'ï' => 'ï',
45
+ 'ñ' => 'ñ',
46
+ 'ò' => 'ò',
47
+ 'ó' => 'ó',
48
+ 'ô' => 'ô',
49
+ 'õ' => 'õ',
50
+ 'ö' => 'ö',
51
+ 'ù' => 'ù',
52
+ 'ú' => 'ú',
53
+ 'û' => 'û',
54
+ 'ü' => 'ü',
55
+ 'ý' => 'ý',
56
+ 'ÿ' => 'ÿ',
57
+ 'Ā' => 'Ā',
58
+ 'ā' => 'ā',
59
+ 'Ă' => 'Ă',
60
+ 'ă' => 'ă',
61
+ 'Ą' => 'Ą',
62
+ 'ą' => 'ą',
63
+ 'Ć' => 'Ć',
64
+ 'ć' => 'ć',
65
+ 'Ĉ' => 'Ĉ',
66
+ 'ĉ' => 'ĉ',
67
+ 'Ċ' => 'Ċ',
68
+ 'ċ' => 'ċ',
69
+ 'Č' => 'Č',
70
+ 'č' => 'č',
71
+ 'Ď' => 'Ď',
72
+ 'ď' => 'ď',
73
+ 'Ē' => 'Ē',
74
+ 'ē' => 'ē',
75
+ 'Ĕ' => 'Ĕ',
76
+ 'ĕ' => 'ĕ',
77
+ 'Ė' => 'Ė',
78
+ 'ė' => 'ė',
79
+ 'Ę' => 'Ę',
80
+ 'ę' => 'ę',
81
+ 'Ě' => 'Ě',
82
+ 'ě' => 'ě',
83
+ 'Ĝ' => 'Ĝ',
84
+ 'ĝ' => 'ĝ',
85
+ 'Ğ' => 'Ğ',
86
+ 'ğ' => 'ğ',
87
+ 'Ġ' => 'Ġ',
88
+ 'ġ' => 'ġ',
89
+ 'Ģ' => 'Ģ',
90
+ 'ģ' => 'ģ',
91
+ 'Ĥ' => 'Ĥ',
92
+ 'ĥ' => 'ĥ',
93
+ 'Ĩ' => 'Ĩ',
94
+ 'ĩ' => 'ĩ',
95
+ 'Ī' => 'Ī',
96
+ 'ī' => 'ī',
97
+ 'Ĭ' => 'Ĭ',
98
+ 'ĭ' => 'ĭ',
99
+ 'Į' => 'Į',
100
+ 'į' => 'į',
101
+ 'İ' => 'İ',
102
+ 'Ĵ' => 'Ĵ',
103
+ 'ĵ' => 'ĵ',
104
+ 'Ķ' => 'Ķ',
105
+ 'ķ' => 'ķ',
106
+ 'Ĺ' => 'Ĺ',
107
+ 'ĺ' => 'ĺ',
108
+ 'Ļ' => 'Ļ',
109
+ 'ļ' => 'ļ',
110
+ 'Ľ' => 'Ľ',
111
+ 'ľ' => 'ľ',
112
+ 'Ń' => 'Ń',
113
+ 'ń' => 'ń',
114
+ 'Ņ' => 'Ņ',
115
+ 'ņ' => 'ņ',
116
+ 'Ň' => 'Ň',
117
+ 'ň' => 'ň',
118
+ 'Ō' => 'Ō',
119
+ 'ō' => 'ō',
120
+ 'Ŏ' => 'Ŏ',
121
+ 'ŏ' => 'ŏ',
122
+ 'Ő' => 'Ő',
123
+ 'ő' => 'ő',
124
+ 'Ŕ' => 'Ŕ',
125
+ 'ŕ' => 'ŕ',
126
+ 'Ŗ' => 'Ŗ',
127
+ 'ŗ' => 'ŗ',
128
+ 'Ř' => 'Ř',
129
+ 'ř' => 'ř',
130
+ 'Ś' => 'Ś',
131
+ 'ś' => 'ś',
132
+ 'Ŝ' => 'Ŝ',
133
+ 'ŝ' => 'ŝ',
134
+ 'Ş' => 'Ş',
135
+ 'ş' => 'ş',
136
+ 'Š' => 'Š',
137
+ 'š' => 'š',
138
+ 'Ţ' => 'Ţ',
139
+ 'ţ' => 'ţ',
140
+ 'Ť' => 'Ť',
141
+ 'ť' => 'ť',
142
+ 'Ũ' => 'Ũ',
143
+ 'ũ' => 'ũ',
144
+ 'Ū' => 'Ū',
145
+ 'ū' => 'ū',
146
+ 'Ŭ' => 'Ŭ',
147
+ 'ŭ' => 'ŭ',
148
+ 'Ů' => 'Ů',
149
+ 'ů' => 'ů',
150
+ 'Ű' => 'Ű',
151
+ 'ű' => 'ű',
152
+ 'Ų' => 'Ų',
153
+ 'ų' => 'ų',
154
+ 'Ŵ' => 'Ŵ',
155
+ 'ŵ' => 'ŵ',
156
+ 'Ŷ' => 'Ŷ',
157
+ 'ŷ' => 'ŷ',
158
+ 'Ÿ' => 'Ÿ',
159
+ 'Ź' => 'Ź',
160
+ 'ź' => 'ź',
161
+ 'Ż' => 'Ż',
162
+ 'ż' => 'ż',
163
+ 'Ž' => 'Ž',
164
+ 'ž' => 'ž',
165
+ 'Ơ' => 'Ơ',
166
+ 'ơ' => 'ơ',
167
+ 'Ư' => 'Ư',
168
+ 'ư' => 'ư',
169
+ 'Ǎ' => 'Ǎ',
170
+ 'ǎ' => 'ǎ',
171
+ 'Ǐ' => 'Ǐ',
172
+ 'ǐ' => 'ǐ',
173
+ 'Ǒ' => 'Ǒ',
174
+ 'ǒ' => 'ǒ',
175
+ 'Ǔ' => 'Ǔ',
176
+ 'ǔ' => 'ǔ',
177
+ 'Ǖ' => 'Ǖ',
178
+ 'ǖ' => 'ǖ',
179
+ 'Ǘ' => 'Ǘ',
180
+ 'ǘ' => 'ǘ',
181
+ 'Ǚ' => 'Ǚ',
182
+ 'ǚ' => 'ǚ',
183
+ 'Ǜ' => 'Ǜ',
184
+ 'ǜ' => 'ǜ',
185
+ 'Ǟ' => 'Ǟ',
186
+ 'ǟ' => 'ǟ',
187
+ 'Ǡ' => 'Ǡ',
188
+ 'ǡ' => 'ǡ',
189
+ 'Ǣ' => 'Ǣ',
190
+ 'ǣ' => 'ǣ',
191
+ 'Ǧ' => 'Ǧ',
192
+ 'ǧ' => 'ǧ',
193
+ 'Ǩ' => 'Ǩ',
194
+ 'ǩ' => 'ǩ',
195
+ 'Ǫ' => 'Ǫ',
196
+ 'ǫ' => 'ǫ',
197
+ 'Ǭ' => 'Ǭ',
198
+ 'ǭ' => 'ǭ',
199
+ 'Ǯ' => 'Ǯ',
200
+ 'ǯ' => 'ǯ',
201
+ 'ǰ' => 'ǰ',
202
+ 'Ǵ' => 'Ǵ',
203
+ 'ǵ' => 'ǵ',
204
+ 'Ǹ' => 'Ǹ',
205
+ 'ǹ' => 'ǹ',
206
+ 'Ǻ' => 'Ǻ',
207
+ 'ǻ' => 'ǻ',
208
+ 'Ǽ' => 'Ǽ',
209
+ 'ǽ' => 'ǽ',
210
+ 'Ǿ' => 'Ǿ',
211
+ 'ǿ' => 'ǿ',
212
+ 'Ȁ' => 'Ȁ',
213
+ 'ȁ' => 'ȁ',
214
+ 'Ȃ' => 'Ȃ',
215
+ 'ȃ' => 'ȃ',
216
+ 'Ȅ' => 'Ȅ',
217
+ 'ȅ' => 'ȅ',
218
+ 'Ȇ' => 'Ȇ',
219
+ 'ȇ' => 'ȇ',
220
+ 'Ȉ' => 'Ȉ',
221
+ 'ȉ' => 'ȉ',
222
+ 'Ȋ' => 'Ȋ',
223
+ 'ȋ' => 'ȋ',
224
+ 'Ȍ' => 'Ȍ',
225
+ 'ȍ' => 'ȍ',
226
+ 'Ȏ' => 'Ȏ',
227
+ 'ȏ' => 'ȏ',
228
+ 'Ȑ' => 'Ȑ',
229
+ 'ȑ' => 'ȑ',
230
+ 'Ȓ' => 'Ȓ',
231
+ 'ȓ' => 'ȓ',
232
+ 'Ȕ' => 'Ȕ',
233
+ 'ȕ' => 'ȕ',
234
+ 'Ȗ' => 'Ȗ',
235
+ 'ȗ' => 'ȗ',
236
+ 'Ș' => 'Ș',
237
+ 'ș' => 'ș',
238
+ 'Ț' => 'Ț',
239
+ 'ț' => 'ț',
240
+ 'Ȟ' => 'Ȟ',
241
+ 'ȟ' => 'ȟ',
242
+ 'Ȧ' => 'Ȧ',
243
+ 'ȧ' => 'ȧ',
244
+ 'Ȩ' => 'Ȩ',
245
+ 'ȩ' => 'ȩ',
246
+ 'Ȫ' => 'Ȫ',
247
+ 'ȫ' => 'ȫ',
248
+ 'Ȭ' => 'Ȭ',
249
+ 'ȭ' => 'ȭ',
250
+ 'Ȯ' => 'Ȯ',
251
+ 'ȯ' => 'ȯ',
252
+ 'Ȱ' => 'Ȱ',
253
+ 'ȱ' => 'ȱ',
254
+ 'Ȳ' => 'Ȳ',
255
+ 'ȳ' => 'ȳ',
256
+ '΅' => '΅',
257
+ 'Ά' => 'Ά',
258
+ 'Έ' => 'Έ',
259
+ 'Ή' => 'Ή',
260
+ 'Ί' => 'Ί',
261
+ 'Ό' => 'Ό',
262
+ 'Ύ' => 'Ύ',
263
+ 'Ώ' => 'Ώ',
264
+ 'ΐ' => 'ΐ',
265
+ 'Ϊ' => 'Ϊ',
266
+ 'Ϋ' => 'Ϋ',
267
+ 'ά' => 'ά',
268
+ 'έ' => 'έ',
269
+ 'ή' => 'ή',
270
+ 'ί' => 'ί',
271
+ 'ΰ' => 'ΰ',
272
+ 'ϊ' => 'ϊ',
273
+ 'ϋ' => 'ϋ',
274
+ 'ό' => 'ό',
275
+ 'ύ' => 'ύ',
276
+ 'ώ' => 'ώ',
277
+ 'ϓ' => 'ϓ',
278
+ 'ϔ' => 'ϔ',
279
+ 'Ѐ' => 'Ѐ',
280
+ 'Ё' => 'Ё',
281
+ 'Ѓ' => 'Ѓ',
282
+ 'Ї' => 'Ї',
283
+ 'Ќ' => 'Ќ',
284
+ 'Ѝ' => 'Ѝ',
285
+ 'Ў' => 'Ў',
286
+ 'Й' => 'Й',
287
+ 'й' => 'й',
288
+ 'ѐ' => 'ѐ',
289
+ 'ё' => 'ё',
290
+ 'ѓ' => 'ѓ',
291
+ 'ї' => 'ї',
292
+ 'ќ' => 'ќ',
293
+ 'ѝ' => 'ѝ',
294
+ 'ў' => 'ў',
295
+ 'Ѷ' => 'Ѷ',
296
+ 'ѷ' => 'ѷ',
297
+ 'Ӂ' => 'Ӂ',
298
+ 'ӂ' => 'ӂ',
299
+ 'Ӑ' => 'Ӑ',
300
+ 'ӑ' => 'ӑ',
301
+ 'Ӓ' => 'Ӓ',
302
+ 'ӓ' => 'ӓ',
303
+ 'Ӗ' => 'Ӗ',
304
+ 'ӗ' => 'ӗ',
305
+ 'Ӛ' => 'Ӛ',
306
+ 'ӛ' => 'ӛ',
307
+ 'Ӝ' => 'Ӝ',
308
+ 'ӝ' => 'ӝ',
309
+ 'Ӟ' => 'Ӟ',
310
+ 'ӟ' => 'ӟ',
311
+ 'Ӣ' => 'Ӣ',
312
+ 'ӣ' => 'ӣ',
313
+ 'Ӥ' => 'Ӥ',
314
+ 'ӥ' => 'ӥ',
315
+ 'Ӧ' => 'Ӧ',
316
+ 'ӧ' => 'ӧ',
317
+ 'Ӫ' => 'Ӫ',
318
+ 'ӫ' => 'ӫ',
319
+ 'Ӭ' => 'Ӭ',
320
+ 'ӭ' => 'ӭ',
321
+ 'Ӯ' => 'Ӯ',
322
+ 'ӯ' => 'ӯ',
323
+ 'Ӱ' => 'Ӱ',
324
+ 'ӱ' => 'ӱ',
325
+ 'Ӳ' => 'Ӳ',
326
+ 'ӳ' => 'ӳ',
327
+ 'Ӵ' => 'Ӵ',
328
+ 'ӵ' => 'ӵ',
329
+ 'Ӹ' => 'Ӹ',
330
+ 'ӹ' => 'ӹ',
331
+ 'آ' => 'آ',
332
+ 'أ' => 'أ',
333
+ 'ؤ' => 'ؤ',
334
+ 'إ' => 'إ',
335
+ 'ئ' => 'ئ',
336
+ 'ۀ' => 'ۀ',
337
+ 'ۂ' => 'ۂ',
338
+ 'ۓ' => 'ۓ',
339
+ 'ऩ' => 'ऩ',
340
+ 'ऱ' => 'ऱ',
341
+ 'ऴ' => 'ऴ',
342
+ 'ো' => 'ো',
343
+ 'ৌ' => 'ৌ',
344
+ 'ୈ' => 'ୈ',
345
+ 'ୋ' => 'ୋ',
346
+ 'ୌ' => 'ୌ',
347
+ 'ஔ' => 'ஔ',
348
+ 'ொ' => 'ொ',
349
+ 'ோ' => 'ோ',
350
+ 'ௌ' => 'ௌ',
351
+ 'ై' => 'ై',
352
+ 'ೀ' => 'ೀ',
353
+ 'ೇ' => 'ೇ',
354
+ 'ೈ' => 'ೈ',
355
+ 'ೊ' => 'ೊ',
356
+ 'ೋ' => 'ೋ',
357
+ 'ൊ' => 'ൊ',
358
+ 'ോ' => 'ോ',
359
+ 'ൌ' => 'ൌ',
360
+ 'ේ' => 'ේ',
361
+ 'ො' => 'ො',
362
+ 'ෝ' => 'ෝ',
363
+ 'ෞ' => 'ෞ',
364
+ 'ဦ' => 'ဦ',
365
+ 'ᬆ' => 'ᬆ',
366
+ 'ᬈ' => 'ᬈ',
367
+ 'ᬊ' => 'ᬊ',
368
+ 'ᬌ' => 'ᬌ',
369
+ 'ᬎ' => 'ᬎ',
370
+ 'ᬒ' => 'ᬒ',
371
+ 'ᬻ' => 'ᬻ',
372
+ 'ᬽ' => 'ᬽ',
373
+ 'ᭀ' => 'ᭀ',
374
+ 'ᭁ' => 'ᭁ',
375
+ 'ᭃ' => 'ᭃ',
376
+ 'Ḁ' => 'Ḁ',
377
+ 'ḁ' => 'ḁ',
378
+ 'Ḃ' => 'Ḃ',
379
+ 'ḃ' => 'ḃ',
380
+ 'Ḅ' => 'Ḅ',
381
+ 'ḅ' => 'ḅ',
382
+ 'Ḇ' => 'Ḇ',
383
+ 'ḇ' => 'ḇ',
384
+ 'Ḉ' => 'Ḉ',
385
+ 'ḉ' => 'ḉ',
386
+ 'Ḋ' => 'Ḋ',
387
+ 'ḋ' => 'ḋ',
388
+ 'Ḍ' => 'Ḍ',
389
+ 'ḍ' => 'ḍ',
390
+ 'Ḏ' => 'Ḏ',
391
+ 'ḏ' => 'ḏ',
392
+ 'Ḑ' => 'Ḑ',
393
+ 'ḑ' => 'ḑ',
394
+ 'Ḓ' => 'Ḓ',
395
+ 'ḓ' => 'ḓ',
396
+ 'Ḕ' => 'Ḕ',
397
+ 'ḕ' => 'ḕ',
398
+ 'Ḗ' => 'Ḗ',
399
+ 'ḗ' => 'ḗ',
400
+ 'Ḙ' => 'Ḙ',
401
+ 'ḙ' => 'ḙ',
402
+ 'Ḛ' => 'Ḛ',
403
+ 'ḛ' => 'ḛ',
404
+ 'Ḝ' => 'Ḝ',
405
+ 'ḝ' => 'ḝ',
406
+ 'Ḟ' => 'Ḟ',
407
+ 'ḟ' => 'ḟ',
408
+ 'Ḡ' => 'Ḡ',
409
+ 'ḡ' => 'ḡ',
410
+ 'Ḣ' => 'Ḣ',
411
+ 'ḣ' => 'ḣ',
412
+ 'Ḥ' => 'Ḥ',
413
+ 'ḥ' => 'ḥ',
414
+ 'Ḧ' => 'Ḧ',
415
+ 'ḧ' => 'ḧ',
416
+ 'Ḩ' => 'Ḩ',
417
+ 'ḩ' => 'ḩ',
418
+ 'Ḫ' => 'Ḫ',
419
+ 'ḫ' => 'ḫ',
420
+ 'Ḭ' => 'Ḭ',
421
+ 'ḭ' => 'ḭ',
422
+ 'Ḯ' => 'Ḯ',
423
+ 'ḯ' => 'ḯ',
424
+ 'Ḱ' => 'Ḱ',
425
+ 'ḱ' => 'ḱ',
426
+ 'Ḳ' => 'Ḳ',
427
+ 'ḳ' => 'ḳ',
428
+ 'Ḵ' => 'Ḵ',
429
+ 'ḵ' => 'ḵ',
430
+ 'Ḷ' => 'Ḷ',
431
+ 'ḷ' => 'ḷ',
432
+ 'Ḹ' => 'Ḹ',
433
+ 'ḹ' => 'ḹ',
434
+ 'Ḻ' => 'Ḻ',
435
+ 'ḻ' => 'ḻ',
436
+ 'Ḽ' => 'Ḽ',
437
+ 'ḽ' => 'ḽ',
438
+ 'Ḿ' => 'Ḿ',
439
+ 'ḿ' => 'ḿ',
440
+ 'Ṁ' => 'Ṁ',
441
+ 'ṁ' => 'ṁ',
442
+ 'Ṃ' => 'Ṃ',
443
+ 'ṃ' => 'ṃ',
444
+ 'Ṅ' => 'Ṅ',
445
+ 'ṅ' => 'ṅ',
446
+ 'Ṇ' => 'Ṇ',
447
+ 'ṇ' => 'ṇ',
448
+ 'Ṉ' => 'Ṉ',
449
+ 'ṉ' => 'ṉ',
450
+ 'Ṋ' => 'Ṋ',
451
+ 'ṋ' => 'ṋ',
452
+ 'Ṍ' => 'Ṍ',
453
+ 'ṍ' => 'ṍ',
454
+ 'Ṏ' => 'Ṏ',
455
+ 'ṏ' => 'ṏ',
456
+ 'Ṑ' => 'Ṑ',
457
+ 'ṑ' => 'ṑ',
458
+ 'Ṓ' => 'Ṓ',
459
+ 'ṓ' => 'ṓ',
460
+ 'Ṕ' => 'Ṕ',
461
+ 'ṕ' => 'ṕ',
462
+ 'Ṗ' => 'Ṗ',
463
+ 'ṗ' => 'ṗ',
464
+ 'Ṙ' => 'Ṙ',
465
+ 'ṙ' => 'ṙ',
466
+ 'Ṛ' => 'Ṛ',
467
+ 'ṛ' => 'ṛ',
468
+ 'Ṝ' => 'Ṝ',
469
+ 'ṝ' => 'ṝ',
470
+ 'Ṟ' => 'Ṟ',
471
+ 'ṟ' => 'ṟ',
472
+ 'Ṡ' => 'Ṡ',
473
+ 'ṡ' => 'ṡ',
474
+ 'Ṣ' => 'Ṣ',
475
+ 'ṣ' => 'ṣ',
476
+ 'Ṥ' => 'Ṥ',
477
+ 'ṥ' => 'ṥ',
478
+ 'Ṧ' => 'Ṧ',
479
+ 'ṧ' => 'ṧ',
480
+ 'Ṩ' => 'Ṩ',
481
+ 'ṩ' => 'ṩ',
482
+ 'Ṫ' => 'Ṫ',
483
+ 'ṫ' => 'ṫ',
484
+ 'Ṭ' => 'Ṭ',
485
+ 'ṭ' => 'ṭ',
486
+ 'Ṯ' => 'Ṯ',
487
+ 'ṯ' => 'ṯ',
488
+ 'Ṱ' => 'Ṱ',
489
+ 'ṱ' => 'ṱ',
490
+ 'Ṳ' => 'Ṳ',
491
+ 'ṳ' => 'ṳ',
492
+ 'Ṵ' => 'Ṵ',
493
+ 'ṵ' => 'ṵ',
494
+ 'Ṷ' => 'Ṷ',
495
+ 'ṷ' => 'ṷ',
496
+ 'Ṹ' => 'Ṹ',
497
+ 'ṹ' => 'ṹ',
498
+ 'Ṻ' => 'Ṻ',
499
+ 'ṻ' => 'ṻ',
500
+ 'Ṽ' => 'Ṽ',
501
+ 'ṽ' => 'ṽ',
502
+ 'Ṿ' => 'Ṿ',
503
+ 'ṿ' => 'ṿ',
504
+ 'Ẁ' => 'Ẁ',
505
+ 'ẁ' => 'ẁ',
506
+ 'Ẃ' => 'Ẃ',
507
+ 'ẃ' => 'ẃ',
508
+ 'Ẅ' => 'Ẅ',
509
+ 'ẅ' => 'ẅ',
510
+ 'Ẇ' => 'Ẇ',
511
+ 'ẇ' => 'ẇ',
512
+ 'Ẉ' => 'Ẉ',
513
+ 'ẉ' => 'ẉ',
514
+ 'Ẋ' => 'Ẋ',
515
+ 'ẋ' => 'ẋ',
516
+ 'Ẍ' => 'Ẍ',
517
+ 'ẍ' => 'ẍ',
518
+ 'Ẏ' => 'Ẏ',
519
+ 'ẏ' => 'ẏ',
520
+ 'Ẑ' => 'Ẑ',
521
+ 'ẑ' => 'ẑ',
522
+ 'Ẓ' => 'Ẓ',
523
+ 'ẓ' => 'ẓ',
524
+ 'Ẕ' => 'Ẕ',
525
+ 'ẕ' => 'ẕ',
526
+ 'ẖ' => 'ẖ',
527
+ 'ẗ' => 'ẗ',
528
+ 'ẘ' => 'ẘ',
529
+ 'ẙ' => 'ẙ',
530
+ 'ẛ' => 'ẛ',
531
+ 'Ạ' => 'Ạ',
532
+ 'ạ' => 'ạ',
533
+ 'Ả' => 'Ả',
534
+ 'ả' => 'ả',
535
+ 'Ấ' => 'Ấ',
536
+ 'ấ' => 'ấ',
537
+ 'Ầ' => 'Ầ',
538
+ 'ầ' => 'ầ',
539
+ 'Ẩ' => 'Ẩ',
540
+ 'ẩ' => 'ẩ',
541
+ 'Ẫ' => 'Ẫ',
542
+ 'ẫ' => 'ẫ',
543
+ 'Ậ' => 'Ậ',
544
+ 'ậ' => 'ậ',
545
+ 'Ắ' => 'Ắ',
546
+ 'ắ' => 'ắ',
547
+ 'Ằ' => 'Ằ',
548
+ 'ằ' => 'ằ',
549
+ 'Ẳ' => 'Ẳ',
550
+ 'ẳ' => 'ẳ',
551
+ 'Ẵ' => 'Ẵ',
552
+ 'ẵ' => 'ẵ',
553
+ 'Ặ' => 'Ặ',
554
+ 'ặ' => 'ặ',
555
+ 'Ẹ' => 'Ẹ',
556
+ 'ẹ' => 'ẹ',
557
+ 'Ẻ' => 'Ẻ',
558
+ 'ẻ' => 'ẻ',
559
+ 'Ẽ' => 'Ẽ',
560
+ 'ẽ' => 'ẽ',
561
+ 'Ế' => 'Ế',
562
+ 'ế' => 'ế',
563
+ 'Ề' => 'Ề',
564
+ 'ề' => 'ề',
565
+ 'Ể' => 'Ể',
566
+ 'ể' => 'ể',
567
+ 'Ễ' => 'Ễ',
568
+ 'ễ' => 'ễ',
569
+ 'Ệ' => 'Ệ',
570
+ 'ệ' => 'ệ',
571
+ 'Ỉ' => 'Ỉ',
572
+ 'ỉ' => 'ỉ',
573
+ 'Ị' => 'Ị',
574
+ 'ị' => 'ị',
575
+ 'Ọ' => 'Ọ',
576
+ 'ọ' => 'ọ',
577
+ 'Ỏ' => 'Ỏ',
578
+ 'ỏ' => 'ỏ',
579
+ 'Ố' => 'Ố',
580
+ 'ố' => 'ố',
581
+ 'Ồ' => 'Ồ',
582
+ 'ồ' => 'ồ',
583
+ 'Ổ' => 'Ổ',
584
+ 'ổ' => 'ổ',
585
+ 'Ỗ' => 'Ỗ',
586
+ 'ỗ' => 'ỗ',
587
+ 'Ộ' => 'Ộ',
588
+ 'ộ' => 'ộ',
589
+ 'Ớ' => 'Ớ',
590
+ 'ớ' => 'ớ',
591
+ 'Ờ' => 'Ờ',
592
+ 'ờ' => 'ờ',
593
+ 'Ở' => 'Ở',
594
+ 'ở' => 'ở',
595
+ 'Ỡ' => 'Ỡ',
596
+ 'ỡ' => 'ỡ',
597
+ 'Ợ' => 'Ợ',
598
+ 'ợ' => 'ợ',
599
+ 'Ụ' => 'Ụ',
600
+ 'ụ' => 'ụ',
601
+ 'Ủ' => 'Ủ',
602
+ 'ủ' => 'ủ',
603
+ 'Ứ' => 'Ứ',
604
+ 'ứ' => 'ứ',
605
+ 'Ừ' => 'Ừ',
606
+ 'ừ' => 'ừ',
607
+ 'Ử' => 'Ử',
608
+ 'ử' => 'ử',
609
+ 'Ữ' => 'Ữ',
610
+ 'ữ' => 'ữ',
611
+ 'Ự' => 'Ự',
612
+ 'ự' => 'ự',
613
+ 'Ỳ' => 'Ỳ',
614
+ 'ỳ' => 'ỳ',
615
+ 'Ỵ' => 'Ỵ',
616
+ 'ỵ' => 'ỵ',
617
+ 'Ỷ' => 'Ỷ',
618
+ 'ỷ' => 'ỷ',
619
+ 'Ỹ' => 'Ỹ',
620
+ 'ỹ' => 'ỹ',
621
+ 'ἀ' => 'ἀ',
622
+ 'ἁ' => 'ἁ',
623
+ 'ἂ' => 'ἂ',
624
+ 'ἃ' => 'ἃ',
625
+ 'ἄ' => 'ἄ',
626
+ 'ἅ' => 'ἅ',
627
+ 'ἆ' => 'ἆ',
628
+ 'ἇ' => 'ἇ',
629
+ 'Ἀ' => 'Ἀ',
630
+ 'Ἁ' => 'Ἁ',
631
+ 'Ἂ' => 'Ἂ',
632
+ 'Ἃ' => 'Ἃ',
633
+ 'Ἄ' => 'Ἄ',
634
+ 'Ἅ' => 'Ἅ',
635
+ 'Ἆ' => 'Ἆ',
636
+ 'Ἇ' => 'Ἇ',
637
+ 'ἐ' => 'ἐ',
638
+ 'ἑ' => 'ἑ',
639
+ 'ἒ' => 'ἒ',
640
+ 'ἓ' => 'ἓ',
641
+ 'ἔ' => 'ἔ',
642
+ 'ἕ' => 'ἕ',
643
+ 'Ἐ' => 'Ἐ',
644
+ 'Ἑ' => 'Ἑ',
645
+ 'Ἒ' => 'Ἒ',
646
+ 'Ἓ' => 'Ἓ',
647
+ 'Ἔ' => 'Ἔ',
648
+ 'Ἕ' => 'Ἕ',
649
+ 'ἠ' => 'ἠ',
650
+ 'ἡ' => 'ἡ',
651
+ 'ἢ' => 'ἢ',
652
+ 'ἣ' => 'ἣ',
653
+ 'ἤ' => 'ἤ',
654
+ 'ἥ' => 'ἥ',
655
+ 'ἦ' => 'ἦ',
656
+ 'ἧ' => 'ἧ',
657
+ 'Ἠ' => 'Ἠ',
658
+ 'Ἡ' => 'Ἡ',
659
+ 'Ἢ' => 'Ἢ',
660
+ 'Ἣ' => 'Ἣ',
661
+ 'Ἤ' => 'Ἤ',
662
+ 'Ἥ' => 'Ἥ',
663
+ 'Ἦ' => 'Ἦ',
664
+ 'Ἧ' => 'Ἧ',
665
+ 'ἰ' => 'ἰ',
666
+ 'ἱ' => 'ἱ',
667
+ 'ἲ' => 'ἲ',
668
+ 'ἳ' => 'ἳ',
669
+ 'ἴ' => 'ἴ',
670
+ 'ἵ' => 'ἵ',
671
+ 'ἶ' => 'ἶ',
672
+ 'ἷ' => 'ἷ',
673
+ 'Ἰ' => 'Ἰ',
674
+ 'Ἱ' => 'Ἱ',
675
+ 'Ἲ' => 'Ἲ',
676
+ 'Ἳ' => 'Ἳ',
677
+ 'Ἴ' => 'Ἴ',
678
+ 'Ἵ' => 'Ἵ',
679
+ 'Ἶ' => 'Ἶ',
680
+ 'Ἷ' => 'Ἷ',
681
+ 'ὀ' => 'ὀ',
682
+ 'ὁ' => 'ὁ',
683
+ 'ὂ' => 'ὂ',
684
+ 'ὃ' => 'ὃ',
685
+ 'ὄ' => 'ὄ',
686
+ 'ὅ' => 'ὅ',
687
+ 'Ὀ' => 'Ὀ',
688
+ 'Ὁ' => 'Ὁ',
689
+ 'Ὂ' => 'Ὂ',
690
+ 'Ὃ' => 'Ὃ',
691
+ 'Ὄ' => 'Ὄ',
692
+ 'Ὅ' => 'Ὅ',
693
+ 'ὐ' => 'ὐ',
694
+ 'ὑ' => 'ὑ',
695
+ 'ὒ' => 'ὒ',
696
+ 'ὓ' => 'ὓ',
697
+ 'ὔ' => 'ὔ',
698
+ 'ὕ' => 'ὕ',
699
+ 'ὖ' => 'ὖ',
700
+ 'ὗ' => 'ὗ',
701
+ 'Ὑ' => 'Ὑ',
702
+ 'Ὓ' => 'Ὓ',
703
+ 'Ὕ' => 'Ὕ',
704
+ 'Ὗ' => 'Ὗ',
705
+ 'ὠ' => 'ὠ',
706
+ 'ὡ' => 'ὡ',
707
+ 'ὢ' => 'ὢ',
708
+ 'ὣ' => 'ὣ',
709
+ 'ὤ' => 'ὤ',
710
+ 'ὥ' => 'ὥ',
711
+ 'ὦ' => 'ὦ',
712
+ 'ὧ' => 'ὧ',
713
+ 'Ὠ' => 'Ὠ',
714
+ 'Ὡ' => 'Ὡ',
715
+ 'Ὢ' => 'Ὢ',
716
+ 'Ὣ' => 'Ὣ',
717
+ 'Ὤ' => 'Ὤ',
718
+ 'Ὥ' => 'Ὥ',
719
+ 'Ὦ' => 'Ὦ',
720
+ 'Ὧ' => 'Ὧ',
721
+ 'ὰ' => 'ὰ',
722
+ 'ὲ' => 'ὲ',
723
+ 'ὴ' => 'ὴ',
724
+ 'ὶ' => 'ὶ',
725
+ 'ὸ' => 'ὸ',
726
+ 'ὺ' => 'ὺ',
727
+ 'ὼ' => 'ὼ',
728
+ 'ᾀ' => 'ᾀ',
729
+ 'ᾁ' => 'ᾁ',
730
+ 'ᾂ' => 'ᾂ',
731
+ 'ᾃ' => 'ᾃ',
732
+ 'ᾄ' => 'ᾄ',
733
+ 'ᾅ' => 'ᾅ',
734
+ 'ᾆ' => 'ᾆ',
735
+ 'ᾇ' => 'ᾇ',
736
+ 'ᾈ' => 'ᾈ',
737
+ 'ᾉ' => 'ᾉ',
738
+ 'ᾊ' => 'ᾊ',
739
+ 'ᾋ' => 'ᾋ',
740
+ 'ᾌ' => 'ᾌ',
741
+ 'ᾍ' => 'ᾍ',
742
+ 'ᾎ' => 'ᾎ',
743
+ 'ᾏ' => 'ᾏ',
744
+ 'ᾐ' => 'ᾐ',
745
+ 'ᾑ' => 'ᾑ',
746
+ 'ᾒ' => 'ᾒ',
747
+ 'ᾓ' => 'ᾓ',
748
+ 'ᾔ' => 'ᾔ',
749
+ 'ᾕ' => 'ᾕ',
750
+ 'ᾖ' => 'ᾖ',
751
+ 'ᾗ' => 'ᾗ',
752
+ 'ᾘ' => 'ᾘ',
753
+ 'ᾙ' => 'ᾙ',
754
+ 'ᾚ' => 'ᾚ',
755
+ 'ᾛ' => 'ᾛ',
756
+ 'ᾜ' => 'ᾜ',
757
+ 'ᾝ' => 'ᾝ',
758
+ 'ᾞ' => 'ᾞ',
759
+ 'ᾟ' => 'ᾟ',
760
+ 'ᾠ' => 'ᾠ',
761
+ 'ᾡ' => 'ᾡ',
762
+ 'ᾢ' => 'ᾢ',
763
+ 'ᾣ' => 'ᾣ',
764
+ 'ᾤ' => 'ᾤ',
765
+ 'ᾥ' => 'ᾥ',
766
+ 'ᾦ' => 'ᾦ',
767
+ 'ᾧ' => 'ᾧ',
768
+ 'ᾨ' => 'ᾨ',
769
+ 'ᾩ' => 'ᾩ',
770
+ 'ᾪ' => 'ᾪ',
771
+ 'ᾫ' => 'ᾫ',
772
+ 'ᾬ' => 'ᾬ',
773
+ 'ᾭ' => 'ᾭ',
774
+ 'ᾮ' => 'ᾮ',
775
+ 'ᾯ' => 'ᾯ',
776
+ 'ᾰ' => 'ᾰ',
777
+ 'ᾱ' => 'ᾱ',
778
+ 'ᾲ' => 'ᾲ',
779
+ 'ᾳ' => 'ᾳ',
780
+ 'ᾴ' => 'ᾴ',
781
+ 'ᾶ' => 'ᾶ',
782
+ 'ᾷ' => 'ᾷ',
783
+ 'Ᾰ' => 'Ᾰ',
784
+ 'Ᾱ' => 'Ᾱ',
785
+ 'Ὰ' => 'Ὰ',
786
+ 'ᾼ' => 'ᾼ',
787
+ '῁' => '῁',
788
+ 'ῂ' => 'ῂ',
789
+ 'ῃ' => 'ῃ',
790
+ 'ῄ' => 'ῄ',
791
+ 'ῆ' => 'ῆ',
792
+ 'ῇ' => 'ῇ',
793
+ 'Ὲ' => 'Ὲ',
794
+ 'Ὴ' => 'Ὴ',
795
+ 'ῌ' => 'ῌ',
796
+ '῍' => '῍',
797
+ '῎' => '῎',
798
+ '῏' => '῏',
799
+ 'ῐ' => 'ῐ',
800
+ 'ῑ' => 'ῑ',
801
+ 'ῒ' => 'ῒ',
802
+ 'ῖ' => 'ῖ',
803
+ 'ῗ' => 'ῗ',
804
+ 'Ῐ' => 'Ῐ',
805
+ 'Ῑ' => 'Ῑ',
806
+ 'Ὶ' => 'Ὶ',
807
+ '῝' => '῝',
808
+ '῞' => '῞',
809
+ '῟' => '῟',
810
+ 'ῠ' => 'ῠ',
811
+ 'ῡ' => 'ῡ',
812
+ 'ῢ' => 'ῢ',
813
+ 'ῤ' => 'ῤ',
814
+ 'ῥ' => 'ῥ',
815
+ 'ῦ' => 'ῦ',
816
+ 'ῧ' => 'ῧ',
817
+ 'Ῠ' => 'Ῠ',
818
+ 'Ῡ' => 'Ῡ',
819
+ 'Ὺ' => 'Ὺ',
820
+ 'Ῥ' => 'Ῥ',
821
+ '῭' => '῭',
822
+ 'ῲ' => 'ῲ',
823
+ 'ῳ' => 'ῳ',
824
+ 'ῴ' => 'ῴ',
825
+ 'ῶ' => 'ῶ',
826
+ 'ῷ' => 'ῷ',
827
+ 'Ὸ' => 'Ὸ',
828
+ 'Ὼ' => 'Ὼ',
829
+ 'ῼ' => 'ῼ',
830
+ '↚' => '↚',
831
+ '↛' => '↛',
832
+ '↮' => '↮',
833
+ '⇍' => '⇍',
834
+ '⇎' => '⇎',
835
+ '⇏' => '⇏',
836
+ '∄' => '∄',
837
+ '∉' => '∉',
838
+ '∌' => '∌',
839
+ '∤' => '∤',
840
+ '∦' => '∦',
841
+ '≁' => '≁',
842
+ '≄' => '≄',
843
+ '≇' => '≇',
844
+ '≉' => '≉',
845
+ '≠' => '≠',
846
+ '≢' => '≢',
847
+ '≭' => '≭',
848
+ '≮' => '≮',
849
+ '≯' => '≯',
850
+ '≰' => '≰',
851
+ '≱' => '≱',
852
+ '≴' => '≴',
853
+ '≵' => '≵',
854
+ '≸' => '≸',
855
+ '≹' => '≹',
856
+ '⊀' => '⊀',
857
+ '⊁' => '⊁',
858
+ '⊄' => '⊄',
859
+ '⊅' => '⊅',
860
+ '⊈' => '⊈',
861
+ '⊉' => '⊉',
862
+ '⊬' => '⊬',
863
+ '⊭' => '⊭',
864
+ '⊮' => '⊮',
865
+ '⊯' => '⊯',
866
+ '⋠' => '⋠',
867
+ '⋡' => '⋡',
868
+ '⋢' => '⋢',
869
+ '⋣' => '⋣',
870
+ '⋪' => '⋪',
871
+ '⋫' => '⋫',
872
+ '⋬' => '⋬',
873
+ '⋭' => '⋭',
874
+ 'が' => 'が',
875
+ 'ぎ' => 'ぎ',
876
+ 'ぐ' => 'ぐ',
877
+ 'げ' => 'げ',
878
+ 'ご' => 'ご',
879
+ 'ざ' => 'ざ',
880
+ 'じ' => 'じ',
881
+ 'ず' => 'ず',
882
+ 'ぜ' => 'ぜ',
883
+ 'ぞ' => 'ぞ',
884
+ 'だ' => 'だ',
885
+ 'ぢ' => 'ぢ',
886
+ 'づ' => 'づ',
887
+ 'で' => 'で',
888
+ 'ど' => 'ど',
889
+ 'ば' => 'ば',
890
+ 'ぱ' => 'ぱ',
891
+ 'び' => 'び',
892
+ 'ぴ' => 'ぴ',
893
+ 'ぶ' => 'ぶ',
894
+ 'ぷ' => 'ぷ',
895
+ 'べ' => 'べ',
896
+ 'ぺ' => 'ぺ',
897
+ 'ぼ' => 'ぼ',
898
+ 'ぽ' => 'ぽ',
899
+ 'ゔ' => 'ゔ',
900
+ 'ゞ' => 'ゞ',
901
+ 'ガ' => 'ガ',
902
+ 'ギ' => 'ギ',
903
+ 'グ' => 'グ',
904
+ 'ゲ' => 'ゲ',
905
+ 'ゴ' => 'ゴ',
906
+ 'ザ' => 'ザ',
907
+ 'ジ' => 'ジ',
908
+ 'ズ' => 'ズ',
909
+ 'ゼ' => 'ゼ',
910
+ 'ゾ' => 'ゾ',
911
+ 'ダ' => 'ダ',
912
+ 'ヂ' => 'ヂ',
913
+ 'ヅ' => 'ヅ',
914
+ 'デ' => 'デ',
915
+ 'ド' => 'ド',
916
+ 'バ' => 'バ',
917
+ 'パ' => 'パ',
918
+ 'ビ' => 'ビ',
919
+ 'ピ' => 'ピ',
920
+ 'ブ' => 'ブ',
921
+ 'プ' => 'プ',
922
+ 'ベ' => 'ベ',
923
+ 'ペ' => 'ペ',
924
+ 'ボ' => 'ボ',
925
+ 'ポ' => 'ポ',
926
+ 'ヴ' => 'ヴ',
927
+ 'ヷ' => 'ヷ',
928
+ 'ヸ' => 'ヸ',
929
+ 'ヹ' => 'ヹ',
930
+ 'ヺ' => 'ヺ',
931
+ 'ヾ' => 'ヾ',
932
+ '𑂚' => '𑂚',
933
+ '𑂜' => '𑂜',
934
+ '𑂫' => '𑂫',
935
+ '𑄮' => '𑄮',
936
+ '𑄯' => '𑄯',
937
+ '𑍋' => '𑍋',
938
+ '𑍌' => '𑍌',
939
+ '𑒻' => '𑒻',
940
+ '𑒼' => '𑒼',
941
+ '𑒾' => '𑒾',
942
+ '𑖺' => '𑖺',
943
+ '𑖻' => '𑖻',
944
+ '𑤸' => '𑤸',
945
+ );
vendor/browscap-php/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalDecomposition.php ADDED
@@ -0,0 +1,2065 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ return array (
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
+ 'á' => 'á',
32
+ 'â' => 'â',
33
+ 'ã' => 'ã',
34
+ 'ä' => 'ä',
35
+ 'å' => 'å',
36
+ 'ç' => 'ç',
37
+ 'è' => 'è',
38
+ 'é' => 'é',
39
+ 'ê' => 'ê',
40
+ 'ë' => 'ë',
41
+ 'ì' => 'ì',
42
+ 'í' => 'í',
43
+ 'î' => 'î',
44
+ 'ï' => 'ï',
45
+ 'ñ' => 'ñ',
46
+ 'ò' => 'ò',
47
+ 'ó' => 'ó',
48
+ 'ô' => 'ô',
49
+ 'õ' => 'õ',
50
+ 'ö' => 'ö',
51
+ 'ù' => 'ù',
52
+ 'ú' => 'ú',
53
+ 'û' => 'û',
54
+ 'ü' => 'ü',
55
+ 'ý' => 'ý',
56
+ 'ÿ' => 'ÿ',
57
+ 'Ā' => 'Ā',
58
+ 'ā' => 'ā',
59
+ 'Ă' => 'Ă',
60
+ 'ă' => 'ă',
61
+ 'Ą' => 'Ą',
62
+ 'ą' => 'ą',
63
+ 'Ć' => 'Ć',
64
+ 'ć' => 'ć',
65
+ 'Ĉ' => 'Ĉ',
66
+ 'ĉ' => 'ĉ',
67
+ 'Ċ' => 'Ċ',
68
+ 'ċ' => 'ċ',
69
+ 'Č' => 'Č',
70
+ 'č' => 'č',
71
+ 'Ď' => 'Ď',
72
+ 'ď' => 'ď',
73
+ 'Ē' => 'Ē',
74
+ 'ē' => 'ē',
75
+ 'Ĕ' => 'Ĕ',
76
+ 'ĕ' => 'ĕ',
77
+ 'Ė' => 'Ė',
78
+ 'ė' => 'ė',
79
+ 'Ę' => 'Ę',
80
+ 'ę' => 'ę',
81
+ 'Ě' => 'Ě',
82
+ 'ě' => 'ě',
83
+ 'Ĝ' => 'Ĝ',
84
+ 'ĝ' => 'ĝ',
85
+ 'Ğ' => 'Ğ',
86
+ 'ğ' => 'ğ',
87
+ 'Ġ' => 'Ġ',
88
+ 'ġ' => 'ġ',
89
+ 'Ģ' => 'Ģ',
90
+ 'ģ' => 'ģ',
91
+ 'Ĥ' => 'Ĥ',
92
+ 'ĥ' => 'ĥ',
93
+ 'Ĩ' => 'Ĩ',
94
+ 'ĩ' => 'ĩ',
95
+ 'Ī' => 'Ī',
96
+ 'ī' => 'ī',
97
+ 'Ĭ' => 'Ĭ',
98
+ 'ĭ' => 'ĭ',
99
+ 'Į' => 'Į',
100
+ 'į' => 'į',
101
+ 'İ' => 'İ',
102
+ 'Ĵ' => 'Ĵ',
103
+ 'ĵ' => 'ĵ',
104
+ 'Ķ' => 'Ķ',
105
+ 'ķ' => 'ķ',
106
+ 'Ĺ' => 'Ĺ',
107
+ 'ĺ' => 'ĺ',
108
+ 'Ļ' => 'Ļ',
109
+ 'ļ' => 'ļ',
110
+ 'Ľ' => 'Ľ',
111
+ 'ľ' => 'ľ',
112
+ 'Ń' => 'Ń',
113
+ 'ń' => 'ń',
114
+ 'Ņ' => 'Ņ',
115
+ 'ņ' => 'ņ',
116
+ 'Ň' => 'Ň',
117
+ 'ň' => 'ň',
118
+ 'Ō' => 'Ō',
119
+ 'ō' => 'ō',
120
+ 'Ŏ' => 'Ŏ',
121
+ 'ŏ' => 'ŏ',
122
+ 'Ő' => 'Ő',
123
+ 'ő' => 'ő',
124
+ 'Ŕ' => 'Ŕ',
125
+ 'ŕ' => 'ŕ',
126
+ 'Ŗ' => 'Ŗ',
127
+ 'ŗ' => 'ŗ',
128
+ 'Ř' => 'Ř',
129
+ 'ř' => 'ř',
130
+ 'Ś' => 'Ś',
131
+ 'ś' => 'ś',
132
+ 'Ŝ' => 'Ŝ',
133
+ 'ŝ' => 'ŝ',
134
+ 'Ş' => 'Ş',
135
+ 'ş' => 'ş',
136
+ 'Š' => 'Š',
137
+ 'š' => 'š',
138
+ 'Ţ' => 'Ţ',
139
+ 'ţ' => 'ţ',
140
+ 'Ť' => 'Ť',
141
+ 'ť' => 'ť',
142
+ 'Ũ' => 'Ũ',
143
+ 'ũ' => 'ũ',
144
+ 'Ū' => 'Ū',
145
+ 'ū' => 'ū',
146
+ 'Ŭ' => 'Ŭ',
147
+ 'ŭ' => 'ŭ',
148
+ 'Ů' => 'Ů',
149
+ 'ů' => 'ů',
150
+ 'Ű' => 'Ű',
151
+ 'ű' => 'ű',
152
+ 'Ų' => 'Ų',
153
+ 'ų' => 'ų',
154
+ 'Ŵ' => 'Ŵ',
155
+ 'ŵ' => 'ŵ',
156
+ 'Ŷ' => 'Ŷ',
157
+ 'ŷ' => 'ŷ',
158
+ 'Ÿ' => 'Ÿ',
159
+ 'Ź' => 'Ź',
160
+ 'ź' => 'ź',
161
+ 'Ż' => 'Ż',
162
+ 'ż' => 'ż',
163
+ 'Ž' => 'Ž',
164
+ 'ž' => 'ž',
165
+ 'Ơ' => 'Ơ',
166
+ 'ơ' => 'ơ',
167
+ 'Ư' => 'Ư',
168
+ 'ư' => 'ư',
169
+ 'Ǎ' => 'Ǎ',
170
+ 'ǎ' => 'ǎ',
171
+ 'Ǐ' => 'Ǐ',
172
+ 'ǐ' => 'ǐ',
173
+ 'Ǒ' => 'Ǒ',
174
+ 'ǒ' => 'ǒ',
175
+ 'Ǔ' => 'Ǔ',
176
+ 'ǔ' => 'ǔ',
177
+ 'Ǖ' => 'Ǖ',
178
+ 'ǖ' => 'ǖ',
179
+ 'Ǘ' => 'Ǘ',
180
+ 'ǘ' => 'ǘ',
181
+ 'Ǚ' => 'Ǚ',
182
+ 'ǚ' => 'ǚ',
183
+ 'Ǜ' => 'Ǜ',
184
+ 'ǜ' => 'ǜ',
185
+ 'Ǟ' => 'Ǟ',
186
+ 'ǟ' => 'ǟ',
187
+ 'Ǡ' => 'Ǡ',
188
+ 'ǡ' => 'ǡ',
189
+ 'Ǣ' => 'Ǣ',
190
+ 'ǣ' => 'ǣ',
191
+ 'Ǧ' => 'Ǧ',
192
+ 'ǧ' => 'ǧ',
193
+ 'Ǩ' => 'Ǩ',
194
+ 'ǩ' => 'ǩ',
195
+ 'Ǫ' => 'Ǫ',
196
+ 'ǫ' => 'ǫ',
197
+ 'Ǭ' => 'Ǭ',
198
+ 'ǭ' => 'ǭ',
199
+ 'Ǯ' => 'Ǯ',
200
+ 'ǯ' => 'ǯ',
201
+ 'ǰ' => 'ǰ',
202
+ 'Ǵ' => 'Ǵ',
203
+ 'ǵ' => 'ǵ',
204
+ 'Ǹ' => 'Ǹ',
205
+ 'ǹ' => 'ǹ',
206
+ 'Ǻ' => 'Ǻ',
207
+ 'ǻ' => 'ǻ',
208
+ 'Ǽ' => 'Ǽ',
209
+ 'ǽ' => 'ǽ',
210
+ 'Ǿ' => 'Ǿ',
211
+ 'ǿ' => 'ǿ',
212
+ 'Ȁ' => 'Ȁ',
213
+ 'ȁ' => 'ȁ',
214
+ 'Ȃ' => 'Ȃ',
215
+ 'ȃ' => 'ȃ',
216
+ 'Ȅ' => 'Ȅ',
217
+ 'ȅ' => 'ȅ',
218
+ 'Ȇ' => 'Ȇ',
219
+ 'ȇ' => 'ȇ',
220
+ 'Ȉ' => 'Ȉ',
221
+ 'ȉ' => 'ȉ',
222
+ 'Ȋ' => 'Ȋ',
223
+ 'ȋ' => 'ȋ',
224
+ 'Ȍ' => 'Ȍ',
225
+ 'ȍ' => 'ȍ',
226
+ 'Ȏ' => 'Ȏ',
227
+ 'ȏ' => 'ȏ',
228
+ 'Ȑ' => 'Ȑ',
229
+ 'ȑ' => 'ȑ',
230
+ 'Ȓ' => 'Ȓ',
231
+ 'ȓ' => 'ȓ',
232
+ 'Ȕ' => 'Ȕ',
233
+ 'ȕ' => 'ȕ',
234
+ 'Ȗ' => 'Ȗ',
235
+ 'ȗ' => 'ȗ',
236
+ 'Ș' => 'Ș',
237
+ 'ș' => 'ș',
238
+ 'Ț' => 'Ț',
239
+ 'ț' => 'ț',
240
+ 'Ȟ' => 'Ȟ',
241
+ 'ȟ' => 'ȟ',
242
+ 'Ȧ' => 'Ȧ',
243
+ 'ȧ' => 'ȧ',
244
+ 'Ȩ' => 'Ȩ',
245
+ 'ȩ' => 'ȩ',
246
+ 'Ȫ' => 'Ȫ',
247
+ 'ȫ' => 'ȫ',
248
+ 'Ȭ' => 'Ȭ',
249
+ 'ȭ' => 'ȭ',
250
+ 'Ȯ' => 'Ȯ',
251
+ 'ȯ' => 'ȯ',
252
+ 'Ȱ' => 'Ȱ',
253
+ 'ȱ' => 'ȱ',
254
+ 'Ȳ' => 'Ȳ',
255
+ 'ȳ' => 'ȳ',
256
+ '̀' => '̀',
257
+ '́' => '́',
258
+ '̓' => '̓',
259
+ '̈́' => '̈́',
260
+ 'ʹ' => 'ʹ',
261
+ ';' => ';',
262
+ '΅' => '΅',
263
+ 'Ά' => 'Ά',
264
+ '·' => '·',
265
+ 'Έ' => 'Έ',
266
+ 'Ή' => 'Ή',
267
+ 'Ί' => 'Ί',
268
+ 'Ό' => 'Ό',
269
+ 'Ύ' => 'Ύ',
270
+ 'Ώ' => 'Ώ',
271
+ 'ΐ' => 'ΐ',
272
+ 'Ϊ' => 'Ϊ',
273
+ 'Ϋ' => 'Ϋ',
274
+ 'ά' => 'ά',
275
+ 'έ' => 'έ',
276
+ 'ή' => 'ή',
277
+ 'ί' => 'ί',
278
+ 'ΰ' => 'ΰ',
279
+ 'ϊ' => 'ϊ',
280
+ 'ϋ' => 'ϋ',
281
+ 'ό' => 'ό',
282
+ 'ύ' => 'ύ',
283
+ 'ώ' => 'ώ',
284
+ 'ϓ' => 'ϓ',
285
+ 'ϔ' => 'ϔ',
286
+ 'Ѐ' => 'Ѐ',
287
+ 'Ё' => 'Ё',
288
+ 'Ѓ' => 'Ѓ',
289
+ 'Ї' => 'Ї',
290
+ 'Ќ' => 'Ќ',
291
+ 'Ѝ' => 'Ѝ',
292
+ 'Ў' => 'Ў',
293
+ 'Й' => 'Й',
294
+ 'й' => 'й',
295
+ 'ѐ' => 'ѐ',
296
+ 'ё' => 'ё',
297
+ 'ѓ' => 'ѓ',
298
+ 'ї' => 'ї',
299
+ 'ќ' => 'ќ',
300
+ 'ѝ' => 'ѝ',
301
+ 'ў' => 'ў',
302
+ 'Ѷ' => 'Ѷ',
303
+ 'ѷ' => 'ѷ',
304
+ 'Ӂ' => 'Ӂ',
305
+ 'ӂ' => 'ӂ',
306
+ 'Ӑ' => 'Ӑ',
307
+ 'ӑ' => 'ӑ',
308
+ 'Ӓ' => 'Ӓ',
309
+ 'ӓ' => 'ӓ',
310
+ 'Ӗ' => 'Ӗ',
311
+ 'ӗ' => 'ӗ',
312
+ 'Ӛ' => 'Ӛ',
313
+ 'ӛ' => 'ӛ',
314
+ 'Ӝ' => 'Ӝ',
315
+ 'ӝ' => 'ӝ',
316
+ 'Ӟ' => 'Ӟ',
317
+ 'ӟ' => 'ӟ',
318
+ 'Ӣ' => 'Ӣ',
319
+ 'ӣ' => 'ӣ',
320
+ 'Ӥ' => 'Ӥ',
321
+ 'ӥ' => 'ӥ',
322
+ 'Ӧ' => 'Ӧ',
323
+ 'ӧ' => 'ӧ',
324
+ 'Ӫ' => 'Ӫ',
325
+ 'ӫ' => 'ӫ',
326
+ 'Ӭ' => 'Ӭ',
327
+ 'ӭ' => 'ӭ',
328
+ 'Ӯ' => 'Ӯ',
329
+ 'ӯ' => 'ӯ',
330
+ 'Ӱ' => 'Ӱ',
331
+ 'ӱ' => 'ӱ',
332
+ 'Ӳ' => 'Ӳ',
333
+ 'ӳ' => 'ӳ',
334
+ 'Ӵ' => 'Ӵ',
335
+ 'ӵ' => 'ӵ',
336
+ 'Ӹ' => 'Ӹ',
337
+ 'ӹ' => 'ӹ',
338
+ 'آ' => 'آ',
339
+ 'أ' => 'أ',
340
+ 'ؤ' => 'ؤ',
341
+ 'إ' => 'إ',
342
+ 'ئ' => 'ئ',
343
+ 'ۀ' => 'ۀ',
344
+ 'ۂ' => 'ۂ',
345
+ 'ۓ' => 'ۓ',
346
+ 'ऩ' => 'ऩ',
347
+ 'ऱ' => 'ऱ',
348
+ 'ऴ' => 'ऴ',
349
+ 'क़' => 'क़',
350
+ 'ख़' => 'ख़',
351
+ 'ग़' => 'ग़',
352
+ 'ज़' => 'ज़',
353
+ 'ड़' => 'ड़',
354
+ 'ढ़' => 'ढ़',
355
+ 'फ़' => 'फ़',
356
+ 'य़' => 'य़',
357
+ 'ো' => 'ো',
358
+ 'ৌ' => 'ৌ',
359
+ 'ড়' => 'ড়',
360
+ 'ঢ়' => 'ঢ়',
361
+ 'য়' => 'য়',
362
+ 'ਲ਼' => 'ਲ਼',
363
+ 'ਸ਼' => 'ਸ਼',
364
+ 'ਖ਼' => 'ਖ਼',
365
+ 'ਗ਼' => 'ਗ਼',
366
+ 'ਜ਼' => 'ਜ਼',
367
+ 'ਫ਼' => 'ਫ਼',
368
+ 'ୈ' => 'ୈ',
369
+ 'ୋ' => 'ୋ',
370
+ 'ୌ' => 'ୌ',
371
+ 'ଡ଼' => 'ଡ଼',
372
+ 'ଢ଼' => 'ଢ଼',
373
+ 'ஔ' => 'ஔ',
374
+ 'ொ' => 'ொ',
375
+ 'ோ' => 'ோ',
376
+ 'ௌ' => 'ௌ',
377
+ 'ై' => 'ై',
378
+ 'ೀ' => 'ೀ',
379
+ 'ೇ' => 'ೇ',
380
+ 'ೈ' => 'ೈ',
381
+ 'ೊ' => 'ೊ',
382
+ 'ೋ' => 'ೋ',
383
+ 'ൊ' => 'ൊ',
384
+ 'ോ' => 'ോ',
385
+ 'ൌ' => 'ൌ',
386
+ 'ේ' => 'ේ',
387
+ 'ො' => 'ො',
388
+ 'ෝ' => 'ෝ',
389
+ 'ෞ' => 'ෞ',
390
+ 'གྷ' => 'གྷ',
391
+ 'ཌྷ' => 'ཌྷ',
392
+ 'དྷ' => 'དྷ',
393
+ 'བྷ' => 'བྷ',
394
+ 'ཛྷ' => 'ཛྷ',
395
+ 'ཀྵ' => 'ཀྵ',
396
+ 'ཱི' => 'ཱི',
397
+ 'ཱུ' => 'ཱུ',
398
+ 'ྲྀ' => 'ྲྀ',
399
+ 'ླྀ' => 'ླྀ',
400
+ 'ཱྀ' => 'ཱྀ',
401
+ 'ྒྷ' => 'ྒྷ',
402
+ 'ྜྷ' => 'ྜྷ',
403
+ 'ྡྷ' => 'ྡྷ',
404
+ 'ྦྷ' => 'ྦྷ',
405
+ 'ྫྷ' => 'ྫྷ',
406
+ 'ྐྵ' => 'ྐྵ',
407
+ 'ဦ' => 'ဦ',
408
+ 'ᬆ' => 'ᬆ',
409
+ 'ᬈ' => 'ᬈ',
410
+ 'ᬊ' => 'ᬊ',
411
+ 'ᬌ' => 'ᬌ',
412
+ 'ᬎ' => 'ᬎ',
413
+ 'ᬒ' => 'ᬒ',
414
+ 'ᬻ' => 'ᬻ',
415
+ 'ᬽ' => 'ᬽ',
416
+ 'ᭀ' => 'ᭀ',
417
+ 'ᭁ' => 'ᭁ',
418
+ 'ᭃ' => 'ᭃ',
419
+ 'Ḁ' => 'Ḁ',
420
+ 'ḁ' => 'ḁ',
421
+ 'Ḃ' => 'Ḃ',
422
+ 'ḃ' => 'ḃ',
423
+ 'Ḅ' => 'Ḅ',
424
+ 'ḅ' => 'ḅ',
425
+ 'Ḇ' => 'Ḇ',
426
+ 'ḇ' => 'ḇ',
427
+ 'Ḉ' => 'Ḉ',
428
+ 'ḉ' => 'ḉ',
429
+ 'Ḋ' => 'Ḋ',
430
+ 'ḋ' => 'ḋ',
431
+ 'Ḍ' => 'Ḍ',
432
+ 'ḍ' => 'ḍ',
433
+ 'Ḏ' => 'Ḏ',
434
+ 'ḏ' => 'ḏ',
435
+ 'Ḑ' => 'Ḑ',
436
+ 'ḑ' => 'ḑ',
437
+ 'Ḓ' => 'Ḓ',
438
+ 'ḓ' => 'ḓ',
439
+ 'Ḕ' => 'Ḕ',
440
+ 'ḕ' => 'ḕ',
441
+ 'Ḗ' => 'Ḗ',
442
+ 'ḗ' => 'ḗ',
443
+ 'Ḙ' => 'Ḙ',
444
+ 'ḙ' => 'ḙ',
445
+ 'Ḛ' => 'Ḛ',
446
+ 'ḛ' => 'ḛ',
447
+ 'Ḝ' => 'Ḝ',
448
+ 'ḝ' => 'ḝ',
449
+ 'Ḟ' => 'Ḟ',
450
+ 'ḟ' => 'ḟ',
451
+ 'Ḡ' => 'Ḡ',
452
+ 'ḡ' => 'ḡ',
453
+ 'Ḣ' => 'Ḣ',
454
+ 'ḣ' => 'ḣ',
455
+ 'Ḥ' => 'Ḥ',
456
+ 'ḥ' => 'ḥ',
457
+ 'Ḧ' => 'Ḧ',
458
+ 'ḧ' => 'ḧ',
459
+ 'Ḩ' => 'Ḩ',
460
+ 'ḩ' => 'ḩ',
461
+ 'Ḫ' => 'Ḫ',
462
+ 'ḫ' => 'ḫ',
463
+ 'Ḭ' => 'Ḭ',
464
+ 'ḭ' => 'ḭ',
465
+ 'Ḯ' => 'Ḯ',
466
+ 'ḯ' => 'ḯ',
467
+ 'Ḱ' => 'Ḱ',
468
+ 'ḱ' => 'ḱ',
469
+ 'Ḳ' => 'Ḳ',
470
+ 'ḳ' => 'ḳ',
471
+ 'Ḵ' => 'Ḵ',
472
+ 'ḵ' => 'ḵ',
473
+ 'Ḷ' => 'Ḷ',
474
+ 'ḷ' => 'ḷ',
475
+ 'Ḹ' => 'Ḹ',
476
+ 'ḹ' => 'ḹ',
477
+ 'Ḻ' => 'Ḻ',
478
+ 'ḻ' => 'ḻ',
479
+ 'Ḽ' => 'Ḽ',
480
+ 'ḽ' => 'ḽ',
481
+ 'Ḿ' => 'Ḿ',
482
+ 'ḿ' => 'ḿ',
483
+ 'Ṁ' => 'Ṁ',
484
+ 'ṁ' => 'ṁ',
485
+ 'Ṃ' => 'Ṃ',
486
+ 'ṃ' => 'ṃ',
487
+ 'Ṅ' => 'Ṅ',
488
+ 'ṅ' => 'ṅ',
489
+ 'Ṇ' => 'Ṇ',
490
+ 'ṇ' => 'ṇ',
491
+ 'Ṉ' => 'Ṉ',
492
+ 'ṉ' => 'ṉ',
493
+ 'Ṋ' => 'Ṋ',
494
+ 'ṋ' => 'ṋ',
495
+ 'Ṍ' => 'Ṍ',
496
+ 'ṍ' => 'ṍ',
497
+ 'Ṏ' => 'Ṏ',
498
+ 'ṏ' => 'ṏ',
499
+ 'Ṑ' => 'Ṑ',
500
+ 'ṑ' => 'ṑ',
501
+ 'Ṓ' => 'Ṓ',
502
+ 'ṓ' => 'ṓ',
503
+ 'Ṕ' => 'Ṕ',
504
+ 'ṕ' => 'ṕ',
505
+ 'Ṗ' => 'Ṗ',
506
+ 'ṗ' => 'ṗ',
507
+ 'Ṙ' => 'Ṙ',
508
+ 'ṙ' => 'ṙ',
509
+ 'Ṛ' => 'Ṛ',
510
+ 'ṛ' => 'ṛ',
511
+ 'Ṝ' => 'Ṝ',
512
+ 'ṝ' => 'ṝ',
513
+ 'Ṟ' => 'Ṟ',
514
+ 'ṟ' => 'ṟ',
515
+ 'Ṡ' => 'Ṡ',
516
+ 'ṡ' => 'ṡ',
517
+ 'Ṣ' => 'Ṣ',
518
+ 'ṣ' => 'ṣ',
519
+ 'Ṥ' => 'Ṥ',
520
+ 'ṥ' => 'ṥ',
521
+ 'Ṧ' => 'Ṧ',
522
+ 'ṧ' => 'ṧ',
523
+ 'Ṩ' => 'Ṩ',
524
+ 'ṩ' => 'ṩ',
525
+ 'Ṫ' => 'Ṫ',
526
+ 'ṫ' => 'ṫ',
527
+ 'Ṭ' => 'Ṭ',
528
+ 'ṭ' => 'ṭ',
529
+ 'Ṯ' => 'Ṯ',
530
+ 'ṯ' => 'ṯ',
531
+ 'Ṱ' => 'Ṱ',
532
+ 'ṱ' => 'ṱ',
533
+ 'Ṳ' => 'Ṳ',
534
+ 'ṳ' => 'ṳ',
535
+ 'Ṵ' => 'Ṵ',
536
+ 'ṵ' => 'ṵ',
537
+ 'Ṷ' => 'Ṷ',
538
+ 'ṷ' => 'ṷ',
539
+ 'Ṹ' => 'Ṹ',
540
+ 'ṹ' => 'ṹ',
541
+ 'Ṻ' => 'Ṻ',
542
+ 'ṻ' => 'ṻ',
543
+ 'Ṽ' => 'Ṽ',
544
+ 'ṽ' => 'ṽ',
545
+ 'Ṿ' => 'Ṿ',
546
+ 'ṿ' => 'ṿ',
547
+ 'Ẁ' => 'Ẁ',
548
+ 'ẁ' => 'ẁ',
549
+ 'Ẃ' => 'Ẃ',
550
+ 'ẃ' => 'ẃ',
551
+ 'Ẅ' => 'Ẅ',
552
+ 'ẅ' => 'ẅ',
553
+ 'Ẇ' => 'Ẇ',
554
+ 'ẇ' => 'ẇ',
555
+ 'Ẉ' => 'Ẉ',
556
+ 'ẉ' => 'ẉ',
557
+ 'Ẋ' => 'Ẋ',
558
+ 'ẋ' => 'ẋ',
559
+ 'Ẍ' => 'Ẍ',
560
+ 'ẍ' => 'ẍ',
561
+ 'Ẏ' => 'Ẏ',
562
+ 'ẏ' => 'ẏ',
563
+ 'Ẑ' => 'Ẑ',
564
+ 'ẑ' => 'ẑ',
565
+ 'Ẓ' => 'Ẓ',
566
+ 'ẓ' => 'ẓ',
567
+ 'Ẕ' => 'Ẕ',
568
+ 'ẕ' => 'ẕ',
569
+ 'ẖ' => 'ẖ',
570
+ 'ẗ' => 'ẗ',
571
+ 'ẘ' => 'ẘ',
572
+ 'ẙ' => 'ẙ',
573
+ 'ẛ' => 'ẛ',
574
+ 'Ạ' => 'Ạ',
575
+ 'ạ' => 'ạ',
576
+ 'Ả' => 'Ả',
577
+ 'ả' => 'ả',
578
+ 'Ấ' => 'Ấ',
579
+ 'ấ' => 'ấ',
580
+ 'Ầ' => 'Ầ',
581
+ 'ầ' => 'ầ',
582
+ 'Ẩ' => 'Ẩ',
583
+ 'ẩ' => 'ẩ',
584
+ 'Ẫ' => 'Ẫ',
585
+ 'ẫ' => 'ẫ',
586
+ 'Ậ' => 'Ậ',
587
+ 'ậ' => 'ậ',
588
+ 'Ắ' => 'Ắ',
589
+ 'ắ' => 'ắ',
590
+ 'Ằ' => 'Ằ',
591
+ 'ằ' => 'ằ',
592
+ 'Ẳ' => 'Ẳ',
593
+ 'ẳ' => 'ẳ',
594
+ 'Ẵ' => 'Ẵ',
595
+ 'ẵ' => 'ẵ',
596
+ 'Ặ' => 'Ặ',
597
+ 'ặ' => 'ặ',
598
+ 'Ẹ' => 'Ẹ',
599
+ 'ẹ' => 'ẹ',
600
+ 'Ẻ' => 'Ẻ',
601
+ 'ẻ' => 'ẻ',
602
+ 'Ẽ' => 'Ẽ',
603
+ 'ẽ' => 'ẽ',
604
+ 'Ế' => 'Ế',
605
+ 'ế' => 'ế',
606
+ 'Ề' => 'Ề',
607
+ 'ề' => 'ề',
608
+ 'Ể' => 'Ể',
609
+ 'ể' => 'ể',
610
+ 'Ễ' => 'Ễ',
611
+ 'ễ' => 'ễ',
612
+ 'Ệ' => 'Ệ',
613
+ 'ệ' => 'ệ',
614
+ 'Ỉ' => 'Ỉ',
615
+ 'ỉ' => 'ỉ',
616
+ 'Ị' => 'Ị',
617
+ 'ị' => 'ị',
618
+ 'Ọ' => 'Ọ',
619
+ 'ọ' => 'ọ',
620
+ 'Ỏ' => 'Ỏ',
621
+ 'ỏ' => 'ỏ',
622
+ 'Ố' => 'Ố',
623
+ 'ố' => 'ố',
624
+ 'Ồ' => 'Ồ',
625
+ 'ồ' => 'ồ',
626
+ 'Ổ' => 'Ổ',
627
+ 'ổ' => 'ổ',
628
+ 'Ỗ' => 'Ỗ',
629
+ 'ỗ' => 'ỗ',
630
+ 'Ộ' => 'Ộ',
631
+ 'ộ' => 'ộ',
632
+ 'Ớ' => 'Ớ',
633
+ 'ớ' => 'ớ',
634
+ 'Ờ' => 'Ờ',
635
+ 'ờ' => 'ờ',
636
+ 'Ở' => 'Ở',
637
+ 'ở' => 'ở',
638
+ 'Ỡ' => 'Ỡ',
639
+ 'ỡ' => 'ỡ',
640
+ 'Ợ' => 'Ợ',
641
+ 'ợ' => 'ợ',
642
+ 'Ụ' => 'Ụ',
643
+ 'ụ' => 'ụ',
644
+ 'Ủ' => 'Ủ',
645
+ 'ủ' => 'ủ',
646
+ 'Ứ' => 'Ứ',
647
+ 'ứ' => 'ứ',
648
+ 'Ừ' => 'Ừ',
649
+ 'ừ' => 'ừ',
650
+ 'Ử' => 'Ử',
651
+ 'ử' => 'ử',
652
+ 'Ữ' => 'Ữ',
653
+ 'ữ' => 'ữ',
654
+ 'Ự' => 'Ự',
655
+ 'ự' => 'ự',
656
+ 'Ỳ' => 'Ỳ',
657
+ 'ỳ' => 'ỳ',
658
+ 'Ỵ' => 'Ỵ',
659
+ 'ỵ' => 'ỵ',
660
+ 'Ỷ' => 'Ỷ',
661
+ 'ỷ' => 'ỷ',
662
+ 'Ỹ' => 'Ỹ',
663
+ 'ỹ' => 'ỹ',
664
+ 'ἀ' => 'ἀ',
665
+ 'ἁ' => 'ἁ',
666
+ 'ἂ' => 'ἂ',
667
+ 'ἃ' => 'ἃ',
668
+ 'ἄ' => 'ἄ',
669
+ 'ἅ' => 'ἅ',
670
+ 'ἆ' => 'ἆ',
671
+ 'ἇ' => 'ἇ',
672
+ 'Ἀ' => 'Ἀ',
673
+ 'Ἁ' => 'Ἁ',
674
+ 'Ἂ' => 'Ἂ',
675
+ 'Ἃ' => 'Ἃ',
676
+ 'Ἄ' => 'Ἄ',
677
+ 'Ἅ' => 'Ἅ',
678
+ 'Ἆ' => 'Ἆ',
679
+ 'Ἇ' => 'Ἇ',
680
+ 'ἐ' => 'ἐ',
681
+ 'ἑ' => 'ἑ',
682
+ 'ἒ' => 'ἒ',
683
+ 'ἓ' => 'ἓ',
684
+ 'ἔ' => 'ἔ',
685
+ 'ἕ' => 'ἕ',
686
+ 'Ἐ' => 'Ἐ',
687
+ 'Ἑ' => 'Ἑ',
688
+ 'Ἒ' => 'Ἒ',
689
+ 'Ἓ' => 'Ἓ',
690
+ 'Ἔ' => 'Ἔ',
691
+ 'Ἕ' => 'Ἕ',
692
+ 'ἠ' => 'ἠ',
693
+ 'ἡ' => 'ἡ',
694
+ 'ἢ' => 'ἢ',
695
+ 'ἣ' => 'ἣ',
696
+ 'ἤ' => 'ἤ',
697
+ 'ἥ' => 'ἥ',
698
+ 'ἦ' => 'ἦ',
699
+ 'ἧ' => 'ἧ',
700
+ 'Ἠ' => 'Ἠ',
701
+ 'Ἡ' => 'Ἡ',
702
+ 'Ἢ' => 'Ἢ',
703
+ 'Ἣ' => 'Ἣ',
704
+ 'Ἤ' => 'Ἤ',
705
+ 'Ἥ' => 'Ἥ',
706
+ 'Ἦ' => 'Ἦ',
707
+ 'Ἧ' => 'Ἧ',
708
+ 'ἰ' => 'ἰ',
709
+ 'ἱ' => 'ἱ',
710
+ 'ἲ' => 'ἲ',
711
+ 'ἳ' => 'ἳ',
712
+ 'ἴ' => 'ἴ',
713
+ 'ἵ' => 'ἵ',
714
+ 'ἶ' => 'ἶ',
715
+ 'ἷ' => 'ἷ',
716
+ 'Ἰ' => 'Ἰ',
717
+ 'Ἱ' => 'Ἱ',
718
+ 'Ἲ' => 'Ἲ',
719
+ 'Ἳ' => 'Ἳ',
720
+ 'Ἴ' => 'Ἴ',
721
+ 'Ἵ' => 'Ἵ',
722
+ 'Ἶ' => 'Ἶ',
723
+ 'Ἷ' => 'Ἷ',
724
+ 'ὀ' => 'ὀ',
725
+ 'ὁ' => 'ὁ',
726
+ 'ὂ' => 'ὂ',
727
+ 'ὃ' => 'ὃ',
728
+ 'ὄ' => 'ὄ',
729
+ 'ὅ' => 'ὅ',
730
+ 'Ὀ' => 'Ὀ',
731
+ 'Ὁ' => 'Ὁ',
732
+ 'Ὂ' => 'Ὂ',
733
+ 'Ὃ' => 'Ὃ',
734
+ 'Ὄ' => 'Ὄ',
735
+ 'Ὅ' => 'Ὅ',
736
+ 'ὐ' => 'ὐ',
737
+ 'ὑ' => 'ὑ',
738
+ 'ὒ' => 'ὒ',
739
+ 'ὓ' => 'ὓ',
740
+ 'ὔ' => 'ὔ',
741
+ 'ὕ' => 'ὕ',
742
+ 'ὖ' => 'ὖ',
743
+ 'ὗ' => 'ὗ',
744
+ 'Ὑ' => 'Ὑ',
745
+ 'Ὓ' => 'Ὓ',
746
+ 'Ὕ' => 'Ὕ',
747
+ 'Ὗ' => 'Ὗ',
748
+ 'ὠ' => 'ὠ',
749
+ 'ὡ' => 'ὡ',
750
+ 'ὢ' => 'ὢ',
751
+ 'ὣ' => 'ὣ',
752
+ 'ὤ' => 'ὤ',
753
+ 'ὥ' => 'ὥ',
754
+ 'ὦ' => 'ὦ',
755
+ 'ὧ' => 'ὧ',
756
+ 'Ὠ' => 'Ὠ',
757
+ 'Ὡ' => 'Ὡ',
758
+ 'Ὢ' => 'Ὢ',
759
+ 'Ὣ' => 'Ὣ',
760
+ 'Ὤ' => 'Ὤ',
761
+ 'Ὥ' => 'Ὥ',
762
+ 'Ὦ' => 'Ὦ',
763
+ 'Ὧ' => 'Ὧ',
764
+ 'ὰ' => 'ὰ',
765
+ 'ά' => 'ά',
766
+ 'ὲ' => 'ὲ',
767
+ 'έ' => 'έ',
768
+ 'ὴ' => 'ὴ',
769
+ 'ή' => 'ή',
770
+ 'ὶ' => 'ὶ',
771
+ 'ί' => 'ί',
772
+ 'ὸ' => 'ὸ',
773
+ 'ό' => 'ό',
774
+ 'ὺ' => 'ὺ',
775
+ 'ύ' => 'ύ',
776
+ 'ὼ' => 'ὼ',
777
+ 'ώ' => 'ώ',
778
+ 'ᾀ' => 'ᾀ',
779
+ 'ᾁ' => 'ᾁ',
780
+ 'ᾂ' => 'ᾂ',
781
+ 'ᾃ' => 'ᾃ',
782
+ 'ᾄ' => 'ᾄ',
783
+ 'ᾅ' => 'ᾅ',
784
+ 'ᾆ' => 'ᾆ',
785
+ 'ᾇ' => 'ᾇ',
786
+ 'ᾈ' => 'ᾈ',
787
+ 'ᾉ' => 'ᾉ',
788
+ 'ᾊ' => 'ᾊ',
789
+ 'ᾋ' => 'ᾋ',
790
+ 'ᾌ' => 'ᾌ',
791
+ 'ᾍ' => 'ᾍ',
792
+ 'ᾎ' => 'ᾎ',
793
+ 'ᾏ' => 'ᾏ',
794
+ 'ᾐ' => 'ᾐ',
795
+ 'ᾑ' => 'ᾑ',
796
+ 'ᾒ' => 'ᾒ',
797
+ 'ᾓ' => 'ᾓ',
798
+ 'ᾔ' => 'ᾔ',
799
+ 'ᾕ' => 'ᾕ',
800
+ 'ᾖ' => 'ᾖ',
801
+ 'ᾗ' => 'ᾗ',
802
+ 'ᾘ' => 'ᾘ',
803
+ 'ᾙ' => 'ᾙ',
804
+ 'ᾚ' => 'ᾚ',
805
+ 'ᾛ' => 'ᾛ',
806
+ 'ᾜ' => 'ᾜ',
807
+ 'ᾝ' => 'ᾝ',
808
+ 'ᾞ' => 'ᾞ',
809
+ 'ᾟ' => 'ᾟ',
810
+ 'ᾠ' => 'ᾠ',
811
+ 'ᾡ' => 'ᾡ',
812
+ 'ᾢ' => 'ᾢ',
813
+ 'ᾣ' => 'ᾣ',
814
+ 'ᾤ' => 'ᾤ',
815
+ 'ᾥ' => 'ᾥ',
816
+ 'ᾦ' => 'ᾦ',
817
+ 'ᾧ' => 'ᾧ',
818
+ 'ᾨ' => 'ᾨ',
819
+ 'ᾩ' => 'ᾩ',
820
+ 'ᾪ' => 'ᾪ',
821
+ 'ᾫ' => 'ᾫ',
822
+ 'ᾬ' => 'ᾬ',
823
+ 'ᾭ' => 'ᾭ',
824
+ 'ᾮ' => 'ᾮ',
825
+ 'ᾯ' => 'ᾯ',
826
+ 'ᾰ' => 'ᾰ',
827
+ 'ᾱ' => 'ᾱ',
828
+ 'ᾲ' => 'ᾲ',
829
+ 'ᾳ' => 'ᾳ',
830
+ 'ᾴ' => 'ᾴ',
831
+ 'ᾶ' => 'ᾶ',
832
+ 'ᾷ' => 'ᾷ',
833
+ 'Ᾰ' => 'Ᾰ',
834
+ 'Ᾱ' => 'Ᾱ',
835
+ 'Ὰ' => 'Ὰ',
836
+ 'Ά' => 'Ά',
837
+ 'ᾼ' => 'ᾼ',
838
+ 'ι' => 'ι',
839
+ '῁' => '῁',
840
+ 'ῂ' => 'ῂ',
841
+ 'ῃ' => 'ῃ',
842
+ 'ῄ' => 'ῄ',
843
+ 'ῆ' => 'ῆ',
844
+ 'ῇ' => 'ῇ',
845
+ 'Ὲ' => 'Ὲ',
846
+ 'Έ' => 'Έ',
847
+ 'Ὴ' => 'Ὴ',
848
+ 'Ή' => 'Ή',
849
+ 'ῌ' => 'ῌ',
850
+ '῍' => '῍',
851
+ '῎' => '῎',
852
+ '῏' => '῏',
853
+ 'ῐ' => 'ῐ',
854
+ 'ῑ' => 'ῑ',
855
+ 'ῒ' => 'ῒ',
856
+ 'ΐ' => 'ΐ',
857
+ 'ῖ' => 'ῖ',
858
+ 'ῗ' => 'ῗ',
859
+ 'Ῐ' => 'Ῐ',
860
+ 'Ῑ' => 'Ῑ',
861
+ 'Ὶ' => 'Ὶ',
862
+ 'Ί' => 'Ί',
863
+ '῝' => '῝',
864
+ '῞' => '῞',
865
+ '῟' => '῟',
866
+ 'ῠ' => 'ῠ',
867
+ 'ῡ' => 'ῡ',
868
+ 'ῢ' => 'ῢ',
869
+ 'ΰ' => 'ΰ',
870
+ 'ῤ' => 'ῤ',
871
+ 'ῥ' => 'ῥ',
872
+ 'ῦ' => 'ῦ',
873
+ 'ῧ' => 'ῧ',
874
+ 'Ῠ' => 'Ῠ',
875
+ 'Ῡ' => 'Ῡ',
876
+ 'Ὺ' => 'Ὺ',
877
+ 'Ύ' => 'Ύ',
878
+ 'Ῥ' => 'Ῥ',
879
+ '῭' => '῭',
880
+ '΅' => '΅',
881
+ '`' => '`',
882
+ 'ῲ' => 'ῲ',
883
+ 'ῳ' => 'ῳ',
884
+ 'ῴ' => 'ῴ',
885
+ 'ῶ' => 'ῶ',
886
+ 'ῷ' => 'ῷ',
887
+ 'Ὸ' => 'Ὸ',
888
+ 'Ό' => 'Ό',
889
+ 'Ὼ' => 'Ὼ',
890
+ 'Ώ' => 'Ώ',
891
+ 'ῼ' => 'ῼ',
892
+ '´' => '´',
893
+ ' ' => ' ',
894
+ ' ' => ' ',
895
+ 'Ω' => 'Ω',
896
+ 'K' => 'K',
897
+ 'Å' => 'Å',
898
+ '↚' => '↚',
899
+ '↛' => '↛',
900
+ '↮' => '↮',
901
+ '⇍' => '⇍',
902
+ '⇎' => '⇎',
903
+ '⇏' => '⇏',
904
+ '∄' => '∄',
905
+ '∉' => '∉',
906
+ '∌' => '∌',
907
+ '∤' => '∤',
908
+ '∦' => '∦',
909
+ '≁' => '≁',
910
+ '≄' => '≄',
911
+ '≇' => '≇',
912
+ '≉' => '≉',
913
+ '≠' => '≠',
914
+ '≢' => '≢',
915
+ '≭' => '≭',
916
+ '≮' => '≮',
917
+ '≯' => '≯',
918
+ '≰' => '≰',
919
+ '≱' => '≱',
920
+ '≴' => '≴',
921
+ '≵' => '≵',
922
+ '≸' => '≸',
923
+ '≹' => '≹',
924
+ '⊀' => '⊀',
925
+ '⊁' => '⊁',
926
+ '⊄' => '⊄',
927
+ '⊅' => '⊅',
928
+ '⊈' => '⊈',
929
+ '⊉' => '⊉',
930
+ '⊬' => '⊬',
931
+ '⊭' => '⊭',
932
+ '⊮' => '⊮',
933
+ '⊯' => '⊯',
934
+ '⋠' => '⋠',
935
+ '⋡' => '⋡',
936
+ '⋢' => '⋢',
937
+ '⋣' => '⋣',
938
+ '⋪' => '⋪',
939
+ '⋫' => '⋫',
940
+ '⋬' => '⋬',
941
+ '⋭' => '⋭',
942
+ '〈' => '〈',
943
+ '〉' => '〉',
944
+ '⫝̸' => '⫝̸',
945
+ 'が' => 'が',
946
+ 'ぎ' => 'ぎ',
947
+ 'ぐ' => 'ぐ',
948
+ 'げ' => 'げ',
949
+ 'ご' => 'ご',
950
+ 'ざ' => 'ざ',
951
+ 'じ' => 'じ',
952
+ 'ず' => 'ず',
953
+ 'ぜ' => 'ぜ',
954
+ 'ぞ' => 'ぞ',
955
+ 'だ' => 'だ',
956
+ 'ぢ' => 'ぢ',
957
+ 'づ' => 'づ',
958
+ 'で' => 'で',
959
+ 'ど' => 'ど',
960
+ 'ば' => 'ば',
961
+ 'ぱ' => 'ぱ',
962
+ 'び' => 'び',
963
+ 'ぴ' => 'ぴ',
964
+ 'ぶ' => 'ぶ',
965
+ 'ぷ' => 'ぷ',
966
+ 'べ' => 'べ',
967
+ 'ぺ' => 'ぺ',
968
+ 'ぼ' => 'ぼ',
969
+ 'ぽ' => 'ぽ',
970
+ 'ゔ' => 'ゔ',
971
+ 'ゞ' => 'ゞ',
972
+ 'ガ' => 'ガ',
973
+ 'ギ' => 'ギ',
974
+ 'グ' => 'グ',
975
+ 'ゲ' => 'ゲ',
976
+ 'ゴ' => 'ゴ',
977
+ 'ザ' => 'ザ',
978
+ 'ジ' => 'ジ',
979
+ 'ズ' => 'ズ',
980
+ 'ゼ' => 'ゼ',
981
+ 'ゾ' => 'ゾ',
982
+ 'ダ' => 'ダ',
983
+ 'ヂ' => 'ヂ',
984
+ 'ヅ' => 'ヅ',
985
+ 'デ' => 'デ',
986
+ 'ド' => 'ド',
987
+ 'バ' => 'バ',
988
+ 'パ' => 'パ',
989
+ 'ビ' => 'ビ',
990
+ 'ピ' => 'ピ',
991
+ 'ブ' => 'ブ',
992
+ 'プ' => 'プ',
993
+ 'ベ' => 'ベ',
994
+ 'ペ' => 'ペ',
995
+ 'ボ' => 'ボ',
996
+ 'ポ' => 'ポ',
997
+ 'ヴ' => 'ヴ',
998
+ 'ヷ' => 'ヷ',
999
+ 'ヸ' => 'ヸ',
1000
+ 'ヹ' => 'ヹ',
1001
+ 'ヺ' => 'ヺ',
1002
+ 'ヾ' => 'ヾ',
1003
+ '豈' => '豈',
1004
+ '更' => '更',
1005
+ '車' => '車',
1006
+ '賈' => '賈',
1007
+ '滑' => '滑',
1008
+ '串' => '串',
1009
+ '句' => '句',
1010
+ '龜' => '龜',
1011
+ '龜' => '龜',
1012
+ '契' => '契',
1013
+ '金' => '金',
1014
+ '喇' => '喇',
1015
+ '奈' => '奈',
1016
+ '懶' => '懶',
1017
+ '癩' => '癩',
1018
+ '羅' => '羅',
1019
+ '蘿' => '蘿',
1020
+ '螺' => '螺',
1021
+ '裸' => '裸',
1022
+ '邏' => '邏',
1023
+ '樂' => '樂',
1024
+ '洛' => '洛',
1025
+ '烙' => '烙',
1026
+ '珞' => '珞',
1027
+ '落' => '落',
1028
+ '酪' => '酪',
1029
+ '駱' => '駱',
1030
+ '亂' => '亂',
1031
+ '卵' => '卵',
1032
+ '欄' => '欄',
1033
+ '爛' => '爛',
1034
+ '蘭' => '蘭',
1035
+ '鸞' => '鸞',
1036
+ '嵐' => '嵐',
1037
+ '濫' => '濫',
1038
+ '藍' => '藍',
1039
+ '襤' => '襤',
1040
+ '拉' => '拉',
1041
+ '臘' => '臘',
1042
+ '蠟' => '蠟',
1043
+ '廊' => '廊',
1044
+ '朗' => '朗',
1045
+ '浪' => '浪',
1046
+ '狼' => '狼',
1047
+ '郎' => '郎',
1048
+ '來' => '來',
1049
+ '冷' => '冷',
1050
+ '勞' => '勞',
1051
+ '擄' => '擄',
1052
+ '櫓' => '櫓',
1053
+ '爐' => '爐',
1054
+ '盧' => '盧',
1055
+ '老' => '老',
1056
+ '蘆' => '蘆',
1057
+ '虜' => '虜',
1058
+ '路' => '路',
1059
+ '露' => '露',
1060
+ '魯' => '魯',
1061
+ '鷺' => '鷺',
1062
+ '碌' => '碌',
1063
+ '祿' => '祿',
1064
+ '綠' => '綠',
1065
+ '菉' => '菉',
1066
+ '錄' => '錄',
1067
+ '鹿' => '鹿',
1068
+ '論' => '論',
1069
+ '壟' => '壟',
1070
+ '弄' => '弄',
1071
+ '籠' => '籠',
1072
+ '聾' => '聾',
1073
+ '牢' => '牢',
1074
+ '磊' => '磊',
1075
+ '賂' => '賂',
1076
+ '雷' => '雷',
1077
+ '壘' => '壘',
1078
+ '屢' => '屢',
1079
+ '樓' => '樓',
1080
+ '淚' => '淚',
1081
+ '漏' => '漏',
1082
+ '累' => '累',
1083
+ '縷' => '縷',
1084
+ '陋' => '陋',
1085
+ '勒' => '勒',
1086
+ '肋' => '肋',
1087
+ '凜' => '凜',
1088
+ '凌' => '凌',
1089
+ '稜' => '稜',
1090
+ '綾' => '綾',
1091
+ '菱' => '菱',
1092
+ '陵' => '陵',
1093
+ '讀' => '讀',
1094
+ '拏' => '拏',
1095
+ '樂' => '樂',
1096
+ '諾' => '諾',
1097
+ '丹' => '丹',
1098
+ '寧' => '寧',
1099
+ '怒' => '怒',
1100
+ '率' => '率',
1101
+ '異' => '異',
1102
+ '北' => '北',
1103
+ '磻' => '磻',
1104
+ '便' => '便',
1105
+ '復' => '復',
1106
+ '不' => '不',
1107
+ '泌' => '泌',
1108
+ '數' => '數',
1109
+ '索' => '索',
1110
+ '參' => '參',
1111
+ '塞' => '塞',
1112
+ '省' => '省',
1113
+ '葉' => '葉',
1114
+ '說' => '說',
1115
+ '殺' => '殺',
1116
+ '辰' => '辰',
1117
+ '沈' => '沈',
1118
+ '拾' => '拾',
1119
+ '若' => '若',
1120
+ '掠' => '掠',
1121
+ '略' => '略',
1122
+ '亮' => '亮',
1123
+ '兩' => '兩',
1124
+ '凉' => '凉',
1125
+ '梁' => '梁',
1126
+ '糧' => '糧',
1127
+ '良' => '良',
1128
+ '諒' => '諒',
1129
+ '量' => '量',
1130
+ '勵' => '勵',
1131
+ '呂' => '呂',
1132
+ '女' => '女',
1133
+ '廬' => '廬',
1134
+ '旅' => '旅',
1135
+ '濾' => '濾',
1136
+ '礪' => '礪',
1137
+ '閭' => '閭',
1138
+ '驪' => '驪',
1139
+ '麗' => '麗',
1140
+ '黎' => '黎',
1141
+ '力' => '力',
1142
+ '曆' => '曆',
1143
+ '歷' => '歷',
1144
+ '轢' => '轢',
1145
+ '年' => '年',
1146
+ '憐' => '憐',
1147
+ '戀' => '戀',
1148
+ '撚' => '撚',
1149
+ '漣' => '漣',
1150
+ '煉' => '煉',
1151
+ '璉' => '璉',
1152
+ '秊' => '秊',
1153
+ '練' => '練',
1154
+ '聯' => '聯',
1155
+ '輦' => '輦',
1156
+ '蓮' => '蓮',
1157
+ '連' => '連',
1158
+ '鍊' => '鍊',
1159
+ '列' => '列',
1160
+ '劣' => '劣',
1161
+ '咽' => '咽',
1162
+ '烈' => '烈',
1163
+ '裂' => '裂',
1164
+ '說' => '說',
1165
+ '廉' => '廉',
1166
+ '念' => '念',
1167
+ '捻' => '捻',
1168
+ '殮' => '殮',
1169
+ '簾' => '簾',
1170
+ '獵' => '獵',
1171
+ '令' => '令',
1172
+ '囹' => '囹',
1173
+ '寧' => '寧',
1174
+ '嶺' => '嶺',
1175
+ '怜' => '怜',
1176
+ '玲' => '玲',
1177
+ '瑩' => '瑩',
1178
+ '羚' => '羚',
1179
+ '聆' => '聆',
1180
+ '鈴' => '鈴',
1181
+ '零' => '零',
1182
+ '靈' => '靈',
1183
+ '領' => '領',
1184
+ '例' => '例',
1185
+ '禮' => '禮',
1186
+ '醴' => '醴',
1187
+ '隸' => '隸',
1188
+ '惡' => '惡',
1189
+ '了' => '了',
1190
+ '僚' => '僚',
1191
+ '寮' => '寮',
1192
+ '尿' => '尿',
1193
+ '料' => '料',
1194
+ '樂' => '樂',
1195
+ '燎' => '燎',
1196
+ '療' => '療',
1197
+ '蓼' => '蓼',
1198
+ '遼' => '遼',
1199
+ '龍' => '龍',
1200
+ '暈' => '暈',
1201
+ '阮' => '阮',
1202
+ '劉' => '劉',
1203
+ '杻' => '杻',
1204
+ '柳' => '柳',
1205
+ '流' => '流',
1206
+ '溜' => '溜',
1207
+ '琉' => '琉',
1208
+ '留' => '留',
1209
+ '硫' => '硫',
1210
+ '紐' => '紐',
1211
+ '類' => '類',
1212
+ '六' => '六',
1213
+ '戮' => '戮',
1214
+ '陸' => '陸',
1215
+ '倫' => '倫',
1216
+ '崙' => '崙',
1217
+ '淪' => '淪',
1218
+ '輪' => '輪',
1219
+ '律' => '律',
1220
+ '慄' => '慄',
1221
+ '栗' => '栗',
1222
+ '率' => '率',
1223
+ '隆' => '隆',
1224
+ '利' => '利',
1225
+ '吏' => '吏',
1226
+ '履' => '履',
1227
+ '易' => '易',
1228
+ '李' => '李',
1229
+ '梨' => '梨',
1230
+ '泥' => '泥',
1231
+ '理' => '理',
1232
+ '痢' => '痢',
1233
+ '罹' => '罹',
1234
+ '裏' => '裏',
1235
+ '裡' => '裡',
1236
+ '里' => '里',
1237
+ '離' => '離',
1238
+ '匿' => '匿',
1239
+ '溺' => '溺',
1240
+ '吝' => '吝',
1241
+ '燐' => '燐',
1242
+ '璘' => '璘',
1243
+ '藺' => '藺',
1244
+ '隣' => '隣',
1245
+ '鱗' => '鱗',
1246
+ '麟' => '麟',
1247
+ '林' => '林',
1248
+ '淋' => '淋',
1249
+ '臨' => '臨',
1250
+ '立' => '立',
1251
+ '笠' => '笠',
1252
+ '粒' => '粒',
1253
+ '狀' => '狀',
1254
+ '炙' => '炙',
1255
+ '識' => '識',
1256
+ '什' => '什',
1257
+ '茶' => '茶',
1258
+ '刺' => '刺',
1259
+ '切' => '切',
1260
+ '度' => '度',
1261
+ '拓' => '拓',
1262
+ '糖' => '糖',
1263
+ '宅' => '宅',
1264
+ '洞' => '洞',
1265
+ '暴' => '暴',
1266
+ '輻' => '輻',
1267
+ '行' => '行',
1268
+ '降' => '降',
1269
+ '見' => '見',
1270
+ '廓' => '廓',
1271
+ '兀' => '兀',
1272
+ '嗀' => '嗀',
1273
+ '塚' => '塚',
1274
+ '晴' => '晴',
1275
+ '凞' => '凞',
1276
+ '猪' => '猪',
1277
+ '益' => '益',
1278
+ '礼' => '礼',
1279
+ '神' => '神',
1280
+ '祥' => '祥',
1281
+ '福' => '福',
1282
+ '靖' => '靖',
1283
+ '精' => '精',
1284
+ '羽' => '羽',
1285
+ '蘒' => '蘒',
1286
+ '諸' => '諸',
1287
+ '逸' => '逸',
1288
+ '都' => '都',
1289
+ '飯' => '飯',
1290
+ '飼' => '飼',
1291
+ '館' => '館',
1292
+ '鶴' => '鶴',
1293
+ '郞' => '郞',
1294
+ '隷' => '隷',
1295
+ '侮' => '侮',
1296
+ '僧' => '僧',
1297
+ '免' => '免',
1298
+ '勉' => '勉',
1299
+ '勤' => '勤',
1300
+ '卑' => '卑',
1301
+ '喝' => '喝',
1302
+ '嘆' => '嘆',
1303
+ '器' => '器',
1304
+ '塀' => '塀',
1305
+ '墨' => '墨',
1306
+ '層' => '層',
1307
+ '屮' => '屮',
1308
+ '悔' => '悔',
1309
+ '慨' => '慨',
1310
+ '憎' => '憎',
1311
+ '懲' => '懲',
1312
+ '敏' => '敏',
1313
+ '既' => '既',
1314
+ '暑' => '暑',
1315
+ '梅' => '梅',
1316
+ '海' => '海',
1317
+ '渚' => '渚',
1318
+ '漢' => '漢',
1319
+ '煮' => '煮',
1320
+ '爫' => '爫',
1321
+ '琢' => '琢',
1322
+ '碑' => '碑',
1323
+ '社' => '社',
1324
+ '祉' => '祉',
1325
+ '祈' => '祈',
1326
+ '祐' => '祐',
1327
+ '祖' => '祖',
1328
+ '祝' => '祝',
1329
+ '禍' => '禍',
1330
+ '禎' => '禎',
1331
+ '穀' => '穀',
1332
+ '突' => '突',
1333
+ '節' => '節',
1334
+ '練' => '練',
1335
+ '縉' => '縉',
1336
+ '繁' => '繁',
1337
+ '署' => '署',
1338
+ '者' => '者',
1339
+ '臭' => '臭',
1340
+ '艹' => '艹',
1341
+ '艹' => '艹',
1342
+ '著' => '著',
1343
+ '褐' => '褐',
1344
+ '視' => '視',
1345
+ '謁' => '謁',
1346
+ '謹' => '謹',
1347
+ '賓' => '賓',
1348
+ '贈' => '贈',
1349
+ '辶' => '辶',
1350
+ '逸' => '逸',
1351
+ '難' => '難',
1352
+ '響' => '響',
1353
+ '頻' => '頻',
1354
+ '恵' => '恵',
1355
+ '𤋮' => '𤋮',
1356
+ '舘' => '舘',
1357
+ '並' => '並',
1358
+ '况' => '况',
1359
+ '全' => '全',
1360
+ '侀' => '侀',
1361
+ '充' => '充',
1362
+ '冀' => '冀',
1363
+ '勇' => '勇',
1364
+ '勺' => '勺',
1365
+ '喝' => '喝',
1366
+ '啕' => '啕',
1367
+ '喙' => '喙',
1368
+ '嗢' => '嗢',
1369
+ '塚' => '塚',
1370
+ '墳' => '墳',
1371
+ '奄' => '奄',
1372
+ '奔' => '奔',
1373
+ '婢' => '婢',
1374
+ '嬨' => '嬨',
1375
+ '廒' => '廒',
1376
+ '廙' => '廙',
1377
+ '彩' => '彩',
1378
+ '徭' => '徭',
1379
+ '惘' => '惘',
1380
+ '慎' => '慎',
1381
+ '愈' => '愈',
1382
+ '憎' => '憎',
1383
+ '慠' => '慠',
1384
+ '懲' => '懲',
1385
+ '戴' => '戴',
1386
+ '揄' => '揄',
1387
+ '搜' => '搜',
1388
+ '摒' => '摒',
1389
+ '敖' => '敖',
1390
+ '晴' => '晴',
1391
+ '朗' => '朗',
1392
+ '望' => '望',
1393
+ '杖' => '杖',
1394
+ '歹' => '歹',
1395
+ '殺' => '殺',
1396
+ '流' => '流',
1397
+ '滛' => '滛',
1398
+ '滋' => '滋',
1399
+ '漢' => '漢',
1400
+ '瀞' => '瀞',
1401
+ '煮' => '煮',
1402
+ '瞧' => '瞧',
1403
+ '爵' => '爵',
1404
+ '犯' => '犯',
1405
+ '猪' => '猪',
1406
+ '瑱' => '瑱',
1407
+ '甆' => '甆',
1408
+ '画' => '画',
1409
+ '瘝' => '瘝',
1410
+ '瘟' => '瘟',
1411
+ '益' => '益',
1412
+ '盛' => '盛',
1413
+ '直' => '直',
1414
+ '睊' => '睊',
1415
+ '着' => '着',
1416
+ '磌' => '磌',
1417
+ '窱' => '窱',
1418
+ '節' => '節',
1419
+ '类' => '类',
1420
+ '絛' => '絛',
1421
+ '練' => '練',
1422
+ '缾' => '缾',
1423
+ '者' => '者',
1424
+ '荒' => '荒',
1425
+ '華' => '華',
1426
+ '蝹' => '蝹',
1427
+ '襁' => '襁',
1428
+ '覆' => '覆',
1429
+ '視' => '視',
1430
+ '調' => '調',
1431
+ '諸' => '諸',
1432
+ '請' => '請',
1433
+ '謁' => '謁',
1434
+ '諾' => '諾',
1435
+ '諭' => '諭',
1436
+ '謹' => '謹',
1437
+ '變' => '變',
1438
+ '贈' => '贈',
1439
+ '輸' => '輸',
1440
+ '遲' => '遲',
1441
+ '醙' => '醙',
1442
+ '鉶' => '鉶',
1443
+ '陼' => '陼',
1444
+ '難' => '難',
1445
+ '靖' => '靖',
1446
+ '韛' => '韛',
1447
+ '響' => '響',
1448
+ '頋' => '頋',
1449
+ '頻' => '頻',
1450
+ '鬒' => '鬒',
1451
+ '龜' => '龜',
1452
+ '𢡊' => '𢡊',
1453
+ '𢡄' => '𢡄',
1454
+ '𣏕' => '𣏕',
1455
+ '㮝' => '㮝',
1456
+ '䀘' => '䀘',
1457
+ '䀹' => '䀹',
1458
+ '𥉉' => '𥉉',
1459
+ '𥳐' => '𥳐',
1460
+ '𧻓' => '𧻓',
1461
+ '齃' => '齃',
1462
+ '龎' => '龎',
1463
+ 'יִ' => 'יִ',
1464
+ 'ײַ' => 'ײַ',
1465
+ 'שׁ' => 'שׁ',
1466
+ 'שׂ' => 'שׂ',
1467
+ 'שּׁ' => 'שּׁ',
1468
+ 'שּׂ' => 'שּׂ',
1469
+ 'אַ' => 'אַ',
1470
+ 'אָ' => 'אָ',
1471
+ 'אּ' => 'אּ',
1472
+ 'בּ' => 'בּ',
1473
+ 'גּ' => 'גּ',
1474
+ 'דּ' => 'דּ',
1475
+ 'הּ' => 'הּ',
1476
+ 'וּ' => 'וּ',
1477
+ 'זּ' => 'זּ',
1478
+ 'טּ' => 'טּ',
1479
+ 'יּ' => 'יּ',
1480
+ 'ךּ' => 'ךּ',
1481
+ 'כּ' => 'כּ',
1482
+ 'לּ' => 'לּ',
1483
+ 'מּ' => 'מּ',
1484
+ 'נּ' => 'נּ',
1485
+ 'סּ' => 'סּ',
1486
+ 'ףּ' => 'ףּ',
1487
+ 'פּ' => 'פּ',
1488
+ 'צּ' => 'צּ',
1489
+ 'קּ' => 'קּ',
1490
+ 'רּ' => 'רּ',
1491
+ 'שּ' => 'שּ',
1492
+ 'תּ' => 'תּ',
1493
+ 'וֹ' => 'וֹ',
1494
+ 'בֿ' => 'בֿ',
1495
+ 'כֿ' => 'כֿ',
1496
+ 'פֿ' => 'פֿ',
1497
+ '𑂚' => '𑂚',
1498
+ '𑂜' => '𑂜',
1499
+ '𑂫' => '𑂫',
1500
+ '𑄮' => '𑄮',
1501
+ '𑄯' => '𑄯',
1502
+ '𑍋' => '𑍋',
1503
+ '𑍌' => '𑍌',
1504
+ '𑒻' => '𑒻',
1505
+ '𑒼' => '𑒼',
1506
+ '𑒾' => '𑒾',
1507
+ '𑖺' => '𑖺',
1508
+ '𑖻' => '𑖻',
1509
+ '𑤸' => '𑤸',
1510
+ '𝅗𝅥' => '𝅗𝅥',
1511
+ '𝅘𝅥' => '𝅘𝅥',
1512
+ '𝅘𝅥𝅮' => '𝅘𝅥𝅮',
1513
+ '𝅘𝅥𝅯' => '𝅘𝅥𝅯',
1514
+ '𝅘𝅥𝅰' => '𝅘𝅥𝅰',
1515
+ '𝅘𝅥𝅱' => '𝅘𝅥𝅱',
1516
+ '𝅘𝅥𝅲' => '𝅘𝅥𝅲',
1517
+ '𝆹𝅥' => '𝆹𝅥',
1518
+ '𝆺𝅥' => '𝆺𝅥',
1519
+ '𝆹𝅥𝅮' => '𝆹𝅥𝅮',
1520
+ '𝆺𝅥𝅮' => '𝆺𝅥𝅮',
1521
+ '𝆹𝅥𝅯' => '𝆹𝅥𝅯',
1522
+ '𝆺𝅥𝅯' => '𝆺𝅥𝅯',
1523
+ '丽' => '丽',
1524
+ '丸' => '丸',
1525
+ '乁' => '乁',
1526
+ '𠄢' => '𠄢',
1527
+ '你' => '你',
1528
+ '侮' => '侮',
1529
+ '侻' => '侻',
1530
+ '倂' => '倂',
1531
+ '偺' => '偺',
1532
+ '備' => '備',
1533
+ '僧' => '僧',
1534
+ '像' => '像',
1535
+ '㒞' => '㒞',
1536
+ '𠘺' => '𠘺',
1537
+ '免' => '免',
1538
+ '兔' => '兔',
1539
+ '兤' => '兤',
1540
+ '具' => '具',
1541
+ '𠔜' => '𠔜',
1542
+ '㒹' => '㒹',
1543
+ '內' => '內',
1544
+ '再' => '再',
1545
+ '𠕋' => '𠕋',
1546
+ '冗' => '冗',
1547
+ '冤' => '冤',
1548
+ '仌' => '仌',
1549
+ '冬' => '冬',
1550
+ '况' => '况',
1551
+ '𩇟' => '𩇟',
1552
+ '凵' => '凵',
1553
+ '刃' => '刃',
1554
+ '㓟' => '㓟',
1555
+ '刻' => '刻',
1556
+ '剆' => '剆',
1557
+ '割' => '割',
1558
+ '剷' => '剷',
1559
+ '㔕' => '㔕',
1560
+ '勇' => '勇',
1561
+ '勉' => '勉',
1562
+ '勤' => '勤',
1563
+ '勺' => '勺',
1564
+ '包' => '包',
1565
+ '匆' => '匆',
1566
+ '北' => '北',
1567
+ '卉' => '卉',
1568
+ '卑' => '卑',
1569
+ '博' => '博',
1570
+ '即' => '即',
1571
+ '卽' => '卽',
1572
+ '卿' => '卿',
1573
+ '卿' => '卿',
1574
+ '卿' => '卿',
1575
+ '𠨬' => '𠨬',
1576
+ '灰' => '灰',
1577
+ '及' => '及',
1578
+ '叟' => '叟',
1579
+ '𠭣' => '𠭣',
1580
+ '叫' => '叫',
1581
+ '叱' => '叱',
1582
+ '吆' => '吆',
1583
+ '咞' => '咞',
1584
+ '吸' => '吸',
1585
+ '呈' => '呈',
1586
+ '周' => '周',
1587
+ '咢' => '咢',
1588
+ '哶' => '哶',
1589
+ '唐' => '唐',
1590
+ '啓' => '啓',
1591
+ '啣' => '啣',
1592
+ '善' => '善',
1593
+ '善' => '善',
1594
+ '喙' => '喙',
1595
+ '喫' => '喫',
1596
+ '喳' => '喳',
1597
+ '嗂' => '嗂',
1598
+ '圖' => '圖',
1599
+ '嘆' => '嘆',
1600
+ '圗' => '圗',
1601
+ '噑' => '噑',
1602
+ '噴' => '噴',
1603
+ '切' => '切',
1604
+ '壮' => '壮',
1605
+ '城' => '城',
1606
+ '埴' => '埴',
1607
+ '堍' => '堍',
1608
+ '型' => '型',
1609
+ '堲' => '堲',
1610
+ '報' => '報',
1611
+ '墬' => '墬',
1612
+ '𡓤' => '𡓤',
1613
+ '売' => '売',
1614
+ '壷' => '壷',
1615
+ '夆' => '夆',
1616
+ '多' => '多',
1617
+ '夢' => '夢',
1618
+ '奢' => '奢',
1619
+ '𡚨' => '𡚨',
1620
+ '𡛪' => '𡛪',
1621
+ '姬' => '姬',
1622
+ '娛' => '娛',
1623
+ '娧' => '娧',
1624
+ '姘' => '姘',
1625
+ '婦' => '婦',
1626
+ '㛮' => '㛮',
1627
+ '㛼' => '㛼',
1628
+ '嬈' => '嬈',
1629
+ '嬾' => '嬾',
1630
+ '嬾' => '嬾',
1631
+ '𡧈' => '𡧈',
1632
+ '寃' => '寃',
1633
+ '寘' => '寘',
1634
+ '寧' => '寧',
1635
+ '寳' => '寳',
1636
+ '𡬘' => '𡬘',
1637
+ '寿' => '寿',
1638
+ '将' => '将',
1639
+ '当' => '当',
1640
+ '尢' => '尢',
1641
+ '㞁' => '㞁',
1642
+ '屠' => '屠',
1643
+ '屮' => '屮',
1644
+ '峀' => '峀',
1645
+ '岍' => '岍',
1646
+ '𡷤' => '𡷤',
1647
+ '嵃' => '嵃',
1648
+ '𡷦' => '𡷦',
1649
+ '嵮' => '嵮',
1650
+ '嵫' => '嵫',
1651
+ '嵼' => '嵼',
1652
+ '巡' => '巡',
1653
+ '巢' => '巢',
1654
+ '㠯' => '㠯',
1655
+ '巽' => '巽',
1656
+ '帨' => '帨',
1657
+ '帽' => '帽',
1658
+ '幩' => '幩',
1659
+ '㡢' => '㡢',
1660
+ '𢆃' => '𢆃',
1661
+ '㡼' => '㡼',
1662
+ '庰' => '庰',
1663
+ '庳' => '庳',
1664
+ '庶' => '庶',
1665
+ '廊' => '廊',
1666
+ '𪎒' => '𪎒',
1667
+ '廾' => '廾',
1668
+ '𢌱' => '𢌱',
1669
+ '𢌱' => '𢌱',
1670
+ '舁' => '舁',
1671
+ '弢' => '弢',
1672
+ '弢' => '弢',
1673
+ '㣇' => '㣇',
1674
+ '𣊸' => '𣊸',
1675
+ '𦇚' => '𦇚',
1676
+ '形' => '形',
1677
+ '彫' => '彫',
1678
+ '㣣' => '㣣',
1679
+ '徚' => '徚',
1680
+ '忍' => '忍',
1681
+ '志' => '志',
1682
+ '忹' => '忹',
1683
+ '悁' => '悁',
1684
+ '㤺' => '㤺',
1685
+ '㤜' => '㤜',
1686
+ '悔' => '悔',
1687
+ '𢛔' => '𢛔',
1688
+ '惇' => '惇',
1689
+ '慈' => '慈',
1690
+ '慌' => '慌',
1691
+ '慎' => '慎',
1692
+ '慌' => '慌',
1693
+ '慺' => '慺',
1694
+ '憎' => '憎',
1695
+ '憲' => '憲',
1696
+ '憤' => '憤',
1697
+ '憯' => '憯',
1698
+ '懞' => '懞',
1699
+ '懲' => '懲',
1700
+ '懶' => '懶',
1701
+ '成' => '成',
1702
+ '戛' => '戛',
1703
+ '扝' => '扝',
1704
+ '抱' => '抱',
1705
+ '拔' => '拔',
1706
+ '捐' => '捐',
1707
+ '𢬌' => '𢬌',
1708
+ '挽' => '挽',
1709
+ '拼' => '拼',
1710
+ '捨' => '捨',
1711
+ '掃' => '掃',
1712
+ '揤' => '揤',
1713
+ '𢯱' => '𢯱',
1714
+ '搢' => '搢',
1715
+ '揅' => '揅',
1716
+ '掩' => '掩',
1717
+ '㨮' => '㨮',
1718
+ '摩' => '摩',
1719
+ '摾' => '摾',
1720
+ '撝' => '撝',
1721
+ '摷' => '摷',
1722
+ '㩬' => '㩬',
1723
+ '敏' => '敏',
1724
+ '敬' => '敬',
1725
+ '𣀊' => '𣀊',
1726
+ '旣' => '旣',
1727
+ '書' => '書',
1728
+ '晉' => '晉',
1729
+ '㬙' => '㬙',
1730
+ '暑' => '暑',
1731
+ '㬈' => '㬈',
1732
+ '㫤' => '㫤',
1733
+ '冒' => '冒',
1734
+ '冕' => '冕',
1735
+ '最' => '最',
1736
+ '暜' => '暜',
1737
+ '肭' => '肭',
1738
+ '䏙' => '䏙',
1739
+ '朗' => '朗',
1740
+ '望' => '望',
1741
+ '朡' => '朡',
1742
+ '杞' => '杞',
1743
+ '杓' => '杓',
1744
+ '𣏃' => '𣏃',
1745
+ '㭉' => '㭉',
1746
+ '柺' => '柺',
1747
+ '枅' => '枅',
1748
+ '桒' => '桒',
1749
+ '梅' => '梅',
1750
+ '𣑭' => '𣑭',
1751
+ '梎' => '梎',
1752
+ '栟' => '栟',
1753
+ '椔' => '椔',
1754
+ '㮝' => '㮝',
1755
+ '楂' => '楂',
1756
+ '榣' => '榣',
1757
+ '槪' => '槪',
1758
+ '檨' => '檨',
1759
+ '𣚣' => '𣚣',
1760
+ '櫛' => '櫛',
1761
+ '㰘' => '㰘',
1762
+ '次' => '次',
1763
+ '𣢧' => '𣢧',
1764
+ '歔' => '歔',
1765
+ '㱎' => '㱎',
1766
+ '歲' => '歲',
1767
+ '殟' => '殟',
1768
+ '殺' => '殺',
1769
+ '殻' => '殻',
1770
+ '𣪍' => '𣪍',
1771
+ '𡴋' => '𡴋',
1772
+ '𣫺' => '𣫺',
1773
+ '汎' => '汎',
1774
+ '𣲼' => '𣲼',
1775
+ '沿' => '沿',
1776
+ '泍' => '泍',
1777
+ '汧' => '汧',
1778
+ '洖' => '洖',
1779
+ '派' => '派',
1780
+ '海' => '海',
1781
+ '流' => '流',
1782
+ '浩' => '浩',
1783
+ '浸' => '浸',
1784
+ '涅' => '涅',
1785
+ '𣴞' => '𣴞',
1786
+ '洴' => '洴',
1787
+ '港' => '港',
1788
+ '湮' => '湮',
1789
+ '㴳' => '㴳',
1790
+ '滋' => '滋',
1791
+ '滇' => '滇',
1792
+ '𣻑' => '𣻑',
1793
+ '淹' => '淹',
1794
+ '潮' => '潮',
1795
+ '𣽞' => '𣽞',
1796
+ '𣾎' => '𣾎',
1797
+ '濆' => '濆',
1798
+ '瀹' => '瀹',
1799
+ '瀞' => '瀞',
1800
+ '瀛' => '瀛',
1801
+ '㶖' => '㶖',
1802
+ '灊' => '灊',
1803
+ '災' => '災',
1804
+ '灷' => '灷',
1805
+ '炭' => '炭',
1806
+ '𠔥' => '𠔥',
1807
+ '煅' => '煅',
1808
+ '𤉣' => '𤉣',
1809
+ '熜' => '熜',
1810
+ '𤎫' => '𤎫',
1811
+ '爨' => '爨',
1812
+ '爵' => '爵',
1813
+ '牐' => '牐',
1814
+ '𤘈' => '𤘈',
1815
+ '犀' => '犀',
1816
+ '犕' => '犕',
1817
+ '𤜵' => '𤜵',
1818
+ '𤠔' => '𤠔',
1819
+ '獺' => '獺',
1820
+ '王' => '王',
1821
+ '㺬' => '㺬',
1822
+ '玥' => '玥',
1823
+ '㺸' => '㺸',
1824
+ '㺸' => '㺸',
1825
+ '瑇' => '瑇',
1826
+ '瑜' => '瑜',
1827
+ '瑱' => '瑱',
1828
+ '璅' => '璅',
1829
+ '瓊' => '瓊',
1830
+ '㼛' => '㼛',
1831
+ '甤' => '甤',
1832
+ '𤰶' => '𤰶',
1833
+ '甾' => '甾',
1834
+ '𤲒' => '𤲒',
1835
+ '異' => '異',
1836
+ '𢆟' => '𢆟',
1837
+ '瘐' => '瘐',
1838
+ '𤾡' => '𤾡',
1839
+ '𤾸' => '𤾸',
1840
+ '𥁄' => '𥁄',
1841
+ '㿼' => '㿼',
1842
+ '䀈' => '䀈',
1843
+ '直' => '直',
1844
+ '𥃳' => '𥃳',
1845
+ '𥃲' => '𥃲',
1846
+ '𥄙' => '𥄙',
1847
+ '𥄳' => '𥄳',
1848
+ '眞' => '眞',
1849
+ '真' => '真',
1850
+ '真' => '真',
1851
+ '睊' => '睊',
1852
+ '䀹' => '䀹',
1853
+ '瞋' => '瞋',
1854
+ '䁆' => '䁆',
1855
+ '䂖' => '䂖',
1856
+ '𥐝' => '𥐝',
1857
+ '硎' => '硎',
1858
+ '碌' => '碌',
1859
+ '磌' => '磌',
1860
+ '䃣' => '䃣',
1861
+ '𥘦' => '𥘦',
1862
+ '祖' => '祖',
1863
+ '𥚚' => '𥚚',
1864
+ '𥛅' => '𥛅',
1865
+ '福' => '福',
1866
+ '秫' => '秫',
1867
+ '䄯' => '䄯',
1868
+ '穀' => '穀',
1869
+ '穊' => '穊',
1870
+ '穏' => '穏',
1871
+ '𥥼' => '𥥼',
1872
+ '𥪧' => '𥪧',
1873
+ '𥪧' => '𥪧',
1874
+ '竮' => '竮',
1875
+ '䈂' => '䈂',
1876
+ '𥮫' => '𥮫',
1877
+ '篆' => '篆',
1878
+ '築' => '築',
1879
+ '䈧' => '䈧',
1880
+ '𥲀' => '𥲀',
1881
+ '糒' => '糒',
1882
+ '䊠' => '䊠',
1883
+ '糨' => '糨',
1884
+ '糣' => '糣',
1885
+ '紀' => '紀',
1886
+ '𥾆' => '𥾆',
1887
+ '絣' => '絣',
1888
+ '䌁' => '䌁',
1889
+ '緇' => '緇',
1890
+ '縂' => '縂',
1891
+ '繅' => '繅',
1892
+ '䌴' => '䌴',
1893
+ '𦈨' => '𦈨',
1894
+ '𦉇' => '𦉇',
1895
+ '䍙' => '䍙',
1896
+ '𦋙' => '𦋙',
1897
+ '罺' => '罺',
1898
+ '𦌾' => '𦌾',
1899
+ '羕' => '羕',
1900
+ '翺' => '翺',
1901
+ '者' => '者',
1902
+ '𦓚' => '𦓚',
1903
+ '𦔣' => '𦔣',
1904
+ '聠' => '聠',
1905
+ '𦖨' => '𦖨',
1906
+ '聰' => '聰',
1907
+ '𣍟' => '𣍟',
1908
+ '䏕' => '䏕',
1909
+ '育' => '育',
1910
+ '脃' => '脃',
1911
+ '䐋' => '䐋',
1912
+ '脾' => '脾',
1913
+ '媵' => '媵',
1914
+ '𦞧' => '𦞧',
1915
+ '𦞵' => '𦞵',
1916
+ '𣎓' => '𣎓',
1917
+ '𣎜' => '𣎜',
1918
+ '舁' => '舁',
1919
+ '舄' => '舄',
1920
+ '辞' => '辞',
1921
+ '䑫' => '䑫',
1922
+ '芑' => '芑',
1923
+ '芋' => '芋',
1924
+ '芝' => '芝',
1925
+ '劳' => '劳',
1926
+ '花' => '花',
1927
+ '芳' => '芳',
1928
+ '芽' => '芽',
1929
+ '苦' => '苦',
1930
+ '𦬼' => '𦬼',
1931
+ '若' => '若',
1932
+ '茝' => '茝',
1933
+ '荣' => '荣',
1934
+ '莭' => '莭',
1935
+ '茣' => '茣',
1936
+ '莽' => '莽',
1937
+ '菧' => '菧',
1938
+ '著' => '著',
1939
+ '荓' => '荓',
1940
+ '菊' => '菊',
1941
+ '菌' => '菌',
1942
+ '菜' => '菜',
1943
+ '𦰶' => '𦰶',
1944
+ '𦵫' => '𦵫',
1945
+ '𦳕' => '𦳕',
1946
+ '䔫' => '䔫',
1947
+ '蓱' => '蓱',
1948
+ '蓳' => '蓳',
1949
+ '蔖' => '蔖',
1950
+ '𧏊' => '𧏊',
1951
+ '蕤' => '蕤',
1952
+ '𦼬' => '𦼬',
1953
+ '䕝' => '䕝',
1954
+ '䕡' => '䕡',
1955
+ '𦾱' => '𦾱',
1956
+ '𧃒' => '𧃒',
1957
+ '䕫' => '䕫',
1958
+ '虐' => '虐',
1959
+ '虜' => '虜',
1960
+ '虧' => '虧',
1961
+ '虩' => '虩',
1962
+ '蚩' => '蚩',
1963
+ '蚈' => '蚈',
1964
+ '蜎' => '蜎',
1965
+ '蛢' => '蛢',
1966
+ '蝹' => '蝹',
1967
+ '蜨' => '蜨',
1968
+ '蝫' => '蝫',
1969
+ '螆' => '螆',
1970
+ '䗗' => '䗗',
1971
+ '蟡' => '蟡',
1972
+ '蠁' => '蠁',
1973
+ '䗹' => '䗹',
1974
+ '衠' => '衠',
1975
+ '衣' => '衣',
1976
+ '𧙧' => '𧙧',
1977
+ '裗' => '裗',
1978
+ '裞' => '裞',
1979
+ '䘵' => '䘵',
1980
+ '裺' => '裺',
1981
+ '㒻' => '㒻',
1982
+ '𧢮' => '𧢮',
1983
+ '𧥦' => '𧥦',
1984
+ '䚾' => '䚾',
1985
+ '䛇' => '䛇',
1986
+ '誠' => '誠',
1987
+ '諭' => '諭',
1988
+ '變' => '變',
1989
+ '豕' => '豕',
1990
+ '𧲨' => '𧲨',
1991
+ '貫' => '貫',
1992
+ '賁' => '賁',
1993
+ '贛' => '贛',
1994
+ '起' => '起',
1995
+ '𧼯' => '𧼯',
1996
+ '𠠄' => '𠠄',
1997
+ '跋' => '跋',
1998
+ '趼' => '趼',
1999
+ '跰' => '跰',
2000
+ '𠣞' => '𠣞',
2001
+ '軔' => '軔',
2002
+ '輸' => '輸',
2003
+ '𨗒' => '𨗒',
2004
+ '𨗭' => '𨗭',
2005
+ '邔' => '邔',
2006
+ '郱' => '郱',
2007
+ '鄑' => '鄑',
2008
+ '𨜮' => '𨜮',
2009
+ '鄛' => '鄛',
2010
+ '鈸' => '鈸',
2011
+ '鋗' => '鋗',
2012
+ '鋘' => '鋘',
2013
+ '鉼' => '鉼',
2014
+ '鏹' => '鏹',
2015
+ '鐕' => '鐕',
2016
+ '𨯺' => '𨯺',
2017
+ '開' => '開',
2018
+ '䦕' => '䦕',
2019
+ '閷' => '閷',
2020
+ '𨵷' => '𨵷',
2021
+ '䧦' => '䧦',
2022
+ '雃' => '雃',
2023
+ '嶲' => '嶲',
2024
+ '霣' => '霣',
2025
+ '𩅅' => '𩅅',
2026
+ '𩈚' => '𩈚',
2027
+ '䩮' => '䩮',
2028
+ '䩶' => '䩶',
2029
+ '韠' => '韠',
2030
+ '𩐊' => '𩐊',
2031
+ '䪲' => '䪲',
2032
+ '𩒖' => '𩒖',
2033
+ '頋' => '頋',
2034
+ '頋' => '頋',
2035
+ '頩' => '頩',
2036
+ '𩖶' => '𩖶',
2037
+ '飢' => '飢',
2038
+ '䬳' => '䬳',
2039
+ '餩' => '餩',
2040
+ '馧' => '馧',
2041
+ '駂' => '駂',
2042
+ '駾' => '駾',
2043
+ '䯎' => '䯎',
2044
+ '𩬰' => '𩬰',
2045
+ '鬒' => '鬒',
2046
+ '鱀' => '鱀',
2047
+ '鳽' => '鳽',
2048
+ '䳎' => '䳎',
2049
+ '䳭' => '䳭',
2050
+ '鵧' => '鵧',
2051
+ '𪃎' => '𪃎',
2052
+ '䳸' => '䳸',
2053
+ '𪄅' => '𪄅',
2054
+ '𪈎' => '𪈎',
2055
+ '𪊑' => '𪊑',
2056
+ '麻' => '麻',
2057
+ '䵖' => '䵖',
2058
+ '黹' => '黹',
2059
+ '黾' => '黾',
2060
+ '鼅' => '鼅',
2061
+ '鼏' => '鼏',
2062
+ '鼖' => '鼖',
2063
+ '鼻' => '鼻',
2064
+ '𪘀' => '𪘀',
2065
+ );
vendor/browscap-php/symfony/polyfill-intl-normalizer/Resources/unidata/combiningClass.php ADDED
@@ -0,0 +1,876 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ return array (
4
+ '̀' => 230,
5
+ '́' => 230,
6
+ '̂' => 230,
7
+ '̃' => 230,
8
+ '̄' => 230,
9
+ '̅' => 230,
10
+ '̆' => 230,
11
+ '̇' => 230,
12
+ '̈' => 230,
13
+ '̉' => 230,
14
+ '̊' => 230,
15
+ '̋' => 230,
16
+ '̌' => 230,
17
+ '̍' => 230,
18
+ '̎' => 230,
19
+ '̏' => 230,
20
+ '̐' => 230,
21
+ '̑' => 230,
22
+ '̒' => 230,
23
+ '̓' => 230,
24
+ '̔' => 230,
25
+ '̕' => 232,
26
+ '̖' => 220,
27
+ '̗' => 220,
28
+ '̘' => 220,
29
+ '̙' => 220,
30
+ '̚' => 232,
31
+ '̛' => 216,
32
+ '̜' => 220,
33
+ '̝' => 220,
34
+ '̞' => 220,
35
+ '̟' => 220,
36
+ '̠' => 220,
37
+ '̡' => 202,
38
+ '̢' => 202,
39
+ '̣' => 220,
40
+ '̤' => 220,
41
+ '̥' => 220,
42
+ '̦' => 220,
43
+ '̧' => 202,
44
+ '̨' => 202,
45
+ '̩' => 220,
46
+ '̪' => 220,
47
+ '̫' => 220,
48
+ '̬' => 220,
49
+ '̭' => 220,
50
+ '̮' => 220,
51
+ '̯' => 220,
52
+ '̰' => 220,
53
+ '̱' => 220,
54
+ '̲' => 220,
55
+ '̳' => 220,
56
+ '̴' => 1,
57
+ '̵' => 1,
58
+ '̶' => 1,
59
+ '̷' => 1,
60
+ '̸' => 1,
61
+ '̹' => 220,
62
+ '̺' => 220,
63
+ '̻' => 220,
64
+ '̼' => 220,
65
+ '̽' => 230,
66
+ '̾' => 230,
67
+ '̿' => 230,
68
+ '̀' => 230,
69
+ '́' => 230,
70
+ '͂' => 230,
71
+ '̓' => 230,
72
+ '̈́' => 230,
73
+ 'ͅ' => 240,
74
+ '͆' => 230,
75
+ '͇' => 220,
76
+ '͈' => 220,
77
+ '͉' => 220,
78
+ '͊' => 230,
79
+ '͋' => 230,
80
+ '͌' => 230,
81
+ '͍' => 220,
82
+ '͎' => 220,
83
+ '͐' => 230,
84
+ '͑' => 230,
85
+ '͒' => 230,
86
+ '͓' => 220,
87
+ '͔' => 220,
88
+ '͕' => 220,
89
+ '͖' => 220,
90
+ '͗' => 230,
91
+ '͘' => 232,
92
+ '͙' => 220,
93
+ '͚' => 220,
94
+ '͛' => 230,
95
+ '͜' => 233,
96
+ '͝' => 234,
97
+ '͞' => 234,
98
+ '͟' => 233,
99
+ '͠' => 234,
100
+ '͡' => 234,
101
+ '͢' => 233,
102
+ 'ͣ' => 230,
103
+ 'ͤ' => 230,
104
+ 'ͥ' => 230,
105
+ 'ͦ' => 230,
106
+ 'ͧ' => 230,
107
+ 'ͨ' => 230,
108
+ 'ͩ' => 230,
109
+ 'ͪ' => 230,
110
+ 'ͫ' => 230,
111
+ 'ͬ' => 230,
112
+ 'ͭ' => 230,
113
+ 'ͮ' => 230,
114
+ 'ͯ' => 230,
115
+ '҃' => 230,
116
+ '҄' => 230,
117
+ '҅' => 230,
118
+ '҆' => 230,
119
+ '҇' => 230,
120
+ '֑' => 220,
121
+ '֒' => 230,
122
+ '֓' => 230,
123
+ '֔' => 230,
124
+ '֕' => 230,
125
+ '֖' => 220,
126
+ '֗' => 230,
127
+ '֘' => 230,
128
+ '֙' => 230,
129
+ '֚' => 222,
130
+ '֛' => 220,
131
+ '֜' => 230,
132
+ '֝' => 230,
133
+ '֞' => 230,
134
+ '֟' => 230,
135
+ '֠' => 230,
136
+ '֡' => 230,
137
+ '֢' => 220,
138
+ '֣' => 220,
139
+ '֤' => 220,
140
+ '֥' => 220,
141
+ '֦' => 220,
142
+ '֧' => 220,
143
+ '֨' => 230,
144
+ '֩' => 230,
145
+ '֪' => 220,
146
+ '֫' => 230,
147
+ '֬' => 230,
148
+ '֭' => 222,
149
+ '֮' => 228,
150
+ '֯' => 230,
151
+ 'ְ' => 10,
152
+ 'ֱ' => 11,
153
+ 'ֲ' => 12,
154
+ 'ֳ' => 13,
155
+ 'ִ' => 14,
156
+ 'ֵ' => 15,
157
+ 'ֶ' => 16,
158
+ 'ַ' => 17,
159
+ 'ָ' => 18,
160
+ 'ֹ' => 19,
161
+ 'ֺ' => 19,
162
+ 'ֻ' => 20,
163
+ 'ּ' => 21,
164
+ 'ֽ' => 22,
165
+ 'ֿ' => 23,
166
+ 'ׁ' => 24,
167
+ 'ׂ' => 25,
168
+ 'ׄ' => 230,
169
+ 'ׅ' => 220,
170
+ 'ׇ' => 18,
171
+ 'ؐ' => 230,
172
+ 'ؑ' => 230,
173
+ 'ؒ' => 230,
174
+ 'ؓ' => 230,
175
+ 'ؔ' => 230,
176
+ 'ؕ' => 230,
177
+ 'ؖ' => 230,
178
+ 'ؗ' => 230,
179
+ 'ؘ' => 30,
180
+ 'ؙ' => 31,
181
+ 'ؚ' => 32,
182
+ 'ً' => 27,
183
+ 'ٌ' => 28,
184
+ 'ٍ' => 29,
185
+ 'َ' => 30,
186
+ 'ُ' => 31,
187
+ 'ِ' => 32,
188
+ 'ّ' => 33,
189
+ 'ْ' => 34,
190
+ 'ٓ' => 230,
191
+ 'ٔ' => 230,
192
+ 'ٕ' => 220,
193
+ 'ٖ' => 220,
194
+ 'ٗ' => 230,
195
+ '٘' => 230,
196
+ 'ٙ' => 230,
197
+ 'ٚ' => 230,
198
+ 'ٛ' => 230,
199
+ 'ٜ' => 220,
200
+ 'ٝ' => 230,
201
+ 'ٞ' => 230,
202
+ 'ٟ' => 220,
203
+ 'ٰ' => 35,
204
+ 'ۖ' => 230,
205
+ 'ۗ' => 230,
206
+ 'ۘ' => 230,
207
+ 'ۙ' => 230,
208
+ 'ۚ' => 230,
209
+ 'ۛ' => 230,
210
+ 'ۜ' => 230,
211
+ '۟' => 230,
212
+ '۠' => 230,
213
+ 'ۡ' => 230,
214
+ 'ۢ' => 230,
215
+ 'ۣ' => 220,
216
+ 'ۤ' => 230,
217
+ 'ۧ' => 230,
218
+ 'ۨ' => 230,
219
+ '۪' => 220,
220
+ '۫' => 230,
221
+ '۬' => 230,
222
+ 'ۭ' => 220,
223
+ 'ܑ' => 36,
224
+ 'ܰ' => 230,
225
+ 'ܱ' => 220,
226
+ 'ܲ' => 230,
227
+ 'ܳ' => 230,
228
+ 'ܴ' => 220,
229
+ 'ܵ' => 230,
230
+ 'ܶ' => 230,
231
+ 'ܷ' => 220,
232
+ 'ܸ' => 220,
233
+ 'ܹ' => 220,
234
+ 'ܺ' => 230,
235
+ 'ܻ' => 220,
236
+ 'ܼ' => 220,
237
+ 'ܽ' => 230,
238
+ 'ܾ' => 220,
239
+ 'ܿ' => 230,
240
+ '݀' => 230,
241
+ '݁' => 230,
242
+ '݂' => 220,
243
+ '݃' => 230,
244
+ '݄' => 220,
245
+ '݅' => 230,
246
+ '݆' => 220,
247
+ '݇' => 230,
248
+ '݈' => 220,
249
+ '݉' => 230,
250
+ '݊' => 230,
251
+ '߫' => 230,
252
+ '߬' => 230,
253
+ '߭' => 230,
254
+ '߮' => 230,
255
+ '߯' => 230,
256
+ '߰' => 230,
257
+ '߱' => 230,
258
+ '߲' => 220,
259
+ '߳' => 230,
260
+ '߽' => 220,
261
+ 'ࠖ' => 230,
262
+ 'ࠗ' => 230,
263
+ '࠘' => 230,
264
+ '࠙' => 230,
265
+ 'ࠛ' => 230,
266
+ 'ࠜ' => 230,
267
+ 'ࠝ' => 230,
268
+ 'ࠞ' => 230,
269
+ 'ࠟ' => 230,
270
+ 'ࠠ' => 230,
271
+ 'ࠡ' => 230,
272
+ 'ࠢ' => 230,
273
+ 'ࠣ' => 230,
274
+ 'ࠥ' => 230,
275
+ 'ࠦ' => 230,
276
+ 'ࠧ' => 230,
277
+ 'ࠩ' => 230,
278
+ 'ࠪ' => 230,
279
+ 'ࠫ' => 230,
280
+ 'ࠬ' => 230,
281
+ '࠭' => 230,
282
+ '࡙' => 220,
283
+ '࡚' => 220,
284
+ '࡛' => 220,
285
+ '࣓' => 220,
286
+ 'ࣔ' => 230,
287
+ 'ࣕ' => 230,
288
+ 'ࣖ' => 230,
289
+ 'ࣗ' => 230,
290
+ 'ࣘ' => 230,
291
+ 'ࣙ' => 230,
292
+ 'ࣚ' => 230,
293
+ 'ࣛ' => 230,
294
+ 'ࣜ' => 230,
295
+ 'ࣝ' => 230,
296
+ 'ࣞ' => 230,
297
+ 'ࣟ' => 230,
298
+ '࣠' => 230,
299
+ '࣡' => 230,
300
+ 'ࣣ' => 220,
301
+ 'ࣤ' => 230,
302
+ 'ࣥ' => 230,
303
+ 'ࣦ' => 220,
304
+ 'ࣧ' => 230,
305
+ 'ࣨ' => 230,
306
+ 'ࣩ' => 220,
307
+ '࣪' => 230,
308
+ '࣫' => 230,
309
+ '࣬' => 230,
310
+ '࣭' => 220,
311
+ '࣮' => 220,
312
+ '࣯' => 220,
313
+ 'ࣰ' => 27,
314
+ 'ࣱ' => 28,
315
+ 'ࣲ' => 29,
316
+ 'ࣳ' => 230,
317
+ 'ࣴ' => 230,
318
+ 'ࣵ' => 230,
319
+ 'ࣶ' => 220,
320
+ 'ࣷ' => 230,
321
+ 'ࣸ' => 230,
322
+ 'ࣹ' => 220,
323
+ 'ࣺ' => 220,
324
+ 'ࣻ' => 230,
325
+ 'ࣼ' => 230,
326
+ 'ࣽ' => 230,
327
+ 'ࣾ' => 230,
328
+ 'ࣿ' => 230,
329
+ '़' => 7,
330
+ '्' => 9,
331
+ '॑' => 230,
332
+ '॒' => 220,
333
+ '॓' => 230,
334
+ '॔' => 230,
335
+ '়' => 7,
336
+ '্' => 9,
337
+ '৾' => 230,
338
+ '਼' => 7,
339
+ '੍' => 9,
340
+ '઼' => 7,
341
+ '્' => 9,
342
+ '଼' => 7,
343
+ '୍' => 9,
344
+ '்' => 9,
345
+ '్' => 9,
346
+ 'ౕ' => 84,
347
+ 'ౖ' => 91,
348
+ '಼' => 7,
349
+ '್' => 9,
350
+ '഻' => 9,
351
+ '഼' => 9,
352
+ '്' => 9,
353
+ '්' => 9,
354
+ 'ุ' => 103,
355
+ 'ู' => 103,
356
+ 'ฺ' => 9,
357
+ '่' => 107,
358
+ '้' => 107,
359
+ '๊' => 107,
360
+ '๋' => 107,
361
+ 'ຸ' => 118,
362
+ 'ູ' => 118,
363
+ '຺' => 9,
364
+ '່' => 122,
365
+ '້' => 122,
366
+ '໊' => 122,
367
+ '໋' => 122,
368
+ '༘' => 220,
369
+ '༙' => 220,
370
+ '༵' => 220,
371
+ '༷' => 220,
372
+ '༹' => 216,
373
+ 'ཱ' => 129,
374
+ 'ི' => 130,
375
+ 'ུ' => 132,
376
+ 'ེ' => 130,
377
+ 'ཻ' => 130,
378
+ 'ོ' => 130,
379
+ 'ཽ' => 130,
380
+ 'ྀ' => 130,
381
+ 'ྂ' => 230,
382
+ 'ྃ' => 230,
383
+ '྄' => 9,
384
+ '྆' => 230,
385
+ '྇' => 230,
386
+ '࿆' => 220,
387
+ '့' => 7,
388
+ '္' => 9,
389
+ '်' => 9,
390
+ 'ႍ' => 220,
391
+ '፝' => 230,
392
+ '፞' => 230,
393
+ '፟' => 230,
394
+ '᜔' => 9,
395
+ '᜴' => 9,
396
+ '្' => 9,
397
+ '៝' => 230,
398
+ 'ᢩ' => 228,
399
+ '᤹' => 222,
400
+ '᤺' => 230,
401
+ '᤻' => 220,
402
+ 'ᨗ' => 230,
403
+ 'ᨘ' => 220,
404
+ '᩠' => 9,
405
+ '᩵' => 230,
406
+ '᩶' => 230,
407
+ '᩷' => 230,
408
+ '᩸' => 230,
409
+ '᩹' => 230,
410
+ '᩺' => 230,
411
+ '᩻' => 230,
412
+ '᩼' => 230,
413
+ '᩿' => 220,
414
+ '᪰' => 230,
415
+ '᪱' => 230,
416
+ '᪲' => 230,
417
+ '᪳' => 230,
418
+ '᪴' => 230,
419
+ '᪵' => 220,
420
+ '᪶' => 220,
421
+ '᪷' => 220,
422
+ '᪸' => 220,
423
+ '᪹' => 220,
424
+ '᪺' => 220,
425
+ '᪻' => 230,
426
+ '᪼' => 230,
427
+ '᪽' => 220,
428
+ 'ᪿ' => 220,
429
+ 'ᫀ' => 220,
430
+ '᬴' => 7,
431
+ '᭄' => 9,
432
+ '᭫' => 230,
433
+ '᭬' => 220,
434
+ '᭭' => 230,
435
+ '᭮' => 230,
436
+ '᭯' => 230,
437
+ '᭰' => 230,
438
+ '᭱' => 230,
439
+ '᭲' => 230,
440
+ '᭳' => 230,
441
+ '᮪' => 9,
442
+ '᮫' => 9,
443
+ '᯦' => 7,
444
+ '᯲' => 9,
445
+ '᯳' => 9,
446
+ '᰷' => 7,
447
+ '᳐' => 230,
448
+ '᳑' => 230,
449
+ '᳒' => 230,
450
+ '᳔' => 1,
451
+ '᳕' => 220,
452
+ '᳖' => 220,
453
+ '᳗' => 220,
454
+ '᳘' => 220,
455
+ '᳙' => 220,
456
+ '᳚' => 230,
457
+ '᳛' => 230,
458
+ '᳜' => 220,
459
+ '᳝' => 220,
460
+ '᳞' => 220,
461
+ '᳟' => 220,
462
+ '᳠' => 230,
463
+ '᳢' => 1,
464
+ '᳣' => 1,
465
+ '᳤' => 1,
466
+ '᳥' => 1,
467
+ '᳦' => 1,
468
+ '᳧' => 1,
469
+ '᳨' => 1,
470
+ '᳭' => 220,
471
+ '᳴' => 230,
472
+ '᳸' => 230,
473
+ '᳹' => 230,
474
+ '᷀' => 230,
475
+ '᷁' => 230,
476
+ '᷂' => 220,
477
+ '᷃' => 230,
478
+ '᷄' => 230,
479
+ '᷅' => 230,
480
+ '᷆' => 230,
481
+ '᷇' => 230,
482
+ '᷈' => 230,
483
+ '᷉' => 230,
484
+ '᷊' => 220,
485
+ '᷋' => 230,
486
+ '᷌' => 230,
487
+ '᷍' => 234,
488
+ '᷎' => 214,
489
+ '᷏' => 220,
490
+ '᷐' => 202,
491
+ '᷑' => 230,
492
+ '᷒' => 230,
493
+ 'ᷓ' => 230,
494
+ 'ᷔ' => 230,
495
+ 'ᷕ' => 230,
496
+ 'ᷖ' => 230,
497
+ 'ᷗ' => 230,
498
+ 'ᷘ' => 230,
499
+ 'ᷙ' => 230,
500
+ 'ᷚ' => 230,
501
+ 'ᷛ' => 230,
502
+ 'ᷜ' => 230,
503
+ 'ᷝ' => 230,
504
+ 'ᷞ' => 230,
505
+ 'ᷟ' => 230,
506
+ 'ᷠ' => 230,
507
+ 'ᷡ' => 230,
508
+ 'ᷢ' => 230,
509
+ 'ᷣ' => 230,
510
+ 'ᷤ' => 230,
511
+ 'ᷥ' => 230,
512
+ 'ᷦ' => 230,
513
+ 'ᷧ' => 230,
514
+ 'ᷨ' => 230,
515
+ 'ᷩ' => 230,
516
+ 'ᷪ' => 230,
517
+ 'ᷫ' => 230,
518
+ 'ᷬ' => 230,
519
+ 'ᷭ' => 230,
520
+ 'ᷮ' => 230,
521
+ 'ᷯ' => 230,
522
+ 'ᷰ' => 230,
523
+ 'ᷱ' => 230,
524
+ 'ᷲ' => 230,
525
+ 'ᷳ' => 230,
526
+ 'ᷴ' => 230,
527
+ '᷵' => 230,
528
+ '᷶' => 232,
529
+ '᷷' => 228,
530
+ '᷸' => 228,
531
+ '᷹' => 220,
532
+ '᷻' => 230,
533
+ '᷼' => 233,
534
+ '᷽' => 220,
535
+ '᷾' => 230,
536
+ '᷿' => 220,
537
+ '⃐' => 230,
538
+ '⃑' => 230,
539
+ '⃒' => 1,
540
+ '⃓' => 1,
541
+ '⃔' => 230,
542
+ '⃕' => 230,
543
+ '⃖' => 230,
544
+ '⃗' => 230,
545
+ '⃘' => 1,
546
+ '⃙' => 1,
547
+ '⃚' => 1,
548
+ '⃛' => 230,
549
+ '⃜' => 230,
550
+ '⃡' => 230,
551
+ '⃥' => 1,
552
+ '⃦' => 1,
553
+ '⃧' => 230,
554
+ '⃨' => 220,
555
+ '⃩' => 230,
556
+ '⃪' => 1,
557
+ '⃫' => 1,
558
+ '⃬' => 220,
559
+ '⃭' => 220,
560
+ '⃮' => 220,
561
+ '⃯' => 220,
562
+ '⃰' => 230,
563
+ '⳯' => 230,
564
+ '⳰' => 230,
565
+ '⳱' => 230,
566
+ '⵿' => 9,
567
+ 'ⷠ' => 230,
568
+ 'ⷡ' => 230,
569
+ 'ⷢ' => 230,
570
+ 'ⷣ' => 230,
571
+ 'ⷤ' => 230,
572
+ 'ⷥ' => 230,
573
+ 'ⷦ' => 230,
574
+ 'ⷧ' => 230,
575
+ 'ⷨ' => 230,
576
+ 'ⷩ' => 230,
577
+ 'ⷪ' => 230,
578
+ 'ⷫ' => 230,
579
+ 'ⷬ' => 230,
580
+ 'ⷭ' => 230,
581
+ 'ⷮ' => 230,
582
+ 'ⷯ' => 230,
583
+ 'ⷰ' => 230,
584
+ 'ⷱ' => 230,
585
+ 'ⷲ' => 230,
586
+ 'ⷳ' => 230,
587
+ 'ⷴ' => 230,
588
+ 'ⷵ' => 230,
589
+ 'ⷶ' => 230,
590
+ 'ⷷ' => 230,
591
+ 'ⷸ' => 230,
592
+ 'ⷹ' => 230,
593
+ 'ⷺ' => 230,
594
+ 'ⷻ' => 230,
595
+ 'ⷼ' => 230,
596
+ 'ⷽ' => 230,
597
+ 'ⷾ' => 230,
598
+ 'ⷿ' => 230,
599
+ '〪' => 218,
600
+ '〫' => 228,
601
+ '〬' => 232,
602
+ '〭' => 222,
603
+ '〮' => 224,
604
+ '〯' => 224,
605
+ '゙' => 8,
606
+ '゚' => 8,
607
+ '꙯' => 230,
608
+ 'ꙴ' => 230,
609
+ 'ꙵ' => 230,
610
+ 'ꙶ' => 230,
611
+ 'ꙷ' => 230,
612
+ 'ꙸ' => 230,
613
+ 'ꙹ' => 230,
614
+ 'ꙺ' => 230,
615
+ 'ꙻ' => 230,
616
+ '꙼' => 230,
617
+ '꙽' => 230,
618
+ 'ꚞ' => 230,
619
+ 'ꚟ' => 230,
620
+ '꛰' => 230,
621
+ '꛱' => 230,
622
+ '꠆' => 9,
623
+ '꠬' => 9,
624
+ '꣄' => 9,
625
+ '꣠' => 230,
626
+ '꣡' => 230,
627
+ '꣢' => 230,
628
+ '꣣' => 230,
629
+ '꣤' => 230,
630
+ '꣥' => 230,
631
+ '꣦' => 230,
632
+ '꣧' => 230,
633
+ '꣨' => 230,
634
+ '꣩' => 230,
635
+ '꣪' => 230,
636
+ '꣫' => 230,
637
+ '꣬' => 230,
638
+ '꣭' => 230,
639
+ '꣮' => 230,
640
+ '꣯' => 230,
641
+ '꣰' => 230,
642
+ '꣱' => 230,
643
+ '꤫' => 220,
644
+ '꤬' => 220,
645
+ '꤭' => 220,
646
+ '꥓' => 9,
647
+ '꦳' => 7,
648
+ '꧀' => 9,
649
+ 'ꪰ' => 230,
650
+ 'ꪲ' => 230,
651
+ 'ꪳ' => 230,
652
+ 'ꪴ' => 220,
653
+ 'ꪷ' => 230,
654
+ 'ꪸ' => 230,
655
+ 'ꪾ' => 230,
656
+ '꪿' => 230,
657
+ '꫁' => 230,
658
+ '꫶' => 9,
659
+ '꯭' => 9,
660
+ 'ﬞ' => 26,
661
+ '︠' => 230,
662
+ '︡' => 230,
663
+ '︢' => 230,
664
+ '︣' => 230,
665
+ '︤' => 230,
666
+ '︥' => 230,
667
+ '︦' => 230,
668
+ '︧' => 220,
669
+ '︨' => 220,
670
+ '︩' => 220,
671
+ '︪' => 220,
672
+ '︫' => 220,
673
+ '︬' => 220,
674
+ '︭' => 220,
675
+ '︮' => 230,
676
+ '︯' => 230,
677
+ '𐇽' => 220,
678
+ '𐋠' => 220,
679
+ '𐍶' => 230,
680
+ '𐍷' => 230,
681
+ '𐍸' => 230,
682
+ '𐍹' => 230,
683
+ '𐍺' => 230,
684
+ '𐨍' => 220,
685
+ '𐨏' => 230,
686
+ '𐨸' => 230,
687
+ '𐨹' => 1,
688
+ '𐨺' => 220,
689
+ '𐨿' => 9,
690
+ '𐫥' => 230,
691
+ '𐫦' => 220,
692
+ '𐴤' => 230,
693
+ '𐴥' => 230,
694
+ '𐴦' => 230,
695
+ '𐴧' => 230,
696
+ '𐺫' => 230,
697
+ '𐺬' => 230,
698
+ '𐽆' => 220,
699
+ '𐽇' => 220,
700
+ '𐽈' => 230,
701
+ '𐽉' => 230,
702
+ '𐽊' => 230,
703
+ '𐽋' => 220,
704
+ '𐽌' => 230,
705
+ '𐽍' => 220,
706
+ '𐽎' => 220,
707
+ '𐽏' => 220,
708
+ '𐽐' => 220,
709
+ '𑁆' => 9,
710
+ '𑁿' => 9,
711
+ '𑂹' => 9,
712
+ '𑂺' => 7,
713
+ '𑄀' => 230,
714
+ '𑄁' => 230,
715
+ '𑄂' => 230,
716
+ '𑄳' => 9,
717
+ '𑄴' => 9,
718
+ '𑅳' => 7,
719
+ '𑇀' => 9,
720
+ '𑇊' => 7,
721
+ '𑈵' => 9,
722
+ '𑈶' => 7,
723
+ '𑋩' => 7,
724
+ '𑋪' => 9,
725
+ '𑌻' => 7,
726
+ '𑌼' => 7,
727
+ '𑍍' => 9,
728
+ '𑍦' => 230,
729
+ '𑍧' => 230,
730
+ '𑍨' => 230,
731
+ '𑍩' => 230,
732
+ '𑍪' => 230,
733
+ '𑍫' => 230,
734
+ '𑍬' => 230,
735
+ '𑍰' => 230,
736
+ '𑍱' => 230,
737
+ '𑍲' => 230,
738
+ '𑍳' => 230,
739
+ '𑍴' => 230,
740
+ '𑑂' => 9,
741
+ '𑑆' => 7,
742
+ '𑑞' => 230,
743
+ '𑓂' => 9,
744
+ '𑓃' => 7,
745
+ '𑖿' => 9,
746
+ '𑗀' => 7,
747
+ '𑘿' => 9,
748
+ '𑚶' => 9,
749
+ '𑚷' => 7,
750
+ '𑜫' => 9,
751
+ '𑠹' => 9,
752
+ '𑠺' => 7,
753
+ '𑤽' => 9,
754
+ '𑤾' => 9,
755
+ '𑥃' => 7,
756
+ '𑧠' => 9,
757
+ '𑨴' => 9,
758
+ '𑩇' => 9,
759
+ '𑪙' => 9,
760
+ '𑰿' => 9,
761
+ '𑵂' => 7,
762
+ '𑵄' => 9,
763
+ '𑵅' => 9,
764
+ '𑶗' => 9,
765
+ '𖫰' => 1,
766
+ '𖫱' => 1,
767
+ '𖫲' => 1,
768
+ '𖫳' => 1,
769
+ '𖫴' => 1,
770
+ '𖬰' => 230,
771
+ '𖬱' => 230,
772
+ '𖬲' => 230,
773
+ '𖬳' => 230,
774
+ '𖬴' => 230,
775
+ '𖬵' => 230,
776
+ '𖬶' => 230,
777
+ '𖿰' => 6,
778
+ '𖿱' => 6,
779
+ '𛲞' => 1,
780
+ '𝅥' => 216,
781
+ '𝅦' => 216,
782
+ '𝅧' => 1,
783
+ '𝅨' => 1,
784
+ '𝅩' => 1,
785
+ '𝅭' => 226,
786
+ '𝅮' => 216,
787
+ '𝅯' => 216,
788
+ '𝅰' => 216,
789
+ '𝅱' => 216,
790
+ '𝅲' => 216,
791
+ '𝅻' => 220,
792
+ '𝅼' => 220,
793
+ '𝅽' => 220,
794
+ '𝅾' => 220,
795
+ '𝅿' => 220,
796
+ '𝆀' => 220,
797
+ '𝆁' => 220,
798
+ '𝆂' => 220,
799
+ '𝆅' => 230,
800
+ '𝆆' => 230,
801
+ '𝆇' => 230,
802
+ '𝆈' => 230,
803
+ '𝆉' => 230,
804
+ '𝆊' => 220,
805
+ '𝆋' => 220,
806
+ '𝆪' => 230,
807
+ '𝆫' => 230,
808
+ '𝆬' => 230,
809
+ '𝆭' => 230,
810
+ '𝉂' => 230,
811
+ '𝉃' => 230,
812
+ '𝉄' => 230,
813
+ '𞀀' => 230,
814
+ '𞀁' => 230,
815
+ '𞀂' => 230,
816
+ '𞀃' => 230,
817
+ '𞀄' => 230,
818
+ '𞀅' => 230,
819
+ '𞀆' => 230,
820
+ '𞀈' => 230,
821
+ '𞀉' => 230,
822
+ '𞀊' => 230,
823
+ '𞀋' => 230,
824
+ '𞀌' => 230,
825
+ '𞀍' => 230,
826
+ '𞀎' => 230,
827
+ '𞀏' => 230,
828
+ '𞀐' => 230,
829
+ '𞀑' => 230,
830
+ '𞀒' => 230,
831
+ '𞀓' => 230,
832
+ '𞀔' => 230,
833
+ '𞀕' => 230,
834
+ '𞀖' => 230,
835
+ '𞀗' => 230,
836
+ '𞀘' => 230,
837
+ '𞀛' => 230,
838
+ '𞀜' => 230,
839
+ '𞀝' => 230,
840
+ '𞀞' => 230,
841
+ '𞀟' => 230,
842
+ '𞀠' => 230,
843
+ '𞀡' => 230,
844
+ '𞀣' => 230,
845
+ '𞀤' => 230,
846
+ '𞀦' => 230,
847
+ '𞀧' => 230,
848
+ '𞀨' => 230,
849
+ '𞀩' => 230,
850
+ '𞀪' => 230,
851
+ '𞄰' => 230,
852
+ '𞄱' => 230,
853
+ '𞄲' => 230,
854
+ '𞄳' => 230,
855
+ '𞄴' => 230,
856
+ '𞄵' => 230,
857
+ '𞄶' => 230,
858
+ '𞋬' => 230,
859
+ '𞋭' => 230,
860
+ '𞋮' => 230,
861
+ '𞋯' => 230,
862
+ '𞣐' => 220,
863
+ '𞣑' => 220,
864
+ '𞣒' => 220,
865
+ '𞣓' => 220,
866
+ '𞣔' => 220,
867
+ '𞣕' => 220,
868
+ '𞣖' => 220,
869
+ '𞥄' => 230,
870
+ '𞥅' => 230,
871
+ '𞥆' => 230,
872
+ '𞥇' => 230,
873
+ '𞥈' => 230,
874
+ '𞥉' => 230,
875
+ '𞥊' => 7,
876
+ );
vendor/browscap-php/symfony/polyfill-intl-normalizer/Resources/unidata/compatibilityDecomposition.php ADDED
@@ -0,0 +1,3695 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ return array (
4
+ ' ' => ' ',
5
+ '¨' => ' ̈',
6
+ 'ª' => 'a',
7
+ '¯' => ' ̄',
8
+ '²' => '2',
9
+ '³' => '3',
10
+ '´' => ' ́',
11
+ 'µ' => 'μ',
12
+ '¸' => ' ̧',
13
+ '¹' => '1',
14
+ 'º' => 'o',
15
+ '¼' => '1⁄4',
16
+ '½' => '1⁄2',
17
+ '¾' => '3⁄4',
18
+ 'IJ' => 'IJ',
19
+ 'ij' => 'ij',
20
+ 'Ŀ' => 'L·',
21
+ 'ŀ' => 'l·',
22
+ 'ʼn' => 'ʼn',
23
+ 'ſ' => 's',
24
+ 'DŽ' => 'DŽ',
25
+ 'Dž' => 'Dž',
26
+ 'dž' => 'dž',
27
+ 'LJ' => 'LJ',
28
+ 'Lj' => 'Lj',
29
+ 'lj' => 'lj',
30
+ 'NJ' => 'NJ',
31
+ 'Nj' => 'Nj',
32
+ 'nj' => 'nj',
33
+ 'DZ' => 'DZ',
34
+ 'Dz' => 'Dz',
35
+ 'dz' => 'dz',
36
+ 'ʰ' => 'h',
37
+ 'ʱ' => 'ɦ',
38
+ 'ʲ' => 'j',
39
+ 'ʳ' => 'r',
40
+ 'ʴ' => 'ɹ',
41
+ 'ʵ' => 'ɻ',
42
+ 'ʶ' => 'ʁ',
43
+ 'ʷ' => 'w',
44
+ 'ʸ' => 'y',
45
+ '˘' => ' ̆',
46
+ '˙' => ' ̇',
47
+ '˚' => ' ̊',
48
+ '˛' => ' ̨',
49
+ '˜' => ' ̃',
50
+ '˝' => ' ̋',
51
+ 'ˠ' => 'ɣ',
52
+ 'ˡ' => 'l',
53
+ 'ˢ' => 's',
54
+ 'ˣ' => 'x',
55
+ 'ˤ' => 'ʕ',
56
+ 'ͺ' => ' ͅ',
57
+ '΄' => ' ́',
58
+ '΅' => ' ̈́',
59
+ 'ϐ' => 'β',
60
+ 'ϑ' => 'θ',
61
+ 'ϒ' => 'Υ',
62
+ 'ϓ' => 'Ύ',
63
+ 'ϔ' => 'Ϋ',
64
+ 'ϕ' => 'φ',
65
+ 'ϖ' => 'π',
66
+ 'ϰ' => 'κ',
67
+ 'ϱ' => 'ρ',
68
+ 'ϲ' => 'ς',
69
+ 'ϴ' => 'Θ',
70
+ 'ϵ' => 'ε',
71
+ 'Ϲ' => 'Σ',
72
+ 'և' => 'եւ',
73
+ 'ٵ' => 'اٴ',
74
+ 'ٶ' => 'وٴ',
75
+ 'ٷ' => 'ۇٴ',
76
+ 'ٸ' => 'يٴ',
77
+ 'ำ' => 'ํา',
78
+ 'ຳ' => 'ໍາ',
79
+ 'ໜ' => 'ຫນ',
80
+ 'ໝ' => 'ຫມ',
81
+ '༌' => '་',
82
+ 'ཷ' => 'ྲཱྀ',
83
+ 'ཹ' => 'ླཱྀ',
84
+ 'ჼ' => 'ნ',
85
+ 'ᴬ' => 'A',
86
+ 'ᴭ' => 'Æ',
87
+ 'ᴮ' => 'B',
88
+ 'ᴰ' => 'D',
89
+ 'ᴱ' => 'E',
90
+ 'ᴲ' => 'Ǝ',
91
+ 'ᴳ' => 'G',
92
+ 'ᴴ' => 'H',
93
+ 'ᴵ' => 'I',
94
+ 'ᴶ' => 'J',
95
+ 'ᴷ' => 'K',
96
+ 'ᴸ' => 'L',
97
+ 'ᴹ' => 'M',
98
+ 'ᴺ' => 'N',
99
+ 'ᴼ' => 'O',
100
+ 'ᴽ' => 'Ȣ',
101
+ 'ᴾ' => 'P',
102
+ 'ᴿ' => 'R',
103
+ 'ᵀ' => 'T',
104
+ 'ᵁ' => 'U',
105
+ 'ᵂ' => 'W',
106
+ 'ᵃ' => 'a',
107
+ 'ᵄ' => 'ɐ',
108
+ 'ᵅ' => 'ɑ',
109
+ 'ᵆ' => 'ᴂ',
110
+ 'ᵇ' => 'b',
111
+ 'ᵈ' => 'd',
112
+ 'ᵉ' => 'e',
113
+ 'ᵊ' => 'ə',
114
+ 'ᵋ' => 'ɛ',
115
+ 'ᵌ' => 'ɜ',
116
+ 'ᵍ' => 'g',
117
+ 'ᵏ' => 'k',
118
+ 'ᵐ' => 'm',
119
+ 'ᵑ' => 'ŋ',
120
+ 'ᵒ' => 'o',
121
+ 'ᵓ' => 'ɔ',
122
+ 'ᵔ' => 'ᴖ',
123
+ 'ᵕ' => 'ᴗ',
124
+ 'ᵖ' => 'p',
125
+ 'ᵗ' => 't',
126
+ 'ᵘ' => 'u',
127
+ 'ᵙ' => 'ᴝ',
128
+ 'ᵚ' => 'ɯ',
129
+ 'ᵛ' => 'v',
130
+ 'ᵜ' => 'ᴥ',
131
+ 'ᵝ' => 'β',
132
+ 'ᵞ' => 'γ',
133
+ 'ᵟ' => 'δ',
134
+ 'ᵠ' => 'φ',
135
+ 'ᵡ' => 'χ',
136
+ 'ᵢ' => 'i',
137
+ 'ᵣ' => 'r',
138
+ 'ᵤ' => 'u',
139
+ 'ᵥ' => 'v',
140
+ 'ᵦ' => 'β',
141
+ 'ᵧ' => 'γ',
142
+ 'ᵨ' => 'ρ',
143
+ 'ᵩ' => 'φ',
144
+ 'ᵪ' => 'χ',
145
+ 'ᵸ' => 'н',
146
+ 'ᶛ' => 'ɒ',
147
+ 'ᶜ' => 'c',
148
+ 'ᶝ' => 'ɕ',
149
+ 'ᶞ' => 'ð',
150
+ 'ᶟ' => 'ɜ',
151
+ 'ᶠ' => 'f',
152
+ 'ᶡ' => 'ɟ',
153
+ 'ᶢ' => 'ɡ',
154
+ 'ᶣ' => 'ɥ',
155
+ 'ᶤ' => 'ɨ',
156
+ 'ᶥ' => 'ɩ',
157
+ 'ᶦ' => 'ɪ',
158
+ 'ᶧ' => 'ᵻ',
159
+ 'ᶨ' => 'ʝ',
160
+ 'ᶩ' => 'ɭ',
161
+ 'ᶪ' => 'ᶅ',
162
+ 'ᶫ' => 'ʟ',
163
+ 'ᶬ' => 'ɱ',
164
+ 'ᶭ' => 'ɰ',
165
+ 'ᶮ' => 'ɲ',
166
+ 'ᶯ' => 'ɳ',
167
+ 'ᶰ' => 'ɴ',
168
+ 'ᶱ' => 'ɵ',
169
+ 'ᶲ' => 'ɸ',
170
+ 'ᶳ' => 'ʂ',
171
+ 'ᶴ' => 'ʃ',
172
+ 'ᶵ' => 'ƫ',
173
+ 'ᶶ' => 'ʉ',
174
+ 'ᶷ' => 'ʊ',
175
+ 'ᶸ' => 'ᴜ',
176
+ 'ᶹ' => 'ʋ',
177
+ 'ᶺ' => 'ʌ',
178
+ 'ᶻ' => 'z',
179
+ 'ᶼ' => 'ʐ',
180
+ 'ᶽ' => 'ʑ',
181
+ 'ᶾ' => 'ʒ',
182
+ 'ᶿ' => 'θ',
183
+ 'ẚ' => 'aʾ',
184
+ 'ẛ' => 'ṡ',
185
+ '᾽' => ' ̓',
186
+ '᾿' => ' ̓',
187
+ '῀' => ' ͂',
188
+ '῁' => ' ̈͂',
189
+ '῍' => ' ̓̀',
190
+ '῎' => ' ̓́',
191
+ '῏' => ' ̓͂',
192
+ '῝' => ' ̔̀',
193
+ '῞' => ' ̔́',
194
+ '῟' => ' ̔͂',
195
+ '῭' => ' ̈̀',
196
+ '΅' => ' ̈́',
197
+ '´' => ' ́',
198
+ '῾' => ' ̔',
199
+ ' ' => ' ',
200
+ ' ' => ' ',
201
+ ' ' => ' ',
202
+ ' ' => ' ',
203
+ ' ' => ' ',
204
+ ' ' => ' ',
205
+ ' ' => ' ',
206
+ ' ' => ' ',
207
+ ' ' => ' ',
208
+ ' ' => ' ',
209
+ ' ' => ' ',
210
+ '‑' => '‐',
211
+ '‗' => ' ̳',
212
+ '․' => '.',
213
+ '‥' => '..',
214
+ '…' => '...',
215
+ ' ' => ' ',
216
+ '″' => '′′',
217
+ '‴' => '′′′',
218
+ '‶' => '‵‵',
219
+ '‷' => '‵‵‵',
220
+ '‼' => '!!',
221
+ '‾' => ' ̅',
222
+ '⁇' => '??',
223
+ '⁈' => '?!',
224
+ '⁉' => '!?',
225
+ '⁗' => '′′′′',
226
+ ' ' => ' ',
227
+ '⁰' => '0',
228
+ 'ⁱ' => 'i',
229
+ '⁴' => '4',
230
+ '⁵' => '5',
231
+ '⁶' => '6',
232
+ '⁷' => '7',
233
+ '⁸' => '8',
234
+ '⁹' => '9',
235
+ '⁺' => '+',
236
+ '⁻' => '−',
237
+ '⁼' => '=',
238
+ '⁽' => '(',
239
+ '⁾' => ')',
240
+ 'ⁿ' => 'n',
241
+ '₀' => '0',
242
+ '₁' => '1',
243
+ '₂' => '2',
244
+ '₃' => '3',
245
+ '₄' => '4',
246
+ '₅' => '5',
247
+ '₆' => '6',
248
+ '₇' => '7',
249
+ '₈' => '8',
250
+ '₉' => '9',
251
+ '₊' => '+',
252
+ '₋' => '−',
253
+ '₌' => '=',
254
+ '₍' => '(',
255
+ '₎' => ')',
256
+ 'ₐ' => 'a',
257
+ 'ₑ' => 'e',
258
+ 'ₒ' => 'o',
259
+ 'ₓ' => 'x',
260
+ 'ₔ' => 'ə',
261
+ 'ₕ' => 'h',
262
+ 'ₖ' => 'k',
263
+ 'ₗ' => 'l',
264
+ 'ₘ' => 'm',
265
+ 'ₙ' => 'n',
266
+ 'ₚ' => 'p',
267
+ 'ₛ' => 's',
268
+ 'ₜ' => 't',
269
+ '₨' => 'Rs',
270
+ '℀' => 'a/c',
271
+ '℁' => 'a/s',
272
+ 'ℂ' => 'C',
273
+ '℃' => '°C',
274
+ '℅' => 'c/o',
275
+ '℆' => 'c/u',
276
+ 'ℇ' => 'Ɛ',
277
+ '℉' => '°F',
278
+ 'ℊ' => 'g',
279
+ 'ℋ' => 'H',
280
+ 'ℌ' => 'H',
281
+ 'ℍ' => 'H',
282
+ 'ℎ' => 'h',
283
+ 'ℏ' => 'ħ',
284
+ 'ℐ' => 'I',
285
+ 'ℑ' => 'I',
286
+ 'ℒ' => 'L',
287
+ 'ℓ' => 'l',
288
+ 'ℕ' => 'N',
289
+ '№' => 'No',
290
+ 'ℙ' => 'P',
291
+ 'ℚ' => 'Q',
292
+ 'ℛ' => 'R',
293
+ 'ℜ' => 'R',
294
+ 'ℝ' => 'R',
295
+ '℠' => 'SM',
296
+ '℡' => 'TEL',
297
+ '™' => 'TM',
298
+ 'ℤ' => 'Z',
299
+ 'ℨ' => 'Z',
300
+ 'ℬ' => 'B',
301
+ 'ℭ' => 'C',
302
+ 'ℯ' => 'e',
303
+ 'ℰ' => 'E',
304
+ 'ℱ' => 'F',
305
+ 'ℳ' => 'M',
306
+ 'ℴ' => 'o',
307
+ 'ℵ' => 'א',
308
+ 'ℶ' => 'ב',
309
+ 'ℷ' => 'ג',
310
+ 'ℸ' => 'ד',
311
+ 'ℹ' => 'i',
312
+ '℻' => 'FAX',
313
+ 'ℼ' => 'π',
314
+ 'ℽ' => 'γ',
315
+ 'ℾ' => 'Γ',
316
+ 'ℿ' => 'Π',
317
+ '⅀' => '∑',
318
+ 'ⅅ' => 'D',
319
+ 'ⅆ' => 'd',
320
+ 'ⅇ' => 'e',
321
+ 'ⅈ' => 'i',
322
+ 'ⅉ' => 'j',
323
+ '⅐' => '1⁄7',
324
+ '⅑' => '1⁄9',
325
+ '⅒' => '1⁄10',
326
+ '⅓' => '1⁄3',
327
+ '⅔' => '2⁄3',
328
+ '⅕' => '1⁄5',
329
+ '⅖' => '2⁄5',
330
+ '⅗' => '3⁄5',
331
+ '⅘' => '4⁄5',
332
+ '⅙' => '1⁄6',
333
+ '⅚' => '5⁄6',
334
+ '⅛' => '1⁄8',
335
+ '⅜' => '3⁄8',
336
+ '⅝' => '5⁄8',
337
+ '⅞' => '7⁄8',
338
+ '⅟' => '1⁄',
339
+ 'Ⅰ' => 'I',
340
+ 'Ⅱ' => 'II',
341
+ 'Ⅲ' => 'III',
342
+ 'Ⅳ' => 'IV',
343
+ 'Ⅴ' => 'V',
344
+ 'Ⅵ' => 'VI',
345
+ 'Ⅶ' => 'VII',
346
+ 'Ⅷ' => 'VIII',
347
+ 'Ⅸ' => 'IX',
348
+ 'Ⅹ' => 'X',
349
+ 'Ⅺ' => 'XI',
350
+ 'Ⅻ' => 'XII',
351
+ 'Ⅼ' => 'L',
352
+ 'Ⅽ' => 'C',
353
+ 'Ⅾ' => 'D',
354
+ 'Ⅿ' => 'M',
355
+ 'ⅰ' => 'i',
356
+ 'ⅱ' => 'ii',
357
+ 'ⅲ' => 'iii',
358
+ 'ⅳ' => 'iv',
359
+ 'ⅴ' => 'v',
360
+ 'ⅵ' => 'vi',
361
+ 'ⅶ' => 'vii',
362
+ 'ⅷ' => 'viii',
363
+ 'ⅸ' => 'ix',
364
+ 'ⅹ' => 'x',
365
+ 'ⅺ' => 'xi',
366
+ 'ⅻ' => 'xii',
367
+ 'ⅼ' => 'l',
368
+ 'ⅽ' => 'c',
369
+ 'ⅾ' => 'd',
370
+ 'ⅿ' => 'm',
371
+ '↉' => '0⁄3',
372
+ '∬' => '∫∫',
373
+ '∭' => '∫∫∫',
374
+ '∯' => '∮∮',
375
+ '∰' => '∮∮∮',
376
+ '①' => '1',
377
+ '②' => '2',
378
+ '③' => '3',
379
+ '④' => '4',
380
+ '⑤' => '5',
381
+ '⑥' => '6',
382
+ '⑦' => '7',
383
+ '⑧' => '8',
384
+ '⑨' => '9',
385
+ '⑩' => '10',
386
+ '⑪' => '11',
387
+ '⑫' => '12',
388
+ '⑬' => '13',
389
+ '⑭' => '14',
390
+ '⑮' => '15',
391
+ '⑯' => '16',
392
+ '⑰' => '17',
393
+ '⑱' => '18',
394
+ '⑲' => '19',
395
+ '⑳' => '20',
396
+ '⑴' => '(1)',
397
+ '⑵' => '(2)',
398
+ '⑶' => '(3)',
399
+ '⑷' => '(4)',
400
+ '⑸' => '(5)',
401
+ '⑹' => '(6)',
402
+ '⑺' => '(7)',
403
+ '⑻' => '(8)',
404
+ '⑼' => '(9)',
405
+ '⑽' => '(10)',
406
+ '⑾' => '(11)',
407
+ '⑿' => '(12)',
408
+ '⒀' => '(13)',
409
+ '⒁' => '(14)',
410
+ '⒂' => '(15)',
411
+ '⒃' => '(16)',
412
+ '⒄' => '(17)',
413
+ '⒅' => '(18)',
414
+ '⒆' => '(19)',
415
+ '⒇' => '(20)',
416
+ '⒈' => '1.',
417
+ '⒉' => '2.',
418
+ '⒊' => '3.',
419
+ '⒋' => '4.',
420
+ '⒌' => '5.',
421
+ '⒍' => '6.',
422
+ '⒎' => '7.',
423
+ '⒏' => '8.',
424
+ '⒐' => '9.',
425
+ '⒑' => '10.',
426
+ '⒒' => '11.',
427
+ '⒓' => '12.',
428
+ '⒔' => '13.',
429
+ '⒕' => '14.',
430
+ '⒖' => '15.',
431
+ '⒗' => '16.',
432
+ '⒘' => '17.',
433
+ '⒙' => '18.',
434
+ '⒚' => '19.',
435
+ '⒛' => '20.',
436
+ '⒜' => '(a)',
437
+ '⒝' => '(b)',
438
+ '⒞' => '(c)',
439
+ '⒟' => '(d)',
440
+ '⒠' => '(e)',
441
+ '⒡' => '(f)',
442
+ '⒢' => '(g)',
443
+ '⒣' => '(h)',
444
+ '⒤' => '(i)',
445
+ '⒥' => '(j)',
446
+ '⒦' => '(k)',
447
+ '⒧' => '(l)',
448
+ '⒨' => '(m)',
449
+ '⒩' => '(n)',
450
+ '⒪' => '(o)',
451
+ '⒫' => '(p)',
452
+ '⒬' => '(q)',
453
+ '⒭' => '(r)',
454
+ '⒮' => '(s)',
455
+ '⒯' => '(t)',
456
+ '⒰' => '(u)',
457
+ '⒱' => '(v)',
458
+ '⒲' => '(w)',
459
+ '⒳' => '(x)',
460
+ '⒴' => '(y)',
461
+ '⒵' => '(z)',
462
+ 'Ⓐ' => 'A',
463
+ 'Ⓑ' => 'B',
464
+ 'Ⓒ' => 'C',
465
+ 'Ⓓ' => 'D',
466
+ 'Ⓔ' => 'E',
467
+ 'Ⓕ' => 'F',
468
+ 'Ⓖ' => 'G',
469
+ 'Ⓗ' => 'H',
470
+ 'Ⓘ' => 'I',
471
+ 'Ⓙ' => 'J',
472
+ 'Ⓚ' => 'K',
473
+ 'Ⓛ' => 'L',
474
+ 'Ⓜ' => 'M',
475
+ 'Ⓝ' => 'N',
476
+ 'Ⓞ' => 'O',
477
+ 'Ⓟ' => 'P',
478
+ 'Ⓠ' => 'Q',
479
+ 'Ⓡ' => 'R',
480
+ 'Ⓢ' => 'S',
481
+ 'Ⓣ' => 'T',
482
+ 'Ⓤ' => 'U',
483
+ 'Ⓥ' => 'V',
484
+ 'Ⓦ' => 'W',
485
+ 'Ⓧ' => 'X',
486
+ 'Ⓨ' => 'Y',
487
+ 'Ⓩ' => 'Z',
488
+ 'ⓐ' => 'a',
489
+ 'ⓑ' => 'b',
490
+ 'ⓒ' => 'c',
491
+ 'ⓓ' => 'd',
492
+ 'ⓔ' => 'e',
493
+ 'ⓕ' => 'f',
494
+ 'ⓖ' => 'g',
495
+ 'ⓗ' => 'h',
496
+ 'ⓘ' => 'i',
497
+ 'ⓙ' => 'j',
498
+ 'ⓚ' => 'k',
499
+ 'ⓛ' => 'l',
500
+ 'ⓜ' => 'm',
501
+ 'ⓝ' => 'n',
502
+ 'ⓞ' => 'o',
503
+ 'ⓟ' => 'p',
504
+ 'ⓠ' => 'q',
505
+ 'ⓡ' => 'r',
506
+ 'ⓢ' => 's',
507
+ 'ⓣ' => 't',
508
+ 'ⓤ' => 'u',
509
+ 'ⓥ' => 'v',
510
+ 'ⓦ' => 'w',
511
+ 'ⓧ' => 'x',
512
+ 'ⓨ' => 'y',
513
+ 'ⓩ' => 'z',
514
+ '⓪' => '0',
515
+ '⨌' => '∫∫∫∫',
516
+ '⩴' => '::=',
517
+ '⩵' => '==',
518
+ '⩶' => '===',
519
+ 'ⱼ' => 'j',
520
+ 'ⱽ' => 'V',
521
+ 'ⵯ' => 'ⵡ',
522
+ '⺟' => '母',
523
+ '⻳' => '龟',
524
+ '⼀' => '一',
525
+ '⼁' => '丨',
526
+ '⼂' => '丶',
527
+ '⼃' => '丿',
528
+ '⼄' => '乙',
529
+ '⼅' => '亅',
530
+ '⼆' => '二',
531
+ '⼇' => '亠',
532
+ '⼈' => '人',
533
+ '⼉' => '儿',
534
+ '⼊' => '入',
535
+ '⼋' => '八',
536
+ '⼌' => '冂',
537
+ '⼍' => '冖',
538
+ '⼎' => '冫',
539
+ '⼏' => '几',
540
+ '⼐' => '凵',
541
+ '⼑' => '刀',
542
+ '⼒' => '力',
543
+ '⼓' => '勹',
544
+ '⼔' => '匕',
545
+ '⼕' => '匚',
546
+ '⼖' => '匸',
547
+ '⼗' => '十',
548
+ '⼘' => '卜',
549
+ '⼙' => '卩',
550
+ '⼚' => '厂',
551
+ '⼛' => '厶',
552
+ '⼜' => '又',
553
+ '⼝' => '口',
554
+ '⼞' => '囗',
555
+ '⼟' => '土',
556
+ '⼠' => '士',
557
+ '⼡' => '夂',
558
+ '⼢' => '夊',
559
+ '⼣' => '夕',
560
+ '⼤' => '大',
561
+ '⼥' => '女',
562
+ '⼦' => '子',
563
+ '⼧' => '宀',
564
+ '⼨' => '寸',
565
+ '⼩' => '小',
566
+ '⼪' => '尢',
567
+ '⼫' => '尸',
568
+ '⼬' => '屮',
569
+ '⼭' => '山',
570
+ '⼮' => '巛',
571
+ '⼯' => '工',
572
+ '⼰' => '己',
573
+ '⼱' => '巾',
574
+ '⼲' => '干',
575
+ '⼳' => '幺',
576
+ '⼴' => '广',
577
+ '⼵' => '廴',
578
+ '⼶' => '廾',
579
+ '⼷' => '弋',
580
+ '⼸' => '弓',
581
+ '⼹' => '彐',
582
+ '⼺' => '彡',
583
+ '⼻' => '彳',
584
+ '⼼' => '心',
585
+ '⼽' => '戈',
586
+ '⼾' => '戶',
587
+ '⼿' => '手',
588
+ '⽀' => '支',
589
+ '⽁' => '攴',
590
+ '⽂' => '文',
591
+ '⽃' => '斗',
592
+ '⽄' => '斤',
593
+ '⽅' => '方',
594
+ '⽆' => '无',
595
+ '⽇' => '日',
596
+ '⽈' => '曰',
597
+ '⽉' => '月',
598
+ '⽊' => '木',
599
+ '⽋' => '欠',
600
+ '⽌' => '止',
601
+ '⽍' => '歹',
602
+ '⽎' => '殳',
603
+ '⽏' => '毋',
604
+ '⽐' => '比',
605
+ '⽑' => '毛',
606
+ '⽒' => '氏',
607
+ '⽓' => '气',
608
+ '⽔' => '水',
609
+ '⽕' => '火',
610
+ '⽖' => '爪',
611
+ '⽗' => '父',
612
+ '⽘' => '爻',
613
+ '⽙' => '爿',
614
+ '⽚' => '片',
615
+ '⽛' => '牙',
616
+ '⽜' => '牛',
617
+ '⽝' => '犬',
618
+ '⽞' => '玄',
619
+ '⽟' => '玉',
620
+ '⽠' => '瓜',
621
+ '⽡' => '瓦',
622
+ '⽢' => '甘',
623
+ '⽣' => '生',
624
+ '⽤' => '用',
625
+ '⽥' => '田',
626
+ '⽦' => '疋',
627
+ '⽧' => '疒',
628
+ '⽨' => '癶',
629
+ '⽩' => '白',
630
+ '⽪' => '皮',
631
+ '⽫' => '皿',
632
+ '⽬' => '目',
633
+ '⽭' => '矛',
634
+ '⽮' => '矢',
635
+ '⽯' => '石',
636
+ '⽰' => '示',
637
+ '⽱' => '禸',
638
+ '⽲' => '禾',
639
+ '⽳' => '穴',
640
+ '⽴' => '立',
641
+ '⽵' => '竹',
642
+ '⽶' => '米',
643
+ '⽷' => '糸',
644
+ '⽸' => '缶',
645
+ '⽹' => '网',
646
+ '⽺' => '羊',
647
+ '⽻' => '羽',
648
+ '⽼' => '老',
649
+ '⽽' => '而',
650
+ '⽾' => '耒',
651
+ '⽿' => '耳',
652
+ '⾀' => '聿',
653
+ '⾁' => '肉',
654
+ '⾂' => '臣',
655
+ '⾃' => '自',
656
+ '⾄' => '至',
657
+ '⾅' => '臼',
658
+ '⾆' => '舌',
659
+ '⾇' => '舛',
660
+ '⾈' => '舟',
661
+ '⾉' => '艮',
662
+ '⾊' => '色',
663
+ '⾋' => '艸',
664
+ '⾌' => '虍',
665
+ '⾍' => '虫',
666
+ '⾎' => '血',
667
+ '⾏' => '行',
668
+ '⾐' => '衣',
669
+ '⾑' => '襾',
670
+ '⾒' => '見',
671
+ '⾓' => '角',
672
+ '⾔' => '言',
673
+ '⾕' => '谷',
674
+ '⾖' => '豆',
675
+ '⾗' => '豕',
676
+ '⾘' => '豸',
677
+ '⾙' => '貝',
678
+ '⾚' => '赤',
679
+ '⾛' => '走',
680
+ '⾜' => '足',
681
+ '⾝' => '身',
682
+ '⾞' => '車',
683
+ '⾟' => '辛',
684
+ '⾠' => '辰',
685
+ '⾡' => '辵',
686
+ '⾢' => '邑',
687
+ '⾣' => '酉',
688
+ '⾤' => '釆',
689
+ '⾥' => '里',
690
+ '⾦' => '金',
691
+ '⾧' => '長',
692
+ '⾨' => '門',
693
+ '⾩' => '阜',
694
+ '⾪' => '隶',
695
+ '⾫' => '隹',
696
+ '⾬' => '雨',
697
+ '⾭' => '靑',
698
+ '⾮' => '非',
699
+ '⾯' => '面',
700
+ '⾰' => '革',
701
+ '⾱' => '韋',
702
+ '⾲' => '韭',
703
+ '⾳' => '音',
704
+ '⾴' => '頁',
705
+ '⾵' => '風',
706
+ '⾶' => '飛',
707
+ '⾷' => '食',
708
+ '⾸' => '首',
709
+ '⾹' => '香',
710
+ '⾺' => '馬',
711
+ '⾻' => '骨',
712
+ '⾼' => '高',
713
+ '⾽' => '髟',
714
+ '⾾' => '鬥',
715
+ '⾿' => '鬯',
716
+ '⿀' => '鬲',
717
+ '⿁' => '鬼',
718
+ '⿂' => '魚',
719
+ '⿃' => '鳥',
720
+ '⿄' => '鹵',
721
+ '⿅' => '鹿',
722
+ '⿆' => '麥',
723
+ '⿇' => '麻',
724
+ '⿈' => '黃',
725
+ '⿉' => '黍',
726
+ '⿊' => '黑',
727
+ '⿋' => '黹',
728
+ '⿌' => '黽',
729
+ '⿍' => '鼎',
730
+ '⿎' => '鼓',
731
+ '⿏' => '鼠',
732
+ '⿐' => '鼻',
733
+ '⿑' => '齊',
734
+ '⿒' => '齒',
735
+ '⿓' => '龍',
736
+ '⿔' => '龜',
737
+ '⿕' => '龠',
738
+ ' ' => ' ',
739
+ '〶' => '〒',
740
+ '〸' => '十',
741
+ '〹' => '卄',
742
+ '〺' => '卅',
743
+ '゛' => ' ゙',
744
+ '゜' => ' ゚',
745
+ 'ゟ' => 'より',
746
+ 'ヿ' => 'コト',
747
+ 'ㄱ' => 'ᄀ',
748
+ 'ㄲ' => 'ᄁ',
749
+ 'ㄳ' => 'ᆪ',
750
+ 'ㄴ' => 'ᄂ',
751
+ 'ㄵ' => 'ᆬ',
752
+ 'ㄶ' => 'ᆭ',
753
+ 'ㄷ' => 'ᄃ',
754
+ 'ㄸ' => 'ᄄ',
755
+ 'ㄹ' => 'ᄅ',
756
+ 'ㄺ' => 'ᆰ',
757
+ 'ㄻ' => 'ᆱ',
758
+ 'ㄼ' => 'ᆲ',
759
+ 'ㄽ' => 'ᆳ',
760
+ 'ㄾ' => 'ᆴ',
761
+ 'ㄿ' => 'ᆵ',
762
+ 'ㅀ' => 'ᄚ',
763
+ 'ㅁ' => 'ᄆ',
764
+ 'ㅂ' => 'ᄇ',
765
+ 'ㅃ' => 'ᄈ',
766
+ 'ㅄ' => 'ᄡ',
767
+ 'ㅅ' => 'ᄉ',
768
+ 'ㅆ' => 'ᄊ',
769
+ 'ㅇ' => 'ᄋ',
770
+ 'ㅈ' => 'ᄌ',
771
+ 'ㅉ' => 'ᄍ',
772
+ 'ㅊ' => 'ᄎ',
773
+ 'ㅋ' => 'ᄏ',
774
+ 'ㅌ' => 'ᄐ',
775
+ 'ㅍ' => 'ᄑ',
776
+ 'ㅎ' => 'ᄒ',
777
+ 'ㅏ' => 'ᅡ',
778
+ 'ㅐ' => 'ᅢ',
779
+ 'ㅑ' => 'ᅣ',
780
+ 'ㅒ' => 'ᅤ',
781
+ 'ㅓ' => 'ᅥ',
782
+ 'ㅔ' => 'ᅦ',
783
+ 'ㅕ' => 'ᅧ',
784
+ 'ㅖ' => 'ᅨ',
785
+ 'ㅗ' => 'ᅩ',
786
+ 'ㅘ' => 'ᅪ',
787
+ 'ㅙ' => 'ᅫ',
788
+ 'ㅚ' => 'ᅬ',
789
+ 'ㅛ' => 'ᅭ',
790
+ 'ㅜ' => 'ᅮ',
791
+ 'ㅝ' => 'ᅯ',
792
+ 'ㅞ' => 'ᅰ',
793
+ 'ㅟ' => 'ᅱ',
794
+ 'ㅠ' => 'ᅲ',
795
+ 'ㅡ' => 'ᅳ',
796
+ 'ㅢ' => 'ᅴ',
797
+ 'ㅣ' => 'ᅵ',
798
+ 'ㅤ' => 'ᅠ',
799
+ 'ㅥ' => 'ᄔ',
800
+ 'ㅦ' => 'ᄕ',
801
+ 'ㅧ' => 'ᇇ',
802
+ 'ㅨ' => 'ᇈ',
803
+ 'ㅩ' => 'ᇌ',
804
+ 'ㅪ' => 'ᇎ',
805
+ 'ㅫ' => 'ᇓ',
806
+ 'ㅬ' => 'ᇗ',
807
+ 'ㅭ' => 'ᇙ',
808
+ 'ㅮ' => 'ᄜ',
809
+ 'ㅯ' => 'ᇝ',
810
+ 'ㅰ' => 'ᇟ',
811
+ 'ㅱ' => 'ᄝ',
812
+ 'ㅲ' => 'ᄞ',
813
+ 'ㅳ' => 'ᄠ',
814
+ 'ㅴ' => 'ᄢ',
815
+ 'ㅵ' => 'ᄣ',
816
+ 'ㅶ' => 'ᄧ',
817
+ 'ㅷ' => 'ᄩ',
818
+ 'ㅸ' => 'ᄫ',
819
+ 'ㅹ' => 'ᄬ',
820
+ 'ㅺ' => 'ᄭ',
821
+ 'ㅻ' => 'ᄮ',
822
+ 'ㅼ' => 'ᄯ',
823
+ 'ㅽ' => 'ᄲ',
824
+ 'ㅾ' => 'ᄶ',
825
+ 'ㅿ' => 'ᅀ',
826
+ 'ㆀ' => 'ᅇ',
827
+ 'ㆁ' => 'ᅌ',
828
+ 'ㆂ' => 'ᇱ',
829
+ 'ㆃ' => 'ᇲ',
830
+ 'ㆄ' => 'ᅗ',
831
+ 'ㆅ' => 'ᅘ',
832
+ 'ㆆ' => 'ᅙ',
833
+ 'ㆇ' => 'ᆄ',
834
+ 'ㆈ' => 'ᆅ',
835
+ 'ㆉ' => 'ᆈ',
836
+ 'ㆊ' => 'ᆑ',
837
+ 'ㆋ' => 'ᆒ',
838
+ 'ㆌ' => 'ᆔ',
839
+ 'ㆍ' => 'ᆞ',
840
+ 'ㆎ' => 'ᆡ',
841
+ '㆒' => '一',
842
+ '㆓' => '二',
843
+ '㆔' => '三',
844
+ '㆕' => '四',
845
+ '㆖' => '上',
846
+ '㆗' => '中',
847
+ '㆘' => '下',
848
+ '㆙' => '甲',
849
+ '㆚' => '乙',
850
+ '㆛' => '丙',
851
+ '㆜' => '丁',
852
+ '㆝' => '天',
853
+ '㆞' => '地',
854
+ '㆟' => '人',
855
+ '㈀' => '(ᄀ)',
856
+ '㈁' => '(ᄂ)',
857
+ '㈂' => '(ᄃ)',
858
+ '㈃' => '(ᄅ)',
859
+ '㈄' => '(ᄆ)',
860
+ '㈅' => '(ᄇ)',
861
+ '㈆' => '(ᄉ)',
862
+ '㈇' => '(ᄋ)',
863
+ '㈈' => '(ᄌ)',
864
+ '㈉' => '(ᄎ)',
865
+ '㈊' => '(ᄏ)',
866
+ '㈋' => '(ᄐ)',
867
+ '㈌' => '(ᄑ)',
868
+ '㈍' => '(ᄒ)',
869
+ '㈎' => '(가)',
870
+ '㈏' => '(나)',
871
+ '㈐' => '(다)',
872
+ '㈑' => '(라)',
873
+ '㈒' => '(마)',
874
+ '㈓' => '(바)',
875
+ '㈔' => '(사)',
876
+ '㈕' => '(아)',
877
+ '㈖' => '(자)',
878
+ '㈗' => '(차)',
879
+ '㈘' => '(카)',
880
+ '㈙' => '(타)',
881
+ '㈚' => '(파)',
882
+ '㈛' => '(하)',
883
+ '㈜' => '(주)',
884
+ '㈝' => '(오전)',
885
+ '㈞' => '(오후)',
886
+ '㈠' => '(一)',
887
+ '㈡' => '(二)',
888
+ '㈢' => '(三)',
889
+ '㈣' => '(四)',
890
+ '㈤' => '(五)',
891
+ '㈥' => '(六)',
892
+ '㈦' => '(七)',
893
+ '㈧' => '(八)',
894
+ '㈨' => '(九)',
895
+ '㈩' => '(十)',
896
+ '㈪' => '(月)',
897
+ '㈫' => '(火)',
898
+ '㈬' => '(水)',
899
+ '㈭' => '(木)',
900
+ '㈮' => '(金)',
901
+ '㈯' => '(土)',
902
+ '㈰' => '(日)',
903
+ '㈱' => '(株)',
904
+ '㈲' => '(有)',
905
+ '㈳' => '(社)',
906
+ '㈴' => '(名)',
907
+ '㈵' => '(特)',
908
+ '㈶' => '(財)',
909
+ '㈷' => '(祝)',
910
+ '㈸' => '(労)',
911
+ '㈹' => '(代)',
912
+ '㈺' => '(呼)',
913
+ '㈻' => '(学)',
914
+ '㈼' => '(監)',
915
+ '㈽' => '(企)',
916
+ '㈾' => '(資)',
917
+ '㈿' => '(協)',
918
+ '㉀' => '(祭)',
919
+ '㉁' => '(休)',
920
+ '㉂' => '(自)',
921
+ '㉃' => '(至)',
922
+ '㉄' => '問',
923
+ '㉅' => '幼',
924
+ '㉆' => '文',
925
+ '㉇' => '箏',
926
+ '㉐' => 'PTE',
927
+ '㉑' => '21',
928
+ '㉒' => '22',
929
+ '㉓' => '23',
930
+ '㉔' => '24',
931
+ '㉕' => '25',
932
+ '㉖' => '26',
933
+ '㉗' => '27',
934
+ '㉘' => '28',
935
+ '㉙' => '29',
936
+ '㉚' => '30',
937
+ '㉛' => '31',
938
+ '㉜' => '32',
939
+ '㉝' => '33',
940
+ '㉞' => '34',
941
+ '㉟' => '35',
942
+ '㉠' => 'ᄀ',
943
+ '㉡' => 'ᄂ',
944
+ '㉢' => 'ᄃ',
945
+ '㉣' => 'ᄅ',
946
+ '㉤' => 'ᄆ',
947
+ '㉥' => 'ᄇ',
948
+ '㉦' => 'ᄉ',
949
+ '㉧' => 'ᄋ',
950
+ '㉨' => 'ᄌ',
951
+ '㉩' => 'ᄎ',
952
+ '㉪' => 'ᄏ',
953
+ '㉫' => 'ᄐ',
954
+ '㉬' => 'ᄑ',
955
+ '㉭' => 'ᄒ',
956
+ '㉮' => '가',
957
+ '㉯' => '나',
958
+ '㉰' => '다',
959
+ '㉱' => '라',
960
+ '㉲' => '마',
961
+ '㉳' => '바',
962
+ '㉴' => '사',
963
+ '㉵' => '아',
964
+ '㉶' => '자',
965
+ '㉷' => '차',
966
+ '㉸' => '카',
967
+ '㉹' => '타',
968
+ '㉺' => '파',
969
+ '㉻' => '하',
970
+ '㉼' => '참고',
971
+ '㉽' => '주의',
972
+ '㉾' => '우',
973
+ '㊀' => '一',
974
+ '㊁' => '二',
975
+ '㊂' => '三',
976
+ '㊃' => '四',
977
+ '㊄' => '五',
978
+ '㊅' => '六',
979
+ '㊆' => '七',
980
+ '㊇' => '八',
981
+ '㊈' => '九',
982
+ '㊉' => '十',
983
+ '㊊' => '月',
984
+ '㊋' => '火',
985
+ '㊌' => '水',
986
+ '㊍' => '木',
987
+ '㊎' => '金',
988
+ '㊏' => '土',
989
+ '㊐' => '日',
990
+ '㊑' => '株',
991
+ '㊒' => '有',
992
+ '㊓' => '社',
993
+ '㊔' => '名',
994
+ '㊕' => '特',
995
+ '㊖' => '財',
996
+ '㊗' => '祝',
997
+ '㊘' => '労',
998
+ '㊙' => '秘',
999
+ '㊚' => '男',
1000
+ '㊛' => '女',
1001
+ '㊜' => '適',
1002
+ '㊝' => '優',
1003
+ '㊞' => '印',
1004
+ '㊟' => '注',
1005
+ '㊠' => '項',
1006
+ '㊡' => '休',
1007
+ '㊢' => '写',
1008
+ '㊣' => '正',
1009
+ '㊤' => '上',
1010
+ '㊥' => '中',
1011
+ '㊦' => '下',
1012
+ '㊧' => '左',
1013
+ '㊨' => '右',
1014
+ '㊩' => '医',
1015
+ '㊪' => '宗',
1016
+ '㊫' => '学',
1017
+ '㊬' => '監',
1018
+ '㊭' => '企',
1019
+ '㊮' => '資',
1020
+ '㊯' => '協',
1021
+ '㊰' => '夜',
1022
+ '㊱' => '36',
1023
+ '㊲' => '37',
1024
+ '㊳' => '38',
1025
+ '㊴' => '39',
1026
+ '㊵' => '40',
1027
+ '㊶' => '41',
1028
+ '㊷' => '42',
1029
+ '㊸' => '43',
1030
+ '㊹' => '44',
1031
+ '㊺' => '45',
1032
+ '㊻' => '46',
1033
+ '㊼' => '47',
1034
+ '㊽' => '48',
1035
+ '㊾' => '49',
1036
+ '㊿' => '50',
1037
+ '㋀' => '1月',
1038
+ '㋁' => '2月',
1039
+ '㋂' => '3月',
1040
+ '㋃' => '4月',
1041
+ '㋄' => '5月',
1042
+ '㋅' => '6月',
1043
+ '㋆' => '7月',
1044
+ '㋇' => '8月',
1045
+ '㋈' => '9月',
1046
+ '㋉' => '10月',
1047
+ '㋊' => '11月',
1048
+ '㋋' => '12月',
1049
+ '㋌' => 'Hg',
1050
+ '㋍' => 'erg',
1051
+ '㋎' => 'eV',
1052
+ '㋏' => 'LTD',
1053
+ '㋐' => 'ア',
1054
+ '㋑' => 'イ',
1055
+ '㋒' => 'ウ',
1056
+ '㋓' => 'エ',
1057
+ '㋔' => 'オ',
1058
+ '㋕' => 'カ',
1059
+ '㋖' => 'キ',
1060
+ '㋗' => 'ク',
1061
+ '㋘' => 'ケ',
1062
+ '㋙' => 'コ',
1063
+ '㋚' => 'サ',
1064
+ '㋛' => 'シ',
1065
+ '㋜' => 'ス',
1066
+ '㋝' => 'セ',
1067
+ '㋞' => 'ソ',
1068
+ '㋟' => 'タ',
1069
+ '㋠' => 'チ',
1070
+ '㋡' => 'ツ',
1071
+ '㋢' => 'テ',
1072
+ '㋣' => 'ト',
1073
+ '㋤' => 'ナ',
1074
+ '㋥' => 'ニ',
1075
+ '㋦' => 'ヌ',
1076
+ '㋧' => 'ネ',
1077
+ '㋨' => 'ノ',
1078
+ '㋩' => 'ハ',
1079
+ '㋪' => 'ヒ',
1080
+ '㋫' => 'フ',
1081
+ '㋬' => 'ヘ',
1082
+ '㋭' => 'ホ',
1083
+ '㋮' => 'マ',
1084
+ '㋯' => 'ミ',
1085
+ '㋰' => 'ム',
1086
+ '㋱' => 'メ',
1087
+ '㋲' => 'モ',
1088
+ '㋳' => 'ヤ',
1089
+ '㋴' => 'ユ',
1090
+ '㋵' => 'ヨ',
1091
+ '㋶' => 'ラ',
1092
+ '㋷' => 'リ',
1093
+ '㋸' => 'ル',
1094
+ '㋹' => 'レ',
1095
+ '㋺' => 'ロ',
1096
+ '㋻' => 'ワ',
1097
+ '㋼' => 'ヰ',
1098
+ '㋽' => 'ヱ',
1099
+ '㋾' => 'ヲ',
1100
+ '㋿' => '令和',
1101
+ '㌀' => 'アパート',
1102
+ '㌁' => 'アルファ',
1103
+ '㌂' => 'アンペア',
1104
+ '㌃' => 'アール',
1105
+ '㌄' => 'イニング',
1106
+ '㌅' => 'インチ',
1107
+ '㌆' => 'ウォン',
1108
+ '㌇' => 'エスクード',
1109
+ '㌈' => 'エーカー',
1110
+ '㌉' => 'オンス',
1111
+ '㌊' => 'オーム',
1112
+ '㌋' => 'カイリ',
1113
+ '㌌' => 'カラット',
1114
+ '㌍' => 'カロリー',
1115
+ '㌎' => 'ガロン',
1116
+ '㌏' => 'ガンマ',
1117
+ '㌐' => 'ギガ',
1118
+ '㌑' => 'ギニー',
1119
+ '㌒' => 'キュリー',
1120
+ '㌓' => 'ギルダー',
1121
+ '㌔' => 'キロ',
1122
+ '㌕' => 'キログラム',
1123
+ '㌖' => 'キロメートル',
1124
+ '㌗' => 'キロワット',
1125
+ '㌘' => 'グラム',
1126
+ '㌙' => 'グラムトン',
1127
+ '㌚' => 'クルゼイロ',
1128
+ '㌛' => 'クローネ',
1129
+ '㌜' => 'ケース',
1130
+ '㌝' => 'コルナ',
1131
+ '㌞' => 'コーポ',
1132
+ '㌟' => 'サイクル',
1133
+ '㌠' => 'サンチーム',
1134
+ '㌡' => 'シリング',
1135
+ '㌢' => 'センチ',
1136
+ '㌣' => 'セント',
1137
+ '㌤' => 'ダース',
1138
+ '㌥' => 'デシ',
1139
+ '㌦' => 'ドル',
1140
+ '㌧' => 'トン',
1141
+ '㌨' => 'ナノ',
1142
+ '㌩' => 'ノット',
1143
+ '㌪' => 'ハイツ',
1144
+ '㌫' => 'パーセント',
1145
+ '㌬' => 'パーツ',
1146
+ '㌭' => 'バーレル',
1147
+ '㌮' => 'ピアストル',
1148
+ '㌯' => 'ピクル',
1149
+ '㌰' => 'ピコ',
1150
+ '㌱' => 'ビル',
1151
+ '㌲' => 'ファラッド',
1152
+ '㌳' => 'フィート',
1153
+ '㌴' => 'ブッシェル',
1154
+ '㌵' => 'フラン',
1155
+ '㌶' => 'ヘクタール',
1156
+ '㌷' => 'ペソ',
1157
+ '㌸' => 'ペニヒ',
1158
+ '㌹' => 'ヘルツ',
1159
+ '㌺' => 'ペンス',
1160
+ '㌻' => 'ページ',
1161
+ '㌼' => 'ベータ',
1162
+ '㌽' => 'ポイント',
1163
+ '㌾' => 'ボルト',
1164
+ '㌿' => 'ホン',
1165
+ '㍀' => 'ポンド',
1166
+ '㍁' => 'ホール',
1167
+ '㍂' => 'ホーン',
1168
+ '㍃' => 'マイクロ',
1169
+ '㍄' => 'マイル',
1170
+ '㍅' => 'マッハ',
1171
+ '㍆' => 'マルク',
1172
+ '㍇' => 'マンション',
1173
+ '㍈' => 'ミクロン',
1174
+ '㍉' => 'ミリ',
1175
+ '㍊' => 'ミリバール',
1176
+ '㍋' => 'メガ',
1177
+ '㍌' => 'メガトン',
1178
+ '㍍' => 'メートル',
1179
+ '㍎' => 'ヤード',
1180
+ '㍏' => 'ヤール',
1181
+ '㍐' => 'ユアン',
1182
+ '㍑' => 'リットル',
1183
+ '㍒' => 'リラ',
1184
+ '㍓' => 'ルピー',
1185
+ '㍔' => 'ルーブル',
1186
+ '㍕' => 'レム',
1187
+ '㍖' => 'レントゲン',
1188
+ '㍗' => 'ワット',
1189
+ '㍘' => '0点',
1190
+ '㍙' => '1点',
1191
+ '㍚' => '2点',
1192
+ '㍛' => '3点',
1193
+ '㍜' => '4点',
1194
+ '㍝' => '5点',
1195
+ '㍞' => '6点',
1196
+ '㍟' => '7点',
1197
+ '㍠' => '8点',
1198
+ '㍡' => '9点',
1199
+ '㍢' => '10点',
1200
+ '㍣' => '11点',
1201
+ '㍤' => '12点',
1202
+ '㍥' => '13点',
1203
+ '㍦' => '14点',
1204
+ '㍧' => '15点',
1205
+ '㍨' => '16点',
1206
+ '㍩' => '17点',
1207
+ '㍪' => '18点',
1208
+ '㍫' => '19点',
1209
+ '㍬' => '20点',
1210
+ '㍭' => '21点',
1211
+ '㍮' => '22点',
1212
+ '㍯' => '23点',
1213
+ '㍰' => '24点',
1214
+ '㍱' => 'hPa',
1215
+ '㍲' => 'da',
1216
+ '㍳' => 'AU',
1217
+ '㍴' => 'bar',
1218
+ '㍵' => 'oV',
1219
+ '㍶' => 'pc',
1220
+ '㍷' => 'dm',
1221
+ '㍸' => 'dm2',
1222
+ '㍹' => 'dm3',
1223
+ '㍺' => 'IU',
1224
+ '㍻' => '平成',
1225
+ '㍼' => '昭和',
1226
+ '㍽' => '大正',
1227
+ '㍾' => '明治',
1228
+ '㍿' => '株式会社',
1229
+ '㎀' => 'pA',
1230
+ '㎁' => 'nA',
1231
+ '㎂' => 'μA',
1232
+ '㎃' => 'mA',
1233
+ '㎄' => 'kA',
1234
+ '㎅' => 'KB',
1235
+ '㎆' => 'MB',
1236
+ '㎇' => 'GB',
1237
+ '㎈' => 'cal',
1238
+ '㎉' => 'kcal',
1239
+ '㎊' => 'pF',
1240
+ '㎋' => 'nF',
1241
+ '㎌' => 'μF',
1242
+ '㎍' => 'μg',
1243
+ '㎎' => 'mg',
1244
+ '㎏' => 'kg',
1245
+ '㎐' => 'Hz',
1246
+ '㎑' => 'kHz',
1247
+ '㎒' => 'MHz',
1248
+ '㎓' => 'GHz',
1249
+ '㎔' => 'THz',
1250
+ '㎕' => 'μl',
1251
+ '㎖' => 'ml',
1252
+ '㎗' => 'dl',
1253
+ '㎘' => 'kl',
1254
+ '㎙' => 'fm',
1255
+ '㎚' => 'nm',
1256
+ '㎛' => 'μm',
1257
+ '㎜' => 'mm',
1258
+ '㎝' => 'cm',
1259
+ '㎞' => 'km',
1260
+ '㎟' => 'mm2',
1261
+ '㎠' => 'cm2',
1262
+ '㎡' => 'm2',
1263
+ '㎢' => 'km2',
1264
+ '㎣' => 'mm3',
1265
+ '㎤' => 'cm3',
1266
+ '㎥' => 'm3',
1267
+ '㎦' => 'km3',
1268
+ '㎧' => 'm∕s',
1269
+ '㎨' => 'm∕s2',
1270
+ '㎩' => 'Pa',
1271
+ '㎪' => 'kPa',
1272
+ '㎫' => 'MPa',
1273
+ '㎬' => 'GPa',
1274
+ '㎭' => 'rad',
1275
+ '㎮' => 'rad∕s',
1276
+ '㎯' => 'rad∕s2',
1277
+ '㎰' => 'ps',
1278
+ '㎱' => 'ns',
1279
+ '㎲' => 'μs',
1280
+ '㎳' => 'ms',
1281
+ '㎴' => 'pV',
1282
+ '㎵' => 'nV',
1283
+ '㎶' => 'μV',
1284
+ '㎷' => 'mV',
1285
+ '㎸' => 'kV',
1286
+ '㎹' => 'MV',
1287
+ '㎺' => 'pW',
1288
+ '㎻' => 'nW',
1289
+ '㎼' => 'μW',
1290
+ '㎽' => 'mW',
1291
+ '㎾' => 'kW',
1292
+ '㎿' => 'MW',
1293
+ '㏀' => 'kΩ',
1294
+ '㏁' => 'MΩ',
1295
+ '㏂' => 'a.m.',
1296
+ '㏃' => 'Bq',
1297
+ '㏄' => 'cc',
1298
+ '㏅' => 'cd',
1299
+ '㏆' => 'C∕kg',
1300
+ '㏇' => 'Co.',
1301
+ '㏈' => 'dB',
1302
+ '㏉' => 'Gy',
1303
+ '㏊' => 'ha',
1304
+ '㏋' => 'HP',
1305
+ '㏌' => 'in',
1306
+ '㏍' => 'KK',
1307
+ '㏎' => 'KM',
1308
+ '㏏' => 'kt',
1309
+ '㏐' => 'lm',
1310
+ '㏑' => 'ln',
1311
+ '㏒' => 'log',
1312
+ '㏓' => 'lx',
1313
+ '㏔' => 'mb',
1314
+ '㏕' => 'mil',
1315
+ '㏖' => 'mol',
1316
+ '㏗' => 'PH',
1317
+ '㏘' => 'p.m.',
1318
+ '㏙' => 'PPM',
1319
+ '㏚' => 'PR',
1320
+ '㏛' => 'sr',
1321
+ '㏜' => 'Sv',
1322
+ '㏝' => 'Wb',
1323
+ '㏞' => 'V∕m',
1324
+ '㏟' => 'A∕m',
1325
+ '㏠' => '1日',
1326
+ '㏡' => '2日',
1327
+ '㏢' => '3日',
1328
+ '㏣' => '4日',
1329
+ '㏤' => '5日',
1330
+ '㏥' => '6日',
1331
+ '㏦' => '7日',
1332
+ '㏧' => '8日',
1333
+ '㏨' => '9日',
1334
+ '㏩' => '10日',
1335
+ '㏪' => '11日',
1336
+ '㏫' => '12日',
1337
+ '㏬' => '13日',
1338
+ '㏭' => '14日',
1339
+ '㏮' => '15日',
1340
+ '㏯' => '16日',
1341
+ '㏰' => '17日',
1342
+ '㏱' => '18日',
1343
+ '㏲' => '19日',
1344
+ '㏳' => '20日',
1345
+ '㏴' => '21日',
1346
+ '㏵' => '22日',
1347
+ '㏶' => '23日',
1348
+ '㏷' => '24日',
1349
+ '㏸' => '25日',
1350
+ '㏹' => '26日',
1351
+ '㏺' => '27日',
1352
+ '㏻' => '28日',
1353
+ '㏼' => '29日',
1354
+ '㏽' => '30日',
1355
+ '㏾' => '31日',
1356
+ '㏿' => 'gal',
1357
+ 'ꚜ' => 'ъ',
1358
+ 'ꚝ' => 'ь',
1359
+ 'ꝰ' => 'ꝯ',
1360
+ 'ꟸ' => 'Ħ',
1361
+ 'ꟹ' => 'œ',
1362
+ 'ꭜ' => 'ꜧ',
1363
+ 'ꭝ' => 'ꬷ',
1364
+ 'ꭞ' => 'ɫ',
1365
+ 'ꭟ' => 'ꭒ',
1366
+ 'ꭩ' => 'ʍ',
1367
+ 'ff' => 'ff',
1368
+ 'fi' => 'fi',
1369
+ 'fl' => 'fl',
1370
+ 'ffi' => 'ffi',
1371
+ 'ffl' => 'ffl',
1372
+ 'ſt' => 'st',
1373
+ 'st' => 'st',
1374
+ 'ﬓ' => 'մն',
1375
+ 'ﬔ' => 'մե',
1376
+ 'ﬕ' => 'մի',
1377
+ 'ﬖ' => 'վն',
1378
+ 'ﬗ' => 'մխ',
1379
+ 'ﬠ' => 'ע',
1380
+ 'ﬡ' => 'א',
1381
+ 'ﬢ' => 'ד',
1382
+ 'ﬣ' => 'ה',
1383
+ 'ﬤ' => 'כ',
1384
+ 'ﬥ' => 'ל',
1385
+ 'ﬦ' => 'ם',
1386
+ 'ﬧ' => 'ר',
1387
+ 'ﬨ' => 'ת',
1388
+ '﬩' => '+',
1389
+ 'ﭏ' => 'אל',
1390
+ 'ﭐ' => 'ٱ',
1391
+ 'ﭑ' => 'ٱ',
1392
+ 'ﭒ' => 'ٻ',
1393
+ 'ﭓ' => 'ٻ',
1394
+ 'ﭔ' => 'ٻ',
1395
+ 'ﭕ' => 'ٻ',
1396
+ 'ﭖ' => 'پ',
1397
+ 'ﭗ' => 'پ',
1398
+ 'ﭘ' => 'پ',
1399
+ 'ﭙ' => 'پ',
1400
+ 'ﭚ' => 'ڀ',
1401
+ 'ﭛ' => 'ڀ',
1402
+ 'ﭜ' => 'ڀ',
1403
+ 'ﭝ' => 'ڀ',
1404
+ 'ﭞ' => 'ٺ',
1405
+ 'ﭟ' => 'ٺ',
1406
+ 'ﭠ' => 'ٺ',
1407
+ 'ﭡ' => 'ٺ',
1408
+ 'ﭢ' => 'ٿ',
1409
+ 'ﭣ' => 'ٿ',
1410
+ 'ﭤ' => 'ٿ',
1411
+ 'ﭥ' => 'ٿ',
1412
+ 'ﭦ' => 'ٹ',
1413
+ 'ﭧ' => 'ٹ',
1414
+ 'ﭨ' => 'ٹ',
1415
+ 'ﭩ' => 'ٹ',
1416
+ 'ﭪ' => 'ڤ',
1417
+ 'ﭫ' => 'ڤ',
1418
+ 'ﭬ' => 'ڤ',
1419
+ 'ﭭ' => 'ڤ',
1420
+ 'ﭮ' => 'ڦ',
1421
+ 'ﭯ' => 'ڦ',
1422
+ 'ﭰ' => 'ڦ',
1423
+ 'ﭱ' => 'ڦ',
1424
+ 'ﭲ' => 'ڄ',
1425
+ 'ﭳ' => 'ڄ',
1426
+ 'ﭴ' => 'ڄ',
1427
+ 'ﭵ' => 'ڄ',
1428
+ 'ﭶ' => 'ڃ',
1429
+ 'ﭷ' => 'ڃ',
1430
+ 'ﭸ' => 'ڃ',
1431
+ 'ﭹ' => 'ڃ',
1432
+ 'ﭺ' => 'چ',
1433
+ 'ﭻ' => 'چ',
1434
+ 'ﭼ' => 'چ',
1435
+ 'ﭽ' => 'چ',
1436
+ 'ﭾ' => 'ڇ',
1437
+ 'ﭿ' => 'ڇ',
1438
+ 'ﮀ' => 'ڇ',
1439
+ 'ﮁ' => 'ڇ',
1440
+ 'ﮂ' => 'ڍ',
1441
+ 'ﮃ' => 'ڍ',
1442
+ 'ﮄ' => 'ڌ',
1443
+ 'ﮅ' => 'ڌ',
1444
+ 'ﮆ' => 'ڎ',
1445
+ 'ﮇ' => 'ڎ',
1446
+ 'ﮈ' => 'ڈ',
1447
+ 'ﮉ' => 'ڈ',
1448
+ 'ﮊ' => 'ژ',
1449
+ 'ﮋ' => 'ژ',
1450
+ 'ﮌ' => 'ڑ',
1451
+ 'ﮍ' => 'ڑ',
1452
+ 'ﮎ' => 'ک',
1453
+ 'ﮏ' => 'ک',
1454
+ 'ﮐ' => 'ک',
1455
+ 'ﮑ' => 'ک',
1456
+ 'ﮒ' => 'گ',
1457
+ 'ﮓ' => 'گ',
1458
+ 'ﮔ' => 'گ',
1459
+ 'ﮕ' => 'گ',
1460
+ 'ﮖ' => 'ڳ',
1461
+ 'ﮗ' => 'ڳ',
1462
+ 'ﮘ' => 'ڳ',
1463
+ 'ﮙ' => 'ڳ',
1464
+ 'ﮚ' => 'ڱ',
1465
+ 'ﮛ' => 'ڱ',
1466
+ 'ﮜ' => 'ڱ',
1467
+ 'ﮝ' => 'ڱ',
1468
+ 'ﮞ' => 'ں',
1469
+ 'ﮟ' => 'ں',
1470
+ 'ﮠ' => 'ڻ',
1471
+ 'ﮡ' => 'ڻ',
1472
+ 'ﮢ' => 'ڻ',
1473
+ 'ﮣ' => 'ڻ',
1474
+ 'ﮤ' => 'ۀ',
1475
+ 'ﮥ' => 'ۀ',
1476
+ 'ﮦ' => 'ہ',
1477
+ 'ﮧ' => 'ہ',
1478
+ 'ﮨ' => 'ہ',
1479
+ 'ﮩ' => 'ہ',
1480
+ 'ﮪ' => 'ھ',
1481
+ 'ﮫ' => 'ھ',
1482
+ 'ﮬ' => 'ھ',
1483
+ 'ﮭ' => 'ھ',
1484
+ 'ﮮ' => 'ے',
1485
+ 'ﮯ' => 'ے',
1486
+ 'ﮰ' => 'ۓ',
1487
+ 'ﮱ' => 'ۓ',
1488
+ 'ﯓ' => 'ڭ',
1489
+ 'ﯔ' => 'ڭ',
1490
+ 'ﯕ' => 'ڭ',
1491
+ 'ﯖ' => 'ڭ',
1492
+ 'ﯗ' => 'ۇ',
1493
+ 'ﯘ' => 'ۇ',
1494
+ 'ﯙ' => 'ۆ',
1495
+ 'ﯚ' => 'ۆ',
1496
+ 'ﯛ' => 'ۈ',
1497
+ 'ﯜ' => 'ۈ',
1498
+ 'ﯝ' => 'ۇٴ',
1499
+ 'ﯞ' => 'ۋ',
1500
+ 'ﯟ' => 'ۋ',
1501
+ 'ﯠ' => 'ۅ',
1502
+ 'ﯡ' => 'ۅ',
1503
+ 'ﯢ' => 'ۉ',
1504
+ 'ﯣ' => 'ۉ',
1505
+ 'ﯤ' => 'ې',
1506
+ 'ﯥ' => 'ې',
1507
+ 'ﯦ' => 'ې',
1508
+ 'ﯧ' => 'ې',
1509
+ 'ﯨ' => 'ى',
1510
+ 'ﯩ' => 'ى',
1511
+ 'ﯪ' => 'ئا',
1512
+ 'ﯫ' => 'ئا',
1513
+ 'ﯬ' => 'ئە',
1514
+ 'ﯭ' => 'ئە',
1515
+ 'ﯮ' => 'ئو',
1516
+ 'ﯯ' => 'ئو',
1517
+ 'ﯰ' => 'ئۇ',
1518
+ 'ﯱ' => 'ئۇ',
1519
+ 'ﯲ' => 'ئۆ',
1520
+ 'ﯳ' => 'ئۆ',
1521
+ 'ﯴ' => 'ئۈ',
1522
+ 'ﯵ' => 'ئۈ',
1523
+ 'ﯶ' => 'ئې',
1524
+ 'ﯷ' => 'ئې',
1525
+ 'ﯸ' => 'ئې',
1526
+ 'ﯹ' => 'ئى',
1527
+ 'ﯺ' => 'ئى',
1528
+ 'ﯻ' => 'ئى',
1529
+ 'ﯼ' => 'ی',
1530
+ 'ﯽ' => 'ی',
1531
+ 'ﯾ' => 'ی',
1532
+ 'ﯿ' => 'ی',
1533
+ 'ﰀ' => 'ئج',
1534
+ 'ﰁ' => 'ئح',
1535
+ 'ﰂ' => 'ئم',
1536
+ 'ﰃ' => 'ئى',
1537
+ 'ﰄ' => 'ئي',
1538
+ 'ﰅ' => 'بج',
1539
+ 'ﰆ' => 'بح',
1540
+ 'ﰇ' => 'بخ',
1541
+ 'ﰈ' => 'بم',
1542
+ 'ﰉ' => 'بى',
1543
+ 'ﰊ' => 'بي',
1544
+ 'ﰋ' => 'تج',
1545
+ 'ﰌ' => 'تح',
1546
+ 'ﰍ' => 'تخ',
1547
+ 'ﰎ' => 'تم',
1548
+ 'ﰏ' => 'تى',
1549
+ 'ﰐ' => 'تي',
1550
+ 'ﰑ' => 'ثج',
1551
+ 'ﰒ' => 'ثم',
1552
+ 'ﰓ' => 'ثى',
1553
+ 'ﰔ' => 'ثي',
1554
+ 'ﰕ' => 'جح',
1555
+ 'ﰖ' => 'جم',
1556
+ 'ﰗ' => 'حج',
1557
+ 'ﰘ' => 'حم',
1558
+ 'ﰙ' => 'خج',
1559
+ 'ﰚ' => 'خح',
1560
+ 'ﰛ' => 'خم',
1561
+ 'ﰜ' => 'سج',
1562
+ 'ﰝ' => 'سح',
1563
+ 'ﰞ' => 'سخ',
1564
+ 'ﰟ' => 'سم',
1565
+ 'ﰠ' => 'صح',
1566
+ 'ﰡ' => 'صم',
1567
+ 'ﰢ' => 'ضج',
1568
+ 'ﰣ' => 'ضح',
1569
+ 'ﰤ' => 'ضخ',
1570
+ 'ﰥ' => 'ضم',
1571
+ 'ﰦ' => 'طح',
1572
+ 'ﰧ' => 'طم',
1573
+ 'ﰨ' => 'ظم',
1574
+ 'ﰩ' => 'عج',
1575
+ 'ﰪ' => 'عم',
1576
+ 'ﰫ' => 'غج',
1577
+ 'ﰬ' => 'غم',
1578
+ 'ﰭ' => 'فج',
1579
+ 'ﰮ' => 'فح',
1580
+ 'ﰯ' => 'فخ',
1581
+ 'ﰰ' => 'فم',
1582
+ 'ﰱ' => 'فى',
1583
+ 'ﰲ' => 'في',
1584
+ 'ﰳ' => 'قح',
1585
+ 'ﰴ' => 'قم',
1586
+ 'ﰵ' => 'قى',
1587
+ 'ﰶ' => 'قي',
1588
+ 'ﰷ' => 'كا',
1589
+ 'ﰸ' => 'كج',
1590
+ 'ﰹ' => 'كح',
1591
+ 'ﰺ' => 'كخ',
1592
+ 'ﰻ' => 'كل',
1593
+ 'ﰼ' => 'كم',
1594
+ 'ﰽ' => 'كى',
1595
+ 'ﰾ' => 'كي',
1596
+ 'ﰿ' => 'لج',
1597
+ 'ﱀ' => 'لح',
1598
+ 'ﱁ' => 'لخ',
1599
+ 'ﱂ' => 'لم',
1600
+ 'ﱃ' => 'لى',
1601
+ 'ﱄ' => 'لي',
1602
+ 'ﱅ' => 'مج',
1603
+ 'ﱆ' => 'مح',
1604
+ 'ﱇ' => 'مخ',
1605
+ 'ﱈ' => 'مم',
1606
+ 'ﱉ' => 'مى',
1607
+ 'ﱊ' => 'مي',
1608
+ 'ﱋ' => 'نج',
1609
+ 'ﱌ' => 'نح',
1610
+ 'ﱍ' => 'نخ',
1611
+ 'ﱎ' => 'نم',
1612
+ 'ﱏ' => 'نى',
1613
+ 'ﱐ' => 'ني',
1614
+ 'ﱑ' => 'هج',
1615
+ 'ﱒ' => 'هم',
1616
+ 'ﱓ' => 'هى',
1617
+ 'ﱔ' => 'هي',
1618
+ 'ﱕ' => 'يج',
1619
+ 'ﱖ' => 'يح',
1620
+ 'ﱗ' => 'يخ',
1621
+ 'ﱘ' => 'يم',
1622
+ 'ﱙ' => 'يى',
1623
+ 'ﱚ' => 'يي',
1624
+ 'ﱛ' => 'ذٰ',
1625
+ 'ﱜ' => 'رٰ',
1626
+ 'ﱝ' => 'ىٰ',
1627
+ 'ﱞ' => ' ٌّ',
1628
+ 'ﱟ' => ' ٍّ',
1629
+ 'ﱠ' => ' َّ',
1630
+ 'ﱡ' => ' ُّ',
1631
+ 'ﱢ' => ' ِّ',
1632
+ 'ﱣ' => ' ّٰ',
1633
+ 'ﱤ' => 'ئر',
1634
+ 'ﱥ' => 'ئز',
1635
+ 'ﱦ' => 'ئم',
1636
+ 'ﱧ' => 'ئن',
1637
+ 'ﱨ' => 'ئى',
1638
+ 'ﱩ' => 'ئي',
1639
+ 'ﱪ' => 'بر',
1640
+ 'ﱫ' => 'بز',
1641
+ 'ﱬ' => 'بم',
1642
+ 'ﱭ' => 'بن',
1643
+ 'ﱮ' => 'بى',
1644
+ 'ﱯ' => 'بي',
1645
+ 'ﱰ' => 'تر',
1646
+ 'ﱱ' => 'تز',
1647
+ 'ﱲ' => 'تم',
1648
+ 'ﱳ' => 'تن',
1649
+ 'ﱴ' => 'تى',
1650
+ 'ﱵ' => 'تي',
1651
+ 'ﱶ' => 'ثر',
1652
+ 'ﱷ' => 'ثز',
1653
+ 'ﱸ' => 'ثم',
1654
+ 'ﱹ' => 'ثن',
1655
+ 'ﱺ' => 'ثى',
1656
+ 'ﱻ' => 'ثي',
1657
+ 'ﱼ' => 'فى',
1658
+ 'ﱽ' => 'في',
1659
+ 'ﱾ' => 'قى',
1660
+ 'ﱿ' => 'قي',
1661
+ 'ﲀ' => 'كا',
1662
+ 'ﲁ' => 'كل',
1663
+ 'ﲂ' => 'كم',
1664
+ 'ﲃ' => 'كى',
1665
+ 'ﲄ' => 'كي',
1666
+ 'ﲅ' => 'لم',
1667
+ 'ﲆ' => 'لى',
1668
+ 'ﲇ' => 'لي',
1669
+ 'ﲈ' => 'ما',
1670
+ 'ﲉ' => 'مم',
1671
+ 'ﲊ' => 'نر',
1672
+ 'ﲋ' => 'نز',
1673
+ 'ﲌ' => 'نم',
1674
+ 'ﲍ' => 'نن',
1675
+ 'ﲎ' => 'نى',
1676
+ 'ﲏ' => 'ني',
1677
+ 'ﲐ' => 'ىٰ',
1678
+ 'ﲑ' => 'ير',
1679
+ 'ﲒ' => 'يز',
1680
+ 'ﲓ' => 'يم',
1681
+ 'ﲔ' => 'ين',
1682
+ 'ﲕ' => 'يى',
1683
+ 'ﲖ' => 'يي',
1684
+ 'ﲗ' => 'ئج',
1685
+ 'ﲘ' => 'ئح',
1686
+ 'ﲙ' => 'ئخ',
1687
+ 'ﲚ' => 'ئم',
1688
+ 'ﲛ' => 'ئه',
1689
+ 'ﲜ' => 'بج',
1690
+ 'ﲝ' => 'بح',
1691
+ 'ﲞ' => 'بخ',
1692
+ 'ﲟ' => 'بم',
1693
+ 'ﲠ' => 'به',
1694
+ 'ﲡ' => 'تج',
1695
+ 'ﲢ' => 'تح',
1696
+ 'ﲣ' => 'تخ',
1697
+ 'ﲤ' => 'تم',
1698
+ 'ﲥ' => 'ته',
1699
+ 'ﲦ' => 'ثم',
1700
+ 'ﲧ' => 'جح',
1701
+ 'ﲨ' => 'جم',
1702
+ 'ﲩ' => 'حج',
1703
+ 'ﲪ' => 'حم',
1704
+ 'ﲫ' => 'خج',
1705
+ 'ﲬ' => 'خم',
1706
+ 'ﲭ' => 'سج',
1707
+ 'ﲮ' => 'سح',
1708
+ 'ﲯ' => 'سخ',
1709
+ 'ﲰ' => 'سم',
1710
+ 'ﲱ' => 'صح',
1711
+ 'ﲲ' => 'صخ',
1712
+ 'ﲳ' => 'صم',
1713
+ 'ﲴ' => 'ضج',
1714
+ 'ﲵ' => 'ضح',
1715
+ 'ﲶ' => 'ضخ',
1716
+ 'ﲷ' => 'ضم',
1717
+ 'ﲸ' => 'طح',
1718
+ 'ﲹ' => 'ظم',
1719
+ 'ﲺ' => 'عج',
1720
+ 'ﲻ' => 'عم',
1721
+ 'ﲼ' => 'غج',
1722
+ 'ﲽ' => 'غم',
1723
+ 'ﲾ' => 'فج',
1724
+ 'ﲿ' => 'فح',
1725
+ 'ﳀ' => 'فخ',
1726
+ 'ﳁ' => 'فم',
1727
+ 'ﳂ' => 'قح',
1728
+ 'ﳃ' => 'قم',
1729
+ 'ﳄ' => 'كج',
1730
+ 'ﳅ' => 'كح',
1731
+ 'ﳆ' => 'كخ',
1732
+ 'ﳇ' => 'كل',
1733
+ 'ﳈ' => 'كم',
1734
+ 'ﳉ' => 'لج',
1735
+ 'ﳊ' => 'لح',
1736
+ 'ﳋ' => 'لخ',
1737
+ 'ﳌ' => 'لم',
1738
+ 'ﳍ' => 'له',
1739
+ 'ﳎ' => 'مج',
1740
+ 'ﳏ' => 'مح',
1741
+ 'ﳐ' => 'مخ',
1742
+ 'ﳑ' => 'مم',
1743
+ 'ﳒ' => 'نج',
1744
+ 'ﳓ' => 'نح',
1745
+ 'ﳔ' => 'نخ',
1746
+ 'ﳕ' => 'نم',
1747
+ 'ﳖ' => 'نه',
1748
+ 'ﳗ' => 'هج',
1749
+ 'ﳘ' => 'هم',
1750
+ 'ﳙ' => 'هٰ',
1751
+ 'ﳚ' => 'يج',
1752
+ 'ﳛ' => 'يح',
1753
+ 'ﳜ' => 'يخ',
1754
+ 'ﳝ' => 'يم',
1755
+ 'ﳞ' => 'يه',
1756
+ 'ﳟ' => 'ئم',
1757
+ 'ﳠ' => 'ئه',
1758
+ 'ﳡ' => 'بم',
1759
+ 'ﳢ' => 'به',
1760
+ 'ﳣ' => 'تم',
1761
+ 'ﳤ' => 'ته',
1762
+ 'ﳥ' => 'ثم',
1763
+ 'ﳦ' => 'ثه',
1764
+ 'ﳧ' => 'سم',
1765
+ 'ﳨ' => 'سه',
1766
+ 'ﳩ' => 'شم',
1767
+ 'ﳪ' => 'شه',
1768
+ 'ﳫ' => 'كل',
1769
+ 'ﳬ' => 'كم',
1770
+ 'ﳭ' => 'لم',
1771
+ 'ﳮ' => 'نم',
1772
+ 'ﳯ' => 'نه',
1773
+ 'ﳰ' => 'يم',
1774
+ 'ﳱ' => 'يه',
1775
+ 'ﳲ' => 'ـَّ',
1776
+ 'ﳳ' => 'ـُّ',
1777
+ 'ﳴ' => 'ـِّ',
1778
+ 'ﳵ' => 'طى',
1779
+ 'ﳶ' => 'طي',
1780
+ 'ﳷ' => 'عى',
1781
+ 'ﳸ' => 'عي',
1782
+ 'ﳹ' => 'غى',
1783
+ 'ﳺ' => 'غي',
1784
+ 'ﳻ' => 'سى',
1785
+ 'ﳼ' => 'سي',
1786
+ 'ﳽ' => 'شى',
1787
+ 'ﳾ' => 'شي',
1788
+ 'ﳿ' => 'حى',
1789
+ 'ﴀ' => 'حي',
1790
+ 'ﴁ' => 'جى',
1791
+ 'ﴂ' => 'جي',
1792
+ 'ﴃ' => 'خى',
1793
+ 'ﴄ' => 'خي',
1794
+ 'ﴅ' => 'صى',
1795
+ 'ﴆ' => 'صي',
1796
+ 'ﴇ' => 'ضى',
1797
+ 'ﴈ' => 'ضي',
1798
+ 'ﴉ' => 'شج',
1799
+ 'ﴊ' => 'شح',
1800
+ 'ﴋ' => 'شخ',
1801
+ 'ﴌ' => 'شم',
1802
+ 'ﴍ' => 'شر',
1803
+ 'ﴎ' => 'سر',
1804
+ 'ﴏ' => 'صر',
1805
+ 'ﴐ' => 'ضر',
1806
+ 'ﴑ' => 'طى',
1807
+ 'ﴒ' => 'طي',
1808
+ 'ﴓ' => 'عى',
1809
+ 'ﴔ' => 'عي',
1810
+ 'ﴕ' => 'غى',
1811
+ 'ﴖ' => 'غي',
1812
+ 'ﴗ' => 'سى',
1813
+ 'ﴘ' => 'سي',
1814
+ 'ﴙ' => 'شى',
1815
+ 'ﴚ' => 'شي',
1816
+ 'ﴛ' => 'حى',
1817
+ 'ﴜ' => 'حي',
1818
+ 'ﴝ' => 'جى',
1819
+ 'ﴞ' => 'جي',
1820
+ 'ﴟ' => 'خى',
1821
+ 'ﴠ' => 'خي',
1822
+ 'ﴡ' => 'صى',
1823
+ 'ﴢ' => 'صي',
1824
+ 'ﴣ' => 'ضى',
1825
+ 'ﴤ' => 'ضي',
1826
+ 'ﴥ' => 'شج',
1827
+ 'ﴦ' => 'شح',
1828
+ 'ﴧ' => 'شخ',
1829
+ 'ﴨ' => 'شم',
1830
+ 'ﴩ' => 'شر',
1831
+ 'ﴪ' => 'سر',
1832
+ 'ﴫ' => 'صر',
1833
+ 'ﴬ' => 'ضر',
1834
+ 'ﴭ' => 'شج',
1835
+ 'ﴮ' => 'شح',
1836
+ 'ﴯ' => 'شخ',
1837
+ 'ﴰ' => 'شم',
1838
+ 'ﴱ' => 'سه',
1839
+ 'ﴲ' => 'شه',
1840
+ 'ﴳ' => 'طم',
1841
+ 'ﴴ' => 'سج',
1842
+ 'ﴵ' => 'سح',
1843
+ 'ﴶ' => 'سخ',
1844
+ 'ﴷ' => 'شج',
1845
+ 'ﴸ' => 'شح',
1846
+ 'ﴹ' => 'شخ',
1847
+ 'ﴺ' => 'طم',
1848
+ 'ﴻ' => 'ظم',
1849
+ 'ﴼ' => 'اً',
1850
+ 'ﴽ' => 'اً',
1851
+ 'ﵐ' => 'تجم',
1852
+ 'ﵑ' => 'تحج',
1853
+ 'ﵒ' => 'تحج',
1854
+ 'ﵓ' => 'تحم',
1855
+ 'ﵔ' => 'تخم',
1856
+ 'ﵕ' => 'تمج',
1857
+ 'ﵖ' => 'تمح',
1858
+ 'ﵗ' => 'تمخ',
1859
+ 'ﵘ' => 'جمح',
1860
+ 'ﵙ' => 'جمح',
1861
+ 'ﵚ' => 'حمي',
1862
+ 'ﵛ' => 'حمى',
1863
+ 'ﵜ' => 'سحج',
1864
+ 'ﵝ' => 'سجح',
1865
+ 'ﵞ' => 'سجى',
1866
+ 'ﵟ' => 'سمح',
1867
+ 'ﵠ' => 'سمح',
1868
+ 'ﵡ' => 'سمج',
1869
+ 'ﵢ' => 'سمم',
1870
+ 'ﵣ' => 'سمم',
1871
+ 'ﵤ' => 'صحح',
1872
+ 'ﵥ' => 'صحح',
1873
+ 'ﵦ' => 'صمم',
1874
+ 'ﵧ' => 'شحم',
1875
+ 'ﵨ' => 'شحم',
1876
+ 'ﵩ' => 'شجي',
1877
+ 'ﵪ' => 'شمخ',
1878
+ 'ﵫ' => 'شمخ',
1879
+ 'ﵬ' => 'شمم',
1880
+ 'ﵭ' => 'شمم',
1881
+ 'ﵮ' => 'ضحى',
1882
+ 'ﵯ' => 'ضخم',
1883
+ 'ﵰ' => 'ضخم',
1884
+ 'ﵱ' => 'طمح',
1885
+ 'ﵲ' => 'طمح',
1886
+ 'ﵳ' => 'طمم',
1887
+ 'ﵴ' => 'طمي',
1888
+ 'ﵵ' => 'عجم',
1889
+ 'ﵶ' => 'عمم',
1890
+ 'ﵷ' => 'عمم',
1891
+ 'ﵸ' => 'عمى',
1892
+ 'ﵹ' => 'غمم',
1893
+ 'ﵺ' => 'غمي',
1894
+ 'ﵻ' => 'غمى',
1895
+ 'ﵼ' => 'فخم',
1896
+ 'ﵽ' => 'فخم',
1897
+ 'ﵾ' => 'قمح',
1898
+ 'ﵿ' => 'قمم',
1899
+ 'ﶀ' => 'لحم',
1900
+ 'ﶁ' => 'لحي',
1901
+ 'ﶂ' => 'لحى',
1902
+ 'ﶃ' => 'لجج',
1903
+ 'ﶄ' => 'لجج',
1904
+ 'ﶅ' => 'لخم',
1905
+ 'ﶆ' => 'لخم',
1906
+ 'ﶇ' => 'لمح',
1907
+ 'ﶈ' => 'لمح',
1908
+ 'ﶉ' => 'محج',
1909
+ 'ﶊ' => 'محم',
1910
+ 'ﶋ' => 'محي',
1911
+ 'ﶌ' => 'مجح',
1912
+ 'ﶍ' => 'مجم',
1913
+ 'ﶎ' => 'مخج',
1914
+ 'ﶏ' => 'مخم',
1915
+ 'ﶒ' => 'مجخ',
1916
+ 'ﶓ' => 'همج',
1917
+ 'ﶔ' => 'همم',
1918
+ 'ﶕ' => 'نحم',
1919
+ 'ﶖ' => 'نحى',
1920
+ 'ﶗ' => 'نجم',
1921
+ 'ﶘ' => 'نجم',
1922
+ 'ﶙ' => 'نجى',
1923
+ 'ﶚ' => 'نمي',
1924
+ 'ﶛ' => 'نمى',
1925
+ 'ﶜ' => 'يمم',
1926
+ 'ﶝ' => 'يمم',
1927
+ 'ﶞ' => 'بخي',
1928
+ 'ﶟ' => 'تجي',
1929
+ 'ﶠ' => 'تجى',
1930
+ 'ﶡ' => 'تخي',
1931
+ 'ﶢ' => 'تخى',
1932
+ 'ﶣ' => 'تمي',
1933
+ 'ﶤ' => 'تمى',
1934
+ 'ﶥ' => 'جمي',
1935
+ 'ﶦ' => 'جحى',
1936
+ 'ﶧ' => 'جمى',
1937
+ 'ﶨ' => 'سخى',
1938
+ 'ﶩ' => 'صحي',
1939
+ 'ﶪ' => 'شحي',
1940
+ 'ﶫ' => 'ضحي',
1941
+ 'ﶬ' => 'لجي',
1942
+ 'ﶭ' => 'لمي',
1943
+ 'ﶮ' => 'يحي',
1944
+ 'ﶯ' => 'يجي',
1945
+ 'ﶰ' => 'يمي',
1946
+ 'ﶱ' => 'ممي',
1947
+ 'ﶲ' => 'قمي',
1948
+ 'ﶳ' => 'نحي',
1949
+ 'ﶴ' => 'قمح',
1950
+ 'ﶵ' => 'لحم',
1951
+ 'ﶶ' => 'عمي',
1952
+ 'ﶷ' => 'كمي',
1953
+ 'ﶸ' => 'نجح',
1954
+ 'ﶹ' => 'مخي',
1955
+ 'ﶺ' => 'لجم',
1956
+ 'ﶻ' => 'كمم',
1957
+ 'ﶼ' => 'لجم',
1958
+ 'ﶽ' => 'نجح',
1959
+ 'ﶾ' => 'جحي',
1960
+ 'ﶿ' => 'حجي',
1961
+ 'ﷀ' => 'مجي',
1962
+ 'ﷁ' => 'فمي',
1963
+ 'ﷂ' => 'بحي',
1964
+ 'ﷃ' => 'كمم',
1965
+ 'ﷄ' => 'عجم',
1966
+ 'ﷅ' => 'صمم',
1967
+ 'ﷆ' => 'سخي',
1968
+ 'ﷇ' => 'نجي',
1969
+ 'ﷰ' => 'صلے',
1970
+ 'ﷱ' => 'قلے',
1971
+ 'ﷲ' => 'الله',
1972
+ 'ﷳ' => 'اكبر',
1973
+ 'ﷴ' => 'محمد',
1974
+ 'ﷵ' => 'صلعم',
1975
+ 'ﷶ' => 'رسول',
1976
+ 'ﷷ' => 'عليه',
1977
+ 'ﷸ' => 'وسلم',
1978
+ 'ﷹ' => 'صلى',
1979
+ 'ﷺ' => 'صلى الله عليه وسلم',
1980
+ 'ﷻ' => 'جل جلاله',
1981
+ '﷼' => 'ریال',
1982
+ '︐' => ',',
1983
+ '︑' => '、',
1984
+ '︒' => '。',
1985
+ '︓' => ':',
1986
+ '︔' => ';',
1987
+ '︕' => '!',
1988
+ '︖' => '?',
1989
+ '︗' => '〖',
1990
+ '︘' => '〗',
1991
+ '︙' => '...',
1992
+ '︰' => '..',
1993
+ '︱' => '—',
1994
+ '︲' => '–',
1995
+ '︳' => '_',
1996
+ '︴' => '_',
1997
+ '︵' => '(',
1998
+ '︶' => ')',
1999
+ '︷' => '{',
2000
+ '︸' => '}',
2001
+ '︹' => '〔',
2002
+ '︺' => '〕',
2003
+ '︻' => '【',
2004
+ '︼' => '】',
2005
+ '︽' => '《',
2006
+ '︾' => '》',
2007
+ '︿' => '〈',
2008
+ '﹀' => '〉',
2009
+ '﹁' => '「',
2010
+ '﹂' => '」',
2011
+ '﹃' => '『',
2012
+ '﹄' => '』',
2013
+ '﹇' => '[',
2014
+ '﹈' => ']',
2015
+ '﹉' => ' ̅',
2016
+ '﹊' => ' ̅',
2017
+ '﹋' => ' ̅',
2018
+ '﹌' => ' ̅',
2019
+ '﹍' => '_',
2020
+ '﹎' => '_',
2021
+ '﹏' => '_',
2022
+ '﹐' => ',',
2023
+ '﹑' => '、',
2024
+ '﹒' => '.',
2025
+ '﹔' => ';',
2026
+ '﹕' => ':',
2027
+ '﹖' => '?',
2028
+ '﹗' => '!',
2029
+ '﹘' => '—',
2030
+ '﹙' => '(',
2031
+ '﹚' => ')',
2032
+ '﹛' => '{',
2033
+ '﹜' => '}',
2034
+ '﹝' => '〔',
2035
+ '﹞' => '〕',
2036
+ '﹟' => '#',
2037
+ '﹠' => '&',
2038
+ '﹡' => '*',
2039
+ '﹢' => '+',
2040
+ '﹣' => '-',
2041
+ '﹤' => '<',
2042
+ '﹥' => '>',
2043
+ '﹦' => '=',
2044
+ '﹨' => '\\',
2045
+ '﹩' => '$',
2046
+ '﹪' => '%',
2047
+ '﹫' => '@',
2048
+ 'ﹰ' => ' ً',
2049
+ 'ﹱ' => 'ـً',
2050
+ 'ﹲ' => ' ٌ',
2051
+ 'ﹴ' => ' ٍ',
2052
+ 'ﹶ' => ' َ',
2053
+ 'ﹷ' => 'ـَ',
2054
+ 'ﹸ' => ' ُ',
2055
+ 'ﹹ' => 'ـُ',
2056
+ 'ﹺ' => ' ِ',
2057
+ 'ﹻ' => 'ـِ',
2058
+ 'ﹼ' => ' ّ',
2059
+ 'ﹽ' => 'ـّ',
2060
+ 'ﹾ' => ' ْ',
2061
+ 'ﹿ' => 'ـْ',
2062
+ 'ﺀ' => 'ء',
2063
+ 'ﺁ' => 'آ',
2064
+ 'ﺂ' => 'آ',
2065
+ 'ﺃ' => 'أ',
2066
+ 'ﺄ' => 'أ',
2067
+ 'ﺅ' => 'ؤ',
2068
+ 'ﺆ' => 'ؤ',
2069
+ 'ﺇ' => 'إ',
2070
+ 'ﺈ' => 'إ',
2071
+ 'ﺉ' => 'ئ',
2072
+ 'ﺊ' => 'ئ',
2073
+ 'ﺋ' => 'ئ',
2074
+ 'ﺌ' => 'ئ',
2075
+ 'ﺍ' => 'ا',
2076
+ 'ﺎ' => 'ا',
2077
+ 'ﺏ' => 'ب',
2078
+ 'ﺐ' => 'ب',
2079
+ 'ﺑ' => 'ب',
2080
+ 'ﺒ' => 'ب',
2081
+ 'ﺓ' => 'ة',
2082
+ 'ﺔ' => 'ة',
2083
+ 'ﺕ' => 'ت',
2084
+ 'ﺖ' => 'ت',
2085
+ 'ﺗ' => 'ت',
2086
+ 'ﺘ' => 'ت',
2087
+ 'ﺙ' => 'ث',
2088
+ 'ﺚ' => 'ث',
2089
+ 'ﺛ' => 'ث',
2090
+ 'ﺜ' => 'ث',
2091
+ 'ﺝ' => 'ج',
2092
+ 'ﺞ' => 'ج',
2093
+ 'ﺟ' => 'ج',
2094
+ 'ﺠ' => 'ج',
2095
+ 'ﺡ' => 'ح',
2096
+ 'ﺢ' => 'ح',
2097
+ 'ﺣ' => 'ح',
2098
+ 'ﺤ' => 'ح',
2099
+ 'ﺥ' => 'خ',
2100
+ 'ﺦ' => 'خ',
2101
+ 'ﺧ' => 'خ',
2102
+ 'ﺨ' => 'خ',
2103
+ 'ﺩ' => 'د',
2104
+ 'ﺪ' => 'د',
2105
+ 'ﺫ' => 'ذ',
2106
+ 'ﺬ' => 'ذ',
2107
+ 'ﺭ' => 'ر',
2108
+ 'ﺮ' => 'ر',
2109
+ 'ﺯ' => 'ز',
2110
+ 'ﺰ' => 'ز',
2111
+ 'ﺱ' => 'س',
2112
+ 'ﺲ' => 'س',
2113
+ 'ﺳ' => 'س',
2114
+ 'ﺴ' => 'س',
2115
+ 'ﺵ' => 'ش',
2116
+ 'ﺶ' => 'ش',
2117
+ 'ﺷ' => 'ش',
2118
+ 'ﺸ' => 'ش',
2119
+ 'ﺹ' => 'ص',
2120
+ 'ﺺ' => 'ص',
2121
+ 'ﺻ' => 'ص',
2122
+ 'ﺼ' => 'ص',
2123
+ 'ﺽ' => 'ض',
2124
+ 'ﺾ' => 'ض',
2125
+ 'ﺿ' => 'ض',
2126
+ 'ﻀ' => 'ض',
2127
+ 'ﻁ' => 'ط',
2128
+ 'ﻂ' => 'ط',
2129
+ 'ﻃ' => 'ط',
2130
+ 'ﻄ' => 'ط',
2131
+ 'ﻅ' => 'ظ',
2132
+ 'ﻆ' => 'ظ',
2133
+ 'ﻇ' => 'ظ',
2134
+ 'ﻈ' => 'ظ',
2135
+ 'ﻉ' => 'ع',
2136
+ 'ﻊ' => 'ع',
2137
+ 'ﻋ' => 'ع',
2138
+ 'ﻌ' => 'ع',
2139
+ 'ﻍ' => 'غ',
2140
+ 'ﻎ' => 'غ',
2141
+ 'ﻏ' => 'غ',
2142
+ 'ﻐ' => 'غ',
2143
+ 'ﻑ' => 'ف',
2144
+ 'ﻒ' => 'ف',
2145
+ 'ﻓ' => 'ف',
2146
+ 'ﻔ' => 'ف',
2147
+ 'ﻕ' => 'ق',
2148
+ 'ﻖ' => 'ق',
2149
+ 'ﻗ' => 'ق',
2150
+ 'ﻘ' => 'ق',
2151
+ 'ﻙ' => 'ك',
2152
+ 'ﻚ' => 'ك',
2153
+ 'ﻛ' => 'ك',
2154
+ 'ﻜ' => 'ك',
2155
+ 'ﻝ' => 'ل',
2156
+ 'ﻞ' => 'ل',
2157
+ 'ﻟ' => 'ل',
2158
+ 'ﻠ' => 'ل',
2159
+ 'ﻡ' => 'م',
2160
+ 'ﻢ' => 'م',
2161
+ 'ﻣ' => 'م',
2162
+ 'ﻤ' => 'م',
2163
+ 'ﻥ' => 'ن',
2164
+ 'ﻦ' => 'ن',
2165
+ 'ﻧ' => 'ن',
2166
+ 'ﻨ' => 'ن',
2167
+ 'ﻩ' => 'ه',
2168
+ 'ﻪ' => 'ه',
2169
+ 'ﻫ' => 'ه',
2170
+ 'ﻬ' => 'ه',
2171
+ 'ﻭ' => 'و',
2172
+ 'ﻮ' => 'و',
2173
+ 'ﻯ' => 'ى',
2174
+ 'ﻰ' => 'ى',
2175
+ 'ﻱ' => 'ي',
2176
+ 'ﻲ' => 'ي',
2177
+ 'ﻳ' => 'ي',
2178
+ 'ﻴ' => 'ي',
2179
+ 'ﻵ' => 'لآ',
2180
+ 'ﻶ' => 'لآ',
2181
+ 'ﻷ' => 'لأ',
2182
+ 'ﻸ' => 'لأ',
2183
+ 'ﻹ' => 'لإ',
2184
+ 'ﻺ' => 'لإ',
2185
+ 'ﻻ' => 'لا',
2186
+ 'ﻼ' => 'لا',
2187
+ '!' => '!',
2188
+ '"' => '"',
2189
+ '#' => '#',
2190
+ '$' => '$',
2191
+ '%' => '%',
2192
+ '&' => '&',
2193
+ ''' => '\'',
2194
+ '(' => '(',
2195
+ ')' => ')',
2196
+ '*' => '*',
2197
+ '+' => '+',
2198
+ ',' => ',',
2199
+ '-' => '-',
2200
+ '.' => '.',
2201
+ '/' => '/',
2202
+ '0' => '0',
2203
+ '1' => '1',
2204
+ '2' => '2',
2205
+ '3' => '3',
2206
+ '4' => '4',
2207
+ '5' => '5',
2208
+ '6' => '6',
2209
+ '7' => '7',
2210
+ '8' => '8',
2211
+ '9' => '9',
2212
+ ':' => ':',
2213
+ ';' => ';',
2214
+ '<' => '<',
2215
+ '=' => '=',
2216
+ '>' => '>',
2217
+ '?' => '?',
2218
+ '@' => '@',
2219
+ 'A' => 'A',
2220
+ 'B' => 'B',
2221
+ 'C' => 'C',
2222
+ 'D' => 'D',
2223
+ 'E' => 'E',
2224
+ 'F' => 'F',
2225
+ 'G' => 'G',
2226
+ 'H' => 'H',
2227
+ 'I' => 'I',
2228
+ 'J' => 'J',
2229
+ 'K' => 'K',
2230
+ 'L' => 'L',
2231
+ 'M' => 'M',
2232
+ 'N' => 'N',
2233
+ 'O' => 'O',
2234
+ 'P' => 'P',
2235
+ 'Q' => 'Q',
2236
+ 'R' => 'R',
2237
+ 'S' => 'S',
2238
+ 'T' => 'T',
2239
+ 'U' => 'U',
2240
+ 'V' => 'V',
2241
+ 'W' => 'W',
2242
+ 'X' => 'X',
2243
+ 'Y' => 'Y',
2244
+ 'Z' => 'Z',
2245
+ '[' => '[',
2246
+ '\' => '\\',
2247
+ ']' => ']',
2248
+ '^' => '^',
2249
+ '_' => '_',
2250
+ '`' => '`',
2251
+ 'a' => 'a',
2252
+ 'b' => 'b',
2253
+ 'c' => 'c',
2254
+ 'd' => 'd',
2255
+ 'e' => 'e',
2256
+ 'f' => 'f',
2257
+ 'g' => 'g',
2258
+ 'h' => 'h',
2259
+ 'i' => 'i',
2260
+ 'j' => 'j',
2261
+ 'k' => 'k',
2262
+ 'l' => 'l',
2263
+ 'm' => 'm',
2264
+ 'n' => 'n',
2265
+ 'o' => 'o',
2266
+ 'p' => 'p',
2267
+ 'q' => 'q',
2268
+ 'r' => 'r',
2269
+ 's' => 's',
2270
+ 't' => 't',
2271
+ 'u' => 'u',
2272
+ 'v' => 'v',
2273
+ 'w' => 'w',
2274
+ 'x' => 'x',
2275
+ 'y' => 'y',
2276
+ 'z' => 'z',
2277
+ '{' => '{',
2278
+ '|' => '|',
2279
+ '}' => '}',
2280
+ '~' => '~',
2281
+ '⦅' => '⦅',
2282
+ '⦆' => '⦆',
2283
+ '。' => '。',
2284
+ '「' => '「',
2285
+ '」' => '」',
2286
+ '、' => '、',
2287
+ '・' => '・',
2288
+ 'ヲ' => 'ヲ',
2289
+ 'ァ' => 'ァ',
2290
+ 'ィ' => 'ィ',
2291
+ 'ゥ' => 'ゥ',
2292
+ 'ェ' => 'ェ',
2293
+ 'ォ' => 'ォ',
2294
+ 'ャ' => 'ャ',
2295
+ 'ュ' => 'ュ',
2296
+ 'ョ' => 'ョ',
2297
+ 'ッ' => 'ッ',
2298
+ 'ー' => 'ー',
2299
+ 'ア' => 'ア',
2300
+ 'イ' => 'イ',
2301
+ 'ウ' => 'ウ',
2302
+ 'エ' => 'エ',
2303
+ 'オ' => 'オ',
2304
+ 'カ' => 'カ',
2305
+ 'キ' => 'キ',
2306
+ 'ク' => 'ク',
2307
+ 'ケ' => 'ケ',
2308
+ 'コ' => 'コ',
2309
+ 'サ' => 'サ',
2310
+ 'シ' => 'シ',
2311
+ 'ス' => 'ス',
2312
+ 'セ' => 'セ',
2313
+ 'ソ' => 'ソ',
2314
+ 'タ' => 'タ',
2315
+ 'チ' => 'チ',
2316
+ 'ツ' => 'ツ',
2317
+ 'テ' => 'テ',
2318
+ 'ト' => 'ト',
2319
+ 'ナ' => 'ナ',
2320
+ 'ニ' => 'ニ',
2321
+ 'ヌ' => 'ヌ',
2322
+ 'ネ' => 'ネ',
2323
+ 'ノ' => 'ノ',
2324
+ 'ハ' => 'ハ',
2325
+ 'ヒ' => 'ヒ',
2326
+ 'フ' => 'フ',
2327
+ 'ヘ' => 'ヘ',
2328
+ 'ホ' => 'ホ',
2329
+ 'マ' => 'マ',
2330
+ 'ミ' => 'ミ',
2331
+ 'ム' => 'ム',
2332
+ 'メ' => 'メ',
2333
+ 'モ' => 'モ',
2334
+ 'ヤ' => 'ヤ',
2335
+ 'ユ' => 'ユ',
2336
+ 'ヨ' => 'ヨ',
2337
+ 'ラ' => 'ラ',
2338
+ 'リ' => 'リ',
2339
+ 'ル' => 'ル',
2340
+ 'レ' => 'レ',
2341
+ 'ロ' => 'ロ',
2342
+ 'ワ' => 'ワ',
2343
+ 'ン' => 'ン',
2344
+ '゙' => '゙',
2345
+ '゚' => '゚',
2346
+ 'ᅠ' => 'ᅠ',
2347
+ 'ᄀ' => 'ᄀ',
2348
+ 'ᄁ' => 'ᄁ',
2349
+ 'ᆪ' => 'ᆪ',
2350
+ 'ᄂ' => 'ᄂ',
2351
+ 'ᆬ' => 'ᆬ',
2352
+ 'ᆭ' => 'ᆭ',
2353
+ 'ᄃ' => 'ᄃ',
2354
+ 'ᄄ' => 'ᄄ',
2355
+ 'ᄅ' => 'ᄅ',
2356
+ 'ᆰ' => 'ᆰ',
2357
+ 'ᆱ' => 'ᆱ',
2358
+ 'ᆲ' => 'ᆲ',
2359
+ 'ᆳ' => 'ᆳ',
2360
+ 'ᆴ' => 'ᆴ',
2361
+ 'ᆵ' => 'ᆵ',
2362
+ 'ᄚ' => 'ᄚ',
2363
+ 'ᄆ' => 'ᄆ',
2364
+ 'ᄇ' => 'ᄇ',
2365
+ 'ᄈ' => 'ᄈ',
2366
+ 'ᄡ' => 'ᄡ',
2367
+ 'ᄉ' => 'ᄉ',
2368
+ 'ᄊ' => 'ᄊ',
2369
+ 'ᄋ' => 'ᄋ',
2370
+ 'ᄌ' => 'ᄌ',
2371
+ 'ᄍ' => 'ᄍ',
2372
+ 'ᄎ' => 'ᄎ',
2373
+ 'ᄏ' => 'ᄏ',
2374
+ 'ᄐ' => 'ᄐ',
2375
+ 'ᄑ' => 'ᄑ',
2376
+ 'ᄒ' => 'ᄒ',
2377
+ 'ᅡ' => 'ᅡ',
2378
+ 'ᅢ' => 'ᅢ',
2379
+ 'ᅣ' => 'ᅣ',
2380
+ 'ᅤ' => 'ᅤ',
2381
+ 'ᅥ' => 'ᅥ',
2382
+ 'ᅦ' => 'ᅦ',
2383
+ 'ᅧ' => 'ᅧ',
2384
+ 'ᅨ' => 'ᅨ',
2385
+ 'ᅩ' => 'ᅩ',
2386
+ 'ᅪ' => 'ᅪ',
2387
+ 'ᅫ' => 'ᅫ',
2388
+ 'ᅬ' => 'ᅬ',
2389
+ 'ᅭ' => 'ᅭ',
2390
+ 'ᅮ' => 'ᅮ',
2391
+ 'ᅯ' => 'ᅯ',
2392
+ 'ᅰ' => 'ᅰ',
2393
+ 'ᅱ' => 'ᅱ',
2394
+ 'ᅲ' => 'ᅲ',
2395
+ 'ᅳ' => 'ᅳ',
2396
+ 'ᅴ' => 'ᅴ',
2397
+ 'ᅵ' => 'ᅵ',
2398
+ '¢' => '¢',
2399
+ '£' => '£',
2400
+ '¬' => '¬',
2401
+ ' ̄' => ' ̄',
2402
+ '¦' => '¦',
2403
+ '¥' => '¥',
2404
+ '₩' => '₩',
2405
+ '│' => '│',
2406
+ '←' => '←',
2407
+ '↑' => '↑',
2408
+ '→' => '→',
2409
+ '↓' => '↓',
2410
+ '■' => '■',
2411
+ '○' => '○',
2412
+ '𝐀' => 'A',
2413
+ '𝐁' => 'B',
2414
+ '𝐂' => 'C',
2415
+ '𝐃' => 'D',
2416
+ '𝐄' => 'E',
2417
+ '𝐅' => 'F',
2418
+ '𝐆' => 'G',
2419
+ '𝐇' => 'H',
2420
+ '𝐈' => 'I',
2421
+ '𝐉' => 'J',
2422
+ '𝐊' => 'K',
2423
+ '𝐋' => 'L',
2424
+ '𝐌' => 'M',
2425
+ '𝐍' => 'N',
2426
+ '𝐎' => 'O',
2427
+ '𝐏' => 'P',
2428
+ '𝐐' => 'Q',
2429
+ '𝐑' => 'R',
2430
+ '𝐒' => 'S',
2431
+ '𝐓' => 'T',
2432
+ '𝐔' => 'U',
2433
+ '𝐕' => 'V',
2434
+ '𝐖' => 'W',
2435
+ '𝐗' => 'X',
2436
+ '𝐘' => 'Y',
2437
+ '𝐙' => 'Z',
2438
+ '𝐚' => 'a',
2439
+ '𝐛' => 'b',
2440
+ '𝐜' => 'c',
2441
+ '𝐝' => 'd',
2442
+ '𝐞' => 'e',
2443
+ '𝐟' => 'f',
2444
+ '𝐠' => 'g',
2445
+ '𝐡' => 'h',
2446
+ '𝐢' => 'i',
2447
+ '𝐣' => 'j',
2448
+ '𝐤' => 'k',
2449
+ '𝐥' => 'l',
2450
+ '𝐦' => 'm',
2451
+ '𝐧' => 'n',
2452
+ '𝐨' => 'o',
2453
+ '𝐩' => 'p',
2454
+ '𝐪' => 'q',
2455
+ '𝐫' => 'r',
2456
+ '𝐬' => 's',
2457
+ '𝐭' => 't',
2458
+ '𝐮' => 'u',
2459
+ '𝐯' => 'v',
2460
+ '𝐰' => 'w',
2461
+ '𝐱' => 'x',
2462
+ '𝐲' => 'y',
2463
+ '𝐳' => 'z',
2464
+ '𝐴' => 'A',
2465
+ '𝐵' => 'B',
2466
+ '𝐶' => 'C',
2467
+ '𝐷' => 'D',
2468
+ '𝐸' => 'E',
2469
+ '𝐹' => 'F',
2470
+ '𝐺' => 'G',
2471
+ '𝐻' => 'H',
2472
+ '𝐼' => 'I',
2473
+ '𝐽' => 'J',
2474
+ '𝐾' => 'K',
2475
+ '𝐿' => 'L',
2476
+ '𝑀' => 'M',
2477
+ '𝑁' => 'N',
2478
+ '𝑂' => 'O',
2479
+ '𝑃' => 'P',
2480
+ '𝑄' => 'Q',
2481
+ '𝑅' => 'R',
2482
+ '𝑆' => 'S',
2483
+ '𝑇' => 'T',
2484
+ '𝑈' => 'U',
2485
+ '𝑉' => 'V',
2486
+ '𝑊' => 'W',
2487
+ '𝑋' => 'X',
2488
+ '𝑌' => 'Y',
2489
+ '𝑍' => 'Z',
2490
+ '𝑎' => 'a',
2491
+ '𝑏' => 'b',
2492
+ '𝑐' => 'c',
2493
+ '𝑑' => 'd',
2494
+ '𝑒' => 'e',
2495
+ '𝑓' => 'f',
2496
+ '𝑔' => 'g',
2497
+ '𝑖' => 'i',
2498
+ '𝑗' => 'j',
2499
+ '𝑘' => 'k',
2500
+ '𝑙' => 'l',
2501
+ '𝑚' => 'm',
2502
+ '𝑛' => 'n',
2503
+ '𝑜' => 'o',
2504
+ '𝑝' => 'p',
2505
+ '𝑞' => 'q',
2506
+ '𝑟' => 'r',
2507
+ '𝑠' => 's',
2508
+ '𝑡' => 't',
2509
+ '𝑢' => 'u',
2510
+ '𝑣' => 'v',
2511
+ '𝑤' => 'w',
2512
+ '𝑥' => 'x',
2513
+ '𝑦' => 'y',
2514
+ '𝑧' => 'z',
2515
+ '𝑨' => 'A',
2516
+ '𝑩' => 'B',
2517
+ '𝑪' => 'C',
2518
+ '𝑫' => 'D',
2519
+ '𝑬' => 'E',
2520
+ '𝑭' => 'F',
2521
+ '𝑮' => 'G',
2522
+ '𝑯' => 'H',
2523
+ '𝑰' => 'I',
2524
+ '𝑱' => 'J',
2525
+ '𝑲' => 'K',
2526
+ '𝑳' => 'L',
2527
+ '𝑴' => 'M',
2528
+ '𝑵' => 'N',
2529
+ '𝑶' => 'O',
2530
+ '𝑷' => 'P',
2531
+ '𝑸' => 'Q',
2532
+ '𝑹' => 'R',
2533
+ '𝑺' => 'S',
2534
+ '𝑻' => 'T',
2535
+ '𝑼' => 'U',
2536
+ '𝑽' => 'V',
2537
+ '𝑾' => 'W',
2538
+ '𝑿' => 'X',
2539
+ '𝒀' => 'Y',
2540
+ '𝒁' => 'Z',
2541
+ '𝒂' => 'a',
2542
+ '𝒃' => 'b',
2543
+ '𝒄' => 'c',
2544
+ '𝒅' => 'd',
2545
+ '𝒆' => 'e',
2546
+ '𝒇' => 'f',
2547
+ '𝒈' => 'g',
2548
+ '𝒉' => 'h',
2549
+ '𝒊' => 'i',
2550
+ '𝒋' => 'j',
2551
+ '𝒌' => 'k',
2552
+ '𝒍' => 'l',
2553
+ '𝒎' => 'm',
2554
+ '𝒏' => 'n',
2555
+ '𝒐' => 'o',
2556
+ '𝒑' => 'p',
2557
+ '𝒒' => 'q',
2558
+ '𝒓' => 'r',
2559
+ '𝒔' => 's',
2560
+ '𝒕' => 't',
2561
+ '𝒖' => 'u',
2562
+ '𝒗' => 'v',
2563
+ '𝒘' => 'w',
2564
+ '𝒙' => 'x',
2565
+ '𝒚' => 'y',
2566
+ '𝒛' => 'z',
2567
+ '𝒜' => 'A',
2568
+ '𝒞' => 'C',
2569
+ '𝒟' => 'D',
2570
+ '𝒢' => 'G',
2571
+ '𝒥' => 'J',
2572
+ '𝒦' => 'K',
2573
+ '𝒩' => 'N',
2574
+ '𝒪' => 'O',
2575
+ '𝒫' => 'P',
2576
+ '𝒬' => 'Q',
2577
+ '𝒮' => 'S',
2578
+ '𝒯' => 'T',
2579
+ '𝒰' => 'U',
2580
+ '𝒱' => 'V',
2581
+ '𝒲' => 'W',
2582
+ '𝒳' => 'X',
2583
+ '𝒴' => 'Y',
2584
+ '𝒵' => 'Z',
2585
+ '𝒶' => 'a',
2586
+ '𝒷' => 'b',
2587
+ '𝒸' => 'c',
2588
+ '𝒹' => 'd',
2589
+ '𝒻' => 'f',
2590
+ '𝒽' => 'h',
2591
+ '𝒾' => 'i',
2592
+ '𝒿' => 'j',
2593
+ '𝓀' => 'k',
2594
+ '𝓁' => 'l',
2595
+ '𝓂' => 'm',
2596
+ '𝓃' => 'n',
2597
+ '𝓅' => 'p',
2598
+ '𝓆' => 'q',
2599
+ '𝓇' => 'r',
2600
+ '𝓈' => 's',
2601
+ '𝓉' => 't',
2602
+ '𝓊' => 'u',
2603
+ '𝓋' => 'v',
2604
+ '𝓌' => 'w',
2605
+ '𝓍' => 'x',
2606
+ '𝓎' => 'y',
2607
+ '𝓏' => 'z',
2608
+ '𝓐' => 'A',
2609
+ '𝓑' => 'B',
2610
+ '𝓒' => 'C',
2611
+ '𝓓' => 'D',
2612
+ '𝓔' => 'E',
2613
+ '𝓕' => 'F',
2614
+ '𝓖' => 'G',
2615
+ '𝓗' => 'H',
2616
+ '𝓘' => 'I',
2617
+ '𝓙' => 'J',
2618
+ '𝓚' => 'K',
2619
+ '𝓛' => 'L',
2620
+ '𝓜' => 'M',
2621
+ '𝓝' => 'N',
2622
+ '𝓞' => 'O',
2623
+ '𝓟' => 'P',
2624
+ '𝓠' => 'Q',
2625
+ '𝓡' => 'R',
2626
+ '𝓢' => 'S',
2627
+ '𝓣' => 'T',
2628
+ '𝓤' => 'U',
2629
+ '𝓥' => 'V',
2630
+ '𝓦' => 'W',
2631
+ '𝓧' => 'X',
2632
+ '𝓨' => 'Y',
2633
+ '𝓩' => 'Z',
2634
+ '𝓪' => 'a',
2635
+ '𝓫' => 'b',
2636
+ '𝓬' => 'c',
2637
+ '𝓭' => 'd',
2638
+ '𝓮' => 'e',
2639
+ '𝓯' => 'f',
2640
+ '𝓰' => 'g',
2641
+ '𝓱' => 'h',
2642
+ '𝓲' => 'i',
2643
+ '𝓳' => 'j',
2644
+ '𝓴' => 'k',
2645
+ '𝓵' => 'l',
2646
+ '𝓶' => 'm',
2647
+ '𝓷' => 'n',
2648
+ '𝓸' => 'o',
2649
+ '𝓹' => 'p',
2650
+ '𝓺' => 'q',
2651
+ '𝓻' => 'r',
2652
+ '𝓼' => 's',
2653
+ '𝓽' => 't',
2654
+ '𝓾' => 'u',
2655
+ '𝓿' => 'v',
2656
+ '𝔀' => 'w',
2657
+ '𝔁' => 'x',
2658
+ '𝔂' => 'y',
2659
+ '𝔃' => 'z',
2660
+ '𝔄' => 'A',
2661
+ '𝔅' => 'B',
2662
+ '𝔇' => 'D',
2663
+ '𝔈' => 'E',
2664
+ '𝔉' => 'F',
2665
+ '𝔊' => 'G',
2666
+ '𝔍' => 'J',
2667
+ '𝔎' => 'K',
2668
+ '𝔏' => 'L',
2669
+ '𝔐' => 'M',
2670
+ '𝔑' => 'N',
2671
+ '𝔒' => 'O',
2672
+ '𝔓' => 'P',
2673
+ '𝔔' => 'Q',
2674
+ '𝔖' => 'S',
2675
+ '𝔗' => 'T',
2676
+ '𝔘' => 'U',
2677
+ '𝔙' => 'V',
2678
+ '𝔚' => 'W',
2679
+ '𝔛' => 'X',
2680
+ '𝔜' => 'Y',
2681
+ '𝔞' => 'a',
2682
+ '𝔟' => 'b',
2683
+ '𝔠' => 'c',
2684
+ '𝔡' => 'd',
2685
+ '𝔢' => 'e',
2686
+ '𝔣' => 'f',
2687
+ '𝔤' => 'g',
2688
+ '𝔥' => 'h',
2689
+ '𝔦' => 'i',
2690
+ '𝔧' => 'j',
2691
+ '𝔨' => 'k',
2692
+ '𝔩' => 'l',
2693
+ '𝔪' => 'm',
2694
+ '𝔫' => 'n',
2695
+ '𝔬' => 'o',
2696
+ '𝔭' => 'p',
2697
+ '𝔮' => 'q',
2698
+ '𝔯' => 'r',
2699
+ '𝔰' => 's',
2700
+ '𝔱' => 't',
2701
+ '𝔲' => 'u',
2702
+ '𝔳' => 'v',
2703
+ '𝔴' => 'w',
2704
+ '𝔵' => 'x',
2705
+ '𝔶' => 'y',
2706
+ '𝔷' => 'z',
2707
+ '𝔸' => 'A',
2708
+ '𝔹' => 'B',
2709
+ '𝔻' => 'D',
2710
+ '𝔼' => 'E',
2711
+ '𝔽' => 'F',
2712
+ '𝔾' => 'G',
2713
+ '𝕀' => 'I',
2714
+ '𝕁' => 'J',
2715
+ '𝕂' => 'K',
2716
+ '𝕃' => 'L',
2717
+ '𝕄' => 'M',
2718
+ '𝕆' => 'O',
2719
+ '𝕊' => 'S',
2720
+ '𝕋' => 'T',
2721
+ '𝕌' => 'U',
2722
+ '𝕍' => 'V',
2723
+ '𝕎' => 'W',
2724
+ '𝕏' => 'X',
2725
+ '𝕐' => 'Y',
2726
+ '𝕒' => 'a',
2727
+ '𝕓' => 'b',
2728
+ '𝕔' => 'c',
2729
+ '𝕕' => 'd',
2730
+ '𝕖' => 'e',
2731
+ '𝕗' => 'f',
2732
+ '𝕘' => 'g',
2733
+ '𝕙' => 'h',
2734
+ '𝕚' => 'i',
2735
+ '𝕛' => 'j',
2736
+ '𝕜' => 'k',
2737
+ '𝕝' => 'l',
2738
+ '𝕞' => 'm',
2739
+ '𝕟' => 'n',
2740
+ '𝕠' => 'o',
2741
+ '𝕡' => 'p',
2742
+ '𝕢' => 'q',
2743
+ '𝕣' => 'r',
2744
+ '𝕤' => 's',
2745
+ '𝕥' => 't',
2746
+ '𝕦' => 'u',
2747
+ '𝕧' => 'v',
2748
+ '𝕨' => 'w',
2749
+ '𝕩' => 'x',
2750
+ '𝕪' => 'y',
2751
+ '𝕫' => 'z',
2752
+ '𝕬' => 'A',
2753
+ '𝕭' => 'B',
2754
+ '𝕮' => 'C',
2755
+ '𝕯' => 'D',
2756
+ '𝕰' => 'E',
2757
+ '𝕱' => 'F',
2758
+ '𝕲' => 'G',
2759
+ '𝕳' => 'H',
2760
+ '𝕴' => 'I',
2761
+ '𝕵' => 'J',
2762
+ '𝕶' => 'K',
2763
+ '𝕷' => 'L',
2764
+ '𝕸' => 'M',
2765
+ '𝕹' => 'N',
2766
+ '𝕺' => 'O',
2767
+ '𝕻' => 'P',
2768
+ '𝕼' => 'Q',
2769
+ '𝕽' => 'R',
2770
+ '𝕾' => 'S',
2771
+ '𝕿' => 'T',
2772
+ '𝖀' => 'U',
2773
+ '𝖁' => 'V',
2774
+ '𝖂' => 'W',
2775
+ '𝖃' => 'X',
2776
+ '𝖄' => 'Y',
2777
+ '𝖅' => 'Z',
2778
+ '𝖆' => 'a',
2779
+ '𝖇' => 'b',
2780
+ '𝖈' => 'c',
2781
+ '𝖉' => 'd',
2782
+ '𝖊' => 'e',
2783
+ '𝖋' => 'f',
2784
+ '𝖌' => 'g',
2785
+ '𝖍' => 'h',
2786
+ '𝖎' => 'i',
2787
+ '𝖏' => 'j',
2788
+ '𝖐' => 'k',
2789
+ '𝖑' => 'l',
2790
+ '𝖒' => 'm',
2791
+ '𝖓' => 'n',
2792
+ '𝖔' => 'o',
2793
+ '𝖕' => 'p',
2794
+ '𝖖' => 'q',
2795
+ '𝖗' => 'r',
2796
+ '𝖘' => 's',
2797
+ '𝖙' => 't',
2798
+ '𝖚' => 'u',
2799
+ '𝖛' => 'v',
2800
+ '𝖜' => 'w',
2801
+ '𝖝' => 'x',
2802
+ '𝖞' => 'y',
2803
+ '𝖟' => 'z',
2804
+ '𝖠' => 'A',
2805
+ '𝖡' => 'B',
2806
+ '𝖢' => 'C',
2807
+ '𝖣' => 'D',
2808
+ '𝖤' => 'E',
2809
+ '𝖥' => 'F',
2810
+ '𝖦' => 'G',
2811
+ '𝖧' => 'H',
2812
+ '𝖨' => 'I',
2813
+ '𝖩' => 'J',
2814
+ '𝖪' => 'K',
2815
+ '𝖫' => 'L',
2816
+ '𝖬' => 'M',
2817
+ '𝖭' => 'N',
2818
+ '𝖮' => 'O',
2819
+ '𝖯' => 'P',
2820
+ '𝖰' => 'Q',
2821
+ '𝖱' => 'R',
2822
+ '𝖲' => 'S',
2823
+ '𝖳' => 'T',
2824
+ '𝖴' => 'U',
2825
+ '𝖵' => 'V',
2826
+ '𝖶' => 'W',
2827
+ '𝖷' => 'X',
2828
+ '𝖸' => 'Y',
2829
+ '𝖹' => 'Z',
2830
+ '𝖺' => 'a',
2831
+ '𝖻' => 'b',
2832
+ '𝖼' => 'c',
2833
+ '𝖽' => 'd',
2834
+ '𝖾' => 'e',
2835
+ '𝖿' => 'f',
2836
+ '𝗀' => 'g',
2837
+ '𝗁' => 'h',
2838
+ '𝗂' => 'i',
2839
+ '𝗃' => 'j',
2840
+ '𝗄' => 'k',
2841
+ '𝗅' => 'l',
2842
+ '𝗆' => 'm',
2843
+ '𝗇' => 'n',
2844
+ '𝗈' => 'o',
2845
+ '𝗉' => 'p',
2846
+ '𝗊' => 'q',
2847
+ '𝗋' => 'r',
2848
+ '𝗌' => 's',
2849
+ '𝗍' => 't',
2850
+ '𝗎' => 'u',
2851
+ '𝗏' => 'v',
2852
+ '𝗐' => 'w',
2853
+ '𝗑' => 'x',
2854
+ '𝗒' => 'y',
2855
+ '𝗓' => 'z',
2856
+ '𝗔' => 'A',
2857
+ '𝗕' => 'B',
2858
+ '𝗖' => 'C',
2859
+ '𝗗' => 'D',
2860
+ '𝗘' => 'E',
2861
+ '𝗙' => 'F',
2862
+ '𝗚' => 'G',
2863
+ '𝗛' => 'H',
2864
+ '𝗜' => 'I',
2865
+ '𝗝' => 'J',
2866
+ '𝗞' => 'K',
2867
+ '𝗟' => 'L',
2868
+ '𝗠' => 'M',
2869
+ '𝗡' => 'N',
2870
+ '𝗢' => 'O',
2871
+ '𝗣' => 'P',
2872
+ '𝗤' => 'Q',
2873
+ '𝗥' => 'R',
2874
+ '𝗦' => 'S',
2875
+ '𝗧' => 'T',
2876
+ '𝗨' => 'U',
2877
+ '𝗩' => 'V',
2878
+ '𝗪' => 'W',
2879
+ '𝗫' => 'X',
2880
+ '𝗬' => 'Y',
2881
+ '𝗭' => 'Z',
2882
+ '𝗮' => 'a',
2883
+ '𝗯' => 'b',
2884
+ '𝗰' => 'c',
2885
+ '𝗱' => 'd',
2886
+ '𝗲' => 'e',
2887
+ '𝗳' => 'f',
2888
+ '𝗴' => 'g',
2889
+ '𝗵' => 'h',
2890
+ '𝗶' => 'i',
2891
+ '𝗷' => 'j',
2892
+ '𝗸' => 'k',
2893
+ '𝗹' => 'l',
2894
+ '𝗺' => 'm',
2895
+ '𝗻' => 'n',
2896
+ '𝗼' => 'o',
2897
+ '𝗽' => 'p',
2898
+ '𝗾' => 'q',
2899
+ '𝗿' => 'r',
2900
+ '𝘀' => 's',
2901
+ '𝘁' => 't',
2902
+ '𝘂' => 'u',
2903
+ '𝘃' => 'v',
2904
+ '𝘄' => 'w',
2905
+ '𝘅' => 'x',
2906
+ '𝘆' => 'y',
2907
+ '𝘇' => 'z',
2908
+ '𝘈' => 'A',
2909
+ '𝘉' => 'B',
2910
+ '𝘊' => 'C',
2911
+ '𝘋' => 'D',
2912
+ '𝘌' => 'E',
2913
+ '𝘍' => 'F',
2914
+ '𝘎' => 'G',
2915
+ '𝘏' => 'H',
2916
+ '𝘐' => 'I',
2917
+ '𝘑' => 'J',
2918
+ '𝘒' => 'K',
2919
+ '𝘓' => 'L',
2920
+ '𝘔' => 'M',
2921
+ '𝘕' => 'N',
2922
+ '𝘖' => 'O',
2923
+ '𝘗' => 'P',
2924
+ '𝘘' => 'Q',
2925
+ '𝘙' => 'R',
2926
+ '𝘚' => 'S',
2927
+ '𝘛' => 'T',
2928
+ '𝘜' => 'U',
2929
+ '𝘝' => 'V',
2930
+ '𝘞' => 'W',
2931
+ '𝘟' => 'X',
2932
+ '𝘠' => 'Y',
2933
+ '𝘡' => 'Z',
2934
+ '𝘢' => 'a',
2935
+ '𝘣' => 'b',
2936
+ '𝘤' => 'c',
2937
+ '𝘥' => 'd',
2938
+ '𝘦' => 'e',
2939
+ '𝘧' => 'f',
2940
+ '𝘨' => 'g',
2941
+ '𝘩' => 'h',
2942
+ '𝘪' => 'i',
2943
+ '𝘫' => 'j',
2944
+ '𝘬' => 'k',
2945
+ '𝘭' => 'l',
2946
+ '𝘮' => 'm',
2947
+ '𝘯' => 'n',
2948
+ '𝘰' => 'o',
2949
+ '𝘱' => 'p',
2950
+ '𝘲' => 'q',
2951
+ '𝘳' => 'r',
2952
+ '𝘴' => 's',
2953
+ '𝘵' => 't',
2954
+ '𝘶' => 'u',
2955
+ '𝘷' => 'v',
2956
+ '𝘸' => 'w',
2957
+ '𝘹' => 'x',
2958
+ '𝘺' => 'y',
2959
+ '𝘻' => 'z',
2960
+ '𝘼' => 'A',
2961
+ '𝘽' => 'B',
2962
+ '𝘾' => 'C',
2963
+ '𝘿' => 'D',
2964
+ '𝙀' => 'E',
2965
+ '𝙁' => 'F',
2966
+ '𝙂' => 'G',
2967
+ '𝙃' => 'H',
2968
+ '𝙄' => 'I',
2969
+ '𝙅' => 'J',
2970
+ '𝙆' => 'K',
2971
+ '𝙇' => 'L',
2972
+ '𝙈' => 'M',
2973
+ '𝙉' => 'N',
2974
+ '𝙊' => 'O',
2975
+ '𝙋' => 'P',
2976
+ '𝙌' => 'Q',
2977
+ '𝙍' => 'R',
2978
+ '𝙎' => 'S',
2979
+ '𝙏' => 'T',
2980
+ '𝙐' => 'U',
2981
+ '𝙑' => 'V',
2982
+ '𝙒' => 'W',
2983
+ '𝙓' => 'X',
2984
+ '𝙔' => 'Y',
2985
+ '𝙕' => 'Z',
2986
+ '𝙖' => 'a',
2987
+ '𝙗' => 'b',
2988
+ '𝙘' => 'c',
2989
+ '𝙙' => 'd',
2990
+ '𝙚' => 'e',
2991
+ '𝙛' => 'f',
2992
+ '𝙜' => 'g',
2993
+ '𝙝' => 'h',
2994
+ '𝙞' => 'i',
2995
+ '𝙟' => 'j',
2996
+ '𝙠' => 'k',
2997
+ '𝙡' => 'l',
2998
+ '𝙢' => 'm',
2999
+ '𝙣' => 'n',
3000
+ '𝙤' => 'o',
3001
+ '𝙥' => 'p',
3002
+ '𝙦' => 'q',
3003
+ '𝙧' => 'r',
3004
+ '𝙨' => 's',
3005
+ '𝙩' => 't',
3006
+ '𝙪' => 'u',
3007
+ '𝙫' => 'v',
3008
+ '𝙬' => 'w',
3009
+ '𝙭' => 'x',
3010
+ '𝙮' => 'y',
3011
+ '𝙯' => 'z',
3012
+ '𝙰' => 'A',
3013
+ '𝙱' => 'B',
3014
+ '𝙲' => 'C',
3015
+ '𝙳' => 'D',
3016
+ '𝙴' => 'E',
3017
+ '𝙵' => 'F',
3018
+ '𝙶' => 'G',
3019
+ '𝙷' => 'H',
3020
+ '𝙸' => 'I',
3021
+ '𝙹' => 'J',
3022
+ '𝙺' => 'K',
3023
+ '𝙻' => 'L',
3024
+ '𝙼' => 'M',
3025
+ '𝙽' => 'N',
3026
+ '𝙾' => 'O',
3027
+ '𝙿' => 'P',
3028
+ '𝚀' => 'Q',
3029
+ '𝚁' => 'R',
3030
+ '𝚂' => 'S',
3031
+ '𝚃' => 'T',
3032
+ '𝚄' => 'U',
3033
+ '𝚅' => 'V',
3034
+ '𝚆' => 'W',
3035
+ '𝚇' => 'X',
3036
+ '𝚈' => 'Y',
3037
+ '𝚉' => 'Z',
3038
+ '𝚊' => 'a',
3039
+ '𝚋' => 'b',
3040
+ '𝚌' => 'c',
3041
+ '𝚍' => 'd',
3042
+ '𝚎' => 'e',
3043
+ '𝚏' => 'f',
3044
+ '𝚐' => 'g',
3045
+ '𝚑' => 'h',
3046
+ '𝚒' => 'i',
3047
+ '𝚓' => 'j',
3048
+ '𝚔' => 'k',
3049
+ '𝚕' => 'l',
3050
+ '𝚖' => 'm',
3051
+ '𝚗' => 'n',
3052
+ '𝚘' => 'o',
3053
+ '𝚙' => 'p',
3054
+ '𝚚' => 'q',
3055
+ '𝚛' => 'r',
3056
+ '𝚜' => 's',
3057
+ '𝚝' => 't',
3058
+ '𝚞' => 'u',
3059
+ '𝚟' => 'v',
3060
+ '𝚠' => 'w',
3061
+ '𝚡' => 'x',
3062
+ '𝚢' => 'y',
3063
+ '𝚣' => 'z',
3064
+ '𝚤' => 'ı',
3065
+ '𝚥' => 'ȷ',
3066
+ '𝚨' => 'Α',
3067
+ '𝚩' => 'Β',
3068
+ '𝚪' => 'Γ',
3069
+ '𝚫' => 'Δ',
3070
+ '𝚬' => 'Ε',
3071
+ '𝚭' => 'Ζ',
3072
+ '𝚮' => 'Η',
3073
+ '𝚯' => 'Θ',
3074
+ '𝚰' => 'Ι',
3075
+ '𝚱' => 'Κ',
3076
+ '𝚲' => 'Λ',
3077
+ '𝚳' => 'Μ',
3078
+ '𝚴' => 'Ν',
3079
+ '𝚵' => 'Ξ',
3080
+ '𝚶' => 'Ο',
3081
+ '𝚷' => 'Π',
3082
+ '𝚸' => 'Ρ',
3083
+ '𝚹' => 'Θ',
3084
+ '𝚺' => 'Σ',
3085
+ '𝚻' => 'Τ',
3086
+ '𝚼' => 'Υ',
3087
+ '𝚽' => 'Φ',
3088
+ '𝚾' => 'Χ',
3089
+ '𝚿' => 'Ψ',
3090
+ '𝛀' => 'Ω',
3091
+ '𝛁' => '∇',
3092
+ '𝛂' => 'α',
3093
+ '𝛃' => 'β',
3094
+ '𝛄' => 'γ',
3095
+ '𝛅' => 'δ',
3096
+ '𝛆' => 'ε',
3097
+ '𝛇' => 'ζ',
3098
+ '𝛈' => 'η',
3099
+ '𝛉' => 'θ',
3100
+ '𝛊' => 'ι',
3101
+ '𝛋' => 'κ',
3102
+ '𝛌' => 'λ',
3103
+ '𝛍' => 'μ',
3104
+ '𝛎' => 'ν',
3105
+ '𝛏' => 'ξ',
3106
+ '𝛐' => 'ο',
3107
+ '𝛑' => 'π',
3108
+ '𝛒' => 'ρ',
3109
+ '𝛓' => 'ς',
3110
+ '𝛔' => 'σ',
3111
+ '𝛕' => 'τ',
3112
+ '𝛖' => 'υ',
3113
+ '𝛗' => 'φ',
3114
+ '𝛘' => 'χ',
3115
+ '𝛙' => 'ψ',
3116
+ '𝛚' => 'ω',
3117
+ '𝛛' => '∂',
3118
+ '𝛜' => 'ε',
3119
+ '𝛝' => 'θ',
3120
+ '𝛞' => 'κ',
3121
+ '𝛟' => 'φ',
3122
+ '𝛠' => 'ρ',
3123
+ '𝛡' => 'π',
3124
+ '𝛢' => 'Α',
3125
+ '𝛣' => 'Β',
3126
+ '𝛤' => 'Γ',
3127
+ '𝛥' => 'Δ',
3128
+ '𝛦' => 'Ε',
3129
+ '𝛧' => 'Ζ',
3130
+ '𝛨' => 'Η',
3131
+ '𝛩' => 'Θ',
3132
+ '𝛪' => 'Ι',
3133
+ '𝛫' => 'Κ',
3134
+ '𝛬' => 'Λ',
3135
+ '𝛭' => 'Μ',
3136
+ '𝛮' => 'Ν',
3137
+ '𝛯' => 'Ξ',
3138
+ '𝛰' => 'Ο',
3139
+ '𝛱' => 'Π',
3140
+ '𝛲' => 'Ρ',
3141
+ '𝛳' => 'Θ',
3142
+ '𝛴' => 'Σ',
3143
+ '𝛵' => 'Τ',
3144
+ '𝛶' => 'Υ',
3145
+ '𝛷' => 'Φ',
3146
+ '𝛸' => 'Χ',
3147
+ '𝛹' => 'Ψ',
3148
+ '𝛺' => 'Ω',
3149
+ '𝛻' => '∇',
3150
+ '𝛼' => 'α',
3151
+ '𝛽' => 'β',
3152
+ '𝛾' => 'γ',
3153
+ '𝛿' => 'δ',
3154
+ '𝜀' => 'ε',
3155
+ '𝜁' => 'ζ',
3156
+ '𝜂' => 'η',
3157
+ '𝜃' => 'θ',
3158
+ '𝜄' => 'ι',
3159
+ '𝜅' => 'κ',
3160
+ '𝜆' => 'λ',
3161
+ '𝜇' => 'μ',
3162
+ '𝜈' => 'ν',
3163
+ '𝜉' => 'ξ',
3164
+ '𝜊' => 'ο',
3165
+ '𝜋' => 'π',
3166
+ '𝜌' => 'ρ',
3167
+ '𝜍' => 'ς',
3168
+ '𝜎' => 'σ',
3169
+ '𝜏' => 'τ',
3170
+ '𝜐' => 'υ',
3171
+ '𝜑' => 'φ',
3172
+ '𝜒' => 'χ',
3173
+ '𝜓' => 'ψ',
3174
+ '𝜔' => 'ω',
3175
+ '𝜕' => '∂',
3176
+ '𝜖' => 'ε',
3177
+ '𝜗' => 'θ',
3178
+ '𝜘' => 'κ',
3179
+ '𝜙' => 'φ',
3180
+ '𝜚' => 'ρ',
3181
+ '𝜛' => 'π',
3182
+ '𝜜' => 'Α',
3183
+ '𝜝' => 'Β',
3184
+ '𝜞' => 'Γ',
3185
+ '𝜟' => 'Δ',
3186
+ '𝜠' => 'Ε',
3187
+ '𝜡' => 'Ζ',
3188
+ '𝜢' => 'Η',
3189
+ '𝜣' => 'Θ',
3190
+ '𝜤' => 'Ι',
3191
+ '𝜥' => 'Κ',
3192
+ '𝜦' => 'Λ',
3193
+ '𝜧' => 'Μ',
3194
+ '𝜨' => 'Ν',
3195
+ '𝜩' => 'Ξ',
3196
+ '𝜪' => 'Ο',
3197
+ '𝜫' => 'Π',
3198
+ '𝜬' => 'Ρ',
3199
+ '𝜭' => 'Θ',
3200
+ '𝜮' => 'Σ',
3201
+ '𝜯' => 'Τ',
3202
+ '𝜰' => 'Υ',
3203
+ '𝜱' => 'Φ',
3204
+ '𝜲' => 'Χ',
3205
+ '𝜳' => 'Ψ',
3206
+ '𝜴' => 'Ω',
3207
+ '𝜵' => '∇',
3208
+ '𝜶' => 'α',
3209
+ '𝜷' => 'β',
3210
+ '𝜸' => 'γ',
3211
+ '𝜹' => 'δ',
3212
+ '𝜺' => 'ε',
3213
+ '𝜻' => 'ζ',
3214
+ '𝜼' => 'η',
3215
+ '𝜽' => 'θ',
3216
+ '𝜾' => 'ι',
3217
+ '𝜿' => 'κ',
3218
+ '𝝀' => 'λ',
3219
+ '𝝁' => 'μ',
3220
+ '𝝂' => 'ν',
3221
+ '𝝃' => 'ξ',
3222
+ '𝝄' => 'ο',
3223
+ '𝝅' => 'π',
3224
+ '𝝆' => 'ρ',
3225
+ '𝝇' => 'ς',
3226
+ '𝝈' => 'σ',
3227
+ '𝝉' => 'τ',
3228
+ '𝝊' => 'υ',
3229
+ '𝝋' => 'φ',
3230
+ '𝝌' => 'χ',
3231
+ '𝝍' => 'ψ',
3232
+ '𝝎' => 'ω',
3233
+ '𝝏' => '∂',
3234
+ '𝝐' => 'ε',
3235
+ '𝝑' => 'θ',
3236
+ '𝝒' => 'κ',
3237
+ '𝝓' => 'φ',
3238
+ '𝝔' => 'ρ',
3239
+ '𝝕' => 'π',
3240
+ '𝝖' => 'Α',
3241
+ '𝝗' => 'Β',
3242
+ '𝝘' => 'Γ',
3243
+ '𝝙' => 'Δ',
3244
+ '𝝚' => 'Ε',
3245
+ '𝝛' => 'Ζ',
3246
+ '𝝜' => 'Η',
3247
+ '𝝝' => 'Θ',
3248
+ '𝝞' => 'Ι',
3249
+ '𝝟' => 'Κ',
3250
+ '𝝠' => 'Λ',
3251
+ '𝝡' => 'Μ',
3252
+ '𝝢' => 'Ν',
3253
+ '𝝣' => 'Ξ',
3254
+ '𝝤' => 'Ο',
3255
+ '𝝥' => 'Π',
3256
+ '𝝦' => 'Ρ',
3257
+ '𝝧' => 'Θ',
3258
+ '𝝨' => 'Σ',
3259
+ '𝝩' => 'Τ',
3260
+ '𝝪' => 'Υ',
3261
+ '𝝫' => 'Φ',
3262
+ '𝝬' => 'Χ',
3263
+ '𝝭' => 'Ψ',
3264
+ '𝝮' => 'Ω',
3265
+ '𝝯' => '∇',
3266
+ '𝝰' => 'α',
3267
+ '𝝱' => 'β',
3268
+ '𝝲' => 'γ',
3269
+ '𝝳' => 'δ',
3270
+ '𝝴' => 'ε',
3271
+ '𝝵' => 'ζ',
3272
+ '𝝶' => 'η',
3273
+ '𝝷' => 'θ',
3274
+ '𝝸' => 'ι',
3275
+ '𝝹' => 'κ',
3276
+ '𝝺' => 'λ',
3277
+ '𝝻' => 'μ',
3278
+ '𝝼' => 'ν',
3279
+ '𝝽' => 'ξ',
3280
+ '𝝾' => 'ο',
3281
+ '𝝿' => 'π',
3282
+ '𝞀' => 'ρ',
3283
+ '𝞁' => 'ς',
3284
+ '𝞂' => 'σ',
3285
+ '𝞃' => 'τ',
3286
+ '𝞄' => 'υ',
3287
+ '𝞅' => 'φ',
3288
+ '𝞆' => 'χ',
3289
+ '𝞇' => 'ψ',
3290
+ '𝞈' => 'ω',
3291
+ '𝞉' => '∂',
3292
+ '𝞊' => 'ε',
3293
+ '𝞋' => 'θ',
3294
+ '𝞌' => 'κ',
3295
+ '𝞍' => 'φ',
3296
+ '𝞎' => 'ρ',
3297
+ '𝞏' => 'π',
3298
+ '𝞐' => 'Α',
3299
+ '𝞑' => 'Β',
3300
+ '𝞒' => 'Γ',
3301
+ '𝞓' => 'Δ',
3302
+ '𝞔' => 'Ε',
3303
+ '𝞕' => 'Ζ',
3304
+ '𝞖' => 'Η',
3305
+ '𝞗' => 'Θ',
3306
+ '𝞘' => 'Ι',
3307
+ '𝞙' => 'Κ',
3308
+ '𝞚' => 'Λ',
3309
+ '𝞛' => 'Μ',
3310
+ '𝞜' => 'Ν',
3311
+ '𝞝' => 'Ξ',
3312
+ '𝞞' => 'Ο',
3313
+ '𝞟' => 'Π',
3314
+ '𝞠' => 'Ρ',
3315
+ '𝞡' => 'Θ',
3316
+ '𝞢' => 'Σ',
3317
+ '𝞣' => 'Τ',
3318
+ '𝞤' => 'Υ',
3319
+ '𝞥' => 'Φ',
3320
+ '𝞦' => 'Χ',
3321
+ '𝞧' => 'Ψ',
3322
+ '𝞨' => 'Ω',
3323
+ '𝞩' => '∇',
3324
+ '𝞪' => 'α',
3325
+ '𝞫' => 'β',
3326
+ '𝞬' => 'γ',
3327
+ '𝞭' => 'δ',
3328
+ '𝞮' => 'ε',
3329
+ '𝞯' => 'ζ',
3330
+ '𝞰' => 'η',
3331
+ '𝞱' => 'θ',
3332
+ '𝞲' => 'ι',
3333
+ '𝞳' => 'κ',
3334
+ '𝞴' => 'λ',
3335
+ '𝞵' => 'μ',
3336
+ '𝞶' => 'ν',
3337
+ '𝞷' => 'ξ',
3338
+ '𝞸' => 'ο',
3339
+ '𝞹' => 'π',
3340
+ '𝞺' => 'ρ',
3341
+ '𝞻' => 'ς',
3342
+ '𝞼' => 'σ',
3343
+ '𝞽' => 'τ',
3344
+ '𝞾' => 'υ',
3345
+ '𝞿' => 'φ',
3346
+ '𝟀' => 'χ',
3347
+ '𝟁' => 'ψ',
3348
+ '𝟂' => 'ω',
3349
+ '𝟃' => '∂',
3350
+ '𝟄' => 'ε',
3351
+ '𝟅' => 'θ',
3352
+ '𝟆' => 'κ',
3353
+ '𝟇' => 'φ',
3354
+ '𝟈' => 'ρ',
3355
+ '𝟉' => 'π',
3356
+ '𝟊' => 'Ϝ',
3357
+ '𝟋' => 'ϝ',
3358
+ '𝟎' => '0',
3359
+ '𝟏' => '1',
3360
+ '𝟐' => '2',
3361
+ '𝟑' => '3',
3362
+ '𝟒' => '4',
3363
+ '𝟓' => '5',
3364
+ '𝟔' => '6',
3365
+ '𝟕' => '7',
3366
+ '𝟖' => '8',
3367
+ '𝟗' => '9',
3368
+ '𝟘' => '0',
3369
+ '𝟙' => '1',
3370
+ '𝟚' => '2',
3371
+ '𝟛' => '3',
3372
+ '𝟜' => '4',
3373
+ '𝟝' => '5',
3374
+ '𝟞' => '6',
3375
+ '𝟟' => '7',
3376
+ '𝟠' => '8',
3377
+ '𝟡' => '9',
3378
+ '𝟢' => '0',
3379
+ '𝟣' => '1',
3380
+ '𝟤' => '2',
3381
+ '𝟥' => '3',
3382
+ '𝟦' => '4',
3383
+ '𝟧' => '5',
3384
+ '𝟨' => '6',
3385
+ '𝟩' => '7',
3386
+ '𝟪' => '8',
3387
+ '𝟫' => '9',
3388
+ '𝟬' => '0',
3389
+ '𝟭' => '1',
3390
+ '𝟮' => '2',
3391
+ '𝟯' => '3',
3392
+ '𝟰' => '4',
3393
+ '𝟱' => '5',
3394
+ '𝟲' => '6',
3395
+ '𝟳' => '7',
3396
+ '𝟴' => '8',
3397
+ '𝟵' => '9',
3398
+ '𝟶' => '0',
3399
+ '𝟷' => '1',
3400
+ '𝟸' => '2',
3401
+ '𝟹' => '3',
3402
+ '𝟺' => '4',
3403
+ '𝟻' => '5',
3404
+ '𝟼' => '6',
3405
+ '𝟽' => '7',
3406
+ '𝟾' => '8',
3407
+ '𝟿' => '9',
3408
+ '𞸀' => 'ا',
3409
+ '𞸁' => 'ب',
3410
+ '𞸂' => 'ج',
3411
+ '𞸃' => 'د',
3412
+ '𞸅' => 'و',
3413
+ '𞸆' => 'ز',
3414
+ '𞸇' => 'ح',
3415
+ '𞸈' => 'ط',
3416
+ '𞸉' => 'ي',
3417
+ '𞸊' => 'ك',
3418
+ '𞸋' => 'ل',
3419
+ '𞸌' => 'م',
3420
+ '𞸍' => 'ن',
3421
+ '𞸎' => 'س',
3422
+ '𞸏' => 'ع',
3423
+ '𞸐' => 'ف',
3424
+ '𞸑' => 'ص',
3425
+ '𞸒' => 'ق',
3426
+ '𞸓' => 'ر',
3427
+ '𞸔' => 'ش',
3428
+ '𞸕' => 'ت',
3429
+ '𞸖' => 'ث',
3430
+ '𞸗' => 'خ',
3431
+ '𞸘' => 'ذ',
3432
+ '𞸙' => 'ض',
3433
+ '𞸚' => 'ظ',
3434
+ '𞸛' => 'غ',
3435
+ '𞸜' => 'ٮ',
3436
+ '𞸝' => 'ں',
3437
+ '𞸞' => 'ڡ',
3438
+ '𞸟' => 'ٯ',
3439
+ '𞸡' => 'ب',
3440
+ '𞸢' => 'ج',
3441
+ '𞸤' => 'ه',
3442
+ '𞸧' => 'ح',
3443
+ '𞸩' => 'ي',
3444
+ '𞸪' => 'ك',
3445
+ '𞸫' => 'ل',
3446
+ '𞸬' => 'م',
3447
+ '𞸭' => 'ن',
3448
+ '𞸮' => 'س',
3449
+ '𞸯' => 'ع',
3450
+ '𞸰' => 'ف',
3451
+ '𞸱' => 'ص',
3452
+ '𞸲' => 'ق',
3453
+ '𞸴' => 'ش',
3454
+ '𞸵' => 'ت',
3455
+ '𞸶' => 'ث',
3456
+ '𞸷' => 'خ',
3457
+ '𞸹' => 'ض',
3458
+ '𞸻' => 'غ',
3459
+ '𞹂' => 'ج',
3460
+ '𞹇' => 'ح',
3461
+ '𞹉' => 'ي',
3462
+ '𞹋' => 'ل',
3463
+ '𞹍' => 'ن',
3464
+ '𞹎' => 'س',
3465
+ '𞹏' => 'ع',
3466
+ '𞹑' => 'ص',
3467
+ '𞹒' => 'ق',
3468
+ '𞹔' => 'ش',
3469
+ '𞹗' => 'خ',
3470
+ '𞹙' => 'ض',
3471
+ '𞹛' => 'غ',
3472
+ '𞹝' => 'ں',
3473
+ '𞹟' => 'ٯ',
3474
+ '𞹡' => 'ب',
3475
+ '𞹢' => 'ج',
3476
+ '𞹤' => 'ه',
3477
+ '𞹧' => 'ح',
3478
+ '𞹨' => 'ط',
3479
+ '𞹩' => 'ي',
3480
+ '𞹪' => 'ك',
3481
+ '𞹬' => 'م',
3482
+ '𞹭' => 'ن',
3483
+ '𞹮' => 'س',
3484
+ '𞹯' => 'ع',
3485
+ '𞹰' => 'ف',
3486
+ '𞹱' => 'ص',
3487
+ '𞹲' => 'ق',
3488
+ '𞹴' => 'ش',
3489
+ '𞹵' => 'ت',
3490
+ '𞹶' => 'ث',
3491
+ '𞹷' => 'خ',
3492
+ '𞹹' => 'ض',
3493
+ '𞹺' => 'ظ',
3494
+ '𞹻' => 'غ',
3495
+ '𞹼' => 'ٮ',
3496
+ '𞹾' => 'ڡ',
3497
+ '𞺀' => 'ا',
3498
+ '𞺁' => 'ب',
3499
+ '𞺂' => 'ج',
3500
+ '𞺃' => 'د',
3501
+ '𞺄' => 'ه',
3502
+ '𞺅' => 'و',
3503
+ '𞺆' => 'ز',
3504
+ '𞺇' => 'ح',
3505
+ '𞺈' => 'ط',
3506
+ '𞺉' => 'ي',
3507
+ '𞺋' => 'ل',
3508
+ '𞺌' => 'م',
3509
+ '𞺍' => 'ن',
3510
+ '𞺎' => 'س',
3511
+ '𞺏' => 'ع',
3512
+ '𞺐' => 'ف',
3513
+ '𞺑' => 'ص',
3514
+ '𞺒' => 'ق',
3515
+ '𞺓' => 'ر',
3516
+ '𞺔' => 'ش',
3517
+ '𞺕' => 'ت',
3518
+ '𞺖' => 'ث',
3519
+ '𞺗' => 'خ',
3520
+ '𞺘' => 'ذ',
3521
+ '𞺙' => 'ض',
3522
+ '𞺚' => 'ظ',
3523
+ '𞺛' => 'غ',
3524
+ '𞺡' => 'ب',
3525
+ '𞺢' => 'ج',
3526
+ '𞺣' => 'د',
3527
+ '𞺥' => 'و',
3528
+ '𞺦' => 'ز',
3529
+ '𞺧' => 'ح',
3530
+ '𞺨' => 'ط',
3531
+ '𞺩' => 'ي',
3532
+ '𞺫' => 'ل',
3533
+ '𞺬' => 'م',
3534
+ '𞺭' => 'ن',
3535
+ '𞺮' => 'س',
3536
+ '𞺯' => 'ع',
3537
+ '𞺰' => 'ف',
3538
+ '𞺱' => 'ص',
3539
+ '𞺲' => 'ق',
3540
+ '𞺳' => 'ر',
3541
+ '𞺴' => 'ش',
3542
+ '𞺵' => 'ت',
3543
+ '𞺶' => 'ث',
3544
+ '𞺷' => 'خ',
3545
+ '𞺸' => 'ذ',
3546
+ '𞺹' => 'ض',
3547
+ '𞺺' => 'ظ',
3548
+ '𞺻' => 'غ',
3549
+ '🄀' => '0.',
3550
+ '🄁' => '0,',
3551
+ '🄂' => '1,',
3552
+ '🄃' => '2,',
3553
+ '🄄' => '3,',
3554
+ '🄅' => '4,',
3555
+ '🄆' => '5,',
3556
+ '🄇' => '6,',
3557
+ '🄈' => '7,',
3558
+ '🄉' => '8,',
3559
+ '🄊' => '9,',
3560
+ '🄐' => '(A)',
3561
+ '🄑' => '(B)',
3562
+ '🄒' => '(C)',
3563
+ '🄓' => '(D)',
3564
+ '🄔' => '(E)',
3565
+ '🄕' => '(F)',
3566
+ '🄖' => '(G)',
3567
+ '🄗' => '(H)',
3568
+ '🄘' => '(I)',
3569
+ '🄙' => '(J)',
3570
+ '🄚' => '(K)',
3571
+ '🄛' => '(L)',
3572
+ '🄜' => '(M)',
3573
+ '🄝' => '(N)',
3574
+ '🄞' => '(O)',
3575
+ '🄟' => '(P)',
3576
+ '🄠' => '(Q)',
3577
+ '🄡' => '(R)',
3578
+ '🄢' => '(S)',
3579
+ '🄣' => '(T)',
3580
+ '🄤' => '(U)',
3581
+ '🄥' => '(V)',
3582
+ '🄦' => '(W)',
3583
+ '🄧' => '(X)',
3584
+ '🄨' => '(Y)',
3585
+ '🄩' => '(Z)',
3586
+ '🄪' => '〔S〕',
3587
+ '🄫' => 'C',
3588
+ '🄬' => 'R',
3589
+ '🄭' => 'CD',
3590
+ '🄮' => 'WZ',
3591
+ '🄰' => 'A',
3592
+ '🄱' => 'B',
3593
+ '🄲' => 'C',
3594
+ '🄳' => 'D',
3595
+ '🄴' => 'E',
3596
+ '🄵' => 'F',
3597
+ '🄶' => 'G',
3598
+ '🄷' => 'H',
3599
+ '🄸' => 'I',
3600
+ '🄹' => 'J',
3601
+ '🄺' => 'K',
3602
+ '🄻' => 'L',
3603
+ '🄼' => 'M',
3604
+ '🄽' => 'N',
3605
+ '🄾' => 'O',
3606
+ '🄿' => 'P',
3607
+ '🅀' => 'Q',
3608
+ '🅁' => 'R',
3609
+ '🅂' => 'S',
3610
+ '🅃' => 'T',
3611
+ '🅄' => 'U',
3612
+ '🅅' => 'V',
3613
+ '🅆' => 'W',
3614
+ '🅇' => 'X',
3615
+ '🅈' => 'Y',
3616
+ '🅉' => 'Z',
3617
+ '🅊' => 'HV',
3618
+ '🅋' => 'MV',
3619
+ '🅌' => 'SD',
3620
+ '🅍' => 'SS',
3621
+ '🅎' => 'PPV',
3622
+ '🅏' => 'WC',
3623
+ '🅪' => 'MC',
3624
+ '🅫' => 'MD',
3625
+ '🅬' => 'MR',
3626
+ '🆐' => 'DJ',
3627
+ '🈀' => 'ほか',
3628
+ '🈁' => 'ココ',
3629
+ '🈂' => 'サ',
3630
+ '🈐' => '手',
3631
+ '🈑' => '字',
3632
+ '🈒' => '双',
3633
+ '🈓' => 'デ',
3634
+ '🈔' => '二',
3635
+ '🈕' => '多',
3636
+ '🈖' => '解',
3637
+ '🈗' => '天',
3638
+ '🈘' => '交',
3639
+ '🈙' => '映',
3640
+ '🈚' => '無',
3641
+ '🈛' => '料',
3642
+ '🈜' => '前',
3643
+ '🈝' => '後',
3644
+ '🈞' => '再',
3645
+ '🈟' => '新',
3646
+ '🈠' => '初',
3647
+ '🈡' => '終',
3648
+ '🈢' => '生',
3649
+ '🈣' => '販',
3650
+ '🈤' => '声',
3651
+ '🈥' => '吹',
3652
+ '🈦' => '演',
3653
+ '🈧' => '投',
3654
+ '🈨' => '捕',
3655
+ '🈩' => '一',
3656
+ '🈪' => '三',
3657
+ '🈫' => '遊',
3658
+ '🈬' => '左',
3659
+ '🈭' => '中',
3660
+ '🈮' => '右',
3661
+ '🈯' => '指',
3662
+ '🈰' => '走',
3663
+ '🈱' => '打',
3664
+ '🈲' => '禁',
3665
+ '🈳' => '空',
3666
+ '🈴' => '合',
3667
+ '🈵' => '満',
3668
+ '🈶' => '有',
3669
+ '🈷' => '月',
3670
+ '🈸' => '申',
3671
+ '🈹' => '割',
3672
+ '🈺' => '営',
3673
+ '🈻' => '配',
3674
+ '🉀' => '〔本〕',
3675
+ '🉁' => '〔三〕',
3676
+ '🉂' => '〔二〕',
3677
+ '🉃' => '〔安〕',
3678
+ '🉄' => '〔点〕',
3679
+ '🉅' => '〔打〕',
3680
+ '🉆' => '〔盗〕',
3681
+ '🉇' => '〔勝〕',
3682
+ '🉈' => '〔敗〕',
3683
+ '🉐' => '得',
3684
+ '🉑' => '可',
3685
+ '🯰' => '0',
3686
+ '🯱' => '1',
3687
+ '🯲' => '2',
3688
+ '🯳' => '3',
3689
+ '🯴' => '4',
3690
+ '🯵' => '5',
3691
+ '🯶' => '6',
3692
+ '🯷' => '7',
3693
+ '🯸' => '8',
3694
+ '🯹' => '9',
3695
+ );
vendor/browscap-php/symfony/polyfill-intl-normalizer/bootstrap.php ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of the Symfony package.
5
+ *
6
+ * (c) Fabien Potencier <fabien@symfony.com>
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ use Symfony\Polyfill\Intl\Normalizer as p;
13
+
14
+ if (!function_exists('normalizer_is_normalized')) {
15
+ function normalizer_is_normalized($string, $form = p\Normalizer::FORM_C) { return p\Normalizer::isNormalized($string, $form); }
16
+ }
17
+ if (!function_exists('normalizer_normalize')) {
18
+ function normalizer_normalize($string, $form = p\Normalizer::FORM_C) { return p\Normalizer::normalize($string, $form); }
19
+ }
vendor/browscap-php/symfony/polyfill-mbstring/Mbstring.php ADDED
@@ -0,0 +1,873 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of the Symfony package.
5
+ *
6
+ * (c) Fabien Potencier <fabien@symfony.com>
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ namespace Symfony\Polyfill\Mbstring;
13
+
14
+ /**
15
+ * Partial mbstring implementation in PHP, iconv based, UTF-8 centric.
16
+ *
17
+ * Implemented:
18
+ * - mb_chr - Returns a specific character from its Unicode code point
19
+ * - mb_convert_encoding - Convert character encoding
20
+ * - mb_convert_variables - Convert character code in variable(s)
21
+ * - mb_decode_mimeheader - Decode string in MIME header field
22
+ * - mb_encode_mimeheader - Encode string for MIME header XXX NATIVE IMPLEMENTATION IS REALLY BUGGED
23
+ * - mb_decode_numericentity - Decode HTML numeric string reference to character
24
+ * - mb_encode_numericentity - Encode character to HTML numeric string reference
25
+ * - mb_convert_case - Perform case folding on a string
26
+ * - mb_detect_encoding - Detect character encoding
27
+ * - mb_get_info - Get internal settings of mbstring
28
+ * - mb_http_input - Detect HTTP input character encoding
29
+ * - mb_http_output - Set/Get HTTP output character encoding
30
+ * - mb_internal_encoding - Set/Get internal character encoding
31
+ * - mb_list_encodings - Returns an array of all supported encodings
32
+ * - mb_ord - Returns the Unicode code point of a character
33
+ * - mb_output_handler - Callback function converts character encoding in output buffer
34
+ * - mb_scrub - Replaces ill-formed byte sequences with substitute characters
35
+ * - mb_strlen - Get string length
36
+ * - mb_strpos - Find position of first occurrence of string in a string
37
+ * - mb_strrpos - Find position of last occurrence of a string in a string
38
+ * - mb_str_split - Convert a string to an array
39
+ * - mb_strtolower - Make a string lowercase
40
+ * - mb_strtoupper - Make a string uppercase
41
+ * - mb_substitute_character - Set/Get substitution character
42
+ * - mb_substr - Get part of string
43
+ * - mb_stripos - Finds position of first occurrence of a string within another, case insensitive
44
+ * - mb_stristr - Finds first occurrence of a string within another, case insensitive
45
+ * - mb_strrchr - Finds the last occurrence of a character in a string within another
46
+ * - mb_strrichr - Finds the last occurrence of a character in a string within another, case insensitive
47
+ * - mb_strripos - Finds position of last occurrence of a string within another, case insensitive
48
+ * - mb_strstr - Finds first occurrence of a string within another
49
+ * - mb_strwidth - Return width of string
50
+ * - mb_substr_count - Count the number of substring occurrences
51
+ *
52
+ * Not implemented:
53
+ * - mb_convert_kana - Convert "kana" one from another ("zen-kaku", "han-kaku" and more)
54
+ * - mb_ereg_* - Regular expression with multibyte support
55
+ * - mb_parse_str - Parse GET/POST/COOKIE data and set global variable
56
+ * - mb_preferred_mime_name - Get MIME charset string
57
+ * - mb_regex_encoding - Returns current encoding for multibyte regex as string
58
+ * - mb_regex_set_options - Set/Get the default options for mbregex functions
59
+ * - mb_send_mail - Send encoded mail
60
+ * - mb_split - Split multibyte string using regular expression
61
+ * - mb_strcut - Get part of string
62
+ * - mb_strimwidth - Get truncated string with specified width
63
+ *
64
+ * @author Nicolas Grekas <p@tchwork.com>
65
+ *
66
+ * @internal
67
+ */
68
+ final class Mbstring
69
+ {
70
+ public const MB_CASE_FOLD = \PHP_INT_MAX;
71
+
72
+ private const CASE_FOLD = [
73
+ ['µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"],
74
+ ['μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1", 'ι'],
75
+ ];
76
+
77
+ private static $encodingList = ['ASCII', 'UTF-8'];
78
+ private static $language = 'neutral';
79
+ private static $internalEncoding = 'UTF-8';
80
+
81
+ public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null)
82
+ {
83
+ if (\is_array($fromEncoding) || ($fromEncoding !== null && false !== strpos($fromEncoding, ','))) {
84
+ $fromEncoding = self::mb_detect_encoding($s, $fromEncoding);
85
+ } else {
86
+ $fromEncoding = self::getEncoding($fromEncoding);
87
+ }
88
+
89
+ $toEncoding = self::getEncoding($toEncoding);
90
+
91
+ if ('BASE64' === $fromEncoding) {
92
+ $s = base64_decode($s);
93
+ $fromEncoding = $toEncoding;
94
+ }
95
+
96
+ if ('BASE64' === $toEncoding) {
97
+ return base64_encode($s);
98
+ }
99
+
100
+ if ('HTML-ENTITIES' === $toEncoding || 'HTML' === $toEncoding) {
101
+ if ('HTML-ENTITIES' === $fromEncoding || 'HTML' === $fromEncoding) {
102
+ $fromEncoding = 'Windows-1252';
103
+ }
104
+ if ('UTF-8' !== $fromEncoding) {
105
+ $s = \iconv($fromEncoding, 'UTF-8//IGNORE', $s);
106
+ }
107
+
108
+ return preg_replace_callback('/[\x80-\xFF]+/', [__CLASS__, 'html_encoding_callback'], $s);
109
+ }
110
+
111
+ if ('HTML-ENTITIES' === $fromEncoding) {
112
+ $s = html_entity_decode($s, \ENT_COMPAT, 'UTF-8');
113
+ $fromEncoding = 'UTF-8';
114
+ }
115
+
116
+ return \iconv($fromEncoding, $toEncoding.'//IGNORE', $s);
117
+ }
118
+
119
+ public static function mb_convert_variables($toEncoding, $fromEncoding, &...$vars)
120
+ {
121
+ $ok = true;
122
+ array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) {
123
+ if (false === $v = self::mb_convert_encoding($v, $toEncoding, $fromEncoding)) {
124
+ $ok = false;
125
+ }
126
+ });
127
+
128
+ return $ok ? $fromEncoding : false;
129
+ }
130
+
131
+ public static function mb_decode_mimeheader($s)
132
+ {
133
+ return \iconv_mime_decode($s, 2, self::$internalEncoding);
134
+ }
135
+
136
+ public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null)
137
+ {
138
+ trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', \E_USER_WARNING);
139
+ }
140
+
141
+ public static function mb_decode_numericentity($s, $convmap, $encoding = null)
142
+ {
143
+ if (null !== $s && !is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) {
144
+ trigger_error('mb_decode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING);
145
+
146
+ return null;
147
+ }
148
+
149
+ if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) {
150
+ return false;
151
+ }
152
+
153
+ if (null !== $encoding && !is_scalar($encoding)) {
154
+ trigger_error('mb_decode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING);
155
+
156
+ return ''; // Instead of null (cf. mb_encode_numericentity).
157
+ }
158
+
159
+ $s = (string) $s;
160
+ if ('' === $s) {
161
+ return '';
162
+ }
163
+
164
+ $encoding = self::getEncoding($encoding);
165
+
166
+ if ('UTF-8' === $encoding) {
167
+ $encoding = null;
168
+ if (!preg_match('//u', $s)) {
169
+ $s = @\iconv('UTF-8', 'UTF-8//IGNORE', $s);
170
+ }
171
+ } else {
172
+ $s = \iconv($encoding, 'UTF-8//IGNORE', $s);
173
+ }
174
+
175
+ $cnt = floor(\count($convmap) / 4) * 4;
176
+
177
+ for ($i = 0; $i < $cnt; $i += 4) {
178
+ // collector_decode_htmlnumericentity ignores $convmap[$i + 3]
179
+ $convmap[$i] += $convmap[$i + 2];
180
+ $convmap[$i + 1] += $convmap[$i + 2];
181
+ }
182
+
183
+ $s = preg_replace_callback('/&#(?:0*([0-9]+)|x0*([0-9a-fA-F]+))(?!&);?/', function (array $m) use ($cnt, $convmap) {
184
+ $c = isset($m[2]) ? (int) hexdec($m[2]) : $m[1];
185
+ for ($i = 0; $i < $cnt; $i += 4) {
186
+ if ($c >= $convmap[$i] && $c <= $convmap[$i + 1]) {
187
+ return self::mb_chr($c - $convmap[$i + 2]);
188
+ }
189
+ }
190
+
191
+ return $m[0];
192
+ }, $s);
193
+
194
+ if (null === $encoding) {
195
+ return $s;
196
+ }
197
+
198
+ return \iconv('UTF-8', $encoding.'//IGNORE', $s);
199
+ }
200
+
201
+ public static function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = false)
202
+ {
203
+ if (null !== $s && !is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) {
204
+ trigger_error('mb_encode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING);
205
+
206
+ return null;
207
+ }
208
+
209
+ if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) {
210
+ return false;
211
+ }
212
+
213
+ if (null !== $encoding && !is_scalar($encoding)) {
214
+ trigger_error('mb_encode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING);
215
+
216
+ return null; // Instead of '' (cf. mb_decode_numericentity).
217
+ }
218
+
219
+ if (null !== $is_hex && !is_scalar($is_hex)) {
220
+ trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, '.\gettype($s).' given', \E_USER_WARNING);
221
+
222
+ return null;
223
+ }
224
+
225
+ $s = (string) $s;
226
+ if ('' === $s) {
227
+ return '';
228
+ }
229
+
230
+ $encoding = self::getEncoding($encoding);
231
+
232
+ if ('UTF-8' === $encoding) {
233
+ $encoding = null;
234
+ if (!preg_match('//u', $s)) {
235
+ $s = @\iconv('UTF-8', 'UTF-8//IGNORE', $s);
236
+ }
237
+ } else {
238
+ $s = \iconv($encoding, 'UTF-8//IGNORE', $s);
239
+ }
240
+
241
+ static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4];
242
+
243
+ $cnt = floor(\count($convmap) / 4) * 4;
244
+ $i = 0;
245
+ $len = \strlen($s);
246
+ $result = '';
247
+
248
+ while ($i < $len) {
249
+ $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"];
250
+ $uchr = substr($s, $i, $ulen);
251
+ $i += $ulen;
252
+ $c = self::mb_ord($uchr);
253
+
254
+ for ($j = 0; $j < $cnt; $j += 4) {
255
+ if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) {
256
+ $cOffset = ($c + $convmap[$j + 2]) & $convmap[$j + 3];
257
+ $result .= $is_hex ? sprintf('&#x%X;', $cOffset) : '&#'.$cOffset.';';
258
+ continue 2;
259
+ }
260
+ }
261
+ $result .= $uchr;
262
+ }
263
+
264
+ if (null === $encoding) {
265
+ return $result;
266
+ }
267
+
268
+ return \iconv('UTF-8', $encoding.'//IGNORE', $result);
269
+ }
270
+
271
+ public static function mb_convert_case($s, $mode, $encoding = null)
272
+ {
273
+ $s = (string) $s;
274
+ if ('' === $s) {
275
+ return '';
276
+ }
277
+
278
+ $encoding = self::getEncoding($encoding);
279
+
280
+ if ('UTF-8' === $encoding) {
281
+ $encoding = null;
282
+ if (!preg_match('//u', $s)) {
283
+ $s = @\iconv('UTF-8', 'UTF-8//IGNORE', $s);
284
+ }
285
+ } else {
286
+ $s = \iconv($encoding, 'UTF-8//IGNORE', $s);
287
+ }
288
+
289
+ if (\MB_CASE_TITLE == $mode) {
290
+ static $titleRegexp = null;
291
+ if (null === $titleRegexp) {
292
+ $titleRegexp = self::getData('titleCaseRegexp');
293
+ }
294
+ $s = preg_replace_callback($titleRegexp, [__CLASS__, 'title_case'], $s);
295
+ } else {
296
+ if (\MB_CASE_UPPER == $mode) {
297
+ static $upper = null;
298
+ if (null === $upper) {
299
+ $upper = self::getData('upperCase');
300
+ }
301
+ $map = $upper;
302
+ } else {
303
+ if (self::MB_CASE_FOLD === $mode) {
304
+ $s = str_replace(self::CASE_FOLD[0], self::CASE_FOLD[1], $s);
305
+ }
306
+
307
+ static $lower = null;
308
+ if (null === $lower) {
309
+ $lower = self::getData('lowerCase');
310
+ }
311
+ $map = $lower;
312
+ }
313
+
314
+ static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4];
315
+
316
+ $i = 0;
317
+ $len = \strlen($s);
318
+
319
+ while ($i < $len) {
320
+ $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"];
321
+ $uchr = substr($s, $i, $ulen);
322
+ $i += $ulen;
323
+
324
+ if (isset($map[$uchr])) {
325
+ $uchr = $map[$uchr];
326
+ $nlen = \strlen($uchr);
327
+
328
+ if ($nlen == $ulen) {
329
+ $nlen = $i;
330
+ do {
331
+ $s[--$nlen] = $uchr[--$ulen];
332
+ } while ($ulen);
333
+ } else {
334
+ $s = substr_replace($s, $uchr, $i - $ulen, $ulen);
335
+ $len += $nlen - $ulen;
336
+ $i += $nlen - $ulen;
337
+ }
338
+ }
339
+ }
340
+ }
341
+
342
+ if (null === $encoding) {
343
+ return $s;
344
+ }
345
+
346
+ return \iconv('UTF-8', $encoding.'//IGNORE', $s);
347
+ }
348
+
349
+ public static function mb_internal_encoding($encoding = null)
350
+ {
351
+ if (null === $encoding) {
352
+ return self::$internalEncoding;
353
+ }
354
+
355
+ $normalizedEncoding = self::getEncoding($encoding);
356
+
357
+ if ('UTF-8' === $normalizedEncoding || false !== @\iconv($normalizedEncoding, $normalizedEncoding, ' ')) {
358
+ self::$internalEncoding = $normalizedEncoding;
359
+
360
+ return true;
361
+ }
362
+
363
+ if (80000 > \PHP_VERSION_ID) {
364
+ return false;
365
+ }
366
+
367
+ throw new \ValueError(sprintf('Argument #1 ($encoding) must be a valid encoding, "%s" given', $encoding));
368
+ }
369
+
370
+ public static function mb_language($lang = null)
371
+ {
372
+ if (null === $lang) {
373
+ return self::$language;
374
+ }
375
+
376
+ switch ($normalizedLang = strtolower($lang)) {
377
+ case 'uni':
378
+ case 'neutral':
379
+ self::$language = $normalizedLang;
380
+
381
+ return true;
382
+ }
383
+
384
+ if (80000 > \PHP_VERSION_ID) {
385
+ return false;
386
+ }
387
+
388
+ throw new \ValueError(sprintf('Argument #1 ($language) must be a valid language, "%s" given', $lang));
389
+ }
390
+
391
+ public static function mb_list_encodings()
392
+ {
393
+ return ['UTF-8'];
394
+ }
395
+
396
+ public static function mb_encoding_aliases($encoding)
397
+ {
398
+ switch (strtoupper($encoding)) {
399
+ case 'UTF8':
400
+ case 'UTF-8':
401
+ return ['utf8'];
402
+ }
403
+
404
+ return false;
405
+ }
406
+
407
+ public static function mb_check_encoding($var = null, $encoding = null)
408
+ {
409
+ if (null === $encoding) {
410
+ if (null === $var) {
411
+ return false;
412
+ }
413
+ $encoding = self::$internalEncoding;
414
+ }
415
+
416
+ return self::mb_detect_encoding($var, [$encoding]) || false !== @\iconv($encoding, $encoding, $var);
417
+ }
418
+
419
+ public static function mb_detect_encoding($str, $encodingList = null, $strict = false)
420
+ {
421
+ if (null === $encodingList) {
422
+ $encodingList = self::$encodingList;
423
+ } else {
424
+ if (!\is_array($encodingList)) {
425
+ $encodingList = array_map('trim', explode(',', $encodingList));
426
+ }
427
+ $encodingList = array_map('strtoupper', $encodingList);
428
+ }
429
+
430
+ foreach ($encodingList as $enc) {
431
+ switch ($enc) {
432
+ case 'ASCII':
433
+ if (!preg_match('/[\x80-\xFF]/', $str)) {
434
+ return $enc;
435
+ }
436
+ break;
437
+
438
+ case 'UTF8':
439
+ case 'UTF-8':
440
+ if (preg_match('//u', $str)) {
441
+ return 'UTF-8';
442
+ }
443
+ break;
444
+
445
+ default:
446
+ if (0 === strncmp($enc, 'ISO-8859-', 9)) {
447
+ return $enc;
448
+ }
449
+ }
450
+ }
451
+
452
+ return false;
453
+ }
454
+
455
+ public static function mb_detect_order($encodingList = null)
456
+ {
457
+ if (null === $encodingList) {
458
+ return self::$encodingList;
459
+ }
460
+
461
+ if (!\is_array($encodingList)) {
462
+ $encodingList = array_map('trim', explode(',', $encodingList));
463
+ }
464
+ $encodingList = array_map('strtoupper', $encodingList);
465
+
466
+ foreach ($encodingList as $enc) {
467
+ switch ($enc) {
468
+ default:
469
+ if (strncmp($enc, 'ISO-8859-', 9)) {
470
+ return false;
471
+ }
472
+ // no break
473
+ case 'ASCII':
474
+ case 'UTF8':
475
+ case 'UTF-8':
476
+ }
477
+ }
478
+
479
+ self::$encodingList = $encodingList;
480
+
481
+ return true;
482
+ }
483
+
484
+ public static function mb_strlen($s, $encoding = null)
485
+ {
486
+ $encoding = self::getEncoding($encoding);
487
+ if ('CP850' === $encoding || 'ASCII' === $encoding) {
488
+ return \strlen($s);
489
+ }
490
+
491
+ return @\iconv_strlen($s, $encoding);
492
+ }
493
+
494
+ public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null)
495
+ {
496
+ $encoding = self::getEncoding($encoding);
497
+ if ('CP850' === $encoding || 'ASCII' === $encoding) {
498
+ return strpos($haystack, $needle, $offset);
499
+ }
500
+
501
+ $needle = (string) $needle;
502
+ if ('' === $needle) {
503
+ if (80000 > \PHP_VERSION_ID) {
504
+ trigger_error(__METHOD__.': Empty delimiter', \E_USER_WARNING);
505
+
506
+ return false;
507
+ }
508
+
509
+ return 0;
510
+ }
511
+
512
+ return \iconv_strpos($haystack, $needle, $offset, $encoding);
513
+ }
514
+
515
+ public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null)
516
+ {
517
+ $encoding = self::getEncoding($encoding);
518
+ if ('CP850' === $encoding || 'ASCII' === $encoding) {
519
+ return strrpos($haystack, $needle, $offset);
520
+ }
521
+
522
+ if ($offset != (int) $offset) {
523
+ $offset = 0;
524
+ } elseif ($offset = (int) $offset) {
525
+ if ($offset < 0) {
526
+ if (0 > $offset += self::mb_strlen($needle)) {
527
+ $haystack = self::mb_substr($haystack, 0, $offset, $encoding);
528
+ }
529
+ $offset = 0;
530
+ } else {
531
+ $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding);
532
+ }
533
+ }
534
+
535
+ $pos = '' !== $needle || 80000 > \PHP_VERSION_ID
536
+ ? \iconv_strrpos($haystack, $needle, $encoding)
537
+ : self::mb_strlen($haystack, $encoding);
538
+
539
+ return false !== $pos ? $offset + $pos : false;
540
+ }
541
+
542
+ public static function mb_str_split($string, $split_length = 1, $encoding = null)
543
+ {
544
+ if (null !== $string && !is_scalar($string) && !(\is_object($string) && method_exists($string, '__toString'))) {
545
+ trigger_error('mb_str_split() expects parameter 1 to be string, '.\gettype($string).' given', \E_USER_WARNING);
546
+
547
+ return null;
548
+ }
549
+
550
+ if (1 > $split_length = (int) $split_length) {
551
+ if (80000 > \PHP_VERSION_ID) {
552
+ trigger_error('The length of each segment must be greater than zero', \E_USER_WARNING);
553
+ return false;
554
+ }
555
+
556
+ throw new \ValueError('Argument #2 ($length) must be greater than 0');
557
+ }
558
+
559
+ if (null === $encoding) {
560
+ $encoding = mb_internal_encoding();
561
+ }
562
+
563
+ if ('UTF-8' === $encoding = self::getEncoding($encoding)) {
564
+ $rx = '/(';
565
+ while (65535 < $split_length) {
566
+ $rx .= '.{65535}';
567
+ $split_length -= 65535;
568
+ }
569
+ $rx .= '.{'.$split_length.'})/us';
570
+
571
+ return preg_split($rx, $string, null, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY);
572
+ }
573
+
574
+ $result = [];
575
+ $length = mb_strlen($string, $encoding);
576
+
577
+ for ($i = 0; $i < $length; $i += $split_length) {
578
+ $result[] = mb_substr($string, $i, $split_length, $encoding);
579
+ }
580
+
581
+ return $result;
582
+ }
583
+
584
+ public static function mb_strtolower($s, $encoding = null)
585
+ {
586
+ return self::mb_convert_case($s, \MB_CASE_LOWER, $encoding);
587
+ }
588
+
589
+ public static function mb_strtoupper($s, $encoding = null)
590
+ {
591
+ return self::mb_convert_case($s, \MB_CASE_UPPER, $encoding);
592
+ }
593
+
594
+ public static function mb_substitute_character($c = null)
595
+ {
596
+ if (null === $c) {
597
+ return 'none';
598
+ }
599
+ if (0 === strcasecmp($c, 'none')) {
600
+ return true;
601
+ }
602
+ if (80000 > \PHP_VERSION_ID) {
603
+ return false;
604
+ }
605
+ if (\is_int($c) || 'long' === $c || 'entity' === $c) {
606
+ return false;
607
+ }
608
+
609
+ throw new \ValueError('Argument #1 ($substitute_character) must be "none", "long", "entity" or a valid codepoint');
610
+ }
611
+
612
+ public static function mb_substr($s, $start, $length = null, $encoding = null)
613
+ {
614
+ $encoding = self::getEncoding($encoding);
615
+ if ('CP850' === $encoding || 'ASCII' === $encoding) {
616
+ return (string) substr($s, $start, null === $length ? 2147483647 : $length);
617
+ }
618
+
619
+ if ($start < 0) {
620
+ $start = \iconv_strlen($s, $encoding) + $start;
621
+ if ($start < 0) {
622
+ $start = 0;
623
+ }
624
+ }
625
+
626
+ if (null === $length) {
627
+ $length = 2147483647;
628
+ } elseif ($length < 0) {
629
+ $length = \iconv_strlen($s, $encoding) + $length - $start;
630
+ if ($length < 0) {
631
+ return '';
632
+ }
633
+ }
634
+
635
+ return (string) \iconv_substr($s, $start, $length, $encoding);
636
+ }
637
+
638
+ public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null)
639
+ {
640
+ $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding);
641
+ $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding);
642
+
643
+ return self::mb_strpos($haystack, $needle, $offset, $encoding);
644
+ }
645
+
646
+ public static function mb_stristr($haystack, $needle, $part = false, $encoding = null)
647
+ {
648
+ $pos = self::mb_stripos($haystack, $needle, 0, $encoding);
649
+
650
+ return self::getSubpart($pos, $part, $haystack, $encoding);
651
+ }
652
+
653
+ public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null)
654
+ {
655
+ $encoding = self::getEncoding($encoding);
656
+ if ('CP850' === $encoding || 'ASCII' === $encoding) {
657
+ $pos = strrpos($haystack, $needle);
658
+ } else {
659
+ $needle = self::mb_substr($needle, 0, 1, $encoding);
660
+ $pos = \iconv_strrpos($haystack, $needle, $encoding);
661
+ }
662
+
663
+ return self::getSubpart($pos, $part, $haystack, $encoding);
664
+ }
665
+
666
+ public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null)
667
+ {
668
+ $needle = self::mb_substr($needle, 0, 1, $encoding);
669
+ $pos = self::mb_strripos($haystack, $needle, $encoding);
670
+
671
+ return self::getSubpart($pos, $part, $haystack, $encoding);
672
+ }
673
+
674
+ public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null)
675
+ {
676
+ $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding);
677
+ $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding);
678
+
679
+ return self::mb_strrpos($haystack, $needle, $offset, $encoding);
680
+ }
681
+
682
+ public static function mb_strstr($haystack, $needle, $part = false, $encoding = null)
683
+ {
684
+ $pos = strpos($haystack, $needle);
685
+ if (false === $pos) {
686
+ return false;
687
+ }
688
+ if ($part) {
689
+ return substr($haystack, 0, $pos);
690
+ }
691
+
692
+ return substr($haystack, $pos);
693
+ }
694
+
695
+ public static function mb_get_info($type = 'all')
696
+ {
697
+ $info = [
698
+ 'internal_encoding' => self::$internalEncoding,
699
+ 'http_output' => 'pass',
700
+ 'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)',
701
+ 'func_overload' => 0,
702
+ 'func_overload_list' => 'no overload',
703
+ 'mail_charset' => 'UTF-8',
704
+ 'mail_header_encoding' => 'BASE64',
705
+ 'mail_body_encoding' => 'BASE64',
706
+ 'illegal_chars' => 0,
707
+ 'encoding_translation' => 'Off',
708
+ 'language' => self::$language,
709
+ 'detect_order' => self::$encodingList,
710
+ 'substitute_character' => 'none',
711
+ 'strict_detection' => 'Off',
712
+ ];
713
+
714
+ if ('all' === $type) {
715
+ return $info;
716
+ }
717
+ if (isset($info[$type])) {
718
+ return $info[$type];
719
+ }
720
+
721
+ return false;
722
+ }
723
+
724
+ public static function mb_http_input($type = '')
725
+ {
726
+ return false;
727
+ }
728
+
729
+ public static function mb_http_output($encoding = null)
730
+ {
731
+ return null !== $encoding ? 'pass' === $encoding : 'pass';
732
+ }
733
+
734
+ public static function mb_strwidth($s, $encoding = null)
735
+ {
736
+ $encoding = self::getEncoding($encoding);
737
+
738
+ if ('UTF-8' !== $encoding) {
739
+ $s = \iconv($encoding, 'UTF-8//IGNORE', $s);
740
+ }
741
+
742
+ $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);
743
+
744
+ return ($wide << 1) + \iconv_strlen($s, 'UTF-8');
745
+ }
746
+
747
+ public static function mb_substr_count($haystack, $needle, $encoding = null)
748
+ {
749
+ return substr_count($haystack, $needle);
750
+ }
751
+
752
+ public static function mb_output_handler($contents, $status)
753
+ {
754
+ return $contents;
755
+ }
756
+
757
+ public static function mb_chr($code, $encoding = null)
758
+ {
759
+ if (0x80 > $code %= 0x200000) {
760
+ $s = \chr($code);
761
+ } elseif (0x800 > $code) {
762
+ $s = \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F);
763
+ } elseif (0x10000 > $code) {
764
+ $s = \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
765
+ } else {
766
+ $s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
767
+ }
768
+
769
+ if ('UTF-8' !== $encoding = self::getEncoding($encoding)) {
770
+ $s = mb_convert_encoding($s, $encoding, 'UTF-8');
771
+ }
772
+
773
+ return $s;
774
+ }
775
+
776
+ public static function mb_ord($s, $encoding = null)
777
+ {
778
+ if ('UTF-8' !== $encoding = self::getEncoding($encoding)) {
779
+ $s = mb_convert_encoding($s, 'UTF-8', $encoding);
780
+ }
781
+
782
+ if (1 === \strlen($s)) {
783
+ return \ord($s);
784
+ }
785
+
786
+ $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0;
787
+ if (0xF0 <= $code) {
788
+ return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80;
789
+ }
790
+ if (0xE0 <= $code) {
791
+ return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80;
792
+ }
793
+ if (0xC0 <= $code) {
794
+ return (($code - 0xC0) << 6) + $s[2] - 0x80;
795
+ }
796
+
797
+ return $code;
798
+ }
799
+
800
+ private static function getSubpart($pos, $part, $haystack, $encoding)
801
+ {
802
+ if (false === $pos) {
803
+ return false;
804
+ }
805
+ if ($part) {
806
+ return self::mb_substr($haystack, 0, $pos, $encoding);
807
+ }
808
+
809
+ return self::mb_substr($haystack, $pos, null, $encoding);
810
+ }
811
+
812
+ private static function html_encoding_callback(array $m)
813
+ {
814
+ $i = 1;
815
+ $entities = '';
816
+ $m = unpack('C*', htmlentities($m[0], \ENT_COMPAT, 'UTF-8'));
817
+
818
+ while (isset($m[$i])) {
819
+ if (0x80 > $m[$i]) {
820
+ $entities .= \chr($m[$i++]);
821
+ continue;
822
+ }
823
+ if (0xF0 <= $m[$i]) {
824
+ $c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80;
825
+ } elseif (0xE0 <= $m[$i]) {
826
+ $c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80;
827
+ } else {
828
+ $c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80;
829
+ }
830
+
831
+ $entities .= '&#'.$c.';';
832
+ }
833
+
834
+ return $entities;
835
+ }
836
+
837
+ private static function title_case(array $s)
838
+ {
839
+ return self::mb_convert_case($s[1], \MB_CASE_UPPER, 'UTF-8').self::mb_convert_case($s[2], \MB_CASE_LOWER, 'UTF-8');
840
+ }
841
+
842
+ private static function getData($file)
843
+ {
844
+ if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) {
845
+ return require $file;
846
+ }
847
+
848
+ return false;
849
+ }
850
+
851
+ private static function getEncoding($encoding)
852
+ {
853
+ if (null === $encoding) {
854
+ return self::$internalEncoding;
855
+ }
856
+
857
+ if ('UTF-8' === $encoding) {
858
+ return 'UTF-8';
859
+ }
860
+
861
+ $encoding = strtoupper($encoding);
862
+
863
+ if ('8BIT' === $encoding || 'BINARY' === $encoding) {
864
+ return 'CP850';
865
+ }
866
+
867
+ if ('UTF8' === $encoding) {
868
+ return 'UTF-8';
869
+ }
870
+
871
+ return $encoding;
872
+ }
873
+ }
vendor/browscap-php/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php ADDED
@@ -0,0 +1,1397 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ return array (
4
+ 'A' => 'a',
5
+ 'B' => 'b',
6
+ 'C' => 'c',
7
+ 'D' => 'd',
8
+ 'E' => 'e',
9
+ 'F' => 'f',
10
+ 'G' => 'g',
11
+ 'H' => 'h',
12
+ 'I' => 'i',
13
+ 'J' => 'j',
14
+ 'K' => 'k',
15
+ 'L' => 'l',
16
+ 'M' => 'm',
17
+ 'N' => 'n',
18
+ 'O' => 'o',
19
+ 'P' => 'p',
20
+ 'Q' => 'q',
21
+ 'R' => 'r',
22
+ 'S' => 's',
23
+ 'T' => 't',
24
+ 'U' => 'u',
25
+ 'V' => 'v',
26
+ 'W' => 'w',
27
+ 'X' => 'x',
28
+ 'Y' => 'y',
29
+ 'Z' => 'z',
30
+ 'À' => 'à',
31
+ 'Á' => 'á',
32
+ 'Â' => 'â',
33
+ 'Ã' => 'ã',
34
+ 'Ä' => 'ä',
35
+ 'Å' => 'å',
36
+ 'Æ' => 'æ',
37
+ 'Ç' => 'ç',
38
+ 'È' => 'è',
39
+ 'É' => 'é',
40
+ 'Ê' => 'ê',
41
+ 'Ë' => 'ë',
42
+ 'Ì' => 'ì',
43
+ 'Í' => 'í',
44
+ 'Î' => 'î',
45
+ 'Ï' => 'ï',
46
+ 'Ð' => 'ð',
47
+ 'Ñ' => 'ñ',
48
+ 'Ò' => 'ò',
49
+ 'Ó' => 'ó',
50
+ 'Ô' => 'ô',
51
+ 'Õ' => 'õ',
52
+ 'Ö' => 'ö',
53
+ 'Ø' => 'ø',
54
+ 'Ù' => 'ù',
55
+ 'Ú' => 'ú',
56
+ 'Û' => 'û',
57
+ 'Ü' => 'ü',
58
+ 'Ý' => 'ý',
59
+ 'Þ' => 'þ',
60
+ 'Ā' => 'ā',
61
+ 'Ă' => 'ă',
62
+ 'Ą' => 'ą',
63
+ 'Ć' => 'ć',
64
+ 'Ĉ' => 'ĉ',
65
+ 'Ċ' => 'ċ',
66
+ 'Č' => 'č',
67
+ 'Ď' => 'ď',
68
+ 'Đ' => 'đ',
69
+ 'Ē' => 'ē',
70
+ 'Ĕ' => 'ĕ',
71
+ 'Ė' => 'ė',
72
+ 'Ę' => 'ę',
73
+ 'Ě' => 'ě',
74
+ 'Ĝ' => 'ĝ',
75
+ 'Ğ' => 'ğ',
76
+ 'Ġ' => 'ġ',
77
+ 'Ģ' => 'ģ',
78
+ 'Ĥ' => 'ĥ',
79
+ 'Ħ' => 'ħ',
80
+ 'Ĩ' => 'ĩ',
81
+ 'Ī' => 'ī',
82
+ 'Ĭ' => 'ĭ',
83
+ 'Į' => 'į',
84
+ 'İ' => 'i̇',
85
+ 'IJ' => 'ij',
86
+ 'Ĵ' => 'ĵ',
87
+ 'Ķ' => 'ķ',
88
+ 'Ĺ' => 'ĺ',
89
+ 'Ļ' => 'ļ',
90
+ 'Ľ' => 'ľ',
91
+ 'Ŀ' => 'ŀ',
92
+ 'Ł' => 'ł',
93
+ 'Ń' => 'ń',
94
+ 'Ņ' => 'ņ',
95
+ 'Ň' => 'ň',
96
+ 'Ŋ' => 'ŋ',
97
+ 'Ō' => 'ō',
98
+ 'Ŏ' => 'ŏ',
99
+ 'Ő' => 'ő',
100
+ 'Œ' => 'œ',
101
+ 'Ŕ' => 'ŕ',
102
+ 'Ŗ' => 'ŗ',
103
+ 'Ř' => 'ř',
104
+ 'Ś' => 'ś',
105
+ 'Ŝ' => 'ŝ',
106
+ 'Ş' => 'ş',
107
+ 'Š' => 'š',
108
+ 'Ţ' => 'ţ',
109
+ 'Ť' => 'ť',
110
+ 'Ŧ' => 'ŧ',
111
+ 'Ũ' => 'ũ',
112
+ 'Ū' => 'ū',
113
+ 'Ŭ' => 'ŭ',
114
+ 'Ů' => 'ů',
115
+ 'Ű' => 'ű',
116
+ 'Ų' => 'ų',
117
+ 'Ŵ' => 'ŵ',
118
+ 'Ŷ' => 'ŷ',
119
+ 'Ÿ' => 'ÿ',
120
+ 'Ź' => 'ź',
121
+ 'Ż' => 'ż',
122
+ 'Ž' => 'ž',
123
+ 'Ɓ' => 'ɓ',
124
+ 'Ƃ' => 'ƃ',
125
+ 'Ƅ' => 'ƅ',
126
+ 'Ɔ' => 'ɔ',
127
+ 'Ƈ' => 'ƈ',
128
+ 'Ɖ' => 'ɖ',
129
+ 'Ɗ' => 'ɗ',
130
+ 'Ƌ' => 'ƌ',
131
+ 'Ǝ' => 'ǝ',
132
+ 'Ə' => 'ə',
133
+ 'Ɛ' => 'ɛ',
134
+ 'Ƒ' => 'ƒ',
135
+ 'Ɠ' => 'ɠ',
136
+ 'Ɣ' => 'ɣ',
137
+ 'Ɩ' => 'ɩ',
138
+ 'Ɨ' => 'ɨ',
139
+ 'Ƙ' => 'ƙ',
140
+ 'Ɯ' => 'ɯ',
141
+ 'Ɲ' => 'ɲ',
142
+ 'Ɵ' => 'ɵ',
143
+ 'Ơ' => 'ơ',
144
+ 'Ƣ' => 'ƣ',
145
+ 'Ƥ' => 'ƥ',
146
+ 'Ʀ' => 'ʀ',
147
+ 'Ƨ' => 'ƨ',
148
+ 'Ʃ' => 'ʃ',
149
+ 'Ƭ' => 'ƭ',
150
+ 'Ʈ' => 'ʈ',
151
+ 'Ư' => 'ư',
152
+ 'Ʊ' => 'ʊ',
153
+ 'Ʋ' => 'ʋ',
154
+ 'Ƴ' => 'ƴ',
155
+ 'Ƶ' => 'ƶ',
156
+ 'Ʒ' => 'ʒ',
157
+ 'Ƹ' => 'ƹ',
158
+ 'Ƽ' => 'ƽ',
159
+ 'DŽ' => 'dž',
160
+ 'Dž' => 'dž',
161
+ 'LJ' => 'lj',
162
+ 'Lj' => 'lj',
163
+ 'NJ' => 'nj',
164
+ 'Nj' => 'nj',
165
+ 'Ǎ' => 'ǎ',
166
+ 'Ǐ' => 'ǐ',
167
+ 'Ǒ' => 'ǒ',
168
+ 'Ǔ' => 'ǔ',
169
+ 'Ǖ' => 'ǖ',
170
+ 'Ǘ' => 'ǘ',
171
+ 'Ǚ' => 'ǚ',
172
+ 'Ǜ' => 'ǜ',
173
+ 'Ǟ' => 'ǟ',
174
+ 'Ǡ' => 'ǡ',
175
+ 'Ǣ' => 'ǣ',
176
+ 'Ǥ' => 'ǥ',
177
+ 'Ǧ' => 'ǧ',
178
+ 'Ǩ' => 'ǩ',
179
+ 'Ǫ' => 'ǫ',
180
+ 'Ǭ' => 'ǭ',
181
+ 'Ǯ' => 'ǯ',
182
+ 'DZ' => 'dz',
183
+ 'Dz' => 'dz',
184
+ 'Ǵ' => 'ǵ',
185
+ 'Ƕ' => 'ƕ',
186
+ 'Ƿ' => 'ƿ',
187
+ 'Ǹ' => 'ǹ',
188
+ 'Ǻ' => 'ǻ',
189
+ 'Ǽ' => 'ǽ',
190
+ 'Ǿ' => 'ǿ',
191
+ 'Ȁ' => 'ȁ',
192
+ 'Ȃ' => 'ȃ',
193
+ 'Ȅ' => 'ȅ',
194
+ 'Ȇ' => 'ȇ',
195
+ 'Ȉ' => 'ȉ',
196
+ 'Ȋ' => 'ȋ',
197
+ 'Ȍ' => 'ȍ',
198
+ 'Ȏ' => 'ȏ',
199
+ 'Ȑ' => 'ȑ',
200
+ 'Ȓ' => 'ȓ',
201
+ 'Ȕ' => 'ȕ',
202
+ 'Ȗ' => 'ȗ',
203
+ 'Ș' => 'ș',
204
+ 'Ț' => 'ț',
205
+ 'Ȝ' => 'ȝ',
206
+ 'Ȟ' => 'ȟ',
207
+ 'Ƞ' => 'ƞ',
208
+ 'Ȣ' => 'ȣ',
209
+ 'Ȥ' => 'ȥ',
210
+ 'Ȧ' => 'ȧ',
211
+ 'Ȩ' => 'ȩ',
212
+ 'Ȫ' => 'ȫ',
213
+ 'Ȭ' => 'ȭ',
214
+ 'Ȯ' => 'ȯ',
215
+ 'Ȱ' => 'ȱ',
216
+ 'Ȳ' => 'ȳ',
217
+ 'Ⱥ' => 'ⱥ',
218
+ 'Ȼ' => 'ȼ',
219
+ 'Ƚ' => 'ƚ',
220
+ 'Ⱦ' => 'ⱦ',
221
+ 'Ɂ' => 'ɂ',
222
+ 'Ƀ' => 'ƀ',
223
+ 'Ʉ' => 'ʉ',
224
+ 'Ʌ' => 'ʌ',
225
+ 'Ɇ' => 'ɇ',
226
+ 'Ɉ' => 'ɉ',
227
+ 'Ɋ' => 'ɋ',
228
+ 'Ɍ' => 'ɍ',
229
+ 'Ɏ' => 'ɏ',
230
+ 'Ͱ' => 'ͱ',
231
+ 'Ͳ' => 'ͳ',
232
+ 'Ͷ' => 'ͷ',
233
+ 'Ϳ' => 'ϳ',
234
+ 'Ά' => 'ά',
235
+ 'Έ' => 'έ',
236
+ 'Ή' => 'ή',
237
+ 'Ί' => 'ί',
238
+ 'Ό' => 'ό',
239
+ 'Ύ' => 'ύ',
240
+ 'Ώ' => 'ώ',
241
+ 'Α' => 'α',
242
+ 'Β' => 'β',
243
+ 'Γ' => 'γ',
244
+ 'Δ' => 'δ',
245
+ 'Ε' => 'ε',
246
+ 'Ζ' => 'ζ',
247
+ 'Η' => 'η',
248
+ 'Θ' => 'θ',
249
+ 'Ι' => 'ι',
250
+ 'Κ' => 'κ',
251
+ 'Λ' => 'λ',
252
+ 'Μ' => 'μ',
253
+ 'Ν' => 'ν',
254
+ 'Ξ' => 'ξ',
255
+ 'Ο' => 'ο',
256
+ 'Π' => 'π',
257
+ 'Ρ' => 'ρ',
258
+ 'Σ' => 'σ',
259
+ 'Τ' => 'τ',
260
+ 'Υ' => 'υ',
261
+ 'Φ' => 'φ',
262
+ 'Χ' => 'χ',
263
+ 'Ψ' => 'ψ',
264
+ 'Ω' => 'ω',
265
+ 'Ϊ' => 'ϊ',
266
+ 'Ϋ' => 'ϋ',
267
+ 'Ϗ' => 'ϗ',
268
+ 'Ϙ' => 'ϙ',
269
+ 'Ϛ' => 'ϛ',
270
+ 'Ϝ' => 'ϝ',
271
+ 'Ϟ' => 'ϟ',
272
+ 'Ϡ' => 'ϡ',
273
+ 'Ϣ' => 'ϣ',
274
+ 'Ϥ' => 'ϥ',
275
+ 'Ϧ' => 'ϧ',
276
+ 'Ϩ' => 'ϩ',
277
+ 'Ϫ' => 'ϫ',
278
+ 'Ϭ' => 'ϭ',
279
+ 'Ϯ' => 'ϯ',
280
+ 'ϴ' => 'θ',
281
+ 'Ϸ' => 'ϸ',
282
+ 'Ϲ' => 'ϲ',
283
+ 'Ϻ' => 'ϻ',
284
+ 'Ͻ' => 'ͻ',
285
+ 'Ͼ' => 'ͼ',
286
+ 'Ͽ' => 'ͽ',
287
+ 'Ѐ' => 'ѐ',
288
+ 'Ё' => 'ё',
289
+ 'Ђ' => 'ђ',
290
+ 'Ѓ' => 'ѓ',
291
+ 'Є' => 'є',
292
+ 'Ѕ' => 'ѕ',
293
+ 'І' => 'і',
294
+ 'Ї' => 'ї',
295
+ 'Ј' => 'ј',
296
+ 'Љ' => 'љ',
297
+ 'Њ' => 'њ',
298
+ 'Ћ' => 'ћ',
299
+ 'Ќ' => 'ќ',
300
+ 'Ѝ' => 'ѝ',
301
+ 'Ў' => 'ў',
302
+ 'Џ' => 'џ',
303
+ 'А' => 'а',
304
+ 'Б' => 'б',
305
+ 'В' => 'в',
306
+ 'Г' => 'г',
307
+ 'Д' => 'д',
308
+ 'Е' => 'е',
309
+ 'Ж' => 'ж',
310
+ 'З' => 'з',
311
+ 'И' => 'и',
312
+ 'Й' => 'й',
313
+ 'К' => 'к',
314
+ 'Л' => 'л',
315
+ 'М' => 'м',
316
+ 'Н' => 'н',
317
+ 'О' => 'о',
318
+ 'П' => 'п',
319
+ 'Р' => 'р',
320
+ 'С' => 'с',
321
+ 'Т' => 'т',
322
+ 'У' => 'у',
323
+ 'Ф' => 'ф',
324
+ 'Х' => 'х',
325
+ 'Ц' => 'ц',
326
+ 'Ч' => 'ч',
327
+ 'Ш' => 'ш',
328
+ 'Щ' => 'щ',
329
+ 'Ъ' => 'ъ',
330
+ 'Ы' => 'ы',
331
+ 'Ь' => 'ь',
332
+ 'Э' => 'э',
333
+ 'Ю' => 'ю',
334
+ 'Я' => 'я',
335
+ 'Ѡ' => 'ѡ',
336
+ 'Ѣ' => 'ѣ',
337
+ 'Ѥ' => 'ѥ',
338
+ 'Ѧ' => 'ѧ',
339
+ 'Ѩ' => 'ѩ',
340
+ 'Ѫ' => 'ѫ',
341
+ 'Ѭ' => 'ѭ',
342
+ 'Ѯ' => 'ѯ',
343
+ 'Ѱ' => 'ѱ',
344
+ 'Ѳ' => 'ѳ',
345
+ 'Ѵ' => 'ѵ',
346
+ 'Ѷ' => 'ѷ',
347
+ 'Ѹ' => 'ѹ',
348
+ 'Ѻ' => 'ѻ',
349
+ 'Ѽ' => 'ѽ',
350
+ 'Ѿ' => 'ѿ',
351
+ 'Ҁ' => 'ҁ',
352
+ 'Ҋ' => 'ҋ',
353
+ 'Ҍ' => 'ҍ',
354
+ 'Ҏ' => 'ҏ',
355
+ 'Ґ' => 'ґ',
356
+ 'Ғ' => 'ғ',
357
+ 'Ҕ' => 'ҕ',
358
+ 'Җ' => 'җ',
359
+ 'Ҙ' => 'ҙ',
360
+ 'Қ' => 'қ',
361
+ 'Ҝ' => 'ҝ',
362
+ 'Ҟ' => 'ҟ',
363
+ 'Ҡ' => 'ҡ',
364
+ 'Ң' => 'ң',
365
+ 'Ҥ' => 'ҥ',
366
+ 'Ҧ' => 'ҧ',
367
+ 'Ҩ' => 'ҩ',
368
+ 'Ҫ' => 'ҫ',
369
+ 'Ҭ' => 'ҭ',
370
+ 'Ү' => 'ү',
371
+ 'Ұ' => 'ұ',
372
+ 'Ҳ' => 'ҳ',
373
+ 'Ҵ' => 'ҵ',
374
+ 'Ҷ' => 'ҷ',
375
+ 'Ҹ' => 'ҹ',
376
+ 'Һ' => 'һ',
377
+ 'Ҽ' => 'ҽ',
378
+ 'Ҿ' => 'ҿ',
379
+ 'Ӏ' => 'ӏ',
380
+ 'Ӂ' => 'ӂ',
381
+ 'Ӄ' => 'ӄ',
382
+ 'Ӆ' => 'ӆ',
383
+ 'Ӈ' => 'ӈ',
384
+ 'Ӊ' => 'ӊ',
385
+ 'Ӌ' => 'ӌ',
386
+ 'Ӎ' => 'ӎ',
387
+ 'Ӑ' => 'ӑ',
388
+ 'Ӓ' => 'ӓ',
389
+ 'Ӕ' => 'ӕ',
390
+ 'Ӗ' => 'ӗ',
391
+ 'Ә' => 'ә',
392
+ 'Ӛ' => 'ӛ',
393
+ 'Ӝ' => 'ӝ',
394
+ 'Ӟ' => 'ӟ',
395
+ 'Ӡ' => 'ӡ',
396
+ 'Ӣ' => 'ӣ',
397
+ 'Ӥ' => 'ӥ',
398
+ 'Ӧ' => 'ӧ',
399
+ 'Ө' => 'ө',
400
+ 'Ӫ' => 'ӫ',
401
+ 'Ӭ' => 'ӭ',
402
+ 'Ӯ' => 'ӯ',
403
+ 'Ӱ' => 'ӱ',
404
+ 'Ӳ' => 'ӳ',
405
+ 'Ӵ' => 'ӵ',
406
+ 'Ӷ' => 'ӷ',
407
+ 'Ӹ' => 'ӹ',
408
+ 'Ӻ' => 'ӻ',
409
+ 'Ӽ' => 'ӽ',
410
+ 'Ӿ' => 'ӿ',
411
+ 'Ԁ' => 'ԁ',
412
+ 'Ԃ' => 'ԃ',
413
+ 'Ԅ' => 'ԅ',
414
+ 'Ԇ' => 'ԇ',
415
+ 'Ԉ' => 'ԉ',
416
+ 'Ԋ' => 'ԋ',
417
+ 'Ԍ' => 'ԍ',
418
+ 'Ԏ' => 'ԏ',
419
+ 'Ԑ' => 'ԑ',
420
+ 'Ԓ' => 'ԓ',
421
+ 'Ԕ' => 'ԕ',
422
+ 'Ԗ' => 'ԗ',
423
+ 'Ԙ' => 'ԙ',
424
+ 'Ԛ' => 'ԛ',
425
+ 'Ԝ' => 'ԝ',
426
+ 'Ԟ' => 'ԟ',
427
+ 'Ԡ' => 'ԡ',
428
+ 'Ԣ' => 'ԣ',
429
+ 'Ԥ' => 'ԥ',
430
+ 'Ԧ' => 'ԧ',
431
+ 'Ԩ' => 'ԩ',
432
+ 'Ԫ' => 'ԫ',
433
+ 'Ԭ' => 'ԭ',
434
+ 'Ԯ' => 'ԯ',
435
+ 'Ա' => 'ա',
436
+ 'Բ' => 'բ',
437
+ 'Գ' => 'գ',
438
+ 'Դ' => 'դ',
439
+ 'Ե' => 'ե',
440
+ 'Զ' => 'զ',
441
+ 'Է' => 'է',
442
+ 'Ը' => 'ը',
443
+ 'Թ' => 'թ',
444
+ 'Ժ' => 'ժ',
445
+ 'Ի' => 'ի',
446
+ 'Լ' => 'լ',
447
+ 'Խ' => 'խ',
448
+ 'Ծ' => 'ծ',
449
+ 'Կ' => 'կ',
450
+ 'Հ' => 'հ',
451
+ 'Ձ' => 'ձ',
452
+ 'Ղ' => 'ղ',
453
+ 'Ճ' => 'ճ',
454
+ 'Մ' => 'մ',
455
+ 'Յ' => 'յ',
456
+ 'Ն' => 'ն',
457
+ 'Շ' => 'շ',
458
+ 'Ո' => 'ո',
459
+ 'Չ' => 'չ',
460
+ 'Պ' => 'պ',
461
+ 'Ջ' => 'ջ',
462
+ 'Ռ' => 'ռ',
463
+ 'Ս' => 'ս',
464
+ 'Վ' => 'վ',
465
+ 'Տ' => 'տ',
466
+ 'Ր' => 'ր',
467
+ 'Ց' => 'ց',
468
+ 'Ւ' => 'ւ',
469
+ 'Փ' => 'փ',
470
+ 'Ք' => 'ք',
471
+ 'Օ' => 'օ',
472
+ 'Ֆ' => 'ֆ',
473
+ 'Ⴀ' => 'ⴀ',
474
+ 'Ⴁ' => 'ⴁ',
475
+ 'Ⴂ' => 'ⴂ',
476
+ 'Ⴃ' => 'ⴃ',
477
+ 'Ⴄ' => 'ⴄ',
478
+ 'Ⴅ' => 'ⴅ',
479
+ 'Ⴆ' => 'ⴆ',
480
+ 'Ⴇ' => 'ⴇ',
481
+ 'Ⴈ' => 'ⴈ',
482
+ 'Ⴉ' => 'ⴉ',
483
+ 'Ⴊ' => 'ⴊ',
484
+ 'Ⴋ' => 'ⴋ',
485
+ 'Ⴌ' => 'ⴌ',
486
+ 'Ⴍ' => 'ⴍ',
487
+ 'Ⴎ' => 'ⴎ',
488
+ 'Ⴏ' => 'ⴏ',
489
+ 'Ⴐ' => 'ⴐ',
490
+ 'Ⴑ' => 'ⴑ',
491
+ 'Ⴒ' => 'ⴒ',
492
+ 'Ⴓ' => 'ⴓ',
493
+ 'Ⴔ' => 'ⴔ',
494
+ 'Ⴕ' => 'ⴕ',
495
+ 'Ⴖ' => 'ⴖ',
496
+ 'Ⴗ' => 'ⴗ',
497
+ 'Ⴘ' => 'ⴘ',
498
+ 'Ⴙ' => 'ⴙ',
499
+ 'Ⴚ' => 'ⴚ',
500
+ 'Ⴛ' => 'ⴛ',
501
+ 'Ⴜ' => 'ⴜ',
502
+ 'Ⴝ' => 'ⴝ',
503
+ 'Ⴞ' => 'ⴞ',
504
+ 'Ⴟ' => 'ⴟ',
505
+ 'Ⴠ' => 'ⴠ',
506
+ 'Ⴡ' => 'ⴡ',
507
+ 'Ⴢ' => 'ⴢ',
508
+ 'Ⴣ' => 'ⴣ',
509
+ 'Ⴤ' => 'ⴤ',
510
+ 'Ⴥ' => 'ⴥ',
511
+ 'Ⴧ' => 'ⴧ',
512
+ 'Ⴭ' => 'ⴭ',
513
+ 'Ꭰ' => 'ꭰ',
514
+ 'Ꭱ' => 'ꭱ',
515
+ 'Ꭲ' => 'ꭲ',
516
+ 'Ꭳ' => 'ꭳ',
517
+ 'Ꭴ' => 'ꭴ',
518
+ 'Ꭵ' => 'ꭵ',
519
+ 'Ꭶ' => 'ꭶ',
520
+ 'Ꭷ' => 'ꭷ',
521
+ 'Ꭸ' => 'ꭸ',
522
+ 'Ꭹ' => 'ꭹ',
523
+ 'Ꭺ' => 'ꭺ',
524
+ 'Ꭻ' => 'ꭻ',
525
+ 'Ꭼ' => 'ꭼ',
526
+ 'Ꭽ' => 'ꭽ',
527
+ 'Ꭾ' => 'ꭾ',
528
+ 'Ꭿ' => 'ꭿ',
529
+ 'Ꮀ' => 'ꮀ',
530
+ 'Ꮁ' => 'ꮁ',
531
+ 'Ꮂ' => 'ꮂ',
532
+ 'Ꮃ' => 'ꮃ',
533
+ 'Ꮄ' => 'ꮄ',
534
+ 'Ꮅ' => 'ꮅ',
535
+ 'Ꮆ' => 'ꮆ',
536
+ 'Ꮇ' => 'ꮇ',
537
+ 'Ꮈ' => 'ꮈ',
538
+ 'Ꮉ' => 'ꮉ',
539
+ 'Ꮊ' => 'ꮊ',
540
+ 'Ꮋ' => 'ꮋ',
541
+ 'Ꮌ' => 'ꮌ',
542
+ 'Ꮍ' => 'ꮍ',
543
+ 'Ꮎ' => 'ꮎ',
544
+ 'Ꮏ' => 'ꮏ',
545
+ 'Ꮐ' => 'ꮐ',
546
+ 'Ꮑ' => 'ꮑ',
547
+ 'Ꮒ' => 'ꮒ',
548
+ 'Ꮓ' => 'ꮓ',
549
+ 'Ꮔ' => 'ꮔ',
550
+ 'Ꮕ' => 'ꮕ',
551
+ 'Ꮖ' => 'ꮖ',
552
+ 'Ꮗ' => 'ꮗ',
553
+ 'Ꮘ' => 'ꮘ',
554
+ 'Ꮙ' => 'ꮙ',
555
+ 'Ꮚ' => 'ꮚ',
556
+ 'Ꮛ' => 'ꮛ',
557
+ 'Ꮜ' => 'ꮜ',
558
+ 'Ꮝ' => 'ꮝ',
559
+ 'Ꮞ' => 'ꮞ',
560
+ 'Ꮟ' => 'ꮟ',
561
+ 'Ꮠ' => 'ꮠ',
562
+ 'Ꮡ' => 'ꮡ',
563
+ 'Ꮢ' => 'ꮢ',
564
+ 'Ꮣ' => 'ꮣ',
565
+ 'Ꮤ' => 'ꮤ',
566
+ 'Ꮥ' => 'ꮥ',
567
+ 'Ꮦ' => 'ꮦ',
568
+ 'Ꮧ' => 'ꮧ',
569
+ 'Ꮨ' => 'ꮨ',
570
+ 'Ꮩ' => 'ꮩ',
571
+ 'Ꮪ' => 'ꮪ',
572
+ 'Ꮫ' => 'ꮫ',
573
+ 'Ꮬ' => 'ꮬ',
574
+ 'Ꮭ' => 'ꮭ',
575
+ 'Ꮮ' => 'ꮮ',
576
+ 'Ꮯ' => 'ꮯ',
577
+ 'Ꮰ' => 'ꮰ',
578
+ 'Ꮱ' => 'ꮱ',
579
+ 'Ꮲ' => 'ꮲ',
580
+ 'Ꮳ' => 'ꮳ',
581
+ 'Ꮴ' => 'ꮴ',
582
+ 'Ꮵ' => 'ꮵ',
583
+ 'Ꮶ' => 'ꮶ',
584
+ 'Ꮷ' => 'ꮷ',
585
+ 'Ꮸ' => 'ꮸ',
586
+ 'Ꮹ' => 'ꮹ',
587
+ 'Ꮺ' => 'ꮺ',
588
+ 'Ꮻ' => 'ꮻ',
589
+ 'Ꮼ' => 'ꮼ',
590
+ 'Ꮽ' => 'ꮽ',
591
+ 'Ꮾ' => 'ꮾ',
592
+ 'Ꮿ' => 'ꮿ',
593
+ 'Ᏸ' => 'ᏸ',
594
+ 'Ᏹ' => 'ᏹ',
595
+ 'Ᏺ' => 'ᏺ',
596
+ 'Ᏻ' => 'ᏻ',
597
+ 'Ᏼ' => 'ᏼ',
598
+ 'Ᏽ' => 'ᏽ',
599
+ 'Ა' => 'ა',
600
+ 'Ბ' => 'ბ',
601
+ 'Გ' => 'გ',
602
+ 'Დ' => 'დ',
603
+ 'Ე' => 'ე',
604
+ 'Ვ' => 'ვ',
605
+ 'Ზ' => 'ზ',
606
+ 'Თ' => 'თ',
607
+ 'Ი' => 'ი',
608
+ 'Კ' => 'კ',
609
+ 'Ლ' => 'ლ',
610
+ 'Მ' => 'მ',
611
+ 'Ნ' => 'ნ',
612
+ 'Ო' => 'ო',
613
+ 'Პ' => 'პ',
614
+ 'Ჟ' => 'ჟ',
615
+ 'Რ' => 'რ',
616
+ 'Ს' => 'ს',
617
+ 'Ტ' => 'ტ',
618
+ 'Უ' => 'უ',
619
+ 'Ფ' => 'ფ',
620
+ 'Ქ' => 'ქ',
621
+ 'Ღ' => 'ღ',
622
+ 'Ყ' => 'ყ',
623
+ 'Შ' => 'შ',
624
+ 'Ჩ' => 'ჩ',
625
+ 'Ც' => 'ც',
626
+ 'Ძ' => 'ძ',
627
+ 'Წ' => 'წ',
628
+ 'Ჭ' => 'ჭ',
629
+ 'Ხ' => 'ხ',
630
+ 'Ჯ' => 'ჯ',
631
+ 'Ჰ' => 'ჰ',
632
+ 'Ჱ' => 'ჱ',
633
+ 'Ჲ' => 'ჲ',
634
+ 'Ჳ' => 'ჳ',
635
+ 'Ჴ' => 'ჴ',
636
+ 'Ჵ' => 'ჵ',
637
+ 'Ჶ' => 'ჶ',
638
+ 'Ჷ' => 'ჷ',
639
+ 'Ჸ' => 'ჸ',
640
+ 'Ჹ' => 'ჹ',
641
+ 'Ჺ' => 'ჺ',
642
+ 'Ჽ' => 'ჽ',
643
+ 'Ჾ' => 'ჾ',
644
+ 'Ჿ' => 'ჿ',
645
+ 'Ḁ' => 'ḁ',
646
+ 'Ḃ' => 'ḃ',
647
+ 'Ḅ' => 'ḅ',
648
+ 'Ḇ' => 'ḇ',
649
+ 'Ḉ' => 'ḉ',
650
+ 'Ḋ' => 'ḋ',
651
+ 'Ḍ' => 'ḍ',
652
+ 'Ḏ' => 'ḏ',
653
+ 'Ḑ' => 'ḑ',
654
+ 'Ḓ' => 'ḓ',
655
+ 'Ḕ' => 'ḕ',
656
+ 'Ḗ' => 'ḗ',
657
+ 'Ḙ' => 'ḙ',
658
+ 'Ḛ' => 'ḛ',
659
+ 'Ḝ' => 'ḝ',
660
+ 'Ḟ' => 'ḟ',
661
+ 'Ḡ' => 'ḡ',
662
+ 'Ḣ' => 'ḣ',
663
+ 'Ḥ' => 'ḥ',
664
+ 'Ḧ' => 'ḧ',
665
+ 'Ḩ' => 'ḩ',
666
+ 'Ḫ' => 'ḫ',
667
+ 'Ḭ' => 'ḭ',
668
+ 'Ḯ' => 'ḯ',
669
+ 'Ḱ' => 'ḱ',
670
+ 'Ḳ' => 'ḳ',
671
+ 'Ḵ' => 'ḵ',
672
+ 'Ḷ' => 'ḷ',
673
+ 'Ḹ' => 'ḹ',
674
+ 'Ḻ' => 'ḻ',
675
+ 'Ḽ' => 'ḽ',
676
+ 'Ḿ' => 'ḿ',
677
+ 'Ṁ' => 'ṁ',
678
+ 'Ṃ' => 'ṃ',
679
+ 'Ṅ' => 'ṅ',
680
+ 'Ṇ' => 'ṇ',
681
+ 'Ṉ' => 'ṉ',
682
+ 'Ṋ' => 'ṋ',
683
+ 'Ṍ' => 'ṍ',
684
+ 'Ṏ' => 'ṏ',
685
+ 'Ṑ' => 'ṑ',
686
+ 'Ṓ' => 'ṓ',
687
+ 'Ṕ' => 'ṕ',
688
+ 'Ṗ' => 'ṗ',
689
+ 'Ṙ' => 'ṙ',
690
+ 'Ṛ' => 'ṛ',
691
+ 'Ṝ' => 'ṝ',
692
+ 'Ṟ' => 'ṟ',
693
+ 'Ṡ' => 'ṡ',
694
+ 'Ṣ' => 'ṣ',
695
+ 'Ṥ' => 'ṥ',
696
+ 'Ṧ' => 'ṧ',
697
+ 'Ṩ' => 'ṩ',
698
+ 'Ṫ' => 'ṫ',
699
+ 'Ṭ' => 'ṭ',
700
+ 'Ṯ' => 'ṯ',
701
+ 'Ṱ' => 'ṱ',
702
+ 'Ṳ' => 'ṳ',
703
+ 'Ṵ' => 'ṵ',
704
+ 'Ṷ' => 'ṷ',
705
+ 'Ṹ' => 'ṹ',
706
+ 'Ṻ' => 'ṻ',
707
+ 'Ṽ' => 'ṽ',
708
+ 'Ṿ' => 'ṿ',
709
+ 'Ẁ' => 'ẁ',
710
+ 'Ẃ' => 'ẃ',
711
+ 'Ẅ' => 'ẅ',
712
+ 'Ẇ' => 'ẇ',
713
+ 'Ẉ' => 'ẉ',
714
+ 'Ẋ' => 'ẋ',
715
+ 'Ẍ' => 'ẍ',
716
+ 'Ẏ' => 'ẏ',
717
+ 'Ẑ' => 'ẑ',
718
+ 'Ẓ' => 'ẓ',
719
+ 'Ẕ' => 'ẕ',
720
+ 'ẞ' => 'ß',
721
+ 'Ạ' => 'ạ',
722
+ 'Ả' => 'ả',
723
+ 'Ấ' => 'ấ',
724
+ 'Ầ' => 'ầ',
725
+ 'Ẩ' => 'ẩ',
726
+ 'Ẫ' => 'ẫ',
727
+ 'Ậ' => 'ậ',
728
+ 'Ắ' => 'ắ',
729
+ 'Ằ' => 'ằ',
730
+ 'Ẳ' => 'ẳ',
731
+ 'Ẵ' => 'ẵ',
732
+ 'Ặ' => 'ặ',
733
+ 'Ẹ' => 'ẹ',
734
+ 'Ẻ' => 'ẻ',
735
+ 'Ẽ' => 'ẽ',
736
+ 'Ế' => 'ế',
737
+ 'Ề' => 'ề',
738
+ 'Ể' => 'ể',
739
+ 'Ễ' => 'ễ',
740
+ 'Ệ' => 'ệ',
741
+ 'Ỉ' => 'ỉ',
742
+ 'Ị' => 'ị',
743
+ 'Ọ' => 'ọ',
744
+ 'Ỏ' => 'ỏ',
745
+ 'Ố' => 'ố',
746
+ 'Ồ' => 'ồ',
747
+ 'Ổ' => 'ổ',
748
+ 'Ỗ' => 'ỗ',
749
+ 'Ộ' => 'ộ',
750
+ 'Ớ' => 'ớ',
751
+ 'Ờ' => 'ờ',
752
+ 'Ở' => 'ở',
753
+ 'Ỡ' => 'ỡ',
754
+ 'Ợ' => 'ợ',
755
+ 'Ụ' => 'ụ',
756
+ 'Ủ' => 'ủ',
757
+ 'Ứ' => 'ứ',
758
+ 'Ừ' => 'ừ',
759
+ 'Ử' => 'ử',
760
+ 'Ữ' => 'ữ',
761
+ 'Ự' => 'ự',
762
+ 'Ỳ' => 'ỳ',
763
+ 'Ỵ' => 'ỵ',
764
+ 'Ỷ' => 'ỷ',
765
+ 'Ỹ' => 'ỹ',
766
+ 'Ỻ' => 'ỻ',
767
+ 'Ỽ' => 'ỽ',
768
+ 'Ỿ' => 'ỿ',
769
+ 'Ἀ' => 'ἀ',
770
+ 'Ἁ' => 'ἁ',
771
+ 'Ἂ' => 'ἂ',
772
+ 'Ἃ' => 'ἃ',
773
+ 'Ἄ' => 'ἄ',
774
+ 'Ἅ' => 'ἅ',
775
+ 'Ἆ' => 'ἆ',
776
+ 'Ἇ' => 'ἇ',
777
+ 'Ἐ' => 'ἐ',
778
+ 'Ἑ' => 'ἑ',
779
+ 'Ἒ' => 'ἒ',
780
+ 'Ἓ' => 'ἓ',
781
+ 'Ἔ' => 'ἔ',
782
+ 'Ἕ' => 'ἕ',
783
+ 'Ἠ' => 'ἠ',
784
+ 'Ἡ' => 'ἡ',
785
+ 'Ἢ' => 'ἢ',
786
+ 'Ἣ' => 'ἣ',
787
+ 'Ἤ' => 'ἤ',
788
+ 'Ἥ' => 'ἥ',
789
+ 'Ἦ' => 'ἦ',
790
+ 'Ἧ' => 'ἧ',
791
+ 'Ἰ' => 'ἰ',
792
+ 'Ἱ' => 'ἱ',
793
+ 'Ἲ' => 'ἲ',
794
+ 'Ἳ' => 'ἳ',
795
+ 'Ἴ' => 'ἴ',
796
+ 'Ἵ' => 'ἵ',
797
+ 'Ἶ' => 'ἶ',
798
+ 'Ἷ' => 'ἷ',
799
+ 'Ὀ' => 'ὀ',
800
+ 'Ὁ' => 'ὁ',
801
+ 'Ὂ' => 'ὂ',
802
+ 'Ὃ' => 'ὃ',
803
+ 'Ὄ' => 'ὄ',
804
+ 'Ὅ' => 'ὅ',
805
+ 'Ὑ' => 'ὑ',
806
+ 'Ὓ' => 'ὓ',
807
+ 'Ὕ' => 'ὕ',
808
+ 'Ὗ' => 'ὗ',
809
+ 'Ὠ' => 'ὠ',
810
+ 'Ὡ' => 'ὡ',
811
+ 'Ὢ' => 'ὢ',
812
+ 'Ὣ' => 'ὣ',
813
+ 'Ὤ' => 'ὤ',
814
+ 'Ὥ' => 'ὥ',
815
+ 'Ὦ' => 'ὦ',
816
+ 'Ὧ' => 'ὧ',
817
+ 'ᾈ' => 'ᾀ',
818
+ 'ᾉ' => 'ᾁ',
819
+ 'ᾊ' => 'ᾂ',
820
+ 'ᾋ' => 'ᾃ',
821
+ 'ᾌ' => 'ᾄ',
822
+ 'ᾍ' => 'ᾅ',
823
+ 'ᾎ' => 'ᾆ',
824
+ 'ᾏ' => 'ᾇ',
825
+ 'ᾘ' => 'ᾐ',
826
+ 'ᾙ' => 'ᾑ',
827
+ 'ᾚ' => 'ᾒ',
828
+ 'ᾛ' => 'ᾓ',
829
+ 'ᾜ' => 'ᾔ',
830
+ 'ᾝ' => 'ᾕ',
831
+ 'ᾞ' => 'ᾖ',
832
+ 'ᾟ' => 'ᾗ',
833
+ 'ᾨ' => 'ᾠ',
834
+ 'ᾩ' => 'ᾡ',
835
+ 'ᾪ' => 'ᾢ',
836
+ 'ᾫ' => 'ᾣ',
837
+ 'ᾬ' => 'ᾤ',
838
+ 'ᾭ' => 'ᾥ',
839
+ 'ᾮ' => 'ᾦ',
840
+ 'ᾯ' => 'ᾧ',
841
+ 'Ᾰ' => 'ᾰ',
842
+ 'Ᾱ' => 'ᾱ',
843
+ 'Ὰ' => 'ὰ',
844
+ 'Ά' => 'ά',
845
+ 'ᾼ' => 'ᾳ',
846
+ 'Ὲ' => 'ὲ',
847
+ 'Έ' => 'έ',
848
+ 'Ὴ' => 'ὴ',
849
+ 'Ή' => 'ή',
850
+ 'ῌ' => 'ῃ',
851
+ 'Ῐ' => 'ῐ',
852
+ 'Ῑ' => 'ῑ',
853
+ 'Ὶ' => 'ὶ',
854
+ 'Ί' => 'ί',
855
+ 'Ῠ' => 'ῠ',
856
+ 'Ῡ' => 'ῡ',
857
+ 'Ὺ' => 'ὺ',
858
+ 'Ύ' => 'ύ',
859
+ 'Ῥ' => 'ῥ',
860
+ 'Ὸ' => 'ὸ',
861
+ 'Ό' => 'ό',
862
+ 'Ὼ' => 'ὼ',
863
+ 'Ώ' => 'ώ',
864
+ 'ῼ' => 'ῳ',
865
+ 'Ω' => 'ω',
866
+ 'K' => 'k',
867
+ 'Å' => 'å',
868
+ 'Ⅎ' => 'ⅎ',
869
+ 'Ⅰ' => 'ⅰ',
870
+ 'Ⅱ' => 'ⅱ',
871
+ 'Ⅲ' => 'ⅲ',
872
+ 'Ⅳ' => 'ⅳ',
873
+ 'Ⅴ' => 'ⅴ',
874
+ 'Ⅵ' => 'ⅵ',
875
+ 'Ⅶ' => 'ⅶ',
876
+ 'Ⅷ' => 'ⅷ',
877
+ 'Ⅸ' => 'ⅸ',
878
+ 'Ⅹ' => 'ⅹ',
879
+ 'Ⅺ' => 'ⅺ',
880
+ 'Ⅻ' => 'ⅻ',
881
+ 'Ⅼ' => 'ⅼ',
882
+ 'Ⅽ' => 'ⅽ',
883
+ 'Ⅾ' => 'ⅾ',
884
+ 'Ⅿ' => 'ⅿ',
885
+ 'Ↄ' => 'ↄ',
886
+ 'Ⓐ' => 'ⓐ',
887
+ 'Ⓑ' => 'ⓑ',
888
+ 'Ⓒ' => 'ⓒ',
889
+ 'Ⓓ' => 'ⓓ',
890
+ 'Ⓔ' => 'ⓔ',
891
+ 'Ⓕ' => 'ⓕ',
892
+ 'Ⓖ' => 'ⓖ',
893
+ 'Ⓗ' => 'ⓗ',
894
+ 'Ⓘ' => 'ⓘ',
895
+ 'Ⓙ' => 'ⓙ',
896
+ 'Ⓚ' => 'ⓚ',
897
+ 'Ⓛ' => 'ⓛ',
898
+ 'Ⓜ' => 'ⓜ',
899
+ 'Ⓝ' => 'ⓝ',
900
+ 'Ⓞ' => 'ⓞ',
901
+ 'Ⓟ' => 'ⓟ',
902
+ 'Ⓠ' => 'ⓠ',
903
+ 'Ⓡ' => 'ⓡ',
904
+ 'Ⓢ' => 'ⓢ',
905
+ 'Ⓣ' => 'ⓣ',
906
+ 'Ⓤ' => 'ⓤ',
907
+ 'Ⓥ' => 'ⓥ',
908
+ 'Ⓦ' => 'ⓦ',
909
+ 'Ⓧ' => 'ⓧ',
910
+ 'Ⓨ' => 'ⓨ',
911
+ 'Ⓩ' => 'ⓩ',
912
+ 'Ⰰ' => 'ⰰ',
913
+ 'Ⰱ' => 'ⰱ',
914
+ 'Ⰲ' => 'ⰲ',
915
+ 'Ⰳ' => 'ⰳ',
916
+ 'Ⰴ' => 'ⰴ',
917
+ 'Ⰵ' => 'ⰵ',
918
+ 'Ⰶ' => 'ⰶ',
919
+ 'Ⰷ' => 'ⰷ',
920
+ 'Ⰸ' => 'ⰸ',
921
+ 'Ⰹ' => 'ⰹ',
922
+ 'Ⰺ' => 'ⰺ',
923
+ 'Ⰻ' => 'ⰻ',
924
+ 'Ⰼ' => 'ⰼ',
925
+ 'Ⰽ' => 'ⰽ',
926
+ 'Ⰾ' => 'ⰾ',
927
+ 'Ⰿ' => 'ⰿ',
928
+ 'Ⱀ' => 'ⱀ',
929
+ 'Ⱁ' => 'ⱁ',
930
+ 'Ⱂ' => 'ⱂ',
931
+ 'Ⱃ' => 'ⱃ',
932
+ 'Ⱄ' => 'ⱄ',
933
+ 'Ⱅ' => 'ⱅ',
934
+ 'Ⱆ' => 'ⱆ',
935
+ 'Ⱇ' => 'ⱇ',
936
+ 'Ⱈ' => 'ⱈ',
937
+ 'Ⱉ' => 'ⱉ',
938
+ 'Ⱊ' => 'ⱊ',
939
+ 'Ⱋ' => 'ⱋ',
940
+ 'Ⱌ' => 'ⱌ',
941
+ 'Ⱍ' => 'ⱍ',
942
+ 'Ⱎ' => 'ⱎ',
943
+ 'Ⱏ' => 'ⱏ',
944
+ 'Ⱐ' => 'ⱐ',
945
+ 'Ⱑ' => 'ⱑ',
946
+ 'Ⱒ' => 'ⱒ',
947
+ 'Ⱓ' => 'ⱓ',
948
+ 'Ⱔ' => 'ⱔ',
949
+ 'Ⱕ' => 'ⱕ',
950
+ 'Ⱖ' => 'ⱖ',
951
+ 'Ⱗ' => 'ⱗ',
952
+ 'Ⱘ' => 'ⱘ',
953
+ 'Ⱙ' => 'ⱙ',
954
+ 'Ⱚ' => 'ⱚ',
955
+ 'Ⱛ' => 'ⱛ',
956
+ 'Ⱜ' => 'ⱜ',
957
+ 'Ⱝ' => 'ⱝ',
958
+ 'Ⱞ' => 'ⱞ',
959
+ 'Ⱡ' => 'ⱡ',
960
+ 'Ɫ' => 'ɫ',
961
+ 'Ᵽ' => 'ᵽ',
962
+ 'Ɽ' => 'ɽ',
963
+ 'Ⱨ' => 'ⱨ',
964
+ 'Ⱪ' => 'ⱪ',
965
+ 'Ⱬ' => 'ⱬ',
966
+ 'Ɑ' => 'ɑ',
967
+ 'Ɱ' => 'ɱ',
968
+ 'Ɐ' => 'ɐ',
969
+ 'Ɒ' => 'ɒ',
970
+ 'Ⱳ' => 'ⱳ',
971
+ 'Ⱶ' => 'ⱶ',
972
+ 'Ȿ' => 'ȿ',
973
+ 'Ɀ' => 'ɀ',
974
+ 'Ⲁ' => 'ⲁ',
975
+ 'Ⲃ' => 'ⲃ',
976
+ 'Ⲅ' => 'ⲅ',
977
+ 'Ⲇ' => 'ⲇ',
978
+ 'Ⲉ' => 'ⲉ',
979
+ 'Ⲋ' => 'ⲋ',
980
+ 'Ⲍ' => 'ⲍ',
981
+ 'Ⲏ' => 'ⲏ',
982
+ 'Ⲑ' => 'ⲑ',
983
+ 'Ⲓ' => 'ⲓ',
984
+ 'Ⲕ' => 'ⲕ',
985
+ 'Ⲗ' => 'ⲗ',
986
+ 'Ⲙ' => 'ⲙ',
987
+ 'Ⲛ' => 'ⲛ',
988
+ 'Ⲝ' => 'ⲝ',
989
+ 'Ⲟ' => 'ⲟ',
990
+ 'Ⲡ' => 'ⲡ',
991
+ 'Ⲣ' => 'ⲣ',
992
+ 'Ⲥ' => 'ⲥ',
993
+ 'Ⲧ' => 'ⲧ',
994
+ 'Ⲩ' => 'ⲩ',
995
+ 'Ⲫ' => 'ⲫ',
996
+ 'Ⲭ' => 'ⲭ',
997
+ 'Ⲯ' => 'ⲯ',
998
+ 'Ⲱ' => 'ⲱ',
999
+ 'Ⲳ' => 'ⲳ',
1000
+ 'Ⲵ' => 'ⲵ',
1001
+ 'Ⲷ' => 'ⲷ',
1002
+ 'Ⲹ' => 'ⲹ',
1003
+ 'Ⲻ' => 'ⲻ',
1004
+ 'Ⲽ' => 'ⲽ',
1005
+ 'Ⲿ' => 'ⲿ',
1006
+ 'Ⳁ' => 'ⳁ',
1007
+ 'Ⳃ' => 'ⳃ',
1008
+ 'Ⳅ' => 'ⳅ',
1009
+ 'Ⳇ' => 'ⳇ',
1010
+ 'Ⳉ' => 'ⳉ',
1011
+ 'Ⳋ' => 'ⳋ',
1012
+ 'Ⳍ' => 'ⳍ',
1013
+ 'Ⳏ' => 'ⳏ',
1014
+ 'Ⳑ' => 'ⳑ',
1015
+ 'Ⳓ' => 'ⳓ',
1016
+ 'Ⳕ' => 'ⳕ',
1017
+ 'Ⳗ' => 'ⳗ',
1018
+ 'Ⳙ' => 'ⳙ',
1019
+ 'Ⳛ' => 'ⳛ',
1020
+ 'Ⳝ' => 'ⳝ',
1021
+ 'Ⳟ' => 'ⳟ',
1022
+ 'Ⳡ' => 'ⳡ',
1023
+ 'Ⳣ' => 'ⳣ',
1024
+ 'Ⳬ' => 'ⳬ',
1025
+ 'Ⳮ' => 'ⳮ',
1026
+ 'Ⳳ' => 'ⳳ',
1027
+ 'Ꙁ' => 'ꙁ',
1028
+ 'Ꙃ' => 'ꙃ',
1029
+ 'Ꙅ' => 'ꙅ',
1030
+ 'Ꙇ' => 'ꙇ',
1031
+ 'Ꙉ' => 'ꙉ',
1032
+ 'Ꙋ' => 'ꙋ',
1033
+ 'Ꙍ' => 'ꙍ',
1034
+ 'Ꙏ' => 'ꙏ',
1035
+ 'Ꙑ' => 'ꙑ',
1036
+ 'Ꙓ' => 'ꙓ',
1037
+ 'Ꙕ' => 'ꙕ',
1038
+ 'Ꙗ' => 'ꙗ',
1039
+ 'Ꙙ' => 'ꙙ',
1040
+ 'Ꙛ' => 'ꙛ',
1041
+ 'Ꙝ' => 'ꙝ',
1042
+ 'Ꙟ' => 'ꙟ',
1043
+ 'Ꙡ' => 'ꙡ',
1044
+ 'Ꙣ' => 'ꙣ',
1045
+ 'Ꙥ' => 'ꙥ',
1046
+ 'Ꙧ' => 'ꙧ',
1047
+ 'Ꙩ' => 'ꙩ',
1048
+ 'Ꙫ' => 'ꙫ',
1049
+ 'Ꙭ' => 'ꙭ',
1050
+ 'Ꚁ' => 'ꚁ',
1051
+ 'Ꚃ' => 'ꚃ',
1052
+ 'Ꚅ' => 'ꚅ',
1053
+ 'Ꚇ' => 'ꚇ',
1054
+ 'Ꚉ' => 'ꚉ',
1055
+ 'Ꚋ' => 'ꚋ',
1056
+ 'Ꚍ' => 'ꚍ',
1057
+ 'Ꚏ' => 'ꚏ',
1058
+ 'Ꚑ' => 'ꚑ',
1059
+ 'Ꚓ' => 'ꚓ',
1060
+ 'Ꚕ' => 'ꚕ',
1061
+ 'Ꚗ' => 'ꚗ',
1062
+ 'Ꚙ' => 'ꚙ',
1063
+ 'Ꚛ' => 'ꚛ',
1064
+ 'Ꜣ' => 'ꜣ',
1065
+ 'Ꜥ' => 'ꜥ',
1066
+ 'Ꜧ' => 'ꜧ',
1067
+ 'Ꜩ' => 'ꜩ',
1068
+ 'Ꜫ' => 'ꜫ',
1069
+ 'Ꜭ' => 'ꜭ',
1070
+ 'Ꜯ' => 'ꜯ',
1071
+ 'Ꜳ' => 'ꜳ',
1072
+ 'Ꜵ' => 'ꜵ',
1073
+ 'Ꜷ' => 'ꜷ',
1074
+ 'Ꜹ' => 'ꜹ',
1075
+ 'Ꜻ' => 'ꜻ',
1076
+ 'Ꜽ' => 'ꜽ',
1077
+ 'Ꜿ' => 'ꜿ',
1078
+ 'Ꝁ' => 'ꝁ',
1079
+ 'Ꝃ' => 'ꝃ',
1080
+ 'Ꝅ' => 'ꝅ',
1081
+ 'Ꝇ' => 'ꝇ',
1082
+ 'Ꝉ' => 'ꝉ',
1083
+ 'Ꝋ' => 'ꝋ',
1084
+ 'Ꝍ' => 'ꝍ',
1085
+ 'Ꝏ' => 'ꝏ',
1086
+ 'Ꝑ' => 'ꝑ',
1087
+ 'Ꝓ' => 'ꝓ',
1088
+ 'Ꝕ' => 'ꝕ',
1089
+ 'Ꝗ' => 'ꝗ',
1090
+ 'Ꝙ' => 'ꝙ',
1091
+ 'Ꝛ' => 'ꝛ',
1092
+ 'Ꝝ' => 'ꝝ',
1093
+ 'Ꝟ' => 'ꝟ',
1094
+ 'Ꝡ' => 'ꝡ',
1095
+ 'Ꝣ' => 'ꝣ',
1096
+ 'Ꝥ' => 'ꝥ',
1097
+ 'Ꝧ' => 'ꝧ',
1098
+ 'Ꝩ' => 'ꝩ',
1099
+ 'Ꝫ' => 'ꝫ',
1100
+ 'Ꝭ' => 'ꝭ',
1101
+ 'Ꝯ' => 'ꝯ',
1102
+ 'Ꝺ' => 'ꝺ',
1103
+ 'Ꝼ' => 'ꝼ',
1104
+ 'Ᵹ' => 'ᵹ',
1105
+ 'Ꝿ' => 'ꝿ',
1106
+ 'Ꞁ' => 'ꞁ',
1107
+ 'Ꞃ' => 'ꞃ',
1108
+ 'Ꞅ' => 'ꞅ',
1109
+ 'Ꞇ' => 'ꞇ',
1110
+ 'Ꞌ' => 'ꞌ',
1111
+ 'Ɥ' => 'ɥ',
1112
+ 'Ꞑ' => 'ꞑ',
1113
+ 'Ꞓ' => 'ꞓ',
1114
+ 'Ꞗ' => 'ꞗ',
1115
+ 'Ꞙ' => 'ꞙ',
1116
+ 'Ꞛ' => 'ꞛ',
1117
+ 'Ꞝ' => 'ꞝ',
1118
+ 'Ꞟ' => 'ꞟ',
1119
+ 'Ꞡ' => 'ꞡ',
1120
+ 'Ꞣ' => 'ꞣ',
1121
+ 'Ꞥ' => 'ꞥ',
1122
+ 'Ꞧ' => 'ꞧ',
1123
+ 'Ꞩ' => 'ꞩ',
1124
+ 'Ɦ' => 'ɦ',
1125
+ 'Ɜ' => 'ɜ',
1126
+ 'Ɡ' => 'ɡ',
1127
+ 'Ɬ' => 'ɬ',
1128
+ 'Ɪ' => 'ɪ',
1129
+ 'Ʞ' => 'ʞ',
1130
+ 'Ʇ' => 'ʇ',
1131
+ 'Ʝ' => 'ʝ',
1132
+ 'Ꭓ' => 'ꭓ',
1133
+ 'Ꞵ' => 'ꞵ',
1134
+ 'Ꞷ' => 'ꞷ',
1135
+ 'Ꞹ' => 'ꞹ',
1136
+ 'Ꞻ' => 'ꞻ',
1137
+ 'Ꞽ' => 'ꞽ',
1138
+ 'Ꞿ' => 'ꞿ',
1139
+ 'Ꟃ' => 'ꟃ',
1140
+ 'Ꞔ' => 'ꞔ',
1141
+ 'Ʂ' => 'ʂ',
1142
+ 'Ᶎ' => 'ᶎ',
1143
+ 'Ꟈ' => 'ꟈ',
1144
+ 'Ꟊ' => 'ꟊ',
1145
+ 'Ꟶ' => 'ꟶ',
1146
+ 'A' => 'a',
1147
+ 'B' => 'b',
1148
+ 'C' => 'c',
1149
+ 'D' => 'd',
1150
+ 'E' => 'e',
1151
+ 'F' => 'f',
1152
+ 'G' => 'g',
1153
+ 'H' => 'h',
1154
+ 'I' => 'i',
1155
+ 'J' => 'j',
1156
+ 'K' => 'k',
1157
+ 'L' => 'l',
1158
+ 'M' => 'm',
1159
+ 'N' => 'n',
1160
+ 'O' => 'o',
1161
+ 'P' => 'p',
1162
+ 'Q' => 'q',
1163
+ 'R' => 'r',
1164
+ 'S' => 's',
1165
+ 'T' => 't',
1166
+ 'U' => 'u',
1167
+ 'V' => 'v',
1168
+ 'W' => 'w',
1169
+ 'X' => 'x',
1170
+ 'Y' => 'y',
1171
+ 'Z' => 'z',
1172
+ '𐐀' => '𐐨',
1173
+ '𐐁' => '𐐩',
1174
+ '𐐂' => '𐐪',
1175
+ '𐐃' => '𐐫',
1176
+ '𐐄' => '𐐬',
1177
+ '𐐅' => '𐐭',
1178
+ '𐐆' => '𐐮',
1179
+ '𐐇' => '𐐯',
1180
+ '𐐈' => '𐐰',
1181
+ '𐐉' => '𐐱',
1182
+ '𐐊' => '𐐲',
1183
+ '𐐋' => '𐐳',
1184
+ '𐐌' => '𐐴',
1185
+ '𐐍' => '𐐵',
1186
+ '𐐎' => '𐐶',
1187
+ '𐐏' => '𐐷',
1188
+ '𐐐' => '𐐸',
1189
+ '𐐑' => '𐐹',
1190
+ '𐐒' => '𐐺',
1191
+ '𐐓' => '𐐻',
1192
+ '𐐔' => '𐐼',
1193
+ '𐐕' => '𐐽',
1194
+ '𐐖' => '𐐾',
1195
+ '𐐗' => '𐐿',
1196
+ '𐐘' => '𐑀',
1197
+ '𐐙' => '𐑁',
1198
+ '𐐚' => '𐑂',
1199
+ '𐐛' => '𐑃',
1200
+ '𐐜' => '𐑄',
1201
+ '𐐝' => '𐑅',
1202
+ '𐐞' => '𐑆',
1203
+ '𐐟' => '𐑇',
1204
+ '𐐠' => '𐑈',
1205
+ '𐐡' => '𐑉',
1206
+ '𐐢' => '𐑊',
1207
+ '𐐣' => '𐑋',
1208
+ '𐐤' => '𐑌',
1209
+ '𐐥' => '𐑍',
1210
+ '𐐦' => '𐑎',
1211
+ '𐐧' => '𐑏',
1212
+ '𐒰' => '𐓘',
1213
+ '𐒱' => '𐓙',
1214
+ '𐒲' => '𐓚',
1215
+ '𐒳' => '𐓛',
1216
+ '𐒴' => '𐓜',
1217
+ '𐒵' => '𐓝',
1218
+ '𐒶' => '𐓞',
1219
+ '𐒷' => '𐓟',
1220
+ '𐒸' => '𐓠',
1221
+ '𐒹' => '𐓡',
1222
+ '𐒺' => '𐓢',
1223
+ '𐒻' => '𐓣',
1224
+ '𐒼' => '𐓤',
1225
+ '𐒽' => '𐓥',
1226
+ '𐒾' => '𐓦',
1227
+ '𐒿' => '𐓧',
1228
+ '𐓀' => '𐓨',
1229
+ '𐓁' => '𐓩',
1230
+ '𐓂' => '𐓪',
1231
+ '𐓃' => '𐓫',
1232
+ '𐓄' => '𐓬',
1233
+ '𐓅' => '𐓭',
1234
+ '𐓆' => '𐓮',
1235
+ '𐓇' => '𐓯',
1236
+ '𐓈' => '𐓰',
1237
+ '𐓉' => '𐓱',
1238
+ '𐓊' => '𐓲',
1239
+ '𐓋' => '𐓳',
1240
+ '𐓌' => '𐓴',
1241
+ '𐓍' => '𐓵',
1242
+ '𐓎' => '𐓶',
1243
+ '𐓏' => '𐓷',
1244
+ '𐓐' => '𐓸',
1245
+ '𐓑' => '𐓹',
1246
+ '𐓒' => '𐓺',
1247
+ '𐓓' => '𐓻',
1248
+ '𐲀' => '𐳀',
1249
+ '𐲁' => '𐳁',
1250
+ '𐲂' => '𐳂',
1251
+ '𐲃' => '𐳃',
1252
+ '𐲄' => '𐳄',
1253
+ '𐲅' => '𐳅',
1254
+ '𐲆' => '𐳆',
1255
+ '𐲇' => '𐳇',
1256
+ '𐲈' => '𐳈',
1257
+ '𐲉' => '𐳉',
1258
+ '𐲊' => '𐳊',
1259
+ '𐲋' => '𐳋',
1260
+ '𐲌' => '𐳌',
1261
+ '𐲍' => '𐳍',
1262
+ '𐲎' => '𐳎',
1263
+ '𐲏' => '𐳏',
1264
+ '𐲐' => '𐳐',
1265
+ '𐲑' => '𐳑',
1266
+ '𐲒' => '𐳒',
1267
+ '𐲓' => '𐳓',
1268
+ '𐲔' => '𐳔',
1269
+ '𐲕' => '𐳕',
1270
+ '𐲖' => '𐳖',
1271
+ '𐲗' => '𐳗',
1272
+ '𐲘' => '𐳘',
1273
+ '𐲙' => '𐳙',
1274
+ '𐲚' => '𐳚',
1275
+ '𐲛' => '𐳛',
1276
+ '𐲜' => '𐳜',
1277
+ '𐲝' => '𐳝',
1278
+ '𐲞' => '𐳞',
1279
+ '𐲟' => '𐳟',
1280
+ '𐲠' => '𐳠',
1281
+ '𐲡' => '𐳡',
1282
+ '𐲢' => '𐳢',
1283
+ '𐲣' => '𐳣',
1284
+ '𐲤' => '𐳤',
1285
+ '𐲥' => '𐳥',
1286
+ '𐲦' => '𐳦',
1287
+ '𐲧' => '𐳧',
1288
+ '𐲨' => '𐳨',
1289
+ '𐲩' => '𐳩',
1290
+ '𐲪' => '𐳪',
1291
+ '𐲫' => '𐳫',
1292
+ '𐲬' => '𐳬',
1293
+ '𐲭' => '𐳭',
1294
+ '𐲮' => '𐳮',
1295
+ '𐲯' => '𐳯',
1296
+ '𐲰' => '𐳰',
1297
+ '𐲱' => '𐳱',
1298
+ '𐲲' => '𐳲',
1299
+ '𑢠' => '𑣀',
1300
+ '𑢡' => '𑣁',
1301
+ '𑢢' => '𑣂',
1302
+ '𑢣' => '𑣃',
1303
+ '𑢤' => '𑣄',
1304
+ '𑢥' => '𑣅',
1305
+ '𑢦' => '𑣆',
1306
+ '𑢧' => '𑣇',
1307
+ '𑢨' => '𑣈',
1308
+ '𑢩' => '𑣉',
1309
+ '𑢪' => '𑣊',
1310
+ '𑢫' => '𑣋',
1311
+ '𑢬' => '𑣌',
1312
+ '𑢭' => '𑣍',
1313
+ '𑢮' => '𑣎',
1314
+ '𑢯' => '𑣏',
1315
+ '𑢰' => '𑣐',
1316
+ '𑢱' => '𑣑',
1317
+ '𑢲' => '𑣒',
1318
+ '𑢳' => '𑣓',
1319
+ '𑢴' => '𑣔',
1320
+ '𑢵' => '𑣕',
1321
+ '𑢶' => '𑣖',
1322
+ '𑢷' => '𑣗',
1323
+ '𑢸' => '𑣘',
1324
+ '𑢹' => '𑣙',
1325
+ '𑢺' => '𑣚',
1326
+ '𑢻' => '𑣛',
1327
+ '𑢼' => '𑣜',
1328
+ '𑢽' => '𑣝',
1329
+ '𑢾' => '𑣞',
1330
+ '𑢿' => '𑣟',
1331
+ '𖹀' => '𖹠',
1332
+ '𖹁' => '𖹡',
1333
+ '𖹂' => '𖹢',
1334
+ '𖹃' => '𖹣',
1335
+ '𖹄' => '𖹤',
1336
+ '𖹅' => '𖹥',
1337
+ '𖹆' => '𖹦',
1338
+ '𖹇' => '𖹧',
1339
+ '𖹈' => '𖹨',
1340
+ '𖹉' => '𖹩',
1341
+ '𖹊' => '𖹪',
1342
+ '𖹋' => '𖹫',
1343
+ '𖹌' => '𖹬',
1344
+ '𖹍' => '𖹭',
1345
+ '𖹎' => '𖹮',
1346
+ '𖹏' => '𖹯',
1347
+ '𖹐' => '𖹰',
1348
+ '𖹑' => '𖹱',
1349
+ '𖹒' => '𖹲',
1350
+ '𖹓' => '𖹳',
1351
+ '𖹔' => '𖹴',
1352
+ '𖹕' => '𖹵',
1353
+ '𖹖' => '𖹶',
1354
+ '𖹗' => '𖹷',
1355
+ '𖹘' => '𖹸',
1356
+ '𖹙' => '𖹹',
1357
+ '𖹚' => '𖹺',
1358
+ '𖹛' => '𖹻',
1359
+ '𖹜' => '𖹼',
1360
+ '𖹝' => '𖹽',
1361
+ '𖹞' => '𖹾',
1362
+ '𖹟' => '𖹿',
1363
+ '𞤀' => '𞤢',
1364
+ '𞤁' => '𞤣',
1365
+ '𞤂' => '𞤤',
1366
+ '𞤃' => '𞤥',
1367
+ '𞤄' => '𞤦',
1368
+ '𞤅' => '𞤧',
1369
+ '𞤆' => '𞤨',
1370
+ '𞤇' => '𞤩',
1371
+ '𞤈' => '𞤪',
1372
+ '𞤉' => '𞤫',
1373
+ '𞤊' => '𞤬',
1374
+ '𞤋' => '𞤭',
1375
+ '𞤌' => '𞤮',
1376
+ '𞤍' => '𞤯',
1377
+ '𞤎' => '𞤰',
1378
+ '𞤏' => '𞤱',
1379
+ '𞤐' => '𞤲',
1380
+ '𞤑' => '𞤳',
1381
+ '𞤒' => '𞤴',
1382
+ '𞤓' => '𞤵',
1383
+ '𞤔' => '𞤶',
1384
+ '𞤕' => '𞤷',
1385
+ '𞤖' => '𞤸',
1386
+ '𞤗' => '𞤹',
1387
+ '𞤘' => '𞤺',
1388
+ '𞤙' => '𞤻',
1389
+ '𞤚' => '𞤼',
1390
+ '𞤛' => '𞤽',
1391
+ '𞤜' => '𞤾',
1392
+ '𞤝' => '𞤿',
1393
+ '𞤞' => '𞥀',
1394
+ '𞤟' => '𞥁',
1395
+ '𞤠' => '𞥂',
1396
+ '𞤡' => '𞥃',
1397
+ );
vendor/browscap-php/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ <?php
2
+
3
+ // from Case_Ignorable in https://unicode.org/Public/UNIDATA/DerivedCoreProperties.txt
4
+
5
+ return '/(?<![\x{0027}\x{002E}\x{003A}\x{005E}\x{0060}\x{00A8}\x{00AD}\x{00AF}\x{00B4}\x{00B7}\x{00B8}\x{02B0}-\x{02C1}\x{02C2}-\x{02C5}\x{02C6}-\x{02D1}\x{02D2}-\x{02DF}\x{02E0}-\x{02E4}\x{02E5}-\x{02EB}\x{02EC}\x{02ED}\x{02EE}\x{02EF}-\x{02FF}\x{0300}-\x{036F}\x{0374}\x{0375}\x{037A}\x{0384}-\x{0385}\x{0387}\x{0483}-\x{0487}\x{0488}-\x{0489}\x{0559}\x{0591}-\x{05BD}\x{05BF}\x{05C1}-\x{05C2}\x{05C4}-\x{05C5}\x{05C7}\x{05F4}\x{0600}-\x{0605}\x{0610}-\x{061A}\x{061C}\x{0640}\x{064B}-\x{065F}\x{0670}\x{06D6}-\x{06DC}\x{06DD}\x{06DF}-\x{06E4}\x{06E5}-\x{06E6}\x{06E7}-\x{06E8}\x{06EA}-\x{06ED}\x{070F}\x{0711}\x{0730}-\x{074A}\x{07A6}-\x{07B0}\x{07EB}-\x{07F3}\x{07F4}-\x{07F5}\x{07FA}\x{07FD}\x{0816}-\x{0819}\x{081A}\x{081B}-\x{0823}\x{0824}\x{0825}-\x{0827}\x{0828}\x{0829}-\x{082D}\x{0859}-\x{085B}\x{08D3}-\x{08E1}\x{08E2}\x{08E3}-\x{0902}\x{093A}\x{093C}\x{0941}-\x{0948}\x{094D}\x{0951}-\x{0957}\x{0962}-\x{0963}\x{0971}\x{0981}\x{09BC}\x{09C1}-\x{09C4}\x{09CD}\x{09E2}-\x{09E3}\x{09FE}\x{0A01}-\x{0A02}\x{0A3C}\x{0A41}-\x{0A42}\x{0A47}-\x{0A48}\x{0A4B}-\x{0A4D}\x{0A51}\x{0A70}-\x{0A71}\x{0A75}\x{0A81}-\x{0A82}\x{0ABC}\x{0AC1}-\x{0AC5}\x{0AC7}-\x{0AC8}\x{0ACD}\x{0AE2}-\x{0AE3}\x{0AFA}-\x{0AFF}\x{0B01}\x{0B3C}\x{0B3F}\x{0B41}-\x{0B44}\x{0B4D}\x{0B56}\x{0B62}-\x{0B63}\x{0B82}\x{0BC0}\x{0BCD}\x{0C00}\x{0C04}\x{0C3E}-\x{0C40}\x{0C46}-\x{0C48}\x{0C4A}-\x{0C4D}\x{0C55}-\x{0C56}\x{0C62}-\x{0C63}\x{0C81}\x{0CBC}\x{0CBF}\x{0CC6}\x{0CCC}-\x{0CCD}\x{0CE2}-\x{0CE3}\x{0D00}-\x{0D01}\x{0D3B}-\x{0D3C}\x{0D41}-\x{0D44}\x{0D4D}\x{0D62}-\x{0D63}\x{0DCA}\x{0DD2}-\x{0DD4}\x{0DD6}\x{0E31}\x{0E34}-\x{0E3A}\x{0E46}\x{0E47}-\x{0E4E}\x{0EB1}\x{0EB4}-\x{0EB9}\x{0EBB}-\x{0EBC}\x{0EC6}\x{0EC8}-\x{0ECD}\x{0F18}-\x{0F19}\x{0F35}\x{0F37}\x{0F39}\x{0F71}-\x{0F7E}\x{0F80}-\x{0F84}\x{0F86}-\x{0F87}\x{0F8D}-\x{0F97}\x{0F99}-\x{0FBC}\x{0FC6}\x{102D}-\x{1030}\x{1032}-\x{1037}\x{1039}-\x{103A}\x{103D}-\x{103E}\x{1058}-\x{1059}\x{105E}-\x{1060}\x{1071}-\x{1074}\x{1082}\x{1085}-\x{1086}\x{108D}\x{109D}\x{10FC}\x{135D}-\x{135F}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}-\x{1753}\x{1772}-\x{1773}\x{17B4}-\x{17B5}\x{17B7}-\x{17BD}\x{17C6}\x{17C9}-\x{17D3}\x{17D7}\x{17DD}\x{180B}-\x{180D}\x{180E}\x{1843}\x{1885}-\x{1886}\x{18A9}\x{1920}-\x{1922}\x{1927}-\x{1928}\x{1932}\x{1939}-\x{193B}\x{1A17}-\x{1A18}\x{1A1B}\x{1A56}\x{1A58}-\x{1A5E}\x{1A60}\x{1A62}\x{1A65}-\x{1A6C}\x{1A73}-\x{1A7C}\x{1A7F}\x{1AA7}\x{1AB0}-\x{1ABD}\x{1ABE}\x{1B00}-\x{1B03}\x{1B34}\x{1B36}-\x{1B3A}\x{1B3C}\x{1B42}\x{1B6B}-\x{1B73}\x{1B80}-\x{1B81}\x{1BA2}-\x{1BA5}\x{1BA8}-\x{1BA9}\x{1BAB}-\x{1BAD}\x{1BE6}\x{1BE8}-\x{1BE9}\x{1BED}\x{1BEF}-\x{1BF1}\x{1C2C}-\x{1C33}\x{1C36}-\x{1C37}\x{1C78}-\x{1C7D}\x{1CD0}-\x{1CD2}\x{1CD4}-\x{1CE0}\x{1CE2}-\x{1CE8}\x{1CED}\x{1CF4}\x{1CF8}-\x{1CF9}\x{1D2C}-\x{1D6A}\x{1D78}\x{1D9B}-\x{1DBF}\x{1DC0}-\x{1DF9}\x{1DFB}-\x{1DFF}\x{1FBD}\x{1FBF}-\x{1FC1}\x{1FCD}-\x{1FCF}\x{1FDD}-\x{1FDF}\x{1FED}-\x{1FEF}\x{1FFD}-\x{1FFE}\x{200B}-\x{200F}\x{2018}\x{2019}\x{2024}\x{2027}\x{202A}-\x{202E}\x{2060}-\x{2064}\x{2066}-\x{206F}\x{2071}\x{207F}\x{2090}-\x{209C}\x{20D0}-\x{20DC}\x{20DD}-\x{20E0}\x{20E1}\x{20E2}-\x{20E4}\x{20E5}-\x{20F0}\x{2C7C}-\x{2C7D}\x{2CEF}-\x{2CF1}\x{2D6F}\x{2D7F}\x{2DE0}-\x{2DFF}\x{2E2F}\x{3005}\x{302A}-\x{302D}\x{3031}-\x{3035}\x{303B}\x{3099}-\x{309A}\x{309B}-\x{309C}\x{309D}-\x{309E}\x{30FC}-\x{30FE}\x{A015}\x{A4F8}-\x{A4FD}\x{A60C}\x{A66F}\x{A670}-\x{A672}\x{A674}-\x{A67D}\x{A67F}\x{A69C}-\x{A69D}\x{A69E}-\x{A69F}\x{A6F0}-\x{A6F1}\x{A700}-\x{A716}\x{A717}-\x{A71F}\x{A720}-\x{A721}\x{A770}\x{A788}\x{A789}-\x{A78A}\x{A7F8}-\x{A7F9}\x{A802}\x{A806}\x{A80B}\x{A825}-\x{A826}\x{A8C4}-\x{A8C5}\x{A8E0}-\x{A8F1}\x{A8FF}\x{A926}-\x{A92D}\x{A947}-\x{A951}\x{A980}-\x{A982}\x{A9B3}\x{A9B6}-\x{A9B9}\x{A9BC}\x{A9CF}\x{A9E5}\x{A9E6}\x{AA29}-\x{AA2E}\x{AA31}-\x{AA32}\x{AA35}-\x{AA36}\x{AA43}\x{AA4C}\x{AA70}\x{AA7C}\x{AAB0}\x{AAB2}-\x{AAB4}\x{AAB7}-\x{AAB8}\x{AABE}-\x{AABF}\x{AAC1}\x{AADD}\x{AAEC}-\x{AAED}\x{AAF3}-\x{AAF4}\x{AAF6}\x{AB5B}\x{AB5C}-\x{AB5F}\x{ABE5}\x{ABE8}\x{ABED}\x{FB1E}\x{FBB2}-\x{FBC1}\x{FE00}-\x{FE0F}\x{FE13}\x{FE20}-\x{FE2F}\x{FE52}\x{FE55}\x{FEFF}\x{FF07}\x{FF0E}\x{FF1A}\x{FF3E}\x{FF40}\x{FF70}\x{FF9E}-\x{FF9F}\x{FFE3}\x{FFF9}-\x{FFFB}\x{101FD}\x{102E0}\x{10376}-\x{1037A}\x{10A01}-\x{10A03}\x{10A05}-\x{10A06}\x{10A0C}-\x{10A0F}\x{10A38}-\x{10A3A}\x{10A3F}\x{10AE5}-\x{10AE6}\x{10D24}-\x{10D27}\x{10F46}-\x{10F50}\x{11001}\x{11038}-\x{11046}\x{1107F}-\x{11081}\x{110B3}-\x{110B6}\x{110B9}-\x{110BA}\x{110BD}\x{110CD}\x{11100}-\x{11102}\x{11127}-\x{1112B}\x{1112D}-\x{11134}\x{11173}\x{11180}-\x{11181}\x{111B6}-\x{111BE}\x{111C9}-\x{111CC}\x{1122F}-\x{11231}\x{11234}\x{11236}-\x{11237}\x{1123E}\x{112DF}\x{112E3}-\x{112EA}\x{11300}-\x{11301}\x{1133B}-\x{1133C}\x{11340}\x{11366}-\x{1136C}\x{11370}-\x{11374}\x{11438}-\x{1143F}\x{11442}-\x{11444}\x{11446}\x{1145E}\x{114B3}-\x{114B8}\x{114BA}\x{114BF}-\x{114C0}\x{114C2}-\x{114C3}\x{115B2}-\x{115B5}\x{115BC}-\x{115BD}\x{115BF}-\x{115C0}\x{115DC}-\x{115DD}\x{11633}-\x{1163A}\x{1163D}\x{1163F}-\x{11640}\x{116AB}\x{116AD}\x{116B0}-\x{116B5}\x{116B7}\x{1171D}-\x{1171F}\x{11722}-\x{11725}\x{11727}-\x{1172B}\x{1182F}-\x{11837}\x{11839}-\x{1183A}\x{11A01}-\x{11A0A}\x{11A33}-\x{11A38}\x{11A3B}-\x{11A3E}\x{11A47}\x{11A51}-\x{11A56}\x{11A59}-\x{11A5B}\x{11A8A}-\x{11A96}\x{11A98}-\x{11A99}\x{11C30}-\x{11C36}\x{11C38}-\x{11C3D}\x{11C3F}\x{11C92}-\x{11CA7}\x{11CAA}-\x{11CB0}\x{11CB2}-\x{11CB3}\x{11CB5}-\x{11CB6}\x{11D31}-\x{11D36}\x{11D3A}\x{11D3C}-\x{11D3D}\x{11D3F}-\x{11D45}\x{11D47}\x{11D90}-\x{11D91}\x{11D95}\x{11D97}\x{11EF3}-\x{11EF4}\x{16AF0}-\x{16AF4}\x{16B30}-\x{16B36}\x{16B40}-\x{16B43}\x{16F8F}-\x{16F92}\x{16F93}-\x{16F9F}\x{16FE0}-\x{16FE1}\x{1BC9D}-\x{1BC9E}\x{1BCA0}-\x{1BCA3}\x{1D167}-\x{1D169}\x{1D173}-\x{1D17A}\x{1D17B}-\x{1D182}\x{1D185}-\x{1D18B}\x{1D1AA}-\x{1D1AD}\x{1D242}-\x{1D244}\x{1DA00}-\x{1DA36}\x{1DA3B}-\x{1DA6C}\x{1DA75}\x{1DA84}\x{1DA9B}-\x{1DA9F}\x{1DAA1}-\x{1DAAF}\x{1E000}-\x{1E006}\x{1E008}-\x{1E018}\x{1E01B}-\x{1E021}\x{1E023}-\x{1E024}\x{1E026}-\x{1E02A}\x{1E8D0}-\x{1E8D6}\x{1E944}-\x{1E94A}\x{1F3FB}-\x{1F3FF}\x{E0001}\x{E0020}-\x{E007F}\x{E0100}-\x{E01EF}])(\pL)(\pL*+)/u';
vendor/browscap-php/symfony/polyfill-mbstring/Resources/unidata/upperCase.php ADDED
@@ -0,0 +1,1489 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ return array (
4
+ 'a' => 'A',
5
+ 'b' => 'B',
6
+ 'c' => 'C',
7
+ 'd' => 'D',
8
+ 'e' => 'E',
9
+ 'f' => 'F',
10
+ 'g' => 'G',
11
+ 'h' => 'H',
12
+ 'i' => 'I',
13
+ 'j' => 'J',
14
+ 'k' => 'K',
15
+ 'l' => 'L',
16
+ 'm' => 'M',
17
+ 'n' => 'N',
18
+ 'o' => 'O',
19
+ 'p' => 'P',
20
+ 'q' => 'Q',
21
+ 'r' => 'R',
22
+ 's' => 'S',
23
+ 't' => 'T',
24
+ 'u' => 'U',
25
+ 'v' => 'V',
26
+ 'w' => 'W',
27
+ 'x' => 'X',
28
+ 'y' => 'Y',
29
+ 'z' => 'Z',
30
+ 'µ' => 'Μ',
31
+ 'à' => 'À',
32
+ 'á' => 'Á',
33
+ 'â' => 'Â',
34
+ 'ã' => 'Ã',
35
+ 'ä' => 'Ä',
36
+ 'å' => 'Å',
37
+ 'æ' => 'Æ',
38
+ 'ç' => 'Ç',
39
+ 'è' => 'È',
40
+ 'é' => 'É',
41
+ 'ê' => 'Ê',
42
+ 'ë' => 'Ë',
43
+ 'ì' => 'Ì',
44
+ 'í' => 'Í',
45
+ 'î' => 'Î',
46
+ 'ï' => 'Ï',
47
+ 'ð' => 'Ð',
48
+ 'ñ' => 'Ñ',
49
+ 'ò' => 'Ò',
50
+ 'ó' => 'Ó',
51
+ 'ô' => 'Ô',
52
+ 'õ' => 'Õ',
53
+ 'ö' => 'Ö',
54
+ 'ø' => 'Ø',
55
+ 'ù' => 'Ù',
56
+ 'ú' => 'Ú',
57
+ 'û' => 'Û',
58
+ 'ü' => 'Ü',
59
+ 'ý' => 'Ý',
60
+ 'þ' => 'Þ',
61
+ 'ÿ' => 'Ÿ',
62
+ 'ā' => 'Ā',
63
+ 'ă' => 'Ă',
64
+ 'ą' => 'Ą',
65
+ 'ć' => 'Ć',
66
+ 'ĉ' => 'Ĉ',
67
+ 'ċ' => 'Ċ',
68
+ 'č' => 'Č',
69
+ 'ď' => 'Ď',
70
+ 'đ' => 'Đ',
71
+ 'ē' => 'Ē',
72
+ 'ĕ' => 'Ĕ',
73
+ 'ė' => 'Ė',
74
+ 'ę' => 'Ę',
75
+ 'ě' => 'Ě',
76
+ 'ĝ' => 'Ĝ',
77
+ 'ğ' => 'Ğ',
78
+ 'ġ' => 'Ġ',
79
+ 'ģ' => 'Ģ',
80
+ 'ĥ' => 'Ĥ',
81
+ 'ħ' => 'Ħ',
82
+ 'ĩ' => 'Ĩ',
83
+ 'ī' => 'Ī',
84
+ 'ĭ' => 'Ĭ',
85
+ 'į' => 'Į',
86
+ 'ı' => 'I',
87
+ 'ij' => 'IJ',
88
+ 'ĵ' => 'Ĵ',
89
+ 'ķ' => 'Ķ',
90
+ 'ĺ' => 'Ĺ',
91
+ 'ļ' => 'Ļ',
92
+ 'ľ' => 'Ľ',
93
+ 'ŀ' => 'Ŀ',
94
+ 'ł' => 'Ł',
95
+ 'ń' => 'Ń',
96
+ 'ņ' => 'Ņ',
97
+ 'ň' => 'Ň',
98
+ 'ŋ' => 'Ŋ',
99
+ 'ō' => 'Ō',
100
+ 'ŏ' => 'Ŏ',
101
+ 'ő' => 'Ő',
102
+ 'œ' => 'Œ',
103
+ 'ŕ' => 'Ŕ',
104
+ 'ŗ' => 'Ŗ',
105
+ 'ř' => 'Ř',
106
+ 'ś' => 'Ś',
107
+ 'ŝ' => 'Ŝ',
108
+ 'ş' => 'Ş',
109
+ 'š' => 'Š',
110
+ 'ţ' => 'Ţ',
111
+ 'ť' => 'Ť',
112
+ 'ŧ' => 'Ŧ',
113
+ 'ũ' => 'Ũ',
114
+ 'ū' => 'Ū',
115
+ 'ŭ' => 'Ŭ',
116
+ 'ů' => 'Ů',
117
+ 'ű' => 'Ű',
118
+ 'ų' => 'Ų',
119
+ 'ŵ' => 'Ŵ',
120
+ 'ŷ' => 'Ŷ',
121
+ 'ź' => 'Ź',
122
+ 'ż' => 'Ż',
123
+ 'ž' => 'Ž',
124
+ 'ſ' => 'S',
125
+ 'ƀ' => 'Ƀ',
126
+ 'ƃ' => 'Ƃ',
127
+ 'ƅ' => 'Ƅ',
128
+ 'ƈ' => 'Ƈ',
129
+ 'ƌ' => 'Ƌ',
130
+ 'ƒ' => 'Ƒ',
131
+ 'ƕ' => 'Ƕ',
132
+ 'ƙ' => 'Ƙ',
133
+ 'ƚ' => 'Ƚ',
134
+ 'ƞ' => 'Ƞ',
135
+ 'ơ' => 'Ơ',
136
+ 'ƣ' => 'Ƣ',
137
+ 'ƥ' => 'Ƥ',
138
+ 'ƨ' => 'Ƨ',
139
+ 'ƭ' => 'Ƭ',
140
+ 'ư' => 'Ư',
141
+ 'ƴ' => 'Ƴ',
142
+ 'ƶ' => 'Ƶ',
143
+ 'ƹ' => 'Ƹ',
144
+ 'ƽ' => 'Ƽ',
145
+ 'ƿ' => 'Ƿ',
146
+ 'Dž' => 'DŽ',
147
+ 'dž' => 'DŽ',
148
+ 'Lj' => 'LJ',
149
+ 'lj' => 'LJ',
150
+ 'Nj' => 'NJ',
151
+ 'nj' => 'NJ',
152
+ 'ǎ' => 'Ǎ',
153
+ 'ǐ' => 'Ǐ',
154
+ 'ǒ' => 'Ǒ',
155
+ 'ǔ' => 'Ǔ',
156
+ 'ǖ' => 'Ǖ',
157
+ 'ǘ' => 'Ǘ',
158
+ 'ǚ' => 'Ǚ',
159
+ 'ǜ' => 'Ǜ',
160
+ 'ǝ' => 'Ǝ',
161
+ 'ǟ' => 'Ǟ',
162
+ 'ǡ' => 'Ǡ',
163
+ 'ǣ' => 'Ǣ',
164
+ 'ǥ' => 'Ǥ',
165
+ 'ǧ' => 'Ǧ',
166
+ 'ǩ' => 'Ǩ',
167
+ 'ǫ' => 'Ǫ',
168
+ 'ǭ' => 'Ǭ',
169
+ 'ǯ' => 'Ǯ',
170
+ 'Dz' => 'DZ',
171
+ 'dz' => 'DZ',
172
+ 'ǵ' => 'Ǵ',
173
+ 'ǹ' => 'Ǹ',
174
+ 'ǻ' => 'Ǻ',
175
+ 'ǽ' => 'Ǽ',
176
+ 'ǿ' => 'Ǿ',
177
+ 'ȁ' => 'Ȁ',
178
+ 'ȃ' => 'Ȃ',
179
+ 'ȅ' => 'Ȅ',
180
+ 'ȇ' => 'Ȇ',
181
+ 'ȉ' => 'Ȉ',
182
+ 'ȋ' => 'Ȋ',
183
+ 'ȍ' => 'Ȍ',
184
+ 'ȏ' => 'Ȏ',
185
+ 'ȑ' => 'Ȑ',
186
+ 'ȓ' => 'Ȓ',
187
+ 'ȕ' => 'Ȕ',
188
+ 'ȗ' => 'Ȗ',
189
+ 'ș' => 'Ș',
190
+ 'ț' => 'Ț',
191
+ 'ȝ' => 'Ȝ',
192
+ 'ȟ' => 'Ȟ',
193
+ 'ȣ' => 'Ȣ',
194
+ 'ȥ' => 'Ȥ',
195
+ 'ȧ' => 'Ȧ',
196
+ 'ȩ' => 'Ȩ',
197
+ 'ȫ' => 'Ȫ',
198
+ 'ȭ' => 'Ȭ',
199
+ 'ȯ' => 'Ȯ',
200
+ 'ȱ' => 'Ȱ',
201
+ 'ȳ' => 'Ȳ',
202
+ 'ȼ' => 'Ȼ',
203
+ 'ȿ' => 'Ȿ',
204
+ 'ɀ' => 'Ɀ',
205
+ 'ɂ' => 'Ɂ',
206
+ 'ɇ' => 'Ɇ',
207
+ 'ɉ' => 'Ɉ',
208
+ 'ɋ' => 'Ɋ',
209
+ 'ɍ' => 'Ɍ',
210
+ 'ɏ' => 'Ɏ',
211
+ 'ɐ' => 'Ɐ',
212
+ 'ɑ' => 'Ɑ',
213
+ 'ɒ' => 'Ɒ',
214
+ 'ɓ' => 'Ɓ',
215
+ 'ɔ' => 'Ɔ',
216
+ 'ɖ' => 'Ɖ',
217
+ 'ɗ' => 'Ɗ',
218
+ 'ə' => 'Ə',
219
+ 'ɛ' => 'Ɛ',
220
+ 'ɜ' => 'Ɜ',
221
+ 'ɠ' => 'Ɠ',
222
+ 'ɡ' => 'Ɡ',
223
+ 'ɣ' => 'Ɣ',
224
+ 'ɥ' => 'Ɥ',
225
+ 'ɦ' => 'Ɦ',
226
+ 'ɨ' => 'Ɨ',
227
+ 'ɩ' => 'Ɩ',
228
+ 'ɪ' => 'Ɪ',
229
+ 'ɫ' => 'Ɫ',
230
+ 'ɬ' => 'Ɬ',
231
+ 'ɯ' => 'Ɯ',
232
+ 'ɱ' => 'Ɱ',
233
+ 'ɲ' => 'Ɲ',
234
+ 'ɵ' => 'Ɵ',
235
+ 'ɽ' => 'Ɽ',
236
+ 'ʀ' => 'Ʀ',
237
+ 'ʂ' => 'Ʂ',
238
+ 'ʃ' => 'Ʃ',
239
+ 'ʇ' => 'Ʇ',
240
+ 'ʈ' => 'Ʈ',
241
+ 'ʉ' => 'Ʉ',
242
+ 'ʊ' => 'Ʊ',
243
+ 'ʋ' => 'Ʋ',
244
+ 'ʌ' => 'Ʌ',
245
+ 'ʒ' => 'Ʒ',
246
+ 'ʝ' => 'Ʝ',
247
+ 'ʞ' => 'Ʞ',
248
+ 'ͅ' => 'Ι',
249
+ 'ͱ' => 'Ͱ',
250
+ 'ͳ' => 'Ͳ',
251
+ 'ͷ' => 'Ͷ',
252
+ 'ͻ' => 'Ͻ',
253
+ 'ͼ' => 'Ͼ',
254
+ 'ͽ' => 'Ͽ',
255
+ 'ά' => 'Ά',
256
+ 'έ' => 'Έ',
257
+ 'ή' => 'Ή',
258
+ 'ί' => 'Ί',
259
+ 'α' => 'Α',
260
+ 'β' => 'Β',
261
+ 'γ' => 'Γ',
262
+ 'δ' => 'Δ',
263
+ 'ε' => 'Ε',
264
+ 'ζ' => 'Ζ',
265
+ 'η' => 'Η',
266
+ 'θ' => 'Θ',
267
+ 'ι' => 'Ι',
268
+ 'κ' => 'Κ',
269
+ 'λ' => 'Λ',
270
+ 'μ' => 'Μ',
271
+ 'ν' => 'Ν',
272
+ 'ξ' => 'Ξ',
273
+ 'ο' => 'Ο',
274
+ 'π' => 'Π',
275
+ 'ρ' => 'Ρ',
276
+ 'ς' => 'Σ',
277
+ 'σ' => 'Σ',
278
+ 'τ' => 'Τ',
279
+ 'υ' => 'Υ',
280
+ 'φ' => 'Φ',
281
+ 'χ' => 'Χ',
282
+ 'ψ' => 'Ψ',
283
+ 'ω' => 'Ω',
284
+ 'ϊ' => 'Ϊ',
285
+ 'ϋ' => 'Ϋ',
286
+ 'ό' => 'Ό',
287
+ 'ύ' => 'Ύ',
288
+ 'ώ' => 'Ώ',
289
+ 'ϐ' => 'Β',
290
+ 'ϑ' => 'Θ',
291
+ 'ϕ' => 'Φ',
292
+ 'ϖ' => 'Π',
293
+ 'ϗ' => 'Ϗ',
294
+ 'ϙ' => 'Ϙ',
295
+ 'ϛ' => 'Ϛ',
296
+ 'ϝ' => 'Ϝ',
297
+ 'ϟ' => 'Ϟ',
298
+ 'ϡ' => 'Ϡ',
299
+ 'ϣ' => 'Ϣ',
300
+ 'ϥ' => 'Ϥ',
301
+ 'ϧ' => 'Ϧ',
302
+ 'ϩ' => 'Ϩ',
303
+ 'ϫ' => 'Ϫ',
304
+ 'ϭ' => 'Ϭ',
305
+ 'ϯ' => 'Ϯ',
306
+ 'ϰ' => 'Κ',
307
+ 'ϱ' => 'Ρ',
308
+ 'ϲ' => 'Ϲ',
309
+ 'ϳ' => 'Ϳ',
310
+ 'ϵ' => 'Ε',
311
+ 'ϸ' => 'Ϸ',
312
+ 'ϻ' => 'Ϻ',
313
+ 'а' => 'А',
314
+ 'б' => 'Б',
315
+ 'в' => 'В',
316
+ 'г' => 'Г',
317
+ 'д' => 'Д',
318
+ 'е' => 'Е',
319
+ 'ж' => 'Ж',
320
+ 'з' => 'З',
321
+ 'и' => 'И',
322
+ 'й' => 'Й',
323
+ 'к' => 'К',
324
+ 'л' => 'Л',
325
+ 'м' => 'М',
326
+ 'н' => 'Н',
327
+ 'о' => 'О',
328
+ 'п' => 'П',
329
+ 'р' => 'Р',
330
+ 'с' => 'С',
331
+ 'т' => 'Т',
332
+ 'у' => 'У',
333
+ 'ф' => 'Ф',
334
+ 'х' => 'Х',
335
+ 'ц' => 'Ц',
336
+ 'ч' => 'Ч',
337
+ 'ш' => 'Ш',
338
+ 'щ' => 'Щ',
339
+ 'ъ' => 'Ъ',
340
+ 'ы' => 'Ы',
341
+ 'ь' => 'Ь',
342
+ 'э' => 'Э',
343
+ 'ю' => 'Ю',
344
+ 'я' => 'Я',
345
+ 'ѐ' => 'Ѐ',
346
+ 'ё' => 'Ё',
347
+ 'ђ' => 'Ђ',
348
+ 'ѓ' => 'Ѓ',
349
+ 'є' => 'Є',
350
+ 'ѕ' => 'Ѕ',
351
+ 'і' => 'І',
352
+ 'ї' => 'Ї',
353
+ 'ј' => 'Ј',
354
+ 'љ' => 'Љ',
355
+ 'њ' => 'Њ',
356
+ 'ћ' => 'Ћ',
357
+ 'ќ' => 'Ќ',
358
+ 'ѝ' => 'Ѝ',
359
+ 'ў' => 'Ў',
360
+ 'џ' => 'Џ',
361
+ 'ѡ' => 'Ѡ',
362
+ 'ѣ' => 'Ѣ',
363
+ 'ѥ' => 'Ѥ',
364
+ 'ѧ' => 'Ѧ',
365
+ 'ѩ' => 'Ѩ',
366
+ 'ѫ' => 'Ѫ',
367
+ 'ѭ' => 'Ѭ',
368
+ 'ѯ' => 'Ѯ',
369
+ 'ѱ' => 'Ѱ',
370
+ 'ѳ' => 'Ѳ',
371
+ 'ѵ' => 'Ѵ',
372
+ 'ѷ' => 'Ѷ',
373
+ 'ѹ' => 'Ѹ',
374
+ 'ѻ' => 'Ѻ',
375
+ 'ѽ' => 'Ѽ',
376
+ 'ѿ' => 'Ѿ',
377
+ 'ҁ' => 'Ҁ',
378
+ 'ҋ' => 'Ҋ',
379
+ 'ҍ' => 'Ҍ',
380
+ 'ҏ' => 'Ҏ',
381
+ 'ґ' => 'Ґ',
382
+ 'ғ' => 'Ғ',
383
+ 'ҕ' => 'Ҕ',
384
+ 'җ' => 'Җ',
385
+ 'ҙ' => 'Ҙ',
386
+ 'қ' => 'Қ',
387
+ 'ҝ' => 'Ҝ',
388
+ 'ҟ' => 'Ҟ',
389
+ 'ҡ' => 'Ҡ',
390
+ 'ң' => 'Ң',
391
+ 'ҥ' => 'Ҥ',
392
+ 'ҧ' => 'Ҧ',
393
+ 'ҩ' => 'Ҩ',
394
+ 'ҫ' => 'Ҫ',
395
+ 'ҭ' => 'Ҭ',
396
+ 'ү' => 'Ү',
397
+ 'ұ' => 'Ұ',
398
+ 'ҳ' => 'Ҳ',
399
+ 'ҵ' => 'Ҵ',
400
+ 'ҷ' => 'Ҷ',
401
+ 'ҹ' => 'Ҹ',
402
+ 'һ' => 'Һ',
403
+ 'ҽ' => 'Ҽ',
404
+ 'ҿ' => 'Ҿ',
405
+ 'ӂ' => 'Ӂ',
406
+ 'ӄ' => 'Ӄ',
407
+ 'ӆ' => 'Ӆ',
408
+ 'ӈ' => 'Ӈ',
409
+ 'ӊ' => 'Ӊ',
410
+ 'ӌ' => 'Ӌ',
411
+ 'ӎ' => 'Ӎ',
412
+ 'ӏ' => 'Ӏ',
413
+ 'ӑ' => 'Ӑ',
414
+ 'ӓ' => 'Ӓ',
415
+ 'ӕ' => 'Ӕ',
416
+ 'ӗ' => 'Ӗ',
417
+ 'ә' => 'Ә',
418
+ 'ӛ' => 'Ӛ',
419
+ 'ӝ' => 'Ӝ',
420
+ 'ӟ' => 'Ӟ',
421
+ 'ӡ' => 'Ӡ',
422
+ 'ӣ' => 'Ӣ',
423
+ 'ӥ' => 'Ӥ',
424
+ 'ӧ' => 'Ӧ',
425
+ 'ө' => 'Ө',
426
+ 'ӫ' => 'Ӫ',
427
+ 'ӭ' => 'Ӭ',
428
+ 'ӯ' => 'Ӯ',
429
+ 'ӱ' => 'Ӱ',
430
+ 'ӳ' => 'Ӳ',
431
+ 'ӵ' => 'Ӵ',
432
+ 'ӷ' => 'Ӷ',
433
+ 'ӹ' => 'Ӹ',
434
+ 'ӻ' => 'Ӻ',
435
+ 'ӽ' => 'Ӽ',
436
+ 'ӿ' => 'Ӿ',
437
+ 'ԁ' => 'Ԁ',
438
+ 'ԃ' => 'Ԃ',
439
+ 'ԅ' => 'Ԅ',
440
+ 'ԇ' => 'Ԇ',
441
+ 'ԉ' => 'Ԉ',
442
+ 'ԋ' => 'Ԋ',
443
+ 'ԍ' => 'Ԍ',
444
+ 'ԏ' => 'Ԏ',
445
+ 'ԑ' => 'Ԑ',
446
+ 'ԓ' => 'Ԓ',
447
+ 'ԕ' => 'Ԕ',
448
+ 'ԗ' => 'Ԗ',
449
+ 'ԙ' => 'Ԙ',
450
+ 'ԛ' => 'Ԛ',
451
+ 'ԝ' => 'Ԝ',
452
+ 'ԟ' => 'Ԟ',
453
+ 'ԡ' => 'Ԡ',
454
+ 'ԣ' => 'Ԣ',
455
+ 'ԥ' => 'Ԥ',
456
+ 'ԧ' => 'Ԧ',
457
+ 'ԩ' => 'Ԩ',
458
+ 'ԫ' => 'Ԫ',
459
+ 'ԭ' => 'Ԭ',
460
+ 'ԯ' => 'Ԯ',
461
+ 'ա' => 'Ա',
462
+ 'բ' => 'Բ',
463
+ 'գ' => 'Գ',
464
+ 'դ' => 'Դ',
465
+ 'ե' => 'Ե',
466
+ 'զ' => 'Զ',
467
+ 'է' => 'Է',
468
+ 'ը' => 'Ը',
469
+ 'թ' => 'Թ',
470
+ 'ժ' => 'Ժ',
471
+ 'ի' => 'Ի',
472
+ 'լ' => 'Լ',
473
+ 'խ' => 'Խ',
474
+ 'ծ' => 'Ծ',
475
+ 'կ' => 'Կ',
476
+ 'հ' => 'Հ',
477
+ 'ձ' => 'Ձ',
478
+ 'ղ' => 'Ղ',
479
+ 'ճ' => 'Ճ',
480
+ 'մ' => 'Մ',
481
+ 'յ' => 'Յ',
482
+ 'ն' => 'Ն',
483
+ 'շ' => 'Շ',
484
+ 'ո' => 'Ո',
485
+ 'չ' => 'Չ',
486
+ 'պ' => 'Պ',
487
+ 'ջ' => 'Ջ',
488
+ 'ռ' => 'Ռ',
489
+ 'ս' => 'Ս',
490
+ 'վ' => 'Վ',
491
+ 'տ' => 'Տ',
492
+ 'ր' => 'Ր',
493
+ 'ց' => 'Ց',
494
+ 'ւ' => 'Ւ',
495
+ 'փ' => 'Փ',
496
+ 'ք' => 'Ք',
497
+ 'օ' => 'Օ',
498
+ 'ֆ' => 'Ֆ',
499
+ 'ა' => 'Ა',
500
+ 'ბ' => 'Ბ',
501
+ 'გ' => 'Გ',
502
+ 'დ' => 'Დ',
503
+ 'ე' => 'Ე',
504
+ 'ვ' => 'Ვ',
505
+ 'ზ' => 'Ზ',
506
+ 'თ' => 'Თ',
507
+ 'ი' => 'Ი',
508
+ 'კ' => 'Კ',
509
+ 'ლ' => 'Ლ',
510
+ 'მ' => 'Მ',
511
+ 'ნ' => 'Ნ',
512
+ 'ო' => 'Ო',
513
+ 'პ' => 'Პ',
514
+ 'ჟ' => 'Ჟ',
515
+ 'რ' => 'Რ',
516
+ 'ს' => 'Ს',
517
+ 'ტ' => 'Ტ',
518
+ 'უ' => 'Უ',
519
+ 'ფ' => 'Ფ',
520
+ 'ქ' => 'Ქ',
521
+ 'ღ' => 'Ღ',
522
+ 'ყ' => 'Ყ',
523
+ 'შ' => 'Შ',
524
+ 'ჩ' => 'Ჩ',
525
+ 'ც' => 'Ც',
526
+ 'ძ' => 'Ძ',
527
+ 'წ' => 'Წ',
528
+ 'ჭ' => 'Ჭ',
529
+ 'ხ' => 'Ხ',
530
+ 'ჯ' => 'Ჯ',
531
+ 'ჰ' => 'Ჰ',
532
+ 'ჱ' => 'Ჱ',
533
+ 'ჲ' => 'Ჲ',
534
+ 'ჳ' => 'Ჳ',
535
+ 'ჴ' => 'Ჴ',
536
+ 'ჵ' => 'Ჵ',
537
+ 'ჶ' => 'Ჶ',
538
+ 'ჷ' => 'Ჷ',
539
+ 'ჸ' => 'Ჸ',
540
+ 'ჹ' => 'Ჹ',
541
+ 'ჺ' => 'Ჺ',
542
+ 'ჽ' => 'Ჽ',
543
+ 'ჾ' => 'Ჾ',
544
+ 'ჿ' => 'Ჿ',
545
+ 'ᏸ' => 'Ᏸ',
546
+ 'ᏹ' => 'Ᏹ',
547
+ 'ᏺ' => 'Ᏺ',
548
+ 'ᏻ' => 'Ᏻ',
549
+ 'ᏼ' => 'Ᏼ',
550
+ 'ᏽ' => 'Ᏽ',
551
+ 'ᲀ' => 'В',
552
+ 'ᲁ' => 'Д',
553
+ 'ᲂ' => 'О',
554
+ 'ᲃ' => 'С',
555
+ 'ᲄ' => 'Т',
556
+ 'ᲅ' => 'Т',
557
+ 'ᲆ' => 'Ъ',
558
+ 'ᲇ' => 'Ѣ',
559
+ 'ᲈ' => 'Ꙋ',
560
+ 'ᵹ' => 'Ᵹ',
561
+ 'ᵽ' => 'Ᵽ',
562
+ 'ᶎ' => 'Ᶎ',
563
+ 'ḁ' => 'Ḁ',
564
+ 'ḃ' => 'Ḃ',
565
+ 'ḅ' => 'Ḅ',
566
+ 'ḇ' => 'Ḇ',
567
+ 'ḉ' => 'Ḉ',
568
+ 'ḋ' => 'Ḋ',
569
+ 'ḍ' => 'Ḍ',
570
+ 'ḏ' => 'Ḏ',
571
+ 'ḑ' => 'Ḑ',
572
+ 'ḓ' => 'Ḓ',
573
+ 'ḕ' => 'Ḕ',
574
+ 'ḗ' => 'Ḗ',
575
+ 'ḙ' => 'Ḙ',
576
+ 'ḛ' => 'Ḛ',
577
+ 'ḝ' => 'Ḝ',
578
+ 'ḟ' => 'Ḟ',
579
+ 'ḡ' => 'Ḡ',
580
+ 'ḣ' => 'Ḣ',
581
+ 'ḥ' => 'Ḥ',
582
+ 'ḧ' => 'Ḧ',
583
+ 'ḩ' => 'Ḩ',
584
+ 'ḫ' => 'Ḫ',
585
+ 'ḭ' => 'Ḭ',
586
+ 'ḯ' => 'Ḯ',
587
+ 'ḱ' => 'Ḱ',
588
+ 'ḳ' => 'Ḳ',
589
+ 'ḵ' => 'Ḵ',
590
+ 'ḷ' => 'Ḷ',
591
+ 'ḹ' => 'Ḹ',
592
+ 'ḻ' => 'Ḻ',
593
+ 'ḽ' => 'Ḽ',
594
+ 'ḿ' => 'Ḿ',
595
+ 'ṁ' => 'Ṁ',
596
+ 'ṃ' => 'Ṃ',
597
+ 'ṅ' => 'Ṅ',
598
+ 'ṇ' => 'Ṇ',
599
+ 'ṉ' => 'Ṉ',
600
+ 'ṋ' => 'Ṋ',
601
+ 'ṍ' => 'Ṍ',
602
+ 'ṏ' => 'Ṏ',
603
+ 'ṑ' => 'Ṑ',
604
+ 'ṓ' => 'Ṓ',
605
+ 'ṕ' => 'Ṕ',
606
+ 'ṗ' => 'Ṗ',
607
+ 'ṙ' => 'Ṙ',
608
+ 'ṛ' => 'Ṛ',
609
+ 'ṝ' => 'Ṝ',
610
+ 'ṟ' => 'Ṟ',
611
+ 'ṡ' => 'Ṡ',
612
+ 'ṣ' => 'Ṣ',
613
+ 'ṥ' => 'Ṥ',
614
+ 'ṧ' => 'Ṧ',
615
+ 'ṩ' => 'Ṩ',
616
+ 'ṫ' => 'Ṫ',
617
+ 'ṭ' => 'Ṭ',
618
+ 'ṯ' => 'Ṯ',
619
+ 'ṱ' => 'Ṱ',
620
+ 'ṳ' => 'Ṳ',
621
+ 'ṵ' => 'Ṵ',
622
+ 'ṷ' => 'Ṷ',
623
+ 'ṹ' => 'Ṹ',
624
+ 'ṻ' => 'Ṻ',
625
+ 'ṽ' => 'Ṽ',
626
+ 'ṿ' => 'Ṿ',
627
+ 'ẁ' => 'Ẁ',
628
+ 'ẃ' => 'Ẃ',
629
+ 'ẅ' => 'Ẅ',
630
+ 'ẇ' => 'Ẇ',
631
+ 'ẉ' => 'Ẉ',
632
+ 'ẋ' => 'Ẋ',
633
+ 'ẍ' => 'Ẍ',
634
+ 'ẏ' => 'Ẏ',
635
+ 'ẑ' => 'Ẑ',
636
+ 'ẓ' => 'Ẓ',
637
+ 'ẕ' => 'Ẕ',
638
+ 'ẛ' => 'Ṡ',
639
+ 'ạ' => 'Ạ',
640
+ 'ả' => 'Ả',
641
+ 'ấ' => 'Ấ',
642
+ 'ầ' => 'Ầ',
643
+ 'ẩ' => 'Ẩ',
644
+ 'ẫ' => 'Ẫ',
645
+ 'ậ' => 'Ậ',
646
+ 'ắ' => 'Ắ',
647
+ 'ằ' => 'Ằ',
648
+ 'ẳ' => 'Ẳ',
649
+ 'ẵ' => 'Ẵ',
650
+ 'ặ' => 'Ặ',
651
+ 'ẹ' => 'Ẹ',
652
+ 'ẻ' => 'Ẻ',
653
+ 'ẽ' => 'Ẽ',
654
+ 'ế' => 'Ế',
655
+ 'ề' => 'Ề',
656
+ 'ể' => 'Ể',
657
+ 'ễ' => 'Ễ',
658
+ 'ệ' => 'Ệ',
659
+ 'ỉ' => 'Ỉ',
660
+ 'ị' => 'Ị',
661
+ 'ọ' => 'Ọ',
662
+ 'ỏ' => 'Ỏ',
663
+ 'ố' => 'Ố',
664
+ 'ồ' => 'Ồ',
665
+ 'ổ' => 'Ổ',
666
+ 'ỗ' => 'Ỗ',
667
+ 'ộ' => 'Ộ',
668
+ 'ớ' => 'Ớ',
669
+ 'ờ' => 'Ờ',
670
+ 'ở' => 'Ở',
671
+ 'ỡ' => 'Ỡ',
672
+ 'ợ' => 'Ợ',
673
+ 'ụ' => 'Ụ',
674
+ 'ủ' => 'Ủ',
675
+ 'ứ' => 'Ứ',
676
+ 'ừ' => 'Ừ',
677
+ 'ử' => 'Ử',
678
+ 'ữ' => 'Ữ',
679
+ 'ự' => 'Ự',
680
+ 'ỳ' => 'Ỳ',
681
+ 'ỵ' => 'Ỵ',
682
+ 'ỷ' => 'Ỷ',
683
+ 'ỹ' => 'Ỹ',
684
+ 'ỻ' => 'Ỻ',
685
+ 'ỽ' => 'Ỽ',
686
+ 'ỿ' => 'Ỿ',
687
+ 'ἀ' => 'Ἀ',
688
+ 'ἁ' => 'Ἁ',
689
+ 'ἂ' => 'Ἂ',
690
+ 'ἃ' => 'Ἃ',
691
+ 'ἄ' => 'Ἄ',
692
+ 'ἅ' => 'Ἅ',
693
+ 'ἆ' => 'Ἆ',
694
+ 'ἇ' => 'Ἇ',
695
+ 'ἐ' => 'Ἐ',
696
+ 'ἑ' => 'Ἑ',
697
+ 'ἒ' => 'Ἒ',
698
+ 'ἓ' => 'Ἓ',
699
+ 'ἔ' => 'Ἔ',
700
+ 'ἕ' => 'Ἕ',
701
+ 'ἠ' => 'Ἠ',
702
+ 'ἡ' => 'Ἡ',
703
+ 'ἢ' => 'Ἢ',
704
+ 'ἣ' => 'Ἣ',
705
+ 'ἤ' => 'Ἤ',
706
+ 'ἥ' => 'Ἥ',
707
+ 'ἦ' => 'Ἦ',
708
+ 'ἧ' => 'Ἧ',
709
+ 'ἰ' => 'Ἰ',
710
+ 'ἱ' => 'Ἱ',
711
+ 'ἲ' => 'Ἲ',
712
+ 'ἳ' => 'Ἳ',
713
+ 'ἴ' => 'Ἴ',
714
+ 'ἵ' => 'Ἵ',
715
+ 'ἶ' => 'Ἶ',
716
+ 'ἷ' => 'Ἷ',
717
+ 'ὀ' => 'Ὀ',
718
+ 'ὁ' => 'Ὁ',
719
+ 'ὂ' => 'Ὂ',
720
+ 'ὃ' => 'Ὃ',
721
+ 'ὄ' => 'Ὄ',
722
+ 'ὅ' => 'Ὅ',
723
+ 'ὑ' => 'Ὑ',
724
+ 'ὓ' => 'Ὓ',
725
+ 'ὕ' => 'Ὕ',
726
+ 'ὗ' => 'Ὗ',
727
+ 'ὠ' => 'Ὠ',
728
+ 'ὡ' => 'Ὡ',
729
+ 'ὢ' => 'Ὢ',
730
+ 'ὣ' => 'Ὣ',
731
+ 'ὤ' => 'Ὤ',
732
+ 'ὥ' => 'Ὥ',
733
+ 'ὦ' => 'Ὦ',
734
+ 'ὧ' => 'Ὧ',
735
+ 'ὰ' => 'Ὰ',
736
+ 'ά' => 'Ά',
737
+ 'ὲ' => 'Ὲ',
738
+ 'έ' => 'Έ',
739
+ 'ὴ' => 'Ὴ',
740
+ 'ή' => 'Ή',
741
+ 'ὶ' => 'Ὶ',
742
+ 'ί' => 'Ί',
743
+ 'ὸ' => 'Ὸ',
744
+ 'ό' => 'Ό',
745
+ 'ὺ' => 'Ὺ',
746
+ 'ύ' => 'Ύ',
747
+ 'ὼ' => 'Ὼ',
748
+ 'ώ' => 'Ώ',
749
+ 'ᾀ' => 'ἈΙ',
750
+ 'ᾁ' => 'ἉΙ',
751
+ 'ᾂ' => 'ἊΙ',
752
+ 'ᾃ' => 'ἋΙ',
753
+ 'ᾄ' => 'ἌΙ',
754
+ 'ᾅ' => 'ἍΙ',
755
+ 'ᾆ' => 'ἎΙ',
756
+ 'ᾇ' => 'ἏΙ',
757
+ 'ᾐ' => 'ἨΙ',
758
+ 'ᾑ' => 'ἩΙ',
759
+ 'ᾒ' => 'ἪΙ',
760
+ 'ᾓ' => 'ἫΙ',
761
+ 'ᾔ' => 'ἬΙ',
762
+ 'ᾕ' => 'ἭΙ',
763
+ 'ᾖ' => 'ἮΙ',
764
+ 'ᾗ' => 'ἯΙ',
765
+ 'ᾠ' => 'ὨΙ',
766
+ 'ᾡ' => 'ὩΙ',
767
+ 'ᾢ' => 'ὪΙ',
768
+ 'ᾣ' => 'ὫΙ',
769
+ 'ᾤ' => 'ὬΙ',
770
+ 'ᾥ' => 'ὭΙ',
771
+ 'ᾦ' => 'ὮΙ',
772
+ 'ᾧ' => 'ὯΙ',
773
+ 'ᾰ' => 'Ᾰ',
774
+ 'ᾱ' => 'Ᾱ',
775
+ 'ᾳ' => 'ΑΙ',
776
+ 'ι' => 'Ι',
777
+ 'ῃ' => 'ΗΙ',
778
+ 'ῐ' => 'Ῐ',
779
+ 'ῑ' => 'Ῑ',
780
+ 'ῠ' => 'Ῠ',
781
+ 'ῡ' => 'Ῡ',
782
+ 'ῥ' => 'Ῥ',
783
+ 'ῳ' => 'ΩΙ',
784
+ 'ⅎ' => 'Ⅎ',
785
+ 'ⅰ' => 'Ⅰ',
786
+ 'ⅱ' => 'Ⅱ',
787
+ 'ⅲ' => 'Ⅲ',
788
+ 'ⅳ' => 'Ⅳ',
789
+ 'ⅴ' => 'Ⅴ',
790
+ 'ⅵ' => 'Ⅵ',
791
+ 'ⅶ' => 'Ⅶ',
792
+ 'ⅷ' => 'Ⅷ',
793
+ 'ⅸ' => 'Ⅸ',
794
+ 'ⅹ' => 'Ⅹ',
795
+ 'ⅺ' => 'Ⅺ',
796
+ 'ⅻ' => 'Ⅻ',
797
+ 'ⅼ' => 'Ⅼ',
798
+ 'ⅽ' => 'Ⅽ',
799
+ 'ⅾ' => 'Ⅾ',
800
+ 'ⅿ' => 'Ⅿ',
801
+ 'ↄ' => 'Ↄ',
802
+ 'ⓐ' => 'Ⓐ',
803
+ 'ⓑ' => 'Ⓑ',
804
+ 'ⓒ' => 'Ⓒ',
805
+ 'ⓓ' => 'Ⓓ',
806
+ 'ⓔ' => 'Ⓔ',
807
+ 'ⓕ' => 'Ⓕ',
808
+ 'ⓖ' => 'Ⓖ',
809
+ 'ⓗ' => 'Ⓗ',
810
+ 'ⓘ' => 'Ⓘ',
811
+ 'ⓙ' => 'Ⓙ',
812
+ 'ⓚ' => 'Ⓚ',
813
+ 'ⓛ' => 'Ⓛ',
814
+ 'ⓜ' => 'Ⓜ',
815
+ 'ⓝ' => 'Ⓝ',
816
+ 'ⓞ' => 'Ⓞ',
817
+ 'ⓟ' => 'Ⓟ',
818
+ 'ⓠ' => 'Ⓠ',
819
+ 'ⓡ' => 'Ⓡ',
820
+ 'ⓢ' => 'Ⓢ',
821
+ 'ⓣ' => 'Ⓣ',
822
+ 'ⓤ' => 'Ⓤ',
823
+ 'ⓥ' => 'Ⓥ',
824
+ 'ⓦ' => 'Ⓦ',
825
+ 'ⓧ' => 'Ⓧ',
826
+ 'ⓨ' => 'Ⓨ',
827
+ 'ⓩ' => 'Ⓩ',
828
+ 'ⰰ' => 'Ⰰ',
829
+ 'ⰱ' => 'Ⰱ',
830
+ 'ⰲ' => 'Ⰲ',
831
+ 'ⰳ' => 'Ⰳ',
832
+ 'ⰴ' => 'Ⰴ',
833
+ 'ⰵ' => 'Ⰵ',
834
+ 'ⰶ' => 'Ⰶ',
835
+ 'ⰷ' => 'Ⰷ',
836
+ 'ⰸ' => 'Ⰸ',
837
+ 'ⰹ' => 'Ⰹ',
838
+ 'ⰺ' => 'Ⰺ',
839
+ 'ⰻ' => 'Ⰻ',
840
+ 'ⰼ' => 'Ⰼ',
841
+ 'ⰽ' => 'Ⰽ',
842
+ 'ⰾ' => 'Ⰾ',
843
+ 'ⰿ' => 'Ⰿ',
844
+ 'ⱀ' => 'Ⱀ',
845
+ 'ⱁ' => 'Ⱁ',
846
+ 'ⱂ' => 'Ⱂ',
847
+ 'ⱃ' => 'Ⱃ',
848
+ 'ⱄ' => 'Ⱄ',
849
+ 'ⱅ' => 'Ⱅ',
850
+ 'ⱆ' => 'Ⱆ',
851
+ 'ⱇ' => 'Ⱇ',
852
+ 'ⱈ' => 'Ⱈ',
853
+ 'ⱉ' => 'Ⱉ',
854
+ 'ⱊ' => 'Ⱊ',
855
+ 'ⱋ' => 'Ⱋ',
856
+ 'ⱌ' => 'Ⱌ',
857
+ 'ⱍ' => 'Ⱍ',
858
+ 'ⱎ' => 'Ⱎ',
859
+ 'ⱏ' => 'Ⱏ',
860
+ 'ⱐ' => 'Ⱐ',
861
+ 'ⱑ' => 'Ⱑ',
862
+ 'ⱒ' => 'Ⱒ',
863
+ 'ⱓ' => 'Ⱓ',
864
+ 'ⱔ' => 'Ⱔ',
865
+ 'ⱕ' => 'Ⱕ',
866
+ 'ⱖ' => 'Ⱖ',
867
+ 'ⱗ' => 'Ⱗ',
868
+ 'ⱘ' => 'Ⱘ',
869
+ 'ⱙ' => 'Ⱙ',
870
+ 'ⱚ' => 'Ⱚ',
871
+ 'ⱛ' => 'Ⱛ',
872
+ 'ⱜ' => 'Ⱜ',
873
+ 'ⱝ' => 'Ⱝ',
874
+ 'ⱞ' => 'Ⱞ',
875
+ 'ⱡ' => 'Ⱡ',
876
+ 'ⱥ' => 'Ⱥ',
877
+ 'ⱦ' => 'Ⱦ',
878
+ 'ⱨ' => 'Ⱨ',
879
+ 'ⱪ' => 'Ⱪ',
880
+ 'ⱬ' => 'Ⱬ',
881
+ 'ⱳ' => 'Ⱳ',
882
+ 'ⱶ' => 'Ⱶ',
883
+ 'ⲁ' => 'Ⲁ',
884
+ 'ⲃ' => 'Ⲃ',
885
+ 'ⲅ' => 'Ⲅ',
886
+ 'ⲇ' => 'Ⲇ',
887
+ 'ⲉ' => 'Ⲉ',
888
+ 'ⲋ' => 'Ⲋ',
889
+ 'ⲍ' => 'Ⲍ',
890
+ 'ⲏ' => 'Ⲏ',
891
+ 'ⲑ' => 'Ⲑ',
892
+ 'ⲓ' => 'Ⲓ',
893
+ 'ⲕ' => 'Ⲕ',
894
+ 'ⲗ' => 'Ⲗ',
895
+ 'ⲙ' => 'Ⲙ',
896
+ 'ⲛ' => 'Ⲛ',
897
+ 'ⲝ' => 'Ⲝ',
898
+ 'ⲟ' => 'Ⲟ',
899
+ 'ⲡ' => 'Ⲡ',
900
+ 'ⲣ' => 'Ⲣ',
901
+ 'ⲥ' => 'Ⲥ',
902
+ 'ⲧ' => 'Ⲧ',
903
+ 'ⲩ' => 'Ⲩ',
904
+ 'ⲫ' => 'Ⲫ',
905
+ 'ⲭ' => 'Ⲭ',
906
+ 'ⲯ' => 'Ⲯ',
907
+ 'ⲱ' => 'Ⲱ',
908
+ 'ⲳ' => 'Ⲳ',
909
+ 'ⲵ' => 'Ⲵ',
910
+ 'ⲷ' => 'Ⲷ',
911
+ 'ⲹ' => 'Ⲹ',
912
+ 'ⲻ' => 'Ⲻ',
913
+ 'ⲽ' => 'Ⲽ',
914
+ 'ⲿ' => 'Ⲿ',
915
+ 'ⳁ' => 'Ⳁ',
916
+ 'ⳃ' => 'Ⳃ',
917
+ 'ⳅ' => 'Ⳅ',
918
+ 'ⳇ' => 'Ⳇ',
919
+ 'ⳉ' => 'Ⳉ',
920
+ 'ⳋ' => 'Ⳋ',
921
+ 'ⳍ' => 'Ⳍ',
922
+ 'ⳏ' => 'Ⳏ',
923
+ 'ⳑ' => 'Ⳑ',
924
+ 'ⳓ' => 'Ⳓ',
925
+ 'ⳕ' => 'Ⳕ',
926
+ 'ⳗ' => 'Ⳗ',
927
+ 'ⳙ' => 'Ⳙ',
928
+ 'ⳛ' => 'Ⳛ',
929
+ 'ⳝ' => 'Ⳝ',
930
+ 'ⳟ' => 'Ⳟ',
931
+ 'ⳡ' => 'Ⳡ',
932
+ 'ⳣ' => 'Ⳣ',
933
+ 'ⳬ' => 'Ⳬ',
934
+ 'ⳮ' => 'Ⳮ',
935
+ 'ⳳ' => 'Ⳳ',
936
+ 'ⴀ' => 'Ⴀ',
937
+ 'ⴁ' => 'Ⴁ',
938
+ 'ⴂ' => 'Ⴂ',
939
+ 'ⴃ' => 'Ⴃ',
940
+ 'ⴄ' => 'Ⴄ',
941
+ 'ⴅ' => 'Ⴅ',
942
+ 'ⴆ' => 'Ⴆ',
943
+ 'ⴇ' => 'Ⴇ',
944
+ 'ⴈ' => 'Ⴈ',
945
+ 'ⴉ' => 'Ⴉ',
946
+ 'ⴊ' => 'Ⴊ',
947
+ 'ⴋ' => 'Ⴋ',
948
+ 'ⴌ' => 'Ⴌ',
949
+ 'ⴍ' => 'Ⴍ',
950
+ 'ⴎ' => 'Ⴎ',
951
+ 'ⴏ' => 'Ⴏ',
952
+ 'ⴐ' => 'Ⴐ',
953
+ 'ⴑ' => 'Ⴑ',
954
+ 'ⴒ' => 'Ⴒ',
955
+ 'ⴓ' => 'Ⴓ',
956
+ 'ⴔ' => 'Ⴔ',
957
+ 'ⴕ' => 'Ⴕ',
958
+ 'ⴖ' => 'Ⴖ',
959
+ 'ⴗ' => 'Ⴗ',
960
+ 'ⴘ' => 'Ⴘ',
961
+ 'ⴙ' => 'Ⴙ',
962
+ 'ⴚ' => 'Ⴚ',
963
+ 'ⴛ' => 'Ⴛ',
964
+ 'ⴜ' => 'Ⴜ',
965
+ 'ⴝ' => 'Ⴝ',
966
+ 'ⴞ' => 'Ⴞ',
967
+ 'ⴟ' => 'Ⴟ',
968
+ 'ⴠ' => 'Ⴠ',
969
+ 'ⴡ' => 'Ⴡ',
970
+ 'ⴢ' => 'Ⴢ',
971
+ 'ⴣ' => 'Ⴣ',
972
+ 'ⴤ' => 'Ⴤ',
973
+ 'ⴥ' => 'Ⴥ',
974
+ 'ⴧ' => 'Ⴧ',
975
+ 'ⴭ' => 'Ⴭ',
976
+ 'ꙁ' => 'Ꙁ',
977
+ 'ꙃ' => 'Ꙃ',
978
+ 'ꙅ' => 'Ꙅ',
979
+ 'ꙇ' => 'Ꙇ',
980
+ 'ꙉ' => 'Ꙉ',
981
+ 'ꙋ' => 'Ꙋ',
982
+ 'ꙍ' => 'Ꙍ',
983
+ 'ꙏ' => 'Ꙏ',
984
+ 'ꙑ' => 'Ꙑ',
985
+ 'ꙓ' => 'Ꙓ',
986
+ 'ꙕ' => 'Ꙕ',
987
+ 'ꙗ' => 'Ꙗ',
988
+ 'ꙙ' => 'Ꙙ',
989
+ 'ꙛ' => 'Ꙛ',
990
+ 'ꙝ' => 'Ꙝ',
991
+ 'ꙟ' => 'Ꙟ',
992
+ 'ꙡ' => 'Ꙡ',
993
+ 'ꙣ' => 'Ꙣ',
994
+ 'ꙥ' => 'Ꙥ',
995
+ 'ꙧ' => 'Ꙧ',
996
+ 'ꙩ' => 'Ꙩ',
997
+ 'ꙫ' => 'Ꙫ',
998
+ 'ꙭ' => 'Ꙭ',
999
+ 'ꚁ' => 'Ꚁ',
1000
+ 'ꚃ' => 'Ꚃ',
1001
+ 'ꚅ' => 'Ꚅ',
1002
+ 'ꚇ' => 'Ꚇ',
1003
+ 'ꚉ' => 'Ꚉ',
1004
+ 'ꚋ' => 'Ꚋ',
1005
+ 'ꚍ' => 'Ꚍ',
1006
+ 'ꚏ' => 'Ꚏ',
1007
+ 'ꚑ' => 'Ꚑ',
1008
+ 'ꚓ' => 'Ꚓ',
1009
+ 'ꚕ' => 'Ꚕ',
1010
+ 'ꚗ' => 'Ꚗ',
1011
+ 'ꚙ' => 'Ꚙ',
1012
+ 'ꚛ' => 'Ꚛ',
1013
+ 'ꜣ' => 'Ꜣ',
1014
+ 'ꜥ' => 'Ꜥ',
1015
+ 'ꜧ' => 'Ꜧ',
1016
+ 'ꜩ' => 'Ꜩ',
1017
+ 'ꜫ' => 'Ꜫ',
1018
+ 'ꜭ' => 'Ꜭ',
1019
+ 'ꜯ' => 'Ꜯ',
1020
+ 'ꜳ' => 'Ꜳ',
1021
+ 'ꜵ' => 'Ꜵ',
1022
+ 'ꜷ' => 'Ꜷ',
1023
+ 'ꜹ' => 'Ꜹ',
1024
+ 'ꜻ' => 'Ꜻ',
1025
+ 'ꜽ' => 'Ꜽ',
1026
+ 'ꜿ' => 'Ꜿ',
1027
+ 'ꝁ' => 'Ꝁ',
1028
+ 'ꝃ' => 'Ꝃ',
1029
+ 'ꝅ' => 'Ꝅ',
1030
+ 'ꝇ' => 'Ꝇ',
1031
+ 'ꝉ' => 'Ꝉ',
1032
+ 'ꝋ' => 'Ꝋ',
1033
+ 'ꝍ' => 'Ꝍ',
1034
+ 'ꝏ' => 'Ꝏ',
1035
+ 'ꝑ' => 'Ꝑ',
1036
+ 'ꝓ' => 'Ꝓ',
1037
+ 'ꝕ' => 'Ꝕ',
1038
+ 'ꝗ' => 'Ꝗ',
1039
+ 'ꝙ' => 'Ꝙ',
1040
+ 'ꝛ' => 'Ꝛ',
1041
+ 'ꝝ' => 'Ꝝ',
1042
+ 'ꝟ' => 'Ꝟ',
1043
+ 'ꝡ' => 'Ꝡ',
1044
+ 'ꝣ' => 'Ꝣ',
1045
+ 'ꝥ' => 'Ꝥ',
1046
+ 'ꝧ' => 'Ꝧ',
1047
+ 'ꝩ' => 'Ꝩ',
1048
+ 'ꝫ' => 'Ꝫ',
1049
+ 'ꝭ' => 'Ꝭ',
1050
+ 'ꝯ' => 'Ꝯ',
1051
+ 'ꝺ' => 'Ꝺ',
1052
+ 'ꝼ' => 'Ꝼ',
1053
+ 'ꝿ' => 'Ꝿ',
1054
+ 'ꞁ' => 'Ꞁ',
1055
+ 'ꞃ' => 'Ꞃ',
1056
+ 'ꞅ' => 'Ꞅ',
1057
+ 'ꞇ' => 'Ꞇ',
1058
+ 'ꞌ' => 'Ꞌ',
1059
+ 'ꞑ' => 'Ꞑ',
1060
+ 'ꞓ' => 'Ꞓ',
1061
+ 'ꞔ' => 'Ꞔ',
1062
+ 'ꞗ' => 'Ꞗ',
1063
+ 'ꞙ' => 'Ꞙ',
1064
+ 'ꞛ' => 'Ꞛ',
1065
+ 'ꞝ' => 'Ꞝ',
1066
+ 'ꞟ' => 'Ꞟ',
1067
+ 'ꞡ' => 'Ꞡ',
1068
+ 'ꞣ' => 'Ꞣ',
1069
+ 'ꞥ' => 'Ꞥ',
1070
+ 'ꞧ' => 'Ꞧ',
1071
+ 'ꞩ' => 'Ꞩ',
1072
+ 'ꞵ' => 'Ꞵ',
1073
+ 'ꞷ' => 'Ꞷ',
1074
+ 'ꞹ' => 'Ꞹ',
1075
+ 'ꞻ' => 'Ꞻ',
1076
+ 'ꞽ' => 'Ꞽ',
1077
+ 'ꞿ' => 'Ꞿ',
1078
+ 'ꟃ' => 'Ꟃ',
1079
+ 'ꟈ' => 'Ꟈ',
1080
+ 'ꟊ' => 'Ꟊ',
1081
+ 'ꟶ' => 'Ꟶ',
1082
+ 'ꭓ' => 'Ꭓ',
1083
+ 'ꭰ' => 'Ꭰ',
1084
+ 'ꭱ' => 'Ꭱ',
1085
+ 'ꭲ' => 'Ꭲ',
1086
+ 'ꭳ' => 'Ꭳ',
1087
+ 'ꭴ' => 'Ꭴ',
1088
+ 'ꭵ' => 'Ꭵ',
1089
+ 'ꭶ' => 'Ꭶ',
1090
+ 'ꭷ' => 'Ꭷ',
1091
+ 'ꭸ' => 'Ꭸ',
1092
+ 'ꭹ' => 'Ꭹ',
1093
+ 'ꭺ' => 'Ꭺ',
1094
+ 'ꭻ' => 'Ꭻ',
1095
+ 'ꭼ' => 'Ꭼ',
1096
+ 'ꭽ' => 'Ꭽ',
1097
+ 'ꭾ' => 'Ꭾ',
1098
+ 'ꭿ' => 'Ꭿ',
1099
+ 'ꮀ' => 'Ꮀ',
1100
+ 'ꮁ' => 'Ꮁ',
1101
+ 'ꮂ' => 'Ꮂ',
1102
+ 'ꮃ' => 'Ꮃ',
1103
+ 'ꮄ' => 'Ꮄ',
1104
+ 'ꮅ' => 'Ꮅ',
1105
+ 'ꮆ' => 'Ꮆ',
1106
+ 'ꮇ' => 'Ꮇ',
1107
+ 'ꮈ' => 'Ꮈ',
1108
+ 'ꮉ' => 'Ꮉ',
1109
+ 'ꮊ' => 'Ꮊ',
1110
+ 'ꮋ' => 'Ꮋ',
1111
+ 'ꮌ' => 'Ꮌ',
1112
+ 'ꮍ' => 'Ꮍ',
1113
+ 'ꮎ' => 'Ꮎ',
1114
+ 'ꮏ' => 'Ꮏ',
1115
+ 'ꮐ' => 'Ꮐ',
1116
+ 'ꮑ' => 'Ꮑ',
1117
+ 'ꮒ' => 'Ꮒ',
1118
+ 'ꮓ' => 'Ꮓ',
1119
+ 'ꮔ' => 'Ꮔ',
1120
+ 'ꮕ' => 'Ꮕ',
1121
+ 'ꮖ' => 'Ꮖ',
1122
+ 'ꮗ' => 'Ꮗ',
1123
+ 'ꮘ' => 'Ꮘ',
1124
+ 'ꮙ' => 'Ꮙ',
1125
+ 'ꮚ' => 'Ꮚ',
1126
+ 'ꮛ' => 'Ꮛ',
1127
+ 'ꮜ' => 'Ꮜ',
1128
+ 'ꮝ' => 'Ꮝ',
1129
+ 'ꮞ' => 'Ꮞ',
1130
+ 'ꮟ' => 'Ꮟ',
1131
+ 'ꮠ' => 'Ꮠ',
1132
+ 'ꮡ' => 'Ꮡ',
1133
+ 'ꮢ' => 'Ꮢ',
1134
+ 'ꮣ' => 'Ꮣ',
1135
+ 'ꮤ' => 'Ꮤ',
1136
+ 'ꮥ' => 'Ꮥ',
1137
+ 'ꮦ' => 'Ꮦ',
1138
+ 'ꮧ' => 'Ꮧ',
1139
+ 'ꮨ' => 'Ꮨ',
1140
+ 'ꮩ' => 'Ꮩ',
1141
+ 'ꮪ' => 'Ꮪ',
1142
+ 'ꮫ' => 'Ꮫ',
1143
+ 'ꮬ' => 'Ꮬ',
1144
+ 'ꮭ' => 'Ꮭ',
1145
+ 'ꮮ' => 'Ꮮ',
1146
+ 'ꮯ' => 'Ꮯ',
1147
+ 'ꮰ' => 'Ꮰ',
1148
+ 'ꮱ' => 'Ꮱ',
1149
+ 'ꮲ' => 'Ꮲ',
1150
+ 'ꮳ' => 'Ꮳ',
1151
+ 'ꮴ' => 'Ꮴ',
1152
+ 'ꮵ' => 'Ꮵ',
1153
+ 'ꮶ' => 'Ꮶ',
1154
+ 'ꮷ' => 'Ꮷ',
1155
+ 'ꮸ' => 'Ꮸ',
1156
+ 'ꮹ' => 'Ꮹ',
1157
+ 'ꮺ' => 'Ꮺ',
1158
+ 'ꮻ' => 'Ꮻ',
1159
+ 'ꮼ' => 'Ꮼ',
1160
+ 'ꮽ' => 'Ꮽ',
1161
+ 'ꮾ' => 'Ꮾ',
1162
+ 'ꮿ' => 'Ꮿ',
1163
+ 'a' => 'A',
1164
+ 'b' => 'B',
1165
+ 'c' => 'C',
1166
+ 'd' => 'D',
1167
+ 'e' => 'E',
1168
+ 'f' => 'F',
1169
+ 'g' => 'G',
1170
+ 'h' => 'H',
1171
+ 'i' => 'I',
1172
+ 'j' => 'J',
1173
+ 'k' => 'K',
1174
+ 'l' => 'L',
1175
+ 'm' => 'M',
1176
+ 'n' => 'N',
1177
+ 'o' => 'O',
1178
+ 'p' => 'P',
1179
+ 'q' => 'Q',
1180
+ 'r' => 'R',
1181
+ 's' => 'S',
1182
+ 't' => 'T',
1183
+ 'u' => 'U',
1184
+ 'v' => 'V',
1185
+ 'w' => 'W',
1186
+ 'x' => 'X',
1187
+ 'y' => 'Y',
1188
+ 'z' => 'Z',
1189
+ '𐐨' => '𐐀',
1190
+ '𐐩' => '𐐁',
1191
+ '𐐪' => '𐐂',
1192
+ '𐐫' => '𐐃',
1193
+ '𐐬' => '𐐄',
1194
+ '𐐭' => '𐐅',
1195
+ '𐐮' => '𐐆',
1196
+ '𐐯' => '𐐇',
1197
+ '𐐰' => '𐐈',
1198
+ '𐐱' => '𐐉',
1199
+ '𐐲' => '𐐊',
1200
+ '𐐳' => '𐐋',
1201
+ '𐐴' => '𐐌',
1202
+ '𐐵' => '𐐍',
1203
+ '𐐶' => '𐐎',
1204
+ '𐐷' => '𐐏',
1205
+ '𐐸' => '𐐐',
1206
+ '𐐹' => '𐐑',
1207
+ '𐐺' => '𐐒',
1208
+ '𐐻' => '𐐓',
1209
+ '𐐼' => '𐐔',
1210
+ '𐐽' => '𐐕',
1211
+ '𐐾' => '𐐖',
1212
+ '𐐿' => '𐐗',
1213
+ '𐑀' => '𐐘',
1214
+ '𐑁' => '𐐙',
1215
+ '𐑂' => '𐐚',
1216
+ '𐑃' => '𐐛',
1217
+ '𐑄' => '𐐜',
1218
+ '𐑅' => '𐐝',
1219
+ '𐑆' => '𐐞',
1220
+ '𐑇' => '𐐟',
1221
+ '𐑈' => '𐐠',
1222
+ '𐑉' => '𐐡',
1223
+ '𐑊' => '𐐢',
1224
+ '𐑋' => '𐐣',
1225
+ '𐑌' => '𐐤',
1226
+ '𐑍' => '𐐥',
1227
+ '𐑎' => '𐐦',
1228
+ '𐑏' => '𐐧',
1229
+ '𐓘' => '𐒰',
1230
+ '𐓙' => '𐒱',
1231
+ '𐓚' => '𐒲',
1232
+ '𐓛' => '𐒳',
1233
+ '𐓜' => '𐒴',
1234
+ '𐓝' => '𐒵',
1235
+ '𐓞' => '𐒶',
1236
+ '𐓟' => '𐒷',
1237
+ '𐓠' => '𐒸',
1238
+ '𐓡' => '𐒹',
1239
+ '𐓢' => '𐒺',
1240
+ '𐓣' => '𐒻',
1241
+ '𐓤' => '𐒼',
1242
+ '𐓥' => '𐒽',
1243
+ '𐓦' => '𐒾',
1244
+ '𐓧' => '𐒿',
1245
+ '𐓨' => '𐓀',
1246
+ '𐓩' => '𐓁',
1247
+ '𐓪' => '𐓂',
1248
+ '𐓫' => '𐓃',
1249
+ '𐓬' => '𐓄',
1250
+ '𐓭' => '𐓅',
1251
+ '𐓮' => '𐓆',
1252
+ '𐓯' => '𐓇',
1253
+ '𐓰' => '𐓈',
1254
+ '𐓱' => '𐓉',
1255
+ '𐓲' => '𐓊',
1256
+ '𐓳' => '𐓋',
1257
+ '𐓴' => '𐓌',
1258
+ '𐓵' => '𐓍',
1259
+ '𐓶' => '𐓎',
1260
+ '𐓷' => '𐓏',
1261
+ '𐓸' => '𐓐',
1262
+ '𐓹' => '𐓑',
1263
+ '𐓺' => '𐓒',
1264
+ '𐓻' => '𐓓',
1265
+ '𐳀' => '𐲀',
1266
+ '𐳁' => '𐲁',
1267
+ '𐳂' => '𐲂',
1268
+ '𐳃' => '𐲃',
1269
+ '𐳄' => '𐲄',
1270
+ '𐳅' => '𐲅',
1271
+ '𐳆' => '𐲆',
1272
+ '𐳇' => '𐲇',
1273
+ '𐳈' => '𐲈',
1274
+ '𐳉' => '𐲉',
1275
+ '𐳊' => '𐲊',
1276
+ '𐳋' => '𐲋',
1277
+ '𐳌' => '𐲌',
1278
+ '𐳍' => '𐲍',
1279
+ '𐳎' => '𐲎',
1280
+ '𐳏' => '𐲏',
1281
+ '𐳐' => '𐲐',
1282
+ '𐳑' => '𐲑',
1283
+ '𐳒' => '𐲒',
1284
+ '𐳓' => '𐲓',
1285
+ '𐳔' => '𐲔',
1286
+ '𐳕' => '𐲕',
1287
+ '𐳖' => '𐲖',
1288
+ '𐳗' => '𐲗',
1289
+ '𐳘' => '𐲘',
1290
+ '𐳙' => '𐲙',
1291
+ '𐳚' => '𐲚',
1292
+ '𐳛' => '𐲛',
1293
+ '𐳜' => '𐲜',
1294
+ '𐳝' => '𐲝',
1295
+ '𐳞' => '𐲞',
1296
+ '𐳟' => '𐲟',
1297
+ '𐳠' => '𐲠',
1298
+ '𐳡' => '𐲡',
1299
+ '𐳢' => '𐲢',
1300
+ '𐳣' => '𐲣',
1301
+ '𐳤' => '𐲤',
1302
+ '𐳥' => '𐲥',
1303
+ '𐳦' => '𐲦',
1304
+ '𐳧' => '𐲧',
1305
+ '𐳨' => '𐲨',
1306
+ '𐳩' => '𐲩',
1307
+ '𐳪' => '𐲪',
1308
+ '𐳫' => '𐲫',
1309
+ '𐳬' => '𐲬',
1310
+ '𐳭' => '𐲭',
1311
+ '𐳮' => '𐲮',
1312
+ '𐳯' => '𐲯',
1313
+ '𐳰' => '𐲰',
1314
+ '𐳱' => '𐲱',
1315
+ '𐳲' => '𐲲',
1316
+ '𑣀' => '𑢠',
1317
+ '𑣁' => '𑢡',
1318
+ '𑣂' => '𑢢',
1319
+ '𑣃' => '𑢣',
1320
+ '𑣄' => '𑢤',
1321
+ '𑣅' => '𑢥',
1322
+ '𑣆' => '𑢦',
1323
+ '𑣇' => '𑢧',
1324
+ '𑣈' => '𑢨',
1325
+ '𑣉' => '𑢩',
1326
+ '𑣊' => '𑢪',
1327
+ '𑣋' => '𑢫',
1328
+ '𑣌' => '𑢬',
1329
+ '𑣍' => '𑢭',
1330
+ '𑣎' => '𑢮',
1331
+ '𑣏' => '𑢯',
1332
+ '𑣐' => '𑢰',
1333
+ '𑣑' => '𑢱',
1334
+ '𑣒' => '𑢲',
1335
+ '𑣓' => '𑢳',
1336
+ '𑣔' => '𑢴',
1337
+ '𑣕' => '𑢵',
1338
+ '𑣖' => '𑢶',
1339
+ '𑣗' => '𑢷',
1340
+ '𑣘' => '𑢸',
1341
+ '𑣙' => '𑢹',
1342
+ '𑣚' => '𑢺',
1343
+ '𑣛' => '𑢻',
1344
+ '𑣜' => '𑢼',
1345
+ '𑣝' => '𑢽',
1346
+ '𑣞' => '𑢾',
1347
+ '𑣟' => '𑢿',
1348
+ '𖹠' => '𖹀',
1349
+ '𖹡' => '𖹁',
1350
+ '𖹢' => '𖹂',
1351
+ '𖹣' => '𖹃',
1352
+ '𖹤' => '𖹄',
1353
+ '𖹥' => '𖹅',
1354
+ '𖹦' => '𖹆',
1355
+ '𖹧' => '𖹇',
1356
+ '𖹨' => '𖹈',
1357
+ '𖹩' => '𖹉',
1358
+ '𖹪' => '𖹊',
1359
+ '𖹫' => '𖹋',
1360
+ '𖹬' => '𖹌',
1361
+ '𖹭' => '𖹍',
1362
+ '𖹮' => '𖹎',
1363
+ '𖹯' => '𖹏',
1364
+ '𖹰' => '𖹐',
1365
+ '𖹱' => '𖹑',
1366
+ '𖹲' => '𖹒',
1367
+ '𖹳' => '𖹓',
1368
+ '𖹴' => '𖹔',
1369
+ '𖹵' => '𖹕',
1370
+ '𖹶' => '𖹖',
1371
+ '𖹷' => '𖹗',
1372
+ '𖹸' => '𖹘',
1373
+ '𖹹' => '𖹙',
1374
+ '𖹺' => '𖹚',
1375
+ '𖹻' => '𖹛',
1376
+ '𖹼' => '𖹜',
1377
+ '𖹽' => '𖹝',
1378
+ '𖹾' => '𖹞',
1379
+ '𖹿' => '𖹟',
1380
+ '𞤢' => '𞤀',
1381
+ '𞤣' => '𞤁',
1382
+ '𞤤' => '𞤂',
1383
+ '𞤥' => '𞤃',
1384
+ '𞤦' => '𞤄',
1385
+ '𞤧' => '𞤅',
1386
+ '𞤨' => '𞤆',
1387
+ '𞤩' => '𞤇',
1388
+ '𞤪' => '𞤈',
1389
+ '𞤫' => '𞤉',
1390
+ '𞤬' => '𞤊',
1391
+ '𞤭' => '𞤋',
1392
+ '𞤮' => '𞤌',
1393
+ '𞤯' => '𞤍',
1394
+ '𞤰' => '𞤎',
1395
+ '𞤱' => '𞤏',
1396
+ '𞤲' => '𞤐',
1397
+ '𞤳' => '𞤑',
1398
+ '𞤴' => '𞤒',
1399
+ '𞤵' => '𞤓',
1400
+ '𞤶' => '𞤔',
1401
+ '𞤷' => '𞤕',
1402
+ '𞤸' => '𞤖',
1403
+ '𞤹' => '𞤗',
1404
+ '𞤺' => '𞤘',
1405
+ '𞤻' => '𞤙',
1406
+ '𞤼' => '𞤚',
1407
+ '𞤽' => '𞤛',
1408
+ '𞤾' => '𞤜',
1409
+ '𞤿' => '𞤝',
1410
+ '𞥀' => '𞤞',
1411
+ '𞥁' => '𞤟',
1412
+ '𞥂' => '𞤠',
1413
+ '𞥃' => '𞤡',
1414
+ 'ß' => 'SS',
1415
+ 'ff' => 'FF',
1416
+ 'fi' => 'FI',
1417
+ 'fl' => 'FL',
1418
+ 'ffi' => 'FFI',
1419
+ 'ffl' => 'FFL',
1420
+ 'ſt' => 'ST',
1421
+ 'st' => 'ST',
1422
+ 'և' => 'ԵՒ',
1423
+ 'ﬓ' => 'ՄՆ',
1424
+ 'ﬔ' => 'ՄԵ',
1425
+ 'ﬕ' => 'ՄԻ',
1426
+ 'ﬖ' => 'ՎՆ',
1427
+ 'ﬗ' => 'ՄԽ',
1428
+ 'ʼn' => 'ʼN',
1429
+ 'ΐ' => 'Ϊ́',
1430
+ 'ΰ' => 'Ϋ́',
1431
+ 'ǰ' => 'J̌',
1432
+ 'ẖ' => 'H̱',
1433
+ 'ẗ' => 'T̈',
1434
+ 'ẘ' => 'W̊',
1435
+ 'ẙ' => 'Y̊',
1436
+ 'ẚ' => 'Aʾ',
1437
+ 'ὐ' => 'Υ̓',
1438
+ 'ὒ' => 'Υ̓̀',
1439
+ 'ὔ' => 'Υ̓́',
1440
+ 'ὖ' => 'Υ̓͂',
1441
+ 'ᾶ' => 'Α͂',
1442
+ 'ῆ' => 'Η͂',
1443
+ 'ῒ' => 'Ϊ̀',
1444
+ 'ΐ' => 'Ϊ́',
1445
+ 'ῖ' => 'Ι͂',
1446
+ 'ῗ' => 'Ϊ͂',
1447
+ 'ῢ' => 'Ϋ̀',
1448
+ 'ΰ' => 'Ϋ́',
1449
+ 'ῤ' => 'Ρ̓',
1450
+ 'ῦ' => 'Υ͂',
1451
+ 'ῧ' => 'Ϋ͂',
1452
+ 'ῶ' => 'Ω͂',
1453
+ 'ᾈ' => 'ἈΙ',
1454
+ 'ᾉ' => 'ἉΙ',
1455
+ 'ᾊ' => 'ἊΙ',
1456
+ 'ᾋ' => 'ἋΙ',
1457
+ 'ᾌ' => 'ἌΙ',
1458
+ 'ᾍ' => 'ἍΙ',
1459
+ 'ᾎ' => 'ἎΙ',
1460
+ 'ᾏ' => 'ἏΙ',
1461
+ 'ᾘ' => 'ἨΙ',
1462
+ 'ᾙ' => 'ἩΙ',
1463
+ 'ᾚ' => 'ἪΙ',
1464
+ 'ᾛ' => 'ἫΙ',
1465
+ 'ᾜ' => 'ἬΙ',
1466
+ 'ᾝ' => 'ἭΙ',
1467
+ 'ᾞ' => 'ἮΙ',
1468
+ 'ᾟ' => 'ἯΙ',
1469
+ 'ᾨ' => 'ὨΙ',
1470
+ 'ᾩ' => 'ὩΙ',
1471
+ 'ᾪ' => 'ὪΙ',
1472
+ 'ᾫ' => 'ὫΙ',
1473
+ 'ᾬ' => 'ὬΙ',
1474
+ 'ᾭ' => 'ὭΙ',
1475
+ 'ᾮ' => 'ὮΙ',
1476
+ 'ᾯ' => 'ὯΙ',
1477
+ 'ᾼ' => 'ΑΙ',
1478
+ 'ῌ' => 'ΗΙ',
1479
+ 'ῼ' => 'ΩΙ',
1480
+ 'ᾲ' => 'ᾺΙ',
1481
+ 'ᾴ' => 'ΆΙ',
1482
+ 'ῂ' => 'ῊΙ',
1483
+ 'ῄ' => 'ΉΙ',
1484
+ 'ῲ' => 'ῺΙ',
1485
+ 'ῴ' => 'ΏΙ',
1486
+ 'ᾷ' => 'Α͂Ι',
1487
+ 'ῇ' => 'Η͂Ι',
1488
+ 'ῷ' => 'Ω͂Ι',
1489
+ );
vendor/browscap-php/symfony/polyfill-mbstring/bootstrap.php ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of the Symfony package.
5
+ *
6
+ * (c) Fabien Potencier <fabien@symfony.com>
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ use Symfony\Polyfill\Mbstring as p;
13
+
14
+ if (!function_exists('mb_convert_encoding')) {
15
+ function mb_convert_encoding($string, $to_encoding, $from_encoding = null) { return p\Mbstring::mb_convert_encoding($string, $to_encoding, $from_encoding); }
16
+ }
17
+ if (!function_exists('mb_decode_mimeheader')) {
18
+ function mb_decode_mimeheader($string) { return p\Mbstring::mb_decode_mimeheader($string); }
19
+ }
20
+ if (!function_exists('mb_encode_mimeheader')) {
21
+ 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); }
22
+ }
23
+ if (!function_exists('mb_decode_numericentity')) {
24
+ function mb_decode_numericentity($string, $map, $encoding = null) { return p\Mbstring::mb_decode_numericentity($string, $map, $encoding); }
25
+ }
26
+ if (!function_exists('mb_encode_numericentity')) {
27
+ function mb_encode_numericentity($string, $map, $encoding = null, $hex = false) { return p\Mbstring::mb_encode_numericentity($string, $map, $encoding, $hex); }
28
+ }
29
+ if (!function_exists('mb_convert_case')) {
30
+ function mb_convert_case($string, $mode, $encoding = null) { return p\Mbstring::mb_convert_case($string, $mode, $encoding); }
31
+ }
32
+ if (!function_exists('mb_internal_encoding')) {
33
+ function mb_internal_encoding($encoding = null) { return p\Mbstring::mb_internal_encoding($encoding); }
34
+ }
35
+ if (!function_exists('mb_language')) {
36
+ function mb_language($language = null) { return p\Mbstring::mb_language($language); }
37
+ }
38
+ if (!function_exists('mb_list_encodings')) {
39
+ function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); }
40
+ }
41
+ if (!function_exists('mb_encoding_aliases')) {
42
+ function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); }
43
+ }
44
+ if (!function_exists('mb_check_encoding')) {
45
+ function mb_check_encoding($value = null, $encoding = null) { return p\Mbstring::mb_check_encoding($value, $encoding); }
46
+ }
47
+ if (!function_exists('mb_detect_encoding')) {
48
+ function mb_detect_encoding($string, $encodings = null, $strict = false) { return p\Mbstring::mb_detect_encoding($string, $encodings, $strict); }
49
+ }
50
+ if (!function_exists('mb_detect_order')) {
51
+ function mb_detect_order($encoding = null) { return p\Mbstring::mb_detect_order($encoding); }
52
+ }
53
+ if (!function_exists('mb_parse_str')) {
54
+ function mb_parse_str($string, &$result = []) { parse_str($string, $result); return (bool) $result; }
55
+ }
56
+ if (!function_exists('mb_strlen')) {
57
+ function mb_strlen($string, $encoding = null) { return p\Mbstring::mb_strlen($string, $encoding); }
58
+ }
59
+ if (!function_exists('mb_strpos')) {
60
+ function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strpos($haystack, $needle, $offset, $encoding); }
61
+ }
62
+ if (!function_exists('mb_strtolower')) {
63
+ function mb_strtolower($string, $encoding = null) { return p\Mbstring::mb_strtolower($string, $encoding); }
64
+ }
65
+ if (!function_exists('mb_strtoupper')) {
66
+ function mb_strtoupper($string, $encoding = null) { return p\Mbstring::mb_strtoupper($string, $encoding); }
67
+ }
68
+ if (!function_exists('mb_substitute_character')) {
69
+ function mb_substitute_character($substitute_character = null) { return p\Mbstring::mb_substitute_character($substitute_character); }
70
+ }
71
+ if (!function_exists('mb_substr')) {
72
+ function mb_substr($string, $start, $length = 2147483647, $encoding = null) { return p\Mbstring::mb_substr($string, $start, $length, $encoding); }
73
+ }
74
+ if (!function_exists('mb_stripos')) {
75
+ function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_stripos($haystack, $needle, $offset, $encoding); }
76
+ }
77
+ if (!function_exists('mb_stristr')) {
78
+ function mb_stristr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_stristr($haystack, $needle, $before_needle, $encoding); }
79
+ }
80
+ if (!function_exists('mb_strrchr')) {
81
+ function mb_strrchr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrchr($haystack, $needle, $before_needle, $encoding); }
82
+ }
83
+ if (!function_exists('mb_strrichr')) {
84
+ function mb_strrichr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrichr($haystack, $needle, $before_needle, $encoding); }
85
+ }
86
+ if (!function_exists('mb_strripos')) {
87
+ function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strripos($haystack, $needle, $offset, $encoding); }
88
+ }
89
+ if (!function_exists('mb_strrpos')) {
90
+ function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strrpos($haystack, $needle, $offset, $encoding); }
91
+ }
92
+ if (!function_exists('mb_strstr')) {
93
+ function mb_strstr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strstr($haystack, $needle, $before_needle, $encoding); }
94
+ }
95
+ if (!function_exists('mb_get_info')) {
96
+ function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); }
97
+ }
98
+ if (!function_exists('mb_http_output')) {
99
+ function mb_http_output($encoding = null) { return p\Mbstring::mb_http_output($encoding); }
100
+ }
101
+ if (!function_exists('mb_strwidth')) {
102
+ function mb_strwidth($string, $encoding = null) { return p\Mbstring::mb_strwidth($string, $encoding); }
103
+ }
104
+ if (!function_exists('mb_substr_count')) {
105
+ function mb_substr_count($haystack, $needle, $encoding = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $encoding); }
106
+ }
107
+ if (!function_exists('mb_output_handler')) {
108
+ function mb_output_handler($string, $status) { return p\Mbstring::mb_output_handler($string, $status); }
109
+ }
110
+ if (!function_exists('mb_http_input')) {
111
+ function mb_http_input($type = null) { return p\Mbstring::mb_http_input($type); }
112
+ }
113
+
114
+ if (!function_exists('mb_convert_variables')) {
115
+ function mb_convert_variables($to_encoding, $from_encoding, &...$vars) { return p\Mbstring::mb_convert_variables($to_encoding, $from_encoding, ...$vars); }
116
+ }
117
+
118
+ if (!function_exists('mb_ord')) {
119
+ function mb_ord($string, $encoding = null) { return p\Mbstring::mb_ord($string, $encoding); }
120
+ }
121
+ if (!function_exists('mb_chr')) {
122
+ function mb_chr($codepoint, $encoding = null) { return p\Mbstring::mb_chr($codepoint, $encoding); }
123
+ }
124
+ if (!function_exists('mb_scrub')) {
125
+ function mb_scrub($string, $encoding = null) { $encoding = null === $encoding ? mb_internal_encoding() : $encoding; return mb_convert_encoding($string, $encoding, $encoding); }
126
+ }
127
+ if (!function_exists('mb_str_split')) {
128
+ function mb_str_split($string, $length = 1, $encoding = null) { return p\Mbstring::mb_str_split($string, $length, $encoding); }
129
+ }
130
+
131
+ if (extension_loaded('mbstring')) {
132
+ return;
133
+ }
134
+
135
+ if (!defined('MB_CASE_UPPER')) {
136
+ define('MB_CASE_UPPER', 0);
137
+ }
138
+ if (!defined('MB_CASE_LOWER')) {
139
+ define('MB_CASE_LOWER', 1);
140
+ }
141
+ if (!defined('MB_CASE_TITLE')) {
142
+ define('MB_CASE_TITLE', 2);
143
+ }
vendor/browscap-php/symfony/polyfill-php73/Php73.php ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of the Symfony package.
5
+ *
6
+ * (c) Fabien Potencier <fabien@symfony.com>
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ namespace Symfony\Polyfill\Php73;
13
+
14
+ /**
15
+ * @author Gabriel Caruso <carusogabriel34@gmail.com>
16
+ * @author Ion Bazan <ion.bazan@gmail.com>
17
+ *
18
+ * @internal
19
+ */
20
+ final class Php73
21
+ {
22
+ public static $startAt = 1533462603;
23
+
24
+ /**
25
+ * @param bool $asNum
26
+ *
27
+ * @return array|float|int
28
+ */
29
+ public static function hrtime($asNum = false)
30
+ {
31
+ $ns = microtime(false);
32
+ $s = substr($ns, 11) - self::$startAt;
33
+ $ns = 1E9 * (float) $ns;
34
+
35
+ if ($asNum) {
36
+ $ns += $s * 1E9;
37
+
38
+ return \PHP_INT_SIZE === 4 ? $ns : (int) $ns;
39
+ }
40
+
41
+ return [$s, (int) $ns];
42
+ }
43
+ }
vendor/browscap-php/symfony/polyfill-php73/Resources/stubs/JsonException.php ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of the Symfony package.
5
+ *
6
+ * (c) Fabien Potencier <fabien@symfony.com>
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ if (\PHP_VERSION_ID < 70300) {
13
+ class JsonException extends Exception
14
+ {
15
+ }
16
+ }
vendor/browscap-php/symfony/polyfill-php73/bootstrap.php ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of the Symfony package.
5
+ *
6
+ * (c) Fabien Potencier <fabien@symfony.com>
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ use Symfony\Polyfill\Php73 as p;
13
+
14
+ if (\PHP_VERSION_ID >= 70300) {
15
+ return;
16
+ }
17
+
18
+ if (!function_exists('is_countable')) {
19
+ function is_countable($value) { return is_array($value) || $value instanceof Countable || $value instanceof ResourceBundle || $value instanceof SimpleXmlElement; }
20
+ }
21
+ if (!function_exists('hrtime')) {
22
+ require_once __DIR__.'/Php73.php';
23
+ p\Php73::$startAt = (int) microtime(true);
24
+ function hrtime($as_number = false) { return p\Php73::hrtime($as_number); }
25
+ }
26
+ if (!function_exists('array_key_first')) {
27
+ function array_key_first(array $array) { foreach ($array as $key => $value) { return $key; } }
28
+ }
29
+ if (!function_exists('array_key_last')) {
30
+ function array_key_last(array $array) { return key(array_slice($array, -1, 1, true)); }
31
+ }
vendor/browscap-php/symfony/polyfill-php80/Php80.php ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of the Symfony package.
5
+ *
6
+ * (c) Fabien Potencier <fabien@symfony.com>
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ namespace Symfony\Polyfill\Php80;
13
+
14
+ /**
15
+ * @author Ion Bazan <ion.bazan@gmail.com>
16
+ * @author Nico Oelgart <nicoswd@gmail.com>
17
+ * @author Nicolas Grekas <p@tchwork.com>
18
+ *
19
+ * @internal
20
+ */
21
+ final class Php80
22
+ {
23
+ public static function fdiv(float $dividend, float $divisor): float
24
+ {
25
+ return @($dividend / $divisor);
26
+ }
27
+
28
+ public static function get_debug_type($value): string
29
+ {
30
+ switch (true) {
31
+ case null === $value: return 'null';
32
+ case \is_bool($value): return 'bool';
33
+ case \is_string($value): return 'string';
34
+ case \is_array($value): return 'array';
35
+ case \is_int($value): return 'int';
36
+ case \is_float($value): return 'float';
37
+ case \is_object($value): break;
38
+ case $value instanceof \__PHP_Incomplete_Class: return '__PHP_Incomplete_Class';
39
+ default:
40
+ if (null === $type = @get_resource_type($value)) {
41
+ return 'unknown';
42
+ }
43
+
44
+ if ('Unknown' === $type) {
45
+ $type = 'closed';
46
+ }
47
+
48
+ return "resource ($type)";
49
+ }
50
+
51
+ $class = \get_class($value);
52
+
53
+ if (false === strpos($class, '@')) {
54
+ return $class;
55
+ }
56
+
57
+ return (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous';
58
+ }
59
+
60
+ public static function get_resource_id($res): int
61
+ {
62
+ if (!\is_resource($res) && null === @get_resource_type($res)) {
63
+ throw new \TypeError(sprintf('Argument 1 passed to get_resource_id() must be of the type resource, %s given', get_debug_type($res)));
64
+ }
65
+
66
+ return (int) $res;
67
+ }
68
+
69
+ public static function preg_last_error_msg(): string
70
+ {
71
+ switch (preg_last_error()) {
72
+ case \PREG_INTERNAL_ERROR:
73
+ return 'Internal error';
74
+ case \PREG_BAD_UTF8_ERROR:
75
+ return 'Malformed UTF-8 characters, possibly incorrectly encoded';
76
+ case \PREG_BAD_UTF8_OFFSET_ERROR:
77
+ return 'The offset did not correspond to the beginning of a valid UTF-8 code point';
78
+ case \PREG_BACKTRACK_LIMIT_ERROR:
79
+ return 'Backtrack limit exhausted';
80
+ case \PREG_RECURSION_LIMIT_ERROR:
81
+ return 'Recursion limit exhausted';
82
+ case \PREG_JIT_STACKLIMIT_ERROR:
83
+ return 'JIT stack limit exhausted';
84
+ case \PREG_NO_ERROR:
85
+ return 'No error';
86
+ default:
87
+ return 'Unknown error';
88
+ }
89
+ }
90
+
91
+ public static function str_contains(string $haystack, string $needle): bool
92
+ {
93
+ return '' === $needle || false !== strpos($haystack, $needle);
94
+ }
95
+
96
+ public static function str_starts_with(string $haystack, string $needle): bool
97
+ {
98
+ return 0 === strncmp($haystack, $needle, \strlen($needle));
99
+ }
100
+
101
+ public static function str_ends_with(string $haystack, string $needle): bool
102
+ {
103
+ return '' === $needle || ('' !== $haystack && 0 === substr_compare($haystack, $needle, -\strlen($needle)));
104
+ }
105
+ }
vendor/browscap-php/symfony/polyfill-php80/Resources/stubs/Attribute.php ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ #[Attribute(Attribute::TARGET_CLASS)]
4
+ final class Attribute
5
+ {
6
+ public const TARGET_CLASS = 1;
7
+ public const TARGET_FUNCTION = 2;
8
+ public const TARGET_METHOD = 4;
9
+ public const TARGET_PROPERTY = 8;
10
+ public const TARGET_CLASS_CONSTANT = 16;
11
+ public const TARGET_PARAMETER = 32;
12
+ public const TARGET_ALL = 63;
13
+ public const IS_REPEATABLE = 64;
14
+
15
+ /** @var int */
16
+ public $flags;
17
+
18
+ public function __construct(int $flags = self::TARGET_ALL)
19
+ {
20
+ $this->flags = $flags;
21
+ }
22
+ }
vendor/browscap-php/symfony/polyfill-php80/Resources/stubs/Stringable.php ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if (\PHP_VERSION_ID < 80000) {
4
+ interface Stringable
5
+ {
6
+ /**
7
+ * @return string
8
+ */
9
+ public function __toString();
10
+ }
11
+ }
vendor/browscap-php/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if (\PHP_VERSION_ID < 80000) {
4
+ class UnhandledMatchError extends Error
5
+ {
6
+ }
7
+ }
vendor/browscap-php/symfony/polyfill-php80/Resources/stubs/ValueError.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if (\PHP_VERSION_ID < 80000) {
4
+ class ValueError extends Error
5
+ {
6
+ }
7
+ }
vendor/browscap-php/symfony/polyfill-php80/bootstrap.php ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of the Symfony package.
5
+ *
6
+ * (c) Fabien Potencier <fabien@symfony.com>
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ use Symfony\Polyfill\Php80 as p;
13
+
14
+ if (\PHP_VERSION_ID >= 80000) {
15
+ return;
16
+ }
17
+
18
+ if (!defined('FILTER_VALIDATE_BOOL') && defined('FILTER_VALIDATE_BOOLEAN')) {
19
+ define('FILTER_VALIDATE_BOOL', \FILTER_VALIDATE_BOOLEAN);
20
+ }
21
+
22
+ if (!function_exists('fdiv')) {
23
+ function fdiv(float $num1, float $num2): float { return p\Php80::fdiv($num1, $num2); }
24
+ }
25
+ if (!function_exists('preg_last_error_msg')) {
26
+ function preg_last_error_msg(): string { return p\Php80::preg_last_error_msg(); }
27
+ }
28
+ if (!function_exists('str_contains')) {
29
+ function str_contains(?string $haystack, ?string $needle): bool { return p\Php80::str_contains($haystack ?? '', $needle ?? ''); }
30
+ }
31
+ if (!function_exists('str_starts_with')) {
32
+ function str_starts_with(?string $haystack, ?string $needle): bool { return p\Php80::str_starts_with($haystack ?? '', $needle ?? ''); }
33
+ }
34
+ if (!function_exists('str_ends_with')) {
35
+ function str_ends_with(?string $haystack, ?string $needle): bool { return p\Php80::str_ends_with($haystack ?? '', $needle ?? ''); }
36
+ }
37
+ if (!function_exists('get_debug_type')) {
38
+ function get_debug_type($value): string { return p\Php80::get_debug_type($value); }
39
+ }
40
+ if (!function_exists('get_resource_id')) {
41
+ function get_resource_id($resource): int { return p\Php80::get_resource_id($resource); }
42
+ }
vendor/browscap-php/symfony/string/AbstractString.php ADDED
@@ -0,0 +1,795 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of the Symfony package.
5
+ *
6
+ * (c) Fabien Potencier <fabien@symfony.com>
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ namespace Symfony\Component\String;
13
+
14
+ use Symfony\Component\String\Exception\ExceptionInterface;
15
+ use Symfony\Component\String\Exception\InvalidArgumentException;
16
+ use Symfony\Component\String\Exception\RuntimeException;
17
+
18
+ /**
19
+ * Represents a string of abstract characters.
20
+ *
21
+ * Unicode defines 3 types of "characters" (bytes, code points and grapheme clusters).
22
+ * This class is the abstract type to use as a type-hint when the logic you want to
23
+ * implement doesn't care about the exact variant it deals with.
24
+ *
25
+ * @author Nicolas Grekas <p@tchwork.com>
26
+ * @author Hugo Hamon <hugohamon@neuf.fr>
27
+ *
28
+ * @throws ExceptionInterface
29
+ */
30
+ abstract class AbstractString implements \Stringable, \JsonSerializable
31
+ {
32
+ public const PREG_PATTERN_ORDER = \PREG_PATTERN_ORDER;
33
+ public const PREG_SET_ORDER = \PREG_SET_ORDER;
34
+ public const PREG_OFFSET_CAPTURE = \PREG_OFFSET_CAPTURE;
35
+ public const PREG_UNMATCHED_AS_NULL = \PREG_UNMATCHED_AS_NULL;
36
+
37
+ public const PREG_SPLIT = 0;
38
+ public const PREG_SPLIT_NO_EMPTY = \PREG_SPLIT_NO_EMPTY;
39
+ public const PREG_SPLIT_DELIM_CAPTURE = \PREG_SPLIT_DELIM_CAPTURE;
40
+ public const PREG_SPLIT_OFFSET_CAPTURE = \PREG_SPLIT_OFFSET_CAPTURE;
41
+
42
+ protected $string = '';
43
+ protected $ignoreCase = false;
44
+
45
+ abstract public function __construct(string $string = '');
46
+
47
+ /**
48
+ * Unwraps instances of AbstractString back to strings.
49
+ *
50
+ * @return string[]|array
51
+ */
52
+ public static function unwrap(array $values): array
53
+ {
54
+ foreach ($values as $k => $v) {
55
+ if ($v instanceof self) {
56
+ $values[$k] = $v->__toString();
57
+ } elseif (\is_array($v) && $values[$k] !== $v = static::unwrap($v)) {
58
+ $values[$k] = $v;
59
+ }
60
+ }
61
+
62
+ return $values;
63
+ }
64
+
65
+ /**
66
+ * Wraps (and normalizes) strings in instances of AbstractString.
67
+ *
68
+ * @return static[]|array
69
+ */
70
+ public static function wrap(array $values): array
71
+ {
72
+ $i = 0;
73
+ $keys = null;
74
+
75
+ foreach ($values as $k => $v) {
76
+ if (\is_string($k) && '' !== $k && $k !== $j = (string) new static($k)) {
77
+ $keys = $keys ?? array_keys($values);
78
+ $keys[$i] = $j;
79
+ }
80
+
81
+ if (\is_string($v)) {
82
+ $values[$k] = new static($v);
83
+ } elseif (\is_array($v) && $values[$k] !== $v = static::wrap($v)) {
84
+ $values[$k] = $v;
85
+ }
86
+
87
+ ++$i;
88
+ }
89
+
90
+ return null !== $keys ? array_combine($keys, $values) : $values;
91
+ }
92
+
93
+ /**
94
+ * @param string|string[] $needle
95
+ *
96
+ * @return static
97
+ */
98
+ public function after($needle, bool $includeNeedle = false, int $offset = 0): self
99
+ {
100
+ $str = clone $this;
101
+ $i = \PHP_INT_MAX;
102
+
103
+ foreach ((array) $needle as $n) {
104
+ $n = (string) $n;
105
+ $j = $this->indexOf($n, $offset);
106
+
107
+ if (null !== $j && $j < $i) {
108
+ $i = $j;
109
+ $str->string = $n;
110
+ }
111
+ }
112
+
113
+ if (\PHP_INT_MAX === $i) {
114
+ return $str;
115
+ }
116
+
117
+ if (!$includeNeedle) {
118
+ $i += $str->length();
119
+ }
120
+
121
+ return $this->slice($i);
122
+ }
123
+
124
+ /**
125
+ * @param string|string[] $needle
126
+ *
127
+ * @return static
128
+ */
129
+ public function afterLast($needle, bool $includeNeedle = false, int $offset = 0): self
130
+ {
131
+ $str = clone $this;
132
+ $i = null;
133
+
134
+ foreach ((array) $needle as $n) {
135
+ $n = (string) $n;
136
+ $j = $this->indexOfLast($n, $offset);
137
+
138
+ if (null !== $j && $j >= $i) {
139
+ $i = $offset = $j;
140
+ $str->string = $n;
141
+ }
142
+ }
143
+
144
+ if (null === $i) {
145
+ return $str;
146
+ }
147
+
148
+ if (!$includeNeedle) {
149
+ $i += $str->length();
150
+ }
151
+
152
+ return $this->slice($i);
153
+ }
154
+
155
+ /**
156
+ * @return static
157
+ */
158
+ abstract public function append(string ...$suffix): self;
159
+
160
+ /**
161
+ * @param string|string[] $needle
162
+ *
163
+ * @return static
164
+ */
165
+ public function before($needle, bool $includeNeedle = false, int $offset = 0): self
166
+ {
167
+ $str = clone $this;
168
+ $i = \PHP_INT_MAX;
169
+
170
+ foreach ((array) $needle as $n) {
171
+ $n = (string) $n;
172
+ $j = $this->indexOf($n, $offset);
173
+
174
+ if (null !== $j && $j < $i) {
175
+ $i = $j;
176
+ $str->string = $n;
177
+ }
178
+ }
179
+
180
+ if (\PHP_INT_MAX === $i) {
181
+ return $str;
182
+ }
183
+
184
+ if ($includeNeedle) {
185
+ $i += $str->length();
186
+ }
187
+
188
+ return $this->slice(0, $i);
189
+ }
190
+
191
+ /**
192
+ * @param string|string[] $needle
193
+ *
194
+ * @return static
195
+ */
196
+ public function beforeLast($needle, bool $includeNeedle = false, int $offset = 0): self
197
+ {
198
+ $str = clone $this;
199
+ $i = null;
200
+
201
+ foreach ((array) $needle as $n) {
202
+ $n = (string) $n;
203
+ $j = $this->indexOfLast($n, $offset);
204
+
205
+ if (null !== $j && $j >= $i) {
206
+ $i = $offset = $j;
207
+ $str->string = $n;
208
+ }
209
+ }
210
+
211
+ if (null === $i) {
212
+ return $str;
213
+ }
214
+
215
+ if ($includeNeedle) {
216
+ $i += $str->length();
217
+ }
218
+
219
+ return $this->slice(0, $i);
220
+ }
221
+
222
+ /**
223
+ * @return int[]
224
+ */
225
+ public function bytesAt(int $offset): array
226
+ {
227
+ $str = $this->slice($offset, 1);
228
+
229
+ return '' === $str->string ? [] : array_values(unpack('C*', $str->string));
230
+ }
231
+
232
+ /**
233
+ * @return static
234
+ */
235
+ abstract public function camel(): self;
236
+
237
+ /**
238
+ * @return static[]
239
+ */
240
+ abstract public function chunk(int $length = 1): array;
241
+
242
+ /**
243
+ * @return static
244
+ */
245
+ public function collapseWhitespace(): self
246
+ {
247
+ $str = clone $this;
248
+ $str->string = trim(preg_replace('/(?:\s{2,}+|[^\S ])/', ' ', $str->string));
249
+
250
+ return $str;
251
+ }
252
+
253
+ /**
254
+ * @param string|string[] $needle
255
+ */
256
+ public function containsAny($needle): bool
257
+ {
258
+ return null !== $this->indexOf($needle);
259
+ }
260
+
261
+ /**
262
+ * @param string|string[] $suffix
263
+ */
264
+ public function endsWith($suffix): bool
265
+ {
266
+ if (!\is_array($suffix) && !$suffix instanceof \Traversable) {
267
+ throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class));
268
+ }
269
+
270
+ foreach ($suffix as $s) {
271
+ if ($this->endsWith((string) $s)) {
272
+ return true;
273
+ }
274
+ }
275
+
276
+ return false;
277
+ }
278
+
279
+ /**
280
+ * @return static
281
+ */
282
+ public function ensureEnd(string $suffix): self
283
+ {
284
+ if (!$this->endsWith($suffix)) {
285
+ return $this->append($suffix);
286
+ }
287
+
288
+ $suffix = preg_quote($suffix);
289
+ $regex = '{('.$suffix.')(?:'.$suffix.')++$}D';
290
+
291
+ return $this->replaceMatches($regex.($this->ignoreCase ? 'i' : ''), '$1');
292
+ }
293
+
294
+ /**
295
+ * @return static
296
+ */
297
+ public function ensureStart(string $prefix): self
298
+ {
299
+ $prefix = new static($prefix);
300
+
301
+ if (!$this->startsWith($prefix)) {
302
+ return $this->prepend($prefix);
303
+ }
304
+
305
+ $str = clone $this;
306
+ $i = $prefixLen = $prefix->length();
307
+
308
+ while ($this->indexOf($prefix, $i) === $i) {
309
+ $str = $str->slice($prefixLen);
310
+ $i += $prefixLen;
311
+ }
312
+
313
+ return $str;
314
+ }
315
+
316
+ /**
317
+ * @param string|string[] $string
318
+ */
319
+ public function equalsTo($string): bool
320
+ {
321
+ if (!\is_array($string) && !$string instanceof \Traversable) {
322
+ throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class));
323
+ }
324
+
325
+ foreach ($string as $s) {
326
+ if ($this->equalsTo((string) $s)) {
327
+ return true;
328
+ }
329
+ }
330
+
331
+ return false;
332
+ }
333
+
334
+ /**
335
+ * @return static
336
+ */
337
+ abstract public function folded(): self;
338
+
339
+ /**
340
+ * @return static
341
+ */
342
+ public function ignoreCase(): self
343
+ {
344
+ $str = clone $this;
345
+ $str->ignoreCase = true;
346
+
347
+ return $str;
348
+ }
349
+
350
+ /**
351
+ * @param string|string[] $needle
352
+ */
353
+ public function indexOf($needle, int $offset = 0): ?int
354
+ {
355
+ if (!\is_array($needle) && !$needle instanceof \Traversable) {
356
+ throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class));
357
+ }
358
+
359
+ $i = \PHP_INT_MAX;
360
+
361
+ foreach ($needle as $n) {
362
+ $j = $this->indexOf((string) $n, $offset);
363
+
364
+ if (null !== $j && $j < $i) {
365
+ $i = $j;
366
+ }
367
+ }
368
+
369
+ return \PHP_INT_MAX === $i ? null : $i;
370
+ }
371
+
372
+ /**
373
+ * @param string|string[] $needle
374
+ */
375
+ public function indexOfLast($needle, int $offset = 0): ?int
376
+ {
377
+ if (!\is_array($needle) && !$needle instanceof \Traversable) {
378
+ throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class));
379
+ }
380
+
381
+ $i = null;
382
+
383
+ foreach ($needle as $n) {
384
+ $j = $this->indexOfLast((string) $n, $offset);
385
+
386
+ if (null !== $j && $j >= $i) {
387
+ $i = $offset = $j;
388
+ }
389
+ }
390
+
391
+ return $i;
392
+ }
393
+
394
+ public function isEmpty(): bool
395
+ {
396
+ return '' === $this->string;
397
+ }
398
+
399
+ /**
400
+ * @return static
401
+ */
402
+ abstract public function join(array $strings, string $lastGlue = null): self;
403
+
404
+ public function jsonSerialize(): string
405
+ {
406
+ return $this->string;
407
+ }
408
+
409
+ abstract public function length(): int;
410
+
411
+ /**
412
+ * @return static
413
+ */
414
+ abstract public function lower(): self;
415
+
416
+ /**
417
+ * Matches the string using a regular expression.
418
+ *
419
+ * Pass PREG_PATTERN_ORDER or PREG_SET_ORDER as $flags to get all occurrences matching the regular expression.
420
+ *
421
+ * @return array All matches in a multi-dimensional array ordered according to flags
422
+ */
423
+ abstract public function match(string $regexp, int $flags = 0, int $offset = 0): array;
424
+
425
+ /**
426
+ * @return static
427
+ */
428
+ abstract public function padBoth(int $length, string $padStr = ' '): self;
429
+
430
+ /**
431
+ * @return static
432
+ */
433
+ abstract public function padEnd(int $length, string $padStr = ' '): self;
434
+
435
+ /**
436
+ * @return static
437
+ */
438
+ abstract public function padStart(int $length, string $padStr = ' '): self;
439
+
440
+ /**
441
+ * @return static
442
+ */
443
+ abstract public function prepend(string ...$prefix): self;
444
+
445
+ /**
446
+ * @return static
447
+ */
448
+ public function repeat(int $multiplier): self
449
+ {
450
+ if (0 > $multiplier) {
451
+ throw new InvalidArgumentException(sprintf('Multiplier must be positive, %d given.', $multiplier));
452
+ }
453
+
454
+ $str = clone $this;
455
+ $str->string = str_repeat($str->string, $multiplier);
456
+
457
+ return $str;
458
+ }
459
+
460
+ /**
461
+ * @return static
462
+ */
463
+ abstract public function replace(string $from, string $to): self;
464
+
465
+ /**
466
+ * @param string|callable $to
467
+ *
468
+ * @return static
469
+ */
470
+ abstract public function replaceMatches(string $fromRegexp, $to): self;
471
+
472
+ /**
473
+ * @return static
474
+ */
475
+ abstract public function reverse(): self;
476
+
477
+ /**
478
+ * @return static
479
+ */
480
+ abstract public function slice(int $start = 0, int $length = null): self;
481
+
482
+ /**
483
+ * @return static
484
+ */
485
+ abstract public function snake(): self;
486
+
487
+ /**
488
+ * @return static
489
+ */
490
+ abstract public function splice(string $replacement, int $start = 0, int $length = null): self;
491
+
492
+ /**
493
+ * @return static[]
494
+ */
495
+ public function split(string $delimiter, int $limit = null, int $flags = null): array
496
+ {
497
+ if (null === $flags) {
498
+ throw new \TypeError('Split behavior when $flags is null must be implemented by child classes.');
499
+ }
500
+
501
+ if ($this->ignoreCase) {
502
+ $delimiter .= 'i';
503
+ }
504
+
505
+ set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); });
506
+
507
+ try {
508
+ if (false === $chunks = preg_split($delimiter, $this->string, $limit, $flags)) {
509
+ $lastError = preg_last_error();
510
+
511
+ foreach (get_defined_constants(true)['pcre'] as $k => $v) {
512
+ if ($lastError === $v && '_ERROR' === substr($k, -6)) {
513
+ throw new RuntimeException('Splitting failed with '.$k.'.');
514
+ }
515
+ }
516
+
517
+ throw new RuntimeException('Splitting failed with unknown error code.');
518
+ }
519
+ } finally {
520
+ restore_error_handler();
521
+ }
522
+
523
+ $str = clone $this;
524
+
525
+ if (self::PREG_SPLIT_OFFSET_CAPTURE & $flags) {
526
+ foreach ($chunks as &$chunk) {
527
+ $str->string = $chunk[0];
528
+ $chunk[0] = clone $str;
529
+ }
530
+ } else {
531
+ foreach ($chunks as &$chunk) {
532
+ $str->string = $chunk;
533
+ $chunk = clone $str;
534
+ }
535
+ }
536
+
537
+ return $chunks;
538
+ }
539
+
540
+ /**
541
+ * @param string|string[] $prefix
542
+ */
543
+ public function startsWith($prefix): bool
544
+ {
545
+ if (!\is_array($prefix) && !$prefix instanceof \Traversable) {
546
+ throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class));
547
+ }
548
+
549
+ foreach ($prefix as $prefix) {
550
+ if ($this->startsWith((string) $prefix)) {
551
+ return true;
552
+ }
553
+ }
554
+
555
+ return false;
556
+ }
557
+
558
+ /**
559
+ * @return static
560
+ */
561
+ abstract public function title(bool $allWords = false): self;
562
+
563
+ public function toByteString(string $toEncoding = null): ByteString
564
+ {
565
+ $b = new ByteString();
566
+
567
+ $toEncoding = \in_array($toEncoding, ['utf8', 'utf-8', 'UTF8'], true) ? 'UTF-8' : $toEncoding;
568
+
569
+ if (null === $toEncoding || $toEncoding === $fromEncoding = $this instanceof AbstractUnicodeString || preg_match('//u', $b->string) ? 'UTF-8' : 'Windows-1252') {
570
+ $b->string = $this->string;
571
+
572
+ return $b;
573
+ }
574
+
575
+ set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); });
576
+
577
+ try {
578
+ try {
579
+ $b->string = mb_convert_encoding($this->string, $toEncoding, 'UTF-8');
580
+ } catch (InvalidArgumentException $e) {
581
+ if (!\function_exists('iconv')) {
582
+ throw $e;
583
+ }
584
+
585
+ $b->string = iconv('UTF-8', $toEncoding, $this->string);
586
+ }
587
+ } finally {
588
+ restore_error_handler();
589
+ }
590
+
591
+ return $b;
592
+ }
593
+
594
+ public function toCodePointString(): CodePointString
595
+ {
596
+ return new CodePointString($this->string);
597
+ }
598
+
599
+ public function toString(): string
600
+ {
601
+ return $this->string;
602
+ }
603
+
604
+ public function toUnicodeString(): UnicodeString
605
+ {
606
+ return new UnicodeString($this->string);
607
+ }
608
+
609
+ /**
610
+ * @return static
611
+ */
612
+ abstract public function trim(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): self;
613
+
614
+ /**
615
+ * @return static
616
+ */
617
+ abstract public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): self;
618
+
619
+ /**
620
+ * @param string|string[] $prefix
621
+ *
622
+ * @return static
623
+ */
624
+ public function trimPrefix($prefix): self
625
+ {
626
+ if (\is_array($prefix) || $prefix instanceof \Traversable) {
627
+ foreach ($prefix as $s) {
628
+ $t = $this->trimPrefix($s);
629
+
630
+ if ($t->string !== $this->string) {
631
+ return $t;
632
+ }
633
+ }
634
+
635
+ return clone $this;
636
+ }
637
+
638
+ $str = clone $this;
639
+
640
+ if ($prefix instanceof self) {
641
+ $prefix = $prefix->string;
642
+ } else {
643
+ $prefix = (string) $prefix;
644
+ }
645
+
646
+ if ('' !== $prefix && \strlen($this->string) >= \strlen($prefix) && 0 === substr_compare($this->string, $prefix, 0, \strlen($prefix), $this->ignoreCase)) {
647
+ $str->string = substr($this->string, \strlen($prefix));
648
+ }
649
+
650
+ return $str;
651
+ }
652
+
653
+ /**
654
+ * @return static
655
+ */
656
+ abstract public function trimStart(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): self;
657
+
658
+ /**
659
+ * @param string|string[] $suffix
660
+ *
661
+ * @return static
662
+ */
663
+ public function trimSuffix($suffix): self
664
+ {
665
+ if (\is_array($suffix) || $suffix instanceof \Traversable) {
666
+ foreach ($suffix as $s) {
667
+ $t = $this->trimSuffix($s);
668
+
669
+ if ($t->string !== $this->string) {
670
+ return $t;
671
+ }
672
+ }
673
+
674
+ return clone $this;
675
+ }
676
+
677
+ $str = clone $this;
678
+
679
+ if ($suffix instanceof self) {
680
+ $suffix = $suffix->string;
681
+ } else {
682
+ $suffix = (string) $suffix;
683
+ }
684
+
685
+ if ('' !== $suffix && \strlen($this->string) >= \strlen($suffix) && 0 === substr_compare($this->string, $suffix, -\strlen($suffix), null, $this->ignoreCase)) {
686
+ $str->string = substr($this->string, 0, -\strlen($suffix));
687
+ }
688
+
689
+ return $str;
690
+ }
691
+
692
+ /**
693
+ * @return static
694
+ */
695
+ public function truncate(int $length, string $ellipsis = '', bool $cut = true): self
696
+ {
697
+ $stringLength = $this->length();
698
+
699
+ if ($stringLength <= $length) {
700
+ return clone $this;
701
+ }
702
+
703
+ $ellipsisLength = '' !== $ellipsis ? (new static($ellipsis))->length() : 0;
704
+
705
+ if ($length < $ellipsisLength) {
706
+ $ellipsisLength = 0;
707
+ }
708
+
709
+ if (!$cut) {
710
+ if (null === $length = $this->indexOf([' ', "\r", "\n", "\t"], ($length ?: 1) - 1)) {
711
+ return clone $this;
712
+ }
713
+
714
+ $length += $ellipsisLength;
715
+ }
716
+
717
+ $str = $this->slice(0, $length - $ellipsisLength);
718
+
719
+ return $ellipsisLength ? $str->trimEnd()->append($ellipsis) : $str;
720
+ }
721
+
722
+ /**
723
+ * @return static
724
+ */
725
+ abstract public function upper(): self;
726
+
727
+ /**
728
+ * Returns the printable length on a terminal.
729
+ */
730
+ abstract public function width(bool $ignoreAnsiDecoration = true): int;
731
+
732
+ /**
733
+ * @return static
734
+ */
735
+ public function wordwrap(int $width = 75, string $break = "\n", bool $cut = false): self
736
+ {
737
+ $lines = '' !== $break ? $this->split($break) : [clone $this];
738
+ $chars = [];
739
+ $mask = '';
740
+
741
+ if (1 === \count($lines) && '' === $lines[0]->string) {
742
+ return $lines[0];
743
+ }
744
+
745
+ foreach ($lines as $i => $line) {
746
+ if ($i) {
747
+ $chars[] = $break;
748
+ $mask .= '#';
749
+ }
750
+
751
+ foreach ($line->chunk() as $char) {
752
+ $chars[] = $char->string;
753
+ $mask .= ' ' === $char->string ? ' ' : '?';
754
+ }
755
+ }
756
+
757
+ $string = '';
758
+ $j = 0;
759
+ $b = $i = -1;
760
+ $mask = wordwrap($mask, $width, '#', $cut);
761
+
762
+ while (false !== $b = strpos($mask, '#', $b + 1)) {
763
+ for (++$i; $i < $b; ++$i) {
764
+ $string .= $chars[$j];
765
+ unset($chars[$j++]);
766
+ }
767
+
768
+ if ($break === $chars[$j] || ' ' === $chars[$j]) {
769
+ unset($chars[$j++]);
770
+ }
771
+
772
+ $string .= $break;
773
+ }
774
+
775
+ $str = clone $this;
776
+ $str->string = $string.implode('', $chars);
777
+
778
+ return $str;
779
+ }
780
+
781
+ public function __sleep(): array
782
+ {
783
+ return ['string'];
784
+ }
785
+
786
+ public function __clone()
787
+ {
788
+ $this->ignoreCase = false;
789
+ }
790
+
791
+ public function __toString(): string
792
+ {
793
+ return $this->string;
794
+ }
795
+ }
vendor/browscap-php/symfony/string/AbstractUnicodeString.php ADDED
@@ -0,0 +1,620 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of the Symfony package.
5
+ *
6
+ * (c) Fabien Potencier <fabien@symfony.com>
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ namespace Symfony\Component\String;
13
+
14
+ use Symfony\Component\String\Exception\ExceptionInterface;
15
+ use Symfony\Component\String\Exception\InvalidArgumentException;
16
+ use Symfony\Component\String\Exception\RuntimeException;
17
+
18
+ /**
19
+ * Represents a string of abstract Unicode characters.
20
+ *
21
+ * Unicode defines 3 types of "characters" (bytes, code points and grapheme clusters).
22
+ * This class is the abstract type to use as a type-hint when the logic you want to
23
+ * implement is Unicode-aware but doesn't care about code points vs grapheme clusters.
24
+ *
25
+ * @author Nicolas Grekas <p@tchwork.com>
26
+ *
27
+ * @throws ExceptionInterface
28
+ */
29
+ abstract class AbstractUnicodeString extends AbstractString
30
+ {
31
+ public const NFC = \Normalizer::NFC;
32
+ public const NFD = \Normalizer::NFD;
33
+ public const NFKC = \Normalizer::NFKC;
34
+ public const NFKD = \Normalizer::NFKD;
35
+
36
+ // all ASCII letters sorted by typical frequency of occurrence
37
+ private const ASCII = "\x20\x65\x69\x61\x73\x6E\x74\x72\x6F\x6C\x75\x64\x5D\x5B\x63\x6D\x70\x27\x0A\x67\x7C\x68\x76\x2E\x66\x62\x2C\x3A\x3D\x2D\x71\x31\x30\x43\x32\x2A\x79\x78\x29\x28\x4C\x39\x41\x53\x2F\x50\x22\x45\x6A\x4D\x49\x6B\x33\x3E\x35\x54\x3C\x44\x34\x7D\x42\x7B\x38\x46\x77\x52\x36\x37\x55\x47\x4E\x3B\x4A\x7A\x56\x23\x48\x4F\x57\x5F\x26\x21\x4B\x3F\x58\x51\x25\x59\x5C\x09\x5A\x2B\x7E\x5E\x24\x40\x60\x7F\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F";
38
+
39
+ // the subset of folded case mappings that is not in lower case mappings
40
+ private const FOLD_FROM = ['İ', 'µ', 'ſ', "\xCD\x85", 'ς', 'ϐ', 'ϑ', 'ϕ', 'ϖ', 'ϰ', 'ϱ', 'ϵ', 'ẛ', "\xE1\xBE\xBE", 'ß', 'İ', 'ʼn', 'ǰ', 'ΐ', 'ΰ', 'և', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'ẚ', 'ẞ', 'ὐ', 'ὒ', 'ὔ', 'ὖ', 'ᾀ', 'ᾁ', 'ᾂ', 'ᾃ', 'ᾄ', 'ᾅ', 'ᾆ', 'ᾇ', 'ᾈ', 'ᾉ', 'ᾊ', 'ᾋ', 'ᾌ', 'ᾍ', 'ᾎ', 'ᾏ', 'ᾐ', 'ᾑ', 'ᾒ', 'ᾓ', 'ᾔ', 'ᾕ', 'ᾖ', 'ᾗ', 'ᾘ', 'ᾙ', 'ᾚ', 'ᾛ', 'ᾜ', 'ᾝ', 'ᾞ', 'ᾟ', 'ᾠ', 'ᾡ', 'ᾢ', 'ᾣ', 'ᾤ', 'ᾥ', 'ᾦ', 'ᾧ', 'ᾨ', 'ᾩ', 'ᾪ', 'ᾫ', 'ᾬ', 'ᾭ', 'ᾮ', 'ᾯ', 'ᾲ', 'ᾳ', 'ᾴ', 'ᾶ', 'ᾷ', 'ᾼ', 'ῂ', 'ῃ', 'ῄ', 'ῆ', 'ῇ', 'ῌ', 'ῒ', 'ΐ', 'ῖ', 'ῗ', 'ῢ', 'ΰ', 'ῤ', 'ῦ', 'ῧ', 'ῲ', 'ῳ', 'ῴ', 'ῶ', 'ῷ', 'ῼ', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'ſt', 'st', 'ﬓ', 'ﬔ', 'ﬕ', 'ﬖ', 'ﬗ'];
41
+ private const FOLD_TO = ['i̇', 'μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', 'ṡ', 'ι', 'ss', 'i̇', 'ʼn', 'ǰ', 'ΐ', 'ΰ', 'եւ', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'aʾ', 'ss', 'ὐ', 'ὒ', 'ὔ', 'ὖ', 'ἀι', 'ἁι', 'ἂι', 'ἃι', 'ἄι', 'ἅι', 'ἆι', 'ἇι', 'ἀι', 'ἁι', 'ἂι', 'ἃι', 'ἄι', 'ἅι', 'ἆι', 'ἇι', 'ἠι', 'ἡι', 'ἢι', 'ἣι', 'ἤι', 'ἥι', 'ἦι', 'ἧι', 'ἠι', 'ἡι', 'ἢι', 'ἣι', 'ἤι', 'ἥι', 'ἦι', 'ἧι', 'ὠι', 'ὡι', 'ὢι', 'ὣι', 'ὤι', 'ὥι', 'ὦι', 'ὧι', 'ὠι', 'ὡι', 'ὢι', 'ὣι', 'ὤι', 'ὥι', 'ὦι', 'ὧι', 'ὰι', 'αι', 'άι', 'ᾶ', 'ᾶι', 'αι', 'ὴι', 'ηι', 'ήι', 'ῆ', 'ῆι', 'ηι', 'ῒ', 'ΐ', 'ῖ', 'ῗ', 'ῢ', 'ΰ', 'ῤ', 'ῦ', 'ῧ', 'ὼι', 'ωι', 'ώι', 'ῶ', 'ῶι', 'ωι', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'st', 'st', 'մն', 'մե', 'մի', 'վն', 'մխ'];
42
+
43
+ // the subset of upper case mappings that map one code point to many code points
44
+ private const UPPER_FROM = ['ß', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'ſt', 'st', 'և', 'ﬓ', 'ﬔ', 'ﬕ', 'ﬖ', 'ﬗ', 'ʼn', 'ΐ', 'ΰ', 'ǰ', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'ẚ', 'ὐ', 'ὒ', 'ὔ', 'ὖ', 'ᾶ', 'ῆ', 'ῒ', 'ΐ', 'ῖ', 'ῗ', 'ῢ', 'ΰ', 'ῤ', 'ῦ', 'ῧ', 'ῶ'];
45
+ private const UPPER_TO = ['SS', 'FF', 'FI', 'FL', 'FFI', 'FFL', 'ST', 'ST', 'ԵՒ', 'ՄՆ', 'ՄԵ', 'ՄԻ', 'ՎՆ', 'ՄԽ', 'ʼN', 'Ϊ́', 'Ϋ́', 'J̌', 'H̱', 'T̈', 'W̊', 'Y̊', 'Aʾ', 'Υ̓', 'Υ̓̀', 'Υ̓́', 'Υ̓͂', 'Α͂', 'Η͂', 'Ϊ̀', 'Ϊ́', 'Ι͂', 'Ϊ͂', 'Ϋ̀', 'Ϋ́', 'Ρ̓', 'Υ͂', 'Ϋ͂', 'Ω͂'];
46
+
47
+ // the subset of https://github.com/unicode-org/cldr/blob/master/common/transforms/Latin-ASCII.xml that is not in NFKD
48
+ private const TRANSLIT_FROM = ['Æ', 'Ð', 'Ø', 'Þ', 'ß', 'æ', 'ð', 'ø', 'þ', 'Đ', 'đ', 'Ħ', 'ħ', 'ı', 'ĸ', 'Ŀ', 'ŀ', 'Ł', 'ł', 'ʼn', 'Ŋ', 'ŋ', 'Œ', 'œ', 'Ŧ', 'ŧ', 'ƀ', 'Ɓ', 'Ƃ', 'ƃ', 'Ƈ', 'ƈ', 'Ɖ', 'Ɗ', 'Ƌ', 'ƌ', 'Ɛ', 'Ƒ', 'ƒ', 'Ɠ', 'ƕ', 'Ɩ', 'Ɨ', 'Ƙ', 'ƙ', 'ƚ', 'Ɲ', 'ƞ', 'Ƣ', 'ƣ', 'Ƥ', 'ƥ', 'ƫ', 'Ƭ', 'ƭ', 'Ʈ', 'Ʋ', 'Ƴ', 'ƴ', 'Ƶ', 'ƶ', 'DŽ', 'Dž', 'dž', 'Ǥ', 'ǥ', 'ȡ', 'Ȥ', 'ȥ', 'ȴ', 'ȵ', 'ȶ', 'ȷ', 'ȸ', 'ȹ', 'Ⱥ', 'Ȼ', 'ȼ', 'Ƚ', 'Ⱦ', 'ȿ', 'ɀ', 'Ƀ', 'Ʉ', 'Ɇ', 'ɇ', 'Ɉ', 'ɉ', 'Ɍ', 'ɍ', 'Ɏ', 'ɏ', 'ɓ', 'ɕ', 'ɖ', 'ɗ', 'ɛ', 'ɟ', 'ɠ', 'ɡ', 'ɢ', 'ɦ', 'ɧ', 'ɨ', 'ɪ', 'ɫ', 'ɬ', 'ɭ', 'ɱ', 'ɲ', 'ɳ', 'ɴ', 'ɶ', 'ɼ', 'ɽ', 'ɾ', 'ʀ', 'ʂ', 'ʈ', 'ʉ', 'ʋ', 'ʏ', 'ʐ', 'ʑ', 'ʙ', 'ʛ', 'ʜ', 'ʝ', 'ʟ', 'ʠ', 'ʣ', 'ʥ', 'ʦ', 'ʪ', 'ʫ', 'ᴀ', 'ᴁ', 'ᴃ', 'ᴄ', 'ᴅ', 'ᴆ', 'ᴇ', 'ᴊ', 'ᴋ', 'ᴌ', 'ᴍ', 'ᴏ', 'ᴘ', 'ᴛ', 'ᴜ', 'ᴠ', 'ᴡ', 'ᴢ', 'ᵫ', 'ᵬ', 'ᵭ', 'ᵮ', 'ᵯ', 'ᵰ', 'ᵱ', 'ᵲ', 'ᵳ', 'ᵴ', 'ᵵ', 'ᵶ', 'ᵺ', 'ᵻ', 'ᵽ', 'ᵾ', 'ᶀ', 'ᶁ', 'ᶂ', 'ᶃ', 'ᶄ', 'ᶅ', 'ᶆ', 'ᶇ', 'ᶈ', 'ᶉ', 'ᶊ', 'ᶌ', 'ᶍ', 'ᶎ', 'ᶏ', 'ᶑ', 'ᶒ', 'ᶓ', 'ᶖ', 'ᶙ', 'ẚ', 'ẜ', 'ẝ', 'ẞ', 'Ỻ', 'ỻ', 'Ỽ', 'ỽ', 'Ỿ', 'ỿ', '©', '®', '₠', '₢', '₣', '₤', '₧', '₺', '₹', 'ℌ', '℞', '㎧', '㎮', '㏆', '㏗', '㏞', '㏟', '¼', '½', '¾', '⅓', '⅔', '⅕', '⅖', '⅗', '⅘', '⅙', '⅚', '⅛', '⅜', '⅝', '⅞', '⅟', '〇', '‘', '’', '‚', '‛', '“', '”', '„', '‟', '′', '″', '〝', '〞', '«', '»', '‹', '›', '‐', '‑', '‒', '–', '—', '―', '︱', '︲', '﹘', '‖', '⁄', '⁅', '⁆', '⁎', '、', '。', '〈', '〉', '《', '》', '〔', '〕', '〘', '〙', '〚', '〛', '︑', '︒', '︹', '︺', '︽', '︾', '︿', '﹀', '﹑', '﹝', '﹞', '⦅', '⦆', '。', '、', '×', '÷', '−', '∕', '∖', '∣', '∥', '≪', '≫', '⦅', '⦆'];
49
+ private const TRANSLIT_TO = ['AE', 'D', 'O', 'TH', 'ss', 'ae', 'd', 'o', 'th', 'D', 'd', 'H', 'h', 'i', 'q', 'L', 'l', 'L', 'l', '\'n', 'N', 'n', 'OE', 'oe', 'T', 't', 'b', 'B', 'B', 'b', 'C', 'c', 'D', 'D', 'D', 'd', 'E', 'F', 'f', 'G', 'hv', 'I', 'I', 'K', 'k', 'l', 'N', 'n', 'OI', 'oi', 'P', 'p', 't', 'T', 't', 'T', 'V', 'Y', 'y', 'Z', 'z', 'DZ', 'Dz', 'dz', 'G', 'g', 'd', 'Z', 'z', 'l', 'n', 't', 'j', 'db', 'qp', 'A', 'C', 'c', 'L', 'T', 's', 'z', 'B', 'U', 'E', 'e', 'J', 'j', 'R', 'r', 'Y', 'y', 'b', 'c', 'd', 'd', 'e', 'j', 'g', 'g', 'G', 'h', 'h', 'i', 'I', 'l', 'l', 'l', 'm', 'n', 'n', 'N', 'OE', 'r', 'r', 'r', 'R', 's', 't', 'u', 'v', 'Y', 'z', 'z', 'B', 'G', 'H', 'j', 'L', 'q', 'dz', 'dz', 'ts', 'ls', 'lz', 'A', 'AE', 'B', 'C', 'D', 'D', 'E', 'J', 'K', 'L', 'M', 'O', 'P', 'T', 'U', 'V', 'W', 'Z', 'ue', 'b', 'd', 'f', 'm', 'n', 'p', 'r', 'r', 's', 't', 'z', 'th', 'I', 'p', 'U', 'b', 'd', 'f', 'g', 'k', 'l', 'm', 'n', 'p', 'r', 's', 'v', 'x', 'z', 'a', 'd', 'e', 'e', 'i', 'u', 'a', 's', 's', 'SS', 'LL', 'll', 'V', 'v', 'Y', 'y', '(C)', '(R)', 'CE', 'Cr', 'Fr.', 'L.', 'Pts', 'TL', 'Rs', 'x', 'Rx', 'm/s', 'rad/s', 'C/kg', 'pH', 'V/m', 'A/m', ' 1/4', ' 1/2', ' 3/4', ' 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/', '0', '\'', '\'', ',', '\'', '"', '"', ',,', '"', '\'', '"', '"', '"', '<<', '>>', '<', '>', '-', '-', '-', '-', '-', '-', '-', '-', '-', '||', '/', '[', ']', '*', ',', '.', '<', '>', '<<', '>>', '[', ']', '[', ']', '[', ']', ',', '.', '[', ']', '<<', '>>', '<', '>', ',', '[', ']', '((', '))', '.', ',', '*', '/', '-', '/', '\\', '|', '||', '<<', '>>', '((', '))'];
50
+
51
+ private static $transliterators = [];
52
+ private static $tableZero;
53
+ private static $tableWide;
54
+
55
+ /**
56
+ * @return static
57
+ */
58
+ public static function fromCodePoints(int ...$codes): self
59
+ {
60
+ $string = '';
61
+
62
+ foreach ($codes as $code) {
63
+ if (0x80 > $code %= 0x200000) {
64
+ $string .= \chr($code);
65
+ } elseif (0x800 > $code) {
66
+ $string .= \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F);
67
+ } elseif (0x10000 > $code) {
68
+ $string .= \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
69
+ } else {
70
+ $string .= \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
71
+ }
72
+ }
73
+
74
+ return new static($string);
75
+ }
76
+
77
+ /**
78
+ * Generic UTF-8 to ASCII transliteration.
79
+ *
80
+ * Install the intl extension for best results.
81
+ *
82
+ * @param string[]|\Transliterator[]|\Closure[] $rules See "*-Latin" rules from Transliterator::listIDs()
83
+ */
84
+ public function ascii(array $rules = []): self
85
+ {
86
+ $str = clone $this;
87
+ $s = $str->string;
88
+ $str->string = '';
89
+
90
+ array_unshift($rules, 'nfd');
91
+ $rules[] = 'latin-ascii';
92
+
93
+ if (\function_exists('transliterator_transliterate')) {
94
+ $rules[] = 'any-latin/bgn';
95
+ }
96
+
97
+ $rules[] = 'nfkd';
98
+ $rules[] = '[:nonspacing mark:] remove';
99
+
100
+ while (\strlen($s) - 1 > $i = strspn($s, self::ASCII)) {
101
+ if (0 < --$i) {
102
+ $str->string .= substr($s, 0, $i);
103
+ $s = substr($s, $i);
104
+ }
105
+
106
+ if (!$rule = array_shift($rules)) {
107
+ $rules = []; // An empty rule interrupts the next ones
108
+ }
109
+
110
+ if ($rule instanceof \Transliterator) {
111
+ $s = $rule->transliterate($s);
112
+ } elseif ($rule instanceof \Closure) {
113
+ $s = $rule($s);
114
+ } elseif ($rule) {
115
+ if ('nfd' === $rule = strtolower($rule)) {
116
+ normalizer_is_normalized($s, self::NFD) ?: $s = normalizer_normalize($s, self::NFD);
117
+ } elseif ('nfkd' === $rule) {
118
+ normalizer_is_normalized($s, self::NFKD) ?: $s = normalizer_normalize($s, self::NFKD);
119
+ } elseif ('[:nonspacing mark:] remove' === $rule) {
120
+ $s = preg_replace('/\p{Mn}++/u', '', $s);
121
+ } elseif ('latin-ascii' === $rule) {
122
+ $s = str_replace(self::TRANSLIT_FROM, self::TRANSLIT_TO, $s);
123
+ } elseif ('de-ascii' === $rule) {
124
+ $s = preg_replace("/([AUO])\u{0308}(?=\p{Ll})/u", '$1e', $s);
125
+ $s = str_replace(["a\u{0308}", "o\u{0308}", "u\u{0308}", "A\u{0308}", "O\u{0308}", "U\u{0308}"], ['ae', 'oe', 'ue', 'AE', 'OE', 'UE'], $s);
126
+ } elseif (\function_exists('transliterator_transliterate')) {
127
+ if (null === $transliterator = self::$transliterators[$rule] ?? self::$transliterators[$rule] = \Transliterator::create($rule)) {
128
+ if ('any-latin/bgn' === $rule) {
129
+ $rule = 'any-latin';
130
+ $transliterator = self::$transliterators[$rule] ?? self::$transliterators[$rule] = \Transliterator::create($rule);
131
+ }
132
+
133
+ if (null === $transliterator) {
134
+ throw new InvalidArgumentException(sprintf('Unknown transliteration rule "%s".', $rule));
135
+ }
136
+
137
+ self::$transliterators['any-latin/bgn'] = $transliterator;
138
+ }
139
+
140
+ $s = $transliterator->transliterate($s);
141
+ }
142
+ } elseif (!\function_exists('iconv')) {
143
+ $s = preg_replace('/[^\x00-\x7F]/u', '?', $s);
144
+ } else {
145
+ $s = @preg_replace_callback('/[^\x00-\x7F]/u', static function ($c) {
146
+ $c = (string) iconv('UTF-8', 'ASCII//TRANSLIT', $c[0]);
147
+
148
+ if ('' === $c && '' === iconv('UTF-8', 'ASCII//TRANSLIT', '²')) {
149
+ throw new \LogicException(sprintf('"%s" requires a translit-able iconv implementation, try installing "gnu-libiconv" if you\'re using Alpine Linux.', static::class));
150
+ }
151
+
152
+ return 1 < \strlen($c) ? ltrim($c, '\'`"^~') : ('' !== $c ? $c : '?');
153
+ }, $s);
154
+ }
155
+ }
156
+
157
+ $str->string .= $s;
158
+
159
+ return $str;
160
+ }
161
+
162
+ public function camel(): parent
163
+ {
164
+ $str = clone $this;
165
+ $str->string = str_replace(' ', '', preg_replace_callback('/\b./u', static function ($m) use (&$i) {
166
+ return 1 === ++$i ? ('İ' === $m[0] ? 'i̇' : mb_strtolower($m[0], 'UTF-8')) : mb_convert_case($m[0], \MB_CASE_TITLE, 'UTF-8');
167
+ }, preg_replace('/[^\pL0-9]++/u', ' ', $this->string)));
168
+
169
+ return $str;
170
+ }
171
+
172
+ /**
173
+ * @return int[]
174
+ */
175
+ public function codePointsAt(int $offset): array
176
+ {
177
+ $str = $this->slice($offset, 1);
178
+
179
+ if ('' === $str->string) {
180
+ return [];
181
+ }
182
+
183
+ $codePoints = [];
184
+
185
+ foreach (preg_split('//u', $str->string, -1, \PREG_SPLIT_NO_EMPTY) as $c) {
186
+ $codePoints[] = mb_ord($c, 'UTF-8');
187
+ }
188
+
189
+ return $codePoints;
190
+ }
191
+
192
+ public function folded(bool $compat = true): parent
193
+ {
194
+ $str = clone $this;
195
+
196
+ if (!$compat || \PHP_VERSION_ID < 70300 || !\defined('Normalizer::NFKC_CF')) {
197
+ $str->string = normalizer_normalize($str->string, $compat ? \Normalizer::NFKC : \Normalizer::NFC);
198
+ $str->string = mb_strtolower(str_replace(self::FOLD_FROM, self::FOLD_TO, $this->string), 'UTF-8');
199
+ } else {
200
+ $str->string = normalizer_normalize($str->string, \Normalizer::NFKC_CF);
201
+ }
202
+
203
+ return $str;
204
+ }
205
+
206
+ public function join(array $strings, string $lastGlue = null): parent
207
+ {
208
+ $str = clone $this;
209
+
210
+ $tail = null !== $lastGlue && 1 < \count($strings) ? $lastGlue.array_pop($strings) : '';
211
+ $str->string = implode($this->string, $strings).$tail;
212
+
213
+ if (!preg_match('//u', $str->string)) {
214
+ throw new InvalidArgumentException('Invalid UTF-8 string.');
215
+ }
216
+
217
+ return $str;
218
+ }
219
+
220
+ public function lower(): parent
221
+ {
222
+ $str = clone $this;
223
+ $str->string = mb_strtolower(str_replace('İ', 'i̇', $str->string), 'UTF-8');
224
+
225
+ return $str;
226
+ }
227
+
228
+ public function match(string $regexp, int $flags = 0, int $offset = 0): array
229
+ {
230
+ $match = ((\PREG_PATTERN_ORDER | \PREG_SET_ORDER) & $flags) ? 'preg_match_all' : 'preg_match';
231
+
232
+ if ($this->ignoreCase) {
233
+ $regexp .= 'i';
234
+ }
235
+
236
+ set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); });
237
+
238
+ try {
239
+ if (false === $match($regexp.'u', $this->string, $matches, $flags | \PREG_UNMATCHED_AS_NULL, $offset)) {
240
+ $lastError = preg_last_error();
241
+
242
+ foreach (get_defined_constants(true)['pcre'] as $k => $v) {
243
+ if ($lastError === $v && '_ERROR' === substr($k, -6)) {
244
+ throw new RuntimeException('Matching failed with '.$k.'.');
245
+ }
246
+ }
247
+
248
+ throw new RuntimeException('Matching failed with unknown error code.');
249
+ }
250
+ } finally {
251
+ restore_error_handler();
252
+ }
253
+
254
+ return $matches;
255
+ }
256
+
257
+ /**
258
+ * @return static
259
+ */
260
+ public function normalize(int $form = self::NFC): self
261
+ {
262
+ if (!\in_array($form, [self::NFC, self::NFD, self::NFKC, self::NFKD])) {
263
+ throw new InvalidArgumentException('Unsupported normalization form.');
264
+ }
265
+
266
+ $str = clone $this;
267
+ normalizer_is_normalized($str->string, $form) ?: $str->string = normalizer_normalize($str->string, $form);
268
+
269
+ return $str;
270
+ }
271
+
272
+ public function padBoth(int $length, string $padStr = ' '): parent
273
+ {
274
+ if ('' === $padStr || !preg_match('//u', $padStr)) {
275
+ throw new InvalidArgumentException('Invalid UTF-8 string.');
276
+ }
277
+
278
+ $pad = clone $this;
279
+ $pad->string = $padStr;
280
+
281
+ return $this->pad($length, $pad, \STR_PAD_BOTH);
282
+ }
283
+
284
+ public function padEnd(int $length, string $padStr = ' '): parent
285
+ {
286
+ if ('' === $padStr || !preg_match('//u', $padStr)) {
287
+ throw new InvalidArgumentException('Invalid UTF-8 string.');
288
+ }
289
+
290
+ $pad = clone $this;
291
+ $pad->string = $padStr;
292
+
293
+ return $this->pad($length, $pad, \STR_PAD_RIGHT);
294
+ }
295
+
296
+ public function padStart(int $length, string $padStr = ' '): parent
297
+ {
298
+ if ('' === $padStr || !preg_match('//u', $padStr)) {
299
+ throw new InvalidArgumentException('Invalid UTF-8 string.');
300
+ }
301
+
302
+ $pad = clone $this;
303
+ $pad->string = $padStr;
304
+
305
+ return $this->pad($length, $pad, \STR_PAD_LEFT);
306
+ }
307
+
308
+ public function replaceMatches(string $fromRegexp, $to): parent
309
+ {
310
+ if ($this->ignoreCase) {
311
+ $fromRegexp .= 'i';
312
+ }
313
+
314
+ if (\is_array($to) || $to instanceof \Closure) {
315
+ if (!\is_callable($to)) {
316
+ throw new \TypeError(sprintf('Argument 2 passed to "%s::replaceMatches()" must be callable, array given.', static::class));
317
+ }
318
+
319
+ $replace = 'preg_replace_callback';
320
+ $to = static function (array $m) use ($to): string {
321
+ $to = $to($m);
322
+
323
+ if ('' !== $to && (!\is_string($to) || !preg_match('//u', $to))) {
324
+ throw new InvalidArgumentException('Replace callback must return a valid UTF-8 string.');
325
+ }
326
+
327
+ return $to;
328
+ };
329
+ } elseif ('' !== $to && !preg_match('//u', $to)) {
330
+ throw new InvalidArgumentException('Invalid UTF-8 string.');
331
+ } else {
332
+ $replace = 'preg_replace';
333
+ }
334
+
335
+ set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); });
336
+
337
+ try {
338
+ if (null === $string = $replace($fromRegexp.'u', $to, $this->string)) {
339
+ $lastError = preg_last_error();
340
+
341
+ foreach (get_defined_constants(true)['pcre'] as $k => $v) {
342
+ if ($lastError === $v && '_ERROR' === substr($k, -6)) {
343
+ throw new RuntimeException('Matching failed with '.$k.'.');
344
+ }
345
+ }
346
+
347
+ throw new RuntimeException('Matching failed with unknown error code.');
348
+ }
349
+ } finally {
350
+ restore_error_handler();
351
+ }
352
+
353
+ $str = clone $this;
354
+ $str->string = $string;
355
+
356
+ return $str;
357
+ }
358
+
359
+ public function reverse(): parent
360
+ {
361
+ $str = clone $this;
362
+ $str->string = implode('', array_reverse(preg_split('/(\X)/u', $str->string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY)));
363
+
364
+ return $str;
365
+ }
366
+
367
+ public function snake(): parent
368
+ {
369
+ $str = $this->camel()->title();
370
+ $str->string = mb_strtolower(preg_replace(['/(\p{Lu}+)(\p{Lu}\p{Ll})/u', '/([\p{Ll}0-9])(\p{Lu})/u'], '\1_\2', $str->string), 'UTF-8');
371
+
372
+ return $str;
373
+ }
374
+
375
+ public function title(bool $allWords = false): parent
376
+ {
377
+ $str = clone $this;
378
+
379
+ $limit = $allWords ? -1 : 1;
380
+
381
+ $str->string = preg_replace_callback('/\b./u', static function (array $m): string {
382
+ return mb_convert_case($m[0], \MB_CASE_TITLE, 'UTF-8');
383
+ }, $str->string, $limit);
384
+
385
+ return $str;
386
+ }
387
+
388
+ public function trim(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): parent
389
+ {
390
+ if (" \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}" !== $chars && !preg_match('//u', $chars)) {
391
+ throw new InvalidArgumentException('Invalid UTF-8 chars.');
392
+ }
393
+ $chars = preg_quote($chars);
394
+
395
+ $str = clone $this;
396
+ $str->string = preg_replace("{^[$chars]++|[$chars]++$}uD", '', $str->string);
397
+
398
+ return $str;
399
+ }
400
+
401
+ public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): parent
402
+ {
403
+ if (" \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}" !== $chars && !preg_match('//u', $chars)) {
404
+ throw new InvalidArgumentException('Invalid UTF-8 chars.');
405
+ }
406
+ $chars = preg_quote($chars);
407
+
408
+ $str = clone $this;
409
+ $str->string = preg_replace("{[$chars]++$}uD", '', $str->string);
410
+
411
+ return $str;
412
+ }
413
+
414
+ public function trimPrefix($prefix): parent
415
+ {
416
+ if (!$this->ignoreCase) {
417
+ return parent::trimPrefix($prefix);
418
+ }
419
+
420
+ $str = clone $this;
421
+
422
+ if ($prefix instanceof \Traversable) {
423
+ $prefix = iterator_to_array($prefix, false);
424
+ } elseif ($prefix instanceof parent) {
425
+ $prefix = $prefix->string;
426
+ }
427
+
428
+ $prefix = implode('|', array_map('preg_quote', (array) $prefix));
429
+ $str->string = preg_replace("{^(?:$prefix)}iuD", '', $this->string);
430
+
431
+ return $str;
432
+ }
433
+
434
+ public function trimStart(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): parent
435
+ {
436
+ if (" \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}" !== $chars && !preg_match('//u', $chars)) {
437
+ throw new InvalidArgumentException('Invalid UTF-8 chars.');
438
+ }
439
+ $chars = preg_quote($chars);
440
+
441
+ $str = clone $this;
442
+ $str->string = preg_replace("{^[$chars]++}uD", '', $str->string);
443
+
444
+ return $str;
445
+ }
446
+
447
+ public function trimSuffix($suffix): parent
448
+ {
449
+ if (!$this->ignoreCase) {
450
+ return parent::trimSuffix($suffix);
451
+ }
452
+
453
+ $str = clone $this;
454
+
455
+ if ($suffix instanceof \Traversable) {
456
+ $suffix = iterator_to_array($suffix, false);
457
+ } elseif ($suffix instanceof parent) {
458
+ $suffix = $suffix->string;
459
+ }
460
+
461
+ $suffix = implode('|', array_map('preg_quote', (array) $suffix));
462
+ $str->string = preg_replace("{(?:$suffix)$}iuD", '', $this->string);
463
+
464
+ return $str;
465
+ }
466
+
467
+ public function upper(): parent
468
+ {
469
+ $str = clone $this;
470
+ $str->string = mb_strtoupper($str->string, 'UTF-8');
471
+
472
+ if (\PHP_VERSION_ID < 70300) {
473
+ $str->string = str_replace(self::UPPER_FROM, self::UPPER_TO, $str->string);
474
+ }
475
+
476
+ return $str;
477
+ }
478
+
479
+ public function width(bool $ignoreAnsiDecoration = true): int
480
+ {
481
+ $width = 0;
482
+ $s = str_replace(["\x00", "\x05", "\x07"], '', $this->string);
483
+
484
+ if (false !== strpos($s, "\r")) {
485
+ $s = str_replace(["\r\n", "\r"], "\n", $s);
486
+ }
487
+
488
+ if (!$ignoreAnsiDecoration) {
489
+ $s = preg_replace('/[\p{Cc}\x7F]++/u', '', $s);
490
+ }
491
+
492
+ foreach (explode("\n", $s) as $s) {
493
+ if ($ignoreAnsiDecoration) {
494
+ $s = preg_replace('/(?:\x1B(?:
495
+ \[ [\x30-\x3F]*+ [\x20-\x2F]*+ [0x40-\x7E]
496
+ | [P\]X^_] .*? \x1B\\\\
497
+ | [\x41-\x7E]
498
+ )|[\p{Cc}\x7F]++)/xu', '', $s);
499
+ }
500
+
501
+ // Non printable characters have been dropped, so wcswidth cannot logically return -1.
502
+ $width += $this->wcswidth($s);
503
+ }
504
+
505
+ return $width;
506
+ }
507
+
508
+ /**
509
+ * @return static
510
+ */
511
+ private function pad(int $len, self $pad, int $type): parent
512
+ {
513
+ $sLen = $this->length();
514
+
515
+ if ($len <= $sLen) {
516
+ return clone $this;
517
+ }
518
+
519
+ $padLen = $pad->length();
520
+ $freeLen = $len - $sLen;
521
+ $len = $freeLen % $padLen;
522
+
523
+ switch ($type) {
524
+ case \STR_PAD_RIGHT:
525
+ return $this->append(str_repeat($pad->string, intdiv($freeLen, $padLen)).($len ? $pad->slice(0, $len) : ''));
526
+
527
+ case \STR_PAD_LEFT:
528
+ return $this->prepend(str_repeat($pad->string, intdiv($freeLen, $padLen)).($len ? $pad->slice(0, $len) : ''));
529
+
530
+ case \STR_PAD_BOTH:
531
+ $freeLen /= 2;
532
+
533
+ $rightLen = ceil($freeLen);
534
+ $len = $rightLen % $padLen;
535
+ $str = $this->append(str_repeat($pad->string, intdiv($rightLen, $padLen)).($len ? $pad->slice(0, $len) : ''));
536
+
537
+ $leftLen = floor($freeLen);
538
+ $len = $leftLen % $padLen;
539
+
540
+ return $str->prepend(str_repeat($pad->string, intdiv($leftLen, $padLen)).($len ? $pad->slice(0, $len) : ''));
541
+
542
+ default:
543
+ throw new InvalidArgumentException('Invalid padding type.');
544
+ }
545
+ }
546
+
547
+ /**
548
+ * Based on https://github.com/jquast/wcwidth, a Python implementation of https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c.
549
+ */
550
+ private function wcswidth(string $string): int
551
+ {
552
+ $width = 0;
553
+
554
+ foreach (preg_split('//u', $string, -1, \PREG_SPLIT_NO_EMPTY) as $c) {
555
+ $codePoint = mb_ord($c, 'UTF-8');
556
+
557
+ if (0 === $codePoint // NULL
558
+ || 0x034F === $codePoint // COMBINING GRAPHEME JOINER
559
+ || (0x200B <= $codePoint && 0x200F >= $codePoint) // ZERO WIDTH SPACE to RIGHT-TO-LEFT MARK
560
+ || 0x2028 === $codePoint // LINE SEPARATOR
561
+ || 0x2029 === $codePoint // PARAGRAPH SEPARATOR
562
+ || (0x202A <= $codePoint && 0x202E >= $codePoint) // LEFT-TO-RIGHT EMBEDDING to RIGHT-TO-LEFT OVERRIDE
563
+ || (0x2060 <= $codePoint && 0x2063 >= $codePoint) // WORD JOINER to INVISIBLE SEPARATOR
564
+ ) {
565
+ continue;
566
+ }
567
+
568
+ // Non printable characters
569
+ if (32 > $codePoint // C0 control characters
570
+ || (0x07F <= $codePoint && 0x0A0 > $codePoint) // C1 control characters and DEL
571
+ ) {
572
+ return -1;
573
+ }
574
+
575
+ if (null === self::$tableZero) {
576
+ self::$tableZero = require __DIR__.'/Resources/data/wcswidth_table_zero.php';
577
+ }
578
+
579
+ if ($codePoint >= self::$tableZero[0][0] && $codePoint <= self::$tableZero[$ubound = \count(self::$tableZero) - 1][1]) {
580
+ $lbound = 0;
581
+ while ($ubound >= $lbound) {
582
+ $mid = floor(($lbound + $ubound) / 2);
583
+
584
+ if ($codePoint > self::$tableZero[$mid][1]) {
585
+ $lbound = $mid + 1;
586
+ } elseif ($codePoint < self::$tableZero[$mid][0]) {
587
+ $ubound = $mid - 1;
588
+ } else {
589
+ continue 2;
590
+ }
591
+ }
592
+ }
593
+
594
+ if (null === self::$tableWide) {
595
+ self::$tableWide = require __DIR__.'/Resources/data/wcswidth_table_wide.php';
596
+ }
597
+
598
+ if ($codePoint >= self::$tableWide[0][0] && $codePoint <= self::$tableWide[$ubound = \count(self::$tableWide) - 1][1]) {
599
+ $lbound = 0;
600
+ while ($ubound >= $lbound) {
601
+ $mid = floor(($lbound + $ubound) / 2);
602
+
603
+ if ($codePoint > self::$tableWide[$mid][1]) {
604
+ $lbound = $mid + 1;
605
+ } elseif ($codePoint < self::$tableWide[$mid][0]) {
606
+ $ubound = $mid - 1;
607
+ } else {
608
+ $width += 2;
609
+
610
+ continue 2;
611
+ }
612
+ }
613
+ }
614
+
615
+ ++$width;
616
+ }
617
+
618
+ return $width;
619
+ }
620
+ }
vendor/browscap-php/symfony/string/ByteString.php ADDED
@@ -0,0 +1,506 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of the Symfony package.
5
+ *
6
+ * (c) Fabien Potencier <fabien@symfony.com>
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ namespace Symfony\Component\String;
13
+
14
+ use Symfony\Component\String\Exception\ExceptionInterface;
15
+ use Symfony\Component\String\Exception\InvalidArgumentException;
16
+ use Symfony\Component\String\Exception\RuntimeException;
17
+
18
+ /**
19
+ * Represents a binary-safe string of bytes.
20
+ *
21
+ * @author Nicolas Grekas <p@tchwork.com>
22
+ * @author Hugo Hamon <hugohamon@neuf.fr>
23
+ *
24
+ * @throws ExceptionInterface
25
+ */
26
+ class ByteString extends AbstractString
27
+ {
28
+ private const ALPHABET_ALPHANUMERIC = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
29
+
30
+ public function __construct(string $string = '')
31
+ {
32
+ $this->string = $string;
33
+ }
34
+
35
+ /*
36
+ * The following method was derived from code of the Hack Standard Library (v4.40 - 2020-05-03)
37
+ *
38
+ * https://github.com/hhvm/hsl/blob/80a42c02f036f72a42f0415e80d6b847f4bf62d5/src/random/private.php#L16
39
+ *
40
+ * Code subject to the MIT license (https://github.com/hhvm/hsl/blob/master/LICENSE).
41
+ *
42
+ * Copyright (c) 2004-2020, Facebook, Inc. (https://www.facebook.com/)
43
+ */
44
+
45
+ public static function fromRandom(int $length = 16, string $alphabet = null): self
46
+ {
47
+ if ($length <= 0) {
48
+ throw new InvalidArgumentException(sprintf('A strictly positive length is expected, "%d" given.', $length));
49
+ }
50
+
51
+ $alphabet = $alphabet ?? self::ALPHABET_ALPHANUMERIC;
52
+ $alphabetSize = \strlen($alphabet);
53
+ $bits = (int) ceil(log($alphabetSize, 2.0));
54
+ if ($bits <= 0 || $bits > 56) {
55
+ throw new InvalidArgumentException('The length of the alphabet must in the [2^1, 2^56] range.');
56
+ }
57
+
58
+ $ret = '';
59
+ while ($length > 0) {
60
+ $urandomLength = (int) ceil(2 * $length * $bits / 8.0);
61
+ $data = random_bytes($urandomLength);
62
+ $unpackedData = 0;
63
+ $unpackedBits = 0;
64
+ for ($i = 0; $i < $urandomLength && $length > 0; ++$i) {
65
+ // Unpack 8 bits
66
+ $unpackedData = ($unpackedData << 8) | \ord($data[$i]);
67
+ $unpackedBits += 8;
68
+
69
+ // While we have enough bits to select a character from the alphabet, keep
70
+ // consuming the random data
71
+ for (; $unpackedBits >= $bits && $length > 0; $unpackedBits -= $bits) {
72
+ $index = ($unpackedData & ((1 << $bits) - 1));
73
+ $unpackedData >>= $bits;
74
+ // Unfortunately, the alphabet size is not necessarily a power of two.
75
+ // Worst case, it is 2^k + 1, which means we need (k+1) bits and we
76
+ // have around a 50% chance of missing as k gets larger
77
+ if ($index < $alphabetSize) {
78
+ $ret .= $alphabet[$index];
79
+ --$length;
80
+ }
81
+ }
82
+ }
83
+ }
84
+
85
+ return new static($ret);
86
+ }
87
+
88
+ public function bytesAt(int $offset): array
89
+ {
90
+ $str = $this->string[$offset] ?? '';
91
+
92
+ return '' === $str ? [] : [\ord($str)];
93
+ }
94
+
95
+ public function append(string ...$suffix): parent
96
+ {
97
+ $str = clone $this;
98
+ $str->string .= 1 >= \count($suffix) ? ($suffix[0] ?? '') : implode('', $suffix);
99
+
100
+ return $str;
101
+ }
102
+
103
+ public function camel(): parent
104
+ {
105
+ $str = clone $this;
106
+ $str->string = lcfirst(str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $this->string))));
107
+
108
+ return $str;
109
+ }
110
+
111
+ public function chunk(int $length = 1): array
112
+ {
113
+ if (1 > $length) {
114
+ throw new InvalidArgumentException('The chunk length must be greater than zero.');
115
+ }
116
+
117
+ if ('' === $this->string) {
118
+ return [];
119
+ }
120
+
121
+ $str = clone $this;
122
+ $chunks = [];
123
+
124
+ foreach (str_split($this->string, $length) as $chunk) {
125
+ $str->string = $chunk;
126
+ $chunks[] = clone $str;
127
+ }
128
+
129
+ return $chunks;
130
+ }
131
+
132
+ public function endsWith($suffix): bool
133
+ {
134
+ if ($suffix instanceof parent) {
135
+ $suffix = $suffix->string;
136
+ } elseif (\is_array($suffix) || $suffix instanceof \Traversable) {
137
+ return parent::endsWith($suffix);
138
+ } else {
139
+ $suffix = (string) $suffix;
140
+ }
141
+
142
+ return '' !== $suffix && \strlen($this->string) >= \strlen($suffix) && 0 === substr_compare($this->string, $suffix, -\strlen($suffix), null, $this->ignoreCase);
143
+ }
144
+
145
+ public function equalsTo($string): bool
146
+ {
147
+ if ($string instanceof parent) {
148
+ $string = $string->string;
149
+ } elseif (\is_array($string) || $string instanceof \Traversable) {
150
+ return parent::equalsTo($string);
151
+ } else {
152
+ $string = (string) $string;
153
+ }
154
+
155
+ if ('' !== $string && $this->ignoreCase) {
156
+ return 0 === strcasecmp($string, $this->string);
157
+ }
158
+
159
+ return $string === $this->string;
160
+ }
161
+
162
+ public function folded(): parent
163
+ {
164
+ $str = clone $this;
165
+ $str->string = strtolower($str->string);
166
+
167
+ return $str;
168
+ }
169
+
170
+ public function indexOf($needle, int $offset = 0): ?int
171
+ {
172
+ if ($needle instanceof parent) {
173
+ $needle = $needle->string;
174
+ } elseif (\is_array($needle) || $needle instanceof \Traversable) {
175
+ return parent::indexOf($needle, $offset);
176
+ } else {
177
+ $needle = (string) $needle;
178
+ }
179
+
180
+ if ('' === $needle) {
181
+ return null;
182
+ }
183
+
184
+ $i = $this->ignoreCase ? stripos($this->string, $needle, $offset) : strpos($this->string, $needle, $offset);
185
+
186
+ return false === $i ? null : $i;
187
+ }
188
+
189
+ public function indexOfLast($needle, int $offset = 0): ?int
190
+ {
191
+ if ($needle instanceof parent) {
192
+ $needle = $needle->string;
193
+ } elseif (\is_array($needle) || $needle instanceof \Traversable) {
194
+ return parent::indexOfLast($needle, $offset);
195
+ } else {
196
+ $needle = (string) $needle;
197
+ }
198
+
199
+ if ('' === $needle) {
200
+ return null;
201
+ }
202
+
203
+ $i = $this->ignoreCase ? strripos($this->string, $needle, $offset) : strrpos($this->string, $needle, $offset);
204
+
205
+ return false === $i ? null : $i;
206
+ }
207
+
208
+ public function isUtf8(): bool
209
+ {
210
+ return '' === $this->string || preg_match('//u', $this->string);
211
+ }
212
+
213
+ public function join(array $strings, string $lastGlue = null): parent
214
+ {
215
+ $str = clone $this;
216
+
217
+ $tail = null !== $lastGlue && 1 < \count($strings) ? $lastGlue.array_pop($strings) : '';
218
+ $str->string = implode($this->string, $strings).$tail;
219
+
220
+ return $str;
221
+ }
222
+
223
+ public function length(): int
224
+ {
225
+ return \strlen($this->string);
226
+ }
227
+
228
+ public function lower(): parent
229
+ {
230
+ $str = clone $this;
231
+ $str->string = strtolower($str->string);
232
+
233
+ return $str;
234
+ }
235
+
236
+ public function match(string $regexp, int $flags = 0, int $offset = 0): array
237
+ {
238
+ $match = ((\PREG_PATTERN_ORDER | \PREG_SET_ORDER) & $flags) ? 'preg_match_all' : 'preg_match';
239
+
240
+ if ($this->ignoreCase) {
241
+ $regexp .= 'i';
242
+ }
243
+
244
+ set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); });
245
+
246
+ try {
247
+ if (false === $match($regexp, $this->string, $matches, $flags | \PREG_UNMATCHED_AS_NULL, $offset)) {
248
+ $lastError = preg_last_error();
249
+
250
+ foreach (get_defined_constants(true)['pcre'] as $k => $v) {
251
+ if ($lastError === $v && '_ERROR' === substr($k, -6)) {
252
+ throw new RuntimeException('Matching failed with '.$k.'.');
253
+ }
254
+ }
255
+
256
+ throw new RuntimeException('Matching failed with unknown error code.');
257
+ }
258
+ } finally {
259
+ restore_error_handler();
260
+ }
261
+
262
+ return $matches;
263
+ }
264
+
265
+ public function padBoth(int $length, string $padStr = ' '): parent
266
+ {
267
+ $str = clone $this;
268
+ $str->string = str_pad($this->string, $length, $padStr, \STR_PAD_BOTH);
269
+
270
+ return $str;
271
+ }
272
+
273
+ public function padEnd(int $length, string $padStr = ' '): parent
274
+ {
275
+ $str = clone $this;
276
+ $str->string = str_pad($this->string, $length, $padStr, \STR_PAD_RIGHT);
277
+
278
+ return $str;
279
+ }
280
+
281
+ public function padStart(int $length, string $padStr = ' '): parent
282
+ {
283
+ $str = clone $this;
284
+ $str->string = str_pad($this->string, $length, $padStr, \STR_PAD_LEFT);
285
+
286
+ return $str;
287
+ }
288
+
289
+ public function prepend(string ...$prefix): parent
290
+ {
291
+ $str = clone $this;
292
+ $str->string = (1 >= \count($prefix) ? ($prefix[0] ?? '') : implode('', $prefix)).$str->string;
293
+
294
+ return $str;
295
+ }
296
+
297
+ public function replace(string $from, string $to): parent
298
+ {
299
+ $str = clone $this;
300
+
301
+ if ('' !== $from) {
302
+ $str->string = $this->ignoreCase ? str_ireplace($from, $to, $this->string) : str_replace($from, $to, $this->string);
303
+ }
304
+
305
+ return $str;
306
+ }
307
+
308
+ public function replaceMatches(string $fromRegexp, $to): parent
309
+ {
310
+ if ($this->ignoreCase) {
311
+ $fromRegexp .= 'i';
312
+ }
313
+
314
+ if (\is_array($to)) {
315
+ if (!\is_callable($to)) {
316
+ throw new \TypeError(sprintf('Argument 2 passed to "%s::replaceMatches()" must be callable, array given.', static::class));
317
+ }
318
+
319
+ $replace = 'preg_replace_callback';
320
+ } else {
321
+ $replace = $to instanceof \Closure ? 'preg_replace_callback' : 'preg_replace';
322
+ }
323
+
324
+ set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); });
325
+
326
+ try {
327
+ if (null === $string = $replace($fromRegexp, $to, $this->string)) {
328
+ $lastError = preg_last_error();
329
+
330
+ foreach (get_defined_constants(true)['pcre'] as $k => $v) {
331
+ if ($lastError === $v && '_ERROR' === substr($k, -6)) {
332
+ throw new RuntimeException('Matching failed with '.$k.'.');
333
+ }
334
+ }
335
+
336
+ throw new RuntimeException('Matching failed with unknown error code.');
337
+ }
338
+ } finally {
339
+ restore_error_handler();
340
+ }
341
+
342
+ $str = clone $this;
343
+ $str->string = $string;
344
+
345
+ return $str;
346
+ }
347
+
348
+ public function reverse(): parent
349
+ {
350
+ $str = clone $this;
351
+ $str->string = strrev($str->string);
352
+
353
+ return $str;
354
+ }
355
+
356
+ public function slice(int $start = 0, int $length = null): parent
357
+ {
358
+ $str = clone $this;
359
+ $str->string = (string) substr($this->string, $start, $length ?? \PHP_INT_MAX);
360
+
361
+ return $str;
362
+ }
363
+
364
+ public function snake(): parent
365
+ {
366
+ $str = $this->camel()->title();
367
+ $str->string = strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], '\1_\2', $str->string));
368
+
369
+ return $str;
370
+ }
371
+
372
+ public function splice(string $replacement, int $start = 0, int $length = null): parent
373
+ {
374
+ $str = clone $this;
375
+ $str->string = substr_replace($this->string, $replacement, $start, $length ?? \PHP_INT_MAX);
376
+
377
+ return $str;
378
+ }
379
+
380
+ public function split(string $delimiter, int $limit = null, int $flags = null): array
381
+ {
382
+ if (1 > $limit = $limit ?? \PHP_INT_MAX) {
383
+ throw new InvalidArgumentException('Split limit must be a positive integer.');
384
+ }
385
+
386
+ if ('' === $delimiter) {
387
+ throw new InvalidArgumentException('Split delimiter is empty.');
388
+ }
389
+
390
+ if (null !== $flags) {
391
+ return parent::split($delimiter, $limit, $flags);
392
+ }
393
+
394
+ $str = clone $this;
395
+ $chunks = $this->ignoreCase
396
+ ? preg_split('{'.preg_quote($delimiter).'}iD', $this->string, $limit)
397
+ : explode($delimiter, $this->string, $limit);
398
+
399
+ foreach ($chunks as &$chunk) {
400
+ $str->string = $chunk;
401
+ $chunk = clone $str;
402
+ }
403
+
404
+ return $chunks;
405
+ }
406
+
407
+ public function startsWith($prefix): bool
408
+ {
409
+ if ($prefix instanceof parent) {
410
+ $prefix = $prefix->string;
411
+ } elseif (!\is_string($prefix)) {
412
+ return parent::startsWith($prefix);
413
+ }
414
+
415
+ return '' !== $prefix && 0 === ($this->ignoreCase ? strncasecmp($this->string, $prefix, \strlen($prefix)) : strncmp($this->string, $prefix, \strlen($prefix)));
416
+ }
417
+
418
+ public function title(bool $allWords = false): parent
419
+ {
420
+ $str = clone $this;
421
+ $str->string = $allWords ? ucwords($str->string) : ucfirst($str->string);
422
+
423
+ return $str;
424
+ }
425
+
426
+ public function toUnicodeString(string $fromEncoding = null): UnicodeString
427
+ {
428
+ return new UnicodeString($this->toCodePointString($fromEncoding)->string);
429
+ }
430
+
431
+ public function toCodePointString(string $fromEncoding = null): CodePointString
432
+ {
433
+ $u = new CodePointString();
434
+
435
+ if (\in_array($fromEncoding, [null, 'utf8', 'utf-8', 'UTF8', 'UTF-8'], true) && preg_match('//u', $this->string)) {
436
+ $u->string = $this->string;
437
+
438
+ return $u;
439
+ }
440
+
441
+ set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); });
442
+
443
+ try {
444
+ try {
445
+ $validEncoding = false !== mb_detect_encoding($this->string, $fromEncoding ?? 'Windows-1252', true);
446
+ } catch (InvalidArgumentException $e) {
447
+ if (!\function_exists('iconv')) {
448
+ throw $e;
449
+ }
450
+
451
+ $u->string = iconv($fromEncoding ?? 'Windows-1252', 'UTF-8', $this->string);
452
+
453
+ return $u;
454
+ }
455
+ } finally {
456
+ restore_error_handler();
457
+ }
458
+
459
+ if (!$validEncoding) {
460
+ throw new InvalidArgumentException(sprintf('Invalid "%s" string.', $fromEncoding ?? 'Windows-1252'));
461
+ }
462
+
463
+ $u->string = mb_convert_encoding($this->string, 'UTF-8', $fromEncoding ?? 'Windows-1252');
464
+
465
+ return $u;
466
+ }
467
+
468
+ public function trim(string $chars = " \t\n\r\0\x0B\x0C"): parent
469
+ {
470
+ $str = clone $this;
471
+ $str->string = trim($str->string, $chars);
472
+
473
+ return $str;
474
+ }
475
+
476
+ public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C"): parent
477
+ {
478
+ $str = clone $this;
479
+ $str->string = rtrim($str->string, $chars);
480
+
481
+ return $str;
482
+ }
483
+
484
+ public function trimStart(string $chars = " \t\n\r\0\x0B\x0C"): parent
485
+ {
486
+ $str = clone $this;
487
+ $str->string = ltrim($str->string, $chars);
488
+
489
+ return $str;
490
+ }
491
+
492
+ public function upper(): parent
493
+ {
494
+ $str = clone $this;
495
+ $str->string = strtoupper($str->string);
496
+
497
+ return $str;
498
+ }
499
+
500
+ public function width(bool $ignoreAnsiDecoration = true): int
501
+ {
502
+ $string = preg_match('//u', $this->string) ? $this->string : preg_replace('/[\x80-\xFF]/', '?', $this->string);
503
+
504
+ return (new CodePointString($string))->width($ignoreAnsiDecoration);
505
+ }
506
+ }
vendor/browscap-php/symfony/string/CodePointString.php ADDED
@@ -0,0 +1,270 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of the Symfony package.
5
+ *
6
+ * (c) Fabien Potencier <fabien@symfony.com>
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ namespace Symfony\Component\String;
13
+
14
+ use Symfony\Component\String\Exception\ExceptionInterface;
15
+ use Symfony\Component\String\Exception\InvalidArgumentException;
16
+
17
+ /**
18
+ * Represents a string of Unicode code points encoded as UTF-8.
19
+ *
20
+ * @author Nicolas Grekas <p@tchwork.com>
21
+ * @author Hugo Hamon <hugohamon@neuf.fr>
22
+ *
23
+ * @throws ExceptionInterface
24
+ */
25
+ class CodePointString extends AbstractUnicodeString
26
+ {
27
+ public function __construct(string $string = '')
28
+ {
29
+ if ('' !== $string && !preg_match('//u', $string)) {
30
+ throw new InvalidArgumentException('Invalid UTF-8 string.');
31
+ }
32
+
33
+ $this->string = $string;
34
+ }
35
+
36
+ public function append(string ...$suffix): AbstractString
37
+ {
38
+ $str = clone $this;
39
+ $str->string .= 1 >= \count($suffix) ? ($suffix[0] ?? '') : implode('', $suffix);
40
+
41
+ if (!preg_match('//u', $str->string)) {
42
+ throw new InvalidArgumentException('Invalid UTF-8 string.');
43
+ }
44
+
45
+ return $str;
46
+ }
47
+
48
+ public function chunk(int $length = 1): array
49
+ {
50
+ if (1 > $length) {
51
+ throw new InvalidArgumentException('The chunk length must be greater than zero.');
52
+ }
53
+
54
+ if ('' === $this->string) {
55
+ return [];
56
+ }
57
+
58
+ $rx = '/(';
59
+ while (65535 < $length) {
60
+ $rx .= '.{65535}';
61
+ $length -= 65535;
62
+ }
63
+ $rx .= '.{'.$length.'})/us';
64
+
65
+ $str = clone $this;
66
+ $chunks = [];
67
+
68
+ foreach (preg_split($rx, $this->string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY) as $chunk) {
69
+ $str->string = $chunk;
70
+ $chunks[] = clone $str;
71
+ }
72
+
73
+ return $chunks;
74
+ }
75
+
76
+ public function codePointsAt(int $offset): array
77
+ {
78
+ $str = $offset ? $this->slice($offset, 1) : $this;
79
+
80
+ return '' === $str->string ? [] : [mb_ord($str->string, 'UTF-8')];
81
+ }
82
+
83
+ public function endsWith($suffix): bool
84
+ {
85
+ if ($suffix instanceof AbstractString) {
86
+ $suffix = $suffix->string;
87
+ } elseif (\is_array($suffix) || $suffix instanceof \Traversable) {
88
+ return parent::endsWith($suffix);
89
+ } else {
90
+ $suffix = (string) $suffix;
91
+ }
92
+
93
+ if ('' === $suffix || !preg_match('//u', $suffix)) {
94
+ return false;
95
+ }
96
+
97
+ if ($this->ignoreCase) {
98
+ return preg_match('{'.preg_quote($suffix).'$}iuD', $this->string);
99
+ }
100
+
101
+ return \strlen($this->string) >= \strlen($suffix) && 0 === substr_compare($this->string, $suffix, -\strlen($suffix));
102
+ }
103
+
104
+ public function equalsTo($string): bool
105
+ {
106
+ if ($string instanceof AbstractString) {
107
+ $string = $string->string;
108
+ } elseif (\is_array($string) || $string instanceof \Traversable) {
109
+ return parent::equalsTo($string);
110
+ } else {
111
+ $string = (string) $string;
112
+ }
113
+
114
+ if ('' !== $string && $this->ignoreCase) {
115
+ return \strlen($string) === \strlen($this->string) && 0 === mb_stripos($this->string, $string, 0, 'UTF-8');
116
+ }
117
+
118
+ return $string === $this->string;
119
+ }
120
+
121
+ public function indexOf($needle, int $offset = 0): ?int
122
+ {
123
+ if ($needle instanceof AbstractString) {
124
+ $needle = $needle->string;
125
+ } elseif (\is_array($needle) || $needle instanceof \Traversable) {
126
+ return parent::indexOf($needle, $offset);
127
+ } else {
128
+ $needle = (string) $needle;
129
+ }
130
+
131
+ if ('' === $needle) {
132
+ return null;
133
+ }
134
+
135
+ $i = $this->ignoreCase ? mb_stripos($this->string, $needle, $offset, 'UTF-8') : mb_strpos($this->string, $needle, $offset, 'UTF-8');
136
+
137
+ return false === $i ? null : $i;
138
+ }
139
+
140
+ public function indexOfLast($needle, int $offset = 0): ?int
141
+ {
142
+ if ($needle instanceof AbstractString) {
143
+ $needle = $needle->string;
144
+ } elseif (\is_array($needle) || $needle instanceof \Traversable) {
145
+ return parent::indexOfLast($needle, $offset);
146
+ } else {
147
+ $needle = (string) $needle;
148
+ }
149
+
150
+ if ('' === $needle) {
151
+ return null;
152
+ }
153
+
154
+ $i = $this->ignoreCase ? mb_strripos($this->string, $needle, $offset, 'UTF-8') : mb_strrpos($this->string, $needle, $offset, 'UTF-8');
155
+
156
+ return false === $i ? null : $i;
157
+ }
158
+
159
+ public function length(): int
160
+ {
161
+ return mb_strlen($this->string, 'UTF-8');
162
+ }
163
+
164
+ public function prepend(string ...$prefix): AbstractString
165
+ {
166
+ $str = clone $this;
167
+ $str->string = (1 >= \count($prefix) ? ($prefix[0] ?? '') : implode('', $prefix)).$this->string;
168
+
169
+ if (!preg_match('//u', $str->string)) {
170
+ throw new InvalidArgumentException('Invalid UTF-8 string.');
171
+ }
172
+
173
+ return $str;
174
+ }
175
+
176
+ public function replace(string $from, string $to): AbstractString
177
+ {
178
+ $str = clone $this;
179
+
180
+ if ('' === $from || !preg_match('//u', $from)) {
181
+ return $str;
182
+ }
183
+
184
+ if ('' !== $to && !preg_match('//u', $to)) {
185
+ throw new InvalidArgumentException('Invalid UTF-8 string.');
186
+ }
187
+
188
+ if ($this->ignoreCase) {
189
+ $str->string = implode($to, preg_split('{'.preg_quote($from).'}iuD', $this->string));
190
+ } else {
191
+ $str->string = str_replace($from, $to, $this->string);
192
+ }
193
+
194
+ return $str;
195
+ }
196
+
197
+ public function slice(int $start = 0, int $length = null): AbstractString
198
+ {
199
+ $str = clone $this;
200
+ $str->string = mb_substr($this->string, $start, $length, 'UTF-8');
201
+
202
+ return $str;
203
+ }
204
+
205
+ public function splice(string $replacement, int $start = 0, int $length = null): AbstractString
206
+ {
207
+ if (!preg_match('//u', $replacement)) {
208
+ throw new InvalidArgumentException('Invalid UTF-8 string.');
209
+ }
210
+
211
+ $str = clone $this;
212
+ $start = $start ? \strlen(mb_substr($this->string, 0, $start, 'UTF-8')) : 0;
213
+ $length = $length ? \strlen(mb_substr($this->string, $start, $length, 'UTF-8')) : $length;
214
+ $str->string = substr_replace($this->string, $replacement, $start, $length ?? \PHP_INT_MAX);
215
+
216
+ return $str;
217
+ }
218
+
219
+ public function split(string $delimiter, int $limit = null, int $flags = null): array
220
+ {
221
+ if (1 > $limit = $limit ?? \PHP_INT_MAX) {
222
+ throw new InvalidArgumentException('Split limit must be a positive integer.');
223
+ }
224
+
225
+ if ('' === $delimiter) {
226
+ throw new InvalidArgumentException('Split delimiter is empty.');
227
+ }
228
+
229
+ if (null !== $flags) {
230
+ return parent::split($delimiter.'u', $limit, $flags);
231
+ }
232
+
233
+ if (!preg_match('//u', $delimiter)) {
234
+ throw new InvalidArgumentException('Split delimiter is not a valid UTF-8 string.');
235
+ }
236
+
237
+ $str = clone $this;
238
+ $chunks = $this->ignoreCase
239
+ ? preg_split('{'.preg_quote($delimiter).'}iuD', $this->string, $limit)
240
+ : explode($delimiter, $this->string, $limit);
241
+
242
+ foreach ($chunks as &$chunk) {
243
+ $str->string = $chunk;
244
+ $chunk = clone $str;
245
+ }
246
+
247
+ return $chunks;
248
+ }
249
+
250
+ public function startsWith($prefix): bool
251
+ {
252
+ if ($prefix instanceof AbstractString) {
253
+ $prefix = $prefix->string;
254
+ } elseif (\is_array($prefix) || $prefix instanceof \Traversable) {
255
+ return parent::startsWith($prefix);
256
+ } else {
257
+ $prefix = (string) $prefix;
258
+ }
259
+
260
+ if ('' === $prefix || !preg_match('//u', $prefix)) {
261
+ return false;
262
+ }
263
+
264
+ if ($this->ignoreCase) {
265
+ return 0 === mb_stripos($this->string, $prefix, 0, 'UTF-8');
266
+ }
267
+
268
+ return 0 === strncmp($this->string, $prefix, \strlen($prefix));
269
+ }
270
+ }
vendor/browscap-php/symfony/string/Exception/ExceptionInterface.php ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of the Symfony package.
5
+ *
6
+ * (c) Fabien Potencier <fabien@symfony.com>
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ namespace Symfony\Component\String\Exception;
13
+
14
+ interface ExceptionInterface extends \Throwable
15
+ {
16
+ }
vendor/browscap-php/symfony/string/Exception/InvalidArgumentException.php ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of the Symfony package.
5
+ *
6
+ * (c) Fabien Potencier <fabien@symfony.com>
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ namespace Symfony\Component\String\Exception;
13
+
14
+ class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
15
+ {
16
+ }
vendor/browscap-php/symfony/string/Exception/RuntimeException.php ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of the Symfony package.
5
+ *
6
+ * (c) Fabien Potencier <fabien@symfony.com>
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ namespace Symfony\Component\String\Exception;
13
+
14
+ class RuntimeException extends \RuntimeException implements ExceptionInterface
15
+ {
16
+ }
vendor/browscap-php/symfony/string/Inflector/EnglishInflector.php ADDED
@@ -0,0 +1,511 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of the Symfony package.
5
+ *
6
+ * (c) Fabien Potencier <fabien@symfony.com>
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ namespace Symfony\Component\String\Inflector;
13
+
14
+ final class EnglishInflector implements InflectorInterface
15
+ {
16
+ /**
17
+ * Map English plural to singular suffixes.
18
+ *
19
+ * @see http://english-zone.com/spelling/plurals.html
20
+ */
21
+ private const PLURAL_MAP = [
22
+ // First entry: plural suffix, reversed
23
+ // Second entry: length of plural suffix
24
+ // Third entry: Whether the suffix may succeed a vocal
25
+ // Fourth entry: Whether the suffix may succeed a consonant
26
+ // Fifth entry: singular suffix, normal
27
+
28
+ // bacteria (bacterium), criteria (criterion), phenomena (phenomenon)
29
+ ['a', 1, true, true, ['on', 'um']],
30
+
31
+ // nebulae (nebula)
32
+ ['ea', 2, true, true, 'a'],
33
+
34
+ // services (service)
35
+ ['secivres', 8, true, true, 'service'],
36
+
37
+ // mice (mouse), lice (louse)
38
+ ['eci', 3, false, true, 'ouse'],
39
+
40
+ // geese (goose)
41
+ ['esee', 4, false, true, 'oose'],
42
+
43
+ // fungi (fungus), alumni (alumnus), syllabi (syllabus), radii (radius)
44
+ ['i', 1, true, true, 'us'],
45
+
46
+ // men (man), women (woman)
47
+ ['nem', 3, true, true, 'man'],
48
+
49
+ // children (child)
50
+ ['nerdlihc', 8, true, true, 'child'],
51
+
52
+ // oxen (ox)
53
+ ['nexo', 4, false, false, 'ox'],
54
+
55
+ // indices (index), appendices (appendix), prices (price)
56
+ ['seci', 4, false, true, ['ex', 'ix', 'ice']],
57
+
58
+ // selfies (selfie)
59
+ ['seifles', 7, true, true, 'selfie'],
60
+
61
+ // zombies (zombie)
62
+ ['seibmoz', 7, true, true, 'zombie'],
63
+
64
+ // movies (movie)
65
+ ['seivom', 6, true, true, 'movie'],
66
+
67
+ // conspectuses (conspectus), prospectuses (prospectus)
68
+ ['sesutcep', 8, true, true, 'pectus'],
69
+
70
+ // feet (foot)
71
+ ['teef', 4, true, true, 'foot'],
72
+
73
+ // geese (goose)
74
+ ['eseeg', 5, true, true, 'goose'],
75
+
76
+ // teeth (tooth)
77
+ ['hteet', 5, true, true, 'tooth'],
78
+
79
+ // news (news)
80
+ ['swen', 4, true, true, 'news'],
81
+
82
+ // series (series)
83
+ ['seires', 6, true, true, 'series'],
84
+
85
+ // babies (baby)
86
+ ['sei', 3, false, true, 'y'],
87
+
88
+ // accesses (access), addresses (address), kisses (kiss)
89
+ ['sess', 4, true, false, 'ss'],
90
+
91
+ // analyses (analysis), ellipses (ellipsis), fungi (fungus),
92
+ // neuroses (neurosis), theses (thesis), emphases (emphasis),
93
+ // oases (oasis), crises (crisis), houses (house), bases (base),
94
+ // atlases (atlas)
95
+ ['ses', 3, true, true, ['s', 'se', 'sis']],
96
+
97
+ // objectives (objective), alternative (alternatives)
98
+ ['sevit', 5, true, true, 'tive'],
99
+
100
+ // drives (drive)
101
+ ['sevird', 6, false, true, 'drive'],
102
+
103
+ // lives (life), wives (wife)
104
+ ['sevi', 4, false, true, 'ife'],
105
+
106
+ // moves (move)
107
+ ['sevom', 5, true, true, 'move'],
108
+
109
+ // hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf), caves (cave), staves (staff)
110
+ ['sev', 3, true, true, ['f', 've', 'ff']],
111
+
112
+ // axes (axis), axes (ax), axes (axe)
113
+ ['sexa', 4, false, false, ['ax', 'axe', 'axis']],
114
+
115
+ // indexes (index), matrixes (matrix)
116
+ ['sex', 3, true, false, 'x'],
117
+
118
+ // quizzes (quiz)
119
+ ['sezz', 4, true, false, 'z'],
120
+
121
+ // bureaus (bureau)
122
+ ['suae', 4, false, true, 'eau'],
123
+
124
+ // fees (fee), trees (tree), employees (employee)
125
+ ['see', 3, true, true, 'ee'],
126
+
127
+ // edges (edge)
128
+ ['segd', 4, true, true, 'dge'],
129
+
130
+ // roses (rose), garages (garage), cassettes (cassette),
131
+ // waltzes (waltz), heroes (hero), bushes (bush), arches (arch),
132
+ // shoes (shoe)
133
+ ['se', 2, true, true, ['', 'e']],
134
+
135
+ // tags (tag)
136
+ ['s', 1, true, true, ''],
137
+
138
+ // chateaux (chateau)
139
+ ['xuae', 4, false, true, 'eau'],
140
+
141
+ // people (person)
142
+ ['elpoep', 6, true, true, 'person'],
143
+ ];
144
+
145
+ /**
146
+ * Map English singular to plural suffixes.
147
+ *
148
+ * @see http://english-zone.com/spelling/plurals.html
149
+ */
150
+ private const SINGULAR_MAP = [
151
+ // First entry: singular suffix, reversed
152
+ // Second entry: length of singular suffix
153
+ // Third entry: Whether the suffix may succeed a vocal
154
+ // Fourth entry: Whether the suffix may succeed a consonant
155
+ // Fifth entry: plural suffix, normal
156
+
157
+ // criterion (criteria)
158
+ ['airetirc', 8, false, false, 'criterion'],
159
+
160
+ // nebulae (nebula)
161
+ ['aluben', 6, false, false, 'nebulae'],
162
+
163
+ // children (child)
164
+ ['dlihc', 5, true, true, 'children'],
165
+
166
+ // prices (price)
167
+ ['eci', 3, false, true, 'ices'],
168
+
169
+ // services (service)
170
+ ['ecivres', 7, true, true, 'services'],
171
+
172
+ // lives (life), wives (wife)
173
+ ['efi', 3, false, true, 'ives'],
174
+
175
+ // selfies (selfie)
176
+ ['eifles', 6, true, true, 'selfies'],
177
+
178
+ // movies (movie)
179
+ ['eivom', 5, true, true, 'movies'],
180
+
181
+ // lice (louse)
182
+ ['esuol', 5, false, true, 'lice'],
183
+
184
+ // mice (mouse)
185
+ ['esuom', 5, false, true, 'mice'],
186
+
187
+ // geese (goose)
188
+ ['esoo', 4, false, true, 'eese'],
189
+
190
+ // houses (house), bases (base)
191
+ ['es', 2, true, true, 'ses'],
192
+
193
+ // geese (goose)
194
+ ['esoog', 5, true, true, 'geese'],
195
+
196
+ // caves (cave)
197
+ ['ev', 2, true, true, 'ves'],
198
+
199
+ // drives (drive)
200
+ ['evird', 5, false, true, 'drives'],
201
+
202
+ // objectives (objective), alternative (alternatives)
203
+ ['evit', 4, true, true, 'tives'],
204
+
205
+ // moves (move)
206
+ ['evom', 4, true, true, 'moves'],
207
+
208
+ // staves (staff)
209
+ ['ffats', 5, true, true, 'staves'],
210
+
211
+ // hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf)
212
+ ['ff', 2, true, true, 'ffs'],
213
+
214
+ // hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf)
215
+ ['f', 1, true, true, ['fs', 'ves']],
216
+
217
+ // arches (arch)
218
+ ['hc', 2, true, true, 'ches'],
219
+
220
+ // bushes (bush)
221
+ ['hs', 2, true, true, 'shes'],
222
+
223
+ // teeth (tooth)
224
+ ['htoot', 5, true, true, 'teeth'],
225
+
226
+ // bacteria (bacterium), criteria (criterion), phenomena (phenomenon)
227
+ ['mu', 2, true, true, 'a'],
228
+
229
+ // men (man), women (woman)
230
+ ['nam', 3, true, true, 'men'],
231
+
232
+ // people (person)
233
+ ['nosrep', 6, true, true, ['persons', 'people']],
234
+
235
+ // bacteria (bacterium), criteria (criterion), phenomena (phenomenon)
236
+ ['noi', 3, true, true, 'ions'],
237
+
238
+ // coupon (coupons)
239
+ ['nop', 3, true, true, 'pons'],
240
+
241
+ // seasons (season), treasons (treason), poisons (poison), lessons (lesson)
242
+ ['nos', 3, true, true, 'sons'],
243
+
244
+ // bacteria (bacterium), criteria (criterion), phenomena (phenomenon)
245
+ ['no', 2, true, true, 'a'],
246
+
247
+ // echoes (echo)
248
+ ['ohce', 4, true, true, 'echoes'],
249
+
250
+ // heroes (hero)
251
+ ['oreh', 4, true, true, 'heroes'],
252
+
253
+ // atlases (atlas)
254
+ ['salta', 5, true, true, 'atlases'],
255
+
256
+ // irises (iris)
257
+ ['siri', 4, true, true, 'irises'],
258
+
259
+ // analyses (analysis), ellipses (ellipsis), neuroses (neurosis)
260
+ // theses (thesis), emphases (emphasis), oases (oasis),
261
+ // crises (crisis)
262
+ ['sis', 3, true, true, 'ses'],
263
+
264
+ // accesses (access), addresses (address), kisses (kiss)
265
+ ['ss', 2, true, false, 'sses'],
266
+
267
+ // syllabi (syllabus)
268
+ ['suballys', 8, true, true, 'syllabi'],
269
+
270
+ // buses (bus)
271
+ ['sub', 3, true, true, 'buses'],
272
+
273
+ // circuses (circus)
274
+ ['suc', 3, true, true, 'cuses'],
275
+
276
+ // conspectuses (conspectus), prospectuses (prospectus)
277
+ ['sutcep', 6, true, true, 'pectuses'],
278
+
279
+ // fungi (fungus), alumni (alumnus), syllabi (syllabus), radii (radius)
280
+ ['su', 2, true, true, 'i'],
281
+
282
+ // news (news)
283
+ ['swen', 4, true, true, 'news'],
284
+
285
+ // feet (foot)
286
+ ['toof', 4, true, true, 'feet'],
287
+
288
+ // chateaux (chateau), bureaus (bureau)
289
+ ['uae', 3, false, true, ['eaus', 'eaux']],
290
+
291
+ // oxen (ox)
292
+ ['xo', 2, false, false, 'oxen'],
293
+
294
+ // hoaxes (hoax)
295
+ ['xaoh', 4, true, false, 'hoaxes'],
296
+
297
+ // indices (index)
298
+ ['xedni', 5, false, true, ['indicies', 'indexes']],
299
+
300
+ // boxes (box)
301
+ ['xo', 2, false, true, 'oxes'],
302
+
303
+ // indexes (index), matrixes (matrix)
304
+ ['x', 1, true, false, ['cies', 'xes']],
305
+
306
+ // appendices (appendix)
307
+ ['xi', 2, false, true, 'ices'],
308
+
309
+ // babies (baby)
310
+ ['y', 1, false, true, 'ies'],
311
+
312
+ // quizzes (quiz)
313
+ ['ziuq', 4, true, false, 'quizzes'],
314
+
315
+ // waltzes (waltz)
316
+ ['z', 1, true, true, 'zes'],
317
+ ];
318
+
319
+ /**
320
+ * A list of words which should not be inflected, reversed.
321
+ */
322
+ private const UNINFLECTED = [
323
+ '',
324
+
325
+ // data
326
+ 'atad',
327
+
328
+ // deer
329
+ 'reed',
330
+
331
+ // feedback
332
+ 'kcabdeef',
333
+
334
+ // fish
335
+ 'hsif',
336
+
337
+ // info
338
+ 'ofni',
339
+
340
+ // moose
341
+ 'esoom',
342
+
343
+ // series
344
+ 'seires',
345
+
346
+ // sheep
347
+ 'peehs',
348
+
349
+ // species
350
+ 'seiceps',
351
+ ];
352
+
353
+ /**
354
+ * {@inheritdoc}
355
+ */
356
+ public function singularize(string $plural): array
357
+ {
358
+ $pluralRev = strrev($plural);
359
+ $lowerPluralRev = strtolower($pluralRev);
360
+ $pluralLength = \strlen($lowerPluralRev);
361
+
362
+ // Check if the word is one which is not inflected, return early if so
363
+ if (\in_array($lowerPluralRev, self::UNINFLECTED, true)) {
364
+ return [$plural];
365
+ }
366
+
367
+ // The outer loop iterates over the entries of the plural table
368
+ // The inner loop $j iterates over the characters of the plural suffix
369
+ // in the plural table to compare them with the characters of the actual
370
+ // given plural suffix
371
+ foreach (self::PLURAL_MAP as $map) {
372
+ $suffix = $map[0];
373
+ $suffixLength = $map[1];
374
+ $j = 0;
375
+
376
+ // Compare characters in the plural table and of the suffix of the
377
+ // given plural one by one
378
+ while ($suffix[$j] === $lowerPluralRev[$j]) {
379
+ // Let $j point to the next character
380
+ ++$j;
381
+
382
+ // Successfully compared the last character
383
+ // Add an entry with the singular suffix to the singular array
384
+ if ($j === $suffixLength) {
385
+ // Is there any character preceding the suffix in the plural string?
386
+ if ($j < $pluralLength) {
387
+ $nextIsVocal = false !== strpos('aeiou', $lowerPluralRev[$j]);
388
+
389
+ if (!$map[2] && $nextIsVocal) {
390
+ // suffix may not succeed a vocal but next char is one
391
+ break;
392
+ }
393
+
394
+ if (!$map[3] && !$nextIsVocal) {
395
+ // suffix may not succeed a consonant but next char is one
396
+ break;
397
+ }
398
+ }
399
+
400
+ $newBase = substr($plural, 0, $pluralLength - $suffixLength);
401
+ $newSuffix = $map[4];
402
+
403
+ // Check whether the first character in the plural suffix
404
+ // is uppercased. If yes, uppercase the first character in
405
+ // the singular suffix too
406
+ $firstUpper = ctype_upper($pluralRev[$j - 1]);
407
+
408
+ if (\is_array($newSuffix)) {
409
+ $singulars = [];
410
+
411
+ foreach ($newSuffix as $newSuffixEntry) {
412
+ $singulars[] = $newBase.($firstUpper ? ucfirst($newSuffixEntry) : $newSuffixEntry);
413
+ }
414
+
415
+ return $singulars;
416
+ }
417
+
418
+ return [$newBase.($firstUpper ? ucfirst($newSuffix) : $newSuffix)];
419
+ }
420
+
421
+ // Suffix is longer than word
422
+ if ($j === $pluralLength) {
423
+ break;
424
+ }
425
+ }
426
+ }
427
+
428
+ // Assume that plural and singular is identical
429
+ return [$plural];
430
+ }
431
+
432
+ /**
433
+ * {@inheritdoc}
434
+ */
435
+ public function pluralize(string $singular): array
436
+ {
437
+ $singularRev = strrev($singular);
438
+ $lowerSingularRev = strtolower($singularRev);
439
+ $singularLength = \strlen($lowerSingularRev);
440
+
441
+ // Check if the word is one which is not inflected, return early if so
442
+ if (\in_array($lowerSingularRev, self::UNINFLECTED, true)) {
443
+ return [$singular];
444
+ }
445
+
446
+ // The outer loop iterates over the entries of the singular table
447
+ // The inner loop $j iterates over the characters of the singular suffix
448
+ // in the singular table to compare them with the characters of the actual
449
+ // given singular suffix
450
+ foreach (self::SINGULAR_MAP as $map) {
451
+ $suffix = $map[0];
452
+ $suffixLength = $map[1];
453
+ $j = 0;
454
+
455
+ // Compare characters in the singular table and of the suffix of the
456
+ // given plural one by one
457
+
458
+ while ($suffix[$j] === $lowerSingularRev[$j]) {
459
+ // Let $j point to the next character
460
+ ++$j;
461
+
462
+ // Successfully compared the last character
463
+ // Add an entry with the plural suffix to the plural array
464
+ if ($j === $suffixLength) {
465
+ // Is there any character preceding the suffix in the plural string?
466
+ if ($j < $singularLength) {
467
+ $nextIsVocal = false !== strpos('aeiou', $lowerSingularRev[$j]);
468
+
469
+ if (!$map[2] && $nextIsVocal) {
470
+ // suffix may not succeed a vocal but next char is one
471
+ break;
472
+ }
473
+
474
+ if (!$map[3] && !$nextIsVocal) {
475
+ // suffix may not succeed a consonant but next char is one
476
+ break;
477
+ }
478
+ }
479
+
480
+ $newBase = substr($singular, 0, $singularLength - $suffixLength);
481
+ $newSuffix = $map[4];
482
+
483
+ // Check whether the first character in the singular suffix
484
+ // is uppercased. If yes, uppercase the first character in
485
+ // the singular suffix too
486
+ $firstUpper = ctype_upper($singularRev[$j - 1]);
487
+
488
+ if (\is_array($newSuffix)) {
489
+ $plurals = [];
490
+
491
+ foreach ($newSuffix as $newSuffixEntry) {
492
+ $plurals[] = $newBase.($firstUpper ? ucfirst($newSuffixEntry) : $newSuffixEntry);
493
+ }
494
+
495
+ return $plurals;
496
+ }
497
+
498
+ return [$newBase.($firstUpper ? ucfirst($newSuffix) : $newSuffix)];
499
+ }
500
+
501
+ // Suffix is longer than word
502
+ if ($j === $singularLength) {
503
+ break;
504
+ }
505
+ }
506
+ }
507
+
508
+ // Assume that plural is singular with a trailing `s`
509
+ return [$singular.'s'];
510
+ }
511
+ }
vendor/browscap-php/symfony/string/Inflector/FrenchInflector.php ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of the Symfony package.
5
+ *
6
+ * (c) Fabien Potencier <fabien@symfony.com>
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ namespace Symfony\Component\String\Inflector;
13
+
14
+ /**
15
+ * French inflector.
16
+ *
17
+ * This class does only inflect nouns; not adjectives nor composed words like "soixante-dix".
18
+ */
19
+ final class FrenchInflector implements InflectorInterface
20
+ {
21
+ /**
22
+ * A list of all rules for pluralise.
23
+ *
24
+ * @see https://la-conjugaison.nouvelobs.com/regles/grammaire/le-pluriel-des-noms-121.php
25
+ */
26
+ private const PLURALIZE_REGEXP = [
27
+ // First entry: regexp
28
+ // Second entry: replacement
29
+
30
+ // Words finishing with "s", "x" or "z" are invariables
31
+ // Les mots finissant par "s", "x" ou "z" sont invariables
32
+ ['/(s|x|z)$/i', '\1'],
33
+
34
+ // Words finishing with "eau" are pluralized with a "x"
35
+ // Les mots finissant par "eau" prennent tous un "x" au pluriel
36
+ ['/(eau)$/i', '\1x'],
37
+
38
+ // Words finishing with "au" are pluralized with a "x" excepted "landau"
39
+ // Les mots finissant par "au" prennent un "x" au pluriel sauf "landau"
40
+ ['/^(landau)$/i', '\1s'],
41
+ ['/(au)$/i', '\1x'],
42
+
43
+ // Words finishing with "eu" are pluralized with a "x" excepted "pneu", "bleu", "émeu"
44
+ // Les mots finissant en "eu" prennent un "x" au pluriel sauf "pneu", "bleu", "émeu"
45
+ ['/^(pneu|bleu|émeu)$/i', '\1s'],
46
+ ['/(eu)$/i', '\1x'],
47
+
48
+ // Words finishing with "al" are pluralized with a "aux" excepted
49
+ // Les mots finissant en "al" se terminent en "aux" sauf
50
+ ['/^(bal|carnaval|caracal|chacal|choral|corral|étal|festival|récital|val)$/i', '\1s'],
51
+ ['/al$/i', '\1aux'],
52
+
53
+ // Aspirail, bail, corail, émail, fermail, soupirail, travail, vantail et vitrail font leur pluriel en -aux
54
+ ['/^(aspir|b|cor|ém|ferm|soupir|trav|vant|vitr)ail$/i', '\1aux'],
55
+
56
+ // Bijou, caillou, chou, genou, hibou, joujou et pou qui prennent un x au pluriel
57
+ ['/^(bij|caill|ch|gen|hib|jouj|p)ou$/i', '\1oux'],
58
+
59
+ // Invariable words
60
+ ['/^(cinquante|soixante|mille)$/i', '\1'],
61
+
62
+ // French titles
63
+ ['/^(mon|ma)(sieur|dame|demoiselle|seigneur)$/', 'mes\2s'],
64
+ ['/^(Mon|Ma)(sieur|dame|demoiselle|seigneur)$/', 'Mes\2s'],
65
+ ];
66
+
67
+ /**
68
+ * A list of all rules for singularize.
69
+ */
70
+ private const SINGULARIZE_REGEXP = [
71
+ // First entry: regexp
72
+ // Second entry: replacement
73
+
74
+ // Aspirail, bail, corail, émail, fermail, soupirail, travail, vantail et vitrail font leur pluriel en -aux
75
+ ['/((aspir|b|cor|ém|ferm|soupir|trav|vant|vitr))aux$/i', '\1ail'],
76
+
77
+ // Words finishing with "eau" are pluralized with a "x"
78
+ // Les mots finissant par "eau" prennent tous un "x" au pluriel
79
+ ['/(eau)x$/i', '\1'],
80
+
81
+ // Words finishing with "al" are pluralized with a "aux" expected
82
+ // Les mots finissant en "al" se terminent en "aux" sauf
83
+ ['/(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'],
84
+
85
+ // Words finishing with "au" are pluralized with a "x" excepted "landau"
86
+ // Les mots finissant par "au" prennent un "x" au pluriel sauf "landau"
87
+ ['/(au)x$/i', '\1'],
88
+
89
+ // Words finishing with "eu" are pluralized with a "x" excepted "pneu", "bleu", "émeu"
90
+ // Les mots finissant en "eu" prennent un "x" au pluriel sauf "pneu", "bleu", "émeu"
91
+ ['/(eu)x$/i', '\1'],
92
+
93
+ // Words finishing with "ou" are pluralized with a "s" excepted bijou, caillou, chou, genou, hibou, joujou, pou
94
+ // Les mots finissant par "ou" prennent un "s" sauf bijou, caillou, chou, genou, hibou, joujou, pou
95
+ ['/(bij|caill|ch|gen|hib|jouj|p)oux$/i', '\1ou'],
96
+
97
+ // French titles
98
+ ['/^mes(dame|demoiselle)s$/', 'ma\1'],
99
+ ['/^Mes(dame|demoiselle)s$/', 'Ma\1'],
100
+ ['/^mes(sieur|seigneur)s$/', 'mon\1'],
101
+ ['/^Mes(sieur|seigneur)s$/', 'Mon\1'],
102
+
103
+ //Default rule
104
+ ['/s$/i', ''],
105
+ ];
106
+
107
+ /**
108
+ * A list of words which should not be inflected.
109
+ * This list is only used by singularize.
110
+ */
111
+ 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|sas|secours|sens|souris|succès|talus|tapis|tas|taudis|temps|tiers|univers|velours|verglas|vernis|virus)$/i';
112
+
113
+ /**
114
+ * {@inheritdoc}
115
+ */
116
+ public function singularize(string $plural): array
117
+ {
118
+ if ($this->isInflectedWord($plural)) {
119
+ return [$plural];
120
+ }
121
+
122
+ foreach (self::SINGULARIZE_REGEXP as $rule) {
123
+ [$regexp, $replace] = $rule;
124
+
125
+ if (1 === preg_match($regexp, $plural)) {
126
+ return [preg_replace($regexp, $replace, $plural)];
127
+ }
128
+ }
129
+
130
+ return [$plural];
131
+ }
132
+
133
+ /**
134
+ * {@inheritdoc}
135
+ */
136
+ public function pluralize(string $singular): array
137
+ {
138
+ if ($this->isInflectedWord($singular)) {
139
+ return [$singular];
140
+ }
141
+
142
+ foreach (self::PLURALIZE_REGEXP as $rule) {
143
+ [$regexp, $replace] = $rule;
144
+
145
+ if (1 === preg_match($regexp, $singular)) {
146
+ return [preg_replace($regexp, $replace, $singular)];
147
+ }
148
+ }
149
+
150
+ return [$singular.'s'];
151
+ }
152
+
153
+ private function isInflectedWord(string $word): bool
154
+ {
155
+ return 1 === preg_match(self::UNINFLECTED, $word);
156
+ }
157
+ }
vendor/browscap-php/symfony/string/Inflector/InflectorInterface.php ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of the Symfony package.
5
+ *
6
+ * (c) Fabien Potencier <fabien@symfony.com>
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ namespace Symfony\Component\String\Inflector;
13
+
14
+ interface InflectorInterface
15
+ {
16
+ /**
17
+ * Returns the singular forms of a string.
18
+ *
19
+ * If the method can't determine the form with certainty, several possible singulars are returned.
20
+ *
21
+ * @return string[]
22
+ */
23
+ public function singularize(string $plural): array;
24
+
25
+ /**
26
+ * Returns the plural forms of a string.
27
+ *
28
+ * If the method can't determine the form with certainty, several possible plurals are returned.
29
+ *
30
+ * @return string[]
31
+ */
32
+ public function pluralize(string $singular): array;
33
+ }
vendor/browscap-php/symfony/string/LazyString.php ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of the Symfony package.
5
+ *
6
+ * (c) Fabien Potencier <fabien@symfony.com>
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ namespace Symfony\Component\String;
13
+
14
+ /**
15
+ * A string whose value is computed lazily by a callback.
16
+ *
17
+ * @author Nicolas Grekas <p@tchwork.com>
18
+ */
19
+ class LazyString implements \Stringable, \JsonSerializable
20
+ {
21
+ private $value;
22
+
23
+ /**
24
+ * @param callable|array $callback A callable or a [Closure, method] lazy-callable
25
+ *
26
+ * @return static
27
+ */
28
+ public static function fromCallable($callback, ...$arguments): self
29
+ {
30
+ if (!\is_callable($callback) && !(\is_array($callback) && isset($callback[0]) && $callback[0] instanceof \Closure && 2 >= \count($callback))) {
31
+ 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)));
32
+ }
33
+
34
+ $lazyString = new static();
35
+ $lazyString->value = static function () use (&$callback, &$arguments, &$value): string {
36
+ if (null !== $arguments) {
37
+ if (!\is_callable($callback)) {
38
+ $callback[0] = $callback[0]();
39
+ $callback[1] = $callback[1] ?? '__invoke';
40
+ }
41
+ $value = $callback(...$arguments);
42
+ $callback = self::getPrettyName($callback);
43
+ $arguments = null;
44
+ }
45
+
46
+ return $value ?? '';
47
+ };
48
+
49
+ return $lazyString;
50
+ }
51
+
52
+ /**
53
+ * @param string|int|float|bool|\Stringable $value
54
+ *
55
+ * @return static
56
+ */
57
+ public static function fromStringable($value): self
58
+ {
59
+ if (!self::isStringable($value)) {
60
+ throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be a scalar or a stringable object, "%s" given.', __METHOD__, get_debug_type($value)));
61
+ }
62
+
63
+ if (\is_object($value)) {
64
+ return static::fromCallable([$value, '__toString']);
65
+ }
66
+
67
+ $lazyString = new static();
68
+ $lazyString->value = (string) $value;
69
+
70
+ return $lazyString;
71
+ }
72
+
73
+ /**
74
+ * Tells whether the provided value can be cast to string.
75
+ */
76
+ final public static function isStringable($value): bool
77
+ {
78
+ return \is_string($value) || $value instanceof self || (\is_object($value) ? method_exist