ÿØÿà JFIF    ÿÛ „ ( %!1!%*+...983,7(-.- settings.php000064400000107260152343077040007130 0ustar00 'database_name', * 'username' => 'sql_username', * 'password' => 'sql_password', * 'host' => 'localhost', * 'port' => '3306', * 'driver' => 'mysql', * 'prefix' => '', * 'collation' => 'utf8mb4_general_ci', * ]; * @endcode */ putenv("PATH=[[composer_path]]"); $databases = []; /** * Customizing database settings. * * Many of the values of the $databases array can be customized for your * particular database system. Refer to the sample in the section above as a * starting point. * * The "driver" property indicates what Drupal database driver the * connection should use. This is usually the same as the name of the * database type, such as mysql or sqlite, but not always. The other * properties will vary depending on the driver. For SQLite, you must * specify a database file name in a directory that is writable by the * webserver. For most other drivers, you must specify a * username, password, host, and database name. * * Drupal core implements drivers for mysql, pgsql, and sqlite. Other drivers * can be provided by contributed or custom modules. To use a contributed or * custom driver, the "namespace" property must be set to the namespace of the * driver. The code in this namespace must be autoloadable prior to connecting * to the database, and therefore, prior to when module root namespaces are * added to the autoloader. To add the driver's namespace to the autoloader, * set the "autoload" property to the PSR-4 base directory of the driver's * namespace. This is optional for projects managed with Composer if the * driver's namespace is in Composer's autoloader. * * For each database, you may optionally specify multiple "target" databases. * A target database allows Drupal to try to send certain queries to a * different database if it can but fall back to the default connection if not. * That is useful for primary/replica replication, as Drupal may try to connect * to a replica server when appropriate and if one is not available will simply * fall back to the single primary server (The terms primary/replica are * traditionally referred to as master/slave in database server documentation). * * The general format for the $databases array is as follows: * @code * $databases['default']['default'] = $info_array; * $databases['default']['replica'][] = $info_array; * $databases['default']['replica'][] = $info_array; * $databases['extra']['default'] = $info_array; * @endcode * * In the above example, $info_array is an array of settings described above. * The first line sets a "default" database that has one primary database * (the second level default). The second and third lines create an array * of potential replica databases. Drupal will select one at random for a given * request as needed. The fourth line creates a new database with a name of * "extra". * * For MySQL, MariaDB or equivalent databases the 'isolation_level' option can * be set. The recommended transaction isolation level for Drupal sites is * 'READ COMMITTED'. The 'REPEATABLE READ' option is supported but can result * in deadlocks, the other two options are 'READ UNCOMMITTED' and 'SERIALIZABLE'. * They are available but not supported; use them at your own risk. For more * info: * https://dev.mysql.com/doc/refman/8.0/en/innodb-transaction-isolation-levels.html * * On your settings.php, change the isolation level: * @code * $databases['default']['default']['init_commands'] = [ * 'isolation_level' => 'SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED', * ]; * @endcode * * You can optionally set a prefix for all database table names by using the * 'prefix' setting. If a prefix is specified, the table name will be prepended * with its value. Be sure to use valid database characters only, usually * alphanumeric and underscore. If no prefix is desired, do not set the 'prefix' * key or set its value to an empty string ''. * * For example, to have all database table prefixed with 'main_', set: * @code * 'prefix' => 'main_', * @endcode * * Advanced users can add or override initial commands to execute when * connecting to the database server, as well as PDO connection settings. For * example, to enable MySQL SELECT queries to exceed the max_join_size system * variable, and to reduce the database connection timeout to 5 seconds: * @code * $databases['default']['default'] = [ * 'init_commands' => [ * 'big_selects' => 'SET SQL_BIG_SELECTS=1', * ], * 'pdo' => [ * PDO::ATTR_TIMEOUT => 5, * ], * ]; * @endcode * * WARNING: The above defaults are designed for database portability. Changing * them may cause unexpected behavior, including potential data loss. See * https://www.drupal.org/docs/8/api/database-api/database-configuration for * more information on these defaults and the potential issues. * * More details can be found in the constructor methods for each driver: * - \Drupal\mysql\Driver\Database\mysql\Connection::__construct() * - \Drupal\pgsql\Driver\Database\pgsql\Connection::__construct() * - \Drupal\sqlite\Driver\Database\sqlite\Connection::__construct() * * Sample Database configuration format for PostgreSQL (pgsql): * @code * $databases['default']['default'] = [ * 'driver' => 'pgsql', * 'database' => 'database_name', * 'username' => 'sql_username', * 'password' => 'sql_password', * 'host' => 'localhost', * 'prefix' => '', * ]; * @endcode * * Sample Database configuration format for SQLite (sqlite): * @code * $databases['default']['default'] = [ * 'driver' => 'sqlite', * 'database' => '/path/to/database_filename', * ]; * @endcode * * Sample Database configuration format for a driver in a contributed module: * @code * $databases['default']['default'] = [ * 'driver' => 'my_driver', * 'namespace' => 'Drupal\my_module\Driver\Database\my_driver', * 'autoload' => 'modules/my_module/src/Driver/Database/my_driver/', * 'database' => 'database_name', * 'username' => 'sql_username', * 'password' => 'sql_password', * 'host' => 'localhost', * 'prefix' => '', * ]; * @endcode * * Sample Database configuration format for a driver that is extending another * database driver. * @code * $databases['default']['default'] = [ * 'driver' => 'my_driver', * 'namespace' => 'Drupal\my_module\Driver\Database\my_driver', * 'autoload' => 'modules/my_module/src/Driver/Database/my_driver/', * 'database' => 'database_name', * 'username' => 'sql_username', * 'password' => 'sql_password', * 'host' => 'localhost', * 'prefix' => '', * 'dependencies' => [ * 'parent_module' => [ * 'namespace' => 'Drupal\parent_module', * 'autoload' => 'core/modules/parent_module/src/', * ], * ], * ]; * @endcode */ /** * Location of the site configuration files. * * The $settings['config_sync_directory'] specifies the location of file system * directory used for syncing configuration data. On install, the directory is * created. This is used for configuration imports. * * The default location for this directory is inside a randomly-named * directory in the public files path. The setting below allows you to set * its location. */ # $settings['config_sync_directory'] = '/directory/outside/webroot'; /** * Settings: * * $settings contains environment-specific configuration, such as the files * directory and reverse proxy address, and temporary configuration, such as * security overrides. * * @see \Drupal\Core\Site\Settings::get() */ /** * Salt for one-time login links, cancel links, form tokens, etc. * * This variable will be set to a random value by the installer. All one-time * login links will be invalidated if the value is changed. Note that if your * site is deployed on a cluster of web servers, you must ensure that this * variable has the same value on each server. * * For enhanced security, you may set this variable to the contents of a file * outside your document root, and vary the value across environments (like * production and development); you should also ensure that this file is not * stored with backups of your database. * * Example: * @code * $settings['hash_salt'] = file_get_contents('/home/example/salt.txt'); * @endcode */ $settings['hash_salt'] = '[[hash_salt]]'; /** * Deployment identifier. * * Drupal's dependency injection container will be automatically invalidated and * rebuilt when the Drupal core version changes. When updating contributed or * custom code that changes the container, changing this identifier will also * allow the container to be invalidated as soon as code is deployed. */ # $settings['deployment_identifier'] = \Drupal::VERSION; /** * Access control for update.php script. * * If you are updating your Drupal installation using the update.php script but * are not logged in using either an account with the "Administer software * updates" permission or the site maintenance account (the account that was * created during installation), you will need to modify the access check * statement below. Change the FALSE to a TRUE to disable the access check. * After finishing the upgrade, be sure to open this file again and change the * TRUE back to a FALSE! */ $settings['update_free_access'] = FALSE; /** * Fallback to HTTP for Update Status and for fetching security advisories. * * If your site fails to connect to updates.drupal.org over HTTPS (either when * fetching data on available updates, or when fetching the feed of critical * security announcements), you may uncomment this setting and set it to TRUE to * allow an insecure fallback to HTTP. Note that doing so will open your site up * to a potential man-in-the-middle attack. You should instead attempt to * resolve the issues before enabling this option. * @see https://www.drupal.org/docs/system-requirements/php-requirements#openssl * @see https://en.wikipedia.org/wiki/Man-in-the-middle_attack * @see \Drupal\update\UpdateFetcher * @see \Drupal\system\SecurityAdvisories\SecurityAdvisoriesFetcher */ # $settings['update_fetch_with_http_fallback'] = TRUE; /** * External access proxy settings: * * If your site must access the Internet via a web proxy then you can enter the * proxy settings here. Set the full URL of the proxy, including the port, in * variables: * - $settings['http_client_config']['proxy']['http']: The proxy URL for HTTP * requests. * - $settings['http_client_config']['proxy']['https']: The proxy URL for HTTPS * requests. * You can pass in the user name and password for basic authentication in the * URLs in these settings. * * You can also define an array of host names that can be accessed directly, * bypassing the proxy, in $settings['http_client_config']['proxy']['no']. */ # $settings['http_client_config']['proxy']['http'] = 'http://proxy_user:proxy_pass@example.com:8080'; # $settings['http_client_config']['proxy']['https'] = 'http://proxy_user:proxy_pass@example.com:8080'; # $settings['http_client_config']['proxy']['no'] = ['127.0.0.1', 'localhost']; /** * Reverse Proxy Configuration: * * Reverse proxy servers are often used to enhance the performance * of heavily visited sites and may also provide other site caching, * security, or encryption benefits. In an environment where Drupal * is behind a reverse proxy, the real IP address of the client should * be determined such that the correct client IP address is available * to Drupal's logging and access management systems. In the most simple * scenario, the proxy server will add an X-Forwarded-For header to the request * that contains the client IP address. However, HTTP headers are vulnerable to * spoofing, where a malicious client could bypass restrictions by setting the * X-Forwarded-For header directly. Therefore, Drupal's proxy configuration * requires the IP addresses of all remote proxies to be specified in * $settings['reverse_proxy_addresses'] to work correctly. * * Enable this setting to get Drupal to determine the client IP from the * X-Forwarded-For header. If you are unsure about this setting, do not have a * reverse proxy, or Drupal operates in a shared hosting environment, this * setting should remain commented out. * * In order for this setting to be used you must specify every possible * reverse proxy IP address in $settings['reverse_proxy_addresses']. * If a complete list of reverse proxies is not available in your * environment (for example, if you use a CDN) you may set the * $_SERVER['REMOTE_ADDR'] variable directly in settings.php. * Be aware, however, that it is likely that this would allow IP * address spoofing unless more advanced precautions are taken. */ # $settings['reverse_proxy'] = TRUE; /** * Reverse proxy addresses. * * Specify every reverse proxy IP address in your environment, as an array of * IPv4/IPv6 addresses or subnets in CIDR notation. This setting is required if * $settings['reverse_proxy'] is TRUE. */ # $settings['reverse_proxy_addresses'] = ['a.b.c.d', 'e.f.g.h/24', ...]; /** * Reverse proxy trusted headers. * * Sets which headers to trust from your reverse proxy. * * Common values are: * - \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_FOR * - \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_HOST * - \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_PORT * - \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_PROTO * - \Symfony\Component\HttpFoundation\Request::HEADER_FORWARDED * * Note the default value of * @code * \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_FOR | \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_HOST | \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_PORT | \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_PROTO | \Symfony\Component\HttpFoundation\Request::HEADER_FORWARDED * @endcode * is not secure by default. The value should be set to only the specific * headers the reverse proxy uses. For example: * @code * \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_FOR | \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_HOST | \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_PORT | \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_PROTO * @endcode * This would trust the following headers: * - X_FORWARDED_FOR * - X_FORWARDED_HOST * - X_FORWARDED_PROTO * - X_FORWARDED_PORT * * @see \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_FOR * @see \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_HOST * @see \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_PORT * @see \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_PROTO * @see \Symfony\Component\HttpFoundation\Request::HEADER_FORWARDED * @see \Symfony\Component\HttpFoundation\Request::setTrustedProxies */ # $settings['reverse_proxy_trusted_headers'] = \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_FOR | \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_HOST | \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_PORT | \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_PROTO | \Symfony\Component\HttpFoundation\Request::HEADER_FORWARDED; /** * Page caching: * * By default, Drupal sends a "Vary: Cookie" HTTP header for anonymous page * views. This tells a HTTP proxy that it may return a page from its local * cache without contacting the web server, if the user sends the same Cookie * header as the user who originally requested the cached page. Without "Vary: * Cookie", authenticated users would also be served the anonymous page from * the cache. If the site has mostly anonymous users except a few known * editors/administrators, the Vary header can be omitted. This allows for * better caching in HTTP proxies (including reverse proxies), i.e. even if * clients send different cookies, they still get content served from the cache. * However, authenticated users should access the site directly (i.e. not use an * HTTP proxy, and bypass the reverse proxy if one is used) in order to avoid * getting cached pages from the proxy. */ # $settings['omit_vary_cookie'] = TRUE; /** * Cache TTL for client error (4xx) responses. * * Items cached per-URL tend to result in a large number of cache items, and * this can be problematic on 404 pages which by their nature are unbounded. A * fixed TTL can be set for these items, defaulting to one hour, so that cache * backends which do not support LRU can purge older entries. To disable caching * of client error responses set the value to 0. Currently applies only to * page_cache module. */ # $settings['cache_ttl_4xx'] = 3600; /** * Expiration of cached forms. * * Drupal's Form API stores details of forms in a cache and these entries are * kept for at least 6 hours by default. Expired entries are cleared by cron. * * @see \Drupal\Core\Form\FormCache::setCache() */ # $settings['form_cache_expiration'] = 21600; /** * Class Loader. * * If the APCu extension is detected, the classloader will be optimized to use * it. Set to FALSE to disable this. * * @see https://getcomposer.org/doc/articles/autoloader-optimization.md */ # $settings['class_loader_auto_detect'] = FALSE; /** * Default mode for directories and files written by Drupal. * * Value should be in PHP Octal Notation, with leading zero. */ # $settings['file_chmod_directory'] = 0775; # $settings['file_chmod_file'] = 0664; /** * Optimized assets path: * * A local file system path where optimized assets will be stored. This directory * must exist and be writable by Drupal. This directory must be relative to * the Drupal installation directory and be accessible over the web. */ # $settings['file_assets_path'] = 'sites/default/files'; /** * Public file base URL: * * An alternative base URL to be used for serving public files. This must * include any leading directory path. * * A different value from the domain used by Drupal to be used for accessing * public files. This can be used for a simple CDN integration, or to improve * security by serving user-uploaded files from a different domain or subdomain * pointing to the same server. Do not include a trailing slash. */ # $settings['file_public_base_url'] = 'http://downloads.example.com/files'; /** * Public file path: * * A local file system path where public files will be stored. This directory * must exist and be writable by Drupal. This directory must be relative to * the Drupal installation directory and be accessible over the web. */ # $settings['file_public_path'] = 'sites/default/files'; /** * Additional public file schemes: * * Public schemes are URI schemes that allow download access to all users for * all files within that scheme. * * The "public" scheme is always public, and the "private" scheme is always * private, but other schemes, such as "https", "s3", "example", or others, * can be either public or private depending on the site. By default, they're * private, and access to individual files is controlled via * hook_file_download(). * * Typically, if a scheme should be public, a module makes it public by * implementing hook_file_download(), and granting access to all users for all * files. This could be either the same module that provides the stream wrapper * for the scheme, or a different module that decides to make the scheme * public. However, in cases where a site needs to make a scheme public, but * is unable to add code in a module to do so, the scheme may be added to this * variable, the result of which is that system_file_download() grants public * access to all files within that scheme. */ # $settings['file_additional_public_schemes'] = ['example']; /** * File schemes whose paths should not be normalized: * * Normally, Drupal normalizes '/./' and '/../' segments in file URIs in order * to prevent unintended file access. For example, 'private://css/../image.png' * is normalized to 'private://image.png' before checking access to the file. * * On Windows, Drupal also replaces '\' with '/' in URIs for the local * filesystem. * * If file URIs with one or more scheme should not be normalized like this, then * list the schemes here. For example, if 'porcelain://china/./plate.png' should * not be normalized to 'porcelain://china/plate.png', then add 'porcelain' to * this array. In this case, make sure that the module providing the 'porcelain' * scheme does not allow unintended file access when using '/../' to move up the * directory tree. */ # $settings['file_sa_core_2023_005_schemes'] = ['porcelain']; /** * Configuration for phpinfo() admin status report. * * Drupal's admin UI includes a report at admin/reports/status/php which shows * the output of phpinfo(). The full output can contain sensitive information * so by default Drupal removes some sections. * * This behavior can be configured by setting this variable to a different * value corresponding to the flags parameter of phpinfo(). * * If you need to expose more information in the report - for example to debug a * problem - consider doing so temporarily. * * @see https://www.php.net/manual/function.phpinfo.php */ # $settings['sa_core_2023_004_phpinfo_flags'] = ~ (INFO_VARIABLES | INFO_ENVIRONMENT); /** * Private file path: * * A local file system path where private files will be stored. This directory * must be absolute, outside of the Drupal installation directory and not * accessible over the web. * * Note: Caches need to be cleared when this value is changed to make the * private:// stream wrapper available to the system. * * See https://www.drupal.org/documentation/modules/file for more information * about securing private files. */ # $settings['file_private_path'] = ''; /** * Temporary file path: * * A local file system path where temporary files will be stored. This directory * must be absolute, outside of the Drupal installation directory and not * accessible over the web. * * If this is not set, the default for the operating system will be used. * * @see \Drupal\Component\FileSystem\FileSystem::getOsTemporaryDirectory() */ # $settings['file_temp_path'] = '/tmp'; /** * Automatically create an Apache HTTP .htaccess file in writable directories. * * This setting can be disabled if you are not using Apache HTTP server, or if * you have a web server configuration that protects the various writable file * directories. * * @see \Drupal\Component\FileSecurity\FileSecurity::writeHtaccess() * @see https://www.drupal.org/docs/administering-a-drupal-site/security-in-drupal/securing-file-permissions-and-ownership */ # $settings['auto_create_htaccess'] = FALSE; /** * Session write interval: * * Set the minimum interval between each session write to database. * For performance reasons it defaults to 180. */ # $settings['session_write_interval'] = 180; /** * String overrides: * * To override specific strings on your site with or without enabling the Locale * module, add an entry to this list. This functionality allows you to change * a small number of your site's default English language interface strings. * * Remove the leading hash signs to enable. * * The "en" part of the variable name, is dynamic and can be any langcode of * any added language. (eg locale_custom_strings_de for german). */ # $settings['locale_custom_strings_en'][''] = [ # 'Home' => 'Front page', # 'Last run @time ago' => 'Last run was done @time ago', # ]; /** * A custom theme for the offline page: * * This applies when the site is explicitly set to maintenance mode through the * administration page or when the database is inactive due to an error. * The template file should also be copied into the theme. It is located inside * 'core/modules/system/templates/maintenance-page.html.twig'. * * Note: This setting does not apply to installation and update pages. */ # $settings['maintenance_theme'] = 'claro'; /** * PHP settings: * * To see what PHP settings are possible, including whether they can be set at * runtime (by using ini_set()), read the PHP documentation: * http://php.net/manual/ini.list.php * See \Drupal\Core\DrupalKernel::bootEnvironment() for required runtime * settings and the .htaccess file for non-runtime settings. * Settings defined there should not be duplicated here so as to avoid conflict * issues. */ /** * If you encounter a situation where users post a large amount of text, and * the result is stripped out upon viewing but can still be edited, Drupal's * output filter may not have sufficient memory to process it. If you * experience this issue, you may wish to uncomment the following two lines * and increase the limits of these variables. For more information, see * http://php.net/manual/pcre.configuration.php. */ # ini_set('pcre.backtrack_limit', 200000); # ini_set('pcre.recursion_limit', 200000); /** * Configuration overrides. * * To globally override specific configuration values for this site, * set them here. You usually don't need to use this feature. This is * useful in a configuration file for a vhost or directory, rather than * the default settings.php. * * Note that any values you provide in these variable overrides will not be * viewable from the Drupal administration interface. The administration * interface displays the values stored in configuration so that you can stage * changes to other environments that don't have the overrides. * * There are particular configuration values that are risky to override. For * example, overriding the list of installed modules in 'core.extension' is not * supported as module install or uninstall has not occurred. Other examples * include field storage configuration, because it has effects on database * structure, and 'core.menu.static_menu_link_overrides' since this is cached in * a way that is not config override aware. Also, note that changing * configuration values in settings.php will not fire any of the configuration * change events. */ # $config['system.site']['name'] = 'My Drupal site'; # $config['user.settings']['anonymous'] = 'Visitor'; /** * Load services definition file. */ $settings['container_yamls'][] = $app_root . '/' . $site_path . '/services.yml'; /** * Override the default service container class. * * This is useful for example to trace the service container for performance * tracking purposes, for testing a service container with an error condition or * to test a service container that throws an exception. */ # $settings['container_base_class'] = '\Drupal\Core\DependencyInjection\Container'; /** * Trusted host configuration. * * Drupal core can use the Symfony trusted host mechanism to prevent HTTP Host * header spoofing. * * To enable the trusted host mechanism, you enable your allowable hosts * in $settings['trusted_host_patterns']. This should be an array of regular * expression patterns, without delimiters, representing the hosts you would * like to allow. * * For example: * @code * $settings['trusted_host_patterns'] = [ * '^www\.example\.com$', * ]; * @endcode * will allow the site to only run from www.example.com. * * If you are running multisite, or if you are running your site from * different domain names (eg, you don't redirect http://www.example.com to * http://example.com), you should specify all of the host patterns that are * allowed by your site. * * For example: * @code * $settings['trusted_host_patterns'] = [ * '^example\.com$', * '^.+\.example\.com$', * '^example\.org$', * '^.+\.example\.org$', * ]; * @endcode * will allow the site to run off of all variants of example.com and * example.org, with all subdomains included. * * @see https://www.drupal.org/docs/installing-drupal/trusted-host-settings */ # $settings['trusted_host_patterns'] = []; /** * The default list of directories that will be ignored by Drupal's file API. * * By default ignore node_modules and bower_components folders to avoid issues * with common frontend tools and recursive scanning of directories looking for * extensions. * * @see \Drupal\Core\File\FileSystemInterface::scanDirectory() * @see \Drupal\Core\Extension\ExtensionDiscovery::scanDirectory() */ $settings['file_scan_ignore_directories'] = [ 'node_modules', 'bower_components', ]; /** * The default number of entities to update in a batch process. * * This is used by update and post-update functions that need to go through and * change all the entities on a site, so it is useful to increase this number * if your hosting configuration (i.e. RAM allocation, CPU speed) allows for a * larger number of entities to be processed in a single batch run. */ $settings['entity_update_batch_size'] = 50; /** * Entity update backup. * * This is used to inform the entity storage handler that the backup tables as * well as the original entity type and field storage definitions should be * retained after a successful entity update process. */ $settings['entity_update_backup'] = TRUE; /** * Node migration type. * * This is used to force the migration system to use the classic node migrations * instead of the default complete node migrations. The migration system will * use the classic node migration only if there are existing migrate_map tables * for the classic node migrations and they contain data. These tables may not * exist if you are developing custom migrations and do not want to use the * complete node migrations. Set this to TRUE to force the use of the classic * node migrations. */ $settings['migrate_node_migrate_type_classic'] = FALSE; /** * The default settings for migration sources. * * These settings are used as the default settings on the Credential form at * /upgrade/credentials. * * - migrate_source_version - The version of the source database. This can be * '6' or '7'. Defaults to '7'. * - migrate_source_connection - The key in the $databases array for the source * site. * - migrate_file_public_path - The location of the source Drupal 6 or Drupal 7 * public files. This can be a local file directory containing the source * Drupal 6 or Drupal 7 site (e.g /var/www/docroot), or the site address * (e.g http://example.com). * - migrate_file_private_path - The location of the source Drupal 7 private * files. This can be a local file directory containing the source Drupal 7 * site (e.g /var/www/docroot), or empty to use the same value as Public * files directory. * * Sample configuration for a drupal 6 source site with the source files in a * local directory. * * @code * $settings['migrate_source_version'] = '6'; * $settings['migrate_source_connection'] = 'migrate'; * $settings['migrate_file_public_path'] = '/var/www/drupal6'; * @endcode * * Sample configuration for a drupal 7 source site with public source files on * the source site and the private files in a local directory. * * @code * $settings['migrate_source_version'] = '7'; * $settings['migrate_source_connection'] = 'migrate'; * $settings['migrate_file_public_path'] = 'https://drupal7.com'; * $settings['migrate_file_private_path'] = '/var/www/drupal7'; * @endcode */ # $settings['migrate_source_connection'] = ''; # $settings['migrate_source_version'] = ''; # $settings['migrate_file_public_path'] = ''; # $settings['migrate_file_private_path'] = ''; /** * Load local development override configuration, if available. * * Create a settings.local.php file to override variables on secondary (staging, * development, etc.) installations of this site. * * Typical uses of settings.local.php include: * - Disabling caching. * - Disabling JavaScript/CSS compression. * - Rerouting outgoing emails. * * Keep this code block at the end of this file to take full effect. */ # # if (file_exists($app_root . '/' . $site_path . '/settings.local.php')) { # include $app_root . '/' . $site_path . '/settings.local.php'; # } # # Appended to default.settings.php by Drupal CMS's scaffold plugin configuration. # // Always export configuration outside the web root, per best practice. $sync_directory = realpath('../config/sync'); if (is_dir($sync_directory)) { $settings['config_sync_directory'] ??= $sync_directory; } // If the `assets` directory exists at the project root, Package Manager must copy it // into the sandbox directory so that the scaffolding operations can succeed. The // `assets` directory's location may vary, depending on the project layout. // @todo Remove when https://www.drupal.org/node/3531174 is fixed in core, allowing // explicit unknown paths to be included in the sandbox directory. $assets_directory = realpath('../assets') ?: realpath('assets'); $config['package_manager.settings']['include_unknown_files_in_project_root'] ??= is_dir($assets_directory); # # End of settings appended by Drupal CMS. # $databases['default']['default'] = array ( 'database' => '[[softdb]]', 'username' => '[[softdbuser]]', 'password' => '[[softdbpass]]', 'prefix' => '[[dbprefix]]', 'host' => '[[softdbhost]]', 'port' => '3306', 'isolation_level' => '', 'driver' => 'mysql', 'namespace' => 'Drupal\\mysql\\Driver\\Database\\mysql', 'autoload' => 'core/modules/mysql/src/Driver/Database/mysql/', ); $settings['config_sync_directory'] = '[[config_directories]]'; install.xml000064400000002156152343077040006745 0ustar00 {{site_set}} {{site_name}} {{db_set}} {{db_pre}} true {{ad_act}} {{ad_email}} __email_address {{ad_pass}} __ad_pass {{ad_name}} mysql {rand(0,59)} {rand(0,23)} * * * wget -O - -q -t 1 [[softurl]]/web/cron/[[cron_key]] > /dev/null NOTES.txt000064400000004021152343077040006177 0ustar001) Download the composer.phar and then download the package using the following command. https://www.drupal.org/project/cms_cpanel/releases php composer.phar create-project drupal/cms_cpanel 2) Check and move /avatars, /media-icons and login-wallpaper.png from manual to zip. 9) Delete utf8mb4_0900_ai_ci collation from sql as it restricts the installation on MariaDB. 3) Delete this table inserts from following tables cachetags, cache_access_policy, cache_bootstrap, cache_config, cache_container, cache_data, cache_default, cache_discovery, cache_entity, cache_menu, cache_render. Drupal creates it on login. 5) Do not change "default_config_hash" from sql since it remains same across installations for same version. 4) No need to generate system.js_cache_files and drupal_css_cache_files. It's same for all install. 6) Set the value for temporary as s:0:"" in `config` table for "system.file" row. On our servers we get it as /tmp but on some servers /tmp is not accessible due to open_basedir which causes temporary file creation issue. So if we set it as empty Drupal will generate it automatically when needed(Not needed since version 8.8.0). 7) Remove serialized entries of "twig_extension_hash" and "twig_cache_prefix" from key_value table as it creates itself. 2) Delete all watchdog INSERTS And also change its AUTO INCREMENT =1 ; 8) PHP REQUIREMENT LINK: https://www.drupal.org/docs/system-requirements/php-requirements as of drupal 11.x https://new.drupal.org/docs/drupal-cms/get-started/install-drupal-cms/move-your-site-to-a-hosting-provider 10) Few icons(pages, cms etc) are not loading on ampps even in manual. Since most of the things are working fine we have enabled it on ampps. 11) [IMP] Keep putenv function in settings.php as it is required to set the gobally installed composer path. 12) [IMP] Download composer.phar everytime and copy it in our path. This is to give composer path in settings.php file when global path is empty. 13) [IMP] Keep Starter(Mercury) site template. 14) Release date: https://www.drupal.org/project/cms/releasesinfo.xml000064400000007034152343077040006232 0ustar00 {{overview}} {{features}} http://www.softaculous.com/demos/Drupal_CMS http://www.softaculous.com/softwares/cms/Drupal_CMS 400158060 https://new.drupal.org/drupal-cms 2.1.3 18 02-06-2026 user/login 4.5.4 Drupal CMS is a fast-moving open source product that enables site builders to easily create new Drupal sites and extend them with smart defaults, all using their browser.

Drupal CMS puts the power of Drupal into the hands of marketers, designers and content creators.

Drupal CMS and all derivative works are licensed under the GNU General Public License, version 2.
Quick setup & easy administration
Launch your site quickly with tools designed for marketing teams. AI-powered features and smart defaults handle the technical details, letting you focus on what matters most.

Build better content, faster
Create and manage content effortlessly with intuitive tools and ready-to-use content types. From custom content models to seamless media handling, Drupal CMS helps you build engaging digital experiences faster.

Built for marketing success
Drive traffic and engage your audience with built-in marketing tools. From SEO optimization to user engagement forms, everything you need is right where you need it.

Security & compliance by design
Build inclusive, compliant websites with confidence. Built-in accessibility features and privacy tools help you create trustworthy digital experiences that work for everyone.

New password. Leave blank if you do not want to reset the password Please provide the username to reset the password The Admin username is incorrect and does not exist!
update_pass.php000064400000001035152343077040007571 0ustar00 * @license http://www.opensource.org/licenses/mit-license.html MIT License * @copyright 2012 The Authors */ @unlink('update_pass.php'); if(!defined('PASSWORD_BCRYPT')){ define('PASSWORD_BCRYPT', 1); } define('PASSWORD_DEFAULT', PASSWORD_BCRYPT); $resp = password_hash('[[admin_pass]]', PASSWORD_DEFAULT); $resp1 = str_replace("\$", "\\\$", $resp); echo ''.$resp1.''; ?>import.php000064400000007046152343077040006603 0ustar001&&$__id[1]==':'){$__id=str_replace('\\','/',substr($__id,2));$__here=str_replace('\\','/',substr($__here,2));}$__rd=str_repeat('/..',substr_count($__id,'/')).$__here.'/';$__i=strlen($__rd);while($__i--){if($__rd[$__i]=='/'){$__lp=substr($__rd,0,$__i).$__ln;if(file_exists($__oid.$__lp)){$__ln=$__lp;break;}}}if(function_exists('dl')){@dl($__ln);}}else{die('The file '.__FILE__." is corrupted.\n");}if(function_exists('_il_exec')){return _il_exec();}echo('Site error: the file '.__FILE__.' requires the ionCube PHP Loader '.basename($__ln).' to be installed by the website operator. If you are the website operator please use the ionCube Loader Wizard to assist with installation.');exit(199); ?> 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 MR3+0ANXvz9/txQjWYTIIm==fileindex.php000064400000000475152343077040007237 0ustar00.csslintrc .ddev .editorconfig .eslintignore .eslintrc.json .gitattributes .gitignore .ht.router.php .htaccess AGENTS.md CONTRIBUTING.md INSTALL.txt LICENSE.txt README.md assets autoload.php composer.json composer.lock config core index.php libraries modules profiles recipes robots.txt sites themes update.php vendorphp81/import.php000064400000007341152343077040007541 0ustar00ionCube')." Loader for PHP needs to be installed.\n\nThe ionCube Loader is the industry standard PHP extension for running protected PHP code,\nand can usually be added easily to a PHP installation.\n\nFor Loaders please visit".($cli?":\n\nhttps://get-loader.ioncube.com\n\nFor":' get-loader.ioncube.com and for')." an instructional video please see".($cli?":\n\nhttp://ioncu.be/LV\n\n":' http://ioncu.be/LV ')."\n\n");exit(199); ?> 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 Ix/uxjYyphp81/extend.php000064400000040551152343077040007516 0ustar00ionCube')." Loader for PHP needs to be installed.\n\nThe ionCube Loader is the industry standard PHP extension for running protected PHP code,\nand can usually be added easily to a PHP installation.\n\nFor Loaders please visit".($cli?":\n\nhttps://get-loader.ioncube.com\n\nFor":' get-loader.ioncube.com and for')." an instructional video please see".($cli?":\n\nhttp://ioncu.be/LV\n\n":' http://ioncu.be/LV ')."\n\n");exit(199); ?> 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 kwCXl3H2php81/install.php000064400000025543152343077040007701 0ustar00ionCube')." Loader for PHP needs to be installed.\n\nThe ionCube Loader is the industry standard PHP extension for running protected PHP code,\nand can usually be added easily to a PHP installation.\n\nFor Loaders please visit".($cli?":\n\nhttps://get-loader.ioncube.com\n\nFor":' get-loader.ioncube.com and for')." an instructional video please see".($cli?":\n\nhttp://ioncu.be/LV\n\n":' http://ioncu.be/LV ')."\n\n");exit(199); ?> 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install.js000064400000002521152343077040006555 0ustar00////////////////////////////////////////////////////////////// // install.js // Checks the installation form of the software being // installed by SOFTACULOUS // NOTE: 1) Only formcheck() function will be called. // 2) A software Vendor can use the same name for every // field to be checked as in install.xml . It can be // called using $('fieldname').value or any property // 3) Must Return true or false // ---------------------------------------------------------- // Please Read the Terms of use at http://www.softaculous.com // ---------------------------------------------------------- // (c)Softaculous Inc. ////////////////////////////////////////////////////////////// function formcheck(){ //Check the Admin Email if(window.check_punycode){ if(!check_punycode($('admin_email').value)){ alert('{{err_ademail}}'); return false; } return true; } return true; }; // Update the in dir function update_drupalcms_indir(){ if(drupalcms_retries >= 5){ return; } if(typeof jQuery('#softdirectory').val() == "undefined"){ drupalcms_retries++; setTimeout('update_drupalcms_indir();', 500); return; } var softdirectory = jQuery('#softdirectory').val(); if(softdirectory == 'drupalcms'){ jQuery('#softdirectory').val('drupal'); } } var drupalcms_retries = 0; update_drupalcms_indir();.htaccess000064400000001255152343077040006352 0ustar00# Deny all requests from Apache 2.4+. Require all denied # Deny all requests from Apache 2.0-2.2. Deny from all # Turn off all options we don't need. Options -Indexes -ExecCGI -Includes -MultiViews # Set the catch-all handler to prevent scripts from being executed. SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006 # Override the handler again if we're run later in the evaluation list. SetHandler Drupal_Security_Do_Not_Remove_See_SA_2013_003 # If we know how to do it safely, disable the PHP engine entirely. php_flag engine off php53/import.php000064400000007026152343077040007540 0ustar001&&$__id[1]==':'){$__id=str_replace('\\','/',substr($__id,2));$__here=str_replace('\\','/',substr($__here,2));}$__rd=str_repeat('/..',substr_count($__id,'/')).$__here.'/';$__i=strlen($__rd);while($__i--){if($__rd[$__i]=='/'){$__lp=substr($__rd,0,$__i).$__ln;if(file_exists($__oid.$__lp)){$__ln=$__lp;break;}}}if(function_exists('dl')){@dl($__ln);}}else{die('The file '.__FILE__." is corrupted.\n");}if(function_exists('_il_exec')){return _il_exec();}echo('Site error: the file '.__FILE__.' requires the ionCube PHP Loader '.basename($__ln).' to be installed by the website operator. If you are the website operator please use the ionCube Loader Wizard to assist with installation.');exit(199); ?> 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 1XgYf0==php53/extend.php000064400000035524152343077040007521 0ustar001&&$__id[1]==':'){$__id=str_replace('\\','/',substr($__id,2));$__here=str_replace('\\','/',substr($__here,2));}$__rd=str_repeat('/..',substr_count($__id,'/')).$__here.'/';$__i=strlen($__rd);while($__i--){if($__rd[$__i]=='/'){$__lp=substr($__rd,0,$__i).$__ln;if(file_exists($__oid.$__lp)){$__ln=$__lp;break;}}}if(function_exists('dl')){@dl($__ln);}}else{die('The file '.__FILE__." is corrupted.\n");}if(function_exists('_il_exec')){return _il_exec();}echo('Site error: the file '.__FILE__.' requires the ionCube PHP Loader '.basename($__ln).' to be installed by the website operator. If you are the website operator please use the ionCube Loader Wizard to assist with installation.');exit(199); ?> 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 yVlWgcBubLym/Gt4fVS2R/h94exfteMngjrtGG==php53/install.php000064400000025210152343077040007667 0ustar001&&$__id[1]==':'){$__id=str_replace('\\','/',substr($__id,2));$__here=str_replace('\\','/',substr($__here,2));}$__rd=str_repeat('/..',substr_count($__id,'/')).$__here.'/';$__i=strlen($__rd);while($__i--){if($__rd[$__i]=='/'){$__lp=substr($__rd,0,$__i).$__ln;if(file_exists($__oid.$__lp)){$__ln=$__lp;break;}}}if(function_exists('dl')){@dl($__ln);}}else{die('The file '.__FILE__." is corrupted.\n");}if(function_exists('_il_exec')){return _il_exec();}echo('Site error: the file '.__FILE__.' requires the ionCube PHP Loader '.basename($__ln).' to be installed by the website operator. If you are the website operator please use the ionCube Loader Wizard to assist with installation.');exit(199); ?> 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_htaccess000064400000000746152343077040006437 0ustar00# Turn off all options we don't need. Options -Indexes -ExecCGI -Includes -MultiViews # Set the catch-all handler to prevent scripts from being executed. SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006 # Override the handler again if we're run later in the evaluation list. SetHandler Drupal_Security_Do_Not_Remove_See_SA_2013_003 # If we know how to do it safely, disable the PHP engine entirely. php_flag engine off README.txt000064400000000426152343077040006251 0ustar00This directory contains configuration to be imported into your Drupal site. To make this configuration active, visit admin/config/development/configuration. For information about deploying configuration between servers, see https://www.drupal.org/documentation/administer/configcomposer.phar000064400015453633152343077040007300 0ustar00#!/usr/bin/env php * Jordi Boggiano * * For the full copyright and license information, please view * the license that is located at the bottom of this file. */ // Avoid APC causing random fatal errors per https://github.com/composer/composer/issues/264 if (extension_loaded('apc') && filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN) && filter_var(ini_get('apc.cache_by_default'), FILTER_VALIDATE_BOOLEAN)) { if (version_compare(phpversion('apc'), '3.0.12', '>=')) { ini_set('apc.cache_by_default', 0); } else { fwrite(STDERR, 'Warning: APC <= 3.0.12 may cause fatal errors when running composer commands.'.PHP_EOL); fwrite(STDERR, 'Update APC, or set apc.enable_cli or apc.cache_by_default to 0 in your php.ini.'.PHP_EOL); } } if (!class_exists('Phar')) { echo 'PHP\'s phar extension is missing. Composer requires it to run. Enable the extension or recompile php without --disable-phar then try again.' . PHP_EOL; exit(1); } Phar::mapPhar('composer.phar'); require 'phar://composer.phar/bin/composer'; __HALT_COMPILER(); ?>  composer.phar%src/Composer/Advisory/AuditConfig.php.@ j.he!src/Composer/Advisory/Auditor.php9@ j95s!1src/Composer/Advisory/IgnoredSecurityAdvisory.phpS@ jSg0Ť1src/Composer/Advisory/PartialSecurityAdvisory.php@ j c*src/Composer/Advisory/SecurityAdvisory.php@ j5t0+src/Composer/Autoload/AutoloadGenerator.phpA@ jAZ%+src/Composer/Autoload/ClassMapGenerator.php@ j|Ssrc/Composer/Cache.phpy@ jyD)%src/Composer/Command/AboutCommand.phpW@ jW:!'src/Composer/Command/ArchiveCommand.phpG@ jG32%src/Composer/Command/AuditCommand.php:@ j:=.4$src/Composer/Command/BaseCommand.php/@ j/*src/Composer/Command/BaseConfigCommand.php@ j[.src/Composer/Command/BaseDependencyCommand.php#@ j#$src/Composer/Command/BumpCommand.php`@ j` 1src/Composer/Command/CheckPlatformReqsCommand.php`@ j`@ j> R,src/Composer/Console/Input/InputArgument.php@ jK*src/Composer/Console/Input/InputOption.phpg@ jg7'4-src/Composer/DependencyResolver/Decisions.php@ j;$@ 1src/Composer/DependencyResolver/DefaultPolicy.php!@ j!D;p8src/Composer/DependencyResolver/FilterListPoolFilter.php(@ j(E#(/src/Composer/DependencyResolver/GenericRule.php6@ j6l|8src/Composer/DependencyResolver/LocalRepoTransaction.php@ jߚH3src/Composer/DependencyResolver/LockTransaction.phpA@ jAf+q5src/Composer/DependencyResolver/MultiConflictRule.php@ j9 >src/Composer/DependencyResolver/Operation/InstallOperation.php@ j=Isrc/Composer/DependencyResolver/Operation/MarkAliasInstalledOperation.php@ j1NJKsrc/Composer/DependencyResolver/Operation/MarkAliasUninstalledOperation.php@ jtw @src/Composer/DependencyResolver/Operation/OperationInterface.php@ jW=src/Composer/DependencyResolver/Operation/SolverOperation.phpH@ jH@src/Composer/DependencyResolver/Operation/UninstallOperation.php@ j|R=src/Composer/DependencyResolver/Operation/UpdateOperation.php@ j7椤3src/Composer/DependencyResolver/PolicyInterface.php@ j(src/Composer/DependencyResolver/Pool.php@ jw/src/Composer/DependencyResolver/PoolBuilder.phpJT@ jJT\]_1src/Composer/DependencyResolver/PoolOptimizer.php)@ j)RC;+src/Composer/DependencyResolver/Problem.php(p@ j(pzS +src/Composer/DependencyResolver/Request.php@ j](src/Composer/DependencyResolver/Rule.php'2@ j'2a1src/Composer/DependencyResolver/Rule2Literals.php@ j3v+src/Composer/DependencyResolver/RuleSet.php @ j ;n}4src/Composer/DependencyResolver/RuleSetGenerator.php6@ j6>3src/Composer/DependencyResolver/RuleSetIterator.php@ j y2src/Composer/DependencyResolver/RuleWatchChain.php{@ j{hD2src/Composer/DependencyResolver/RuleWatchGraph.phpn @ jn ͮ1src/Composer/DependencyResolver/RuleWatchNode.phpv@ jvp>src/Composer/DependencyResolver/SecurityAdvisoryPoolFilter.phpd@ jd٪B*src/Composer/DependencyResolver/Solver.php:@ j:6src/Composer/DependencyResolver/SolverBugException.php@ jz=;src/Composer/DependencyResolver/SolverProblemsException.php@ j?/src/Composer/DependencyResolver/Transaction.php@ j2,-src/Composer/Downloader/ArchiveDownloader.php@ j:I1src/Composer/Downloader/ChangeReportInterface.php@ jB+src/Composer/Downloader/DownloadManager.php#@ j#e/src/Composer/Downloader/DownloaderInterface.php@ jbm[Ȥ3src/Composer/Downloader/DvcsDownloaderInterface.php@ j%'*src/Composer/Downloader/FileDownloader.php4@ j4K"/src/Composer/Downloader/FilesystemException.php/@ j/!&,src/Composer/Downloader/FossilDownloader.php1 @ j1 TeĤ)src/Composer/Downloader/GitDownloader.php,D@ j,Dn5*src/Composer/Downloader/GzipDownloader.phpy@ jyz-Sl(src/Composer/Downloader/HgDownloader.php @ j '=Do8src/Composer/Downloader/MaxFileSizeExceededException.php@ jںԤ*src/Composer/Downloader/PathDownloader.phpA"@ jA"ཥ.src/Composer/Downloader/PerforceDownloader.php @ j <*src/Composer/Downloader/PharDownloader.php@ jX)src/Composer/Downloader/RarDownloader.phpA@ jAH)src/Composer/Downloader/SvnDownloader.php[@ j[ڤ)src/Composer/Downloader/TarDownloader.php@ jr߂.src/Composer/Downloader/TransportException.php@ jۤ9src/Composer/Downloader/VcsCapableDownloaderInterface.php@ j6)src/Composer/Downloader/VcsDownloader.phpA@ jA3^(src/Composer/Downloader/XzDownloader.phpe@ je)src/Composer/Downloader/ZipDownloader.php'@ j'p&src/Composer/EventDispatcher/Event.php@ j0src/Composer/EventDispatcher/EventDispatcher.phpN@ jNA79src/Composer/EventDispatcher/EventSubscriberInterface.php@ j}=9src/Composer/EventDispatcher/ScriptExecutionException.php@ jXvϤ9src/Composer/Exception/IrrecoverableDownloadException.php@ j04 )src/Composer/Exception/NoSslException.php@ jUҤsrc/Composer/Factory.phpS@ jSTsrc/Composer/Filter/PlatformRequirementFilter/IgnoreAllPlatformRequirementFilter.php@ jZkUsrc/Composer/Filter/PlatformRequirementFilter/IgnoreListPlatformRequirementFilter.php.@ j.!;Xsrc/Composer/Filter/PlatformRequirementFilter/IgnoreNothingPlatformRequirementFilter.phpT@ jTRsrc/Composer/Filter/PlatformRequirementFilter/PlatformRequirementFilterFactory.php~@ j~¾WTsrc/Composer/Filter/PlatformRequirementFilter/PlatformRequirementFilterInterface.php@ jg?src/Composer/FilterList/ComposerRepositoryFilterInformation.php@ j¤/src/Composer/FilterList/FilterListApiClient.phpx@ jx (ڤ-src/Composer/FilterList/FilterListAuditor.php@ j=ݤ+src/Composer/FilterList/FilterListEntry.php@ j`2src/Composer/FilterList/FilterListEntryBuilder.phpr@ jr6Dsrc/Composer/FilterList/FilterListProvider/FilterListProviderSet.php@ jc5Jsrc/Composer/FilterList/FilterListProvider/UrlSourceFilterListProvider.php @ j R2src/Composer/FilterList/Source/SourceValidator.php@ jeo,src/Composer/FilterList/Source/UrlSource.php@ j.src/Composer/IO/BaseIO.php@ jdܤsrc/Composer/IO/BufferIO.php%@ j%w꺤src/Composer/IO/ConsoleIO.php!@ j!src/Composer/IO/IOInterface.php@ j~hsrc/Composer/IO/NullIO.phpO@ jObssrc/Composer/Installer.php@ jv=Ȥ*src/Composer/Installer/BinaryInstaller.php*@ j*: ;B2src/Composer/Installer/BinaryPresenceInterface.php@ j3.src/Composer/Installer/InstallationManager.php6@ j6 ̤)src/Composer/Installer/InstallerEvent.php*@ j*FZ٤*src/Composer/Installer/InstallerEvents.php@ j>Ǥ-src/Composer/Installer/InstallerInterface.php~@ j~͔դ+src/Composer/Installer/LibraryInstaller.php @ j (/src/Composer/Installer/MetapackageInstaller.php@ j "(src/Composer/Installer/NoopInstaller.phpa@ jaxQn'src/Composer/Installer/PackageEvent.php@ jb\A(src/Composer/Installer/PackageEvents.php@ jK*src/Composer/Installer/PluginInstaller.php @ j =+src/Composer/Installer/ProjectInstaller.php9 @ j9 Uц4src/Composer/Installer/SuggestedPackagesReporter.php@ jXisrc/Composer/Json/JsonFile.php@ jr}X#src/Composer/Json/JsonFormatter.php@ j0d%src/Composer/Json/JsonManipulator.phpj@ jj-#-src/Composer/Json/JsonValidationException.php@ j^^2src/Composer/PHPStan/ConfigReturnTypeExtension.php@ jsb:src/Composer/PHPStan/RuleReasonDataReturnTypeExtension.php @ j t%src/Composer/Package/AliasPackage.php@ jB17src/Composer/Package/Archiver/ArchivableFilesFilter.phpS@ jS'25f7src/Composer/Package/Archiver/ArchivableFilesFinder.php@ j3N0src/Composer/Package/Archiver/ArchiveManager.php@ j3src/Composer/Package/Archiver/ArchiverInterface.phpS@ jS?3src/Composer/Package/Archiver/BaseExcludeFilter.php@ jx6Ȥ7src/Composer/Package/Archiver/ComposerExcludeFilter.php?@ j??`^2src/Composer/Package/Archiver/GitExcludeFilter.php$@ j$t8N.src/Composer/Package/Archiver/PharArchiver.php @ j h-src/Composer/Package/Archiver/ZipArchiver.php@ jq$src/Composer/Package/BasePackage.php@ jX*src/Composer/Package/Comparer/Comparer.php @ j ڰ-src/Composer/Package/CompleteAliasPackage.phpq @ jq #(src/Composer/Package/CompletePackage.php @ j J1src/Composer/Package/CompletePackageInterface.php@ jj21+src/Composer/Package/Dumper/ArrayDumper.php @ j ڕusrc/Composer/Package/Link.php@ j"j+src/Composer/Package/Loader/ArrayLoader.php/@ j/W7src/Composer/Package/Loader/InvalidPackageException.php@ jM*src/Composer/Package/Loader/JsonLoader.php@ j. N/src/Composer/Package/Loader/LoaderInterface.php@ jFB1src/Composer/Package/Loader/RootPackageLoader.php@ j|5src/Composer/Package/Loader/ValidatingArrayLoader.php{g@ j{gsrc/Composer/Package/Locker.php4@ j4eQa src/Composer/Package/Package.php%@ j%)src/Composer/Package/PackageInterface.php @ j f)src/Composer/Package/RootAliasPackage.php @ j A:D$src/Composer/Package/RootPackage.php@ j -src/Composer/Package/RootPackageInterface.php=@ j=]Ĥ0src/Composer/Package/Version/StabilityFilter.php@ jhj.src/Composer/Package/Version/VersionBumper.php @ j G/src/Composer/Package/Version/VersionGuesser.phpT+@ jT+w9.src/Composer/Package/Version/VersionParser.php@ j0src/Composer/Package/Version/VersionSelector.phpm@ jmLJ  src/Composer/PartialComposer.php@ j1Ƥ&src/Composer/Platform/HhvmDetector.php@ j!src/Composer/Platform/Runtime.php@ jttԤ!src/Composer/Platform/Version.php@ j]ߤ-src/Composer/Plugin/Capability/Capability.phpp@ jp#c;2src/Composer/Plugin/Capability/CommandProvider.php@ jF٤src/Composer/Plugin/Capable.php@ jh$src/Composer/Plugin/CommandEvent.php?@ j?|U`.src/Composer/Plugin/PluginBlockedException.php@ jr$src/Composer/Plugin/PluginEvents.php@ j\K֤'src/Composer/Plugin/PluginInterface.php@ j%src/Composer/Plugin/PluginManager.phpU@ jU-X-src/Composer/Plugin/PostFileDownloadEvent.php@ j@>*src/Composer/Plugin/PreCommandRunEvent.php/@ j/3 ,src/Composer/Plugin/PreFileDownloadEvent.php@ jql*src/Composer/Plugin/PrePoolCreateEvent.php@ jۤ-src/Composer/Policy/AbandonedPolicyConfig.phpf@ jf(դ.src/Composer/Policy/AdvisoriesPolicyConfig.php @ j M.src/Composer/Policy/CustomListPolicyConfig.php@ ji,̤$src/Composer/Policy/IgnoreIdRule.php @ j O)src/Composer/Policy/IgnorePackageRule.php @ j Ϥ*src/Composer/Policy/IgnoreSeverityRule.php5@ j5%)src/Composer/Policy/IgnoreUnreachable.php @ j ?(src/Composer/Policy/ListPolicyConfig.php@ j5V+src/Composer/Policy/MalwarePolicyConfig.php@ j{]$src/Composer/Policy/PolicyConfig.php@ jC&4src/Composer/Question/StrictConfirmationQuestion.php@ jr5src/Composer/Repository/AdvisoryProviderInterface.php@ jа+src/Composer/Repository/ArrayRepository.php4@ j4դ.src/Composer/Repository/ArtifactRepository.phpy @ jy 2src/Composer/Repository/CanonicalPackagesTrait.php@ j{.src/Composer/Repository/ComposerRepository.phpV@ jV@Q/src/Composer/Repository/CompositeRepository.phpu @ ju ;src/Composer/Repository/ConfigurableRepositoryInterface.php@ jce*20src/Composer/Repository/FilesystemRepository.php*@ j*j(17src/Composer/Repository/FilterListProviderInterface.php@ jc^9,src/Composer/Repository/FilterRepository.phpj@ jj= %4src/Composer/Repository/InstalledArrayRepository.phpX@ jX,[9src/Composer/Repository/InstalledFilesystemRepository.phpJ@ jJ'kC/src/Composer/Repository/InstalledRepository.phpm@ jm:8src/Composer/Repository/InstalledRepositoryInterface.php@ jg6src/Composer/Repository/InvalidRepositoryException.php@ j W<_/src/Composer/Repository/LockArrayRepository.php@ j^-src/Composer/Repository/PackageRepository.phpn @ jn fb*src/Composer/Repository/PathRepository.phpn@ jn96v*src/Composer/Repository/PearRepository.php@ jQuj.src/Composer/Repository/PlatformRepository.phpY[@ jY[DqT-src/Composer/Repository/RepositoryFactory.php@ j"/src/Composer/Repository/RepositoryInterface.php@ j1"-src/Composer/Repository/RepositoryManager.php @ j H97src/Composer/Repository/RepositorySecurityException.php@ jqt)src/Composer/Repository/RepositorySet.phpq&@ jq&+src/Composer/Repository/RepositoryUtils.php@ j)[ݤ1src/Composer/Repository/RootPackageRepository.php\@ j\`9-src/Composer/Repository/Vcs/ForgejoDriver.php@ j䠤,src/Composer/Repository/Vcs/FossilDriver.php@ j8K\2src/Composer/Repository/Vcs/GitBitbucketDriver.phpR'@ jR';{)src/Composer/Repository/Vcs/GitDriver.php@ j!y,src/Composer/Repository/Vcs/GitHubDriver.php=@ j=B,src/Composer/Repository/Vcs/GitLabDriver.php33@ j33u_5(src/Composer/Repository/Vcs/HgDriver.php_@ j_Ɛ.src/Composer/Repository/Vcs/PerforceDriver.php @ j %ڤ)src/Composer/Repository/Vcs/SvnDriver.phpU@ jU)src/Composer/Repository/Vcs/VcsDriver.php> @ j> 2src/Composer/Repository/Vcs/VcsDriverInterface.php@ jT)src/Composer/Repository/VcsRepository.php5@ j5v#1src/Composer/Repository/VersionCacheInterface.php@ jh3src/Composer/Repository/WritableArrayRepository.php@ jl&#7src/Composer/Repository/WritableRepositoryInterface.phpw@ jw~0src/Composer/Script/Event.php@ j$src/Composer/Script/ScriptEvents.phpI@ jID4 src/Composer/SelfUpdate/Keys.php@ j s$src/Composer/SelfUpdate/Versions.php] @ j]  src/Composer/Util/AuthHelper.php+@ j+(src/Composer/Util/Bitbucket.phpN@ jNj j$src/Composer/Util/ComposerMirror.php@ j#%src/Composer/Util/ConfigValidator.phps@ jsH"src/Composer/Util/ErrorHandler.php @ j }= src/Composer/Util/Filesystem.phpA@ jANRsrc/Composer/Util/Forgejo.php @ j +src/Composer/Util/ForgejoRepositoryData.phpa@ ja, src/Composer/Util/ForgejoUrl.php@ j<x,src/Composer/Util/Git.phpG@ jGCsrc/Composer/Util/GitHub.php@ jIsrc/Composer/Util/GitLab.php@ j,p src/Composer/Util/Hg.php @ j :)src/Composer/Util/Http/CurlDownloader.phpQ@ jQϤ'src/Composer/Util/Http/CurlResponse.php@ j;$src/Composer/Util/Http/ProxyItem.phpE@ jEyդ'src/Composer/Util/Http/ProxyManager.php @ j 0Qt'src/Composer/Util/Http/RequestProxy.php@ jYD?#src/Composer/Util/Http/Response.php@ jz}'$src/Composer/Util/HttpDownloader.php2*@ j2*asrc/Composer/Util/IniHelper.php@ jmBsrc/Composer/Util/Loop.php@ j;'vendor/composer/autoload_namespaces.phpZ@ jZᖤ!vendor/composer/autoload_psr4.php< @ j< Ԯ!vendor/composer/autoload_real.php@ j_#vendor/composer/autoload_static.php@ jzD!vendor/composer/ca-bundle/LICENSE@ jG _(vendor/composer/ca-bundle/res/cacert.pem@ j]*vendor/composer/ca-bundle/src/CaBundle.php>@ j><+vendor/composer/class-map-generator/LICENSE@ j=4vendor/composer/class-map-generator/src/ClassMap.php @ j 0=vendor/composer/class-map-generator/src/ClassMapGenerator.php@ jݤ4vendor/composer/class-map-generator/src/FileList.php$@ j$hSq:vendor/composer/class-map-generator/src/PhpFileCleaner.php@ jDC9vendor/composer/class-map-generator/src/PhpFileParser.phph @ jh vendor/composer/installed.json=@ j=nvendor/composer/installed.php"@ j"ԠG)vendor/composer/metadata-minifier/LICENSE@ jǤ:vendor/composer/metadata-minifier/src/MetadataMinifier.php@ j0&vendor/composer/pcre/LICENSE@ jǤ+vendor/composer/pcre/src/MatchAllResult.php@ j \97vendor/composer/pcre/src/MatchAllStrictGroupsResult.php)@ j)hI6vendor/composer/pcre/src/MatchAllWithOffsetsResult.php*@ j*A (vendor/composer/pcre/src/MatchResult.php@ j<4vendor/composer/pcre/src/MatchStrictGroupsResult.php@ j0Q3vendor/composer/pcre/src/MatchWithOffsetsResult.php@ j{*,o<vendor/composer/pcre/src/PHPStan/InvalidRegexPatternRule.php @ j פ3vendor/composer/pcre/src/PHPStan/PregMatchFlags.phpp@ jpYMGvendor/composer/pcre/src/PHPStan/PregMatchParameterOutTypeExtension.php1@ j1}ҤEvendor/composer/pcre/src/PHPStan/PregMatchTypeSpecifyingExtension.php @ j CǤLvendor/composer/pcre/src/PHPStan/PregReplaceCallbackClosureTypeExtension.php@ j/?vendor/composer/pcre/src/PHPStan/UnsafeStrictGroupsCallRule.phpk @ jk 3E*vendor/composer/pcre/src/PcreException.phpj@ jj*\!vendor/composer/pcre/src/Preg.php0"@ j0"ѹ{"vendor/composer/pcre/src/Regex.php @ j 뇤*vendor/composer/pcre/src/ReplaceResult.php@ j'9vendor/composer/pcre/src/UnexpectedNullMatchException.php@ jvendor/composer/semver/LICENSE@ jSRm)vendor/composer/semver/src/Comparator.php@ j^_E/vendor/composer/semver/src/CompilingMatcher.phpc@ jc/vendor/composer/semver/src/Constraint/Bound.phpw@ jwW4]W4vendor/composer/semver/src/Constraint/Constraint.php@ j=vendor/composer/semver/src/Constraint/ConstraintInterface.php@ j5y<vendor/composer/semver/src/Constraint/MatchAllConstraint.php@ jE=vendor/composer/semver/src/Constraint/MatchNoneConstraint.php@ j֯خ9vendor/composer/semver/src/Constraint/MultiConstraint.php@ jĤ'vendor/composer/semver/src/Interval.php@ j=[i(vendor/composer/semver/src/Intervals.php+@ j+@1%vendor/composer/semver/src/Semver.php@ j-i,vendor/composer/semver/src/VersionParser.php-@ j-*p%vendor/composer/spdx-licenses/LICENSE@ jSRm6vendor/composer/spdx-licenses/res/spdx-exceptions.json;@ j;W4vendor/composer/spdx-licenses/res/spdx-licenses.json1 @ j1 Yzx2vendor/composer/spdx-licenses/src/SpdxLicenses.php|@ j|$&vendor/composer/xdebug-handler/LICENSE+@ j+@T0vendor/composer/xdebug-handler/src/PhpConfig.php@ jgԏv.vendor/composer/xdebug-handler/src/Process.php@ jfZ.-vendor/composer/xdebug-handler/src/Status.php @ j t4vendor/composer/xdebug-handler/src/XdebugHandler.php,@ j,:@(vendor/justinrainbow/json-schema/LICENSE"@ j" |Cvendor/justinrainbow/json-schema/src/JsonSchema/ConstraintError.phph@ jho9 Nvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/BaseConstraint.php @ j h'Tvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/CollectionConstraint.php @ j HOvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/ConstConstraint.phpH@ jHJvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Constraint.php@ jդSvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/ConstraintInterface.php@ jVhvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/AdditionalItemsConstraint.php@ jJämvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/AdditionalPropertiesConstraint.php&@ j&9N^vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/AllOfConstraint.php@ jY]^vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/AnyOfConstraint.php)@ j)7'^vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/ConstConstraint.php@ jTѤavendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/ContainsConstraint.php@ j|evendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/DependenciesConstraint.php@ ja龤`vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/Draft06Constraint.php0 @ j0 io=]vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/EnumConstraint.php@ j%xivendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/ExclusiveMaximumConstraint.phpW@ jW BȤivendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/ExclusiveMinimumConstraint.phpW@ jWa:SڤVvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/Factory.php@ jtB=_vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/FormatConstraint.phpX@ jX~^vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/ItemsConstraint.php>@ j>bavendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MaxItemsConstraint.php>@ j>{bvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MaxLengthConstraint.phpL@ jLVT fvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MaxPropertiesConstraint.phpn@ jnG^`vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MaximumConstraint.php!@ j! ~avendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MinItemsConstraint.php>@ j>uVpbvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MinLengthConstraint.phpL@ jLuI^fvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MinPropertiesConstraint.phpn@ jn0S`vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MinimumConstraint.php!@ j!cvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MultipleOfConstraint.php@ jݫc\vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/NotConstraint.phpL@ jLd=^vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/OneOfConstraint.php@ jͤ`vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/PatternConstraint.php @ j pNjvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/PatternPropertiesConstraint.php@ jQcvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/PropertiesConstraint.php@ j]hvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/PropertiesNamesConstraint.php @ j 7\vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/RefConstraint.php@ javendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/RequiredConstraint.php[@ j[r2]vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/TypeConstraint.php]@ j]dvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/UniqueItemsConstraint.php@ j(-.hvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AdditionalItemsConstraint.php@ jmvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AdditionalPropertiesConstraint.php&@ j&^vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AllOfConstraint.php@ j>jS^vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AnyOfConstraint.php)@ j)H^vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ConstConstraint.php@ jnavendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ContainsConstraint.php@ j|`vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ContentConstraint.php_@ j_Ywevendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/DependenciesConstraint.php@ jY]`vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/Draft07Constraint.php @ j HS\v]vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/EnumConstraint.php@ jDgivendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ExclusiveMaximumConstraint.phpW@ jWJ2Vivendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ExclusiveMinimumConstraint.phpW@ jW"#DVvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/Factory.phpg@ jgՂ]_vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/FormatConstraint.phpi@ ji}cvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/IfThenElseConstraint.phpF@ jF^vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ItemsConstraint.php>@ j>@ j>0[bvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaxLengthConstraint.phpL@ jL[P3 fvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaxPropertiesConstraint.phpn@ jnf`vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaximumConstraint.php!@ j!7]avendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinItemsConstraint.php>@ j> vݤbvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinLengthConstraint.phpL@ jL.Yfvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinPropertiesConstraint.phpn@ jn`vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinimumConstraint.php!@ j!Hcvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MultipleOfConstraint.php@ jr\vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/NotConstraint.phpL@ jLb:^vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/OneOfConstraint.php@ j1YT`vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PatternConstraint.php @ j jvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PatternPropertiesConstraint.php@ jt#cvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PropertiesConstraint.php@ jБ5hvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PropertiesNamesConstraint.php @ j ]E\vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/RefConstraint.php@ j?YCɤavendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/RequiredConstraint.php[@ j[:]vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/TypeConstraint.php]@ j]rSdvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/UniqueItemsConstraint.php@ jSUNvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/EnumConstraint.php@ jpqgGvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Factory.phpA@ jAVA`Pvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/FormatConstraint.php@ jјPvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/NumberConstraint.phpE@ jEϔE<Pvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/ObjectConstraint.php@ jyȘPvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/SchemaConstraint.php @ j ~>Pvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/StringConstraint.php#@ j#5Xvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/LooseTypeCheck.php@ jj&Yvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/StrictTypeCheck.php@ j 9h\vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/TypeCheckInterface.php@ jA Nvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeConstraint.php@ jSvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/UndefinedConstraint.php$@ j$3Dvendor/justinrainbow/json-schema/src/JsonSchema/DraftIdentifiers.php@ jK}ФCvendor/justinrainbow/json-schema/src/JsonSchema/Entity/ErrorBag.php@ jwHvendor/justinrainbow/json-schema/src/JsonSchema/Entity/ErrorBagProxy.php@ jcFvendor/justinrainbow/json-schema/src/JsonSchema/Entity/JsonPointer.php)@ j)8vendor/justinrainbow/json-schema/src/JsonSchema/Enum.phpg@ jg/Pvendor/justinrainbow/json-schema/src/JsonSchema/Exception/ExceptionInterface.phpc@ jcWfVvendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidArgumentException.php@ jFt* Tvendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidConfigException.php@ j))ZTvendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSchemaException.php@ j:'s]vendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSchemaMediaTypeException.php@ jwTT:Wvendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSourceUriException.php@ j ~Svendor/justinrainbow/json-schema/src/JsonSchema/Exception/JsonDecodingException.php@ jQ3<Wvendor/justinrainbow/json-schema/src/JsonSchema/Exception/ResourceNotFoundException.php@ j/Nvendor/justinrainbow/json-schema/src/JsonSchema/Exception/RuntimeException.php@ j7bФ^vendor/justinrainbow/json-schema/src/JsonSchema/Exception/UnresolvableJsonPointerException.php@ j\*Rvendor/justinrainbow/json-schema/src/JsonSchema/Exception/UriResolverException.php@ jM߈Qvendor/justinrainbow/json-schema/src/JsonSchema/Exception/ValidationException.php@ jlKvendor/justinrainbow/json-schema/src/JsonSchema/Iterator/ObjectIterator.php>@ j>T(;vendor/justinrainbow/json-schema/src/JsonSchema/Rfc3339.php@ js!SAvendor/justinrainbow/json-schema/src/JsonSchema/SchemaStorage.php @ j RFJvendor/justinrainbow/json-schema/src/JsonSchema/SchemaStorageInterface.php1@ j1 SEvendor/justinrainbow/json-schema/src/JsonSchema/Tool/DeepComparer.phpb@ jbEځAvendor/justinrainbow/json-schema/src/JsonSchema/Tool/DeepCopy.php@ jx/3]vendor/justinrainbow/json-schema/src/JsonSchema/Tool/Validator/RelativeReferenceValidator.php-@ j-Ovendor/justinrainbow/json-schema/src/JsonSchema/Tool/Validator/UriValidator.phpY@ jY]ݠTvendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/AbstractRetriever.php@ j`<Gvendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/Curl.php@ j}Rvendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/FileGetContents.php`@ j`K Rvendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/PredefinedArray.phpF@ jFr+Xvendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/UriRetrieverInterface.php@ j5Cvendor/justinrainbow/json-schema/src/JsonSchema/Uri/UriResolver.php @ j lDvendor/justinrainbow/json-schema/src/JsonSchema/Uri/UriRetriever.phpm@ jm@Hvendor/justinrainbow/json-schema/src/JsonSchema/UriResolverInterface.php@ jCIvendor/justinrainbow/json-schema/src/JsonSchema/UriRetrieverInterface.php@ jҡ"=vendor/justinrainbow/json-schema/src/JsonSchema/Validator.php@ jx%vendor/marc-mabe/php-enum/LICENSE.txt@ jCѤ&vendor/marc-mabe/php-enum/src/Enum.php`@ j`Q)vendor/marc-mabe/php-enum/src/EnumMap.phpC@ jC{~ʤ7vendor/marc-mabe/php-enum/src/EnumSerializableTrait.php@ j;蟤)vendor/marc-mabe/php-enum/src/EnumSet.php6@ j6_.vendor/marc-mabe/php-enum/stubs/Stringable.phpb@ jbћ<vendor/psr/container/LICENSE{@ j{e8vendor/psr/container/src/ContainerExceptionInterface.phpN@ jNL/vendor/psr/container/src/ContainerInterface.php@ j7vendor/psr/container/src/NotFoundExceptionInterface.phpq@ jqRvendor/psr/log/LICENSE?@ j? )vendor/psr/log/Psr/Log/AbstractLogger.php;@ j;>3[3vendor/psr/log/Psr/Log/InvalidArgumentException.php`@ j` X1#vendor/psr/log/Psr/Log/LogLevel.php@ jj8/vendor/psr/log/Psr/Log/LoggerAwareInterface.php|@ j|$+vendor/psr/log/Psr/Log/LoggerAwareTrait.php@ jTB*vendor/psr/log/Psr/Log/LoggerInterface.php@ jx&vendor/psr/log/Psr/Log/LoggerTrait.phpk@ jk}%vendor/psr/log/Psr/Log/NullLogger.php@ jX)vendor/psr/log/Psr/Log/Test/DummyTest.phpp@ jp3vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php @ j $/Ҥ*vendor/psr/log/Psr/Log/Test/TestLogger.php<@ j<(Ivendor/react/promise/LICENSEi@ ji3}%vendor/react/promise/src/Deferred.phpS@ jS׆9vendor/react/promise/src/Exception/CompositeException.php@ j!6vendor/react/promise/src/Exception/LengthException.php^@ j^?q7vendor/react/promise/src/Internal/CancellationQueue.phpr@ jrx_6vendor/react/promise/src/Internal/FulfilledPromise.phpx@ jx:sxŤ5vendor/react/promise/src/Internal/RejectedPromise.php@ j$vendor/react/promise/src/Promise.php@ jC-vendor/react/promise/src/PromiseInterface.phpi@ ji&](&vendor/react/promise/src/functions.php@ j`Ҥ.vendor/react/promise/src/functions_include.php]@ j]Q<vendor/seld/jsonlint/LICENSE$@ j$4:~@vendor/seld/jsonlint/src/Seld/JsonLint/DuplicateKeyException.php|@ j| 5vendor/seld/jsonlint/src/Seld/JsonLint/JsonParser.phpM9@ jM9|n0vendor/seld/jsonlint/src/Seld/JsonLint/Lexer.php@ jȻ_;vendor/seld/jsonlint/src/Seld/JsonLint/ParsingException.php(@ j(>v}4vendor/seld/jsonlint/src/Seld/JsonLint/Undefined.php>@ j>qvendor/seld/phar-utils/LICENSE$@ j$,M%vendor/seld/phar-utils/src/Linter.phpi@ jiޤ)vendor/seld/phar-utils/src/Timestamps.php^ @ j^ D"vendor/seld/signal-handler/LICENSE$@ j$,M0vendor/seld/signal-handler/src/SignalHandler.php@ j &vendor/symfony/console/Application.phpq@ jqm!.vendor/symfony/console/Attribute/AsCommand.php@ j32vendor/symfony/console/CI/GithubActionReporter.php@ j"g vendor/symfony/console/Color.phph@ jhGb痤*vendor/symfony/console/Command/Command.phpJ'@ jJ' w2vendor/symfony/console/Command/CompleteCommand.php@ j}8vendor/symfony/console/Command/DumpCompletionCommand.php@ j99.vendor/symfony/console/Command/HelpCommand.php @ j }.vendor/symfony/console/Command/LazyCommand.php@ jm.vendor/symfony/console/Command/ListCommand.php1 @ j1 $D|0vendor/symfony/console/Command/LockableTrait.php@ jq5gj=vendor/symfony/console/Command/SignalableCommandInterface.php@ jB`?vendor/symfony/console/CommandLoader/CommandLoaderInterface.phpQ@ jQH?vendor/symfony/console/CommandLoader/ContainerCommandLoader.phpU@ jU_=vendor/symfony/console/CommandLoader/FactoryCommandLoader.php@ jd"z5vendor/symfony/console/Completion/CompletionInput.php@ j$k;vendor/symfony/console/Completion/CompletionSuggestions.php@ jAAvendor/symfony/console/Completion/Output/BashCompletionOutput.phpg@ jgWFvendor/symfony/console/Completion/Output/CompletionOutputInterface.phpF@ jF2nM0vendor/symfony/console/Completion/Suggestion.php3@ j3̆ (vendor/symfony/console/ConsoleEvents.php@ jgw]!vendor/symfony/console/Cursor.php @ j ƫDvendor/symfony/console/DependencyInjection/AddConsoleCommandPass.phpf@ jf# $<vendor/symfony/console/Descriptor/ApplicationDescription.php @ j pC^0vendor/symfony/console/Descriptor/Descriptor.php}@ j}3^9vendor/symfony/console/Descriptor/DescriptorInterface.php@ j@4vendor/symfony/console/Descriptor/JsonDescriptor.php@ j1%菤8vendor/symfony/console/Descriptor/MarkdownDescriptor.phpQ@ jQtv4vendor/symfony/console/Descriptor/TextDescriptor.php"@ j"ȸˤ3vendor/symfony/console/Descriptor/XmlDescriptor.php@ juI@4vendor/symfony/console/Event/ConsoleCommandEvent.php@ j2vendor/symfony/console/Event/ConsoleErrorEvent.php@ j%-vendor/symfony/console/Event/ConsoleEvent.php@ jo٤3vendor/symfony/console/Event/ConsoleSignalEvent.phpG@ jG 6vendor/symfony/console/Event/ConsoleTerminateEvent.php~@ j~hr֤6vendor/symfony/console/EventListener/ErrorListener.php'@ j'𢡉=vendor/symfony/console/Exception/CommandNotFoundException.php@ jK}7vendor/symfony/console/Exception/ExceptionInterface.phpy@ jy9[&=vendor/symfony/console/Exception/InvalidArgumentException.php@ j̽Z;vendor/symfony/console/Exception/InvalidOptionException.php@ jH3vendor/symfony/console/Exception/LogicException.php@ jO\e:vendor/symfony/console/Exception/MissingInputException.php@ jS ?vendor/symfony/console/Exception/NamespaceNotFoundException.php@ jn5vendor/symfony/console/Exception/RuntimeException.php@ j,68vendor/symfony/console/Formatter/NullOutputFormatter.php@ j!& u=vendor/symfony/console/Formatter/NullOutputFormatterStyle.php @ j 0ؤ4vendor/symfony/console/Formatter/OutputFormatter.php@ j=vendor/symfony/console/Formatter/OutputFormatterInterface.php@ jY ߤ9vendor/symfony/console/Formatter/OutputFormatterStyle.phpz@ jzjaBvendor/symfony/console/Formatter/OutputFormatterStyleInterface.php@ jw>vendor/symfony/console/Formatter/OutputFormatterStyleStack.php@ jLኤFvendor/symfony/console/Formatter/WrappableOutputFormatterInterface.php@ jZ6vendor/symfony/console/Helper/DebugFormatterHelper.php@ j62vendor/symfony/console/Helper/DescriptorHelper.php@ jḡ(vendor/symfony/console/Helper/Dumper.php@ j<ߤ1vendor/symfony/console/Helper/FormatterHelper.phpi@ jiw(vendor/symfony/console/Helper/Helper.php @ j 1T1vendor/symfony/console/Helper/HelperInterface.php@ j`R\+vendor/symfony/console/Helper/HelperSet.phpI@ jI=D2vendor/symfony/console/Helper/InputAwareHelper.phpc@ jc/vendor/symfony/console/Helper/ProcessHelper.php\ @ j\ zA-vendor/symfony/console/Helper/ProgressBar.phpX/@ jX/2p3vendor/symfony/console/Helper/ProgressIndicator.php@ je0vendor/symfony/console/Helper/QuestionHelper.phpR-@ jR-1i7vendor/symfony/console/Helper/SymfonyQuestionHelper.php @ j d'vendor/symfony/console/Helper/Table.phpJ@ jJS+vendor/symfony/console/Helper/TableCell.phpA@ jAӠ0vendor/symfony/console/Helper/TableCellStyle.php@ jэ_+vendor/symfony/console/Helper/TableRows.php)@ j)W3z0vendor/symfony/console/Helper/TableSeparator.php@ j,vendor/symfony/console/Helper/TableStyle.php@ jW*vendor/symfony/console/Input/ArgvInput.php+@ j+FѤ+vendor/symfony/console/Input/ArrayInput.php @ j .&vendor/symfony/console/Input/Input.php @ j o.vendor/symfony/console/Input/InputArgument.php@ jΠ4vendor/symfony/console/Input/InputAwareInterface.php@ jO0vendor/symfony/console/Input/InputDefinition.php#@ j# !3/vendor/symfony/console/Input/InputInterface.php@ jG,vendor/symfony/console/Input/InputOption.php@ jI/9vendor/symfony/console/Input/StreamableInputInterface.php@ jB,vendor/symfony/console/Input/StringInput.php?@ j?ˤvendor/symfony/console/LICENSE.@ j.k/vendor/symfony/console/Logger/ConsoleLogger.php @ j Q0vendor/symfony/console/Output/BufferedOutput.phpl@ jly:/vendor/symfony/console/Output/ConsoleOutput.php @ j \b8vendor/symfony/console/Output/ConsoleOutputInterface.php@ j6vendor/symfony/console/Output/ConsoleSectionOutput.php @ j go@,vendor/symfony/console/Output/NullOutput.phpB@ jB%#RƤ(vendor/symfony/console/Output/Output.php @ j 8x1vendor/symfony/console/Output/OutputInterface.php@ j.vendor/symfony/console/Output/StreamOutput.php@ j,"5vendor/symfony/console/Output/TrimmedBufferOutput.php@ jlk~2vendor/symfony/console/Question/ChoiceQuestion.php @ j c䦤8vendor/symfony/console/Question/ConfirmationQuestion.php@ jyń,vendor/symfony/console/Question/Question.php" @ j" ޹ۤ4vendor/symfony/console/Resources/bin/hiddeninput.exe$@ j$v0vendor/symfony/console/Resources/completion.bash @ j 'r8vendor/symfony/console/SignalRegistry/SignalRegistry.php!@ j!~.:3vendor/symfony/console/SingleCommandApplication.php7@ j71,vendor/symfony/console/Style/OutputStyle.phpt@ jtO;Ƥ/vendor/symfony/console/Style/StyleInterface.php@ j\-vendor/symfony/console/Style/SymfonyStyle.php5'@ j5'yl#vendor/symfony/console/Terminal.phpm @ jm Pv3vendor/symfony/console/Tester/ApplicationTester.php@ jI9vendor/symfony/console/Tester/CommandCompletionTester.php@ jʛ;/vendor/symfony/console/Tester/CommandTester.php@ jD@vendor/symfony/console/Tester/Constraint/CommandIsSuccessful.php@ jƃ-vendor/symfony/console/Tester/TesterTrait.php @ j 2'L,vendor/symfony/deprecation-contracts/LICENSE.@ j.1vendor/symfony/deprecation-contracts/function.php=@ j= :vendor/symfony/filesystem/Exception/ExceptionInterface.php|@ j|D=vendor/symfony/filesystem/Exception/FileNotFoundException.php@ jb}3vendor/symfony/filesystem/Exception/IOException.php@ j3_<vendor/symfony/filesystem/Exception/IOExceptionInterface.php@ jjwM@vendor/symfony/filesystem/Exception/InvalidArgumentException.php@ j!Ǥ8vendor/symfony/filesystem/Exception/RuntimeException.php@ jUUH(vendor/symfony/filesystem/Filesystem.php$B@ j$Bw;!vendor/symfony/filesystem/LICENSE.@ j.k"vendor/symfony/filesystem/Path.phpb'@ jb'ߣ/vendor/symfony/finder/Comparator/Comparator.php4@ j4"դ3vendor/symfony/finder/Comparator/DateComparator.php@ j%e5vendor/symfony/finder/Comparator/NumberComparator.phpz@ jz+h9vendor/symfony/finder/Exception/AccessDeniedException.php@ js>vendor/symfony/finder/Exception/DirectoryNotFoundException.php@ ja) vendor/symfony/finder/Finder.php'@ j'?#vendor/symfony/finder/Gitignore.php@ js 4vendor/symfony/finder/Glob.php@ jDb7vendor/symfony/finder/Iterator/CustomFilterIterator.phpc@ jc7:vendor/symfony/finder/Iterator/DateRangeFilterIterator.php@ jPs;vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php@ jAvendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php0@ j0.wv9vendor/symfony/finder/Iterator/FileTypeFilterIterator.php@ j^"<vendor/symfony/finder/Iterator/FilecontentFilterIterator.phpW@ jW٤9vendor/symfony/finder/Iterator/FilenameFilterIterator.php@ j=/vendor/symfony/finder/Iterator/LazyIterator.phpQ@ jQn]=vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php@ j5vendor/symfony/finder/Iterator/PathFilterIterator.php@ jg"_=vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.phpK @ jK F :vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php|@ j|3ٟ3vendor/symfony/finder/Iterator/SortableIterator.php( @ j( ;vendor/symfony/finder/Iterator/VcsIgnoredFilterIterator.phpw @ jw ŵ3vendor/symfony/finder/LICENSE.@ j.k%vendor/symfony/finder/SplFileInfo.php@ jy'vendor/symfony/polyfill-ctype/Ctype.php @ j %vendor/symfony/polyfill-ctype/LICENSE.@ j.X+vendor/symfony/polyfill-ctype/bootstrap.php2@ j2a8-vendor/symfony/polyfill-ctype/bootstrap80.phph@ jhKy2vendor/symfony/polyfill-intl-grapheme/Grapheme.php"@ j"'0-vendor/symfony/polyfill-intl-grapheme/LICENSE.@ j.k3vendor/symfony/polyfill-intl-grapheme/bootstrap.php @ j us5vendor/symfony/polyfill-intl-grapheme/bootstrap80.phpr @ jr gͤ5vendor/symfony/polyfill-intl-grapheme/bootstrap85.php@ jc!dz/vendor/symfony/polyfill-intl-normalizer/LICENSE.@ j.k6vendor/symfony/polyfill-intl-normalizer/Normalizer.php@ j@פFvendor/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php.@ j.Qs$Rvendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalComposition.php=@ j=*o?Tvendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalDecomposition.phpa@ jaR}Lvendor/symfony/polyfill-intl-normalizer/Resources/unidata/combiningClass.phpt.@ jt. qܤXvendor/symfony/polyfill-intl-normalizer/Resources/unidata/compatibilityDecomposition.php@ joe)Wvendor/symfony/polyfill-intl-normalizer/Resources/unidata/rawCanonicalDecomposition.phpц@ jц%j[vendor/symfony/polyfill-intl-normalizer/Resources/unidata/rawCompatibilityDecomposition.php/@ j/>5vendor/symfony/polyfill-intl-normalizer/bootstrap.php@ jj7vendor/symfony/polyfill-intl-normalizer/bootstrap80.php@ jʤ(vendor/symfony/polyfill-mbstring/LICENSE.@ j.k-vendor/symfony/polyfill-mbstring/Mbstring.phpf@ jf6Bvendor/symfony/polyfill-mbstring/Resources/unidata/caseFolding.php@ j@vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.phpT@ jT+Fvendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php@ jy_@vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php8[@ j8[+R*.vendor/symfony/polyfill-mbstring/bootstrap.php@ j0vendor/symfony/polyfill-mbstring/bootstrap72.phpH@ jHHpz0vendor/symfony/polyfill-mbstring/bootstrap80.php%@ j%%vendor/symfony/polyfill-php73/LICENSE.@ j.X'vendor/symfony/polyfill-php73/Php73.phpn@ jnCsl?vendor/symfony/polyfill-php73/Resources/stubs/JsonException.php[@ j[Mܤ+vendor/symfony/polyfill-php73/bootstrap.php@ jAY8Ƥ%vendor/symfony/polyfill-php80/LICENSE.@ j.'vendor/symfony/polyfill-php80/Php80.php @ j \C*vendor/symfony/polyfill-php80/PhpToken.php@ jxˤ;vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php@ j:vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php@ jڔݤ<vendor/symfony/polyfill-php80/Resources/stubs/Stringable.phpk@ jk+Evendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php]@ j]g<vendor/symfony/polyfill-php80/Resources/stubs/ValueError.phpT@ jTw+vendor/symfony/polyfill-php80/bootstrap.php@ j%vendor/symfony/polyfill-php81/LICENSE.@ j.;c'vendor/symfony/polyfill-php81/Php81.php;@ j;d@vendor/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php@ j?Fvendor/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php@ j+vendor/symfony/polyfill-php81/bootstrap.php@ jyI%vendor/symfony/polyfill-php84/LICENSE.@ j.5'vendor/symfony/polyfill-php84/Php84.php0@ j0 n6vendor/symfony/polyfill-php84/Resources/Deprecated.phpx@ jx_R8vendor/symfony/polyfill-php84/Resources/RoundingMode.php@ jԩ<vendor/symfony/polyfill-php84/Resources/stubs/Deprecated.php@ j^Τ;vendor/symfony/polyfill-php84/Resources/stubs/Pdo/Dblib.php3@ j3 >vendor/symfony/polyfill-php84/Resources/stubs/Pdo/Firebird.php@ jP;vendor/symfony/polyfill-php84/Resources/stubs/Pdo/Mysql.php@ j7.:vendor/symfony/polyfill-php84/Resources/stubs/Pdo/Odbc.php @ j _;vendor/symfony/polyfill-php84/Resources/stubs/Pdo/Pgsql.php @ j e'<vendor/symfony/polyfill-php84/Resources/stubs/Pdo/Sqlite.phpj@ jjӱDvendor/symfony/polyfill-php84/Resources/stubs/ReflectionConstant.php @ j @>vendor/symfony/polyfill-php84/Resources/stubs/RoundingMode.phpf@ jfA+vendor/symfony/polyfill-php84/bootstrap.phpC @ jC Jݤ-vendor/symfony/polyfill-php84/bootstrap72.php@ jg-vendor/symfony/polyfill-php84/bootstrap82.php-@ j-,4G7vendor/symfony/process/Exception/ExceptionInterface.phpy@ jyqVXJ=vendor/symfony/process/Exception/InvalidArgumentException.php@ j+_3vendor/symfony/process/Exception/LogicException.php@ j ;vendor/symfony/process/Exception/ProcessFailedException.phpx@ jxzy=vendor/symfony/process/Exception/ProcessSignaledException.php@ jYש=vendor/symfony/process/Exception/ProcessTimedOutException.php1@ j1'Z5vendor/symfony/process/Exception/RuntimeException.php@ j:+vendor/symfony/process/ExecutableFinder.php@ j[&vendor/symfony/process/InputStream.php0@ j0>vendor/symfony/process/LICENSE.@ j.k.vendor/symfony/process/PhpExecutableFinder.php@ jŌ%vendor/symfony/process/PhpProcess.php@ jpX.vendor/symfony/process/Pipes/AbstractPipes.php @ j kSQ/vendor/symfony/process/Pipes/PipesInterface.php@ jfQ *vendor/symfony/process/Pipes/UnixPipes.php@ jVv-vendor/symfony/process/Pipes/WindowsPipes.php4 @ j4 V"vendor/symfony/process/Process.phpLh@ jLhs]'vendor/symfony/process/ProcessUtils.php@ jX7vendor/symfony/service-contracts/Attribute/Required.php@ jxj네@vendor/symfony/service-contracts/Attribute/SubscribedService.php @ j 1IT(vendor/symfony/service-contracts/LICENSE.@ j.X3vendor/symfony/service-contracts/ResetInterface.phpy@ jyj8vendor/symfony/service-contracts/ServiceLocatorTrait.php @ j "5=vendor/symfony/service-contracts/ServiceProviderInterface.php@ jRk?vendor/symfony/service-contracts/ServiceSubscriberInterface.php@ jd ;vendor/symfony/service-contracts/ServiceSubscriberTrait.php` @ j` 3u<vendor/symfony/service-contracts/Test/ServiceLocatorTest.php@ jmԤ@vendor/symfony/service-contracts/Test/ServiceLocatorTestCase.php@ jŚ(vendor/symfony/string/AbstractString.php*4@ j*4J /vendor/symfony/string/AbstractUnicodeString.phpR@ jR#$vendor/symfony/string/ByteString.php+@ j+K^)vendor/symfony/string/CodePointString.php@ jB6vendor/symfony/string/Exception/ExceptionInterface.phps@ jsqHV<vendor/symfony/string/Exception/InvalidArgumentException.php@ jA4vendor/symfony/string/Exception/RuntimeException.php@ jug4vendor/symfony/string/Inflector/EnglishInflector.phpJ@ jJ33vendor/symfony/string/Inflector/FrenchInflector.php @ j H6vendor/symfony/string/Inflector/InflectorInterface.php@ jhDvendor/symfony/string/LICENSE.@ j.?w$vendor/symfony/string/LazyString.phpC @ jC b<vendor/symfony/string/Resources/data/wcswidth_table_wide.php$@ j$K<vendor/symfony/string/Resources/data/wcswidth_table_zero.php@ j$-vendor/symfony/string/Resources/functions.php@ jcڪ".vendor/symfony/string/Slugger/AsciiSlugger.php@ j}&ʤ2vendor/symfony/string/Slugger/SluggerInterface.php@ jk'vendor/symfony/string/UnicodeString.php%@ j%5 bin/composer7@ j7*ELICENSE.@ j. audit = $audit; $this->auditFormat = $auditFormat; } } advisories->getIgnoreListForOperation('audit'); $ignoredSeverities = $policyConfig->advisories->getIgnoreSeverityForOperation('audit'); $result = $repoSet->getMatchingSecurityAdvisories($packages, $format === self::FORMAT_SUMMARY, $policyConfig->ignoreUnreachable->audit); $allAdvisories = $result['advisories']; $unreachableRepos = $result['unreachableRepos']; if ($format === self::FORMAT_SUMMARY && $this->needsCompleteAdvisoryLoad($allAdvisories, $ignoreList)) { $result = $repoSet->getMatchingSecurityAdvisories($packages, false, $policyConfig->ignoreUnreachable->audit); $allAdvisories = $result['advisories']; $unreachableRepos = array_merge($unreachableRepos, $result['unreachableRepos']); } ['advisories' => $advisories, 'ignoredAdvisories' => $ignoredAdvisories] = $this->processAdvisories($allAdvisories, $ignoreList, $ignoredSeverities); $abandonedCount = 0; $affectedPackagesCount = count($advisories); if ($policyConfig->abandoned->audit === ListPolicyConfig::AUDIT_IGNORE) { $abandonedPackages = []; } else { $abandonedPackages = $this->filterAbandonedPackages($packages, $policyConfig->abandoned->getFlatIgnoreForOperation('audit')); if ($policyConfig->abandoned->audit === ListPolicyConfig::AUDIT_FAIL) { $abandonedCount = count($abandonedPackages); } } $filterAuditor = new FilterListAuditor(); $filteredPackages = []; $filteredCount = 0; $activeAuditFilterLists = $policyConfig->getActiveAuditFilterLists(); if ($filterListProviderSet !== null && count($activeAuditFilterLists) > 0) { $failingListNames = []; foreach ($activeAuditFilterLists as $name => $list) { if ($list->audit === ListPolicyConfig::AUDIT_FAIL) { $failingListNames[$name] = true; } } $filterResult = $filterAuditor->collectFilterLists($packages, $filterListProviderSet, array_keys($activeAuditFilterLists), $policyConfig->ignoreUnreachable->audit); $unreachableRepos = array_merge($unreachableRepos, $filterResult['unreachableRepos']); foreach ($packages as $package) { $matchingEntries = $filterAuditor->getMatchingAuditEntries($package, $filterResult['filter'], $policyConfig); foreach ($matchingEntries as $entry) { $filteredPackages[$package->getName()][] = $entry; if (isset($failingListNames[$entry->listName])) { $filteredCount++; } } } } $auditResult = (0 < $affectedPackagesCount || 0 < $abandonedCount || 0 < $filteredCount) ? self::STATUS_FAILED : self::STATUS_OK; if (self::FORMAT_JSON === $format) { $json = ['advisories' => $advisories]; if ($ignoredAdvisories !== []) { $json['ignored-advisories'] = $ignoredAdvisories; } if ($unreachableRepos !== []) { $json['unreachable-repositories'] = $unreachableRepos; } $json['abandoned'] = array_reduce($abandonedPackages, static function (array $carry, CompletePackageInterface $package): array { $carry[$package->getPrettyName()] = $package->getReplacementPackage(); return $carry; }, []); $json['filter'] = array_map(static function (array $entries) { return array_map(static function (FilterListEntry $entry) { $data = (array) $entry; $data['constraint'] = $entry->constraint->getPrettyString(); return $data; }, $entries); }, $filteredPackages); $io->write(JsonFile::encode($json)); return $auditResult; } $errorOrWarn = $warningOnly ? 'warning' : 'error'; if ($affectedPackagesCount > 0 || count($ignoredAdvisories) > 0) { $passes = [ [$ignoredAdvisories, "Found %d ignored security vulnerability advisor%s affecting %d package%s%s"], [$advisories, "<$errorOrWarn>Found %d security vulnerability advisor%s affecting %d package%s%s"], ]; foreach ($passes as [$advisoriesToOutput, $message]) { [$pkgCount, $totalAdvisoryCount] = $this->countAdvisories($advisoriesToOutput); if ($pkgCount > 0) { $plurality = $totalAdvisoryCount === 1 ? 'y' : 'ies'; $pkgPlurality = $pkgCount === 1 ? '' : 's'; $punctuation = $format === 'summary' ? '.' : ':'; $io->writeError(sprintf($message, $totalAdvisoryCount, $plurality, $pkgCount, $pkgPlurality, $punctuation)); $this->outputAdvisories($io, $advisoriesToOutput, $format); } } if ($format === self::FORMAT_SUMMARY) { $io->writeError('Run "composer audit" for a full list of advisories.'); } } else { $io->writeError('No security vulnerability advisories found.'); } if (count($unreachableRepos) > 0) { $io->writeError('The following repositories were unreachable:'); foreach ($unreachableRepos as $repo) { $io->writeError(' - ' . $repo); } } if (count($abandonedPackages) > 0 && $format !== self::FORMAT_SUMMARY) { $this->outputAbandonedPackages($io, $abandonedPackages, $format); } if (count($filteredPackages) > 0) { $plurality = count($filteredPackages) === 1 ? '' : 's'; $punctuation = $format === self::FORMAT_SUMMARY ? '.' : ':'; $style = $filteredCount > 0 ? 'error' : 'warning'; $io->writeError(sprintf('<%s>Found %d package%s matching filters%s', $style, count($filteredPackages), $plurality, $punctuation, $style)); if ($format !== self::FORMAT_SUMMARY) { $this->outputFilteredPackages($io, $filteredPackages, $format); } } return $auditResult; } public function needsCompleteAdvisoryLoad(array $advisories, array $ignoreList): bool { if (\count($advisories) === 0) { return false; } if (array_all($advisories, static function (array $pkgAdvisories) { return array_all($pkgAdvisories, static function ($advisory) { return $advisory instanceof SecurityAdvisory; }); })) { return false; } $ignoredIds = array_keys($ignoreList); return array_any($ignoredIds, static function (string $id) { return !str_starts_with($id, 'PKSA-'); }); } public function filterAbandonedPackages(array $packages, array $ignoreAbandoned): array { $filter = null; if (\count($ignoreAbandoned) !== 0) { $filter = BasePackage::packageNamesToRegexp(array_keys($ignoreAbandoned)); } return array_filter($packages, static function (PackageInterface $pkg) use ($filter): bool { return $pkg instanceof CompletePackageInterface && $pkg->isAbandoned() && ($filter === null || !Preg::isMatch($filter, $pkg->getName())); }); } public function processAdvisories(array $allAdvisories, array $ignoreList, array $ignoredSeverities): array { if ($ignoreList === [] && $ignoredSeverities === []) { return ['advisories' => $allAdvisories, 'ignoredAdvisories' => []]; } $advisories = []; $ignored = []; $ignoreReason = null; foreach ($allAdvisories as $package => $pkgAdvisories) { foreach ($pkgAdvisories as $advisory) { $isActive = true; if (array_key_exists($package, $ignoreList)) { $isActive = false; $ignoreReason = $ignoreList[$package] ?? null; } if (array_key_exists($advisory->advisoryId, $ignoreList)) { $isActive = false; $ignoreReason = $ignoreList[$advisory->advisoryId] ?? null; } if ($advisory instanceof SecurityAdvisory) { if (is_string($advisory->severity) && array_key_exists($advisory->severity, $ignoredSeverities)) { $isActive = false; $ignoreReason = $ignoredSeverities[$advisory->severity] ?? $advisory->severity.' severity is ignored'; } if (is_string($advisory->cve) && array_key_exists($advisory->cve, $ignoreList)) { $isActive = false; $ignoreReason = $ignoreList[$advisory->cve] ?? null; } foreach ($advisory->sources as $source) { if (array_key_exists($source['remoteId'], $ignoreList)) { $isActive = false; $ignoreReason = $ignoreList[$source['remoteId']] ?? null; break; } } } if ($isActive) { $advisories[$package][] = $advisory; continue; } if ($advisory instanceof SecurityAdvisory) { $advisory = $advisory->toIgnoredAdvisory($ignoreReason); } $ignored[$package][] = $advisory; } } return ['advisories' => $advisories, 'ignoredAdvisories' => $ignored]; } private function countAdvisories(array $advisories): array { $count = 0; foreach ($advisories as $packageAdvisories) { $count += count($packageAdvisories); } return [count($advisories), $count]; } private function outputAdvisories(IOInterface $io, array $advisories, string $format): void { switch ($format) { case self::FORMAT_TABLE: if (!($io instanceof ConsoleIO)) { throw new InvalidArgumentException('Cannot use table format with ' . get_class($io)); } $this->outputAdvisoriesTable($io, $advisories); return; case self::FORMAT_PLAIN: $this->outputAdvisoriesPlain($io, $advisories); return; case self::FORMAT_SUMMARY: return; default: throw new InvalidArgumentException('Invalid format "'.$format.'".'); } } private function outputAdvisoriesTable(ConsoleIO $io, array $advisories): void { foreach ($advisories as $packageAdvisories) { foreach ($packageAdvisories as $advisory) { $headers = [ 'Package', 'Severity', 'Advisory ID', 'CVE', 'Title', 'URL', 'Affected versions', 'Reported at', ]; $row = [ $advisory->packageName, $this->getSeverity($advisory), $this->getAdvisoryId($advisory), $this->getCVE($advisory), $advisory->title, $this->getURL($advisory), $advisory->affectedVersions->getPrettyString(), $advisory->reportedAt->format(DATE_ATOM), ]; if ($advisory instanceof IgnoredSecurityAdvisory) { $headers[] = 'Ignore reason'; $row[] = $advisory->ignoreReason ?? 'None specified'; } $io->getTable() ->setHorizontal() ->setHeaders($headers) ->addRow(ConsoleIO::sanitize($row)) ->setColumnWidth(1, 80) ->setColumnMaxWidth(1, 80) ->render(); } } } private function outputAdvisoriesPlain(IOInterface $io, array $advisories): void { $error = []; $firstAdvisory = true; foreach ($advisories as $packageAdvisories) { foreach ($packageAdvisories as $advisory) { if (!$firstAdvisory) { $error[] = '--------'; } $error[] = "Package: ".$advisory->packageName; $error[] = "Severity: ".$this->getSeverity($advisory); $error[] = "Advisory ID: ".$this->getAdvisoryId($advisory); $error[] = "CVE: ".$this->getCVE($advisory); $error[] = "Title: ".OutputFormatter::escape($advisory->title); $error[] = "URL: ".$this->getURL($advisory); $error[] = "Affected versions: ".OutputFormatter::escape($advisory->affectedVersions->getPrettyString()); $error[] = "Reported at: ".$advisory->reportedAt->format(DATE_ATOM); if ($advisory instanceof IgnoredSecurityAdvisory) { $error[] = "Ignore reason: ".($advisory->ignoreReason ?? 'None specified'); } $firstAdvisory = false; } } $io->writeError($error); } private function outputAbandonedPackages(IOInterface $io, array $packages, string $format): void { $io->writeError(sprintf('Found %d abandoned package%s:', count($packages), count($packages) > 1 ? 's' : '')); if ($format === self::FORMAT_PLAIN) { foreach ($packages as $pkg) { $replacement = $pkg->getReplacementPackage() !== null ? 'Use '.$pkg->getReplacementPackage().' instead' : 'No replacement was suggested'; $io->writeError(sprintf( '%s is abandoned. %s.', $this->getPackageNameWithLink($pkg), $replacement )); } return; } if (!($io instanceof ConsoleIO)) { throw new InvalidArgumentException('Cannot use table format with ' . get_class($io)); } $table = $io->getTable() ->setHeaders(['Abandoned Package', 'Suggested Replacement']) ->setColumnWidth(1, 80) ->setColumnMaxWidth(1, 80); foreach ($packages as $pkg) { $replacement = $pkg->getReplacementPackage() !== null ? $pkg->getReplacementPackage() : 'none'; $table->addRow(ConsoleIO::sanitize([$this->getPackageNameWithLink($pkg), $replacement])); } $table->render(); } private function getPackageNameWithLink(PackageInterface $package): string { $packageUrl = PackageInfo::getViewSourceOrHomepageUrl($package); return $packageUrl !== null ? '' . $package->getPrettyName() . '' : $package->getPrettyName(); } private function getSeverity(SecurityAdvisory $advisory): string { if ($advisory->severity === null) { return ''; } return $advisory->severity; } private function getAdvisoryId(SecurityAdvisory $advisory): string { if (str_starts_with($advisory->advisoryId, 'PKSA-')) { return 'advisoryId.'>'.$advisory->advisoryId.''; } return $advisory->advisoryId; } private function getCVE(SecurityAdvisory $advisory): string { if ($advisory->cve === null) { return 'NO CVE'; } return ''.$advisory->cve.''; } private function getURL(SecurityAdvisory $advisory): string { if ($advisory->link === null) { return ''; } return 'link).'>'.OutputFormatter::escape($advisory->link).''; } private function outputFilteredPackages(IOInterface $io, array $filteredPackages, string $format): void { if ($format === self::FORMAT_PLAIN) { foreach ($filteredPackages as $data) { foreach ($data as $entry) { $parts = [ $entry->packageName . ' matched dependency policy "' . $entry->listName . '"', ]; if ($entry->reason !== null) { $parts[] = 'Reason: ' . $entry->reason; } if ($entry->url !== null) { $parts[] = 'URL: ' . $entry->url; } $io->writeError(implode('. ', $parts) . '.'); } } return; } if (!($io instanceof ConsoleIO)) { throw new InvalidArgumentException('Cannot use table format with ' . get_class($io)); } $table = $io->getTable() ->setHeaders(['Package', 'Versions', 'List', 'URL', 'Reason', 'ID', 'Source']) ->setColumnMaxWidth(4, 40) ; foreach ($filteredPackages as $data) { foreach ($data as $entry) { $table->addRow(ConsoleIO::sanitize([ $entry->packageName, $entry->constraint->getPrettyString(), $entry->listName, $entry->url ?? '', $entry->reason ?? '', $entry->id ?? '', $entry->source ?? '', ])); } } $table->render(); } } ignoreReason = $ignoreReason; } #[\ReturnTypeWillChange] public function jsonSerialize() { $data = parent::jsonSerialize(); if ($this->ignoreReason === null) { unset($data['ignoreReason']); } return $data; } } parseConstraints($data['affectedVersions']); } catch (\UnexpectedValueException $e) { try { $affectedVersion = Preg::replace('{(^[>=<^~]*[\d.]+).*}', '$1', $data['affectedVersions']); $constraint = $parser->parseConstraints($affectedVersion); } catch (\UnexpectedValueException $e) { $constraint = new Constraint('==', '0.0.0-invalid-version'); } } if (isset($data['title'], $data['sources'], $data['reportedAt'])) { return new SecurityAdvisory($packageName, $data['advisoryId'], $constraint, $data['title'], $data['sources'], new \DateTimeImmutable($data['reportedAt'], new \DateTimeZone('UTC')), $data['cve'] ?? null, $data['link'] ?? null, $data['severity'] ?? null); } return new self($packageName, $data['advisoryId'], $constraint); } public function __construct(string $packageName, string $advisoryId, ConstraintInterface $affectedVersions) { $this->advisoryId = $advisoryId; $this->packageName = $packageName; $this->affectedVersions = $affectedVersions; } #[\ReturnTypeWillChange] public function jsonSerialize() { $data = (array) $this; $data['affectedVersions'] = $data['affectedVersions']->getPrettyString(); return $data; } } title = $title; $this->sources = $sources; $this->reportedAt = $reportedAt; $this->cve = $cve; $this->link = $link; $this->severity = $severity; } public function toIgnoredAdvisory(?string $ignoreReason): IgnoredSecurityAdvisory { return new IgnoredSecurityAdvisory( $this->packageName, $this->advisoryId, $this->affectedVersions, $this->title, $this->sources, $this->reportedAt, $this->cve, $this->link, $ignoreReason, $this->severity ); } #[\ReturnTypeWillChange] public function jsonSerialize() { $data = parent::jsonSerialize(); $data['reportedAt'] = $data['reportedAt']->format(DATE_RFC3339); return $data; } } eventDispatcher = $eventDispatcher; $this->io = $io ?? new NullIO(); $this->platformRequirementFilter = PlatformRequirementFilterFactory::ignoreNothing(); } public function setDevMode(bool $devMode = true) { $this->devMode = $devMode; } public function setClassMapAuthoritative(bool $classMapAuthoritative) { $this->classMapAuthoritative = $classMapAuthoritative; } public function setApcu(bool $apcu, ?string $apcuPrefix = null) { $this->apcu = $apcu; $this->apcuPrefix = $apcuPrefix; } public function setRunScripts(bool $runScripts = true) { $this->runScripts = $runScripts; } public function setDryRun(bool $dryRun = true): void { $this->dryRun = $dryRun; } public function setIgnorePlatformRequirements($ignorePlatformReqs) { trigger_error('AutoloadGenerator::setIgnorePlatformRequirements is deprecated since Composer 2.2, use setPlatformRequirementFilter instead.', E_USER_DEPRECATED); $this->setPlatformRequirementFilter(PlatformRequirementFilterFactory::fromBoolOrList($ignorePlatformReqs)); } public function setPlatformRequirementFilter(PlatformRequirementFilterInterface $platformRequirementFilter) { $this->platformRequirementFilter = $platformRequirementFilter; } public function dump(Config $config, InstalledRepositoryInterface $localRepo, RootPackageInterface $rootPackage, InstallationManager $installationManager, string $targetDir, bool $scanPsrPackages = false, ?string $suffix = null, ?Locker $locker = null, bool $strictAmbiguous = false) { if ($this->classMapAuthoritative) { $scanPsrPackages = true; } if (null === $this->devMode) { $this->devMode = false; $installedJson = new JsonFile($config->get('vendor-dir').'/composer/installed.json'); if ($installedJson->exists()) { $installedJson = $installedJson->read(); if (isset($installedJson['dev'])) { $this->devMode = $installedJson['dev']; } } } if ($this->runScripts) { if (!isset($_SERVER['COMPOSER_DEV_MODE'])) { Platform::putEnv('COMPOSER_DEV_MODE', $this->devMode ? '1' : '0'); } $this->eventDispatcher->dispatchScript(ScriptEvents::PRE_AUTOLOAD_DUMP, $this->devMode, [], [ 'optimize' => $scanPsrPackages, ]); } $classMapGenerator = new ClassMapGenerator(['php', 'inc', 'hh']); $classMapGenerator->avoidDuplicateScans(); $filesystem = new Filesystem(); $filesystem->ensureDirectoryExists($config->get('vendor-dir')); $basePath = $filesystem->normalizePath(realpath(realpath(Platform::getCwd()))); $vendorPath = $filesystem->normalizePath(realpath(realpath($config->get('vendor-dir')))); $useGlobalIncludePath = $config->get('use-include-path'); $prependAutoloader = $config->get('prepend-autoloader') === false ? 'false' : 'true'; $targetDir = $vendorPath.'/'.$targetDir; $filesystem->ensureDirectoryExists($targetDir); $vendorPathCode = $filesystem->findShortestPathCode(realpath($targetDir), $vendorPath, true); $vendorPathToTargetDirCode = $filesystem->findShortestPathCode($vendorPath, realpath($targetDir), true); $appBaseDirCode = $filesystem->findShortestPathCode($vendorPath, $basePath, true); $appBaseDirCode = str_replace('__DIR__', '$vendorDir', $appBaseDirCode); $namespacesFile = <<getDevPackageNames(); $packageMap = $this->buildPackageMap($installationManager, $rootPackage, $localRepo->getCanonicalPackages()); if ($this->devMode) { $filteredDevPackages = false; } else { $filteredDevPackages = $devPackageNames ?: true; } $autoloads = $this->parseAutoloads($packageMap, $rootPackage, $filteredDevPackages); foreach ($autoloads['psr-0'] as $namespace => $paths) { $exportedPaths = []; foreach ($paths as $path) { $exportedPaths[] = $this->getPathCode($filesystem, $basePath, $vendorPath, $path); } $exportedPrefix = var_export($namespace, true); $namespacesFile .= " $exportedPrefix => "; $namespacesFile .= "array(".implode(', ', $exportedPaths)."),\n"; } $namespacesFile .= ");\n"; foreach ($autoloads['psr-4'] as $namespace => $paths) { $exportedPaths = []; foreach ($paths as $path) { $exportedPaths[] = $this->getPathCode($filesystem, $basePath, $vendorPath, $path); } $exportedPrefix = var_export($namespace, true); $psr4File .= " $exportedPrefix => "; $psr4File .= "array(".implode(', ', $exportedPaths)."),\n"; } $psr4File .= ");\n"; $targetDirLoader = null; $mainAutoload = $rootPackage->getAutoload(); if ($rootPackage->getTargetDir() && !empty($mainAutoload['psr-0'])) { $levels = substr_count($filesystem->normalizePath($rootPackage->getTargetDir()), '/') + 1; $prefixes = implode(', ', array_map(static function ($prefix): string { return var_export($prefix, true); }, array_keys($mainAutoload['psr-0']))); $baseDirFromTargetDirCode = $filesystem->findShortestPathCode($targetDir, $basePath, true); $targetDirLoader = <<scanPaths($dir, $this->buildExclusionRegex($dir, $excluded)); } if ($scanPsrPackages) { $namespacesToScan = []; foreach (['psr-4', 'psr-0'] as $psrType) { foreach ($autoloads[$psrType] as $namespace => $paths) { $namespacesToScan[$namespace][] = ['paths' => $paths, 'type' => $psrType]; } } krsort($namespacesToScan); foreach ($namespacesToScan as $namespace => $groups) { foreach ($groups as $group) { foreach ($group['paths'] as $dir) { $dir = $filesystem->normalizePath($filesystem->isAbsolutePath($dir) ? $dir : $basePath.'/'.$dir); if (!is_dir($dir)) { continue; } if (str_contains($vendorPath, $dir.'/')) { $exclusionRegex = $this->buildExclusionRegex($dir, array_merge($excluded, [$vendorPath.'/'])); } else { $exclusionRegex = $this->buildExclusionRegex($dir, $excluded); } $classMapGenerator->scanPaths($dir, $exclusionRegex, $group['type'], $namespace); } } } } $classMap = $classMapGenerator->getClassMap(); if ($strictAmbiguous) { $ambiguousClasses = $classMap->getAmbiguousClasses(false); } else { $ambiguousClasses = $classMap->getAmbiguousClasses(); } foreach ($ambiguousClasses as $className => $ambiguousPaths) { if (count($ambiguousPaths) > 1) { $this->io->writeError( 'Warning: Ambiguous class resolution, "'.$className.'"'. ' was found '. (count($ambiguousPaths) + 1) .'x: in "'.$classMap->getClassPath($className).'" and "'. implode('", "', $ambiguousPaths) .'", the first will be used.' ); } else { $this->io->writeError( 'Warning: Ambiguous class resolution, "'.$className.'"'. ' was found in both "'.$classMap->getClassPath($className).'" and "'. implode('", "', $ambiguousPaths) .'", the first will be used.' ); } } if (\count($ambiguousClasses) > 0) { $this->io->writeError('To resolve ambiguity in classes not under your control you can ignore them by path using exclude-from-classmap'); } $classMap->clearPsrViolationsByPath($vendorPath); foreach ($classMap->getPsrViolations() as $msg) { $this->io->writeError("$msg"); } $classMap->addClass('Composer\InstalledVersions', $vendorPath . '/composer/InstalledVersions.php'); $classMap->sort(); $classmapFile = <<getMap() as $className => $path) { $pathCode = $this->getPathCode($filesystem, $basePath, $vendorPath, $path).",\n"; $classmapFile .= ' '.var_export($className, true).' => '.$pathCode; } $classmapFile .= ");\n"; if ('' === $suffix) { $suffix = null; } if (null === $suffix) { $suffix = $config->get('autoloader-suffix'); if (null === $suffix && Filesystem::isReadable($vendorPath.'/autoload.php')) { $content = (string) file_get_contents($vendorPath.'/autoload.php'); if (Preg::isMatch('{ComposerAutoloaderInit([^:\s]+)::}', $content, $match)) { $suffix = $match[1]; } } if (null === $suffix) { $suffix = $locker !== null && $locker->isLocked() ? $locker->getLockData()['content-hash'] : bin2hex(random_bytes(16)); } } if ($this->dryRun) { return $classMap; } $filesystem->filePutContentsIfModified($targetDir.'/autoload_namespaces.php', $namespacesFile); $filesystem->filePutContentsIfModified($targetDir.'/autoload_psr4.php', $psr4File); $filesystem->filePutContentsIfModified($targetDir.'/autoload_classmap.php', $classmapFile); $includePathFilePath = $targetDir.'/include_paths.php'; if ($includePathFileContents = $this->getIncludePathsFile($packageMap, $filesystem, $basePath, $vendorPath, $vendorPathCode, $appBaseDirCode)) { $filesystem->filePutContentsIfModified($includePathFilePath, $includePathFileContents); } elseif (file_exists($includePathFilePath)) { unlink($includePathFilePath); } $includeFilesFilePath = $targetDir.'/autoload_files.php'; if ($includeFilesFileContents = $this->getIncludeFilesFile($autoloads['files'], $filesystem, $basePath, $vendorPath, $vendorPathCode, $appBaseDirCode)) { $filesystem->filePutContentsIfModified($includeFilesFilePath, $includeFilesFileContents); } elseif (file_exists($includeFilesFilePath)) { unlink($includeFilesFilePath); } $filesystem->filePutContentsIfModified($targetDir.'/autoload_static.php', $this->getStaticFile($suffix, $targetDir, $vendorPath, $basePath)); $checkPlatform = $config->get('platform-check') !== false && !($this->platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter); $platformCheckContent = null; if ($checkPlatform) { $platformCheckContent = $this->getPlatformCheck($packageMap, $config->get('platform-check'), $devPackageNames); if (null === $platformCheckContent) { $checkPlatform = false; } } if ($checkPlatform) { $filesystem->filePutContentsIfModified($targetDir.'/platform_check.php', $platformCheckContent); } elseif (file_exists($targetDir.'/platform_check.php')) { unlink($targetDir.'/platform_check.php'); } $filesystem->filePutContentsIfModified($vendorPath.'/autoload.php', $this->getAutoloadFile($vendorPathToTargetDirCode, $suffix)); $filesystem->filePutContentsIfModified($targetDir.'/autoload_real.php', $this->getAutoloadRealFile(true, (bool) $includePathFileContents, $targetDirLoader, (bool) $includeFilesFileContents, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader, $checkPlatform)); $filesystem->safeCopy(__DIR__.'/ClassLoader.php', $targetDir.'/ClassLoader.php'); $filesystem->safeCopy(__DIR__.'/../../../LICENSE', $targetDir.'/LICENSE'); if ($this->runScripts) { $this->eventDispatcher->dispatchScript(ScriptEvents::POST_AUTOLOAD_DUMP, $this->devMode, [], [ 'optimize' => $scanPsrPackages, ]); } return $classMap; } private function buildExclusionRegex(string $dir, array $excluded): ?string { if ([] === $excluded) { return null; } if (file_exists($dir)) { $dirMatch = preg_quote(strtr(realpath($dir), '\\', '/')); $fs = new Filesystem(); $absDir = $fs->isAbsolutePath($dir) ? $dir : realpath(Platform::getCwd()).'/'.$dir; $dirMatchNormalized = preg_quote(strtr($fs->normalizePath($absDir), '\\', '/')); $isSymlink = $dirMatch !== $dirMatchNormalized; foreach ($excluded as $index => $pattern) { $pattern = Preg::replace('{^(([^.+*?\[^\]$(){}=!<>|:\\\\#-]+|\\\\[.+*?\[^\]$(){}=!<>|:#-])*).*}', '$1', $pattern); if ( (!str_starts_with($pattern, $dirMatch) && !str_starts_with($dirMatch, $pattern)) && ( !$isSymlink || (!str_starts_with($pattern, $dirMatchNormalized) && !str_starts_with($dirMatchNormalized, $pattern)) ) ) { unset($excluded[$index]); } } } return \count($excluded) > 0 ? '{(' . implode('|', $excluded) . ')}' : null; } public function buildPackageMap(InstallationManager $installationManager, PackageInterface $rootPackage, array $packages) { $packageMap = [[$rootPackage, '']]; foreach ($packages as $package) { if ($package instanceof AliasPackage) { continue; } $this->validatePackage($package); $packageMap[] = [ $package, $installationManager->getInstallPath($package), ]; } return $packageMap; } protected function validatePackage(PackageInterface $package) { $autoload = $package->getAutoload(); if (!empty($autoload['psr-4']) && null !== $package->getTargetDir()) { $name = $package->getName(); $package->getTargetDir(); throw new \InvalidArgumentException("PSR-4 autoloading is incompatible with the target-dir property, remove the target-dir in package '$name'."); } if (!empty($autoload['psr-4'])) { foreach ($autoload['psr-4'] as $namespace => $dirs) { if ($namespace !== '' && '\\' !== substr($namespace, -1)) { throw new \InvalidArgumentException("psr-4 namespaces must end with a namespace separator, '$namespace' does not, use '$namespace\\'."); } } } } public function parseAutoloads(array $packageMap, PackageInterface $rootPackage, $filteredDevPackages = false) { if (isset($packageMap[0][0]) && $packageMap[0][0] instanceof RootPackageInterface) { $rootPackageMap = array_shift($packageMap); } else { $rootPackageMap = null; } if (is_array($filteredDevPackages)) { $packageMap = array_filter($packageMap, static function ($item) use ($filteredDevPackages): bool { return !in_array($item[0]->getName(), $filteredDevPackages, true); }); } elseif ($filteredDevPackages) { $packageMap = $this->filterPackageMap($packageMap, $rootPackage); } $sortedPackageMap = $this->sortPackageMap($packageMap); if ($rootPackageMap !== null) { $sortedPackageMap[] = $rootPackageMap; } $reverseSortedMap = array_reverse($sortedPackageMap); $psr0 = $this->parseAutoloadsType($reverseSortedMap, 'psr-0', $rootPackage); $psr4 = $this->parseAutoloadsType($reverseSortedMap, 'psr-4', $rootPackage); $classmap = $this->parseAutoloadsType($reverseSortedMap, 'classmap', $rootPackage); $files = $this->parseAutoloadsType($sortedPackageMap, 'files', $rootPackage); $exclude = $this->parseAutoloadsType($sortedPackageMap, 'exclude-from-classmap', $rootPackage); krsort($psr0); krsort($psr4); return [ 'psr-0' => $psr0, 'psr-4' => $psr4, 'classmap' => $classmap, 'files' => $files, 'exclude-from-classmap' => $exclude, ]; } public function createLoader(array $autoloads, ?string $vendorDir = null) { $loader = new ClassLoader($vendorDir); if (isset($autoloads['psr-0'])) { foreach ($autoloads['psr-0'] as $namespace => $path) { $loader->add($namespace, $path); } } if (isset($autoloads['psr-4'])) { foreach ($autoloads['psr-4'] as $namespace => $path) { $loader->addPsr4($namespace, $path); } } if (isset($autoloads['classmap'])) { $excluded = []; if (!empty($autoloads['exclude-from-classmap'])) { $excluded = $autoloads['exclude-from-classmap']; } $classMapGenerator = new ClassMapGenerator(['php', 'inc', 'hh']); $classMapGenerator->avoidDuplicateScans(); foreach ($autoloads['classmap'] as $dir) { try { $classMapGenerator->scanPaths($dir, $this->buildExclusionRegex($dir, $excluded)); } catch (\RuntimeException $e) { $this->io->writeError(''.$e->getMessage().''); } } $loader->addClassMap($classMapGenerator->getClassMap()->getMap()); } return $loader; } protected function getIncludePathsFile(array $packageMap, Filesystem $filesystem, string $basePath, string $vendorPath, string $vendorPathCode, string $appBaseDirCode) { $includePaths = []; foreach ($packageMap as $item) { [$package, $installPath] = $item; if (null === $installPath) { continue; } if (null !== $package->getTargetDir() && strlen($package->getTargetDir()) > 0) { $installPath = substr($installPath, 0, -strlen('/'.$package->getTargetDir())); } foreach ($package->getIncludePaths() as $includePath) { $includePath = trim($includePath, '/'); $includePaths[] = $installPath === '' ? $includePath : $installPath.'/'.$includePath; } } if (\count($includePaths) === 0) { return null; } $includePathsCode = ''; foreach ($includePaths as $path) { $includePathsCode .= " " . $this->getPathCode($filesystem, $basePath, $vendorPath, $path) . ",\n"; } return <<getPathCode($filesystem, $basePath, $vendorPath, $functionFile); }, $files ); $uniqueFiles = array_unique($files); if (count($uniqueFiles) < count($files)) { $this->io->writeError('The following "files" autoload rules are included multiple times, this may cause issues and should be resolved:'); foreach (array_unique(array_diff_assoc($files, $uniqueFiles)) as $duplicateFile) { $this->io->writeError(' - '.$duplicateFile.''); } } unset($uniqueFiles); $filesCode = ''; foreach ($files as $fileIdentifier => $functionFile) { $filesCode .= ' ' . var_export($fileIdentifier, true) . ' => ' . $functionFile . ",\n"; } if (!$filesCode) { return null; } return <<isAbsolutePath($path)) { $path = $basePath . '/' . $path; } $path = $filesystem->normalizePath($path); $baseDir = ''; if (strpos($path.'/', $vendorPath.'/') === 0) { $path = (string) substr($path, strlen($vendorPath)); $baseDir = '$vendorDir . '; } else { $path = $filesystem->normalizePath($filesystem->findShortestPath($basePath, $path, true)); if (!$filesystem->isAbsolutePath($path)) { $baseDir = '$baseDir . '; $path = '/' . $path; } } if (Preg::isMatch('{\.phar([\\\\/]|$)}', $path)) { $baseDir = "'phar://' . " . $baseDir; } return $baseDir . var_export($path, true); } protected function getPlatformCheck(array $packageMap, $checkPlatform, array $devPackageNames) { $lowestPhpVersion = Bound::zero(); $requiredPhp64bit = false; $requiredExtensions = []; $extensionProviders = []; foreach ($packageMap as $item) { $package = $item[0]; foreach (array_merge($package->getReplaces(), $package->getProvides()) as $link) { if (Preg::isMatch('{^ext-(.+)$}iD', $link->getTarget(), $match)) { $extensionProviders[$match[1]][] = $link->getConstraint(); } } } foreach ($packageMap as $item) { $package = $item[0]; if (in_array($package->getName(), $devPackageNames, true)) { continue; } foreach ($package->getRequires() as $link) { if ($this->platformRequirementFilter->isIgnored($link->getTarget())) { continue; } if (in_array($link->getTarget(), ['php', 'php-64bit'], true)) { $constraint = $link->getConstraint(); if ($constraint->getLowerBound()->compareTo($lowestPhpVersion, '>')) { $lowestPhpVersion = $constraint->getLowerBound(); } } if ('php-64bit' === $link->getTarget()) { $requiredPhp64bit = true; } if ($checkPlatform === true && Preg::isMatch('{^ext-(.+)$}iD', $link->getTarget(), $match)) { if (isset($extensionProviders[$match[1]])) { foreach ($extensionProviders[$match[1]] as $provided) { if ($provided->matches($link->getConstraint())) { continue 2; } } } if ($match[1] === 'zend-opcache') { $match[1] = 'zend opcache'; } $extension = var_export($match[1], true); if ($match[1] === 'pcntl' || $match[1] === 'readline') { $requiredExtensions[$extension] = "PHP_SAPI !== 'cli' || extension_loaded($extension) || \$missingExtensions[] = $extension;\n"; } else { $requiredExtensions[$extension] = "extension_loaded($extension) || \$missingExtensions[] = $extension;\n"; } } } } ksort($requiredExtensions); $formatToPhpVersionId = static function (Bound $bound): int { if ($bound->isZero()) { return 0; } if ($bound->isPositiveInfinity()) { return 99999; } $version = str_replace('-', '.', $bound->getVersion()); $chunks = array_map('intval', explode('.', $version)); return $chunks[0] * 10000 + $chunks[1] * 100 + $chunks[2]; }; $formatToHumanReadable = static function (Bound $bound) { if ($bound->isZero()) { return 0; } if ($bound->isPositiveInfinity()) { return 99999; } $version = str_replace('-', '.', $bound->getVersion()); $chunks = explode('.', $version); $chunks = array_slice($chunks, 0, 3); return implode('.', $chunks); }; $requiredPhp = ''; $requiredPhpError = ''; if (!$lowestPhpVersion->isZero()) { $operator = $lowestPhpVersion->isInclusive() ? '>=' : '>'; $requiredPhp = 'PHP_VERSION_ID '.$operator.' '.$formatToPhpVersionId($lowestPhpVersion); $requiredPhpError = '"'.$operator.' '.$formatToHumanReadable($lowestPhpVersion).'"'; } if ($requiredPhp) { $requiredPhp = <<classMapAuthoritative) { $file .= <<<'CLASSMAPAUTHORITATIVE' $loader->setClassMapAuthoritative(true); CLASSMAPAUTHORITATIVE; } if ($this->apcu) { $apcuPrefix = var_export(($this->apcuPrefix !== null ? $this->apcuPrefix : bin2hex(random_bytes(10))), true); $file .= <<setApcuPrefix($apcuPrefix); APCU; } if ($useGlobalIncludePath) { $file .= <<<'INCLUDEPATH' $loader->setUseIncludePath(true); INCLUDEPATH; } if ($targetDirLoader) { $file .= <<register($prependAutoloader); REGISTER_LOADER; if ($useIncludeFiles) { $file .= << \$file) { \$requireFile(\$fileIdentifier, \$file); } INCLUDE_FILES; } $file .= << $path) { $loader->set($namespace, $path); } $map = require $targetDir . '/autoload_psr4.php'; foreach ($map as $namespace => $path) { $loader->setPsr4($namespace, $path); } $classMap = require $targetDir . '/autoload_classmap.php'; if ($classMap) { $loader->addClassMap($classMap); } $filesystem = new Filesystem(); $vendorPathCode = ' => ' . $filesystem->findShortestPathCode(realpath($targetDir), $vendorPath, true, true) . " . '/"; $vendorPharPathCode = ' => \'phar://\' . ' . $filesystem->findShortestPathCode(realpath($targetDir), $vendorPath, true, true) . " . '/"; $appBaseDirCode = ' => ' . $filesystem->findShortestPathCode(realpath($targetDir), $basePath, true, true) . " . '/"; $appBaseDirPharCode = ' => \'phar://\' . ' . $filesystem->findShortestPathCode(realpath($targetDir), $basePath, true, true) . " . '/"; $absoluteVendorPathCode = ' => ' . substr(var_export(rtrim($vendorDir, '\\/') . '/', true), 0, -1); $absoluteVendorPharPathCode = ' => ' . substr(var_export(rtrim('phar://' . $vendorDir, '\\/') . '/', true), 0, -1); $absoluteAppBaseDirCode = ' => ' . substr(var_export(rtrim($baseDir, '\\/') . '/', true), 0, -1); $absoluteAppBaseDirPharCode = ' => ' . substr(var_export(rtrim('phar://' . $baseDir, '\\/') . '/', true), 0, -1); $initializer = ''; $prefix = "\0Composer\Autoload\ClassLoader\0"; $prefixLen = strlen($prefix); if (file_exists($targetDir . '/autoload_files.php')) { $maps = ['files' => require $targetDir . '/autoload_files.php']; } else { $maps = []; } foreach ((array) $loader as $prop => $value) { if (!is_array($value) || \count($value) === 0 || !str_starts_with($prop, $prefix)) { continue; } $maps[substr($prop, $prefixLen)] = $value; } foreach ($maps as $prop => $value) { $value = strtr( var_export($value, true), [ $absoluteVendorPathCode => $vendorPathCode, $absoluteVendorPharPathCode => $vendorPharPathCode, $absoluteAppBaseDirCode => $appBaseDirCode, $absoluteAppBaseDirPharCode => $appBaseDirPharCode, ] ); $value = ltrim(Preg::replace('/^ */m', ' $0$0', $value)); $value = Preg::replace('/ +$/m', '', $value); $file .= sprintf(" public static $%s = %s;\n\n", $prop, $value); if ('files' !== $prop) { $initializer .= " \$loader->$prop = ComposerStaticInit$suffix::\$$prop;\n"; } } return $file . <<getAutoload(); if ($this->devMode && $package === $rootPackage) { $autoload = array_merge_recursive($autoload, $package->getDevAutoload()); } if (!isset($autoload[$type]) || !is_array($autoload[$type])) { continue; } if (null !== $package->getTargetDir() && $package !== $rootPackage) { $installPath = substr($installPath, 0, -strlen('/'.$package->getTargetDir())); } foreach ($autoload[$type] as $namespace => $paths) { if (in_array($type, ['psr-4', 'psr-0'], true)) { $namespace = ltrim($namespace, '\\'); } foreach ((array) $paths as $path) { if (($type === 'files' || $type === 'classmap' || $type === 'exclude-from-classmap') && $package->getTargetDir() && !Filesystem::isReadable($installPath.'/'.$path)) { if ($package === $rootPackage) { $targetDir = str_replace('\\', '[\\\\/]', preg_quote(str_replace(['/', '\\'], '', $package->getTargetDir()))); $path = ltrim(Preg::replace('{^'.$targetDir.'}', '', ltrim($path, '\\/')), '\\/'); } else { $path = $package->getTargetDir() . '/' . $path; } } if ($type === 'exclude-from-classmap') { $path = Preg::replace('{/+}', '/', preg_quote(trim(strtr($path, '\\', '/'), '/'))); $path = strtr($path, ['\\*\\*' => '.+?', '\\*' => '[^/]+?']); $updir = null; $path = Preg::replaceCallback( '{^((?:(?:\\\\\\.){1,2}+/)+)}', static function ($matches) use (&$updir): string { $updir = str_replace('\\.', '.', $matches[1]); return ''; }, $path ); if (empty($installPath)) { $installPath = strtr(Platform::getCwd(), '\\', '/'); } $resolvedPath = realpath($installPath . '/' . $updir); if (false === $resolvedPath) { continue; } $autoloads[] = preg_quote(strtr($resolvedPath, '\\', '/')) . '/' . $path . '($|/)'; continue; } $relativePath = empty($installPath) ? (empty($path) ? '.' : $path) : $installPath.'/'.$path; if ($type === 'files') { $autoloads[$this->getFileIdentifier($package, $path)] = $relativePath; continue; } if ($type === 'classmap') { $autoloads[] = $relativePath; continue; } $autoloads[$namespace][] = $relativePath; } } } return $autoloads; } protected function getFileIdentifier(PackageInterface $package, string $path) { return hash('md5', $package->getName() . ':' . $path); } protected function filterPackageMap(array $packageMap, RootPackageInterface $rootPackage) { $packages = []; $include = []; $replacedBy = []; foreach ($packageMap as $item) { $package = $item[0]; $name = $package->getName(); $packages[$name] = $package; foreach ($package->getReplaces() as $replace) { $replacedBy[$replace->getTarget()] = $name; } } $add = static function (PackageInterface $package) use (&$add, $packages, &$include, $replacedBy): void { foreach ($package->getRequires() as $link) { $target = $link->getTarget(); if (isset($replacedBy[$target])) { $target = $replacedBy[$target]; } if (!isset($include[$target])) { $include[$target] = true; if (isset($packages[$target])) { $add($packages[$target]); } } } }; $add($rootPackage); return array_filter( $packageMap, static function ($item) use ($include): bool { $package = $item[0]; foreach ($package->getNames() as $name) { if (isset($include[$name])) { return true; } } return false; } ); } protected function sortPackageMap(array $packageMap) { $packages = []; $paths = []; foreach ($packageMap as $item) { [$package, $path] = $item; $name = $package->getName(); $packages[$name] = $package; $paths[$name] = $path; } $sortedPackages = PackageSorter::sortPackages($packages); $sortedPackageMap = []; foreach ($sortedPackages as $package) { $name = $package->getName(); $sortedPackageMap[] = [$packages[$name], $paths[$name]]; } return $sortedPackageMap; } } function composerRequire(string $fileIdentifier, string $file): void { if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; require $file; } } files = $scannedFiles; $generator->avoidDuplicateScans($fileList); $generator->scanPaths($path, $excluded, $autoloadType ?? 'classmap', $namespace); $classMap = $generator->getClassMap(); $scannedFiles = $fileList->files; if ($io !== null) { foreach ($classMap->getPsrViolations() as $msg) { $io->writeError("$msg"); } foreach ($classMap->getAmbiguousClasses() as $class => $paths) { if (count($paths) > 1) { $io->writeError( 'Warning: Ambiguous class resolution, "'.$class.'"'. ' was found '. (count($paths) + 1) .'x: in "'.$classMap->getClassPath($class).'" and "'. implode('", "', $paths) .'", the first will be used.' ); } else { $io->writeError( 'Warning: Ambiguous class resolution, "'.$class.'"'. ' was found in both "'.$classMap->getClassPath($class).'" and "'. implode('", "', $paths) .'", the first will be used.' ); } } } return $classMap->getMap(); } } io = $io; $this->root = rtrim($cacheDir, '/\\') . '/'; $this->allowlist = $allowlist; $this->filesystem = $filesystem ?: new Filesystem(); $this->readOnly = $readOnly; if (!self::isUsable($cacheDir)) { $this->enabled = false; } } public function setReadOnly(bool $readOnly) { $this->readOnly = $readOnly; } public function isReadOnly() { return $this->readOnly; } public static function isUsable(string $path) { return !Preg::isMatch('{(^|[\\\\/])(\$null|nul|NUL|/dev/null)([\\\\/]|$)}', $path); } public function isEnabled() { if ($this->enabled === null) { $this->enabled = true; if ( !$this->readOnly && ( (!is_dir($this->root) && !Silencer::call('mkdir', $this->root, 0777, true)) || !is_writable($this->root) ) ) { $this->io->writeError('Cannot create cache directory ' . $this->root . ', or directory is not writable. Proceeding without cache. See also cache-read-only config if your filesystem is read-only.'); $this->enabled = false; } } return $this->enabled; } public function getRoot() { return $this->root; } public function read(string $file) { if ($this->isEnabled()) { $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file); if (file_exists($this->root . $file)) { $this->io->writeError('Reading '.$this->root . $file.' from cache', true, IOInterface::DEBUG); return file_get_contents($this->root . $file); } } return false; } public function write(string $file, string $contents) { $wasEnabled = $this->enabled === true; if ($this->isEnabled() && !$this->readOnly) { $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file); $this->io->writeError('Writing '.$this->root . $file.' into cache', true, IOInterface::DEBUG); $tempFileName = $this->root . $file . bin2hex(random_bytes(5)) . '.tmp'; try { return file_put_contents($tempFileName, $contents) !== false && rename($tempFileName, $this->root . $file); } catch (\ErrorException $e) { if ($wasEnabled) { clearstatcache(); $this->enabled = null; return $this->write($file, $contents); } $this->io->writeError('Failed to write into cache: '.$e->getMessage().'', true, IOInterface::DEBUG); if (Preg::isMatch('{^file_put_contents\(\): Only ([0-9]+) of ([0-9]+) bytes written}', $e->getMessage(), $m)) { unlink($tempFileName); $message = sprintf( 'Writing %1$s into cache failed after %2$u of %3$u bytes written, only %4$s bytes of free space available', $tempFileName, $m[1], $m[2], function_exists('disk_free_space') ? @disk_free_space(dirname($tempFileName)) : 'unknown' ); $this->io->writeError($message); return false; } throw $e; } } return false; } public function copyFrom(string $file, string $source) { if ($this->isEnabled() && !$this->readOnly) { $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file); $this->filesystem->ensureDirectoryExists(dirname($this->root . $file)); if (!file_exists($source)) { $this->io->writeError(''.$source.' does not exist, can not write into cache'); } elseif ($this->io->isDebug()) { $this->io->writeError('Writing '.$this->root . $file.' into cache from '.$source); } return $this->filesystem->copy($source, $this->root . $file); } return false; } public function copyTo(string $file, string $target) { if ($this->isEnabled()) { $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file); if (file_exists($this->root . $file)) { try { touch($this->root . $file, (int) filemtime($this->root . $file), time()); } catch (\ErrorException $e) { Silencer::call('touch', $this->root . $file); } $this->io->writeError('Reading '.$this->root . $file.' from cache', true, IOInterface::DEBUG); return $this->filesystem->copy($this->root . $file, $target); } } return false; } public function gcIsNecessary() { if (self::$cacheCollected) { return false; } self::$cacheCollected = true; if (Platform::getEnv('COMPOSER_TEST_SUITE')) { return false; } if (Platform::isInputCompletionProcess()) { return false; } return !random_int(0, 50); } public function remove(string $file) { if ($this->isEnabled() && !$this->readOnly) { $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file); if (file_exists($this->root . $file)) { return $this->filesystem->unlink($this->root . $file); } } return false; } public function clear() { if ($this->isEnabled() && !$this->readOnly) { $this->filesystem->emptyDirectory($this->root); return true; } return false; } public function getAge(string $file) { if ($this->isEnabled()) { $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file); if (file_exists($this->root . $file) && ($mtime = filemtime($this->root . $file)) !== false) { return abs(time() - $mtime); } } return false; } public function gc(int $ttl, int $maxSize) { if ($this->isEnabled() && !$this->readOnly) { $expire = new \DateTime(); $expire->modify('-'.$ttl.' seconds'); $finder = $this->getFinder()->date('until '.$expire->format('Y-m-d H:i:s')); foreach ($finder as $file) { $this->filesystem->unlink($file->getPathname()); } $totalSize = $this->filesystem->size($this->root); if ($totalSize > $maxSize) { $iterator = $this->getFinder()->sortByAccessedTime()->getIterator(); while ($totalSize > $maxSize && $iterator->valid()) { $filepath = $iterator->current()->getPathname(); $totalSize -= $this->filesystem->size($filepath); $this->filesystem->unlink($filepath); $iterator->next(); } } self::$cacheCollected = true; return true; } return false; } public function gcVcsCache(int $ttl): bool { if ($this->isEnabled()) { $expire = new \DateTime(); $expire->modify('-'.$ttl.' seconds'); $finder = Finder::create()->in($this->root)->directories()->depth(0)->date('until '.$expire->format('Y-m-d H:i:s')); foreach ($finder as $file) { $this->filesystem->removeDirectory($file->getPathname()); } self::$cacheCollected = true; return true; } return false; } public function sha1(string $file) { if ($this->isEnabled()) { $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file); if (file_exists($this->root . $file)) { return hash_file('sha1', $this->root . $file); } } return false; } public function sha256(string $file) { if ($this->isEnabled()) { $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file); if (file_exists($this->root . $file)) { return hash_file('sha256', $this->root . $file); } } return false; } protected function getFinder() { return Finder::create()->in($this->root)->files(); } } setName('about') ->setDescription('Shows a short information about Composer') ->setHelp( <<php composer.phar about EOT ) ; } protected function execute(InputInterface $input, OutputInterface $output): int { $composerVersion = Composer::getVersion(); $this->getIO()->write( <<Composer - Dependency Manager for PHP - version $composerVersion Composer is a dependency manager tracking local dependencies of your projects and libraries. See https://getcomposer.org/ for more information. EOT ); return 0; } } setName('archive') ->setDescription('Creates an archive of this composer package') ->setDefinition([ new InputArgument('package', InputArgument::OPTIONAL, 'The package to archive instead of the current project', null, $this->suggestAvailablePackage()), new InputArgument('version', InputArgument::OPTIONAL, 'A version constraint to find the package to archive'), new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the resulting archive: tar, tar.gz, tar.bz2 or zip (default tar)', null, self::FORMATS), new InputOption('dir', null, InputOption::VALUE_REQUIRED, 'Write the archive to this directory'), new InputOption('file', null, InputOption::VALUE_REQUIRED, 'Write the archive with the given file name.' .' Note that the format will be appended.'), new InputOption('ignore-filters', null, InputOption::VALUE_NONE, 'Ignore filters when saving package'), ]) ->setHelp( <<archive command creates an archive of the specified format containing the files and directories of the Composer project or the specified package in the specified version and writes it to the specified directory. php composer.phar archive [--format=zip] [--dir=/foo] [--file=filename] [package [version]] Read more at https://getcomposer.org/doc/03-cli.md#archive EOT ) ; } protected function execute(InputInterface $input, OutputInterface $output): int { $composer = $this->tryComposer(); $config = null; if ($composer) { $config = $composer->getConfig(); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'archive', $input, $output); $eventDispatcher = $composer->getEventDispatcher(); $eventDispatcher->dispatch($commandEvent->getName(), $commandEvent); $eventDispatcher->dispatchScript(ScriptEvents::PRE_ARCHIVE_CMD); } if (!$config) { $config = Factory::createConfig(); } $format = $input->getOption('format') ?? $config->get('archive-format'); $dir = $input->getOption('dir') ?? $config->get('archive-dir'); $returnCode = $this->archive( $this->getIO(), $config, $input->getArgument('package'), $input->getArgument('version'), $format, $dir, $input->getOption('file'), $input->getOption('ignore-filters'), $composer ); if (0 === $returnCode && $composer) { $composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_ARCHIVE_CMD); } return $returnCode; } protected function archive(IOInterface $io, Config $config, ?string $packageName, ?string $version, string $format, string $dest, ?string $fileName, bool $ignoreFilters, ?Composer $composer): int { if ($composer) { $archiveManager = $composer->getArchiveManager(); } else { $factory = new Factory; $process = new ProcessExecutor(); $httpDownloader = Factory::createHttpDownloader($io, $config); $downloadManager = $factory->createDownloadManager($io, $config, $httpDownloader, $process); $archiveManager = $factory->createArchiveManager($config, $downloadManager, new Loop($httpDownloader, $process)); } if ($packageName) { $package = $this->selectPackage($io, $packageName, $version); if (!$package) { return 1; } } else { $package = $this->requireComposer()->getPackage(); } $io->writeError('Creating the archive into "'.$dest.'".'); $packagePath = $archiveManager->archive($package, $format, $dest, $fileName, $ignoreFilters); $fs = new Filesystem; $shortPath = $fs->findShortestPath(Platform::getCwd(), $packagePath, true); $io->writeError('Created: ', false); $io->write(strlen($shortPath) < strlen($packagePath) ? $shortPath : $packagePath); return 0; } protected function selectPackage(IOInterface $io, string $packageName, ?string $version = null) { $io->writeError('Searching for the specified package.'); if ($composer = $this->tryComposer()) { $localRepo = $composer->getRepositoryManager()->getLocalRepository(); $repo = new CompositeRepository(array_merge([$localRepo], $composer->getRepositoryManager()->getRepositories())); $minStability = $composer->getPackage()->getMinimumStability(); } else { $defaultRepos = RepositoryFactory::defaultReposWithDefaultManager($io); $io->writeError('No composer.json found in the current directory, searching packages from ' . implode(', ', array_keys($defaultRepos))); $repo = new CompositeRepository($defaultRepos); $minStability = 'stable'; } if ($version !== null && Preg::isMatchStrictGroups('{@(stable|RC|beta|alpha|dev)$}i', $version, $match)) { $minStability = VersionParser::normalizeStability($match[1]); $version = (string) substr($version, 0, -strlen($match[0])); } $repoSet = new RepositorySet($minStability); $repoSet->addRepository($repo); $parser = new VersionParser(); $constraint = $version !== null ? $parser->parseConstraints($version) : null; $packages = $repoSet->findPackages(strtolower($packageName), $constraint); if (count($packages) > 1) { $versionSelector = new VersionSelector($repoSet); $package = $versionSelector->findBestCandidate(strtolower($packageName), $version, $minStability); if ($package === false) { $package = reset($packages); } $io->writeError('Found multiple matches, selected '.$package->getPrettyString().'.'); $io->writeError('Alternatives were '.implode(', ', array_map(static function ($p): string { return $p->getPrettyString(); }, $packages)).'.'); $io->writeError('Please use a more specific constraint to pick a different package.'); } elseif (count($packages) === 1) { $package = reset($packages); $io->writeError('Found an exact match '.$package->getPrettyString().'.'); } else { $io->writeError('Could not find a package matching '.$packageName.'.'); return false; } if (!$package instanceof CompletePackageInterface) { throw new \LogicException('Expected a CompletePackageInterface instance but found '.get_class($package)); } if (!$package instanceof BasePackage) { throw new \LogicException('Expected a BasePackage instance but found '.get_class($package)); } return $package; } } setName('audit') ->setDescription('Checks for security vulnerability advisories for installed packages') ->setDefinition([ new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables auditing of require-dev packages.'), new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Output format. Must be "table", "plain", "json", or "summary".', Auditor::FORMAT_TABLE, Auditor::FORMATS), new InputOption('locked', null, InputOption::VALUE_NONE, 'Audit based on the lock file instead of the installed packages.'), new InputOption('abandoned', null, InputOption::VALUE_REQUIRED, 'Behavior on abandoned packages. Must be "ignore", "report", or "fail".', null, ListPolicyConfig::AUDITS), new InputOption('ignore-severity', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Ignore advisories of a certain severity level.', [], ['low', 'medium', 'high', 'critical']), new InputOption('ignore-unreachable', null, InputOption::VALUE_NONE, 'Ignore repositories that are unreachable or return a non-200 status code.'), ]) ->setHelp( <<audit command checks for security vulnerability advisories for installed packages. If you do not want to include dev dependencies in the audit you can omit them with --no-dev If you want to ignore repositories that are unreachable or return a non-200 status code, use --ignore-unreachable Read more at https://getcomposer.org/doc/03-cli.md#audit EOT ) ; } protected function execute(InputInterface $input, OutputInterface $output): int { $composer = $this->requireComposer(); $packages = $this->getPackages($composer, $input); if (count($packages) === 0) { if ($composer->getPackage()->getRequires() !== [] || (!$input->getOption('no-dev') && $composer->getPackage()->getDevRequires() !== [])) { $this->getIO()->writeError('No installed packages found. Please run "composer install" before running "audit" or pass "--locked" to audit the lock file.'); return Auditor::STATUS_FAILED; } $this->getIO()->writeError('No packages - skipping audit.'); return Auditor::STATUS_OK; } $auditor = new Auditor(); $repoSet = new RepositorySet(); foreach ($composer->getRepositoryManager()->getRepositories() as $repo) { $repoSet->addRepository($repo); } $abandoned = $input->getOption('abandoned'); if ($abandoned !== null && !in_array($abandoned, ListPolicyConfig::AUDITS, true)) { throw new \InvalidArgumentException('--abandoned must be one of '.implode(', ', ListPolicyConfig::AUDITS).'.'); } $policyConfig = $this->createPolicyConfig($composer->getConfig(), $input); if ($abandoned !== null) { $policyConfig = $policyConfig->withAudit($abandoned); } $ignoreSeverities = $input->getOption('ignore-severity'); if (count($ignoreSeverities) > 0) { $policyConfig = $policyConfig->withIgnoreSeverity(array_values($ignoreSeverities)); } if ($input->getOption('ignore-unreachable')) { $policyConfig = $policyConfig->withIgnoreUnreachable('audit'); } $filterListProviderSet = $policyConfig->enabled ? FilterListProviderSet::create($policyConfig, $composer->getRepositoryManager()->getRepositories(), $composer->getLoop()->getHttpDownloader()) : null; return min(255, $auditor->audit( $this->getIO(), $repoSet, $policyConfig, $packages, $this->getAuditFormat($input, 'format'), false, $filterListProviderSet )); } private function getPackages(Composer $composer, InputInterface $input): array { if ($input->getOption('locked')) { if (!$composer->getLocker()->isLocked()) { throw new \UnexpectedValueException('Valid composer.json and composer.lock files are required to run this command with --locked'); } $locker = $composer->getLocker(); return $locker->getLockedRepository(!$input->getOption('no-dev'))->getPackages(); } $rootPkg = $composer->getPackage(); $installedRepo = new InstalledRepository([$composer->getRepositoryManager()->getLocalRepository()]); if ($input->getOption('no-dev')) { return RepositoryUtils::filterRequiredPackages($installedRepo->getPackages(), $rootPkg); } return $installedRepo->getPackages(); } } requireComposer($disablePlugins, $disableScripts); } return $this->tryComposer($disablePlugins, $disableScripts); } public function requireComposer(?bool $disablePlugins = null, ?bool $disableScripts = null): Composer { if (null === $this->composer) { $application = parent::getApplication(); if ($application instanceof Application) { $this->composer = $application->getComposer(true, $disablePlugins, $disableScripts); assert($this->composer instanceof Composer); } else { throw new \RuntimeException( 'Could not create a Composer\Composer instance, you must inject '. 'one if this command is not used with a Composer\Console\Application instance' ); } } return $this->composer; } public function tryComposer(?bool $disablePlugins = null, ?bool $disableScripts = null): ?Composer { if (null === $this->composer) { $application = parent::getApplication(); if ($application instanceof Application) { $this->composer = $application->getComposer(false, $disablePlugins, $disableScripts); } } return $this->composer; } public function setComposer(Composer $composer) { $this->composer = $composer; } public function resetComposer() { $this->composer = null; $this->getApplication()->resetComposer(); } public function isProxyCommand() { return false; } public function getIO() { if (null === $this->io) { $application = parent::getApplication(); if ($application instanceof Application) { $this->io = $application->getIO(); } else { $this->io = new NullIO(); } } return $this->io; } public function setIO(IOInterface $io) { $this->io = $io; } public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void { $definition = $this->getDefinition(); $name = (string) $input->getCompletionName(); if (CompletionInput::TYPE_OPTION_VALUE === $input->getCompletionType() && $definition->hasOption($name) && ($option = $definition->getOption($name)) instanceof InputOption ) { $option->complete($input, $suggestions); } elseif (CompletionInput::TYPE_ARGUMENT_VALUE === $input->getCompletionType() && $definition->hasArgument($name) && ($argument = $definition->getArgument($name)) instanceof InputArgument ) { $argument->complete($input, $suggestions); } else { parent::complete($input, $suggestions); } } protected function initialize(InputInterface $input, OutputInterface $output): void { $disablePlugins = $input->hasParameterOption('--no-plugins'); $disableScripts = $input->hasParameterOption('--no-scripts'); $application = parent::getApplication(); if ($application instanceof Application && $application->getDisablePluginsByDefault()) { $disablePlugins = true; } if ($application instanceof Application && $application->getDisableScriptsByDefault()) { $disableScripts = true; } if ($this instanceof SelfUpdateCommand) { $disablePlugins = true; $disableScripts = true; } $composer = $this->tryComposer($disablePlugins, $disableScripts); $io = $this->getIO(); if (null === $composer) { $composer = Factory::createGlobal($this->getIO(), $disablePlugins, $disableScripts); } if ($composer) { $preCommandRunEvent = new PreCommandRunEvent(PluginEvents::PRE_COMMAND_RUN, $input, $this->getName()); $composer->getEventDispatcher()->dispatch($preCommandRunEvent->getName(), $preCommandRunEvent); } if (true === $input->hasParameterOption(['--no-ansi']) && $input->hasOption('no-progress')) { $input->setOption('no-progress', true); } $envOptions = [ 'COMPOSER_NO_AUDIT' => ['no-audit'], 'COMPOSER_NO_DEV' => ['no-dev', 'update-no-dev'], 'COMPOSER_PREFER_STABLE' => ['prefer-stable'], 'COMPOSER_PREFER_LOWEST' => ['prefer-lowest'], 'COMPOSER_MINIMAL_CHANGES' => ['minimal-changes'], 'COMPOSER_WITH_DEPENDENCIES' => ['with-dependencies'], 'COMPOSER_WITH_ALL_DEPENDENCIES' => ['with-all-dependencies'], 'COMPOSER_NO_SECURITY_BLOCKING' => ['no-security-blocking'], 'COMPOSER_NO_BLOCKING' => ['no-blocking'], ]; foreach ($envOptions as $envName => $optionNames) { foreach ($optionNames as $optionName) { if (true === $input->hasOption($optionName)) { if (false === $input->getOption($optionName) && (bool) Platform::getEnv($envName)) { $input->setOption($optionName, true); } } } } if (true === $input->hasOption('ignore-platform-reqs')) { if (!$input->getOption('ignore-platform-reqs') && (bool) Platform::getEnv('COMPOSER_IGNORE_PLATFORM_REQS')) { $input->setOption('ignore-platform-reqs', true); $io->writeError('COMPOSER_IGNORE_PLATFORM_REQS is set. You may experience unexpected errors.'); } } if (true === $input->hasOption('ignore-platform-req') && (!$input->hasOption('ignore-platform-reqs') || !$input->getOption('ignore-platform-reqs'))) { $ignorePlatformReqEnv = Platform::getEnv('COMPOSER_IGNORE_PLATFORM_REQ'); if (0 === count($input->getOption('ignore-platform-req')) && is_string($ignorePlatformReqEnv) && '' !== $ignorePlatformReqEnv) { $input->setOption('ignore-platform-req', explode(',', $ignorePlatformReqEnv)); $io->writeError('COMPOSER_IGNORE_PLATFORM_REQ is set to ignore '.$ignorePlatformReqEnv.'. You may experience unexpected errors.'); } } parent::initialize($input, $output); } protected function createComposerInstance(InputInterface $input, IOInterface $io, $config = null, ?bool $disablePlugins = null, ?bool $disableScripts = null): Composer { $disablePlugins = $disablePlugins === true || $input->hasParameterOption('--no-plugins'); $disableScripts = $disableScripts === true || $input->hasParameterOption('--no-scripts'); $application = parent::getApplication(); if ($application instanceof Application && $application->getDisablePluginsByDefault()) { $disablePlugins = true; } if ($application instanceof Application && $application->getDisableScriptsByDefault()) { $disableScripts = true; } return Factory::create($io, $config, $disablePlugins, $disableScripts); } protected function getPreferredInstallOptions(Config $config, InputInterface $input, bool $keepVcsRequiresPreferSource = false) { $preferSource = false; $preferDist = false; switch ($config->get('preferred-install')) { case 'source': $preferSource = true; break; case 'dist': $preferDist = true; break; case 'auto': default: break; } if (!$input->hasOption('prefer-dist') || !$input->hasOption('prefer-source')) { return [$preferSource, $preferDist]; } if ($input->hasOption('prefer-install') && is_string($input->getOption('prefer-install'))) { if ($input->getOption('prefer-source')) { throw new \InvalidArgumentException('--prefer-source can not be used together with --prefer-install'); } if ($input->getOption('prefer-dist')) { throw new \InvalidArgumentException('--prefer-dist can not be used together with --prefer-install'); } switch ($input->getOption('prefer-install')) { case 'dist': $input->setOption('prefer-dist', true); break; case 'source': $input->setOption('prefer-source', true); break; case 'auto': $preferDist = false; $preferSource = false; break; default: throw new \UnexpectedValueException('--prefer-install accepts one of "dist", "source" or "auto", got '.$input->getOption('prefer-install')); } } if ($input->getOption('prefer-source') || $input->getOption('prefer-dist') || ($keepVcsRequiresPreferSource && $input->hasOption('keep-vcs') && $input->getOption('keep-vcs'))) { $preferSource = $input->getOption('prefer-source') || ($keepVcsRequiresPreferSource && $input->hasOption('keep-vcs') && $input->getOption('keep-vcs')); $preferDist = $input->getOption('prefer-dist'); } return [$preferSource, $preferDist]; } protected function getPlatformRequirementFilter(InputInterface $input): PlatformRequirementFilterInterface { if (!$input->hasOption('ignore-platform-reqs') || !$input->hasOption('ignore-platform-req')) { throw new \LogicException('Calling getPlatformRequirementFilter from a command which does not define the --ignore-platform-req[s] flags is not permitted.'); } if (true === $input->getOption('ignore-platform-reqs')) { return PlatformRequirementFilterFactory::ignoreAll(); } $ignores = $input->getOption('ignore-platform-req'); if (count($ignores) > 0) { return PlatformRequirementFilterFactory::fromBoolOrList($ignores); } return PlatformRequirementFilterFactory::ignoreNothing(); } protected function formatRequirements(array $requirements) { $requires = []; $requirements = $this->normalizeRequirements($requirements); foreach ($requirements as $requirement) { if (!isset($requirement['version'])) { throw new \UnexpectedValueException('Option '.$requirement['name'] .' is missing a version constraint, use e.g. '.$requirement['name'].':^1.0'); } $requires[$requirement['name']] = $requirement['version']; } return $requires; } protected function normalizeRequirements(array $requirements) { $parser = new VersionParser(); return $parser->parseNameVersionPairs($requirements); } protected function renderTable(array $table, OutputInterface $output) { $renderer = new Table($output); $renderer->setStyle('compact'); $renderer->setRows($table)->render(); } protected function getTerminalWidth() { $terminal = new Terminal(); $width = $terminal->getWidth(); if (Platform::isWindows()) { $width--; } else { $width = max(80, $width); } return $width; } protected function getAuditFormat(InputInterface $input, string $optName = 'audit-format'): string { if (!$input->hasOption($optName)) { throw new \LogicException('This should not be called on a Command which has no '.$optName.' option defined.'); } $val = $input->getOption($optName); if (!in_array($val, Auditor::FORMATS, true)) { throw new \InvalidArgumentException('--'.$optName.' must be one of '.implode(', ', Auditor::FORMATS).'.'); } return $val; } protected function createPolicyConfig(Config $config, ?InputInterface $input): PolicyConfig { $policyConfig = PolicyConfig::fromConfig($config); $noBlocking = Platform::getBoolEnv('COMPOSER_NO_BLOCKING', false) || Platform::getBoolEnv('COMPOSER_NO_SECURITY_BLOCKING', false) || ($input !== null && $input->hasOption('no-security-blocking') && $input->getOption('no-security-blocking')) || ($input !== null && $input->hasOption('no-blocking') && $input->getOption('no-blocking')); if ($noBlocking) { $policyConfig = $policyConfig->withBlockingDisabled(); } return $policyConfig; } protected function createAuditConfig(InputInterface $input): AuditConfig { if ($input->hasOption('audit')) { $audit = (bool) $input->getOption('audit'); } else { $audit = !($input->hasOption('no-audit') && $input->getOption('no-audit')); } $auditFormat = $input->hasOption('audit-format') ? $this->getAuditFormat($input) : Auditor::FORMAT_SUMMARY; return new AuditConfig($audit, $auditFormat); } } getOption('global') && null !== $input->getOption('file')) { throw new \RuntimeException('--file and --global can not be combined'); } $io = $this->getIO(); $this->config = Factory::createConfig($io); if ($input->getOption('global')) { $this->config->setBaseDir($this->config->get('home')); } $configFile = $this->getComposerConfigFile($input, $this->config); if ( ($configFile === 'composer.json' || $configFile === './composer.json') && !file_exists($configFile) && realpath(Platform::getCwd()) === realpath($this->config->get('home')) ) { file_put_contents($configFile, "{\n}\n"); } $this->configFile = new JsonFile($configFile, null, $io); $this->configSource = new JsonConfigSource($this->configFile); if ($input->getOption('global') && !$this->configFile->exists()) { touch($this->configFile->getPath()); $this->configFile->write(['config' => new \ArrayObject]); Silencer::call('chmod', $this->configFile->getPath(), 0600); } if (!$this->configFile->exists()) { throw new \RuntimeException(sprintf('File "%s" cannot be found in the current directory', $configFile)); } } protected function getComposerConfigFile(InputInterface $input, Config $config): string { return $input->getOption('global') ? ($config->get('home') . '/config.json') : ($input->getOption('file') ?? Factory::getComposerFile()) ; } protected function getAuthConfigFile(InputInterface $input, Config $config): string { return $input->getOption('global') ? ($config->get('home') . '/auth.json') : dirname($this->getComposerConfigFile($input, $config)) . '/auth.json' ; } } requireComposer(); $commandEvent = new CommandEvent(PluginEvents::COMMAND, $this->getName(), $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $repos = []; $repos[] = new RootPackageRepository(clone $composer->getPackage()); if ($input->getOption('locked')) { $locker = $composer->getLocker(); if (!$locker->isLocked()) { throw new \UnexpectedValueException('A valid composer.lock file is required to run this command with --locked'); } $repos[] = $locker->getLockedRepository(true); $repos[] = new PlatformRepository([], $locker->getPlatformOverrides()); } else { $localRepo = $composer->getRepositoryManager()->getLocalRepository(); $rootPkg = $composer->getPackage(); if (count($localRepo->getPackages()) === 0 && (count($rootPkg->getRequires()) > 0 || count($rootPkg->getDevRequires()) > 0)) { $output->writeln('No dependencies installed. Try running composer install or update, or use --locked.'); return 1; } $repos[] = $localRepo; $platformOverrides = $composer->getConfig()->get('platform') ?: []; $repos[] = new PlatformRepository([], $platformOverrides); } $installedRepo = new InstalledRepository($repos); $needle = $input->getArgument(self::ARGUMENT_PACKAGE); $textConstraint = $input->hasArgument(self::ARGUMENT_CONSTRAINT) ? $input->getArgument(self::ARGUMENT_CONSTRAINT) : '*'; $packages = $installedRepo->findPackagesWithReplacersAndProviders($needle); if (empty($packages)) { throw new \InvalidArgumentException(sprintf('Could not find package "%s" in your project', $needle)); } $matchedPackage = $installedRepo->findPackage($needle, $textConstraint); if (!$matchedPackage) { $defaultRepos = new CompositeRepository(RepositoryFactory::defaultRepos($this->getIO(), $composer->getConfig(), $composer->getRepositoryManager())); if ($match = $defaultRepos->findPackage($needle, $textConstraint)) { $installedRepo->addRepository(new InstalledArrayRepository([clone $match])); } elseif (PlatformRepository::isPlatformPackage($needle)) { $parser = new VersionParser(); $constraint = $parser->parseConstraints($textConstraint); if ($constraint->getLowerBound() !== Bound::zero()) { $tempPlatformPkg = new Package($needle, $constraint->getLowerBound()->getVersion(), $constraint->getLowerBound()->getVersion()); $installedRepo->addRepository(new InstalledArrayRepository([$tempPlatformPkg])); } } else { $this->getIO()->writeError('Package "'.$needle.'" could not be found with constraint "'.$textConstraint.'", results below will most likely be incomplete.'); } } elseif (PlatformRepository::isPlatformPackage($needle)) { $extraNotice = ''; if (($matchedPackage->getExtra()['config.platform'] ?? false) === true) { $extraNotice = ' (version provided by config.platform)'; } $this->getIO()->writeError('Package "'.$needle.' '.$textConstraint.'" found in version "'.$matchedPackage->getPrettyVersion().'"'.$extraNotice.'.'); } elseif ($inverted) { $this->getIO()->write('Package "'.$needle.'" '.$matchedPackage->getPrettyVersion().' is already installed! To find out why, run `composer why '.$needle.'`'); return 0; } $needles = [$needle]; if ($inverted) { foreach ($packages as $package) { $needles = array_merge($needles, array_map(static function (Link $link): string { return $link->getTarget(); }, $package->getReplaces())); } } if ('*' !== $textConstraint) { $versionParser = new VersionParser(); $constraint = $versionParser->parseConstraints($textConstraint); } else { $constraint = null; } $renderTree = $input->getOption(self::OPTION_TREE); $recursive = $renderTree || $input->getOption(self::OPTION_RECURSIVE); $return = $inverted ? 1 : 0; $results = $installedRepo->getDependents($needles, $constraint, $inverted, $recursive); if (empty($results)) { $extra = (null !== $constraint) ? sprintf(' in versions %smatching %s', $inverted ? 'not ' : '', $textConstraint) : ''; $this->getIO()->writeError(sprintf( 'There is no installed package depending on "%s"%s', $needle, $extra )); $return = $inverted ? 0 : 1; } elseif ($renderTree) { $this->initStyles($output); $root = $packages[0]; $this->getIO()->write(sprintf('%s %s %s', $root->getPrettyName(), $root->getPrettyVersion(), $root instanceof CompletePackageInterface ? $root->getDescription() : '')); $this->printTree($results); } else { $this->printTable($output, $results); } if ($inverted && $input->hasArgument(self::ARGUMENT_CONSTRAINT) && !PlatformRepository::isPlatformPackage($needle)) { $composerCommand = 'update'; foreach ($composer->getPackage()->getRequires() as $rootRequirement) { if ($rootRequirement->getTarget() === $needle) { $composerCommand = 'require'; break; } } foreach ($composer->getPackage()->getDevRequires() as $rootRequirement) { if ($rootRequirement->getTarget() === $needle) { $composerCommand = 'require --dev'; break; } } $this->getIO()->writeError('Not finding what you were looking for? Try calling `composer '.$composerCommand.' "'.$needle.':'.$textConstraint.'" --dry-run` to get another view on the problem.'); } return $return; } protected function printTable(OutputInterface $output, array $results): void { $table = []; $doubles = []; do { $queue = []; $rows = []; foreach ($results as $result) { [$package, $link, $children] = $result; $unique = (string) $link; if (isset($doubles[$unique])) { continue; } $doubles[$unique] = true; $version = $package->getPrettyVersion() === RootPackage::DEFAULT_PRETTY_VERSION ? '-' : $package->getPrettyVersion(); $packageUrl = PackageInfo::getViewSourceOrHomepageUrl($package); $nameWithLink = $packageUrl !== null ? '' . $package->getPrettyName() . '' : $package->getPrettyName(); $rows[] = [$nameWithLink, $version, $link->getDescription(), sprintf('%s (%s)', $link->getTarget(), $link->getPrettyConstraint())]; if (is_array($children)) { $queue = array_merge($queue, $children); } } $results = $queue; $table = array_merge($rows, $table); } while (\count($results) > 0); $this->renderTable($table, $output); } protected function initStyles(OutputInterface $output): void { $this->colors = [ 'green', 'yellow', 'cyan', 'magenta', 'blue', ]; foreach ($this->colors as $color) { $style = new OutputFormatterStyle($color); $output->getFormatter()->setStyle($color, $style); } } protected function printTree(array $results, string $prefix = '', int $level = 1): void { $count = count($results); $idx = 0; foreach ($results as $result) { [$package, $link, $children] = $result; $color = $this->colors[$level % count($this->colors)]; $prevColor = $this->colors[($level - 1) % count($this->colors)]; $isLast = (++$idx === $count); $versionText = $package->getPrettyVersion() === RootPackage::DEFAULT_PRETTY_VERSION ? '' : $package->getPrettyVersion(); $packageUrl = PackageInfo::getViewSourceOrHomepageUrl($package); $nameWithLink = $packageUrl !== null ? '' . $package->getPrettyName() . '' : $package->getPrettyName(); $packageText = rtrim(sprintf('<%s>%s %s', $color, $nameWithLink, $versionText)); $linkText = sprintf('%s <%s>%s %s', $link->getDescription(), $prevColor, $link->getTarget(), $link->getPrettyConstraint()); $circularWarn = $children === false ? '(circular dependency aborted here)' : ''; $this->writeTreeLine(rtrim(sprintf("%s%s%s (%s) %s", $prefix, $isLast ? '└──' : '├──', $packageText, $linkText, $circularWarn))); if (is_array($children)) { $this->printTree($children, $prefix . ($isLast ? ' ' : '│ '), $level + 1); } } } private function writeTreeLine(string $line): void { $io = $this->getIO(); if (!$io->isDecorated()) { $line = str_replace(['└', '├', '──', '│'], ['`-', '|-', '-', '|'], $line); } $io->write($line); } } setName('bump') ->setDescription('Increases the lower limit of your composer.json requirements to the currently installed versions') ->setDefinition([ new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Optional package name(s) to restrict which packages are bumped.', null, $this->suggestRootRequirement()), new InputOption('dev-only', 'D', InputOption::VALUE_NONE, 'Only bump requirements in "require-dev".'), new InputOption('no-dev-only', 'R', InputOption::VALUE_NONE, 'Only bump requirements in "require".'), new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the packages to bump, but will not execute anything.'), ]) ->setHelp( <<bump command increases the lower limit of your composer.json requirements to the currently installed versions. This helps to ensure your dependencies do not accidentally get downgraded due to some other conflict, and can slightly improve dependency resolution performance as it limits the amount of package versions Composer has to look at. Running this blindly on libraries is **NOT** recommended as it will narrow down your allowed dependencies, which may cause dependency hell for your users. Running it with --dev-only on libraries may be fine however as dev requirements are local to the library and do not affect consumers of the package. EOT ) ; } protected function execute(InputInterface $input, OutputInterface $output): int { return $this->doBump( $this->getIO(), $input->getOption('dev-only'), $input->getOption('no-dev-only'), $input->getOption('dry-run'), $input->getArgument('packages') ); } public function doBump( IOInterface $io, bool $devOnly, bool $noDevOnly, bool $dryRun, array $packagesFilter, string $devOnlyFlagHint = '--dev-only' ): int { $composerJsonPath = Factory::getComposerFile(); if (!Filesystem::isReadable($composerJsonPath)) { $io->writeError(''.$composerJsonPath.' is not readable.'); return self::ERROR_GENERIC; } $composerJson = new JsonFile($composerJsonPath); $contents = file_get_contents($composerJson->getPath()); if (false === $contents) { $io->writeError(''.$composerJsonPath.' is not readable.'); return self::ERROR_GENERIC; } if (!is_writable($composerJsonPath) && false === Silencer::call('file_put_contents', $composerJsonPath, $contents)) { $io->writeError(''.$composerJsonPath.' is not writable.'); return self::ERROR_GENERIC; } unset($contents); $composer = $this->requireComposer(); $hasLockfileDisabled = !$composer->getConfig()->has('lock') || $composer->getConfig()->get('lock'); if (!$hasLockfileDisabled) { $repo = $composer->getLocker()->getLockedRepository(true); } elseif ($composer->getLocker()->isLocked()) { if (!$composer->getLocker()->isFresh()) { $io->writeError('The lock file is not up to date with the latest changes in composer.json. Run the appropriate `update` to fix that before you use the `bump` command.'); return self::ERROR_LOCK_OUTDATED; } $repo = $composer->getLocker()->getLockedRepository(true); } else { $repo = $composer->getRepositoryManager()->getLocalRepository(); } if ($composer->getPackage()->getType() !== 'project' && !$devOnly) { $io->writeError('Warning: Bumping dependency constraints is not recommended for libraries as it will narrow down your dependencies and may cause problems for your users.'); $contents = $composerJson->read(); if (!isset($contents['type'])) { $io->writeError('If your package is not a library, you can explicitly specify the "type" by using "composer config type project".'); $io->writeError('Alternatively you can use '.$devOnlyFlagHint.' to only bump dependencies within "require-dev".'); } unset($contents); } $bumper = new VersionBumper(); $tasks = []; if (!$devOnly) { $tasks['require'] = $composer->getPackage()->getRequires(); } if (!$noDevOnly) { $tasks['require-dev'] = $composer->getPackage()->getDevRequires(); } if (count($packagesFilter) > 0) { $packagesFilter = array_map(static function ($constraint) { return Preg::replace('{[:= ].+}', '', $constraint); }, $packagesFilter); $pattern = BasePackage::packageNamesToRegexp(array_unique(array_map('strtolower', $packagesFilter))); foreach ($tasks as $key => $reqs) { foreach ($reqs as $pkgName => $link) { if (!Preg::isMatch($pattern, $pkgName)) { unset($tasks[$key][$pkgName]); } } } } $updates = []; foreach ($tasks as $key => $reqs) { foreach ($reqs as $pkgName => $link) { if (PlatformRepository::isPlatformPackage($pkgName)) { continue; } $currentConstraint = $link->getPrettyConstraint(); $package = $repo->findPackage($pkgName, '*'); if (null === $package) { continue; } while ($package instanceof AliasPackage) { $package = $package->getAliasOf(); } $bumped = $bumper->bumpRequirement($link->getConstraint(), $package); if ($bumped === $currentConstraint) { continue; } $updates[$key][$pkgName] = $bumped; } } if (!$dryRun && !$this->updateFileCleanly($composerJson, $updates)) { $composerDefinition = $composerJson->read(); foreach ($updates as $key => $packages) { foreach ($packages as $package => $version) { $composerDefinition[$key][$package] = $version; } } $composerJson->write($composerDefinition); } $changeCount = array_sum(array_map('count', $updates)); if ($changeCount > 0) { if ($dryRun) { $io->write('' . $composerJsonPath . ' would be updated with:'); foreach ($updates as $requireType => $packages) { foreach ($packages as $package => $version) { $io->write(sprintf(' - %s.%s: %s', $requireType, $package, $version)); } } } else { $io->write('' . $composerJsonPath . ' has been updated (' . $changeCount . ' changes).'); } } else { $io->write('No requirements to update in '.$composerJsonPath.'.'); } if (!$dryRun && $composer->getLocker()->isLocked() && $composer->getConfig()->get('lock') && $changeCount > 0) { $composer->getLocker()->updateHash($composerJson); } if ($dryRun && $changeCount > 0) { return self::ERROR_GENERIC; } return 0; } private function updateFileCleanly(JsonFile $json, array $updates): bool { $contents = file_get_contents($json->getPath()); if (false === $contents) { throw new \RuntimeException('Unable to read '.$json->getPath().' contents.'); } $manipulator = new JsonManipulator($contents); foreach ($updates as $key => $packages) { foreach ($packages as $package => $version) { if (!$manipulator->addLink($key, $package, $version)) { return false; } } } if (false === file_put_contents($json->getPath(), $manipulator->getContents())) { throw new \RuntimeException('Unable to write new '.$json->getPath().' contents.'); } return true; } } setName('check-platform-reqs') ->setDescription('Check that platform requirements are satisfied') ->setDefinition([ new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables checking of require-dev packages requirements.'), new InputOption('lock', null, InputOption::VALUE_NONE, 'Checks requirements only from the lock file, not from installed packages.'), new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text', ['json', 'text']), ]) ->setHelp( <<php composer.phar check-platform-reqs EOT ); } protected function execute(InputInterface $input, OutputInterface $output): int { $composer = $this->requireComposer(); $requires = []; $removePackages = []; if ($input->getOption('lock')) { $this->getIO()->writeError('Checking '.($input->getOption('no-dev') ? 'non-dev ' : '').'platform requirements using the lock file'); $installedRepo = $composer->getLocker()->getLockedRepository(!$input->getOption('no-dev')); } else { $installedRepo = $composer->getRepositoryManager()->getLocalRepository(); if (!$installedRepo->getPackages()) { $this->getIO()->writeError('No vendor dir present, checking '.($input->getOption('no-dev') ? 'non-dev ' : '').'platform requirements from the lock file'); $installedRepo = $composer->getLocker()->getLockedRepository(!$input->getOption('no-dev')); } else { if ($input->getOption('no-dev')) { $removePackages = $installedRepo->getDevPackageNames(); } $this->getIO()->writeError('Checking '.($input->getOption('no-dev') ? 'non-dev ' : '').'platform requirements for packages in the vendor dir'); } } if (!$input->getOption('no-dev')) { foreach ($composer->getPackage()->getDevRequires() as $require => $link) { $requires[$require] = [$link]; } } $installedRepo = new InstalledRepository([$installedRepo, new RootPackageRepository(clone $composer->getPackage())]); foreach ($installedRepo->getPackages() as $package) { if (in_array($package->getName(), $removePackages, true)) { continue; } foreach ($package->getRequires() as $require => $link) { $requires[$require][] = $link; } } ksort($requires); $installedRepo->addRepository(new PlatformRepository([], [])); $results = []; $exitCode = 0; foreach ($requires as $require => $links) { if (PlatformRepository::isPlatformPackage($require)) { $candidates = $installedRepo->findPackagesWithReplacersAndProviders($require); if ($candidates) { $reqResults = []; foreach ($candidates as $candidate) { $candidateConstraint = null; if ($candidate->getName() === $require) { $candidateConstraint = new Constraint('=', $candidate->getVersion()); $candidateConstraint->setPrettyString($candidate->getPrettyVersion()); } else { foreach (array_merge($candidate->getProvides(), $candidate->getReplaces()) as $link) { if ($link->getTarget() === $require) { $candidateConstraint = $link->getConstraint(); break; } } } if (!$candidateConstraint) { continue; } foreach ($links as $link) { if (!$link->getConstraint()->matches($candidateConstraint)) { $reqResults[] = [ $candidate->getName() === $require ? $candidate->getPrettyName() : $require, $candidateConstraint->getPrettyString(), $link, 'failed', $candidate->getName() === $require ? '' : 'provided by '.$candidate->getPrettyName().'', ]; continue 2; } } $results[] = [ $candidate->getName() === $require ? $candidate->getPrettyName() : $require, $candidateConstraint->getPrettyString(), null, 'success', $candidate->getName() === $require ? '' : 'provided by '.$candidate->getPrettyName().'', ]; continue 2; } $results = array_merge($results, $reqResults); $exitCode = max($exitCode, 1); continue; } $results[] = [ $require, 'n/a', $links[0], 'missing', '', ]; $exitCode = max($exitCode, 2); } } $this->printTable($output, $results, $input->getOption('format')); return $exitCode; } protected function printTable(OutputInterface $output, array $results, string $format): void { $rows = []; foreach ($results as $result) { [$platformPackage, $version, $link, $status, $provider] = $result; if ('json' === $format) { $rows[] = [ "name" => $platformPackage, "version" => $version, "status" => strip_tags($status), "failed_requirement" => $link instanceof Link ? [ 'source' => $link->getSource(), 'type' => $link->getDescription(), 'target' => $link->getTarget(), 'constraint' => $link->getPrettyConstraint(), ] : null, "provider" => $provider === '' ? null : strip_tags($provider), ]; } else { $rows[] = [ $platformPackage, $version, $link, $link ? sprintf('%s %s %s (%s)', $link->getSource(), $link->getDescription(), $link->getTarget(), $link->getPrettyConstraint()) : '', rtrim($status.' '.$provider), ]; } } if ('json' === $format) { $this->getIO()->write(JsonFile::encode($rows)); } else { $this->renderTable($rows, $output); } } } setName('clear-cache') ->setAliases(['clearcache', 'cc']) ->setDescription('Clears composer\'s internal package cache') ->setDefinition([ new InputOption('gc', null, InputOption::VALUE_NONE, 'Only run garbage collection, not a full cache clear'), ]) ->setHelp( <<clear-cache deletes all cached packages from composer's cache directory. Read more at https://getcomposer.org/doc/03-cli.md#clear-cache-clearcache-cc EOT ) ; } protected function execute(InputInterface $input, OutputInterface $output): int { $composer = $this->tryComposer(); if ($composer !== null) { $config = $composer->getConfig(); } else { $config = Factory::createConfig(); } $io = $this->getIO(); $cachePaths = [ 'cache-vcs-dir' => $config->get('cache-vcs-dir'), 'cache-repo-dir' => $config->get('cache-repo-dir'), 'cache-files-dir' => $config->get('cache-files-dir'), 'cache-dir' => $config->get('cache-dir'), ]; foreach ($cachePaths as $key => $cachePath) { if ($key === 'cache-dir' && $input->getOption('gc')) { continue; } $cachePath = realpath($cachePath); if (!$cachePath) { $io->writeError("Cache directory does not exist ($key): $cachePath"); continue; } $cache = new Cache($io, $cachePath); $cache->setReadOnly($config->get('cache-read-only')); if (!$cache->isEnabled()) { $io->writeError("Cache is not enabled ($key): $cachePath"); continue; } if ($input->getOption('gc')) { $io->writeError("Garbage-collecting cache ($key): $cachePath"); if ($key === 'cache-files-dir') { $cache->gc($config->get('cache-files-ttl'), $config->get('cache-files-maxsize')); } elseif ($key === 'cache-repo-dir') { $cache->gc($config->get('cache-ttl'), 1024 * 1024 * 1024 ); } elseif ($key === 'cache-vcs-dir') { $cache->gcVcsCache($config->get('cache-ttl')); } } else { $io->writeError("Clearing cache ($key): $cachePath"); $cache->clear(); } } if ($input->getOption('gc')) { $io->writeError('All caches garbage-collected.'); } else { $io->writeError('All caches cleared.'); } return 0; } } requireComposer(); return array_merge(array_keys($composer->getPackage()->getRequires()), array_keys($composer->getPackage()->getDevRequires())); }; } private function suggestInstalledPackage(bool $includeRootPackage = true, bool $includePlatformPackages = false): \Closure { return function (CompletionInput $input) use ($includeRootPackage, $includePlatformPackages): array { $composer = $this->requireComposer(); $installedRepos = []; if ($includeRootPackage) { $installedRepos[] = new RootPackageRepository(clone $composer->getPackage()); } $locker = $composer->getLocker(); if ($locker->isLocked()) { $installedRepos[] = $locker->getLockedRepository(true); } else { $installedRepos[] = $composer->getRepositoryManager()->getLocalRepository(); } $platformHint = []; if ($includePlatformPackages) { if ($locker->isLocked()) { $platformRepo = new PlatformRepository([], $locker->getPlatformOverrides()); } else { $platformRepo = new PlatformRepository([], $composer->getConfig()->get('platform')); } if ($input->getCompletionValue() === '') { $hintsToFind = ['ext-' => 0, 'lib-' => 0, 'php' => 99, 'composer' => 99]; foreach ($platformRepo->getPackages() as $pkg) { foreach ($hintsToFind as $hintPrefix => $hintCount) { if (str_starts_with($pkg->getName(), $hintPrefix)) { if ($hintCount === 0 || $hintCount >= 99) { $platformHint[] = $pkg->getName(); $hintsToFind[$hintPrefix]++; } elseif ($hintCount === 1) { unset($hintsToFind[$hintPrefix]); $platformHint[] = substr($pkg->getName(), 0, max(strlen($pkg->getName()) - 3, strlen($hintPrefix) + 1)).'...'; } continue 2; } } } } else { $installedRepos[] = $platformRepo; } } $installedRepo = new InstalledRepository($installedRepos); return array_merge( array_map(static function (PackageInterface $package) { return $package->getName(); }, $installedRepo->getPackages()), $platformHint ); }; } private function suggestInstalledPackageTypes(bool $includeRootPackage = true): \Closure { return function (CompletionInput $input) use ($includeRootPackage): array { $composer = $this->requireComposer(); $installedRepos = []; if ($includeRootPackage) { $installedRepos[] = new RootPackageRepository(clone $composer->getPackage()); } $locker = $composer->getLocker(); if ($locker->isLocked()) { $installedRepos[] = $locker->getLockedRepository(true); } else { $installedRepos[] = $composer->getRepositoryManager()->getLocalRepository(); } $installedRepo = new InstalledRepository($installedRepos); return array_values(array_unique( array_map(static function (PackageInterface $package) { return $package->getType(); }, $installedRepo->getPackages()) )); }; } private function suggestAvailablePackage(int $max = 99): \Closure { return function (CompletionInput $input) use ($max): array { if ($max < 1) { return []; } $composer = $this->requireComposer(); $repos = new CompositeRepository($composer->getRepositoryManager()->getRepositories()); $results = []; $showVendors = false; if (!str_contains($input->getCompletionValue(), '/')) { $results = $repos->search('^' . preg_quote($input->getCompletionValue()), RepositoryInterface::SEARCH_VENDOR); $showVendors = true; } if (\count($results) <= 1) { $results = $repos->search('^'.preg_quote($input->getCompletionValue()), RepositoryInterface::SEARCH_NAME); $showVendors = false; } $results = array_column($results, 'name'); if ($showVendors) { $results = array_map(static function (string $name): string { return $name.'/'; }, $results); usort($results, static function (string $a, string $b) { $lenA = \strlen($a); $lenB = \strlen($b); if ($lenA === $lenB) { return $a <=> $b; } return $lenA - $lenB; }); $pinned = []; $completionInput = $input->getCompletionValue().'/'; if (false !== ($exactIndex = array_search($completionInput, $results, true))) { $pinned[] = $completionInput; array_splice($results, $exactIndex, 1); } return array_merge($pinned, array_slice($results, 0, $max - \count($pinned))); } return array_slice($results, 0, $max); }; } private function suggestAvailablePackageInclPlatform(): \Closure { return function (CompletionInput $input): array { if (Preg::isMatch('{^(ext|lib|php)(-|$)|^com}', $input->getCompletionValue())) { $matches = $this->suggestPlatformPackage()($input); } else { $matches = []; } return array_merge($matches, $this->suggestAvailablePackage(99 - \count($matches))($input)); }; } private function suggestPlatformPackage(): \Closure { return function (CompletionInput $input): array { $repos = new PlatformRepository([], $this->requireComposer()->getConfig()->get('platform')); $pattern = BasePackage::packageNameToRegexp($input->getCompletionValue().'*'); return array_filter(array_map(static function (PackageInterface $package) { return $package->getName(); }, $repos->getPackages()), static function (string $name) use ($pattern): bool { return Preg::isMatch($pattern, $name); }); }; } } setName('config') ->setDescription('Sets config options') ->setDefinition([ new InputOption('global', 'g', InputOption::VALUE_NONE, 'Apply command to the global config file'), new InputOption('editor', 'e', InputOption::VALUE_NONE, 'Open editor'), new InputOption('auth', 'a', InputOption::VALUE_NONE, 'Affect auth config file (only used for --editor)'), new InputOption('unset', null, InputOption::VALUE_NONE, 'Unset the given setting-key'), new InputOption('list', 'l', InputOption::VALUE_NONE, 'List configuration settings'), new InputOption('file', 'f', InputOption::VALUE_REQUIRED, 'If you want to choose a different composer.json or config.json'), new InputOption('absolute', null, InputOption::VALUE_NONE, 'Returns absolute paths when fetching *-dir config values instead of relative'), new InputOption('json', 'j', InputOption::VALUE_NONE, 'JSON decode the setting value, to be used with extra.* keys'), new InputOption('merge', 'm', InputOption::VALUE_NONE, 'Merge the setting value with the current value, to be used with extra.* or audit.ignore[-abandoned] keys in combination with --json'), new InputOption('append', null, InputOption::VALUE_NONE, 'When adding a repository, append it (lowest priority) to the existing ones instead of prepending it (highest priority)'), new InputOption('source', null, InputOption::VALUE_NONE, 'Display where the config value is loaded from'), new InputArgument('setting-key', null, 'Setting key', null, $this->suggestSettingKeys()), new InputArgument('setting-value', InputArgument::IS_ARRAY, 'Setting value'), ]) ->setHelp( <<%command.full_name% bin-dir bin/ To read a config setting: %command.full_name% bin-dir Outputs: bin To edit the global config.json file: %command.full_name% --global To add a repository: %command.full_name% repositories.foo vcs https://bar.com To remove a repository (repo is a short alias for repositories): %command.full_name% --unset repo.foo To disable packagist.org: %command.full_name% repo.packagist.org false You can alter repositories in the global config.json file by passing in the --global option. To add or edit suggested packages you can use: %command.full_name% suggest.package reason for the suggestion To add or edit extra properties you can use: %command.full_name% extra.property value Or to add a complex value you can use json with: %command.full_name% extra.property --json '{"foo":true, "bar": []}' To edit the file in an external editor: %command.full_name% --editor To choose your editor you can set the "EDITOR" env variable. To get a list of configuration values in the file: %command.full_name% --list You can always pass more than one option. As an example, if you want to edit the global config.json file. %command.full_name% --editor --global Read more at https://getcomposer.org/doc/03-cli.md#config EOT ) ; } protected function initialize(InputInterface $input, OutputInterface $output): void { parent::initialize($input, $output); $authConfigFile = $this->getAuthConfigFile($input, $this->config); $this->authConfigFile = new JsonFile($authConfigFile, null, $this->getIO()); $this->authConfigSource = new JsonConfigSource($this->authConfigFile, true); if ($input->getOption('global') && !$this->authConfigFile->exists()) { touch($this->authConfigFile->getPath()); $this->authConfigFile->write(['bitbucket-oauth' => new \ArrayObject, 'github-oauth' => new \ArrayObject, 'gitlab-oauth' => new \ArrayObject, 'gitlab-token' => new \ArrayObject, 'http-basic' => new \ArrayObject, 'bearer' => new \ArrayObject, 'forgejo-token' => new \ArrayObject()]); Silencer::call('chmod', $this->authConfigFile->getPath(), 0600); } } protected function execute(InputInterface $input, OutputInterface $output): int { if (true === $input->getOption('editor')) { $editor = Platform::getEnv('EDITOR'); if (false === $editor || '' === $editor) { if (Platform::isWindows()) { $editor = 'notepad'; } else { foreach (['editor', 'vim', 'vi', 'nano', 'pico', 'ed'] as $candidate) { if (exec('which '.$candidate)) { $editor = $candidate; break; } } } } else { $editor = escapeshellcmd($editor); } $file = $input->getOption('auth') ? $this->authConfigFile->getPath() : $this->configFile->getPath(); system($editor . ' ' . $file . (Platform::isWindows() ? '' : ' > `tty`')); return 0; } if (false === $input->getOption('global')) { $this->config->merge($this->configFile->read(), $this->configFile->getPath()); $this->config->merge(['config' => $this->authConfigFile->exists() ? $this->authConfigFile->read() : []], $this->authConfigFile->getPath()); } $this->getIO()->loadConfiguration($this->config); if (true === $input->getOption('list')) { $this->listConfiguration($this->config->all(), $this->config->raw(), $output, null, $input->getOption('source')); return 0; } $settingKey = $input->getArgument('setting-key'); if (!is_string($settingKey)) { return 0; } if ([] !== $input->getArgument('setting-value') && $input->getOption('unset')) { throw new \RuntimeException('You can not combine a setting value with --unset'); } if ([] === $input->getArgument('setting-value') && !$input->getOption('unset')) { $properties = self::CONFIGURABLE_PACKAGE_PROPERTIES; $propertiesDefaults = [ 'type' => 'library', 'description' => '', 'homepage' => '', 'minimum-stability' => 'stable', 'prefer-stable' => false, 'keywords' => [], 'license' => [], 'suggest' => [], 'extra' => [], ]; $rawData = $this->configFile->read(); $data = $this->config->all(); $source = $this->config->getSourceOfValue($settingKey); if (Preg::isMatch('/^repos?(?:itories)?(?:\.(.+))?/', $settingKey, $matches)) { if (!isset($matches[1])) { $value = $data['repositories'] ?? []; } else { if (!isset($data['repositories'][$matches[1]])) { throw new \InvalidArgumentException('There is no '.$matches[1].' repository defined'); } $value = $data['repositories'][$matches[1]]; } } elseif (strpos($settingKey, '.')) { $bits = explode('.', $settingKey); if ($bits[0] === 'extra' || $bits[0] === 'suggest') { $data = $rawData; } else { $data = $data['config']; } $match = false; foreach ($bits as $bit) { $key = isset($key) ? $key.'.'.$bit : $bit; $match = false; if (isset($data[$key])) { $match = true; $data = $data[$key]; unset($key); } } if (!$match) { throw new \RuntimeException($settingKey.' is not defined.'); } $value = $data; } elseif (isset($data['config'][$settingKey])) { $value = $this->config->get($settingKey, $input->getOption('absolute') ? 0 : Config::RELATIVE_PATHS); if ($value === []) { $schema = JsonFile::parseJson((string) file_get_contents(JsonFile::COMPOSER_SCHEMA_PATH)); if ( isset($schema['properties']['config']['properties'][$settingKey]['type']) && in_array('object', (array) $schema['properties']['config']['properties'][$settingKey]['type'], true) ) { $value = new \stdClass; } } } elseif (isset($rawData[$settingKey]) && in_array($settingKey, $properties, true)) { $value = $rawData[$settingKey]; $source = $this->configFile->getPath(); } elseif (isset($propertiesDefaults[$settingKey])) { $value = $propertiesDefaults[$settingKey]; $source = 'defaults'; } else { throw new \RuntimeException($settingKey.' is not defined'); } if (is_array($value) || is_object($value) || is_bool($value)) { $value = JsonFile::encode($value, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); } $sourceOfConfigValue = ''; if ($input->getOption('source')) { $sourceOfConfigValue = ' (' . $source . ')'; } $this->getIO()->write($value . $sourceOfConfigValue, true, IOInterface::QUIET); return 0; } $values = $input->getArgument('setting-value'); $booleanValidator = static function ($val): bool { return in_array($val, ['true', 'false', '1', '0'], true); }; $booleanNormalizer = static function ($val): bool { return $val !== 'false' && (bool) $val; }; $auditValidator = static function ($val): bool { return in_array($val, [ListPolicyConfig::AUDIT_IGNORE, ListPolicyConfig::AUDIT_REPORT, ListPolicyConfig::AUDIT_FAIL], true); }; $keepAsIsNormalizer = static function ($val) { return $val; }; $uniqueConfigValues = [ 'process-timeout' => ['is_numeric', 'intval'], 'use-include-path' => [$booleanValidator, $booleanNormalizer], 'use-github-api' => [$booleanValidator, $booleanNormalizer], 'preferred-install' => [ static function ($val): bool { return in_array($val, ['auto', 'source', 'dist'], true); }, static function ($val) { return $val; }, ], 'gitlab-protocol' => [ static function ($val): bool { return in_array($val, ['git', 'http', 'https'], true); }, static function ($val) { return $val; }, ], 'store-auths' => [ static function ($val): bool { return in_array($val, ['true', 'false', 'prompt'], true); }, static function ($val) { if ('prompt' === $val) { return 'prompt'; } return $val !== 'false' && (bool) $val; }, ], 'notify-on-install' => [$booleanValidator, $booleanNormalizer], 'vendor-dir' => ['is_string', static function ($val) { return $val; }], 'bin-dir' => ['is_string', static function ($val) { return $val; }], 'archive-dir' => ['is_string', static function ($val) { return $val; }], 'archive-format' => ['is_string', static function ($val) { return $val; }], 'data-dir' => ['is_string', static function ($val) { return $val; }], 'cache-dir' => ['is_string', static function ($val) { return $val; }], 'cache-files-dir' => ['is_string', static function ($val) { return $val; }], 'cache-repo-dir' => ['is_string', static function ($val) { return $val; }], 'cache-vcs-dir' => ['is_string', static function ($val) { return $val; }], 'cache-ttl' => ['is_numeric', 'intval'], 'cache-files-ttl' => ['is_numeric', 'intval'], 'cache-files-maxsize' => [ static function ($val): bool { return Preg::isMatch('/^\s*([0-9.]+)\s*(?:([kmg])(?:i?b)?)?\s*$/i', $val); }, static function ($val) { return $val; }, ], 'bin-compat' => [ static function ($val): bool { return in_array($val, ['auto', 'full', 'proxy', 'symlink']); }, static function ($val) { return $val; }, ], 'discard-changes' => [ static function ($val): bool { return in_array($val, ['stash', 'true', 'false', '1', '0'], true); }, static function ($val) { if ('stash' === $val) { return 'stash'; } return $val !== 'false' && (bool) $val; }, ], 'autoloader-suffix' => ['is_string', static function ($val) { return $val === 'null' ? null : $val; }], 'sort-packages' => [$booleanValidator, $booleanNormalizer], 'optimize-autoloader' => [$booleanValidator, $booleanNormalizer], 'classmap-authoritative' => [$booleanValidator, $booleanNormalizer], 'apcu-autoloader' => [$booleanValidator, $booleanNormalizer], 'prepend-autoloader' => [$booleanValidator, $booleanNormalizer], 'update-with-minimal-changes' => [$booleanValidator, $booleanNormalizer], 'disable-tls' => [$booleanValidator, $booleanNormalizer], 'secure-http' => [$booleanValidator, $booleanNormalizer], 'source-fallback' => [$booleanValidator, $booleanNormalizer], 'bump-after-update' => [ static function ($val): bool { return in_array($val, ['dev', 'no-dev', 'true', 'false', '1', '0'], true); }, static function ($val) { if ('dev' === $val || 'no-dev' === $val) { return $val; } return $val !== 'false' && (bool) $val; }, ], 'cafile' => [ static function ($val): bool { return file_exists($val) && Filesystem::isReadable($val); }, static function ($val) { return $val === 'null' ? null : $val; }, ], 'capath' => [ static function ($val): bool { return is_dir($val) && Filesystem::isReadable($val); }, static function ($val) { return $val === 'null' ? null : $val; }, ], 'github-expose-hostname' => [$booleanValidator, $booleanNormalizer], 'htaccess-protect' => [$booleanValidator, $booleanNormalizer], 'lock' => [$booleanValidator, $booleanNormalizer], 'allow-plugins' => [$booleanValidator, $booleanNormalizer], 'platform-check' => [ static function ($val): bool { return in_array($val, ['php-only', 'true', 'false', '1', '0'], true); }, static function ($val) { if ('php-only' === $val) { return 'php-only'; } return $val !== 'false' && (bool) $val; }, ], 'use-parent-dir' => [ static function ($val): bool { return in_array($val, ['true', 'false', 'prompt'], true); }, static function ($val) { if ('prompt' === $val) { return 'prompt'; } return $val !== 'false' && (bool) $val; }, ], 'audit.abandoned' => [$auditValidator, $keepAsIsNormalizer], 'audit.ignore-unreachable' => [$booleanValidator, $booleanNormalizer], 'audit.block-insecure' => [$booleanValidator, $booleanNormalizer], 'audit.block-abandoned' => [$booleanValidator, $booleanNormalizer], 'policy.advisories.block' => [$booleanValidator, $booleanNormalizer], 'policy.advisories.audit' => [$auditValidator, $keepAsIsNormalizer], 'policy.malware.block' => [$booleanValidator, $booleanNormalizer], 'policy.malware.block-scope' => [ static function ($val): bool { return in_array($val, ['all', 'update', 'install'], true); }, $keepAsIsNormalizer, ], 'policy.malware.audit' => [$auditValidator, $keepAsIsNormalizer], 'policy.abandoned.block' => [$booleanValidator, $booleanNormalizer], 'policy.abandoned.audit' => [$auditValidator, $keepAsIsNormalizer], 'policy.ignore-unreachable' => [$booleanValidator, $booleanNormalizer], ]; $multiConfigValues = [ 'github-protocols' => [ static function ($vals) { if (!is_array($vals)) { return 'array expected'; } foreach ($vals as $val) { if (!in_array($val, ['git', 'https', 'ssh'])) { return 'valid protocols include: git, https, ssh'; } } return true; }, static function ($vals) { return $vals; }, ], 'github-domains' => [ static function ($vals) { if (!is_array($vals)) { return 'array expected'; } return true; }, static function ($vals) { return $vals; }, ], 'gitlab-domains' => [ static function ($vals) { if (!is_array($vals)) { return 'array expected'; } return true; }, static function ($vals) { return $vals; }, ], 'audit.ignore-severity' => $ignoreSeverityValidatorAndNormalizer = [ static function ($vals) { if (!is_array($vals)) { return 'array expected'; } foreach ($vals as $val) { if (!in_array($val, ['low', 'medium', 'high', 'critical'], true)) { return 'valid severities include: low, medium, high, critical'; } } return true; }, static function ($vals) { return $vals; }, ], 'policy.advisories.ignore-severity' => $ignoreSeverityValidatorAndNormalizer, 'policy.malware.ignore-source' => [ static function ($vals) { if (!is_array($vals)) { return 'array expected'; } foreach ($vals as $val) { if (!is_string($val)) { return 'string values expected'; } } return true; }, $keepAsIsNormalizer, ], ]; if ($input->getOption('unset') && ($settingKey === 'audit' || $settingKey === 'policy' || strpos($settingKey, 'policy.') === 0)) { $this->configSource->removeConfigSetting($settingKey); return 0; } $policyJsonMergeKeys = ['policy.advisories.ignore-id']; foreach (PolicyConfig::BUILTIN_LIST_NAMES as $listName) { $policyJsonMergeKeys[] = 'policy.'.$listName.'.ignore'; } $nonCustomPolicyKeys = array_merge(PolicyConfig::BUILTIN_LIST_NAMES, PolicyConfig::NON_LIST_KEYS); $nonCustomAlternation = implode('|', array_map(static function (string $name): string { return preg_quote($name, '/'); }, $nonCustomPolicyKeys)); $isCustomPolicyIgnore = Preg::isMatch('/^policy\.(?!(?:'.$nonCustomAlternation.')$)([^.]+)\.ignore$/', $settingKey, $customIgnoreMatches); if (in_array($settingKey, $policyJsonMergeKeys, true) || $isCustomPolicyIgnore) { if ($isCustomPolicyIgnore) { $reservedError = PolicyConfig::getFutureReservedListNameError((string) $customIgnoreMatches[1]); if ($reservedError !== null) { throw new \RuntimeException('Invalid dependency policy name: '.$reservedError); } } $value = $values; if ($input->getOption('json')) { $value = JsonFile::parseJson($values[0]); if (!is_array($value)) { throw new \RuntimeException('Expected an array or object for '.$settingKey); } } if ($input->getOption('merge')) { $currentConfig = $this->configFile->read(); $currentValue = $currentConfig['config'] ?? null; foreach (explode('.', $settingKey) as $bit) { if (!is_array($currentValue) || !isset($currentValue[$bit])) { $currentValue = null; break; } $currentValue = $currentValue[$bit]; } if ($currentValue !== null && is_array($currentValue) && is_array($value)) { if (array_is_list($currentValue) && array_is_list($value)) { $value = array_merge($currentValue, $value); } elseif (!array_is_list($currentValue) && !array_is_list($value)) { $value = $value + $currentValue; } else { throw new \RuntimeException('Cannot merge array and object for '.$settingKey); } } } $this->configSource->addConfigSetting($settingKey, $value); return 0; } if ($settingKey === 'policy.ignore-unreachable') { if ($input->getOption('json')) { $value = JsonFile::parseJson($values[0]); if (!is_array($value)) { throw new \RuntimeException('Expected a boolean or array for '.$settingKey); } foreach ($value as $v) { if (!in_array($v, IgnoreUnreachable::SCOPES, true)) { throw new \RuntimeException('valid values for '.$settingKey.' include: '.implode(', ', IgnoreUnreachable::SCOPES)); } } $this->configSource->addConfigSetting($settingKey, $value); return 0; } if (count($values) > 0 && in_array($values[0], IgnoreUnreachable::SCOPES, true)) { foreach ($values as $v) { if (!in_array($v, IgnoreUnreachable::SCOPES, true)) { throw new \RuntimeException('valid values for '.$settingKey.' include: '.implode(', ', IgnoreUnreachable::SCOPES)); } } $this->configSource->addConfigSetting($settingKey, $values); return 0; } } if (Preg::isMatch('/^policy\.([^.]+)$/', $settingKey, $matches) && !in_array($matches[1], PolicyConfig::NON_LIST_KEYS, true) ) { $reservedError = PolicyConfig::getFutureReservedListNameError($matches[1]); if ($reservedError !== null) { throw new \RuntimeException('Invalid dependency policy name: '.$reservedError); } if (!$booleanValidator($values[0])) { throw new \RuntimeException(sprintf('"%s" is an invalid value for %s, expected a boolean', $values[0], $settingKey)); } $this->configSource->addConfigSetting($settingKey, $booleanNormalizer($values[0])); return 0; } if (Preg::isMatch('/^policy\.([^.]+)\.(block|audit)$/', $settingKey, $matches) && !in_array($matches[1], $nonCustomPolicyKeys, true) ) { $reservedError = PolicyConfig::getFutureReservedListNameError($matches[1]); if ($reservedError !== null) { throw new \RuntimeException('Invalid dependency policy name: '.$reservedError); } if ($matches[2] === 'block') { if (!$booleanValidator($values[0])) { throw new \RuntimeException(sprintf('"%s" is an invalid value for %s, expected a boolean', $values[0], $settingKey)); } $this->configSource->addConfigSetting($settingKey, $booleanNormalizer($values[0])); } else { if (!$auditValidator($values[0])) { throw new \RuntimeException(sprintf('"%s" is an invalid value for %s, must be one of: ignore, report, fail', $values[0], $settingKey)); } $this->configSource->addConfigSetting($settingKey, $values[0]); } return 0; } if (Preg::isMatch('/^policy\.([^.]+)\.sources$/', $settingKey, $matches)) { throw new \RuntimeException(sprintf( 'Setting dependency policy sources is not supported by `composer config`. Use `composer policy add-source %s url ` instead.', $matches[1] )); } if ($input->getOption('unset') && (isset($uniqueConfigValues[$settingKey]) || isset($multiConfigValues[$settingKey]))) { if ($settingKey === 'disable-tls' && $this->config->get('disable-tls')) { $this->getIO()->writeError('You are now running Composer with SSL/TLS protection enabled.'); } $this->configSource->removeConfigSetting($settingKey); return 0; } if (isset($uniqueConfigValues[$settingKey])) { $this->handleSingleValue($settingKey, $uniqueConfigValues[$settingKey], $values, 'addConfigSetting'); return 0; } if (isset($multiConfigValues[$settingKey])) { $this->handleMultiValue($settingKey, $multiConfigValues[$settingKey], $values, 'addConfigSetting'); return 0; } if (Preg::isMatch('/^preferred-install\.(.+)/', $settingKey, $matches)) { if ($input->getOption('unset')) { $this->configSource->removeConfigSetting($settingKey); return 0; } [$validator] = $uniqueConfigValues['preferred-install']; if (!$validator($values[0])) { throw new \RuntimeException('Invalid value for '.$settingKey.'. Should be one of: auto, source, or dist'); } $this->configSource->addConfigSetting($settingKey, $values[0]); return 0; } if (Preg::isMatch('{^allow-plugins\.([a-zA-Z0-9/*-]+)}', $settingKey, $matches)) { if ($input->getOption('unset')) { $this->configSource->removeConfigSetting($settingKey); return 0; } if (true !== $booleanValidator($values[0])) { throw new \RuntimeException(sprintf( '"%s" is an invalid value', $values[0] )); } $normalizedValue = $booleanNormalizer($values[0]); $this->configSource->addConfigSetting($settingKey, $normalizedValue); return 0; } $uniqueProps = [ 'name' => ['is_string', static function ($val) { return $val; }], 'type' => ['is_string', static function ($val) { return $val; }], 'description' => ['is_string', static function ($val) { return $val; }], 'homepage' => ['is_string', static function ($val) { return $val; }], 'version' => ['is_string', static function ($val) { return $val; }], 'minimum-stability' => [ static function ($val): bool { return isset(BasePackage::STABILITIES[VersionParser::normalizeStability($val)]); }, static function ($val): string { return VersionParser::normalizeStability($val); }, ], 'prefer-stable' => [$booleanValidator, $booleanNormalizer], ]; $multiProps = [ 'keywords' => [ static function ($vals) { if (!is_array($vals)) { return 'array expected'; } return true; }, static function ($vals) { return $vals; }, ], 'license' => [ static function ($vals) { if (!is_array($vals)) { return 'array expected'; } return true; }, static function ($vals) { return $vals; }, ], ]; if ($input->getOption('global') && (isset($uniqueProps[$settingKey]) || isset($multiProps[$settingKey]) || strpos($settingKey, 'extra.') === 0)) { throw new \InvalidArgumentException('The ' . $settingKey . ' property can not be set in the global config.json file. Use `composer global config` to apply changes to the global composer.json'); } if ($input->getOption('unset') && (isset($uniqueProps[$settingKey]) || isset($multiProps[$settingKey]))) { $this->configSource->removeProperty($settingKey); return 0; } if (isset($uniqueProps[$settingKey])) { $this->handleSingleValue($settingKey, $uniqueProps[$settingKey], $values, 'addProperty'); return 0; } if (isset($multiProps[$settingKey])) { $this->handleMultiValue($settingKey, $multiProps[$settingKey], $values, 'addProperty'); return 0; } if (Preg::isMatchStrictGroups('/^repos?(?:itories)?\.(.+)/', $settingKey, $matches)) { if ($input->getOption('unset')) { $this->configSource->removeRepository($matches[1]); return 0; } if (2 === count($values)) { $this->configSource->addRepository($matches[1], [ 'type' => $values[0], 'url' => $values[1], ], $input->getOption('append')); return 0; } if (1 === count($values)) { $value = strtolower($values[0]); if (true === $booleanValidator($value)) { if (false === $booleanNormalizer($value)) { $this->configSource->addRepository($matches[1], false, $input->getOption('append')); return 0; } } else { $value = JsonFile::parseJson($values[0]); $this->configSource->addRepository($matches[1], $value, $input->getOption('append')); return 0; } } throw new \RuntimeException('You must pass the type and a url. Example: php composer.phar config repositories.foo vcs https://bar.com'); } if (Preg::isMatch('/^extra\.(.+)/', $settingKey, $matches)) { if ($input->getOption('unset')) { $this->configSource->removeProperty($settingKey); return 0; } $value = $values[0]; if ($input->getOption('json')) { $value = JsonFile::parseJson($value); if ($input->getOption('merge')) { $currentValue = $this->configFile->read(); $bits = explode('.', $settingKey); foreach ($bits as $bit) { $currentValue = $currentValue[$bit] ?? null; } if (is_array($currentValue) && is_array($value)) { if (array_is_list($currentValue) && array_is_list($value)) { $value = array_merge($currentValue, $value); } else { $value = $value + $currentValue; } } } } $this->configSource->addProperty($settingKey, $value); return 0; } if (Preg::isMatch('/^suggest\.(.+)/', $settingKey, $matches)) { if ($input->getOption('unset')) { $this->configSource->removeProperty($settingKey); return 0; } $this->configSource->addProperty($settingKey, implode(' ', $values)); return 0; } if (in_array($settingKey, ['suggest', 'extra'], true) && $input->getOption('unset')) { $this->configSource->removeProperty($settingKey); return 0; } if (Preg::isMatch('/^platform\.(.+)/', $settingKey, $matches)) { if ($input->getOption('unset')) { $this->configSource->removeConfigSetting($settingKey); return 0; } $this->configSource->addConfigSetting($settingKey, $values[0] === 'false' ? false : $values[0]); return 0; } if ($settingKey === 'platform' && $input->getOption('unset')) { $this->configSource->removeConfigSetting($settingKey); return 0; } if (in_array($settingKey, ['audit.ignore', 'audit.ignore-abandoned'], true)) { if ($input->getOption('unset')) { $this->configSource->removeConfigSetting($settingKey); return 0; } $value = $values; if ($input->getOption('json')) { $value = JsonFile::parseJson($values[0]); if (!is_array($value)) { throw new \RuntimeException('Expected an array or object for '.$settingKey); } } if ($input->getOption('merge')) { $currentConfig = $this->configFile->read(); $currentValue = $currentConfig['config']['audit'][str_replace('audit.', '', $settingKey)] ?? null; if ($currentValue !== null && is_array($currentValue) && is_array($value)) { if (array_is_list($currentValue) && array_is_list($value)) { $value = array_merge($currentValue, $value); } elseif (!array_is_list($currentValue) && !array_is_list($value)) { $value = $value + $currentValue; } else { throw new \RuntimeException('Cannot merge array and object for '.$settingKey); } } } $this->configSource->addConfigSetting($settingKey, $value); return 0; } if (Preg::isMatch('/^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|http-basic|custom-headers|bearer|forgejo-token)\.(.+)/', $settingKey, $matches)) { if ($input->getOption('unset')) { $this->authConfigSource->removeConfigSetting($matches[1].'.'.$matches[2]); $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]); return 0; } if ($matches[1] === 'bitbucket-oauth') { if (2 !== count($values)) { throw new \RuntimeException('Expected two arguments (consumer-key, consumer-secret), got '.count($values)); } $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]); $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], ['consumer-key' => $values[0], 'consumer-secret' => $values[1]]); } elseif ($matches[1] === 'gitlab-token' && 2 === count($values)) { $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]); $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], ['username' => $values[0], 'token' => $values[1]]); } elseif (in_array($matches[1], ['github-oauth', 'gitlab-oauth', 'gitlab-token', 'bearer'], true)) { if (1 !== count($values)) { throw new \RuntimeException('Too many arguments, expected only one token'); } $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]); $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], $values[0]); } elseif ($matches[1] === 'http-basic') { if (2 !== count($values)) { throw new \RuntimeException('Expected two arguments (username, password), got '.count($values)); } $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]); $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], ['username' => $values[0], 'password' => $values[1]]); } elseif ($matches[1] === 'custom-headers') { if (count($values) === 0) { throw new \RuntimeException('Expected at least one argument (header), got none'); } $formattedHeaders = []; foreach ($values as $header) { if (!is_string($header)) { throw new \RuntimeException('Headers must be strings in "Header-Name: Header-Value" format'); } if (!Preg::isMatch('/^[^:]+:\s*.+$/', $header, $headerParts)) { throw new \RuntimeException('Header "' . $header . '" is not in "Header-Name: Header-Value" format'); } $formattedHeaders[] = $header; } $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]); $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], $formattedHeaders); } elseif ($matches[1] === 'forgejo-token') { if (2 !== count($values)) { throw new \RuntimeException('Expected two arguments (username, access token), got '.count($values)); } $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]); $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], ['username' => $values[0], 'token' => $values[1]]); } return 0; } if (Preg::isMatch('/^scripts\.(.+)/', $settingKey, $matches)) { if ($input->getOption('unset')) { $this->configSource->removeProperty($settingKey); return 0; } $this->configSource->addProperty($settingKey, count($values) > 1 ? $values : $values[0]); return 0; } if ($input->getOption('unset')) { $this->configSource->removeProperty($settingKey); return 0; } throw new \InvalidArgumentException('Setting '.$settingKey.' does not exist or is not supported by this command'); } protected function handleSingleValue(string $key, array $callbacks, array $values, string $method): void { [$validator, $normalizer] = $callbacks; if (1 !== count($values)) { throw new \RuntimeException('You can only pass one value. Example: php composer.phar config process-timeout 300'); } if (true !== $validation = $validator($values[0])) { throw new \RuntimeException(sprintf( '"%s" is an invalid value'.($validation ? ' ('.$validation.')' : ''), $values[0] )); } $normalizedValue = $normalizer($values[0]); if ($key === 'disable-tls') { if (!$normalizedValue && $this->config->get('disable-tls')) { $this->getIO()->writeError('You are now running Composer with SSL/TLS protection enabled.'); } elseif ($normalizedValue && !$this->config->get('disable-tls')) { $this->getIO()->writeError('You are now running Composer with SSL/TLS protection disabled.'); } } call_user_func([$this->configSource, $method], $key, $normalizedValue); } protected function handleMultiValue(string $key, array $callbacks, array $values, string $method): void { [$validator, $normalizer] = $callbacks; if (true !== $validation = $validator($values)) { throw new \RuntimeException(sprintf( '%s is an invalid value'.($validation ? ' ('.$validation.')' : ''), json_encode($values) )); } call_user_func([$this->configSource, $method], $key, $normalizer($values)); } protected function listConfiguration(array $contents, array $rawContents, OutputInterface $output, ?string $k = null, bool $showSource = false): void { $origK = $k; $io = $this->getIO(); foreach ($contents as $key => $value) { if ($k === null && !in_array($key, ['config', 'repositories'])) { continue; } $rawVal = $rawContents[$key] ?? null; if (is_array($value) && (!is_numeric(key($value)) || ($key === 'repositories' && null === $k))) { $k .= Preg::replace('{^config\.}', '', $key . '.'); $this->listConfiguration($value, $rawVal, $output, $k, $showSource); $k = $origK; continue; } if (is_array($value)) { $value = array_map(static function ($val) { return is_array($val) ? json_encode($val) : $val; }, $value); $value = '['.implode(', ', $value).']'; } if (is_bool($value)) { $value = var_export($value, true); } $source = ''; if ($showSource) { $source = ' (' . $this->config->getSourceOfValue($k . $key) . ')'; } if (null !== $k && 0 === strpos($k, 'repositories')) { $link = 'https://getcomposer.org/doc/05-repositories.md'; } else { $id = Preg::replace('{\..*$}', '', $k === '' || $k === null ? (string) $key : $k); $id = Preg::replace('{[^a-z0-9]}i', '-', strtolower(trim($id))); $id = Preg::replace('{-+}', '-', $id); $link = 'https://getcomposer.org/doc/06-config.md#' . $id; } if (is_string($rawVal) && $rawVal !== $value) { $io->write('[' . $k . $key . '] ' . $rawVal . ' (' . $value . ')' . $source, true, IOInterface::QUIET); } else { $io->write('[' . $k . $key . '] ' . $value . '' . $source, true, IOInterface::QUIET); } } } private function suggestSettingKeys(): \Closure { return function (CompletionInput $input): array { if ($input->getOption('list') || $input->getOption('editor') || $input->getOption('auth')) { return []; } $config = Factory::createConfig(); $configFile = new JsonFile($this->getComposerConfigFile($input, $config)); if ($configFile->exists()) { $config->merge($configFile->read(), $configFile->getPath()); } $authConfigFile = new JsonFile($this->getAuthConfigFile($input, $config)); if ($authConfigFile->exists()) { $config->merge(['config' => $authConfigFile->read()], $authConfigFile->getPath()); } $rawConfig = $config->raw(); $keys = array_merge( $this->flattenSettingKeys($rawConfig['config']), $this->flattenSettingKeys($rawConfig['repositories'], 'repositories.') ); if ($input->getOption('unset')) { $sources = [$configFile->getPath(), $authConfigFile->getPath()]; $keys = array_filter( $keys, static function (string $key) use ($config, $sources): bool { return in_array($config->getSourceOfValue($key), $sources, true); } ); } else { $keys = array_merge($keys, self::CONFIGURABLE_PACKAGE_PROPERTIES); } if ($configFile->exists()) { $properties = array_filter( $configFile->read(), static function (string $key): bool { return in_array($key, self::CONFIGURABLE_PACKAGE_PROPERTIES, true); }, ARRAY_FILTER_USE_KEY ); $keys = array_merge( $keys, $this->flattenSettingKeys($properties) ); } $completionValue = $input->getCompletionValue(); if ($completionValue !== '') { $keys = array_filter( $keys, static function (string $key) use ($completionValue): bool { return str_starts_with($key, $completionValue); } ); } sort($keys); return array_unique($keys); }; } private function flattenSettingKeys(array $config, string $prefix = ''): array { $keys = []; foreach ($config as $key => $value) { $keys[] = [$prefix . $key]; if (is_array($value) && !array_is_list($value) && $prefix !== 'repositories.') { $keys[] = $this->flattenSettingKeys($value, $prefix . $key . '.'); } } return array_merge(...$keys); } } setName('create-project') ->setDescription('Creates new project from a package into given directory') ->setDefinition([ new InputArgument('package', InputArgument::OPTIONAL, 'Package name to be installed', null, $this->suggestAvailablePackage()), new InputArgument('directory', InputArgument::OPTIONAL, 'Directory where the files should be created'), new InputArgument('version', InputArgument::OPTIONAL, 'Version, will default to latest'), new InputOption('stability', 's', InputOption::VALUE_REQUIRED, 'Minimum-stability allowed (unless a version is specified).'), new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'), new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist (default behavior).'), new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).', null, $this->suggestPreferInstall()), new InputOption('repository', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Add custom repositories to look the package up, either by URL or using JSON arrays'), new InputOption('repository-url', null, InputOption::VALUE_REQUIRED, 'DEPRECATED: Use --repository instead.'), new InputOption('add-repository', null, InputOption::VALUE_NONE, 'Add the custom repository in the composer.json. If a lock file is present it will be deleted and an update will be run instead of install.'), new InputOption('require', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Require additional package(s) to composer.json after installing the project. If a lock file is present it will be deleted and an update will be run instead of install.'), new InputOption('dev', null, InputOption::VALUE_NONE, 'Enables installation of require-dev packages (enabled by default, only present for BC).'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'), new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'DEPRECATED: Use no-plugins instead.'), new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Whether to prevent execution of all defined scripts in the root package.'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('no-secure-http', null, InputOption::VALUE_NONE, 'Disable the secure-http config option temporarily while installing the root package. Use at your own risk. Using this flag is a bad idea.'), new InputOption('keep-vcs', null, InputOption::VALUE_NONE, 'Whether to prevent deleting the vcs folder.'), new InputOption('remove-vcs', null, InputOption::VALUE_NONE, 'Whether to force deletion of the vcs folder without prompting.'), new InputOption('no-install', null, InputOption::VALUE_NONE, 'Whether to skip installation of the package dependencies.'), new InputOption('no-audit', null, InputOption::VALUE_NONE, 'Whether to skip auditing of the installed package dependencies (can also be set via the COMPOSER_NO_AUDIT=1 env var).'), new InputOption('audit-format', null, InputOption::VALUE_REQUIRED, 'Audit output format. Must be "table", "plain", "json" or "summary".', Auditor::FORMAT_SUMMARY, Auditor::FORMATS), new InputOption('no-security-blocking', null, InputOption::VALUE_NONE, 'DEPRECATED: use --no-blocking instead. Allows installing packages with security advisories or that are abandoned (can also be set via the COMPOSER_NO_SECURITY_BLOCKING=1 env var).'), new InputOption('no-blocking', null, InputOption::VALUE_NONE, 'Disables all policy blocking during this command (can also be set via the COMPOSER_NO_BLOCKING=1 env var).'), new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'), new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'), new InputOption('ask', null, InputOption::VALUE_NONE, 'Whether to ask for project directory.'), ]) ->setHelp( <<create-project command creates a new project from a given package into a new directory. If executed without params and in a directory with a composer.json file it installs the packages for the current project. You can use this command to bootstrap new projects or setup a clean version-controlled installation for developers of your project. php composer.phar create-project vendor/project target-directory [version] You can also specify the version with the package name using = or : as separator. php composer.phar create-project vendor/project:version target-directory To install unstable packages, either specify the version you want, or use the --stability=dev (where dev can be one of RC, beta, alpha or dev). To setup a developer workable version you should create the project using the source controlled code by appending the '--prefer-source' flag. To install a package from another repository than the default one you can pass the '--repository=https://myrepository.org' flag. Read more at https://getcomposer.org/doc/03-cli.md#create-project EOT ) ; } protected function execute(InputInterface $input, OutputInterface $output): int { $config = Factory::createConfig(); $io = $this->getIO(); [$preferSource, $preferDist] = $this->getPreferredInstallOptions($config, $input, true); if ($input->getOption('dev')) { $io->writeError('You are using the deprecated option "dev". Dev packages are installed by default now.'); } if ($input->getOption('no-custom-installers')) { $io->writeError('You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.'); $input->setOption('no-plugins', true); } if ($input->isInteractive() && $input->getOption('ask')) { $package = $input->getArgument('package'); if (null === $package) { throw new \RuntimeException('Not enough arguments (missing: "package").'); } $parts = explode("/", strtolower($package), 2); $input->setArgument('directory', $io->ask('New project directory ['.array_pop($parts).']: ')); } return $this->installProject( $io, $config, $input, $input->getArgument('package'), $input->getArgument('directory'), $input->getArgument('version'), $input->getOption('stability'), $preferSource, $preferDist, !$input->getOption('no-dev'), \count($input->getOption('repository')) > 0 ? $input->getOption('repository') : $input->getOption('repository-url'), $input->getOption('no-plugins'), $input->getOption('no-scripts'), $input->getOption('no-progress'), $input->getOption('no-install'), $this->getPlatformRequirementFilter($input), !$input->getOption('no-secure-http'), $input->getOption('add-repository'), $input->getOption('require') ); } public function installProject(IOInterface $io, Config $config, InputInterface $input, ?string $packageName = null, ?string $directory = null, ?string $packageVersion = null, ?string $stability = 'stable', bool $preferSource = false, bool $preferDist = false, bool $installDevPackages = false, $repositories = null, bool $disablePlugins = false, bool $disableScripts = false, bool $noProgress = false, bool $noInstall = false, ?PlatformRequirementFilterInterface $platformRequirementFilter = null, bool $secureHttp = true, bool $addRepository = false, array $requiredPackages = []): int { $oldCwd = Platform::getCwd(); if ($repositories !== null && !is_array($repositories)) { $repositories = (array) $repositories; } $platformRequirementFilter = $platformRequirementFilter ?? PlatformRequirementFilterFactory::ignoreNothing(); $io->loadConfiguration($config); $this->suggestedPackagesReporter = new SuggestedPackagesReporter($io); if ($packageName !== null) { $installedFromVcs = $this->installRootPackage($input, $io, $config, $packageName, $platformRequirementFilter, $directory, $packageVersion, $stability, $preferSource, $preferDist, $installDevPackages, $repositories, $disablePlugins, $disableScripts, $noProgress, $secureHttp); } else { $installedFromVcs = false; } $composer = $this->createComposerInstance($input, $io, null, $disablePlugins, $disableScripts); $flushLockFile = false; if ($repositories !== null && $addRepository) { foreach ($repositories as $index => $repo) { $repoConfig = RepositoryFactory::configFromString($io, $composer->getConfig(), $repo, true); $composerJsonRepositoriesConfig = $composer->getConfig()->getRepositories(); $name = RepositoryFactory::generateRepositoryName($index, $repoConfig, $composerJsonRepositoriesConfig); $configSource = new JsonConfigSource(new JsonFile('composer.json')); if ( (isset($repoConfig['packagist']) && $repoConfig === ['packagist' => false]) || (isset($repoConfig['packagist.org']) && $repoConfig === ['packagist.org' => false]) ) { $configSource->addRepository('packagist.org', false); } else { $configSource->addRepository($name, $repoConfig, false); } $flushLockFile = true; $composer = $this->createComposerInstance($input, $io, null, $disablePlugins); } } if (\count($requiredPackages) > 0) { $configSource = new JsonConfigSource(new JsonFile('composer.json')); $flushLockFile = true; foreach ($requiredPackages as $nameColonConstraint) { $nameColonConstraintExploded = explode(':', $nameColonConstraint); $nameColonConstraintExploded[1] = $nameColonConstraintExploded[1] ?? '*'; $configSource->addLink( 'require', $nameColonConstraintExploded[0], $nameColonConstraintExploded[1] ); } $composer = $this->createComposerInstance($input, $io, null, $disablePlugins); } if ($flushLockFile && is_file('composer.lock')) { unlink('composer.lock'); } $process = $composer->getLoop()->getProcessExecutor(); $fs = new Filesystem($process); $composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_ROOT_PACKAGE_INSTALL, $installDevPackages); $config = $composer->getConfig(); [$preferSource, $preferDist] = $this->getPreferredInstallOptions($config, $input); if ($noInstall === false) { $composer->getInstallationManager()->setOutputProgress(!$noProgress); $installer = Installer::create($io, $composer); $installer->setPreferSource($preferSource) ->setPreferDist($preferDist) ->setDevMode($installDevPackages) ->setPlatformRequirementFilter($platformRequirementFilter) ->setSuggestedPackagesReporter($this->suggestedPackagesReporter) ->setOptimizeAutoloader($config->get('optimize-autoloader')) ->setClassMapAuthoritative($config->get('classmap-authoritative')) ->setApcuAutoloader($config->get('apcu-autoloader')) ->setPolicyConfig($this->createPolicyConfig($config, $input)) ->setAuditConfig($this->createAuditConfig($input)); if (!$composer->getLocker()->isLocked()) { $installer->setUpdate(true); } if ($disablePlugins) { $installer->disablePlugins(); } try { $status = $installer->run(); if (0 !== $status) { return $status; } } catch (PluginBlockedException $e) { $io->writeError('Hint: To allow running the config command recommended below before dependencies are installed, run create-project with --no-install.'); $io->writeError('You can then cd into '.getcwd().', configure allow-plugins, and finally run a composer install to complete the process.'); throw $e; } } $hasVcs = $installedFromVcs; if ( !$input->getOption('keep-vcs') && $installedFromVcs && ( $input->getOption('remove-vcs') || !$io->isInteractive() || $io->askConfirmation('Do you want to remove the existing VCS (.git, .svn..) history? [y,n]? ') ) ) { $finder = new Finder(); $finder->depth(0)->directories()->in(Platform::getCwd())->ignoreVCS(false)->ignoreDotFiles(false); foreach (['.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg', '.fslckout', '_FOSSIL_'] as $vcsName) { $finder->name($vcsName); } try { $dirs = iterator_to_array($finder); unset($finder); foreach ($dirs as $dir) { if (!$fs->removeDirectory((string) $dir)) { throw new \RuntimeException('Could not remove '.$dir); } } } catch (\Exception $e) { $io->writeError('An error occurred while removing the VCS metadata: '.$e->getMessage().''); } $hasVcs = false; } if (!$hasVcs) { $package = $composer->getPackage(); $configSource = new JsonConfigSource(new JsonFile('composer.json')); foreach (BasePackage::$supportedLinkTypes as $type => $meta) { foreach ($package->{'get'.$meta['method']}() as $link) { if ($link->getPrettyConstraint() === 'self.version') { $configSource->addLink($type, $link->getTarget(), $package->getPrettyVersion()); } } } } $composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_CREATE_PROJECT_CMD, $installDevPackages); chdir($oldCwd); return 0; } protected function installRootPackage(InputInterface $input, IOInterface $io, Config $config, string $packageName, PlatformRequirementFilterInterface $platformRequirementFilter, ?string $directory = null, ?string $packageVersion = null, ?string $stability = 'stable', bool $preferSource = false, bool $preferDist = false, bool $installDevPackages = false, ?array $repositories = null, bool $disablePlugins = false, bool $disableScripts = false, bool $noProgress = false, bool $secureHttp = true): bool { $parser = new VersionParser(); $requirements = $parser->parseNameVersionPairs([$packageName]); $name = strtolower($requirements[0]['name']); if (!$packageVersion && isset($requirements[0]['version'])) { $packageVersion = $requirements[0]['version']; } if (null === $directory) { $parts = explode("/", $name, 2); $directory = Platform::getCwd() . DIRECTORY_SEPARATOR . array_pop($parts); } $directory = rtrim($directory, '/\\'); $process = new ProcessExecutor($io); $fs = new Filesystem($process); if (!$fs->isAbsolutePath($directory)) { $directory = Platform::getCwd() . DIRECTORY_SEPARATOR . $directory; } if ('' === $directory) { throw new \UnexpectedValueException('Got an empty target directory, something went wrong'); } $config->setBaseDir($directory); if (!$secureHttp) { $config->merge(['config' => ['secure-http' => false]], Config::SOURCE_COMMAND); } $io->writeError('Creating a "' . $packageName . '" project at "' . $fs->findShortestPath(Platform::getCwd(), $directory, true) . '"'); if (file_exists($directory)) { if (!is_dir($directory)) { throw new \InvalidArgumentException('Cannot create project directory at "'.$directory.'", it exists as a file.'); } if (!$fs->isDirEmpty($directory)) { throw new \InvalidArgumentException('Project directory "'.$directory.'" is not empty.'); } } if (null === $stability) { if (null === $packageVersion) { $stability = 'stable'; } elseif (Preg::isMatchStrictGroups('{^[^,\s]*?@('.implode('|', array_keys(BasePackage::STABILITIES)).')$}i', $packageVersion, $match)) { $stability = $match[1]; } else { $stability = VersionParser::parseStability($packageVersion); } } $stability = VersionParser::normalizeStability($stability); if (!isset(BasePackage::STABILITIES[$stability])) { throw new \InvalidArgumentException('Invalid stability provided ('.$stability.'), must be one of: '.implode(', ', array_keys(BasePackage::STABILITIES))); } $composer = $this->createComposerInstance($input, $io, $config->all(), $disablePlugins, $disableScripts); $config = $composer->getConfig(); $config->setBaseDir($directory); $rm = $composer->getRepositoryManager(); $repositorySet = new RepositorySet($stability); if (null === $repositories) { $repositorySet->addRepository(new CompositeRepository(RepositoryFactory::defaultRepos($io, $config, $rm))); } else { foreach ($repositories as $repo) { $repoConfig = RepositoryFactory::configFromString($io, $config, $repo, true); if ( (isset($repoConfig['packagist']) && $repoConfig === ['packagist' => false]) || (isset($repoConfig['packagist.org']) && $repoConfig === ['packagist.org' => false]) ) { continue; } if (($repoConfig['type'] ?? null) === 'path' && !isset($repoConfig['options']['symlink'])) { $repoConfig['options']['symlink'] = false; } $repositorySet->addRepository(RepositoryFactory::createRepo($io, $config, $repoConfig, $rm)); } } $platformOverrides = $config->get('platform'); $platformRepo = new PlatformRepository([], $platformOverrides); $versionSelector = new VersionSelector($repositorySet, $platformRepo); $package = $versionSelector->findBestCandidate($name, $packageVersion, $stability, $platformRequirementFilter, 0, $io); if (!$package) { $errorMessage = "Could not find package $name with " . ($packageVersion ? "version $packageVersion" : "stability $stability"); if (!($platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter) && $versionSelector->findBestCandidate($name, $packageVersion, $stability, PlatformRequirementFilterFactory::ignoreAll())) { throw new \InvalidArgumentException($errorMessage .' in a version installable using your PHP version, PHP extensions and Composer version.'); } throw new \InvalidArgumentException($errorMessage .'.'); } @mkdir($directory, 0777, true); if (false !== ($realDir = realpath($directory))) { $signalHandler = SignalHandler::create([SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP], function (string $signal, SignalHandler $handler) use ($realDir) { $this->getIO()->writeError('Received '.$signal.', aborting', true, IOInterface::DEBUG); $fs = new Filesystem(); $fs->removeDirectory($realDir); $handler->exitWithLastSignal(); }); } if ($package instanceof AliasPackage && $package->getPrettyVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) { $package = $package->getAliasOf(); } $io->writeError('Installing ' . $package->getName() . ' (' . $package->getFullPrettyVersion(false) . ')'); if ($disablePlugins) { $io->writeError('Plugins have been disabled.'); } if ($package instanceof AliasPackage) { $package = $package->getAliasOf(); } $dm = $composer->getDownloadManager(); $dm->setPreferSource($preferSource) ->setPreferDist($preferDist); $projectInstaller = new ProjectInstaller($directory, $dm, $fs); $im = $composer->getInstallationManager(); $im->setOutputProgress(!$noProgress); $im->addInstaller($projectInstaller); $im->execute(new InstalledArrayRepository(), [new InstallOperation($package)]); $im->notifyInstalls($io); $this->suggestedPackagesReporter->addSuggestionsFromPackage($package); $installedFromVcs = 'source' === $package->getInstallationSource(); $io->writeError('Created project in ' . $directory . ''); chdir($directory); if (file_exists($directory.'/composer.json') && Platform::getEnv('COMPOSER') !== false) { Platform::clearEnv('COMPOSER'); } Platform::putEnv('COMPOSER_ROOT_VERSION', $package->getPrettyVersion()); if (isset($signalHandler)) { $signalHandler->unregister(); } return $installedFromVcs; } } setName('depends') ->setAliases(['why']) ->setDescription('Shows which packages cause the given package to be installed') ->setDefinition([ new InputArgument(self::ARGUMENT_PACKAGE, InputArgument::REQUIRED, 'Package to inspect', null, $this->suggestInstalledPackage(true, true)), new InputOption(self::OPTION_RECURSIVE, 'r', InputOption::VALUE_NONE, 'Recursively resolves up to the root package'), new InputOption(self::OPTION_TREE, 't', InputOption::VALUE_NONE, 'Prints the results as a nested tree'), new InputOption('locked', null, InputOption::VALUE_NONE, 'Read dependency information from composer.lock'), ]) ->setHelp( <<php composer.phar depends composer/composer Read more at https://getcomposer.org/doc/03-cli.md#depends-why EOT ) ; } protected function execute(InputInterface $input, OutputInterface $output): int { return parent::doExecute($input, $output); } } setName('diagnose') ->setDescription('Diagnoses the system to identify common errors') ->setHelp( <<diagnose command checks common errors to help debugging problems. The process exit code will be 1 in case of warnings and 2 for errors. Read more at https://getcomposer.org/doc/03-cli.md#diagnose EOT ) ; } protected function execute(InputInterface $input, OutputInterface $output): int { $composer = $this->tryComposer(); $io = $this->getIO(); if ($composer) { $config = $composer->getConfig(); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'diagnose', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $this->process = $composer->getLoop()->getProcessExecutor() ?? new ProcessExecutor($io); } else { $config = Factory::createConfig(); $this->process = new ProcessExecutor($io); } $config->merge(['config' => ['secure-http' => false]], Config::SOURCE_COMMAND); $config->prohibitUrlByConfig('http://repo.packagist.org', new NullIO); $this->httpDownloader = Factory::createHttpDownloader($io, $config); if (strpos(__FILE__, 'phar:') === 0) { $io->write('Checking pubkeys: ', false); $this->outputResult($this->checkPubKeys($config)); $io->write('Checking Composer version: ', false); $this->outputResult($this->checkVersion($config)); } $io->write(sprintf('Composer version: %s', Composer::getVersion())); $io->write('Checking Composer and its dependencies for vulnerabilities: ', false); $this->outputResult($this->checkComposerAudit($config)); $platformOverrides = $config->get('platform') ?: []; $platformRepo = new PlatformRepository([], $platformOverrides); $phpPkg = $platformRepo->findPackage('php', '*'); $phpVersion = $phpPkg->getPrettyVersion(); if ($phpPkg instanceof CompletePackageInterface && str_contains((string) $phpPkg->getDescription(), 'overridden')) { $phpVersion .= ' - ' . $phpPkg->getDescription(); } $io->write(sprintf('PHP version: %s', $phpVersion)); if (defined('PHP_BINARY')) { $io->write(sprintf('PHP binary path: %s', PHP_BINARY)); } $io->write('OpenSSL version: ' . (defined('OPENSSL_VERSION_TEXT') ? ''.OPENSSL_VERSION_TEXT.'' : 'missing')); $io->write('curl version: ' . $this->getCurlVersion()); $finder = new ExecutableFinder; $hasSystemUnzip = (bool) $finder->find('unzip'); $bin7zip = ''; if ($hasSystem7zip = (bool) $finder->find('7z', null, ['C:\Program Files\7-Zip'])) { $bin7zip = '7z'; } elseif (!Platform::isWindows() && $hasSystem7zip = (bool) $finder->find('7zz')) { $bin7zip = '7zz'; } elseif (!Platform::isWindows() && $hasSystem7zip = (bool) $finder->find('7za')) { $bin7zip = '7za'; } $io->write( 'zip: ' . (extension_loaded('zip') ? 'extension present' : 'extension not loaded') . ', ' . ($hasSystemUnzip ? 'unzip present' : 'unzip not available') . ', ' . ($hasSystem7zip ? '7-Zip present ('.$bin7zip.')' : '7-Zip not available') . (($hasSystem7zip || $hasSystemUnzip) && !function_exists('proc_open') ? ', proc_open is disabled or not present, unzip/7-z will not be usable' : '') ); if ($composer) { $io->write('Active plugins: '.implode(', ', $composer->getPluginManager()->getRegisteredPlugins())); $io->write('Checking composer.json: ', false); $this->outputResult($this->checkComposerSchema()); if ($composer->getLocker()->isLocked()) { $io->write('Checking composer.lock: ', false); $this->outputResult($this->checkComposerLockSchema($composer->getLocker())); } } $io->write('Checking platform settings: ', false); $this->outputResult($this->checkPlatform()); $io->write('Checking git settings: ', false); $this->outputResult($this->checkGit()); $io->write('Checking http connectivity to packagist: ', false); $this->outputResult($this->checkHttp('http', $config)); $io->write('Checking https connectivity to packagist: ', false); $this->outputResult($this->checkHttp('https', $config)); foreach ($config->getRepositories() as $repo) { if (($repo['type'] ?? null) === 'composer' && isset($repo['url'])) { $composerRepo = new ComposerRepository($repo, $this->getIO(), $config, $this->httpDownloader); $reflMethod = new \ReflectionMethod($composerRepo, 'getPackagesJsonUrl'); if (PHP_VERSION_ID < 80100) { $reflMethod->setAccessible(true); } $url = $reflMethod->invoke($composerRepo); if (!str_starts_with($url, 'http')) { continue; } if (str_starts_with($url, 'https://repo.packagist.org')) { continue; } $io->write('Checking connectivity to ' . $repo['url'].': ', false); $this->outputResult($this->checkComposerRepo($url, $config)); } } $proxyManager = ProxyManager::getInstance(); $protos = $config->get('disable-tls') === true ? ['http'] : ['http', 'https']; try { foreach ($protos as $proto) { $proxy = $proxyManager->getProxyForRequest($proto.'://repo.packagist.org'); if ($proxy->getStatus() !== '') { $type = $proxy->isSecure() ? 'HTTPS' : 'HTTP'; $io->write('Checking '.$type.' proxy with '.$proto.': ', false); $this->outputResult($this->checkHttpProxy($proxy, $proto)); } } } catch (TransportException $e) { $io->write('Checking HTTP proxy: ', false); $status = $this->checkConnectivityAndComposerNetworkHttpEnablement(); $this->outputResult(is_string($status) ? $status : $e); } if (count($oauth = $config->get('github-oauth')) > 0) { foreach ($oauth as $domain => $token) { $io->write('Checking '.$domain.' oauth access: ', false); $this->outputResult($this->checkGithubOauth($domain, $token)); } } else { $io->write('Checking github.com rate limit: ', false); try { $rate = $this->getGithubRateLimit('github.com'); if (!is_array($rate)) { $this->outputResult($rate); } elseif (10 > $rate['remaining']) { $io->write('WARNING'); $io->write(sprintf( 'GitHub has a rate limit on their API. ' . 'You currently have %u ' . 'out of %u requests left.' . PHP_EOL . 'See https://developer.github.com/v3/#rate-limiting and also' . PHP_EOL . ' https://getcomposer.org/doc/articles/troubleshooting.md#api-rate-limit-and-oauth-tokens', $rate['remaining'], $rate['limit'] )); } else { $this->outputResult(true); } } catch (\Exception $e) { if ($e instanceof TransportException && $e->getCode() === 401) { $this->outputResult('The oauth token for github.com seems invalid, run "composer config --global --unset github-oauth.github.com" to remove it'); } else { $this->outputResult($e); } } } $io->write('Checking disk free space: ', false); $this->outputResult($this->checkDiskSpace($config)); return $this->exitCode; } private function checkComposerSchema() { $validator = new ConfigValidator($this->getIO()); [$errors, , $warnings] = $validator->validate(Factory::getComposerFile()); if ($errors || $warnings) { $messages = [ 'error' => $errors, 'warning' => $warnings, ]; $output = ''; foreach ($messages as $style => $msgs) { foreach ($msgs as $msg) { $output .= '<' . $style . '>' . $msg . '' . PHP_EOL; } } return rtrim($output); } return true; } private function checkComposerLockSchema(Locker $locker) { $json = $locker->getJsonFile(); try { $json->validateSchema(JsonFile::LOCK_SCHEMA); } catch (JsonValidationException $e) { $output = ''; foreach ($e->getErrors() as $error) { $output .= ''.$error.''.PHP_EOL; } return trim($output); } return true; } private function checkGit(): string { if (!function_exists('proc_open')) { return 'proc_open is not available, git cannot be used'; } $this->process->execute(['git', 'config', 'color.ui'], $output); if (strtolower(trim($output)) === 'always') { return 'Your git color.ui setting is set to always, this is known to create issues. Use "git config --global color.ui true" to set it correctly.'; } $gitVersion = Git::getVersion($this->process); if (null === $gitVersion) { return 'No git process found'; } if (version_compare('2.24.0', $gitVersion, '>')) { return 'Your git version ('.$gitVersion.') is too old and possibly will cause issues. Please upgrade to git 2.24 or above'; } return 'OK git version '.$gitVersion.''; } private function checkHttp(string $proto, Config $config) { $result = $this->checkConnectivityAndComposerNetworkHttpEnablement(); if ($result !== true) { return $result; } $result = []; if ($proto === 'https' && $config->get('disable-tls') === true) { $tlsWarning = 'Composer is configured to disable SSL/TLS protection. This will leave remote HTTPS requests vulnerable to Man-In-The-Middle attacks.'; } try { $this->httpDownloader->get($proto . '://repo.packagist.org/packages.json'); } catch (TransportException $e) { $hints = HttpDownloader::getExceptionHints($e); if (null !== $hints && count($hints) > 0) { foreach ($hints as $hint) { $result[] = $hint; } } $result[] = '[' . get_class($e) . '] ' . $e->getMessage() . ''; } if (isset($tlsWarning)) { $result[] = $tlsWarning; } if (count($result) > 0) { return $result; } return true; } private function checkComposerRepo(string $url, Config $config) { $result = $this->checkConnectivityAndComposerNetworkHttpEnablement(); if ($result !== true) { return $result; } $result = []; if (str_starts_with($url, 'https://') && $config->get('disable-tls') === true) { $tlsWarning = 'Composer is configured to disable SSL/TLS protection. This will leave remote HTTPS requests vulnerable to Man-In-The-Middle attacks.'; } try { $this->httpDownloader->get($url); } catch (TransportException $e) { $hints = HttpDownloader::getExceptionHints($e); if (null !== $hints && count($hints) > 0) { foreach ($hints as $hint) { $result[] = $hint; } } $result[] = '[' . get_class($e) . '] ' . $e->getMessage() . ''; } if (isset($tlsWarning)) { $result[] = $tlsWarning; } if (count($result) > 0) { return $result; } return true; } private function checkHttpProxy(RequestProxy $proxy, string $protocol) { $result = $this->checkConnectivityAndComposerNetworkHttpEnablement(); if ($result !== true) { return $result; } try { $proxyStatus = $proxy->getStatus(); if ($proxy->isExcludedByNoProxy()) { return 'SKIP Because repo.packagist.org is '.$proxyStatus.''; } $json = $this->httpDownloader->get($protocol.'://repo.packagist.org/packages.json')->decodeJson(); if (isset($json['provider-includes'])) { $hash = reset($json['provider-includes']); $hash = $hash['sha256']; $path = str_replace('%hash%', $hash, key($json['provider-includes'])); $provider = $this->httpDownloader->get($protocol.'://repo.packagist.org/'.$path)->getBody(); if (hash('sha256', $provider) !== $hash) { return 'It seems that your proxy ('.$proxyStatus.') is modifying '.$protocol.' traffic on the fly'; } } return 'OK '.$proxyStatus.''; } catch (\Exception $e) { return $e; } } private function checkGithubOauth(string $domain, string $token) { $result = $this->checkConnectivityAndComposerNetworkHttpEnablement(); if ($result !== true) { return $result; } $this->getIO()->setAuthentication($domain, $token, 'x-oauth-basic'); try { $url = $domain === 'github.com' ? 'https://api.'.$domain.'/' : 'https://'.$domain.'/api/v3/'; $response = $this->httpDownloader->get($url, [ 'retry-auth-failure' => false, ]); $expiration = $response->getHeader('github-authentication-token-expiration'); if ($expiration === null) { return 'OK does not expire'; } return 'OK expires on '. $expiration .''; } catch (\Exception $e) { if ($e instanceof TransportException && $e->getCode() === 401) { return 'The oauth token for '.$domain.' seems invalid, run "composer config --global --unset github-oauth.'.$domain.'" to remove it'; } return $e; } } private function getGithubRateLimit(string $domain, ?string $token = null) { $result = $this->checkConnectivityAndComposerNetworkHttpEnablement(); if ($result !== true) { return $result; } if ($token) { $this->getIO()->setAuthentication($domain, $token, 'x-oauth-basic'); } $url = $domain === 'github.com' ? 'https://api.'.$domain.'/rate_limit' : 'https://'.$domain.'/api/rate_limit'; $data = $this->httpDownloader->get($url, ['retry-auth-failure' => false])->decodeJson(); return $data['resources']['core']; } private function checkDiskSpace(Config $config) { if (!function_exists('disk_free_space')) { return true; } $minSpaceFree = 1024 * 1024; if ((($df = @disk_free_space($dir = $config->get('home'))) !== false && $df < $minSpaceFree) || (($df = @disk_free_space($dir = $config->get('vendor-dir'))) !== false && $df < $minSpaceFree) ) { return 'The disk hosting '.$dir.' is full'; } return true; } private function checkPubKeys(Config $config) { $home = $config->get('home'); $errors = []; $io = $this->getIO(); if (file_exists($home.'/keys.tags.pub') && file_exists($home.'/keys.dev.pub')) { $io->write(''); } if (file_exists($home.'/keys.tags.pub')) { $io->write('Tags Public Key Fingerprint: ' . Keys::fingerprint($home.'/keys.tags.pub')); } else { $errors[] = 'Missing pubkey for tags verification'; } if (file_exists($home.'/keys.dev.pub')) { $io->write('Dev Public Key Fingerprint: ' . Keys::fingerprint($home.'/keys.dev.pub')); } else { $errors[] = 'Missing pubkey for dev verification'; } if ($errors) { $errors[] = 'Run composer self-update --update-keys to set them up'; } return $errors ?: true; } private function checkVersion(Config $config) { $result = $this->checkConnectivityAndComposerNetworkHttpEnablement(); if ($result !== true) { return $result; } $versionsUtil = new Versions($config, $this->httpDownloader); try { $latest = $versionsUtil->getLatest(); } catch (\Exception $e) { return $e; } if (Composer::VERSION !== $latest['version'] && Composer::VERSION !== '@package_version@') { return 'You are not running the latest '.$versionsUtil->getChannel().' version, run `composer self-update` to update ('.Composer::VERSION.' => '.$latest['version'].')'; } return true; } private function checkComposerAudit(Config $config) { $result = $this->checkConnectivityAndComposerNetworkHttpEnablement(); if ($result !== true) { return $result; } $auditor = new Auditor(); $repoSet = new RepositorySet(); $installedJson = new JsonFile(__DIR__ . '/../../../vendor/composer/installed.json'); if (!$installedJson->exists()) { return 'Could not find Composer\'s installed.json, this must be a non-standard Composer installation.'; } $localRepo = new FilesystemRepository($installedJson); $version = Composer::getVersion(); $packages = $localRepo->getCanonicalPackages(); if ($version !== '@package_version@') { $versionParser = new VersionParser(); $normalizedVersion = $versionParser->normalize($version); $rootPkg = new RootPackage('composer/composer', $normalizedVersion, $version); $packages[] = $rootPkg; } $repoSet->addRepository(new ComposerRepository(['type' => 'composer', 'url' => 'https://packagist.org'], new NullIO(), $config, $this->httpDownloader)); $policyConfig = $this->createPolicyConfig($config, null); $policyConfig = $policyConfig->withAudit(ListPolicyConfig::AUDIT_IGNORE); try { $io = new BufferIO(); $result = $auditor->audit($io, $repoSet, $policyConfig, $packages, Auditor::FORMAT_TABLE); } catch (\Throwable $e) { return 'Failed performing audit: '.$e->getMessage().''; } if ($result > 0) { return 'Audit found some issues:' . PHP_EOL . $io->getOutput(); } return true; } private function getCurlVersion(): string { if (extension_loaded('curl')) { if (!HttpDownloader::isCurlEnabled()) { return 'disabled via disable_functions, using php streams fallback, which reduces performance'; } $version = curl_version(); $libzVersion = isset($version['libz_version']) && $version['libz_version'] !== '' ? $version['libz_version'] : 'missing'; $brotliVersion = isset($version['brotli_version']) && $version['brotli_version'] !== '' ? $version['brotli_version'] : 'missing'; $sslVersion = isset($version['ssl_version']) && $version['ssl_version'] !== '' ? $version['ssl_version'] : 'missing'; $hasZstd = isset($version['features']) && defined('CURL_VERSION_ZSTD') && 0 !== ($version['features'] & CURL_VERSION_ZSTD); $httpVersions = '1.0, 1.1'; if (isset($version['features']) && \defined('CURL_VERSION_HTTP2') && \defined('CURL_HTTP_VERSION_2_0') && (CURL_VERSION_HTTP2 & $version['features']) !== 0) { $httpVersions .= ', 2'; } if (isset($version['features']) && \defined('CURL_VERSION_HTTP3') && ($version['features'] & CURL_VERSION_HTTP3) !== 0) { $httpVersions .= ', 3'; } return ''.$version['version'].' '. 'libz '.$libzVersion.' '. 'brotli '.$brotliVersion.' '. 'zstd '.($hasZstd ? 'supported' : 'missing').' '. 'ssl '.$sslVersion.' '. 'HTTP '.$httpVersions.''; } return 'missing, using php streams fallback, which reduces performance'; } private function outputResult($result): void { $io = $this->getIO(); if (true === $result) { $io->write('OK'); return; } $hadError = false; $hadWarning = false; if ($result instanceof \Exception) { $result = '['.get_class($result).'] '.$result->getMessage().''; } if (!$result) { $hadError = true; } else { if (!is_array($result)) { $result = [$result]; } foreach ($result as $message) { if (false !== strpos($message, '')) { $hadError = true; } elseif (false !== strpos($message, '')) { $hadWarning = true; } } } if ($hadError) { $io->write('FAIL'); $this->exitCode = max($this->exitCode, 2); } elseif ($hadWarning) { $io->write('WARNING'); $this->exitCode = max($this->exitCode, 1); } if ($result) { foreach ($result as $message) { $io->write(trim($message)); } } } private function checkPlatform() { $output = ''; $out = static function ($msg, $style) use (&$output): void { $output .= '<'.$style.'>'.$msg.''.PHP_EOL; }; $errors = []; $warnings = []; $displayIniMessage = false; $iniMessage = PHP_EOL.PHP_EOL.IniHelper::getMessage(); $iniMessage .= PHP_EOL.'If you can not modify the ini file, you can also run `php -d option=value` to modify ini values on the fly. You can use -d multiple times.'; if (!function_exists('json_decode')) { $errors['json'] = true; } if (!extension_loaded('Phar')) { $errors['phar'] = true; } if (!extension_loaded('filter')) { $errors['filter'] = true; } if (!extension_loaded('hash')) { $errors['hash'] = true; } if (!extension_loaded('iconv') && !extension_loaded('mbstring')) { $errors['iconv_mbstring'] = true; } if (!filter_var(ini_get('allow_url_fopen'), FILTER_VALIDATE_BOOLEAN)) { $errors['allow_url_fopen'] = true; } if (extension_loaded('ionCube Loader') && ioncube_loader_iversion() < 40009) { $errors['ioncube'] = ioncube_loader_version(); } if (\PHP_VERSION_ID < 70205) { $errors['php'] = PHP_VERSION; } if (!extension_loaded('openssl')) { $errors['openssl'] = true; } if (extension_loaded('openssl') && OPENSSL_VERSION_NUMBER < 0x1000100f) { $warnings['openssl_version'] = true; } if (!defined('HHVM_VERSION') && !extension_loaded('apcu') && filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN)) { $warnings['apc_cli'] = true; } if (!extension_loaded('zlib')) { $warnings['zlib'] = true; } ob_start(); phpinfo(INFO_GENERAL); $phpinfo = ob_get_clean(); if (is_string($phpinfo) && Preg::isMatchStrictGroups('{Configure Command(?: *| *=> *)(.*?)(?:|$)}m', $phpinfo, $match)) { $configure = $match[1]; if (str_contains($configure, '--enable-sigchild')) { $warnings['sigchild'] = true; } if (str_contains($configure, '--with-curlwrappers')) { $warnings['curlwrappers'] = true; } } if (filter_var(ini_get('xdebug.profiler_enabled'), FILTER_VALIDATE_BOOLEAN)) { $warnings['xdebug_profile'] = true; } elseif (XdebugHandler::isXdebugActive()) { $warnings['xdebug_loaded'] = true; } if (defined('PHP_WINDOWS_VERSION_BUILD') && (version_compare(PHP_VERSION, '7.2.23', '<') || (version_compare(PHP_VERSION, '7.3.0', '>=') && version_compare(PHP_VERSION, '7.3.10', '<')))) { $warnings['onedrive'] = PHP_VERSION; } if (extension_loaded('uopz') && !(filter_var(ini_get('uopz.disable'), FILTER_VALIDATE_BOOLEAN) || filter_var(ini_get('uopz.exit'), FILTER_VALIDATE_BOOLEAN))) { $warnings['uopz'] = true; } if (!empty($errors)) { foreach ($errors as $error => $current) { switch ($error) { case 'json': $text = PHP_EOL."The json extension is missing.".PHP_EOL; $text .= "Install it or recompile php without --disable-json"; break; case 'phar': $text = PHP_EOL."The phar extension is missing.".PHP_EOL; $text .= "Install it or recompile php without --disable-phar"; break; case 'filter': $text = PHP_EOL."The filter extension is missing.".PHP_EOL; $text .= "Install it or recompile php without --disable-filter"; break; case 'hash': $text = PHP_EOL."The hash extension is missing.".PHP_EOL; $text .= "Install it or recompile php without --disable-hash"; break; case 'iconv_mbstring': $text = PHP_EOL."The iconv OR mbstring extension is required and both are missing.".PHP_EOL; $text .= "Install either of them or recompile php without --disable-iconv"; break; case 'php': $text = PHP_EOL."Your PHP ({$current}) is too old, you must upgrade to PHP 7.2.5 or higher."; break; case 'allow_url_fopen': $text = PHP_EOL."The allow_url_fopen setting is incorrect.".PHP_EOL; $text .= "Add the following to the end of your `php.ini`:".PHP_EOL; $text .= " allow_url_fopen = On"; $displayIniMessage = true; break; case 'ioncube': $text = PHP_EOL."Your ionCube Loader extension ($current) is incompatible with Phar files.".PHP_EOL; $text .= "Upgrade to ionCube 4.0.9 or higher or remove this line (path may be different) from your `php.ini` to disable it:".PHP_EOL; $text .= " zend_extension = /usr/lib/php5/20090626+lfs/ioncube_loader_lin_5.3.so"; $displayIniMessage = true; break; case 'openssl': $text = PHP_EOL."The openssl extension is missing, which means that secure HTTPS transfers are impossible.".PHP_EOL; $text .= "If possible you should enable it or recompile php with --with-openssl"; break; default: throw new \InvalidArgumentException(sprintf("DiagnoseCommand: Unknown error type \"%s\". Please report at https://github.com/composer/composer/issues/new.", $error)); } $out($text, 'error'); } $output .= PHP_EOL; } if (!empty($warnings)) { foreach ($warnings as $warning => $current) { switch ($warning) { case 'apc_cli': $text = "The apc.enable_cli setting is incorrect.".PHP_EOL; $text .= "Add the following to the end of your `php.ini`:".PHP_EOL; $text .= " apc.enable_cli = Off"; $displayIniMessage = true; break; case 'zlib': $text = 'The zlib extension is not loaded, this can slow down Composer a lot.'.PHP_EOL; $text .= 'If possible, enable it or recompile php with --with-zlib'.PHP_EOL; $displayIniMessage = true; break; case 'sigchild': $text = "PHP was compiled with --enable-sigchild which can cause issues on some platforms.".PHP_EOL; $text .= "Recompile it without this flag if possible, see also:".PHP_EOL; $text .= " https://bugs.php.net/bug.php?id=22999"; break; case 'curlwrappers': $text = "PHP was compiled with --with-curlwrappers which will cause issues with HTTP authentication and GitHub.".PHP_EOL; $text .= " Recompile it without this flag if possible"; break; case 'openssl_version': $opensslVersion = strstr(trim(strstr(OPENSSL_VERSION_TEXT, ' ')), ' ', true); $opensslVersion = $opensslVersion ?: OPENSSL_VERSION_TEXT; $text = "The OpenSSL library ({$opensslVersion}) used by PHP does not support TLSv1.2 or TLSv1.1.".PHP_EOL; $text .= "If possible you should upgrade OpenSSL to version 1.0.1 or above."; break; case 'xdebug_loaded': $text = "The xdebug extension is loaded, this can slow down Composer a little.".PHP_EOL; $text .= " Disabling it when using Composer is recommended."; break; case 'xdebug_profile': $text = "The xdebug.profiler_enabled setting is enabled, this can slow down Composer a lot.".PHP_EOL; $text .= "Add the following to the end of your `php.ini` to disable it:".PHP_EOL; $text .= " xdebug.profiler_enabled = 0"; $displayIniMessage = true; break; case 'onedrive': $text = "The Windows OneDrive folder is not supported on PHP versions below 7.2.23 and 7.3.10.".PHP_EOL; $text .= "Upgrade your PHP ({$current}) to use this location with Composer.".PHP_EOL; break; case 'uopz': $text = "The uopz extension ignores exit calls and may not work with all Composer commands.".PHP_EOL; $text .= "Disabling it when using Composer is recommended."; break; default: throw new \InvalidArgumentException(sprintf("DiagnoseCommand: Unknown warning type \"%s\". Please report at https://github.com/composer/composer/issues/new.", $warning)); } $out($text, 'comment'); } } if ($displayIniMessage) { $out($iniMessage, 'comment'); } if (in_array(Platform::getEnv('COMPOSER_IPRESOLVE'), ['4', '6'], true)) { $warnings['ipresolve'] = true; $out('The COMPOSER_IPRESOLVE env var is set to ' . Platform::getEnv('COMPOSER_IPRESOLVE') .' which may result in network failures below.', 'comment'); } return count($warnings) === 0 && count($errors) === 0 ? true : $output; } private function checkConnectivity() { if (!ini_get('allow_url_fopen')) { return 'SKIP Because allow_url_fopen is missing.'; } return true; } private function checkConnectivityAndComposerNetworkHttpEnablement() { $result = $this->checkConnectivity(); if ($result !== true) { return $result; } $result = $this->checkComposerNetworkHttpEnablement(); if ($result !== true) { return $result; } return true; } private function checkComposerNetworkHttpEnablement() { if ((bool) Platform::getEnv('COMPOSER_DISABLE_NETWORK')) { return 'SKIP Network is disabled by COMPOSER_DISABLE_NETWORK.'; } return true; } } setName('dump-autoload') ->setAliases(['dumpautoload']) ->setDescription('Dumps the autoloader') ->setDefinition([ new InputOption('optimize', 'o', InputOption::VALUE_NONE, 'Optimizes PSR0 and PSR4 packages to be loaded with classmaps too, good for production.'), new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize`.'), new InputOption('apcu', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'), new InputOption('apcu-prefix', null, InputOption::VALUE_REQUIRED, 'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu'), new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything.'), new InputOption('dev', null, InputOption::VALUE_NONE, 'Enables autoload-dev rules. Composer will by default infer this automatically according to the last install or update --no-dev state.'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables autoload-dev rules. Composer will by default infer this automatically according to the last install or update --no-dev state.'), new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'), new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'), new InputOption('strict-psr', null, InputOption::VALUE_NONE, 'Return a failed status code (1) if PSR-4 or PSR-0 mapping errors are present. Requires --optimize to work.'), new InputOption('strict-ambiguous', null, InputOption::VALUE_NONE, 'Return a failed status code (2) if the same class is found in multiple files. Requires --optimize to work.'), ]) ->setHelp( <<php composer.phar dump-autoload Read more at https://getcomposer.org/doc/03-cli.md#dump-autoload-dumpautoload EOT ) ; } protected function execute(InputInterface $input, OutputInterface $output): int { $composer = $this->requireComposer(); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'dump-autoload', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $installationManager = $composer->getInstallationManager(); $localRepo = $composer->getRepositoryManager()->getLocalRepository(); $package = $composer->getPackage(); $config = $composer->getConfig(); $missingDependencies = false; foreach ($localRepo->getCanonicalPackages() as $localPkg) { $installPath = $installationManager->getInstallPath($localPkg); if ($installPath !== null && file_exists($installPath) === false) { $missingDependencies = true; $this->getIO()->write('Not all dependencies are installed. Make sure to run a "composer install" to install missing dependencies'); break; } } $optimize = $input->getOption('optimize') || $config->get('optimize-autoloader'); $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative'); $apcuPrefix = $input->getOption('apcu-prefix'); $apcu = $apcuPrefix !== null || $input->getOption('apcu') || $config->get('apcu-autoloader'); if ($input->getOption('strict-psr') && !$optimize && !$authoritative) { throw new \InvalidArgumentException('--strict-psr mode only works with optimized autoloader, use --optimize or --classmap-authoritative if you want a strict return value.'); } if ($input->getOption('strict-ambiguous') && !$optimize && !$authoritative) { throw new \InvalidArgumentException('--strict-ambiguous mode only works with optimized autoloader, use --optimize or --classmap-authoritative if you want a strict return value.'); } if ($authoritative) { $this->getIO()->write('Generating optimized autoload files (authoritative)'); } elseif ($optimize) { $this->getIO()->write('Generating optimized autoload files'); } else { $this->getIO()->write('Generating autoload files'); } $generator = $composer->getAutoloadGenerator(); if ($input->getOption('dry-run')) { $generator->setDryRun(true); } if ($input->getOption('no-dev')) { $generator->setDevMode(false); } if ($input->getOption('dev')) { if ($input->getOption('no-dev')) { throw new \InvalidArgumentException('You can not use both --no-dev and --dev as they conflict with each other.'); } $generator->setDevMode(true); } $generator->setClassMapAuthoritative($authoritative); $generator->setRunScripts(true); $generator->setApcu($apcu, $apcuPrefix); $generator->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input)); $classMap = $generator->dump( $config, $localRepo, $package, $installationManager, 'composer', $optimize, null, $composer->getLocker(), $input->getOption('strict-ambiguous') ); $numberOfClasses = count($classMap); if ($authoritative) { $this->getIO()->write('Generated optimized autoload files (authoritative) containing '. $numberOfClasses .' classes'); } elseif ($optimize) { $this->getIO()->write('Generated optimized autoload files containing '. $numberOfClasses .' classes'); } else { $this->getIO()->write('Generated autoload files'); } if ($missingDependencies || ($input->getOption('strict-psr') && count($classMap->getPsrViolations()) > 0)) { return 1; } if ($input->getOption('strict-ambiguous') && count($classMap->getAmbiguousClasses(false)) > 0) { return 2; } return 0; } } setName('exec') ->setDescription('Executes a vendored binary/script') ->setDefinition([ new InputOption('list', 'l', InputOption::VALUE_NONE), new InputArgument('binary', InputArgument::OPTIONAL, 'The binary to run, e.g. phpunit', null, function () { return $this->getBinaries(false); }), new InputArgument( 'args', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Arguments to pass to the binary. Use -- to separate from composer arguments' ), ]) ->setHelp( <<getBinaries(false); if (count($binaries) === 0) { return; } if ($input->getArgument('binary') !== null || $input->getOption('list')) { return; } $io = $this->getIO(); $binary = $io->select( 'Binary to run: ', $binaries, '', 1, 'Invalid binary name "%s"' ); $input->setArgument('binary', $binaries[$binary]); } protected function execute(InputInterface $input, OutputInterface $output): int { $composer = $this->requireComposer(); if ($input->getOption('list') || null === $input->getArgument('binary')) { $bins = $this->getBinaries(true); if ([] === $bins) { $binDir = $composer->getConfig()->get('bin-dir'); throw new \RuntimeException("No binaries found in composer.json or in bin-dir ($binDir)"); } $this->getIO()->write( <<Available binaries: EOT ); foreach ($bins as $bin) { $this->getIO()->write( <<- $bin EOT ); } return 0; } $binary = $input->getArgument('binary'); $dispatcher = $composer->getEventDispatcher(); $dispatcher->addListener('__exec_command', $binary); if (getcwd() !== $this->getApplication()->getInitialWorkingDirectory() && $this->getApplication()->getInitialWorkingDirectory() !== false) { try { chdir($this->getApplication()->getInitialWorkingDirectory()); } catch (\Exception $e) { throw new \RuntimeException('Could not switch back to working directory "'.$this->getApplication()->getInitialWorkingDirectory().'"', 0, $e); } } return $dispatcher->dispatchScript('__exec_command', true, $input->getArgument('args')); } private function getBinaries(bool $forDisplay): array { $composer = $this->requireComposer(); $binDir = $composer->getConfig()->get('bin-dir'); $bins = glob($binDir . '/*'); $localBins = $composer->getPackage()->getBinaries(); if ($forDisplay) { $localBins = array_map(static function ($e) { return "$e (local)"; }, $localBins); } $binaries = []; foreach (array_merge($bins, $localBins) as $bin) { if (isset($previousBin) && $bin === $previousBin.'.bat') { continue; } $previousBin = $bin; $binaries[] = basename($bin); } return $binaries; } } setName('fund') ->setDescription('Discover how to help fund the maintenance of your dependencies') ->setDefinition([ new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text', ['text', 'json']), ]) ; } protected function execute(InputInterface $input, OutputInterface $output): int { $composer = $this->requireComposer(); $repo = $composer->getRepositoryManager()->getLocalRepository(); $remoteRepos = new CompositeRepository($composer->getRepositoryManager()->getRepositories()); $fundings = []; $packagesToLoad = []; foreach ($repo->getPackages() as $package) { if ($package instanceof AliasPackage) { continue; } $packagesToLoad[$package->getName()] = new MatchAllConstraint(); } $result = $remoteRepos->loadPackages($packagesToLoad, ['dev' => BasePackage::STABILITY_DEV], []); foreach ($result['packages'] as $package) { if ( !$package instanceof AliasPackage && $package instanceof CompletePackageInterface && $package->isDefaultBranch() && $package->getFunding() && isset($packagesToLoad[$package->getName()]) ) { $fundings = $this->insertFundingData($fundings, $package); unset($packagesToLoad[$package->getName()]); } } foreach ($repo->getPackages() as $package) { if ($package instanceof AliasPackage || !isset($packagesToLoad[$package->getName()])) { continue; } if ($package instanceof CompletePackageInterface && $package->getFunding()) { $fundings = $this->insertFundingData($fundings, $package); } } ksort($fundings); $io = $this->getIO(); $format = $input->getOption('format'); if (!in_array($format, ['text', 'json'])) { $io->writeError(sprintf('Unsupported format "%s". See help for supported formats.', $format)); return 1; } if ($fundings && $format === 'text') { $prev = null; $io->write('The following packages were found in your dependencies which publish funding information:'); foreach ($fundings as $vendor => $links) { $io->write(''); $io->write(sprintf("%s", $vendor)); foreach ($links as $url => $packages) { $line = sprintf(' %s', implode(', ', $packages)); if ($prev !== $line) { $io->write($line); $prev = $line; } $io->write(sprintf(' %s', OutputFormatter::escape($url), $url)); } } $io->write(""); $io->write("Please consider following these links and sponsoring the work of package authors!"); $io->write("Thank you!"); } elseif ($format === 'json') { $io->write(JsonFile::encode($fundings)); } else { $io->write("No funding links were found in your package dependencies. This doesn't mean they don't need your support!"); } return 0; } private function insertFundingData(array $fundings, CompletePackageInterface $package): array { foreach ($package->getFunding() as $fundingOption) { [$vendor, $packageName] = explode('/', $package->getPrettyName()); if (empty($fundingOption['url'])) { continue; } $url = $fundingOption['url']; if (!empty($fundingOption['type']) && $fundingOption['type'] === 'github' && Preg::isMatch('{^https://github.com/([^/]+)$}', $url, $match)) { $url = 'https://github.com/sponsors/'.$match[1]; } $fundings[$vendor][$url][] = $packageName; } return $fundings; } } getApplication(); if ($input->mustSuggestArgumentValuesFor('command-name')) { $suggestions->suggestValues(array_values(array_filter( array_map(static function (Command $command) { return $command->isHidden() ? null : $command->getName(); }, $application->all()), static function (?string $cmd) { return $cmd !== null; } ))); return; } if ($application->has($commandName = $input->getArgument('command-name'))) { $input = $this->prepareSubcommandInput($input, true); $input = CompletionInput::fromString($input->__toString(), 2); $command = $application->find($commandName); $command->mergeApplicationDefinition(); $input->bind($command->getDefinition()); $command->complete($input, $suggestions); } } protected function configure(): void { $this ->setName('global') ->setDescription('Allows running commands in the global composer dir ($COMPOSER_HOME)') ->setDefinition([ new InputArgument('command-name', InputArgument::REQUIRED, ''), new InputArgument('args', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, ''), ]) ->setHelp( <<\AppData\Roaming\Composer on Windows and /home//.composer on unix systems. If your system uses freedesktop.org standards, then it will first check XDG_CONFIG_HOME or default to /home//.config/composer Note: This path may vary depending on customizations to bin-dir in composer.json or the environmental variable COMPOSER_BIN_DIR. Read more at https://getcomposer.org/doc/03-cli.md#global EOT ) ; } public function run(InputInterface $input, OutputInterface $output): int { if (!method_exists($input, '__toString')) { throw new \LogicException('Expected an Input instance that is stringable, got '.get_class($input)); } $tokens = Preg::split('{\s+}', $input->__toString()); $args = []; foreach ($tokens as $token) { if ($token && $token[0] !== '-') { $args[] = $token; if (count($args) >= 2) { break; } } } if (count($args) < 2) { return parent::run($input, $output); } $input = $this->prepareSubcommandInput($input); return $this->getApplication()->run($input, $output); } private function prepareSubcommandInput(InputInterface $input, bool $quiet = false): StringInput { if (!method_exists($input, '__toString')) { throw new \LogicException('Expected an Input instance that is stringable, got '.get_class($input)); } if (Platform::getEnv('COMPOSER')) { Platform::clearEnv('COMPOSER'); } $config = Factory::createConfig(); $home = $config->get('home'); if (!is_dir($home)) { $fs = new Filesystem(); $fs->ensureDirectoryExists($home); if (!is_dir($home)) { throw new \RuntimeException('Could not create home directory'); } } try { chdir($home); } catch (\Exception $e) { throw new \RuntimeException('Could not switch to home directory "'.$home.'"', 0, $e); } if (!$quiet) { $this->getIO()->writeError('Changed current directory to '.$home.''); } $input = new StringInput(Preg::replace('{\bg(?:l(?:o(?:b(?:a(?:l)?)?)?)?)?\b}', '', $input->__toString(), 1)); $this->getApplication()->resetComposer(); return $input; } public function isProxyCommand(): bool { return true; } } setName('browse') ->setAliases(['home']) ->setDescription('Opens the package\'s repository URL or homepage in your browser') ->setDefinition([ new InputArgument('packages', InputArgument::IS_ARRAY, 'Package(s) to browse to.', null, $this->suggestInstalledPackage()), new InputOption('homepage', 'H', InputOption::VALUE_NONE, 'Open the homepage instead of the repository URL.'), new InputOption('show', 's', InputOption::VALUE_NONE, 'Only show the homepage or repository URL.'), ]) ->setHelp( <<initializeRepos(); $io = $this->getIO(); $return = 0; $packages = $input->getArgument('packages'); if (count($packages) === 0) { $io->writeError('No package specified, opening homepage for the root package'); $packages = [$this->requireComposer()->getPackage()->getName()]; } foreach ($packages as $packageName) { $handled = false; $packageExists = false; foreach ($repos as $repo) { foreach ($repo->findPackages($packageName) as $package) { $packageExists = true; if ($package instanceof CompletePackageInterface && $this->handlePackage($package, $input->getOption('homepage'), $input->getOption('show'))) { $handled = true; break 2; } } } if (!$packageExists) { $return = 1; $io->writeError('Package '.$packageName.' not found'); } if (!$handled) { $return = 1; $io->writeError(''.($input->getOption('homepage') ? 'Invalid or missing homepage' : 'Invalid or missing repository URL').' for '.$packageName.''); } } return $return; } private function handlePackage(CompletePackageInterface $package, bool $showHomepage, bool $showOnly): bool { $support = $package->getSupport(); $url = $support['source'] ?? $package->getSourceUrl(); if (!$url || $showHomepage) { $url = $package->getHomepage(); } if (!$url || !filter_var($url, FILTER_VALIDATE_URL)) { return false; } if ($showOnly) { $this->getIO()->write(sprintf('%s', $url)); } else { $this->openBrowser($url); } return true; } private function openBrowser(string $url): void { $process = new ProcessExecutor($this->getIO()); if (Platform::isWindows()) { $process->execute(['start', '"web"', 'explorer', $url], $output); return; } $linux = $process->execute(['which', 'xdg-open'], $output); $osx = $process->execute(['which', 'open'], $output); if (0 === $linux) { $process->execute(['xdg-open', $url], $output); } elseif (0 === $osx) { $process->execute(['open', $url], $output); } else { $this->getIO()->writeError('No suitable browser opening command found, open yourself: ' . $url); } } private function initializeRepos(): array { $composer = $this->tryComposer(); if ($composer) { return array_merge( [new RootPackageRepository(clone $composer->getPackage())], [$composer->getRepositoryManager()->getLocalRepository()], $composer->getRepositoryManager()->getRepositories() ); } return RepositoryFactory::defaultReposWithDefaultManager($this->getIO()); } } setName('init') ->setDescription('Creates a basic composer.json file in current directory') ->setDefinition([ new InputOption('name', null, InputOption::VALUE_REQUIRED, 'Name of the package'), new InputOption('description', null, InputOption::VALUE_REQUIRED, 'Description of package'), new InputOption('author', null, InputOption::VALUE_REQUIRED, 'Author name of package'), new InputOption('type', null, InputOption::VALUE_REQUIRED, 'Type of package (e.g. library, project, metapackage, composer-plugin)'), new InputOption('homepage', null, InputOption::VALUE_REQUIRED, 'Homepage of package'), new InputOption('require', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Package to require with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"', null, $this->suggestAvailablePackageInclPlatform()), new InputOption('require-dev', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Package to require for development with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"', null, $this->suggestAvailablePackageInclPlatform()), new InputOption('stability', 's', InputOption::VALUE_REQUIRED, 'Minimum stability (empty or one of: '.implode(', ', array_keys(BasePackage::STABILITIES)).')'), new InputOption('license', 'l', InputOption::VALUE_REQUIRED, 'License of package'), new InputOption('repository', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Add custom repositories, either by URL or using JSON arrays'), new InputOption('autoload', 'a', InputOption::VALUE_REQUIRED, 'Add PSR-4 autoload mapping. Maps your package\'s namespace to the provided directory. (Expects a relative path, e.g. src/)'), ]) ->setHelp( <<init command creates a basic composer.json file in the current directory. php composer.phar init Read more at https://getcomposer.org/doc/03-cli.md#init EOT ) ; } protected function execute(InputInterface $input, OutputInterface $output): int { $io = $this->getIO(); $allowlist = ['name', 'description', 'author', 'type', 'homepage', 'require', 'require-dev', 'stability', 'license', 'autoload']; $options = array_filter(array_intersect_key($input->getOptions(), array_flip($allowlist)), static function ($val) { return $val !== null && $val !== []; }); if (isset($options['name']) && !Preg::isMatch('{^[a-z0-9]([_.-]?[a-z0-9]+)*\/[a-z0-9](([_.]|-{1,2})?[a-z0-9]+)*$}D', $options['name'])) { throw new \InvalidArgumentException( 'The package name '.$options['name'].' is invalid, it should be lowercase and have a vendor name, a forward slash, and a package name, matching: [a-z0-9_.-]+/[a-z0-9_.-]+' ); } if (isset($options['author'])) { $options['authors'] = $this->formatAuthors($options['author']); unset($options['author']); } $repositories = $input->getOption('repository'); if (count($repositories) > 0) { $config = Factory::createConfig($io); foreach ($repositories as $repo) { $options['repositories'][] = RepositoryFactory::configFromString($io, $config, $repo, true); } } if (isset($options['stability'])) { $options['minimum-stability'] = $options['stability']; unset($options['stability']); } $options['require'] = isset($options['require']) ? $this->formatRequirements($options['require']) : new \stdClass; if ([] === $options['require']) { $options['require'] = new \stdClass; } if (isset($options['require-dev'])) { $options['require-dev'] = $this->formatRequirements($options['require-dev']); if ([] === $options['require-dev']) { $options['require-dev'] = new \stdClass; } } $autoloadPath = null; if (isset($options['autoload'])) { $autoloadPath = $options['autoload']; $namespace = $this->namespaceFromPackageName((string) $input->getOption('name')); $options['autoload'] = (object) [ 'psr-4' => [ $namespace . '\\' => $autoloadPath, ], ]; } $file = new JsonFile(Factory::getComposerFile()); $json = JsonFile::encode($options); if ($input->isInteractive()) { $io->writeError(['', $json, '']); if (!$io->askConfirmation('Do you confirm generation [yes]? ')) { $io->writeError('Command aborted'); return 1; } } else { $io->writeError('Writing '.$file->getPath()); } $file->write($options); try { $file->validateSchema(JsonFile::LAX_SCHEMA); } catch (JsonValidationException $e) { $io->writeError('Schema validation error, aborting'); $errors = ' - ' . implode(PHP_EOL . ' - ', $e->getErrors()); $io->writeError($e->getMessage() . ':' . PHP_EOL . $errors); Silencer::call('unlink', $file->getPath()); return 1; } if ($autoloadPath) { $filesystem = new Filesystem(); $filesystem->ensureDirectoryExists($autoloadPath); if (!$this->hasDependencies($options)) { $this->runDumpAutoloadCommand($output); } } if ($input->isInteractive() && is_dir('.git')) { $ignoreFile = realpath('.gitignore'); if (false === $ignoreFile) { $ignoreFile = realpath('.') . '/.gitignore'; } if (!$this->hasVendorIgnore($ignoreFile)) { $question = 'Would you like the vendor directory added to your .gitignore [yes]? '; if ($io->askConfirmation($question)) { $this->addVendorIgnore($ignoreFile); } } } $question = 'Would you like to install dependencies now [yes]? '; if ($input->isInteractive() && $this->hasDependencies($options) && $io->askConfirmation($question)) { $this->updateDependencies($output); } if ($autoloadPath) { $namespace = $this->namespaceFromPackageName((string) $input->getOption('name')); $io->writeError('PSR-4 autoloading configured. Use "namespace '.$namespace.';" in '.$autoloadPath); $io->writeError('Include the Composer autoloader with: require \'vendor/autoload.php\';'); } return 0; } protected function initialize(InputInterface $input, OutputInterface $output): void { parent::initialize($input, $output); if (!$input->isInteractive()) { if ($input->getOption('name') === null) { $input->setOption('name', $this->getDefaultPackageName()); } if ($input->getOption('author') === null) { $input->setOption('author', $this->getDefaultAuthor()); } } } protected function interact(InputInterface $input, OutputInterface $output): void { $io = $this->getIO(); $formatter = $this->getHelperSet()->get('formatter'); $repositories = $input->getOption('repository'); if (count($repositories) > 0) { $config = Factory::createConfig($io); $io->loadConfiguration($config); $repoManager = RepositoryFactory::manager($io, $config); $repos = [new PlatformRepository]; $createDefaultPackagistRepo = true; foreach ($repositories as $repo) { $repoConfig = RepositoryFactory::configFromString($io, $config, $repo, true); if ( (isset($repoConfig['packagist']) && $repoConfig === ['packagist' => false]) || (isset($repoConfig['packagist.org']) && $repoConfig === ['packagist.org' => false]) ) { $createDefaultPackagistRepo = false; continue; } $repos[] = RepositoryFactory::createRepo($io, $config, $repoConfig, $repoManager); } if ($createDefaultPackagistRepo) { $repos[] = RepositoryFactory::createRepo($io, $config, [ 'type' => 'composer', 'url' => 'https://repo.packagist.org', ], $repoManager); } $this->repos = new CompositeRepository($repos); unset($repos, $config, $repositories); } $io->writeError([ '', $formatter->formatBlock('Welcome to the Composer config generator', 'bg=blue;fg=white', true), '', ]); $io->writeError([ '', 'This command will guide you through creating your composer.json config.', '', ]); $name = $input->getOption('name') ?? $this->getDefaultPackageName(); $name = $io->askAndValidate( 'Package name (/) ['.$name.']: ', static function ($value) use ($name) { if (null === $value) { return $name; } if (!Preg::isMatch('{^[a-z0-9]([_.-]?[a-z0-9]+)*\/[a-z0-9](([_.]|-{1,2})?[a-z0-9]+)*$}D', $value)) { throw new \InvalidArgumentException( 'The package name '.$value.' is invalid, it should be lowercase and have a vendor name, a forward slash, and a package name, matching: [a-z0-9_.-]+/[a-z0-9_.-]+' ); } return $value; }, null, $name ); $input->setOption('name', $name); $description = $input->getOption('description') ?: null; $description = $io->ask( 'Description ['.$description.']: ', $description ); $input->setOption('description', $description); $author = $input->getOption('author') ?? $this->getDefaultAuthor(); $author = $io->askAndValidate( 'Author ['.(is_string($author) ? ''.$author.', ' : '') . 'n to skip]: ', function ($value) use ($author) { if ($value === 'n' || $value === 'no') { return; } $value = $value ?: $author; $author = $this->parseAuthorString($value ?? ''); if ($author['email'] === null) { return $author['name']; } return sprintf('%s <%s>', $author['name'], $author['email']); }, null, $author ); $input->setOption('author', $author); $minimumStability = $input->getOption('stability') ?: null; $minimumStability = $io->askAndValidate( 'Minimum Stability ['.$minimumStability.']: ', static function ($value) use ($minimumStability) { if (null === $value) { return $minimumStability; } if (!isset(BasePackage::STABILITIES[$value])) { throw new \InvalidArgumentException( 'Invalid minimum stability "'.$value.'". Must be empty or one of: '. implode(', ', array_keys(BasePackage::STABILITIES)) ); } return $value; }, null, $minimumStability ); $input->setOption('stability', $minimumStability); $type = $input->getOption('type'); $type = $io->ask( 'Package Type (e.g. library, project, metapackage, composer-plugin) ['.$type.']: ', $type ); if ($type === '' || $type === false) { $type = null; } $input->setOption('type', $type); if (null === $license = $input->getOption('license')) { if (!empty($_SERVER['COMPOSER_DEFAULT_LICENSE'])) { $license = $_SERVER['COMPOSER_DEFAULT_LICENSE']; } } $license = $io->ask( 'License ['.$license.']: ', $license ); $spdx = new SpdxLicenses(); if (null !== $license && !$spdx->validate($license) && $license !== 'proprietary') { throw new \InvalidArgumentException('Invalid license provided: '.$license.'. Only SPDX license identifiers (https://spdx.org/licenses/) or "proprietary" are accepted.'); } $input->setOption('license', $license); $io->writeError(['', 'Define your dependencies.', '']); $repos = $this->getRepos(); $preferredStability = $minimumStability ?: 'stable'; $platformRepo = null; if ($repos instanceof CompositeRepository) { foreach ($repos->getRepositories() as $candidateRepo) { if ($candidateRepo instanceof PlatformRepository) { $platformRepo = $candidateRepo; break; } } } $question = 'Would you like to define your dependencies (require) interactively [yes]? '; $require = $input->getOption('require'); $requirements = []; if (count($require) > 0 || $io->askConfirmation($question)) { $requirements = $this->determineRequirements($input, $output, $require, $platformRepo, $preferredStability); } $input->setOption('require', $requirements); $question = 'Would you like to define your dev dependencies (require-dev) interactively [yes]? '; $requireDev = $input->getOption('require-dev'); $devRequirements = []; if (count($requireDev) > 0 || $io->askConfirmation($question)) { $devRequirements = $this->determineRequirements($input, $output, $requireDev, $platformRepo, $preferredStability); } $input->setOption('require-dev', $devRequirements); $autoload = $input->getOption('autoload') ?: 'src/'; $namespace = $this->namespaceFromPackageName((string) $input->getOption('name')); $autoload = $io->askAndValidate( 'Add PSR-4 autoload mapping? Maps namespace "'.$namespace.'" to the entered relative path. ['.$autoload.', n to skip]: ', static function ($value) use ($autoload) { if (null === $value) { return $autoload; } if ($value === 'n' || $value === 'no') { return; } $value = $value ?: $autoload; if (!Preg::isMatch('{^[^/][A-Za-z0-9\-_/]+/$}', $value)) { throw new \InvalidArgumentException(sprintf( 'The src folder name "%s" is invalid. Please add a relative path with tailing forward slash. [A-Za-z0-9_-/]+/', $value )); } return $value; }, null, $autoload ); $input->setOption('autoload', $autoload); } private function parseAuthorString(string $author): array { if (Preg::isMatch('/^(?P[- .,\p{L}\p{N}\p{Mn}\'’"()]+)(?:\s+<(?P.+?)>)?$/u', $author, $match)) { if (null !== $match['email'] && !$this->isValidEmail($match['email'])) { throw new \InvalidArgumentException('Invalid email "'.$match['email'].'"'); } return [ 'name' => trim($match['name']), 'email' => $match['email'], ]; } throw new \InvalidArgumentException( 'Invalid author string. Must be in the formats: '. 'Jane Doe or John Smith ' ); } protected function formatAuthors(string $author): array { $author = $this->parseAuthorString($author); if (null === $author['email']) { unset($author['email']); } return [$author]; } public function namespaceFromPackageName(string $packageName): ?string { if (!$packageName || strpos($packageName, '/') === false) { return null; } $namespace = array_map( static function ($part): string { $part = Preg::replace('/[^a-z0-9]/i', ' ', $part); $part = ucwords($part); return str_replace(' ', '', $part); }, explode('/', $packageName) ); return implode('\\', $namespace); } protected function getGitConfig(): array { if (null !== $this->gitConfig) { return $this->gitConfig; } $process = new ProcessExecutor($this->getIO()); if (0 === $process->execute(['git', 'config', '-l'], $output)) { $this->gitConfig = []; Preg::matchAllStrictGroups('{^([^=]+)=(.*)$}m', $output, $matches); foreach ($matches[1] as $key => $match) { $this->gitConfig[$match] = $matches[2][$key]; } return $this->gitConfig; } return $this->gitConfig = []; } protected function hasVendorIgnore(string $ignoreFile, string $vendor = 'vendor'): bool { if (!file_exists($ignoreFile)) { return false; } $pattern = sprintf('{^/?%s(/\*?)?$}', preg_quote($vendor)); $lines = file($ignoreFile, FILE_IGNORE_NEW_LINES); foreach ($lines as $line) { if (Preg::isMatch($pattern, $line)) { return true; } } return false; } protected function addVendorIgnore(string $ignoreFile, string $vendor = '/vendor/'): void { $contents = ""; if (file_exists($ignoreFile)) { $contents = file_get_contents($ignoreFile); if (strpos($contents, "\n") !== 0) { $contents .= "\n"; } } file_put_contents($ignoreFile, $contents . $vendor. "\n"); } protected function isValidEmail(string $email): bool { if (!function_exists('filter_var')) { return true; } return false !== filter_var($email, FILTER_VALIDATE_EMAIL); } private function updateDependencies(OutputInterface $output): void { try { $updateCommand = $this->getApplication()->find('update'); $this->getApplication()->resetComposer(); $updateCommand->run(new ArrayInput([]), $output); } catch (\Exception $e) { $this->getIO()->writeError('Could not update dependencies. Run `composer update` to see more information.'); } } private function runDumpAutoloadCommand(OutputInterface $output): void { try { $command = $this->getApplication()->find('dump-autoload'); $this->getApplication()->resetComposer(); $command->run(new ArrayInput([]), $output); } catch (\Exception $e) { $this->getIO()->writeError('Could not run dump-autoload.'); } } private function hasDependencies(array $options): bool { $requires = (array) $options['require']; $devRequires = isset($options['require-dev']) ? (array) $options['require-dev'] : []; return !empty($requires) || !empty($devRequires); } private function sanitizePackageNameComponent(string $name): string { $name = Preg::replace('{(?:([a-z])([A-Z])|([A-Z])([A-Z][a-z]))}', '\\1\\3-\\2\\4', $name); $name = strtolower($name); $name = Preg::replace('{^[_.-]+|[_.-]+$|[^a-z0-9_.-]}u', '', $name); $name = Preg::replace('{([_.-]){2,}}u', '$1', $name); return $name; } private function getDefaultPackageName(): string { $git = $this->getGitConfig(); $cwd = realpath("."); $name = basename($cwd); $name = $this->sanitizePackageNameComponent($name); $vendor = $name; if (!empty($_SERVER['COMPOSER_DEFAULT_VENDOR'])) { $vendor = $_SERVER['COMPOSER_DEFAULT_VENDOR']; } elseif (isset($git['github.user'])) { $vendor = $git['github.user']; } elseif (!empty($_SERVER['USERNAME'])) { $vendor = $_SERVER['USERNAME']; } elseif (!empty($_SERVER['USER'])) { $vendor = $_SERVER['USER']; } elseif (get_current_user()) { $vendor = get_current_user(); } $vendor = $this->sanitizePackageNameComponent($vendor); return $vendor . '/' . $name; } private function getDefaultAuthor(): ?string { $git = $this->getGitConfig(); if (!empty($_SERVER['COMPOSER_DEFAULT_AUTHOR'])) { $author_name = $_SERVER['COMPOSER_DEFAULT_AUTHOR']; } elseif (isset($git['user.name'])) { $author_name = $git['user.name']; } if (!empty($_SERVER['COMPOSER_DEFAULT_EMAIL'])) { $author_email = $_SERVER['COMPOSER_DEFAULT_EMAIL']; } elseif (isset($git['user.email'])) { $author_email = $git['user.email']; } if (isset($author_name, $author_email)) { return sprintf('%s <%s>', $author_name, $author_email); } return null; } } setName('install') ->setAliases(['i']) ->setDescription('Installs the project dependencies from the composer.lock file if present, or falls back on the composer.json') ->setDefinition([ new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'), new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist (default behavior).'), new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).', null, $this->suggestPreferInstall()), new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'), new InputOption('download-only', null, InputOption::VALUE_NONE, 'Download only, do not install packages.'), new InputOption('dev', null, InputOption::VALUE_NONE, 'DEPRECATED: Enables installation of require-dev packages (enabled by default, only present for BC).'), new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'DEPRECATED: This flag does not exist anymore.'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'), new InputOption('no-security-blocking', null, InputOption::VALUE_NONE, 'DEPRECATED: use --no-blocking instead. Allows installing packages with security advisories or that are abandoned (can also be set via the COMPOSER_NO_SECURITY_BLOCKING=1 env var).'), new InputOption('no-blocking', null, InputOption::VALUE_NONE, 'Disables all policy blocking during this command (can also be set via the COMPOSER_NO_BLOCKING=1 env var).'), new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('no-install', null, InputOption::VALUE_NONE, 'Do not use, only defined here to catch misuse of the install command.'), new InputOption('audit', null, InputOption::VALUE_NONE, 'Run an audit after installation is complete.'), new InputOption('audit-format', null, InputOption::VALUE_REQUIRED, 'Audit output format. Must be "table", "plain", "json", or "summary".', Auditor::FORMAT_SUMMARY, Auditor::FORMATS), new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'), new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'), new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'), new InputOption('strict-psr-autoloader', null, InputOption::VALUE_NONE, 'Return a failed status code (6) if PSR-4 or PSR-0 mapping errors are present. Requires --optimize-autoloader to work.'), new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'), new InputOption('apcu-autoloader-prefix', null, InputOption::VALUE_REQUIRED, 'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader'), new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'), new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'), new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Should not be provided, use composer require instead to add a given package to composer.json.'), ]) ->setHelp( <<install command reads the composer.lock file from the current directory, processes it, and downloads and installs all the libraries and dependencies outlined in that file. If the file does not exist it will look for composer.json and do the same. php composer.phar install Read more at https://getcomposer.org/doc/03-cli.md#install-i EOT ) ; } protected function execute(InputInterface $input, OutputInterface $output): int { $io = $this->getIO(); if ($input->getOption('dev')) { $io->writeError('You are using the deprecated option "--dev". It has no effect and will break in Composer 3.'); } if ($input->getOption('no-suggest')) { $io->writeError('You are using the deprecated option "--no-suggest". It has no effect and will break in Composer 3.'); } $args = $input->getArgument('packages'); if (count($args) > 0) { $io->writeError('Invalid argument '.implode(' ', $args).'. Use "composer require '.implode(' ', $args).'" instead to add packages to your composer.json.'); return 1; } if ($input->getOption('no-install')) { $io->writeError('Invalid option "--no-install". Use "composer update --no-install" instead if you are trying to update the composer.lock file.'); return 1; } $composer = $this->requireComposer(); if (!$composer->getLocker()->isLocked() && !HttpDownloader::isCurlEnabled()) { $io->writeError('Composer is operating significantly slower than normal because you do not have the PHP curl extension enabled.'); } $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'install', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $install = Installer::create($io, $composer); $config = $composer->getConfig(); [$preferSource, $preferDist] = $this->getPreferredInstallOptions($config, $input); $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader'); $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative'); $apcuPrefix = $input->getOption('apcu-autoloader-prefix'); $apcu = $apcuPrefix !== null || $input->getOption('apcu-autoloader') || $config->get('apcu-autoloader'); if ($input->getOption('strict-psr-autoloader') && !$optimize && !$authoritative) { throw new \InvalidArgumentException('--strict-psr-autoloader mode only works with optimized autoloader, use --optimize-autoloader or --classmap-authoritative if you want a strict return value.'); } $composer->getInstallationManager()->setOutputProgress(!$input->getOption('no-progress')); $install ->setDryRun($input->getOption('dry-run')) ->setDownloadOnly($input->getOption('download-only')) ->setVerbose($input->getOption('verbose')) ->setPreferSource($preferSource) ->setPreferDist($preferDist) ->setDevMode(!$input->getOption('no-dev')) ->setDumpAutoloader(!$input->getOption('no-autoloader')) ->setOptimizeAutoloader($optimize) ->setClassMapAuthoritative($authoritative) ->setStrictPsrAutoloader($input->getOption('strict-psr-autoloader')) ->setApcuAutoloader($apcu, $apcuPrefix) ->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input)) ->setPolicyConfig($this->createPolicyConfig($composer->getConfig(), $input)) ->setAuditConfig($this->createAuditConfig($input)) ->setErrorOnAudit($input->getOption('audit')) ; if ($input->getOption('no-plugins')) { $install->disablePlugins(); } return $install->run(); } } setName('licenses') ->setDescription('Shows information about licenses of dependencies') ->setDefinition([ new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text, json or summary', 'text', ['text', 'json', 'summary']), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables search in require-dev packages.'), new InputOption('locked', null, InputOption::VALUE_NONE, 'Shows licenses from the lock file instead of installed packages.'), ]) ->setHelp( <<requireComposer(); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'licenses', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $root = $composer->getPackage(); if ($input->getOption('locked')) { if (!$composer->getLocker()->isLocked()) { throw new \UnexpectedValueException('Valid composer.json and composer.lock files are required to run this command with --locked'); } $locker = $composer->getLocker(); $repo = $locker->getLockedRepository(!$input->getOption('no-dev')); $packages = $repo->getPackages(); } else { $repo = $composer->getRepositoryManager()->getLocalRepository(); if ($input->getOption('no-dev')) { $packages = RepositoryUtils::filterRequiredPackages($repo->getPackages(), $root); } else { $packages = $repo->getPackages(); } } $packages = PackageSorter::sortPackagesAlphabetically($packages); $io = $this->getIO(); switch ($format = $input->getOption('format')) { case 'text': $io->write('Name: '.$root->getPrettyName().''); $io->write('Version: '.$root->getFullPrettyVersion().''); $io->write('Licenses: '.(implode(', ', $root->getLicense()) ?: 'none').''); $io->write('Dependencies:'); $io->write(''); $table = new Table($output); $table->setStyle('compact'); $table->setHeaders(['Name', 'Version', 'Licenses']); foreach ($packages as $package) { $link = PackageInfo::getViewSourceOrHomepageUrl($package); if ($link !== null) { $name = ''.$package->getPrettyName().''; } else { $name = $package->getPrettyName(); } $table->addRow([ $name, $package->getFullPrettyVersion(), implode(', ', $package instanceof CompletePackageInterface ? $package->getLicense() : []) ?: 'none', ]); } $table->render(); break; case 'json': $dependencies = []; foreach ($packages as $package) { $dependencies[$package->getPrettyName()] = [ 'version' => $package->getFullPrettyVersion(), 'license' => $package instanceof CompletePackageInterface ? $package->getLicense() : [], ]; } $io->write(JsonFile::encode([ 'name' => $root->getPrettyName(), 'version' => $root->getFullPrettyVersion(), 'license' => $root->getLicense(), 'dependencies' => $dependencies, ])); break; case 'summary': $usedLicenses = []; foreach ($packages as $package) { $licenses = $package instanceof CompletePackageInterface ? $package->getLicense() : []; if (count($licenses) === 0) { $licenses[] = 'none'; } foreach ($licenses as $licenseName) { if (!isset($usedLicenses[$licenseName])) { $usedLicenses[$licenseName] = 0; } $usedLicenses[$licenseName]++; } } arsort($usedLicenses, SORT_NUMERIC); $rows = []; foreach ($usedLicenses as $usedLicense => $numberOfDependencies) { $rows[] = [$usedLicense, $numberOfDependencies]; } $symfonyIo = new SymfonyStyle($input, $output); $symfonyIo->table( ['License', 'Number of dependencies'], $rows ); break; default: throw new \RuntimeException(sprintf('Unsupported format "%s". See help for supported formats.', $format)); } return 0; } } setName('outdated') ->setDescription('Shows a list of installed packages that have updates available, including their latest version') ->setDefinition([ new InputArgument('package', InputArgument::OPTIONAL, 'Package to inspect. Or a name including a wildcard (*) to filter lists of packages instead.', null, $this->suggestInstalledPackage(false)), new InputOption('outdated', 'o', InputOption::VALUE_NONE, 'Show only packages that are outdated (this is the default, but present here for compat with `show`'), new InputOption('all', 'a', InputOption::VALUE_NONE, 'Show all installed packages with their latest versions'), new InputOption('locked', null, InputOption::VALUE_NONE, 'Shows updates for packages from the lock file, regardless of what is currently in vendor dir'), new InputOption('direct', 'D', InputOption::VALUE_NONE, 'Shows only packages that are directly required by the root package'), new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code when there are outdated packages'), new InputOption('major-only', 'M', InputOption::VALUE_NONE, 'Show only packages that have major SemVer-compatible updates.'), new InputOption('minor-only', 'm', InputOption::VALUE_NONE, 'Show only packages that have minor SemVer-compatible updates.'), new InputOption('patch-only', 'p', InputOption::VALUE_NONE, 'Show only packages that have patch SemVer-compatible updates.'), new InputOption('sort-by-age', 'A', InputOption::VALUE_NONE, 'Displays the installed version\'s age, and sorts packages oldest first.'), new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text', ['json', 'text']), new InputOption('ignore', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore specified package(s). Can contain wildcards (*). Use it if you don\'t want to be informed about new versions of some packages.', null, $this->suggestInstalledPackage(false)), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables search in require-dev packages.'), new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages). Use with the --outdated option'), new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages). Use with the --outdated option'), ]) ->setHelp( <<green (=): Dependency is in the latest version and is up to date. - yellow (~): Dependency has a new version available that includes backwards compatibility breaks according to semver, so upgrade when you can but it may involve work. - red (!): Dependency has a new version that is semver-compatible and you should upgrade it. Read more at https://getcomposer.org/doc/03-cli.md#outdated EOT ) ; } protected function execute(InputInterface $input, OutputInterface $output): int { $args = [ 'command' => 'show', '--latest' => true, ]; if ($input->getOption('no-interaction')) { $args['--no-interaction'] = true; } if ($input->getOption('no-plugins')) { $args['--no-plugins'] = true; } if ($input->getOption('no-scripts')) { $args['--no-scripts'] = true; } if ($input->getOption('no-cache')) { $args['--no-cache'] = true; } if (!$input->getOption('all')) { $args['--outdated'] = true; } if ($input->getOption('direct')) { $args['--direct'] = true; } if (null !== $input->getArgument('package')) { $args['package'] = $input->getArgument('package'); } if ($input->getOption('strict')) { $args['--strict'] = true; } if ($input->getOption('major-only')) { $args['--major-only'] = true; } if ($input->getOption('minor-only')) { $args['--minor-only'] = true; } if ($input->getOption('patch-only')) { $args['--patch-only'] = true; } if ($input->getOption('locked')) { $args['--locked'] = true; } if ($input->getOption('no-dev')) { $args['--no-dev'] = true; } if ($input->getOption('sort-by-age')) { $args['--sort-by-age'] = true; } $args['--ignore-platform-req'] = $input->getOption('ignore-platform-req'); if ($input->getOption('ignore-platform-reqs')) { $args['--ignore-platform-reqs'] = true; } $args['--format'] = $input->getOption('format'); $args['--ignore'] = $input->getOption('ignore'); $input = new ArrayInput($args); return $this->getApplication()->run($input, $output); } public function isProxyCommand(): bool { return true; } } repos) { $this->repos = new CompositeRepository(array_merge( [new PlatformRepository], RepositoryFactory::defaultReposWithDefaultManager($this->getIO()) )); } return $this->repos; } private function getRepositorySet(InputInterface $input, ?string $minimumStability = null): RepositorySet { $key = $minimumStability ?? 'default'; if (!isset($this->repositorySets[$key])) { $this->repositorySets[$key] = $repositorySet = new RepositorySet($minimumStability ?? $this->getMinimumStability($input)); $repositorySet->addRepository($this->getRepos()); } return $this->repositorySets[$key]; } private function getMinimumStability(InputInterface $input): string { if ($input->hasOption('stability')) { return VersionParser::normalizeStability($input->getOption('stability') ?? 'stable'); } $file = Factory::getComposerFile(); if (is_file($file) && Filesystem::isReadable($file) && is_array($composer = json_decode((string) file_get_contents($file), true))) { if (isset($composer['minimum-stability'])) { return VersionParser::normalizeStability($composer['minimum-stability']); } } return 'stable'; } final protected function determineRequirements(InputInterface $input, OutputInterface $output, array $requires = [], ?PlatformRepository $platformRepo = null, string $preferredStability = 'stable', bool $useBestVersionConstraint = true, bool $fixed = false): array { if (count($requires) > 0) { foreach ($requires as $package) { if (strtolower($package) === 'as') { throw new \InvalidArgumentException(sprintf( 'Cannot use "%s" as a separate argument. Quote the inline alias as one argument, e.g. "vendor/package:dev-main as 1.2.x-dev".', $package )); } } $requires = $this->normalizeRequirements($requires); $result = []; $io = $this->getIO(); foreach ($requires as $requirement) { if (isset($requirement['version']) && Preg::isMatch('{^\d+(\.\d+)?$}', $requirement['version'])) { $io->writeError('The "'.$requirement['version'].'" constraint for "'.$requirement['name'].'" appears too strict and will likely not match what you want. See https://getcomposer.org/constraints'); } if (!isset($requirement['version'])) { [$name, $version] = $this->findBestVersionAndNameForPackage($this->getIO(), $input, $requirement['name'], $platformRepo, $preferredStability, $fixed); $requirement['name'] = $name; if ($useBestVersionConstraint) { $requirement['version'] = $version; $io->writeError(sprintf( 'Using version %s for %s', $requirement['version'], $requirement['name'] )); } else { $requirement['version'] = 'guess'; } } $result[] = $requirement['name'] . ' ' . $requirement['version']; } return $result; } $versionParser = new VersionParser(); $composer = $this->tryComposer(); $installedRepo = null; if (null !== $composer) { $installedRepo = $composer->getRepositoryManager()->getLocalRepository(); } $existingPackages = []; if (null !== $installedRepo) { foreach ($installedRepo->getPackages() as $package) { $existingPackages[] = $package->getName(); } } unset($composer, $installedRepo); $io = $this->getIO(); while (null !== $package = $io->ask('Search for a package: ')) { $matches = $this->getRepos()->search($package); if (count($matches) > 0) { foreach ($matches as $position => $foundPackage) { if (in_array($foundPackage['name'], $existingPackages, true)) { unset($matches[$position]); } } $matches = array_values($matches); $exactMatch = false; foreach ($matches as $match) { if ($match['name'] === $package) { $exactMatch = true; break; } } if (!$exactMatch) { $providers = $this->getRepos()->getProviders($package); if (count($providers) > 0) { array_unshift($matches, ['name' => $package, 'description' => '']); } $choices = []; foreach ($matches as $position => $foundPackage) { $abandoned = ''; if (isset($foundPackage['abandoned'])) { if (is_string($foundPackage['abandoned'])) { $replacement = sprintf('Use %s instead', $foundPackage['abandoned']); } else { $replacement = 'No replacement was suggested'; } $abandoned = sprintf('Abandoned. %s.', $replacement); } $choices[] = sprintf(' %5s %s %s', "[$position]", $foundPackage['name'], $abandoned); } $io->writeError([ '', sprintf('Found %s packages matching %s', count($matches), $package), '', ]); $io->writeError($choices); $io->writeError(''); $validator = static function (string $selection) use ($matches, $versionParser) { if ('' === $selection) { return false; } if (is_numeric($selection) && isset($matches[(int) $selection])) { $package = $matches[(int) $selection]; return $package['name']; } if (Preg::isMatch('{^\s*(?P[\S/]+)(?:\s+(?P\S+))?\s*$}', $selection, $packageMatches)) { if (isset($packageMatches['version'])) { $versionParser->parseConstraints($packageMatches['version']); return $packageMatches['name'].' '.$packageMatches['version']; } return $packageMatches['name']; } throw new \Exception('Not a valid selection'); }; $package = $io->askAndValidate( 'Enter package # to add, or the complete package name if it is not listed: ', $validator, 3, '' ); } if (false !== $package && false === strpos($package, ' ')) { $validator = static function (string $input) { $input = trim($input); return strlen($input) > 0 ? $input : false; }; $constraint = $io->askAndValidate( 'Enter the version constraint to require (or leave blank to use the latest version): ', $validator, 3, '' ); if (false === $constraint) { [, $constraint] = $this->findBestVersionAndNameForPackage($this->getIO(), $input, $package, $platformRepo, $preferredStability); $io->writeError(sprintf( 'Using version %s for %s', $constraint, $package )); } $package .= ' '.$constraint; } if (false !== $package) { $requires[] = $package; $existingPackages[] = explode(' ', $package)[0]; } } } return $requires; } private function findBestVersionAndNameForPackage(IOInterface $io, InputInterface $input, string $name, ?PlatformRepository $platformRepo = null, string $preferredStability = 'stable', bool $fixed = false): array { if ($input->hasOption('ignore-platform-reqs') && $input->hasOption('ignore-platform-req')) { $platformRequirementFilter = $this->getPlatformRequirementFilter($input); } else { $platformRequirementFilter = PlatformRequirementFilterFactory::ignoreNothing(); } $repoSet = $this->getRepositorySet($input); $versionSelector = new VersionSelector($repoSet, $platformRepo); $effectiveMinimumStability = $this->getMinimumStability($input); $package = $versionSelector->findBestCandidate($name, null, $preferredStability, $platformRequirementFilter, 0, $this->getIO()); if (false === $package) { if ($platformRequirementFilter->isIgnored($name)) { return [$name, '*']; } $providers = $repoSet->getProviders($name); if (count($providers) > 0) { $constraint = '*'; if ($input->isInteractive()) { $constraint = $this->getIO()->askAndValidate('Package "'.$name.'" does not exist but is provided by '.count($providers).' packages. Which version constraint would you like to use? [*] ', static function ($value) { $parser = new VersionParser(); $parser->parseConstraints($value); return $value; }, 3, '*'); } return [$name, $constraint]; } if (!($platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter) && false !== ($candidate = $versionSelector->findBestCandidate($name, null, $preferredStability, PlatformRequirementFilterFactory::ignoreAll()))) { throw new \InvalidArgumentException(sprintf( 'Package %s has requirements incompatible with your PHP version, PHP extensions and Composer version' . $this->getPlatformExceptionDetails($candidate, $platformRepo), $name )); } if (false !== ($package = $versionSelector->findBestCandidate($name, null, $preferredStability, $platformRequirementFilter, RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES))) { if (false !== ($allReposPackage = $versionSelector->findBestCandidate($name, null, $preferredStability, $platformRequirementFilter, RepositorySet::ALLOW_SHADOWED_REPOSITORIES))) { throw new \InvalidArgumentException( 'Package '.$name.' exists in '.$allReposPackage->getRepository()->getRepoName().' and '.$package->getRepository()->getRepoName().' which has a higher repository priority. The packages from the higher priority repository do not match your minimum-stability and are therefore not installable. That repository is canonical so the lower priority repo\'s packages are not installable. See https://getcomposer.org/repoprio for details and assistance.' ); } throw new \InvalidArgumentException(sprintf( 'Could not find a version of package %s matching your minimum-stability (%s). Require it with an explicit version constraint allowing its desired stability.', $name, $effectiveMinimumStability )); } if (!$platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter && false !== ($candidate = $versionSelector->findBestCandidate($name, null, $preferredStability, PlatformRequirementFilterFactory::ignoreAll(), RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES))) { $additional = ''; if (false === $versionSelector->findBestCandidate($name, null, $preferredStability, PlatformRequirementFilterFactory::ignoreAll())) { $additional = PHP_EOL.PHP_EOL.'Additionally, the package was only found with a stability of "'.$candidate->getStability().'" while your minimum stability is "'.$effectiveMinimumStability.'".'; } throw new \InvalidArgumentException(sprintf( 'Could not find package %s in any version matching your PHP version, PHP extensions and Composer version' . $this->getPlatformExceptionDetails($candidate, $platformRepo) . '%s', $name, $additional )); } $similar = $this->findSimilar($name); if (count($similar) > 0) { if (in_array($name, $similar, true)) { throw new \InvalidArgumentException(sprintf( "Could not find package %s. It was however found via repository search, which indicates a consistency issue with the repository.", $name )); } if ($input->isInteractive()) { $result = $io->select("Could not find package $name.\nPick one of these or leave empty to abort:", $similar, false, 1); if ($result !== false) { return $this->findBestVersionAndNameForPackage($io, $input, $similar[$result], $platformRepo, $preferredStability, $fixed); } } throw new \InvalidArgumentException(sprintf( "Could not find package %s.\n\nDid you mean " . (count($similar) > 1 ? 'one of these' : 'this') . "?\n %s", $name, implode("\n ", $similar) )); } throw new \InvalidArgumentException(sprintf( 'Could not find a matching version of package %s. Check the package spelling, your version constraint and that the package is available in a stability which matches your minimum-stability (%s).', $name, $effectiveMinimumStability )); } return [ $package->getPrettyName(), $fixed ? $package->getPrettyVersion() : $versionSelector->findRecommendedRequireVersion($package), ]; } private function findSimilar(string $package): array { try { if (null === $this->repos) { throw new \LogicException('findSimilar was called before $this->repos was initialized'); } $results = $this->repos->search($package); } catch (\Throwable $e) { if ($e instanceof \LogicException) { throw $e; } return []; } $similarPackages = []; $installedRepo = $this->requireComposer()->getRepositoryManager()->getLocalRepository(); foreach ($results as $result) { if (null !== $installedRepo->findPackage($result['name'], '*')) { continue; } $similarPackages[$result['name']] = levenshtein($package, $result['name']); } asort($similarPackages); return array_keys(array_slice($similarPackages, 0, 5)); } private function getPlatformExceptionDetails(PackageInterface $candidate, ?PlatformRepository $platformRepo = null): string { $details = []; if (null === $platformRepo) { return ''; } foreach ($candidate->getRequires() as $link) { if (!PlatformRepository::isPlatformPackage($link->getTarget())) { continue; } $platformPkg = $platformRepo->findPackage($link->getTarget(), '*'); if (null === $platformPkg) { if ($platformRepo->isPlatformPackageDisabled($link->getTarget())) { $details[] = $candidate->getPrettyName().' '.$candidate->getPrettyVersion().' requires '.$link->getTarget().' '.$link->getPrettyConstraint().' but it is disabled by your platform config. Enable it again with "composer config platform.'.$link->getTarget().' --unset".'; } else { $details[] = $candidate->getPrettyName().' '.$candidate->getPrettyVersion().' requires '.$link->getTarget().' '.$link->getPrettyConstraint().' but it is not present.'; } continue; } if (!$link->getConstraint()->matches(new Constraint('==', $platformPkg->getVersion()))) { $platformPkgVersion = $platformPkg->getPrettyVersion(); $platformExtra = $platformPkg->getExtra(); if (isset($platformExtra['config.platform']) && $platformPkg instanceof CompletePackageInterface) { $platformPkgVersion .= ' ('.$platformPkg->getDescription().')'; } $details[] = $candidate->getPrettyName().' '.$candidate->getPrettyVersion().' requires '.$link->getTarget().' '.$link->getPrettyConstraint().' which does not match your installed version '.$platformPkgVersion.'.'; } } if (count($details) === 0) { return ''; } return ':'.PHP_EOL.' - ' . implode(PHP_EOL.' - ', $details); } } setName('policy') ->setDescription('Manages custom dependency policies and their sources') ->setDefinition([ new InputOption('global', 'g', InputOption::VALUE_NONE, 'Apply command to the global config file'), new InputOption('file', 'f', InputOption::VALUE_REQUIRED, 'If you want to choose a different composer.json or config.json'), new InputArgument('action', InputArgument::REQUIRED, 'Action to perform: add-source', null, ['add-source']), new InputArgument('name', InputArgument::OPTIONAL, 'Policy name', null, $this->suggestListNames()), new InputArgument('arg1', InputArgument::OPTIONAL, 'Source type (e.g. "url") for add-source', null, $this->suggestArg1()), new InputArgument('arg2', InputArgument::OPTIONAL, 'URL for add-source (if not using JSON)'), ]) ->setHelp( <<.` to adjust their settings. Use --global/-g to alter the global config.json instead. Use --file to alter a specific file. EOT ); } protected function execute(InputInterface $input, OutputInterface $output): int { $action = strtolower((string) $input->getArgument('action')); $listName = $input->getArgument('name'); $arg1 = $input->getArgument('arg1'); $arg2 = $input->getArgument('arg2'); switch ($action) { case 'add-source': if ($listName === null) { throw new \RuntimeException('You must pass a dependency policy name. Example: composer policy add-source my-policy url https://example.org'); } $this->assertCustomListName((string) $listName); if ($arg1 === null) { throw new \RuntimeException('You must pass the source type and a url, or a JSON string.'); } if (is_string($arg1) && Preg::isMatch('{^\s*\{}', $arg1)) { $sourceConfig = JsonFile::parseJson($arg1); if (!is_array($sourceConfig)) { throw new \RuntimeException('Source JSON must be an object.'); } } else { if ($arg2 === null) { throw new \RuntimeException('You must pass the source type and a url. Example: composer policy add-source my-policy url https://example.org'); } $sourceConfig = ['type' => (string) $arg1, 'url' => (string) $arg2]; } $this->validateSourceConfig($listName, $sourceConfig); $data = $this->configFile->read(); $currentSources = $data['config']['policy'][$listName]['sources'] ?? []; if (!is_array($currentSources)) { $currentSources = []; } foreach ($currentSources as $existing) { if (is_array($existing) && ($existing['type'] ?? null) === $sourceConfig['type'] && ($existing['url'] ?? null) === ($sourceConfig['url'] ?? null) ) { $this->getIO()->write('Source '.$sourceConfig['url'].' already present in policy '.$listName.''); return 0; } } $currentSources[] = $sourceConfig; $this->configSource->addConfigSetting('policy.'.$listName.'.sources', $currentSources); return 0; default: throw new \InvalidArgumentException('Unknown action "'.$action.'". Use add-source.'); } } private function assertCustomListName(string $name): void { if (in_array($name, PolicyConfig::BUILTIN_LIST_NAMES, true)) { throw new \RuntimeException('Built-in dependency policy "'.$name.'" does not support sources. Use `composer config policy.'.$name.'.` to configure it.'); } $error = PolicyConfig::getFutureReservedListNameError($name); if ($error !== null) { throw new \RuntimeException($error); } if ($name === '' || strpos($name, '.') !== false) { throw new \RuntimeException('Invalid dependency policy name "'.$name.'".'); } } private function validateSourceConfig(string $listName, array $source): void { $sourceValidator = new SourceValidator(); $sourceValidator->validate($listName, $source); } private function suggestListNames(): \Closure { return function (CompletionInput $input): array { $action = $input->getArgument('action'); if (!in_array($action, ['add-source'], true)) { return []; } $config = Factory::createConfig(); $configFile = new JsonFile($this->getComposerConfigFile($input, $config)); if (!$configFile->exists()) { return []; } $data = $configFile->read(); $policy = $data['config']['policy'] ?? []; if (!is_array($policy)) { return []; } $names = []; foreach ($policy as $listName => $_) { if (in_array($listName, PolicyConfig::BUILTIN_LIST_NAMES, true) || in_array($listName, PolicyConfig::NON_LIST_KEYS, true)) { continue; } $names[] = (string) $listName; } sort($names); return $names; }; } private function suggestArg1(): \Closure { return static function (CompletionInput $input): array { if ($input->getArgument('action') === 'add-source') { return ['url']; } return []; }; } } setName('prohibits') ->setAliases(['why-not']) ->setDescription('Shows which packages prevent the given package from being installed') ->setDefinition([ new InputArgument(self::ARGUMENT_PACKAGE, InputArgument::REQUIRED, 'Package to inspect', null, $this->suggestAvailablePackage()), new InputArgument(self::ARGUMENT_CONSTRAINT, InputArgument::REQUIRED, 'Version constraint, which version you expected to be installed'), new InputOption(self::OPTION_RECURSIVE, 'r', InputOption::VALUE_NONE, 'Recursively resolves up to the root package'), new InputOption(self::OPTION_TREE, 't', InputOption::VALUE_NONE, 'Prints the results as a nested tree'), new InputOption('locked', null, InputOption::VALUE_NONE, 'Read dependency information from composer.lock'), ]) ->setHelp( <<php composer.phar prohibits composer/composer Read more at https://getcomposer.org/doc/03-cli.md#prohibits-why-not EOT ) ; } protected function execute(InputInterface $input, OutputInterface $output): int { return parent::doExecute($input, $output, true); } } setName('reinstall') ->setDescription('Uninstalls and reinstalls the given package names') ->setDefinition([ new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'), new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist (default behavior).'), new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).', null, $this->suggestPreferInstall()), new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'), new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'), new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'), new InputOption('apcu-autoloader-prefix', null, InputOption::VALUE_REQUIRED, 'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader'), new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'), new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'), new InputOption('type', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Filter packages to reinstall by type(s)', null, $this->suggestInstalledPackageTypes(false)), new InputArgument('packages', InputArgument::IS_ARRAY, 'List of package names to reinstall, can include a wildcard (*) to match any substring.', null, $this->suggestInstalledPackage(false)), ]) ->setHelp( <<reinstall command looks up installed packages by name, uninstalls them and reinstalls them. This lets you do a clean install of a package if you messed with its files, or if you wish to change the installation type using --prefer-install. php composer.phar reinstall acme/foo "acme/bar-*" Read more at https://getcomposer.org/doc/03-cli.md#reinstall EOT ) ; } protected function execute(InputInterface $input, OutputInterface $output): int { $io = $this->getIO(); $composer = $this->requireComposer(); $localRepo = $composer->getRepositoryManager()->getLocalRepository(); $packagesToReinstall = []; $packageNamesToReinstall = []; if (\count($input->getOption('type')) > 0) { if (\count($input->getArgument('packages')) > 0) { throw new \InvalidArgumentException('You cannot specify package names and filter by type at the same time.'); } foreach ($localRepo->getCanonicalPackages() as $package) { if (in_array($package->getType(), $input->getOption('type'), true)) { $packagesToReinstall[] = $package; $packageNamesToReinstall[] = $package->getName(); } } } else { if (\count($input->getArgument('packages')) === 0) { throw new \InvalidArgumentException('You must pass one or more package names to be reinstalled.'); } foreach ($input->getArgument('packages') as $pattern) { $patternRegexp = BasePackage::packageNameToRegexp($pattern); $matched = false; foreach ($localRepo->getCanonicalPackages() as $package) { if (Preg::isMatch($patternRegexp, $package->getName())) { $matched = true; $packagesToReinstall[] = $package; $packageNamesToReinstall[] = $package->getName(); } } if (!$matched) { $io->writeError('Pattern "' . $pattern . '" does not match any currently installed packages.'); } } } if (0 === \count($packagesToReinstall)) { $io->writeError('Found no packages to reinstall, aborting.'); return 1; } $uninstallOperations = []; foreach ($packagesToReinstall as $package) { $uninstallOperations[] = new UninstallOperation($package); } $presentPackages = $localRepo->getPackages(); $resultPackages = $presentPackages; foreach ($presentPackages as $index => $package) { if (in_array($package->getName(), $packageNamesToReinstall, true)) { unset($presentPackages[$index]); } } $transaction = new Transaction($presentPackages, $resultPackages); $installOperations = $transaction->getOperations(); $installOrder = []; foreach ($installOperations as $index => $op) { if ($op instanceof InstallOperation && !$op->getPackage() instanceof AliasPackage) { $installOrder[$op->getPackage()->getName()] = $index; } } usort($uninstallOperations, static function ($a, $b) use ($installOrder): int { return $installOrder[$b->getPackage()->getName()] - $installOrder[$a->getPackage()->getName()]; }); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'reinstall', $input, $output); $eventDispatcher = $composer->getEventDispatcher(); $eventDispatcher->dispatch($commandEvent->getName(), $commandEvent); $config = $composer->getConfig(); [$preferSource, $preferDist] = $this->getPreferredInstallOptions($config, $input); $installationManager = $composer->getInstallationManager(); $downloadManager = $composer->getDownloadManager(); $package = $composer->getPackage(); $installationManager->setOutputProgress(!$input->getOption('no-progress')); if ($input->getOption('no-plugins')) { $installationManager->disablePlugins(); } $downloadManager->setPreferSource($preferSource); $downloadManager->setPreferDist($preferDist); $devMode = $localRepo->getDevMode() !== null ? $localRepo->getDevMode() : true; Platform::putEnv('COMPOSER_DEV_MODE', $devMode ? '1' : '0'); $eventDispatcher->dispatchScript(ScriptEvents::PRE_INSTALL_CMD, $devMode); $installationManager->execute($localRepo, $uninstallOperations, $devMode); $installationManager->execute($localRepo, $installOperations, $devMode); if (!$input->getOption('no-autoloader')) { $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader'); $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative'); $apcuPrefix = $input->getOption('apcu-autoloader-prefix'); $apcu = $apcuPrefix !== null || $input->getOption('apcu-autoloader') || $config->get('apcu-autoloader'); $generator = $composer->getAutoloadGenerator(); $generator->setClassMapAuthoritative($authoritative); $generator->setApcu($apcu, $apcuPrefix); $generator->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input)); $generator->dump( $config, $localRepo, $package, $installationManager, 'composer', $optimize, null, $composer->getLocker() ); } $eventDispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, $devMode); return 0; } } setName('remove') ->setAliases(['rm', 'uninstall']) ->setDescription('Removes a package from the require or require-dev') ->setDefinition([ new InputArgument('packages', InputArgument::IS_ARRAY, 'Packages that should be removed.', null, $this->suggestRootRequirement()), new InputOption('dev', null, InputOption::VALUE_NONE, 'Removes a package from the require-dev section.'), new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('no-update', null, InputOption::VALUE_NONE, 'Disables the automatic update of the dependencies (implies --no-install).'), new InputOption('no-install', null, InputOption::VALUE_NONE, 'Skip the install step after updating the composer.lock file.'), new InputOption('no-audit', null, InputOption::VALUE_NONE, 'Skip the audit step after updating the composer.lock file (can also be set via the COMPOSER_NO_AUDIT=1 env var).'), new InputOption('audit-format', null, InputOption::VALUE_REQUIRED, 'Audit output format. Must be "table", "plain", "json", or "summary".', Auditor::FORMAT_SUMMARY, Auditor::FORMATS), new InputOption('no-security-blocking', null, InputOption::VALUE_NONE, 'DEPRECATED: use --no-blocking instead. Allows installing packages with security advisories or that are abandoned (can also be set via the COMPOSER_NO_SECURITY_BLOCKING=1 env var).'), new InputOption('no-blocking', null, InputOption::VALUE_NONE, 'Disables all policy blocking during this command (can also be set via the COMPOSER_NO_BLOCKING=1 env var).'), new InputOption('update-no-dev', null, InputOption::VALUE_NONE, 'Run the dependency update with the --no-dev option.'), new InputOption('update-with-dependencies', 'w', InputOption::VALUE_NONE, 'Allows inherited dependencies to be updated with explicit dependencies (can also be set via the COMPOSER_WITH_DEPENDENCIES=1 env var). (Deprecated, is now default behavior)'), new InputOption('update-with-all-dependencies', 'W', InputOption::VALUE_NONE, 'Allows all inherited dependencies to be updated, including those that are root requirements (can also be set via the COMPOSER_WITH_ALL_DEPENDENCIES=1 env var).'), new InputOption('with-all-dependencies', null, InputOption::VALUE_NONE, 'Alias for --update-with-all-dependencies'), new InputOption('no-update-with-dependencies', null, InputOption::VALUE_NONE, 'Does not allow inherited dependencies to be updated with explicit dependencies.'), new InputOption('minimal-changes', 'm', InputOption::VALUE_NONE, 'During an update with -w/-W, only perform absolutely necessary changes to transitive dependencies (can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var).'), new InputOption('unused', null, InputOption::VALUE_NONE, 'Remove all packages which are locked but not required by any other package.'), new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'), new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'), new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'), new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'), new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'), new InputOption('apcu-autoloader-prefix', null, InputOption::VALUE_REQUIRED, 'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader'), ]) ->setHelp( <<remove command removes a package from the current list of installed packages php composer.phar remove Read more at https://getcomposer.org/doc/03-cli.md#remove-rm EOT ) ; } protected function execute(InputInterface $input, OutputInterface $output): int { if ($input->getArgument('packages') === [] && !$input->getOption('unused')) { throw new InvalidArgumentException('Not enough arguments (missing: "packages").'); } $packages = $input->getArgument('packages'); $packages = array_map('strtolower', $packages); if ($input->getOption('unused')) { $composer = $this->requireComposer(); $locker = $composer->getLocker(); if (!$locker->isLocked()) { throw new \UnexpectedValueException('A valid composer.lock file is required to run this command with --unused'); } $lockedPackages = $locker->getLockedRepository()->getPackages(); $required = []; foreach (array_merge($composer->getPackage()->getRequires(), $composer->getPackage()->getDevRequires()) as $link) { $required[$link->getTarget()] = true; } do { $found = false; foreach ($lockedPackages as $index => $package) { foreach ($package->getNames() as $name) { if (isset($required[$name])) { foreach ($package->getRequires() as $link) { $required[$link->getTarget()] = true; } $found = true; unset($lockedPackages[$index]); break; } } } } while ($found); $unused = []; foreach ($lockedPackages as $package) { $unused[] = $package->getName(); } $packages = array_merge($packages, $unused); if (count($packages) === 0) { $this->getIO()->writeError('No unused packages to remove'); return 0; } } $file = Factory::getComposerFile(); $jsonFile = new JsonFile($file); $composer = $jsonFile->read(); $composerBackup = file_get_contents($jsonFile->getPath()); $json = new JsonConfigSource($jsonFile); $type = $input->getOption('dev') ? 'require-dev' : 'require'; $altType = !$input->getOption('dev') ? 'require-dev' : 'require'; $io = $this->getIO(); if ($input->getOption('update-with-dependencies')) { $io->writeError('You are using the deprecated option "update-with-dependencies". This is now default behaviour. The --no-update-with-dependencies option can be used to remove a package without its dependencies.'); } foreach (['require', 'require-dev'] as $linkType) { if (isset($composer[$linkType])) { foreach ($composer[$linkType] as $name => $version) { $composer[$linkType][strtolower($name)] = $name; } } } $dryRun = $input->getOption('dry-run'); $toRemove = []; foreach ($packages as $package) { if (isset($composer[$type][$package])) { if ($dryRun) { $toRemove[$type][] = $composer[$type][$package]; } else { $json->removeLink($type, $composer[$type][$package]); } } elseif (isset($composer[$altType][$package])) { $io->writeError('' . $composer[$altType][$package] . ' could not be found in ' . $type . ' but it is present in ' . $altType . ''); if ($io->isInteractive()) { if ($io->askConfirmation('Do you want to remove it from ' . $altType . ' [yes]? ')) { if ($dryRun) { $toRemove[$altType][] = $composer[$altType][$package]; } else { $json->removeLink($altType, $composer[$altType][$package]); } } } } elseif (isset($composer[$type]) && count($matches = Preg::grep(BasePackage::packageNameToRegexp($package), array_keys($composer[$type]))) > 0) { foreach ($matches as $matchedPackage) { if ($dryRun) { $toRemove[$type][] = $matchedPackage; } else { $json->removeLink($type, $matchedPackage); } } } elseif (isset($composer[$altType]) && count($matches = Preg::grep(BasePackage::packageNameToRegexp($package), array_keys($composer[$altType]))) > 0) { foreach ($matches as $matchedPackage) { $io->writeError('' . $matchedPackage . ' could not be found in ' . $type . ' but it is present in ' . $altType . ''); if ($io->isInteractive()) { if ($io->askConfirmation('Do you want to remove it from ' . $altType . ' [yes]? ')) { if ($dryRun) { $toRemove[$altType][] = $matchedPackage; } else { $json->removeLink($altType, $matchedPackage); } } } } } else { $io->writeError(''.$package.' is not required in your composer.json and has not been removed'); } } $io->writeError(''.$file.' has been updated'); if ($input->getOption('no-update')) { return 0; } if ($composer = $this->tryComposer()) { $composer->getPluginManager()->deactivateInstalledPlugins(); } $this->resetComposer(); $composer = $this->requireComposer(); if ($dryRun) { $rootPackage = $composer->getPackage(); $links = [ 'require' => $rootPackage->getRequires(), 'require-dev' => $rootPackage->getDevRequires(), ]; foreach ($toRemove as $type => $names) { foreach ($names as $name) { unset($links[$type][$name]); } } $rootPackage->setRequires($links['require']); $rootPackage->setDevRequires($links['require-dev']); } $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'remove', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $allowPlugins = $composer->getConfig()->get('allow-plugins'); $removedPlugins = is_array($allowPlugins) ? array_intersect(array_keys($allowPlugins), $packages) : []; if (!$dryRun && is_array($allowPlugins) && count($removedPlugins) > 0) { if (count($allowPlugins) === count($removedPlugins)) { $json->removeConfigSetting('allow-plugins'); } else { foreach ($removedPlugins as $plugin) { $json->removeConfigSetting('allow-plugins.'.$plugin); } } } $composer->getInstallationManager()->setOutputProgress(!$input->getOption('no-progress')); $install = Installer::create($io, $composer); $updateDevMode = !$input->getOption('update-no-dev'); $optimize = $input->getOption('optimize-autoloader') || $composer->getConfig()->get('optimize-autoloader'); $authoritative = $input->getOption('classmap-authoritative') || $composer->getConfig()->get('classmap-authoritative'); $apcuPrefix = $input->getOption('apcu-autoloader-prefix'); $apcu = $apcuPrefix !== null || $input->getOption('apcu-autoloader') || $composer->getConfig()->get('apcu-autoloader'); $minimalChanges = $input->getOption('minimal-changes') || $composer->getConfig()->get('update-with-minimal-changes'); $updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE; $flags = ''; if ($input->getOption('update-with-all-dependencies') || $input->getOption('with-all-dependencies')) { $updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS; $flags .= ' --with-all-dependencies'; } elseif ($input->getOption('no-update-with-dependencies')) { $updateAllowTransitiveDependencies = Request::UPDATE_ONLY_LISTED; $flags .= ' --with-dependencies'; } $io->writeError('Running composer update '.implode(' ', $packages).$flags.''); $install ->setVerbose($input->getOption('verbose')) ->setDevMode($updateDevMode) ->setOptimizeAutoloader($optimize) ->setClassMapAuthoritative($authoritative) ->setApcuAutoloader($apcu, $apcuPrefix) ->setUpdate(true) ->setInstall(!$input->getOption('no-install')) ->setUpdateAllowTransitiveDependencies($updateAllowTransitiveDependencies) ->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input)) ->setDryRun($dryRun) ->setPolicyConfig($this->createPolicyConfig($composer->getConfig(), $input)) ->setAuditConfig($this->createAuditConfig($input)) ->setMinimalUpdate($minimalChanges) ; if ($composer->getLocker()->isLocked()) { $install->setUpdateAllowList($packages); } $status = $install->run(); if ($status !== 0) { $io->writeError("\n".'Removal failed, reverting '.$file.' to its original content.'); file_put_contents($jsonFile->getPath(), $composerBackup); } if (!$dryRun) { foreach ($packages as $package) { if ($composer->getRepositoryManager()->getLocalRepository()->findPackages($package)) { $io->writeError('Removal failed, '.$package.' is still present, it may be required by another package. See `composer why '.$package.'`.'); return 2; } } } return $status; } } setName('repository') ->setAliases(['repo']) ->setDescription('Manages repositories') ->setDefinition([ new InputOption('global', 'g', InputOption::VALUE_NONE, 'Apply command to the global config file'), new InputOption('file', 'f', InputOption::VALUE_REQUIRED, 'If you want to choose a different composer.json or config.json'), new InputOption('append', null, InputOption::VALUE_NONE, 'When adding a repository, append it (lower priority) instead of prepending it'), new InputOption('before', null, InputOption::VALUE_REQUIRED, 'When adding a repository, insert it before the given repository name', null, $this->suggestRepoNames()), new InputOption('after', null, InputOption::VALUE_REQUIRED, 'When adding a repository, insert it after the given repository name', null, $this->suggestRepoNames()), new InputArgument('action', InputArgument::OPTIONAL, 'Action to perform: list, add, remove, set-url, get-url, enable, disable', 'list', ['list', 'add', 'remove', 'set-url', 'get-url', 'enable', 'disable']), new InputArgument('name', InputArgument::OPTIONAL, 'Repository name (or special name packagist.org for enable/disable)', null, $this->suggestRepoNames()), new InputArgument('arg1', InputArgument::OPTIONAL, 'Type for add, or new URL for set-url, or JSON config for add', null, $this->suggestTypeForAdd()), new InputArgument('arg2', InputArgument::OPTIONAL, 'URL for add (if not using JSON)'), ]) ->setHelp( <<getArgument('action')); $name = $input->getArgument('name'); $arg1 = $input->getArgument('arg1'); $arg2 = $input->getArgument('arg2'); $this->config->merge($this->configFile->read(), $this->configFile->getPath()); $repos = $this->config->getRepositories(); switch ($action) { case 'list': case 'ls': case 'show': $this->listRepositories($repos); return 0; case 'add': if ($name === null) { throw new \RuntimeException('You must pass a repository name. Example: composer repo add foo vcs https://example.org'); } if ($arg1 === null) { throw new \RuntimeException('You must pass the type and a url, or a JSON string.'); } if (is_string($arg1) && Preg::isMatch('{^\s*\{}', $arg1)) { $repoConfig = JsonFile::parseJson($arg1); } else { if ($arg2 === null) { throw new \RuntimeException('You must pass the type and a url. Example: composer repo add foo vcs https://example.org'); } $repoConfig = ['type' => (string) $arg1, 'url' => (string) $arg2]; } $before = $input->getOption('before'); $after = $input->getOption('after'); if ($before !== null && $after !== null) { throw new \RuntimeException('You can not combine --before and --after'); } if ($before !== null || $after !== null) { if ($repoConfig === false) { throw new \RuntimeException('Cannot use --before/--after with boolean repository values'); } $this->configSource->insertRepository((string) $name, $repoConfig, $before ?? $after, $after !== null ? 1 : 0); return 0; } $this->configSource->addRepository((string) $name, $repoConfig, (bool) $input->getOption('append')); return 0; case 'remove': case 'rm': case 'delete': if ($name === null) { throw new \RuntimeException('You must pass the repository name to remove.'); } $this->configSource->removeRepository((string) $name); if (in_array($name, ['packagist', 'packagist.org'], true)) { $this->configSource->addRepository('packagist.org', false); } return 0; case 'set-url': case 'seturl': if ($name === null || $arg1 === null) { throw new \RuntimeException('Usage: composer repo set-url '); } $this->configSource->setRepositoryUrl($name, $arg1); return 0; case 'get-url': case 'geturl': if ($name === null) { throw new \RuntimeException('Usage: composer repo get-url '); } if (isset($repos[$name]) && is_array($repos[$name])) { $url = $repos[$name]['url'] ?? null; if (!is_string($url)) { throw new \InvalidArgumentException('The '.$name.' repository does not have a URL'); } $this->getIO()->write($url); return 0; } if (is_array($repos)) { foreach ($repos as $val) { if (is_array($val) && isset($val['name']) && $val['name'] === $name) { $url = $val['url'] ?? null; if (!is_string($url)) { throw new \InvalidArgumentException('The '.$name.' repository does not have a URL'); } $this->getIO()->write($url); return 0; } } } throw new \InvalidArgumentException('There is no '.$name.' repository defined'); case 'disable': if ($name === null) { throw new \RuntimeException('Usage: composer repo disable packagist.org'); } if (in_array($name, ['packagist', 'packagist.org'], true)) { $this->configSource->addRepository('packagist.org', false, (bool) $input->getOption('append')); return 0; } throw new \RuntimeException('Only packagist.org can be enabled/disabled using this command. Use add/remove for other repositories.'); case 'enable': if ($name === null) { throw new \RuntimeException('Usage: composer repo enable packagist.org'); } if (in_array($name, ['packagist', 'packagist.org'], true)) { $this->configSource->removeRepository('packagist.org'); return 0; } throw new \RuntimeException('Only packagist.org can be enabled/disabled using this command.'); default: throw new \InvalidArgumentException('Unknown action "'.$action.'". Use list, add, remove, set-url, get-url, enable, disable'); } } private function listRepositories(array $repos): void { $io = $this->getIO(); $packagistPresent = false; foreach ($repos as $key => $repo) { if (isset($repo['type'], $repo['url']) && $repo['type'] === 'composer' && str_ends_with((string) parse_url($repo['url'], PHP_URL_HOST), 'packagist.org')) { $packagistPresent = true; break; } } if (!$packagistPresent) { $repos[] = ['packagist.org' => false]; } if ($repos === []) { $io->write('No repositories configured'); return; } foreach ($repos as $key => $repo) { if ($repo === false) { $io->write('['.$key.'] disabled'); continue; } if (is_array($repo)) { if (1 === \count($repo) && false === current($repo)) { $io->write('['.array_key_first($repo).'] disabled'); continue; } $name = $repo['name'] ?? $key; $type = $repo['type'] ?? 'unknown'; $url = $repo['url'] ?? JsonFile::encode($repo); $io->write('['.$name.'] '.$type.' '.$url); } } } private function suggestTypeForAdd(): \Closure { return static function (CompletionInput $input): array { if ($input->getArgument('action') === 'add') { return ['composer', 'vcs', 'artifact', 'path']; } return []; }; } private function suggestRepoNames(): \Closure { return function (CompletionInput $input): array { if (in_array($input->getArgument('action'), ['enable', 'disable'], true)) { return ['packagist.org']; } if (!in_array($input->getArgument('action'), ['remove', 'set-url', 'get-url'], true)) { return []; } $config = Factory::createConfig(); $configFile = new JsonFile($this->getComposerConfigFile($input, $config)); $data = $configFile->read(); $repos = []; foreach (($data['repositories'] ?? []) as $repo) { if (isset($repo['name'])) { $repos[] = $repo['name']; } } sort($repos); return $repos; }; } } setName('require') ->setAliases(['r']) ->setDescription('Adds required packages to your composer.json and installs them') ->setDefinition([ new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Optional package name can also include a version constraint, e.g. foo/bar or foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"', null, $this->suggestAvailablePackageInclPlatform()), new InputOption('dev', null, InputOption::VALUE_NONE, 'Add requirement to require-dev.'), new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'), new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'), new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist (default behavior).'), new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).', null, $this->suggestPreferInstall()), new InputOption('fixed', null, InputOption::VALUE_NONE, 'Write fixed version to the composer.json.'), new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'DEPRECATED: This flag does not exist anymore.'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('no-update', null, InputOption::VALUE_NONE, 'Disables the automatic update of the dependencies (implies --no-install).'), new InputOption('no-install', null, InputOption::VALUE_NONE, 'Skip the install step after updating the composer.lock file.'), new InputOption('no-audit', null, InputOption::VALUE_NONE, 'Skip the audit step after updating the composer.lock file (can also be set via the COMPOSER_NO_AUDIT=1 env var).'), new InputOption('audit-format', null, InputOption::VALUE_REQUIRED, 'Audit output format. Must be "table", "plain", "json", or "summary".', Auditor::FORMAT_SUMMARY, Auditor::FORMATS), new InputOption('no-security-blocking', null, InputOption::VALUE_NONE, 'DEPRECATED: use --no-blocking instead. Allows installing packages with security advisories or that are abandoned (can also be set via the COMPOSER_NO_SECURITY_BLOCKING=1 env var).'), new InputOption('no-blocking', null, InputOption::VALUE_NONE, 'Disables all policy blocking during this command (can also be set via the COMPOSER_NO_BLOCKING=1 env var).'), new InputOption('update-no-dev', null, InputOption::VALUE_NONE, 'Run the dependency update with the --no-dev option.'), new InputOption('update-with-dependencies', 'w', InputOption::VALUE_NONE, 'Allows inherited dependencies to be updated, except those that are root requirements (can also be set via the COMPOSER_WITH_DEPENDENCIES=1 env var).'), new InputOption('update-with-all-dependencies', 'W', InputOption::VALUE_NONE, 'Allows all inherited dependencies to be updated, including those that are root requirements (can also be set via the COMPOSER_WITH_ALL_DEPENDENCIES=1 env var).'), new InputOption('with-dependencies', null, InputOption::VALUE_NONE, 'Alias for --update-with-dependencies'), new InputOption('with-all-dependencies', null, InputOption::VALUE_NONE, 'Alias for --update-with-all-dependencies'), new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'), new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'), new InputOption('prefer-stable', null, InputOption::VALUE_NONE, 'Prefer stable versions of dependencies (can also be set via the COMPOSER_PREFER_STABLE=1 env var).'), new InputOption('prefer-lowest', null, InputOption::VALUE_NONE, 'Prefer lowest versions of dependencies (can also be set via the COMPOSER_PREFER_LOWEST=1 env var).'), new InputOption('minimal-changes', 'm', InputOption::VALUE_NONE, 'During an update with -w/-W, only perform absolutely necessary changes to transitive dependencies (can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var).'), new InputOption('sort-packages', null, InputOption::VALUE_NONE, 'Sorts packages when adding/updating a new dependency'), new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'), new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'), new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'), new InputOption('apcu-autoloader-prefix', null, InputOption::VALUE_REQUIRED, 'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader'), ]) ->setHelp( <<file = Factory::getComposerFile(); $io = $this->getIO(); if ($input->getOption('no-suggest')) { $io->writeError('You are using the deprecated option "--no-suggest". It has no effect and will break in Composer 3.'); } $this->newlyCreated = !file_exists($this->file); if ($this->newlyCreated && !file_put_contents($this->file, "{\n}\n")) { $io->writeError(''.$this->file.' could not be created.'); return 1; } if (!Filesystem::isReadable($this->file)) { $io->writeError(''.$this->file.' is not readable.'); return 1; } if (filesize($this->file) === 0) { file_put_contents($this->file, "{\n}\n"); } $this->json = new JsonFile($this->file); $this->lock = Factory::getLockFile($this->file); $this->composerBackup = file_get_contents($this->json->getPath()); $this->lockBackup = file_exists($this->lock) ? file_get_contents($this->lock) : null; $signalHandler = SignalHandler::create([SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP], function (string $signal, SignalHandler $handler) { $this->getIO()->writeError('Received '.$signal.', aborting', true, IOInterface::DEBUG); $this->revertComposerFile(); $handler->exitWithLastSignal(); }); if (!is_writable($this->file) && false === Silencer::call('file_put_contents', $this->file, $this->composerBackup)) { $io->writeError(''.$this->file.' is not writable.'); return 1; } if ($input->getOption('fixed') === true) { $config = $this->json->read(); $packageType = empty($config['type']) ? 'library' : $config['type']; if ($packageType !== 'project' && !$input->getOption('dev')) { $io->writeError('The "--fixed" option is only allowed for packages with a "project" type or for dev dependencies to prevent possible misuses.'); if (!isset($config['type'])) { $io->writeError('If your package is not a library, you can explicitly specify the "type" by using "composer config type project".'); } return 1; } } $composer = $this->requireComposer(); $repos = $composer->getRepositoryManager()->getRepositories(); $platformOverrides = $composer->getConfig()->get('platform'); $this->repos = new CompositeRepository(array_merge( [$platformRepo = new PlatformRepository([], $platformOverrides)], $repos )); if ($composer->getPackage()->getPreferStable()) { $preferredStability = 'stable'; } else { $preferredStability = $composer->getPackage()->getMinimumStability(); } try { $requirements = $this->determineRequirements( $input, $output, $input->getArgument('packages'), $platformRepo, $preferredStability, $input->getOption('no-update'), $input->getOption('fixed') ); } catch (\Exception $e) { if ($this->newlyCreated) { $this->revertComposerFile(); throw new \RuntimeException('No composer.json present in the current directory ('.$this->file.'), this may be the cause of the following exception.', 0, $e); } throw $e; } $requirements = $this->formatRequirements($requirements); if (!$input->getOption('dev') && $io->isInteractive() && !$composer->isGlobal()) { $devPackages = []; $devTags = ['dev', 'testing', 'static analysis']; $currentRequiresByKey = $this->getPackagesByRequireKey(); foreach ($requirements as $name => $version) { if (isset($currentRequiresByKey[$name])) { continue; } $pkg = PackageSorter::getMostCurrentVersion($this->getRepos()->findPackages($name)); if ($pkg instanceof CompletePackageInterface) { $pkgDevTags = array_intersect($devTags, array_map('strtolower', $pkg->getKeywords())); if (count($pkgDevTags) > 0) { $devPackages[] = $pkgDevTags; } } } if (count($devPackages) === count($requirements)) { $plural = count($requirements) > 1 ? 's' : ''; $plural2 = count($requirements) > 1 ? 'are' : 'is'; $plural3 = count($requirements) > 1 ? 'they are' : 'it is'; $pkgDevTags = array_unique(array_merge(...$devPackages)); $io->warning('The package'.$plural.' you required '.$plural2.' recommended to be placed in require-dev (because '.$plural3.' tagged as "'.implode('", "', $pkgDevTags).'") but you did not use --dev.'); if ($io->askConfirmation('Do you want to re-run the command with --dev? [yes]? ')) { $input->setOption('dev', true); } } unset($devPackages, $pkgDevTags); } $requireKey = $input->getOption('dev') ? 'require-dev' : 'require'; $removeKey = $input->getOption('dev') ? 'require' : 'require-dev'; $requirementsToGuess = []; foreach ($requirements as $package => $constraint) { if ($constraint === 'guess') { $requirements[$package] = '*'; $requirementsToGuess[] = $package; } } $versionParser = new VersionParser(); foreach ($requirements as $package => $constraint) { if (strtolower($package) === $composer->getPackage()->getName()) { $io->writeError(sprintf('Root package \'%s\' cannot require itself in its composer.json', $package)); return 1; } if ($constraint === 'self.version') { continue; } $versionParser->parseConstraints($constraint); } $inconsistentRequireKeys = $this->getInconsistentRequireKeys($requirements, $requireKey); if (count($inconsistentRequireKeys) > 0) { foreach ($inconsistentRequireKeys as $package) { $io->warning(sprintf( '%s is currently present in the %s key and you ran the command %s the --dev flag, which will move it to the %s key.', $package, $removeKey, $input->getOption('dev') ? 'with' : 'without', $requireKey )); } if ($io->isInteractive()) { if (!$io->askConfirmation(sprintf('Do you want to move %s? [no]? ', count($inconsistentRequireKeys) > 1 ? 'these requirements' : 'this requirement'), false)) { if (!$io->askConfirmation(sprintf('Do you want to re-run the command %s --dev? [yes]? ', $input->getOption('dev') ? 'without' : 'with'), true)) { return 0; } $input->setOption('dev', true); [$requireKey, $removeKey] = [$removeKey, $requireKey]; } } } $sortPackages = $input->getOption('sort-packages') || $composer->getConfig()->get('sort-packages'); $this->firstRequire = $this->newlyCreated; if (!$this->firstRequire) { $composerDefinition = $this->json->read(); if (count($composerDefinition['require'] ?? []) === 0 && count($composerDefinition['require-dev'] ?? []) === 0) { $this->firstRequire = true; } } if (!$input->getOption('dry-run')) { $this->updateFile($this->json, $requirements, $requireKey, $removeKey, $sortPackages); } $io->writeError(''.$this->file.' has been '.($this->newlyCreated ? 'created' : 'updated').''); if ($input->getOption('no-update')) { return 0; } $composer->getPluginManager()->deactivateInstalledPlugins(); try { $result = $this->doUpdate($input, $output, $io, $requirements, $requireKey, $removeKey); if ($result === 0 && count($requirementsToGuess) > 0) { $result = $this->updateRequirementsAfterResolution($requirementsToGuess, $requireKey, $removeKey, $sortPackages, $input->getOption('dry-run'), $input->getOption('fixed')); } return $result; } catch (\Exception $e) { if (!$this->dependencyResolutionCompleted) { $this->revertComposerFile(); } throw $e; } finally { if ($input->getOption('dry-run') && $this->newlyCreated) { @unlink($this->json->getPath()); } $signalHandler->unregister(); } } private function getInconsistentRequireKeys(array $newRequirements, string $requireKey): array { $requireKeys = $this->getPackagesByRequireKey(); $inconsistentRequirements = []; foreach ($requireKeys as $package => $packageRequireKey) { if (!isset($newRequirements[$package])) { continue; } if ($requireKey !== $packageRequireKey) { $inconsistentRequirements[] = $package; } } return $inconsistentRequirements; } private function getPackagesByRequireKey(): array { $composerDefinition = $this->json->read(); $require = []; $requireDev = []; if (isset($composerDefinition['require'])) { $require = $composerDefinition['require']; } if (isset($composerDefinition['require-dev'])) { $requireDev = $composerDefinition['require-dev']; } return array_merge( array_fill_keys(array_keys($require), 'require'), array_fill_keys(array_keys($requireDev), 'require-dev') ); } private function doUpdate(InputInterface $input, OutputInterface $output, IOInterface $io, array $requirements, string $requireKey, string $removeKey): int { $this->resetComposer(); $composer = $this->requireComposer(); $this->dependencyResolutionCompleted = false; $composer->getEventDispatcher()->addListener(InstallerEvents::PRE_OPERATIONS_EXEC, function (): void { $this->dependencyResolutionCompleted = true; }, 10000); if ($input->getOption('dry-run')) { $rootPackage = $composer->getPackage(); $links = [ 'require' => $rootPackage->getRequires(), 'require-dev' => $rootPackage->getDevRequires(), ]; $loader = new ArrayLoader(); $newLinks = $loader->parseLinks($rootPackage->getName(), $rootPackage->getPrettyVersion(), BasePackage::$supportedLinkTypes[$requireKey]['method'], $requirements); $links[$requireKey] = array_merge($links[$requireKey], $newLinks); foreach ($requirements as $package => $constraint) { unset($links[$removeKey][$package]); } $rootPackage->setRequires($links['require']); $rootPackage->setDevRequires($links['require-dev']); $references = $rootPackage->getReferences(); $references = RootPackageLoader::extractReferences($requirements, $references); $rootPackage->setReferences($references); $stabilityFlags = $rootPackage->getStabilityFlags(); $stabilityFlags = RootPackageLoader::extractStabilityFlags($requirements, $rootPackage->getMinimumStability(), $stabilityFlags); $rootPackage->setStabilityFlags($stabilityFlags); unset($stabilityFlags, $references); } $updateDevMode = !$input->getOption('update-no-dev'); $optimize = $input->getOption('optimize-autoloader') || $composer->getConfig()->get('optimize-autoloader'); $authoritative = $input->getOption('classmap-authoritative') || $composer->getConfig()->get('classmap-authoritative'); $apcuPrefix = $input->getOption('apcu-autoloader-prefix'); $apcu = $apcuPrefix !== null || $input->getOption('apcu-autoloader') || $composer->getConfig()->get('apcu-autoloader'); $minimalChanges = $input->getOption('minimal-changes') || $composer->getConfig()->get('update-with-minimal-changes'); $updateAllowTransitiveDependencies = Request::UPDATE_ONLY_LISTED; $flags = ''; if ($input->getOption('update-with-all-dependencies') || $input->getOption('with-all-dependencies')) { $updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS; $flags .= ' --with-all-dependencies'; } elseif ($input->getOption('update-with-dependencies') || $input->getOption('with-dependencies')) { $updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE; $flags .= ' --with-dependencies'; } $io->writeError('Running composer update '.implode(' ', array_keys($requirements)).$flags.''); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'require', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $composer->getInstallationManager()->setOutputProgress(!$input->getOption('no-progress')); $install = Installer::create($io, $composer); [$preferSource, $preferDist] = $this->getPreferredInstallOptions($composer->getConfig(), $input); $install ->setDryRun($input->getOption('dry-run')) ->setVerbose($input->getOption('verbose')) ->setPreferSource($preferSource) ->setPreferDist($preferDist) ->setDevMode($updateDevMode) ->setOptimizeAutoloader($optimize) ->setClassMapAuthoritative($authoritative) ->setApcuAutoloader($apcu, $apcuPrefix) ->setUpdate(true) ->setInstall(!$input->getOption('no-install')) ->setUpdateAllowTransitiveDependencies($updateAllowTransitiveDependencies) ->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input)) ->setPreferStable($input->getOption('prefer-stable')) ->setPreferLowest($input->getOption('prefer-lowest')) ->setPolicyConfig($this->createPolicyConfig($composer->getConfig(), $input)) ->setAuditConfig($this->createAuditConfig($input)) ->setMinimalUpdate($minimalChanges) ; if (!$this->firstRequire && $composer->getLocker()->isLocked()) { $install->setUpdateAllowList(array_keys($requirements)); } $status = $install->run(); if ($status !== 0 && $status !== Installer::ERROR_AUDIT_FAILED) { if ($status === Installer::ERROR_DEPENDENCY_RESOLUTION_FAILED) { foreach ($this->normalizeRequirements($input->getArgument('packages')) as $req) { if (!isset($req['version'])) { $io->writeError('You can also try re-running composer require with an explicit version constraint, e.g. "composer require '.$req['name'].':*" to figure out if any version is installable, or "composer require '.$req['name'].':^2.1" if you know which you need.'); break; } } } $this->revertComposerFile(); } return $status; } private function updateRequirementsAfterResolution(array $requirementsToUpdate, string $requireKey, string $removeKey, bool $sortPackages, bool $dryRun, bool $fixed): int { $composer = $this->requireComposer(); $locker = $composer->getLocker(); $requirements = []; $versionSelector = new VersionSelector(new RepositorySet()); $repo = $locker->isLocked() ? $composer->getLocker()->getLockedRepository(true) : $composer->getRepositoryManager()->getLocalRepository(); foreach ($requirementsToUpdate as $packageName) { $package = $repo->findPackage($packageName, '*'); while ($package instanceof AliasPackage) { $package = $package->getAliasOf(); } if (!$package instanceof PackageInterface) { continue; } if ($fixed) { $requirements[$packageName] = $package->getPrettyVersion(); } else { $requirements[$packageName] = $versionSelector->findRecommendedRequireVersion($package); } $this->getIO()->writeError(sprintf( 'Using version %s for %s', $requirements[$packageName], $packageName )); if (Preg::isMatch('{^dev-(?!main$|master$|trunk$|latest$)}', $requirements[$packageName])) { $this->getIO()->warning('Version '.$requirements[$packageName].' looks like it may be a feature branch which is unlikely to keep working in the long run and may be in an unstable state'); if ($this->getIO()->isInteractive() && !$this->getIO()->askConfirmation('Are you sure you want to use this constraint (y) or would you rather abort (n) the whole operation [y,n]? ')) { $this->revertComposerFile(); return 1; } } } if (!$dryRun) { $this->updateFile($this->json, $requirements, $requireKey, $removeKey, $sortPackages); if ($locker->isLocked() && $composer->getConfig()->get('lock')) { $stabilityFlags = RootPackageLoader::extractStabilityFlags($requirements, $composer->getPackage()->getMinimumStability(), []); $locker->updateHash($this->json, static function (array $lockData) use ($stabilityFlags) { foreach ($stabilityFlags as $packageName => $flag) { $lockData['stability-flags'][$packageName] = $flag; } return $lockData; }); } } return 0; } private function updateFile(JsonFile $json, array $new, string $requireKey, string $removeKey, bool $sortPackages): void { if ($this->updateFileCleanly($json, $new, $requireKey, $removeKey, $sortPackages)) { return; } $composerDefinition = $this->json->read(); foreach ($new as $package => $version) { $composerDefinition[$requireKey][$package] = $version; unset($composerDefinition[$removeKey][$package]); if (isset($composerDefinition[$removeKey]) && count($composerDefinition[$removeKey]) === 0) { unset($composerDefinition[$removeKey]); } } $this->json->write($composerDefinition); } private function updateFileCleanly(JsonFile $json, array $new, string $requireKey, string $removeKey, bool $sortPackages): bool { $contents = file_get_contents($json->getPath()); $manipulator = new JsonManipulator($contents); foreach ($new as $package => $constraint) { if (!$manipulator->addLink($requireKey, $package, $constraint, $sortPackages)) { return false; } if (!$manipulator->removeSubNode($removeKey, $package)) { return false; } } $manipulator->removeMainKeyIfEmpty($removeKey); file_put_contents($json->getPath(), $manipulator->getContents()); return true; } protected function interact(InputInterface $input, OutputInterface $output): void { } private function revertComposerFile(): void { $io = $this->getIO(); if ($this->newlyCreated) { $io->writeError("\n".'Installation failed, deleting '.$this->file.'.'); unlink($this->json->getPath()); if (file_exists($this->lock)) { unlink($this->lock); } } else { $msg = ' to its '; if ($this->lockBackup) { $msg = ' and '.$this->lock.' to their '; } $io->writeError("\n".'Installation failed, reverting '.$this->file.$msg.'original content.'); file_put_contents($this->json->getPath(), $this->composerBackup); if ($this->lockBackup) { file_put_contents($this->lock, $this->lockBackup); } } } } setName('run-script') ->setAliases(['run']) ->setDescription('Runs the scripts defined in composer.json') ->setDefinition([ new InputArgument('script', InputArgument::OPTIONAL, 'Script name to run.', null, function () { return array_map(static function ($script) { return $script['name']; }, $this->getScripts()); }), new InputArgument('args', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, ''), new InputOption('timeout', null, InputOption::VALUE_REQUIRED, 'Sets script timeout in seconds, or 0 for never.'), new InputOption('dev', null, InputOption::VALUE_NONE, 'Sets the dev mode.'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables the dev mode.'), new InputOption('list', 'l', InputOption::VALUE_NONE, 'List scripts.'), ]) ->setHelp( <<run-script command runs scripts defined in composer.json: php composer.phar run-script post-update-cmd Read more at https://getcomposer.org/doc/03-cli.md#run-script-run EOT ) ; } protected function interact(InputInterface $input, OutputInterface $output): void { $scripts = $this->getScripts(); if (count($scripts) === 0) { return; } if ($input->getArgument('script') !== null || $input->getOption('list')) { return; } $options = []; foreach ($scripts as $script) { $options[$script['name']] = $script['description']; } $io = $this->getIO(); $script = $io->select( 'Script to run: ', $options, '', 1, 'Invalid script name "%s"' ); $input->setArgument('script', $script); } protected function execute(InputInterface $input, OutputInterface $output): int { if ($input->getOption('list')) { return $this->listScripts($output); } $script = $input->getArgument('script'); if ($script === null) { throw new \RuntimeException('Missing required argument "script"'); } if (!in_array($script, $this->scriptEvents)) { if (defined('Composer\Script\ScriptEvents::'.str_replace('-', '_', strtoupper($script)))) { throw new \InvalidArgumentException(sprintf('Script "%s" cannot be run with this command', $script)); } } $composer = $this->requireComposer(); $devMode = $input->getOption('dev') || !$input->getOption('no-dev'); $event = new ScriptEvent($script, $composer, $this->getIO(), $devMode); $hasListeners = $composer->getEventDispatcher()->hasEventListeners($event); if (!$hasListeners) { throw new \InvalidArgumentException(sprintf('Script "%s" is not defined in this package', $script)); } $args = $input->getArgument('args'); if (null !== $timeout = $input->getOption('timeout')) { if (!ctype_digit($timeout)) { throw new \RuntimeException('Timeout value must be numeric and positive if defined, or 0 for forever'); } ProcessExecutor::setTimeout((int) $timeout); } Platform::putEnv('COMPOSER_DEV_MODE', $devMode ? '1' : '0'); return $composer->getEventDispatcher()->dispatchScript($script, $devMode, $args); } protected function listScripts(OutputInterface $output): int { $scripts = $this->getScripts(); if (count($scripts) === 0) { return 0; } $io = $this->getIO(); $io->writeError('scripts:'); $table = []; foreach ($scripts as $script) { $table[] = [' '.$script['name'], $script['description']]; } $this->renderTable($table, $output); return 0; } private function getScripts(): array { $scripts = $this->requireComposer()->getPackage()->getScripts(); if (count($scripts) === 0) { return []; } $result = []; foreach ($scripts as $name => $script) { $description = ''; try { $cmd = $this->getApplication()->find($name); $description = $cmd->getDescription(); } catch (\Symfony\Component\Console\Exception\CommandNotFoundException $e) { } $result[] = ['name' => $name, 'description' => $description]; } return $result; } } script = $script; $this->description = $description ?? 'Runs the '.$script.' script as defined in composer.json'; $this->aliases = $aliases; foreach ($this->aliases as $alias) { if (!is_string($alias)) { throw new \InvalidArgumentException('"scripts-aliases" element array values should contain only strings'); } } $this->ignoreValidationErrors(); parent::__construct(); } protected function configure(): void { $this ->setName($this->script) ->setDescription($this->description) ->setAliases($this->aliases) ->setDefinition([ new InputOption('dev', null, InputOption::VALUE_NONE, 'Sets the dev mode.'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables the dev mode.'), new InputArgument('args', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, ''), ]) ->setHelp( <<run-script command runs scripts defined in composer.json: php composer.phar run-script post-update-cmd Read more at https://getcomposer.org/doc/03-cli.md#run-script-run EOT ) ; } protected function execute(InputInterface $input, OutputInterface $output): int { $composer = $this->requireComposer(); $args = $input->getArguments(); if (!method_exists($input, '__toString')) { throw new \LogicException('Expected an Input instance that is stringable, got '.get_class($input)); } $devMode = $input->getOption('dev') || !$input->getOption('no-dev'); Platform::putEnv('COMPOSER_DEV_MODE', $devMode ? '1' : '0'); return $composer->getEventDispatcher()->dispatchScript($this->script, $devMode, $args['args'], ['script-alias-input' => Preg::replace('{^\S+ ?}', '', $input->__toString(), 1)]); } } setName('search') ->setDescription('Searches for packages') ->setDefinition([ new InputOption('only-name', 'N', InputOption::VALUE_NONE, 'Search only in package names'), new InputOption('only-vendor', 'O', InputOption::VALUE_NONE, 'Search only for vendor / organization names, returns only "vendor" as result'), new InputOption('type', 't', InputOption::VALUE_REQUIRED, 'Search for a specific package type'), new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text', ['json', 'text']), new InputArgument('tokens', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'tokens to search for'), ]) ->setHelp( <<php composer.phar search symfony composer Read more at https://getcomposer.org/doc/03-cli.md#search EOT ) ; } protected function execute(InputInterface $input, OutputInterface $output): int { $platformRepo = new PlatformRepository; $io = $this->getIO(); $format = $input->getOption('format'); if (!in_array($format, ['text', 'json'])) { $io->writeError(sprintf('Unsupported format "%s". See help for supported formats.', $format)); return 1; } if (!($composer = $this->tryComposer())) { $composer = $this->createComposerInstance($input, $this->getIO(), []); } $localRepo = $composer->getRepositoryManager()->getLocalRepository(); $installedRepo = new CompositeRepository([$localRepo, $platformRepo]); $repos = new CompositeRepository(array_merge([$installedRepo], $composer->getRepositoryManager()->getRepositories())); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'search', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $mode = RepositoryInterface::SEARCH_FULLTEXT; if ($input->getOption('only-name') === true) { if ($input->getOption('only-vendor') === true) { throw new \InvalidArgumentException('--only-name and --only-vendor cannot be used together'); } $mode = RepositoryInterface::SEARCH_NAME; } elseif ($input->getOption('only-vendor') === true) { $mode = RepositoryInterface::SEARCH_VENDOR; } $type = $input->getOption('type'); $query = implode(' ', $input->getArgument('tokens')); if ($mode !== RepositoryInterface::SEARCH_FULLTEXT) { $query = preg_quote($query); } $results = $repos->search($query, $mode, $type, $io); if (\count($results) > 0 && $format === 'text') { $width = $this->getTerminalWidth(); $nameLength = 0; foreach ($results as $result) { $nameLength = max(strlen($result['name']), $nameLength); } $nameLength += 1; foreach ($results as $result) { $description = $result['description'] ?? ''; $warning = !empty($result['abandoned']) ? '! Abandoned ! ' : ''; $remaining = $width - $nameLength - strlen($warning) - 2; if (strlen($description) > $remaining) { $description = substr($description, 0, $remaining - 3) . '...'; } $link = $result['url'] ?? null; if ($link !== null) { $io->write(''.$result['name'].''. str_repeat(' ', $nameLength - strlen($result['name'])) . $warning . $description); } else { $io->write(str_pad($result['name'], $nameLength, ' ') . $warning . $description); } } } elseif ($format === 'json') { $io->write(JsonFile::encode($results)); } return 0; } } setName('self-update') ->setAliases(['selfupdate']) ->setDescription('Updates composer.phar to the latest version') ->setDefinition([ new InputOption('rollback', 'r', InputOption::VALUE_NONE, 'Revert to an older installation of composer'), new InputOption('clean-backups', null, InputOption::VALUE_NONE, 'Delete old backups during an update. This makes the current version of composer the only backup available after the update'), new InputArgument('version', InputArgument::OPTIONAL, 'The version to update to'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('update-keys', null, InputOption::VALUE_NONE, 'Prompt user for a key update'), new InputOption('stable', null, InputOption::VALUE_NONE, 'Force an update to the stable channel'), new InputOption('preview', null, InputOption::VALUE_NONE, 'Force an update to the preview channel'), new InputOption('snapshot', null, InputOption::VALUE_NONE, 'Force an update to the snapshot channel'), new InputOption('1', null, InputOption::VALUE_NONE, 'Force an update to the stable channel, but only use 1.x versions'), new InputOption('2', null, InputOption::VALUE_NONE, 'Force an update to the stable channel, but only use 2.x versions'), new InputOption('2.2', null, InputOption::VALUE_NONE, 'Force an update to the stable channel, but only use 2.2.x LTS versions'), new InputOption('set-channel-only', null, InputOption::VALUE_NONE, 'Only store the channel as the default one and then exit'), ]) ->setHelp( <<self-update command checks getcomposer.org for newer versions of composer and if found, installs the latest. php composer.phar self-update Read more at https://getcomposer.org/doc/03-cli.md#self-update-selfupdate EOT ) ; } protected function execute(InputInterface $input, OutputInterface $output): int { if (strpos(__FILE__, 'phar:') !== 0) { if (str_contains(strtr(__DIR__, '\\', '/'), 'vendor/composer/composer')) { $projDir = dirname(__DIR__, 6); $output->writeln('This instance of Composer does not have the self-update command.'); $output->writeln('You are running Composer installed as a package in your current project ("'.$projDir.'").'); $output->writeln('To update Composer, download a composer.phar from https://getcomposer.org and then run `composer.phar update composer/composer` in your project.'); } else { $output->writeln('This instance of Composer does not have the self-update command.'); $output->writeln('This could be due to a number of reasons, such as Composer being installed as a system package on your OS, or Composer being installed as a package in the current project.'); } return 1; } if ($_SERVER['argv'][0] === 'Standard input code') { return 1; } class_exists('Composer\Util\Platform'); class_exists('Composer\Downloader\FilesystemException'); class_exists('Composer\Console\GithubActionError'); $config = Factory::createConfig(); if ($config->get('disable-tls') === true) { $baseUrl = 'http://' . self::HOMEPAGE; } else { $baseUrl = 'https://' . self::HOMEPAGE; } $io = $this->getIO(); $httpDownloader = Factory::createHttpDownloader($io, $config); $versionsUtil = new Versions($config, $httpDownloader); $requestedChannel = null; foreach (Versions::CHANNELS as $channel) { if ($input->getOption($channel)) { $requestedChannel = $channel; $versionsUtil->setChannel($channel, $io); break; } } if ($input->getOption('set-channel-only')) { return 0; } $cacheDir = $config->get('cache-dir'); $rollbackDir = $config->get('data-dir'); $home = $config->get('home'); $localFilename = Phar::running(false); if ('' === $localFilename) { throw new \RuntimeException('Could not determine the location of the composer.phar file as it appears you are not running this code from a phar archive.'); } if ($input->getOption('update-keys')) { $this->fetchKeys($io, $config); return 0; } if (!file_exists($localFilename)) { throw new FilesystemException('Composer update failed: the "'.$localFilename.'" is not accessible'); } $tmpDir = is_writable(dirname($localFilename)) ? dirname($localFilename) : $cacheDir; if (!is_writable($tmpDir)) { throw new FilesystemException('Composer update failed: the "'.$tmpDir.'" directory used to download the temp file could not be written'); } if (function_exists('posix_getpwuid') && function_exists('posix_geteuid')) { $composerUser = posix_getpwuid(posix_geteuid()); $homeDirOwnerId = fileowner($home); if (is_array($composerUser) && $homeDirOwnerId !== false) { $homeOwner = posix_getpwuid($homeDirOwnerId); if (is_array($homeOwner) && $composerUser['name'] !== $homeOwner['name']) { $io->writeError('You are running Composer as "'.$composerUser['name'].'", while "'.$home.'" is owned by "'.$homeOwner['name'].'"'); } } } if ($input->getOption('rollback')) { return $this->rollback($output, $rollbackDir, $localFilename); } if ($input->getArgument('command') === 'self' && $input->getArgument('version') === 'update') { $input->setArgument('version', null); } $latest = $versionsUtil->getLatest(); $latestStable = $versionsUtil->getLatest('stable'); try { $latestPreview = $versionsUtil->getLatest('preview'); } catch (\UnexpectedValueException $e) { $latestPreview = $latestStable; } $latestVersion = $latest['version']; $updateVersion = $input->getArgument('version') ?? $latestVersion; $currentMajorVersion = Preg::replace('{^(\d+).*}', '$1', Composer::getVersion()); $updateMajorVersion = Preg::replace('{^(\d+).*}', '$1', $updateVersion); $previewMajorVersion = Preg::replace('{^(\d+).*}', '$1', $latestPreview['version']); if ($versionsUtil->getChannel() === 'stable' && null === $input->getArgument('version')) { if ($currentMajorVersion < $updateMajorVersion) { $skippedVersion = $updateVersion; $versionsUtil->setChannel($currentMajorVersion); $latest = $versionsUtil->getLatest(); $latestStable = $versionsUtil->getLatest('stable'); $latestVersion = $latest['version']; $updateVersion = $latestVersion; $io->writeError('A new stable major version of Composer is available ('.$skippedVersion.'), run "composer self-update --'.$updateMajorVersion.'" to update to it. See also https://getcomposer.org/'.$updateMajorVersion.''); } elseif ($currentMajorVersion < $previewMajorVersion) { $io->writeError('A preview release of the next major version of Composer is available ('.$latestPreview['version'].'), run "composer self-update --preview" to give it a try. See also https://github.com/composer/composer/releases for changelogs.'); } } $effectiveChannel = $requestedChannel === null ? $versionsUtil->getChannel() : $requestedChannel; if (is_numeric($effectiveChannel) && strpos($latestStable['version'], $effectiveChannel) !== 0) { $io->writeError('Warning: You forced the install of '.$latestVersion.' via --'.$effectiveChannel.', but '.$latestStable['version'].' is the latest stable version. Updating to it via composer self-update --stable is recommended.'); } if (isset($latest['eol'])) { $io->writeError('Warning: Version '.$latestVersion.' is EOL / End of Life. '.$latestStable['version'].' is the latest stable version. Updating to it via composer self-update --stable is recommended.'); } if (Preg::isMatch('{^[0-9a-f]{40}$}', $updateVersion) && $updateVersion !== $latestVersion) { $io->writeError('You can not update to a specific SHA-1 as those phars are not available for download'); return 1; } $channelString = $versionsUtil->getChannel(); if (is_numeric($channelString)) { $channelString .= '.x'; } if (Composer::VERSION === $updateVersion) { $io->writeError( sprintf( 'You are already using the latest available Composer version %s (%s channel).', $updateVersion, $channelString ) ); if ($input->getOption('clean-backups')) { $this->cleanBackups($rollbackDir, $this->getLastBackupVersion($rollbackDir)); } return 0; } $tempFilename = $tmpDir . '/' . basename($localFilename, '.phar').'-temp'.random_int(0, 10000000).'.phar'; $backupFile = sprintf( '%s/%s-%s%s', $rollbackDir, strtr(Composer::RELEASE_DATE, ' :', '_-'), Preg::replace('{^([0-9a-f]{7})[0-9a-f]{33}$}', '$1', Composer::VERSION), self::OLD_INSTALL_EXT ); $updatingToTag = !Preg::isMatch('{^[0-9a-f]{40}$}', $updateVersion); $io->write(sprintf("Upgrading to version %s (%s channel).", $updateVersion, $channelString)); $remoteFilename = $baseUrl . ($updatingToTag ? "/download/{$updateVersion}/composer.phar" : '/composer.phar'); try { $signature = $httpDownloader->get($remoteFilename.'.sig')->getBody(); } catch (TransportException $e) { if ($e->getStatusCode() === 404) { throw new \InvalidArgumentException('Version "'.$updateVersion.'" could not be found.', 0, $e); } throw $e; } $io->writeError(' ', false); $httpDownloader->copy($remoteFilename, $tempFilename); $io->writeError(''); if (!file_exists($tempFilename) || null === $signature || '' === $signature) { $io->writeError('The download of the new composer version failed for an unexpected reason'); return 1; } if (!extension_loaded('openssl') && $config->get('disable-tls')) { $io->writeError('Skipping phar signature verification as you have disabled OpenSSL via config.disable-tls'); } else { if (!extension_loaded('openssl')) { throw new \RuntimeException('The openssl extension is required for phar signatures to be verified but it is not available. ' . 'If you can not enable the openssl extension, you can disable this error, at your own risk, by setting the \'disable-tls\' option to true.'); } $sigFile = 'file://'.$home.'/' . ($updatingToTag ? 'keys.tags.pub' : 'keys.dev.pub'); if (!file_exists($sigFile)) { file_put_contents( $home.'/keys.dev.pub', <<getOption('clean-backups')) { $this->cleanBackups($rollbackDir); } if (!$this->setLocalPhar($localFilename, $tempFilename, $backupFile)) { @unlink($tempFilename); return 1; } if (file_exists($backupFile)) { $io->writeError(sprintf( 'Use composer self-update --rollback to return to version %s', Composer::VERSION )); } else { $io->writeError('A backup of the current version could not be written to '.$backupFile.', no rollback possible'); } return 0; } protected function fetchKeys(IOInterface $io, Config $config): void { if (!$io->isInteractive()) { throw new \RuntimeException('Public keys can not be fetched in non-interactive mode, please run Composer interactively'); } $io->write('Open https://composer.github.io/pubkeys.html to find the latest keys'); $validator = static function ($value): string { $value = (string) $value; if (!Preg::isMatch('{^-----BEGIN PUBLIC KEY-----$}', trim($value))) { throw new \UnexpectedValueException('Invalid input'); } return trim($value)."\n"; }; $devKey = ''; while (!Preg::isMatch('{(-----BEGIN PUBLIC KEY-----.+?-----END PUBLIC KEY-----)}s', $devKey, $match)) { $devKey = $io->askAndValidate('Enter Dev / Snapshot Public Key (including lines with -----): ', $validator); while ($line = $io->ask('', '')) { $devKey .= trim($line)."\n"; if (trim($line) === '-----END PUBLIC KEY-----') { break; } } } file_put_contents($keyPath = $config->get('home').'/keys.dev.pub', $match[0]); $io->write('Stored key with fingerprint: ' . Keys::fingerprint($keyPath)); $tagsKey = ''; while (!Preg::isMatch('{(-----BEGIN PUBLIC KEY-----.+?-----END PUBLIC KEY-----)}s', $tagsKey, $match)) { $tagsKey = $io->askAndValidate('Enter Tags Public Key (including lines with -----): ', $validator); while ($line = $io->ask('', '')) { $tagsKey .= trim($line)."\n"; if (trim($line) === '-----END PUBLIC KEY-----') { break; } } } file_put_contents($keyPath = $config->get('home').'/keys.tags.pub', $match[0]); $io->write('Stored key with fingerprint: ' . Keys::fingerprint($keyPath)); $io->write('Public keys stored in '.$config->get('home')); } protected function rollback(OutputInterface $output, string $rollbackDir, string $localFilename): int { $rollbackVersion = $this->getLastBackupVersion($rollbackDir); if (null === $rollbackVersion) { throw new \UnexpectedValueException('Composer rollback failed: no installation to roll back to in "'.$rollbackDir.'"'); } $oldFile = $rollbackDir . '/' . $rollbackVersion . self::OLD_INSTALL_EXT; if (!is_file($oldFile)) { throw new FilesystemException('Composer rollback failed: "'.$oldFile.'" could not be found'); } if (!Filesystem::isReadable($oldFile)) { throw new FilesystemException('Composer rollback failed: "'.$oldFile.'" could not be read'); } $io = $this->getIO(); $io->writeError(sprintf("Rolling back to version %s.", $rollbackVersion)); if (!$this->setLocalPhar($localFilename, $oldFile)) { return 1; } return 0; } protected function setLocalPhar(string $localFilename, string $newFilename, ?string $backupTarget = null): bool { $io = $this->getIO(); $perms = @fileperms($localFilename); if ($perms !== false) { @chmod($newFilename, $perms); } if (!$this->validatePhar($newFilename, $error)) { $io->writeError('The '.($backupTarget !== null ? 'update' : 'backup').' file is corrupted ('.$error.')'); if ($backupTarget !== null) { $io->writeError('Please re-run the self-update command to try again.'); } return false; } if ($backupTarget !== null) { @copy($localFilename, $backupTarget); } try { if (Platform::isWindows()) { copy($newFilename, $localFilename); @unlink($newFilename); } else { rename($newFilename, $localFilename); } return true; } catch (\Exception $e) { if (!is_writable(dirname($localFilename)) && $io->isInteractive() && $this->isWindowsNonAdminUser()) { return $this->tryAsWindowsAdmin($localFilename, $newFilename); } @unlink($newFilename); $action = 'Composer '.($backupTarget !== null ? 'update' : 'rollback'); throw new FilesystemException($action.' failed: "'.$localFilename.'" could not be written.'.PHP_EOL.$e->getMessage()); } } protected function cleanBackups(string $rollbackDir, ?string $except = null): void { $finder = $this->getOldInstallationFinder($rollbackDir); $io = $this->getIO(); $fs = new Filesystem; foreach ($finder as $file) { if ($file->getBasename(self::OLD_INSTALL_EXT) === $except) { continue; } $file = (string) $file; $io->writeError('Removing: '.$file.''); $fs->remove($file); } } protected function getLastBackupVersion(string $rollbackDir): ?string { $finder = $this->getOldInstallationFinder($rollbackDir); $finder->sortByName(); $files = iterator_to_array($finder); if (count($files) > 0) { return end($files)->getBasename(self::OLD_INSTALL_EXT); } return null; } protected function getOldInstallationFinder(string $rollbackDir): Finder { return Finder::create() ->depth(0) ->files() ->name('*' . self::OLD_INSTALL_EXT) ->in($rollbackDir); } protected function validatePhar(string $pharFile, ?string &$error): bool { if ((bool) ini_get('phar.readonly')) { return true; } try { $phar = new Phar($pharFile); unset($phar); $result = true; } catch (\Exception $e) { if (!$e instanceof \UnexpectedValueException && !$e instanceof \PharException) { throw $e; } $error = $e->getMessage(); $result = false; } return $result; } protected function isWindowsNonAdminUser(): bool { if (!Platform::isWindows()) { return false; } exec('fltmc.exe filters', $output, $exitCode); return $exitCode !== 0; } protected function tryAsWindowsAdmin(string $localFilename, string $newFilename): bool { $io = $this->getIO(); $io->writeError('Unable to write "'.$localFilename.'". Access is denied.'); $helpMessage = 'Please run the self-update command as an Administrator.'; $question = 'Complete this operation with Administrator privileges [Y,n]? '; if (!$io->askConfirmation($question, true)) { $io->writeError('Operation cancelled. '.$helpMessage.''); return false; } $tmpFile = tempnam(sys_get_temp_dir(), ''); if (false === $tmpFile) { $io->writeError('Operation failed. '.$helpMessage.''); return false; } exec('sudo config 2> NUL', $output, $exitCode); $usingSudo = $exitCode === 0; $script = $usingSudo ? $tmpFile.'.bat' : $tmpFile.'.vbs'; rename($tmpFile, $script); $checksum = hash_file('sha256', $newFilename); $source = str_replace('/', '\\', $newFilename); $destination = str_replace('/', '\\', $localFilename); if ($usingSudo) { $code = sprintf('move "%s" "%s"', $source, $destination); } else { $code = <<writeError('Operation succeeded.'); } else { $io->writeError('Operation failed. '.$helpMessage.''); } return $result; } } setName('show') ->setAliases(['info']) ->setDescription('Shows information about packages') ->setDefinition([ new InputArgument('package', InputArgument::OPTIONAL, 'Package to inspect. Or a name including a wildcard (*) to filter lists of packages instead.', null, $this->suggestPackageBasedOnMode()), new InputArgument('version', InputArgument::OPTIONAL, 'Version or version constraint to inspect'), new InputOption('all', null, InputOption::VALUE_NONE, 'List all packages'), new InputOption('locked', null, InputOption::VALUE_NONE, 'List all locked packages'), new InputOption('installed', 'i', InputOption::VALUE_NONE, 'List installed packages only (enabled by default, only present for BC).'), new InputOption('platform', 'p', InputOption::VALUE_NONE, 'List platform packages only'), new InputOption('available', 'a', InputOption::VALUE_NONE, 'List available packages only'), new InputOption('self', 's', InputOption::VALUE_NONE, 'Show the root package information'), new InputOption('name-only', 'N', InputOption::VALUE_NONE, 'List package names only'), new InputOption('path', 'P', InputOption::VALUE_NONE, 'Show package paths'), new InputOption('tree', 't', InputOption::VALUE_NONE, 'List the dependencies as a tree'), new InputOption('latest', 'l', InputOption::VALUE_NONE, 'Show the latest version'), new InputOption('outdated', 'o', InputOption::VALUE_NONE, 'Show the latest version but only for packages that are outdated'), new InputOption('ignore', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore specified package(s). Can contain wildcards (*). Use it with the --outdated option if you don\'t want to be informed about new versions of some packages.', null, $this->suggestInstalledPackage(false)), new InputOption('major-only', 'M', InputOption::VALUE_NONE, 'Show only packages that have major SemVer-compatible updates. Use with the --latest or --outdated option.'), new InputOption('minor-only', 'm', InputOption::VALUE_NONE, 'Show only packages that have minor SemVer-compatible updates. Use with the --latest or --outdated option.'), new InputOption('patch-only', null, InputOption::VALUE_NONE, 'Show only packages that have patch SemVer-compatible updates. Use with the --latest or --outdated option.'), new InputOption('sort-by-age', 'A', InputOption::VALUE_NONE, 'Displays the installed version\'s age, and sorts packages oldest first. Use with the --latest or --outdated option.'), new InputOption('direct', 'D', InputOption::VALUE_NONE, 'Shows only packages that are directly required by the root package'), new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code when there are outdated packages'), new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text', ['json', 'text']), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables search in require-dev packages.'), new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages). Use with the --outdated option'), new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages). Use with the --outdated option'), ]) ->setHelp( <<getOption('available') || $input->getOption('all')) { return $this->suggestAvailablePackageInclPlatform()($input); } if ($input->getOption('platform')) { return $this->suggestPlatformPackage()($input); } return $this->suggestInstalledPackage(false)($input); }; } protected function execute(InputInterface $input, OutputInterface $output): int { $this->versionParser = new VersionParser; if ($input->getOption('tree')) { $this->initStyles($output); } $composer = $this->tryComposer(); $io = $this->getIO(); if ($input->getOption('installed') && !$input->getOption('self')) { $io->writeError('You are using the deprecated option "installed". Only installed packages are shown by default now. The --all option can be used to show all packages.'); } if ($input->getOption('outdated')) { $input->setOption('latest', true); } elseif (count($input->getOption('ignore')) > 0) { $io->writeError('You are using the option "ignore" for action other than "outdated", it will be ignored.'); } if ($input->getOption('direct') && ($input->getOption('all') || $input->getOption('available') || $input->getOption('platform'))) { $io->writeError('The --direct (-D) option is not usable in combination with --all, --platform (-p) or --available (-a)'); return 1; } if ($input->getOption('tree') && ($input->getOption('all') || $input->getOption('available'))) { $io->writeError('The --tree (-t) option is not usable in combination with --all or --available (-a)'); return 1; } if (count(array_filter([$input->getOption('patch-only'), $input->getOption('minor-only'), $input->getOption('major-only')])) > 1) { $io->writeError('Only one of --major-only, --minor-only or --patch-only can be used at once'); return 1; } if ($input->getOption('tree') && $input->getOption('latest')) { $io->writeError('The --tree (-t) option is not usable in combination with --latest (-l)'); return 1; } if ($input->getOption('tree') && $input->getOption('path')) { $io->writeError('The --tree (-t) option is not usable in combination with --path (-P)'); return 1; } $format = $input->getOption('format'); if (!in_array($format, ['text', 'json'])) { $io->writeError(sprintf('Unsupported format "%s". See help for supported formats.', $format)); return 1; } $platformReqFilter = $this->getPlatformRequirementFilter($input); $platformOverrides = []; if ($composer) { $platformOverrides = $composer->getConfig()->get('platform'); } $platformRepo = new PlatformRepository([], $platformOverrides); $lockedRepo = null; if ($input->getOption('self') && !$input->getOption('installed') && !$input->getOption('locked')) { $package = clone $this->requireComposer()->getPackage(); if ($input->getOption('name-only')) { $io->write($package->getName()); return 0; } if ($input->getArgument('package')) { throw new \InvalidArgumentException('You cannot use --self together with a package name'); } $repos = $installedRepo = new InstalledRepository([new RootPackageRepository($package)]); } elseif ($input->getOption('platform')) { $repos = $installedRepo = new InstalledRepository([$platformRepo]); } elseif ($input->getOption('available')) { $installedRepo = new InstalledRepository([$platformRepo]); if ($composer) { $repos = new CompositeRepository($composer->getRepositoryManager()->getRepositories()); $installedRepo->addRepository($composer->getRepositoryManager()->getLocalRepository()); } else { $defaultRepos = RepositoryFactory::defaultReposWithDefaultManager($io); $repos = new CompositeRepository($defaultRepos); $io->writeError('No composer.json found in the current directory, showing available packages from ' . implode(', ', array_keys($defaultRepos))); } } elseif ($input->getOption('all') && $composer) { $localRepo = $composer->getRepositoryManager()->getLocalRepository(); $locker = $composer->getLocker(); if ($locker->isLocked()) { $lockedRepo = $locker->getLockedRepository(true); $installedRepo = new InstalledRepository([$lockedRepo, $localRepo, $platformRepo]); } else { $installedRepo = new InstalledRepository([$localRepo, $platformRepo]); } $repos = new CompositeRepository(array_merge([new FilterRepository($installedRepo, ['canonical' => false])], $composer->getRepositoryManager()->getRepositories())); } elseif ($input->getOption('all')) { $defaultRepos = RepositoryFactory::defaultReposWithDefaultManager($io); $io->writeError('No composer.json found in the current directory, showing available packages from ' . implode(', ', array_keys($defaultRepos))); $installedRepo = new InstalledRepository([$platformRepo]); $repos = new CompositeRepository(array_merge([$installedRepo], $defaultRepos)); } elseif ($input->getOption('locked')) { if (!$composer || !$composer->getLocker()->isLocked()) { throw new \UnexpectedValueException('A valid composer.json and composer.lock files is required to run this command with --locked'); } $locker = $composer->getLocker(); $lockedRepo = $locker->getLockedRepository(!$input->getOption('no-dev')); if ($input->getOption('self')) { $lockedRepo->addPackage(clone $composer->getPackage()); } $repos = $installedRepo = new InstalledRepository([$lockedRepo]); } else { if (!$composer) { $composer = $this->requireComposer(); } $rootPkg = $composer->getPackage(); $rootRepo = new InstalledArrayRepository(); if ($input->getOption('self')) { $rootRepo = new RootPackageRepository(clone $rootPkg); } if ($input->getOption('no-dev')) { $packages = RepositoryUtils::filterRequiredPackages($composer->getRepositoryManager()->getLocalRepository()->getPackages(), $rootPkg); $repos = $installedRepo = new InstalledRepository([$rootRepo, new InstalledArrayRepository(array_map(static function ($pkg): PackageInterface { return clone $pkg; }, $packages))]); } else { $repos = $installedRepo = new InstalledRepository([$rootRepo, $composer->getRepositoryManager()->getLocalRepository()]); } if (!$installedRepo->getPackages()) { $hasNonPlatformReqs = static function (array $reqs): bool { return (bool) array_filter(array_keys($reqs), static function (string $name) { return !PlatformRepository::isPlatformPackage($name); }); }; if ($hasNonPlatformReqs($rootPkg->getRequires()) || $hasNonPlatformReqs($rootPkg->getDevRequires())) { $io->writeError('No dependencies installed. Try running composer install or update.'); } } } if ($composer) { $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'show', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); } if ($input->getOption('latest') && null === $composer) { $io->writeError('No composer.json found in the current directory, disabling "latest" option'); $input->setOption('latest', false); } $packageFilter = $input->getArgument('package'); if (isset($package)) { $versions = [$package->getPrettyVersion() => $package->getVersion()]; } elseif (null !== $packageFilter && !str_contains($packageFilter, '*')) { [$package, $versions] = $this->getPackage($installedRepo, $repos, $packageFilter, $input->getArgument('version')); if (isset($package) && $input->getOption('direct')) { if (!in_array($package->getName(), $this->getRootRequires(), true)) { throw new \InvalidArgumentException('Package "' . $package->getName() . '" is installed but not a direct dependent of the root package.'); } } if (!isset($package)) { $options = $input->getOptions(); $hint = ''; if ($input->getOption('locked')) { $hint .= ' in lock file'; } if (isset($options['working-dir'])) { $hint .= ' in ' . $options['working-dir'] . '/composer.json'; } if (PlatformRepository::isPlatformPackage($packageFilter) && !$input->getOption('platform')) { $hint .= ', try using --platform (-p) to show platform packages'; } if (!$input->getOption('all') && !$input->getOption('available')) { $hint .= ', try using --available (-a) to show all available packages'; } throw new \InvalidArgumentException('Package "' . $packageFilter . '" not found'.$hint.'.'); } } if (isset($package)) { assert(isset($versions)); $exitCode = 0; if ($input->getOption('tree')) { $arrayTree = $this->generatePackageTree($package, $installedRepo, $repos); if ('json' === $format) { $io->write(JsonFile::encode(['installed' => [$arrayTree]])); } else { $this->displayPackageTree([$arrayTree]); } return $exitCode; } $latestPackage = null; if ($input->getOption('latest')) { $latestPackage = $this->findLatestPackage($package, $composer, $platformRepo, $input->getOption('major-only'), $input->getOption('minor-only'), $input->getOption('patch-only'), $platformReqFilter); } if ( $input->getOption('outdated') && $input->getOption('strict') && null !== $latestPackage && $latestPackage->getFullPrettyVersion() !== $package->getFullPrettyVersion() && (!$latestPackage instanceof CompletePackageInterface || !$latestPackage->isAbandoned()) ) { $exitCode = 1; } if ($input->getOption('path')) { $io->write($package->getName(), false); $path = $composer->getInstallationManager()->getInstallPath($package); if (is_string($path)) { $io->write(' ' . strtok(realpath($path), "\r\n")); } else { $io->write(' null'); } return $exitCode; } if ('json' === $format) { $this->printPackageInfoAsJson($package, $versions, $installedRepo, $latestPackage ?: null); } else { $this->printPackageInfo($package, $versions, $installedRepo, $latestPackage ?: null); } return $exitCode; } if ($input->getOption('tree')) { $rootRequires = $this->getRootRequires(); $packages = $installedRepo->getPackages(); usort($packages, static function (BasePackage $a, BasePackage $b): int { return strcmp((string) $a, (string) $b); }); $arrayTree = []; foreach ($packages as $package) { if (in_array($package->getName(), $rootRequires, true)) { $arrayTree[] = $this->generatePackageTree($package, $installedRepo, $repos); } } if ('json' === $format) { $io->write(JsonFile::encode(['installed' => $arrayTree])); } else { $this->displayPackageTree($arrayTree); } return 0; } $packages = []; $packageFilterRegex = null; if (null !== $packageFilter) { $packageFilterRegex = '{^'.str_replace('\\*', '.*?', preg_quote($packageFilter)).'$}i'; } $packageListFilter = null; if ($input->getOption('direct')) { $packageListFilter = $this->getRootRequires(); } if ($input->getOption('path') && null === $composer) { $io->writeError('No composer.json found in the current directory, disabling "path" option'); $input->setOption('path', false); } foreach (RepositoryUtils::flattenRepositories($repos) as $repo) { if ($repo === $platformRepo) { $type = 'platform'; } elseif ($lockedRepo !== null && $repo === $lockedRepo) { $type = 'locked'; } elseif ($repo === $installedRepo || in_array($repo, $installedRepo->getRepositories(), true)) { $type = 'installed'; } else { $type = 'available'; } if ($repo instanceof ComposerRepository) { foreach ($repo->getPackageNames($packageFilter) as $name) { $packages[$type][$name] = $name; } } else { foreach ($repo->getPackages() as $package) { if (!isset($packages[$type][$package->getName()]) || !is_object($packages[$type][$package->getName()]) || version_compare($packages[$type][$package->getName()]->getVersion(), $package->getVersion(), '<') ) { while ($package instanceof AliasPackage) { $package = $package->getAliasOf(); } if (!$packageFilterRegex || Preg::isMatch($packageFilterRegex, $package->getName())) { if (null === $packageListFilter || in_array($package->getName(), $packageListFilter, true)) { $packages[$type][$package->getName()] = $package; } } } } if ($repo === $platformRepo) { foreach ($platformRepo->getDisabledPackages() as $name => $package) { $packages[$type][$name] = $package; } } } } $showAllTypes = $input->getOption('all'); $showLatest = $input->getOption('latest'); $showMajorOnly = $input->getOption('major-only'); $showMinorOnly = $input->getOption('minor-only'); $showPatchOnly = $input->getOption('patch-only'); $ignoredPackagesRegex = BasePackage::packageNamesToRegexp(array_map('strtolower', $input->getOption('ignore'))); $indent = $showAllTypes ? ' ' : ''; $latestPackages = []; $exitCode = 0; $viewData = []; $viewMetaData = []; $writeVersion = false; $writeDescription = false; foreach (['platform' => true, 'locked' => true, 'available' => false, 'installed' => true] as $type => $showVersion) { if (isset($packages[$type])) { ksort($packages[$type]); $nameLength = $versionLength = $latestLength = $releaseDateLength = 0; if ($showLatest && $showVersion) { foreach ($packages[$type] as $package) { if (is_object($package) && !Preg::isMatch($ignoredPackagesRegex, $package->getPrettyName())) { $latestPackage = $this->findLatestPackage($package, $composer, $platformRepo, $showMajorOnly, $showMinorOnly, $showPatchOnly, $platformReqFilter); if ($latestPackage === null) { continue; } $latestPackages[$package->getPrettyName()] = $latestPackage; } } } $writePath = !$input->getOption('name-only') && $input->getOption('path'); $writeVersion = !$input->getOption('name-only') && !$input->getOption('path') && $showVersion; $writeLatest = $writeVersion && $showLatest; $writeDescription = !$input->getOption('name-only') && !$input->getOption('path'); $writeReleaseDate = $writeLatest && ($input->getOption('sort-by-age') || $format === 'json'); $hasOutdatedPackages = false; if ($input->getOption('sort-by-age')) { usort($packages[$type], static function ($a, $b) { if (is_object($a) && is_object($b)) { return $a->getReleaseDate() <=> $b->getReleaseDate(); } return 0; }); } $viewData[$type] = []; foreach ($packages[$type] as $package) { $packageViewData = []; if (is_object($package)) { $latestPackage = null; if ($showLatest && isset($latestPackages[$package->getPrettyName()])) { $latestPackage = $latestPackages[$package->getPrettyName()]; } $packageIsUpToDate = $latestPackage && $latestPackage->getFullPrettyVersion() === $package->getFullPrettyVersion() && (!$latestPackage instanceof CompletePackageInterface || !$latestPackage->isAbandoned()); $packageIsUpToDate = $packageIsUpToDate || ($latestPackage === null && $showMajorOnly); $packageIsIgnored = Preg::isMatch($ignoredPackagesRegex, $package->getPrettyName()); if ($input->getOption('outdated') && ($packageIsUpToDate || $packageIsIgnored)) { continue; } if ($input->getOption('outdated') || $input->getOption('strict')) { $hasOutdatedPackages = true; } $packageViewData['name'] = $package->getPrettyName(); $packageViewData['direct-dependency'] = in_array($package->getName(), $this->getRootRequires(), true); if ($format !== 'json' || true !== $input->getOption('name-only')) { $packageViewData['homepage'] = $package instanceof CompletePackageInterface ? $package->getHomepage() : null; $packageViewData['source'] = PackageInfo::getViewSourceUrl($package); } $nameLength = max($nameLength, strlen($packageViewData['name'])); if ($writeVersion) { $packageViewData['version'] = $package->getFullPrettyVersion(); if ($format === 'text') { $packageViewData['version'] = ltrim($packageViewData['version'], 'v'); } $versionLength = max($versionLength, strlen($packageViewData['version'])); } if ($writeReleaseDate) { if ($package->getReleaseDate() !== null) { $packageViewData['release-age'] = str_replace(' ago', ' old', $this->getRelativeTime($package->getReleaseDate())); if (!str_contains($packageViewData['release-age'], ' old')) { $packageViewData['release-age'] = 'from '.$packageViewData['release-age']; } $releaseDateLength = max($releaseDateLength, strlen($packageViewData['release-age'])); $packageViewData['release-date'] = $package->getReleaseDate()->format(DateTimeInterface::ATOM); } else { $packageViewData['release-age'] = ''; $packageViewData['release-date'] = ''; } } if ($writeLatest && $latestPackage) { $packageViewData['latest'] = $latestPackage->getFullPrettyVersion(); if ($format === 'text') { $packageViewData['latest'] = ltrim($packageViewData['latest'], 'v'); } $packageViewData['latest-status'] = $this->getUpdateStatus($latestPackage, $package); $latestLength = max($latestLength, strlen($packageViewData['latest'])); if ($latestPackage->getReleaseDate() !== null) { $packageViewData['latest-release-date'] = $latestPackage->getReleaseDate()->format(DateTimeInterface::ATOM); } else { $packageViewData['latest-release-date'] = ''; } } elseif ($writeLatest) { $packageViewData['latest'] = '[none matched]'; $packageViewData['latest-status'] = 'up-to-date'; $latestLength = max($latestLength, strlen($packageViewData['latest'])); } if ($writeDescription && $package instanceof CompletePackageInterface) { $packageViewData['description'] = $package->getDescription(); } if ($writePath) { $path = $composer->getInstallationManager()->getInstallPath($package); if (is_string($path)) { $packageViewData['path'] = strtok(realpath($path), "\r\n"); } else { $packageViewData['path'] = null; } } $packageIsAbandoned = false; if ($latestPackage instanceof CompletePackageInterface && $latestPackage->isAbandoned()) { $replacementPackageName = $latestPackage->getReplacementPackage(); $replacement = $replacementPackageName !== null ? 'Use ' . $latestPackage->getReplacementPackage() . ' instead' : 'No replacement was suggested'; $packageWarning = sprintf( 'Package %s is abandoned, you should avoid using it. %s.', $package->getPrettyName(), $replacement ); $packageViewData['warning'] = $packageWarning; $packageIsAbandoned = $replacementPackageName ?? true; } $packageViewData['abandoned'] = $packageIsAbandoned; } else { $packageViewData['name'] = $package; $nameLength = max($nameLength, strlen($package)); } $viewData[$type][] = $packageViewData; } $viewMetaData[$type] = [ 'nameLength' => $nameLength, 'versionLength' => $versionLength, 'latestLength' => $latestLength, 'releaseDateLength' => $releaseDateLength, 'writeLatest' => $writeLatest, 'writeReleaseDate' => $writeReleaseDate, ]; if ($input->getOption('strict') && $hasOutdatedPackages) { $exitCode = 1; break; } } } if ('json' === $format) { $io->write(JsonFile::encode($viewData)); } else { if ($input->getOption('latest') && array_filter($viewData)) { if (!$io->isDecorated()) { $io->writeError('Legend:'); $io->writeError('! patch or minor release available - update recommended'); $io->writeError('~ major release available - update possible'); if (!$input->getOption('outdated')) { $io->writeError('= up to date version'); } } else { $io->writeError('Color legend:'); $io->writeError('- patch or minor release available - update recommended'); $io->writeError('- major release available - update possible'); if (!$input->getOption('outdated')) { $io->writeError('- up to date version'); } } } $width = $this->getTerminalWidth(); foreach ($viewData as $type => $packages) { $nameLength = $viewMetaData[$type]['nameLength']; $versionLength = $viewMetaData[$type]['versionLength']; $latestLength = $viewMetaData[$type]['latestLength']; $releaseDateLength = $viewMetaData[$type]['releaseDateLength']; $writeLatest = $viewMetaData[$type]['writeLatest']; $writeReleaseDate = $viewMetaData[$type]['writeReleaseDate']; $versionFits = $nameLength + $versionLength + 3 <= $width; $latestFits = $nameLength + $versionLength + $latestLength + 3 <= $width; $releaseDateFits = $nameLength + $versionLength + $latestLength + $releaseDateLength + 3 <= $width; $descriptionFits = $nameLength + $versionLength + $latestLength + $releaseDateLength + 24 <= $width; if ($latestFits && !$io->isDecorated()) { $latestLength += 2; } if ($showAllTypes) { if ('available' === $type) { $io->write('' . $type . ':'); } else { $io->write('' . $type . ':'); } } if ($writeLatest && !$input->getOption('direct')) { $directDeps = []; $transitiveDeps = []; foreach ($packages as $pkg) { if ($pkg['direct-dependency'] ?? false) { $directDeps[] = $pkg; } else { $transitiveDeps[] = $pkg; } } $io->writeError(''); $io->writeError('Direct dependencies required in composer.json:'); if (\count($directDeps) > 0) { $this->printPackages($io, $directDeps, $indent, $writeVersion && $versionFits, $latestFits, $writeDescription && $descriptionFits, $width, $versionLength, $nameLength, $latestLength, $writeReleaseDate && $releaseDateFits, $releaseDateLength); } else { $io->writeError('Everything up to date'); } $io->writeError(''); $io->writeError('Transitive dependencies not required in composer.json:'); if (\count($transitiveDeps) > 0) { $this->printPackages($io, $transitiveDeps, $indent, $writeVersion && $versionFits, $latestFits, $writeDescription && $descriptionFits, $width, $versionLength, $nameLength, $latestLength, $writeReleaseDate && $releaseDateFits, $releaseDateLength); } else { $io->writeError('Everything up to date'); } } else { if ($writeLatest && \count($packages) === 0) { $io->writeError('All your direct dependencies are up to date'); } else { $this->printPackages($io, $packages, $indent, $writeVersion && $versionFits, $writeLatest && $latestFits, $writeDescription && $descriptionFits, $width, $versionLength, $nameLength, $latestLength, $writeReleaseDate && $releaseDateFits, $releaseDateLength); } } if ($showAllTypes) { $io->write(''); } } } return $exitCode; } private function printPackages(IOInterface $io, array $packages, string $indent, bool $writeVersion, bool $writeLatest, bool $writeDescription, int $width, int $versionLength, int $nameLength, int $latestLength, bool $writeReleaseDate, int $releaseDateLength): void { $padName = $writeVersion || $writeLatest || $writeReleaseDate || $writeDescription; $padVersion = $writeLatest || $writeReleaseDate || $writeDescription; $padLatest = $writeDescription || $writeReleaseDate; $padReleaseDate = $writeDescription; foreach ($packages as $package) { $link = $package['source'] ?? $package['homepage'] ?? ''; if ($link !== '') { $io->write($indent . ''.$package['name'].''. str_repeat(' ', ($padName ? $nameLength - strlen($package['name']) : 0)), false); } else { $io->write($indent . str_pad($package['name'], ($padName ? $nameLength : 0), ' '), false); } if (isset($package['version']) && $writeVersion) { $io->write(' ' . str_pad($package['version'], ($padVersion ? $versionLength : 0), ' '), false); } if (isset($package['latest']) && isset($package['latest-status']) && $writeLatest) { $latestVersion = $package['latest']; $updateStatus = $package['latest-status']; $style = $this->updateStatusToVersionStyle($updateStatus); if (!$io->isDecorated()) { $latestVersion = str_replace(['up-to-date', 'semver-safe-update', 'update-possible'], ['=', '!', '~'], $updateStatus) . ' ' . $latestVersion; } $io->write(' <' . $style . '>' . str_pad($latestVersion, ($padLatest ? $latestLength : 0), ' ') . '', false); if ($writeReleaseDate && isset($package['release-age'])) { $io->write(' '.str_pad($package['release-age'], ($padReleaseDate ? $releaseDateLength : 0), ' '), false); } } if (isset($package['description']) && $writeDescription) { $description = (string) strtok($package['description'], "\r\n"); $remaining = $width - $nameLength - $versionLength - $releaseDateLength - 4; if ($writeLatest) { $remaining -= $latestLength; } if ($remaining <= 0) { $description = ''; } elseif (extension_loaded('mbstring')) { if (mb_strwidth($description, 'UTF-8') > $remaining) { $description = mb_strimwidth($description, 0, $remaining, '...', 'UTF-8'); } } else { $cut = max(0, $remaining - 3); if (strlen($description) > $cut) { $description = substr($description, 0, $cut) . '...'; } } $io->write(' ' . $description, false); } if (array_key_exists('path', $package)) { $io->write(' '.(is_string($package['path']) ? $package['path'] : 'null'), false); } $io->write(''); if (isset($package['warning'])) { $io->write('' . $package['warning'] . ''); } } } protected function getRootRequires(): array { $composer = $this->tryComposer(); if ($composer === null) { return []; } $rootPackage = $composer->getPackage(); return array_map( 'strtolower', array_keys(array_merge($rootPackage->getRequires(), $rootPackage->getDevRequires())) ); } protected function getVersionStyle(PackageInterface $latestPackage, PackageInterface $package) { return $this->updateStatusToVersionStyle($this->getUpdateStatus($latestPackage, $package)); } protected function getPackage(InstalledRepository $installedRepo, RepositoryInterface $repos, string $name, $version = null): array { $name = strtolower($name); $constraint = is_string($version) ? $this->versionParser->parseConstraints($version) : $version; $policy = new DefaultPolicy(); $repositorySet = new RepositorySet('dev'); $repositorySet->allowInstalledRepositories(); $repositorySet->addRepository($repos); $matchedPackage = null; $versions = []; if (PlatformRepository::isPlatformPackage($name)) { $pool = $repositorySet->createPoolWithAllPackages(); } else { $pool = $repositorySet->createPoolForPackage($name); } $matches = $pool->whatProvides($name, $constraint); $literals = []; foreach ($matches as $package) { if ($package instanceof AliasPackage && $package->getVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) { $package = $package->getAliasOf(); } if (null === $version && $installedRepo->hasPackage($package)) { $matchedPackage = $package; } $versions[$package->getPrettyVersion()] = $package->getVersion(); $literals[] = $package->getId(); } if (null === $matchedPackage && \count($literals) > 0) { $preferred = $policy->selectPreferredPackages($pool, $literals); $matchedPackage = $pool->literalToPackage($preferred[0]); } if ($matchedPackage !== null && !$matchedPackage instanceof CompletePackageInterface) { throw new \LogicException('ShowCommand::getPackage can only work with CompletePackageInterface, but got '.get_class($matchedPackage)); } return [$matchedPackage, $versions]; } protected function printPackageInfo(CompletePackageInterface $package, array $versions, InstalledRepository $installedRepo, ?PackageInterface $latestPackage = null): void { $io = $this->getIO(); $this->printMeta($package, $versions, $installedRepo, $latestPackage ?: null); $this->printLinks($package, Link::TYPE_REQUIRE); $this->printLinks($package, Link::TYPE_DEV_REQUIRE, 'requires (dev)'); if ($package->getSuggests()) { $io->write("\nsuggests"); foreach ($package->getSuggests() as $suggested => $reason) { $io->write($suggested . ' ' . $reason . ''); } } $this->printLinks($package, Link::TYPE_PROVIDE); $this->printLinks($package, Link::TYPE_CONFLICT); $this->printLinks($package, Link::TYPE_REPLACE); } protected function printMeta(CompletePackageInterface $package, array $versions, InstalledRepository $installedRepo, ?PackageInterface $latestPackage = null): void { $isInstalledPackage = !PlatformRepository::isPlatformPackage($package->getName()) && $installedRepo->hasPackage($package); $io = $this->getIO(); $io->write('name : ' . $package->getPrettyName()); $io->write('descrip. : ' . $package->getDescription()); $io->write('keywords : ' . implode(', ', $package->getKeywords() ?: [])); $this->printVersions($package, $versions, $installedRepo); if ($isInstalledPackage && $package->getReleaseDate() !== null) { $io->write('released : ' . $package->getReleaseDate()->format('Y-m-d') . ', ' . $this->getRelativeTime($package->getReleaseDate())); } if ($latestPackage) { $style = $this->getVersionStyle($latestPackage, $package); $releasedTime = $latestPackage->getReleaseDate() === null ? '' : ' released ' . $latestPackage->getReleaseDate()->format('Y-m-d') . ', ' . $this->getRelativeTime($latestPackage->getReleaseDate()); $io->write('latest : <'.$style.'>' . $latestPackage->getPrettyVersion() . '' . $releasedTime); } else { $latestPackage = $package; } $io->write('type : ' . $package->getType()); $this->printLicenses($package); $io->write('homepage : ' . $package->getHomepage()); $io->write('source : ' . sprintf('[%s] %s %s', $package->getSourceType(), $package->getSourceUrl(), $package->getSourceReference())); $io->write('dist : ' . sprintf('[%s] %s %s', $package->getDistType(), $package->getDistUrl(), $package->getDistReference())); if ($isInstalledPackage) { $path = $this->requireComposer()->getInstallationManager()->getInstallPath($package); if (is_string($path)) { $io->write('path : ' . realpath($path)); } else { $io->write('path : null'); } } $io->write('names : ' . implode(', ', $package->getNames())); if ($latestPackage instanceof CompletePackageInterface && $latestPackage->isAbandoned()) { $replacement = ($latestPackage->getReplacementPackage() !== null) ? ' The author suggests using the ' . $latestPackage->getReplacementPackage(). ' package instead.' : null; $io->writeError( sprintf('Attention: This package is abandoned and no longer maintained.%s', $replacement) ); } if ($package->getSupport()) { $io->write("\nsupport"); foreach ($package->getSupport() as $type => $value) { $io->write('' . $type . ' : '.$value); } } if (\count($package->getAutoload()) > 0) { $io->write("\nautoload"); $autoloadConfig = $package->getAutoload(); foreach ($autoloadConfig as $type => $autoloads) { $io->write('' . $type . ''); if ($type === 'psr-0' || $type === 'psr-4') { foreach ($autoloads as $name => $path) { $io->write(($name ?: '*') . ' => ' . (is_array($path) ? implode(', ', $path) : ($path ?: '.'))); } } elseif ($type === 'classmap') { $io->write(implode(', ', $autoloadConfig[$type])); } } if ($package->getIncludePaths()) { $io->write('include-path'); $io->write(implode(', ', $package->getIncludePaths())); } } } protected function printVersions(CompletePackageInterface $package, array $versions, InstalledRepository $installedRepo): void { $versions = array_keys($versions); $versions = Semver::rsort($versions); if ($installedPackages = $installedRepo->findPackages($package->getName())) { foreach ($installedPackages as $installedPackage) { $installedVersion = $installedPackage->getPrettyVersion(); $key = array_search($installedVersion, $versions); if (false !== $key) { $versions[$key] = '* ' . $installedVersion . ''; } } } $versions = implode(', ', $versions); $this->getIO()->write('versions : ' . $versions); } protected function printLinks(CompletePackageInterface $package, string $linkType, ?string $title = null): void { $title = $title ?: $linkType; $io = $this->getIO(); if ($links = $package->{'get'.ucfirst($linkType)}()) { $io->write("\n" . $title . ""); foreach ($links as $link) { $io->write($link->getTarget() . ' ' . $link->getPrettyConstraint() . ''); } } } protected function printLicenses(CompletePackageInterface $package): void { $spdxLicenses = new SpdxLicenses(); $licenses = $package->getLicense(); $io = $this->getIO(); foreach ($licenses as $licenseId) { $license = $spdxLicenses->getLicenseByIdentifier($licenseId); if (!$license) { $out = $licenseId; } else { if ($license[1] === true) { $out = sprintf('%s (%s) (OSI approved) %s', $license[0], $licenseId, $license[2]); } else { $out = sprintf('%s (%s) %s', $license[0], $licenseId, $license[2]); } } $io->write('license : ' . $out); } } protected function printPackageInfoAsJson(CompletePackageInterface $package, array $versions, InstalledRepository $installedRepo, ?PackageInterface $latestPackage = null): void { $json = [ 'name' => $package->getPrettyName(), 'description' => $package->getDescription(), 'keywords' => $package->getKeywords() ?: [], 'type' => $package->getType(), 'homepage' => $package->getHomepage(), 'names' => $package->getNames(), ]; $json = $this->appendVersions($json, $versions); $json = $this->appendLicenses($json, $package); if ($latestPackage) { $json['latest'] = $latestPackage->getPrettyVersion(); } else { $latestPackage = $package; } if (null !== $package->getSourceType()) { $json['source'] = [ 'type' => $package->getSourceType(), 'url' => $package->getSourceUrl(), 'reference' => $package->getSourceReference(), ]; } if (null !== $package->getDistType()) { $json['dist'] = [ 'type' => $package->getDistType(), 'url' => $package->getDistUrl(), 'reference' => $package->getDistReference(), ]; } if (!PlatformRepository::isPlatformPackage($package->getName()) && $installedRepo->hasPackage($package)) { $path = $this->requireComposer()->getInstallationManager()->getInstallPath($package); if (is_string($path)) { $path = realpath($path); if ($path !== false) { $json['path'] = $path; } } else { $json['path'] = null; } if ($package->getReleaseDate() !== null) { $json['released'] = $package->getReleaseDate()->format(DATE_ATOM); } } if ($latestPackage instanceof CompletePackageInterface && $latestPackage->isAbandoned()) { $json['replacement'] = $latestPackage->getReplacementPackage(); } if ($package->getSuggests()) { $json['suggests'] = $package->getSuggests(); } if ($package->getSupport()) { $json['support'] = $package->getSupport(); } $json = $this->appendAutoload($json, $package); if ($package->getIncludePaths()) { $json['include_path'] = $package->getIncludePaths(); } $json = $this->appendLinks($json, $package); $this->getIO()->write(JsonFile::encode($json)); } private function appendVersions(array $json, array $versions): array { uasort($versions, 'version_compare'); $versions = array_keys(array_reverse($versions)); $json['versions'] = $versions; return $json; } private function appendLicenses(array $json, CompletePackageInterface $package): array { if ($licenses = $package->getLicense()) { $spdxLicenses = new SpdxLicenses(); $json['licenses'] = array_map(static function ($licenseId) use ($spdxLicenses) { $license = $spdxLicenses->getLicenseByIdentifier($licenseId); if (!$license) { return $licenseId; } return [ 'name' => $license[0], 'osi' => $licenseId, 'url' => $license[2], ]; }, $licenses); } return $json; } private function appendAutoload(array $json, CompletePackageInterface $package): array { if (\count($package->getAutoload()) > 0) { $autoload = []; foreach ($package->getAutoload() as $type => $autoloads) { if ($type === 'psr-0' || $type === 'psr-4') { $psr = []; foreach ($autoloads as $name => $path) { if (!$path) { $path = '.'; } $psr[$name ?: '*'] = $path; } $autoload[$type] = $psr; } elseif ($type === 'classmap') { $autoload['classmap'] = $autoloads; } } $json['autoload'] = $autoload; } return $json; } private function appendLinks(array $json, CompletePackageInterface $package): array { foreach (Link::$TYPES as $linkType) { $json = $this->appendLink($json, $package, $linkType); } return $json; } private function appendLink(array $json, CompletePackageInterface $package, string $linkType): array { $links = $package->{'get' . ucfirst($linkType)}(); if ($links) { $json[$linkType] = []; foreach ($links as $link) { $json[$linkType][$link->getTarget()] = $link->getPrettyConstraint(); } } return $json; } protected function initStyles(OutputInterface $output): void { $this->colors = [ 'green', 'yellow', 'cyan', 'magenta', 'blue', ]; foreach ($this->colors as $color) { $style = new OutputFormatterStyle($color); $output->getFormatter()->setStyle($color, $style); } } protected function displayPackageTree(array $arrayTree): void { $io = $this->getIO(); foreach ($arrayTree as $package) { $io->write(sprintf('%s', $package['name']), false); $io->write(' ' . $package['version'], false); if (isset($package['description'])) { $io->write(' ' . strtok($package['description'], "\r\n")); } else { $io->write(''); } if (isset($package['requires'])) { $requires = $package['requires']; $treeBar = '├'; $j = 0; $total = count($requires); foreach ($requires as $require) { $requireName = $require['name']; $j++; if ($j === $total) { $treeBar = '└'; } $level = 1; $color = $this->colors[$level]; $info = sprintf( '%s──<%s>%s %s', $treeBar, $color, $requireName, $color, $require['version'] ); $this->writeTreeLine($info); $treeBar = str_replace('└', ' ', $treeBar); $packagesInTree = [$package['name'], $requireName]; $this->displayTree($require, $packagesInTree, $treeBar, $level + 1); } } } } protected function generatePackageTree( PackageInterface $package, InstalledRepository $installedRepo, RepositoryInterface $remoteRepos ): array { $requires = $package->getRequires(); ksort($requires); $children = []; foreach ($requires as $requireName => $require) { $packagesInTree = [$package->getName(), $requireName]; $treeChildDesc = [ 'name' => $requireName, 'version' => $require->getPrettyConstraint(), ]; $deepChildren = $this->addTree($requireName, $require, $installedRepo, $remoteRepos, $packagesInTree); if ($deepChildren) { $treeChildDesc['requires'] = $deepChildren; } $children[] = $treeChildDesc; } $tree = [ 'name' => $package->getPrettyName(), 'version' => $package->getPrettyVersion(), 'description' => $package instanceof CompletePackageInterface ? $package->getDescription() : '', ]; if ($children) { $tree['requires'] = $children; } return $tree; } protected function displayTree( $package, array $packagesInTree, string $previousTreeBar = '├', int $level = 1 ): void { $previousTreeBar = str_replace('├', '│', $previousTreeBar); if (is_array($package) && isset($package['requires'])) { $requires = $package['requires']; $treeBar = $previousTreeBar . ' ├'; $i = 0; $total = count($requires); foreach ($requires as $require) { $currentTree = $packagesInTree; $i++; if ($i === $total) { $treeBar = $previousTreeBar . ' └'; } $colorIdent = $level % count($this->colors); $color = $this->colors[$colorIdent]; assert(is_string($require['name'])); assert(is_string($require['version'])); $circularWarn = in_array( $require['name'], $currentTree, true ) ? '(circular dependency aborted here)' : ''; $info = rtrim(sprintf( '%s──<%s>%s %s %s', $treeBar, $color, $require['name'], $color, $require['version'], $circularWarn )); $this->writeTreeLine($info); $treeBar = str_replace('└', ' ', $treeBar); $currentTree[] = $require['name']; $this->displayTree($require, $currentTree, $treeBar, $level + 1); } } } protected function addTree( string $name, Link $link, InstalledRepository $installedRepo, RepositoryInterface $remoteRepos, array $packagesInTree ): array { $children = []; [$package] = $this->getPackage( $installedRepo, $remoteRepos, $name, $link->getPrettyConstraint() === 'self.version' ? $link->getConstraint() : $link->getPrettyConstraint() ); if (is_object($package)) { $requires = $package->getRequires(); ksort($requires); foreach ($requires as $requireName => $require) { $currentTree = $packagesInTree; $treeChildDesc = [ 'name' => $requireName, 'version' => $require->getPrettyConstraint(), ]; if (!in_array($requireName, $currentTree, true)) { $currentTree[] = $requireName; $deepChildren = $this->addTree($requireName, $require, $installedRepo, $remoteRepos, $currentTree); if ($deepChildren) { $treeChildDesc['requires'] = $deepChildren; } } $children[] = $treeChildDesc; } } return $children; } private function updateStatusToVersionStyle(string $updateStatus): string { return str_replace(['up-to-date', 'semver-safe-update', 'update-possible'], ['info', 'highlight', 'comment'], $updateStatus); } private function getUpdateStatus(PackageInterface $latestPackage, PackageInterface $package): string { if ($latestPackage->getFullPrettyVersion() === $package->getFullPrettyVersion()) { return 'up-to-date'; } $constraint = $package->getVersion(); if (0 !== strpos($constraint, 'dev-')) { $constraint = '^'.$constraint; } if ($latestPackage->getVersion() && Semver::satisfies($latestPackage->getVersion(), $constraint)) { return 'semver-safe-update'; } return 'update-possible'; } private function writeTreeLine(string $line): void { $io = $this->getIO(); if (!$io->isDecorated()) { $line = str_replace(['└', '├', '──', '│'], ['`-', '|-', '-', '|'], $line); } $io->write($line); } private function findLatestPackage(PackageInterface $package, Composer $composer, PlatformRepository $platformRepo, bool $majorOnly, bool $minorOnly, bool $patchOnly, PlatformRequirementFilterInterface $platformReqFilter): ?PackageInterface { $name = $package->getName(); $versionSelector = new VersionSelector($this->getRepositorySet($composer), $platformRepo); $stability = $composer->getPackage()->getMinimumStability(); $flags = $composer->getPackage()->getStabilityFlags(); if (isset($flags[$name])) { $stability = array_search($flags[$name], BasePackage::STABILITIES, true); } $bestStability = $stability; if ($composer->getPackage()->getPreferStable()) { $bestStability = $package->getStability(); } $targetVersion = null; if (0 === strpos($package->getVersion(), 'dev-')) { $targetVersion = $package->getVersion(); if ($majorOnly) { return null; } } if ($targetVersion === null) { if ($majorOnly && Preg::isMatch('{^(?P(?:0\.)+)?(?P\d+)\.}', $package->getVersion(), $match)) { $targetVersion = '>='.$match['zero_major'].(((int) $match['first_meaningful']) + 1).',<9999999-dev'; } if ($minorOnly) { $targetVersion = '^'.$package->getVersion(); } if ($patchOnly) { $trimmedVersion = Preg::replace('{(\.0)+$}D', '', $package->getVersion()); $partsNeeded = substr($trimmedVersion, 0, 1) === '0' ? 4 : 3; while (substr_count($trimmedVersion, '.') + 1 < $partsNeeded) { $trimmedVersion .= '.0'; } $targetVersion = '~'.$trimmedVersion; } } if ($this->getIO()->isVerbose()) { $showWarnings = true; } else { $showWarnings = static function (PackageInterface $candidate) use ($package): bool { if (str_starts_with($candidate->getVersion(), 'dev-') || str_starts_with($package->getVersion(), 'dev-')) { return false; } return version_compare($candidate->getVersion(), $package->getVersion(), '<='); }; } $candidate = $versionSelector->findBestCandidate($name, $targetVersion, $bestStability, $platformReqFilter, 0, $this->getIO(), $showWarnings); while ($candidate instanceof AliasPackage) { $candidate = $candidate->getAliasOf(); } return $candidate !== false ? $candidate : null; } private function getRepositorySet(Composer $composer): RepositorySet { if (!$this->repositorySet) { $this->repositorySet = new RepositorySet($composer->getPackage()->getMinimumStability(), $composer->getPackage()->getStabilityFlags()); $this->repositorySet->addRepository(new CompositeRepository($composer->getRepositoryManager()->getRepositories())); } return $this->repositorySet; } private function getRelativeTime(DateTimeInterface $releaseDate): string { if ($releaseDate->format('Ymd') === date('Ymd')) { return 'today'; } $diff = $releaseDate->diff(new \DateTimeImmutable()); if ($diff->days < 7) { return 'this week'; } if ($diff->days < 14) { return 'last week'; } if ($diff->m < 1 && $diff->days < 31) { return floor($diff->days / 7) . ' weeks ago'; } if ($diff->y < 1) { return $diff->m . ' month' . ($diff->m > 1 ? 's' : '') . ' ago'; } return $diff->y . ' year' . ($diff->y > 1 ? 's' : '') . ' ago'; } } setName('status') ->setDescription('Shows a list of locally modified packages') ->setDefinition([ new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Show modified files for each directory that contains changes.'), ]) ->setHelp( <<requireComposer(); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'status', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $composer->getEventDispatcher()->dispatchScript(ScriptEvents::PRE_STATUS_CMD, true); $exitCode = $this->doExecute($input); $composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_STATUS_CMD, true); return $exitCode; } private function doExecute(InputInterface $input): int { $composer = $this->requireComposer(); $installedRepo = $composer->getRepositoryManager()->getLocalRepository(); $dm = $composer->getDownloadManager(); $im = $composer->getInstallationManager(); $errors = []; $io = $this->getIO(); $unpushedChanges = []; $vcsVersionChanges = []; $parser = new VersionParser; $guesser = new VersionGuesser($composer->getConfig(), $composer->getLoop()->getProcessExecutor() ?? new ProcessExecutor($io), $parser, $io); $dumper = new ArrayDumper; foreach ($installedRepo->getCanonicalPackages() as $package) { $downloader = $dm->getDownloaderForPackage($package); $targetDir = $im->getInstallPath($package); if ($targetDir === null) { continue; } if ($downloader instanceof ChangeReportInterface) { if (is_link($targetDir)) { $errors[$targetDir] = $targetDir . ' is a symbolic link.'; } if (null !== ($changes = $downloader->getLocalChanges($package, $targetDir))) { $errors[$targetDir] = $changes; } } if ($downloader instanceof VcsCapableDownloaderInterface) { if ($downloader->getVcsReference($package, $targetDir)) { switch ($package->getInstallationSource()) { case 'source': $previousRef = $package->getSourceReference(); break; case 'dist': $previousRef = $package->getDistReference(); break; default: $previousRef = null; } $currentVersion = $guesser->guessVersion($dumper->dump($package), $targetDir); if ($previousRef && $currentVersion && $currentVersion['commit'] !== $previousRef && $currentVersion['pretty_version'] !== $previousRef) { $vcsVersionChanges[$targetDir] = [ 'previous' => [ 'version' => $package->getPrettyVersion(), 'ref' => $previousRef, ], 'current' => [ 'version' => $currentVersion['pretty_version'], 'ref' => $currentVersion['commit'], ], ]; } } } if ($downloader instanceof DvcsDownloaderInterface) { if ($unpushed = $downloader->getUnpushedChanges($package, $targetDir)) { $unpushedChanges[$targetDir] = $unpushed; } } } if (!$errors && !$unpushedChanges && !$vcsVersionChanges) { $io->writeError('No local changes'); return 0; } if ($errors) { $io->writeError('You have changes in the following dependencies:'); foreach ($errors as $path => $changes) { if ($input->getOption('verbose')) { $indentedChanges = implode("\n", array_map(static function ($line): string { return ' ' . ltrim($line); }, explode("\n", $changes))); $io->write(''.$path.':'); $io->write($indentedChanges); } else { $io->write($path); } } } if ($unpushedChanges) { $io->writeError('You have unpushed changes on the current branch in the following dependencies:'); foreach ($unpushedChanges as $path => $changes) { if ($input->getOption('verbose')) { $indentedChanges = implode("\n", array_map(static function ($line): string { return ' ' . ltrim($line); }, explode("\n", $changes))); $io->write(''.$path.':'); $io->write($indentedChanges); } else { $io->write($path); } } } if ($vcsVersionChanges) { $io->writeError('You have version variations in the following dependencies:'); foreach ($vcsVersionChanges as $path => $changes) { if ($input->getOption('verbose')) { $currentVersion = $changes['current']['version'] ?: $changes['current']['ref']; $previousVersion = $changes['previous']['version'] ?: $changes['previous']['ref']; if ($io->isVeryVerbose()) { $currentVersion .= sprintf(' (%s)', $changes['current']['ref']); $previousVersion .= sprintf(' (%s)', $changes['previous']['ref']); } $io->write(''.$path.':'); $io->write(sprintf(' From %s to %s', $previousVersion, $currentVersion)); } else { $io->write($path); } } } if (($errors || $unpushedChanges || $vcsVersionChanges) && !$input->getOption('verbose')) { $io->writeError('Use --verbose (-v) to see a list of files'); } return ($errors ? self::EXIT_CODE_ERRORS : 0) + ($unpushedChanges ? self::EXIT_CODE_UNPUSHED_CHANGES : 0) + ($vcsVersionChanges ? self::EXIT_CODE_VERSION_CHANGES : 0); } } setName('suggests') ->setDescription('Shows package suggestions') ->setDefinition([ new InputOption('by-package', null, InputOption::VALUE_NONE, 'Groups output by suggesting package (default)'), new InputOption('by-suggestion', null, InputOption::VALUE_NONE, 'Groups output by suggested package'), new InputOption('all', 'a', InputOption::VALUE_NONE, 'Show suggestions from all dependencies, including transitive ones'), new InputOption('list', null, InputOption::VALUE_NONE, 'Show only list of suggested package names'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Exclude suggestions from require-dev packages'), new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Packages that you want to list suggestions from.', null, $this->suggestInstalledPackage()), ]) ->setHelp( <<%command.name% command shows a sorted list of suggested packages. Read more at https://getcomposer.org/doc/03-cli.md#suggests EOT ) ; } protected function execute(InputInterface $input, OutputInterface $output): int { $composer = $this->requireComposer(); $installedRepos = [ new RootPackageRepository(clone $composer->getPackage()), ]; $locker = $composer->getLocker(); if ($locker->isLocked()) { $installedRepos[] = new PlatformRepository([], $locker->getPlatformOverrides()); $installedRepos[] = $locker->getLockedRepository(!$input->getOption('no-dev')); } else { $installedRepos[] = new PlatformRepository([], $composer->getConfig()->get('platform')); $installedRepos[] = $composer->getRepositoryManager()->getLocalRepository(); } $installedRepo = new InstalledRepository($installedRepos); $reporter = new SuggestedPackagesReporter($this->getIO()); $filter = $input->getArgument('packages'); $packages = $installedRepo->getPackages(); $packages[] = $composer->getPackage(); foreach ($packages as $package) { if (!empty($filter) && !in_array($package->getName(), $filter)) { continue; } $reporter->addSuggestionsFromPackage($package); } $mode = SuggestedPackagesReporter::MODE_BY_PACKAGE; if ($input->getOption('by-suggestion')) { $mode = SuggestedPackagesReporter::MODE_BY_SUGGESTION; } if ($input->getOption('by-package')) { $mode |= SuggestedPackagesReporter::MODE_BY_PACKAGE; } if ($input->getOption('list')) { $mode = SuggestedPackagesReporter::MODE_LIST; } $reporter->output($mode, $installedRepo, empty($filter) && !$input->getOption('all') ? $composer->getPackage() : null); return 0; } } setName('update') ->setAliases(['u', 'upgrade']) ->setDescription('Updates your dependencies to the latest version according to composer.json, and updates the composer.lock file') ->setDefinition([ new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Packages that should be updated, if not provided all packages are.', null, $this->suggestInstalledPackage(false)), new InputOption('with', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Temporary version constraint to add, e.g. foo/bar:1.0.0 or foo/bar=1.0.0'), new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'), new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist (default behavior).'), new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).', null, $this->suggestPreferInstall()), new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'), new InputOption('dev', null, InputOption::VALUE_NONE, 'DEPRECATED: Enables installation of require-dev packages (enabled by default, only present for BC).'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'), new InputOption('lock', null, InputOption::VALUE_NONE, 'Overwrites the lock file hash to suppress warning about the lock file being out of date without updating package versions. Package metadata like mirrors and URLs are updated if they changed.'), new InputOption('no-install', null, InputOption::VALUE_NONE, 'Skip the install step after updating the composer.lock file.'), new InputOption('no-audit', null, InputOption::VALUE_NONE, 'Skip the audit step after updating the composer.lock file (can also be set via the COMPOSER_NO_AUDIT=1 env var).'), new InputOption('audit-format', null, InputOption::VALUE_REQUIRED, 'Audit output format. Must be "table", "plain", "json", or "summary".', Auditor::FORMAT_SUMMARY, Auditor::FORMATS), new InputOption('no-security-blocking', null, InputOption::VALUE_NONE, 'DEPRECATED: use --no-blocking instead. Allows installing packages with security advisories or that are abandoned (can also be set via the COMPOSER_NO_SECURITY_BLOCKING=1 env var).'), new InputOption('no-blocking', null, InputOption::VALUE_NONE, 'Disables all policy blocking during this command (can also be set via the COMPOSER_NO_BLOCKING=1 env var).'), new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'), new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'DEPRECATED: This flag does not exist anymore.'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('with-dependencies', 'w', InputOption::VALUE_NONE, 'Update also dependencies of packages in the argument list, except those which are root requirements (can also be set via the COMPOSER_WITH_DEPENDENCIES=1 env var).'), new InputOption('with-all-dependencies', 'W', InputOption::VALUE_NONE, 'Update also dependencies of packages in the argument list, including those which are root requirements (can also be set via the COMPOSER_WITH_ALL_DEPENDENCIES=1 env var).'), new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'), new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump.'), new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'), new InputOption('strict-psr-autoloader', null, InputOption::VALUE_NONE, 'Return a failed status code (6) if PSR-4 or PSR-0 mapping errors are present. Requires --optimize-autoloader to work.'), new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'), new InputOption('apcu-autoloader-prefix', null, InputOption::VALUE_REQUIRED, 'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader'), new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'), new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'), new InputOption('prefer-stable', null, InputOption::VALUE_NONE, 'Prefer stable versions of dependencies (can also be set via the COMPOSER_PREFER_STABLE=1 env var).'), new InputOption('prefer-lowest', null, InputOption::VALUE_NONE, 'Prefer lowest versions of dependencies (can also be set via the COMPOSER_PREFER_LOWEST=1 env var).'), new InputOption('minimal-changes', 'm', InputOption::VALUE_NONE, 'Only perform absolutely necessary changes to dependencies. If packages cannot be kept at their currently locked version they are updated. For partial updates the allow-listed packages are always updated fully. (can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var).'), new InputOption('patch-only', null, InputOption::VALUE_NONE, 'Only allow patch version updates for currently installed dependencies.'), new InputOption('interactive', 'i', InputOption::VALUE_NONE, 'Interactive interface with autocompletion to select the packages to update.'), new InputOption('root-reqs', null, InputOption::VALUE_NONE, 'Restricts the update to your first degree dependencies.'), new InputOption('bump-after-update', null, InputOption::VALUE_OPTIONAL, 'Runs bump after performing the update.', false, ['dev', 'no-dev', 'all']), ]) ->setHelp( <<update command reads the composer.json file from the current directory, processes it, and updates, removes or installs all the dependencies. php composer.phar update To limit the update operation to a few packages, you can list the package(s) you want to update as such: php composer.phar update vendor/package1 foo/mypackage [...] You may also use an asterisk (*) pattern to limit the update operation to package(s) from a specific vendor: php composer.phar update vendor/package1 foo/* [...] To run an update with more restrictive constraints you can use: php composer.phar update --with vendor/package:1.0.* To run a partial update with more restrictive constraints you can use the shorthand: php composer.phar update vendor/package:1.0.* To select packages names interactively with auto-completion use -i. Read more at https://getcomposer.org/doc/03-cli.md#update-u-upgrade EOT ) ; } protected function execute(InputInterface $input, OutputInterface $output): int { $io = $this->getIO(); if ($input->getOption('dev')) { $io->writeError('You are using the deprecated option "--dev". It has no effect and will break in Composer 3.'); } if ($input->getOption('no-suggest')) { $io->writeError('You are using the deprecated option "--no-suggest". It has no effect and will break in Composer 3.'); } $composer = $this->requireComposer(); if (!HttpDownloader::isCurlEnabled()) { $io->writeError('Composer is operating significantly slower than normal because you do not have the PHP curl extension enabled.'); } $packages = $input->getArgument('packages'); $reqs = $this->formatRequirements($input->getOption('with')); if (count($packages) > 0) { $allowlistPackagesWithRequirements = array_filter($packages, static function ($pkg): bool { return Preg::isMatch('{\S+[ =:]\S+}', $pkg); }); foreach ($this->formatRequirements($allowlistPackagesWithRequirements) as $package => $constraint) { $reqs[$package] = $constraint; } foreach ($allowlistPackagesWithRequirements as $package) { $packageName = Preg::replace('{^([^ =:]+)[ =:].*$}', '$1', $package); $index = array_search($package, $packages); $packages[$index] = $packageName; } } $rootPackage = $composer->getPackage(); $rootPackage->setReferences(RootPackageLoader::extractReferences($reqs, $rootPackage->getReferences())); $rootPackage->setStabilityFlags(RootPackageLoader::extractStabilityFlags($reqs, $rootPackage->getMinimumStability(), $rootPackage->getStabilityFlags())); $parser = new VersionParser; $temporaryConstraints = []; $rootRequirements = array_merge($rootPackage->getRequires(), $rootPackage->getDevRequires()); foreach ($reqs as $package => $constraint) { $package = strtolower($package); $parsedConstraint = $parser->parseConstraints($constraint); if (str_contains($package, '*')) { $packageFilterRegex = BasePackage::packageNameToRegexp($package); foreach ($rootRequirements as $rootRequirementPackageName => $rootRequirement) { if (!Preg::isMatch($packageFilterRegex, $rootRequirementPackageName)) { continue; } $temporaryConstraints[$rootRequirementPackageName] = $parsedConstraint; if (!Intervals::haveIntersections($parsedConstraint, $rootRequirement->getConstraint())) { $io->writeError('The temporary constraint "'.$constraint.'" for "'.$package.'" matching "'.$rootRequirementPackageName.'" must be a subset of the constraint in your composer.json ('.$rootRequirement->getPrettyConstraint().')'); return self::FAILURE; } } } else { $temporaryConstraints[$package] = $parsedConstraint; if (isset($rootRequirements[$package]) && !Intervals::haveIntersections($parsedConstraint, $rootRequirements[$package]->getConstraint())) { $io->writeError('The temporary constraint "'.$constraint.'" for "'.$package.'" must be a subset of the constraint in your composer.json ('.$rootRequirements[$package]->getPrettyConstraint().')'); $io->write('Run `composer require '.$package.'` or `composer require '.$package.':'.$constraint.'` instead to replace the constraint'); return self::FAILURE; } } } if ($input->getOption('patch-only')) { if (!$composer->getLocker()->isLocked()) { throw new \InvalidArgumentException('patch-only can only be used with a lock file present'); } foreach ($composer->getLocker()->getLockedRepository(true)->getCanonicalPackages() as $package) { if ($package->isDev()) { continue; } if (!Preg::isMatch('{^(\d+\.\d+\.\d+)}', $package->getVersion(), $match)) { continue; } $constraint = $parser->parseConstraints('~'.$match[1]); if (isset($temporaryConstraints[$package->getName()])) { $temporaryConstraints[$package->getName()] = MultiConstraint::create([$temporaryConstraints[$package->getName()], $constraint], true); } else { $temporaryConstraints[$package->getName()] = $constraint; } } } if ($input->getOption('interactive')) { $packages = $this->getPackagesInteractively($io, $input, $output, $composer, $packages); } if ($input->getOption('root-reqs')) { $requires = array_keys($rootPackage->getRequires()); if (!$input->getOption('no-dev')) { $requires = array_merge($requires, array_keys($rootPackage->getDevRequires())); } if (!empty($packages)) { $packages = array_intersect($packages, $requires); } else { $packages = $requires; } } $filteredPackages = array_filter($packages, static function ($package): bool { return !in_array($package, ['lock', 'nothing', 'mirrors'], true); }); $updateMirrors = $input->getOption('lock') || count($filteredPackages) !== count($packages); $packages = $filteredPackages; if ($updateMirrors && !empty($packages)) { $io->writeError('You cannot simultaneously update only a selection of packages and regenerate the lock file metadata.'); return -1; } $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'update', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $composer->getInstallationManager()->setOutputProgress(!$input->getOption('no-progress')); $install = Installer::create($io, $composer); $config = $composer->getConfig(); [$preferSource, $preferDist] = $this->getPreferredInstallOptions($config, $input); $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader'); $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative'); $apcuPrefix = $input->getOption('apcu-autoloader-prefix'); $apcu = $apcuPrefix !== null || $input->getOption('apcu-autoloader') || $config->get('apcu-autoloader'); $minimalChanges = $input->getOption('minimal-changes') || $config->get('update-with-minimal-changes'); if ($input->getOption('strict-psr-autoloader') && !$optimize && !$authoritative) { throw new \InvalidArgumentException('--strict-psr-autoloader mode only works with optimized autoloader, use --optimize-autoloader or --classmap-authoritative if you want a strict return value.'); } $updateAllowTransitiveDependencies = Request::UPDATE_ONLY_LISTED; if ($input->getOption('with-all-dependencies')) { $updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS; } elseif ($input->getOption('with-dependencies')) { $updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE; } $install ->setDryRun($input->getOption('dry-run')) ->setVerbose($input->getOption('verbose')) ->setPreferSource($preferSource) ->setPreferDist($preferDist) ->setDevMode(!$input->getOption('no-dev')) ->setDumpAutoloader(!$input->getOption('no-autoloader')) ->setOptimizeAutoloader($optimize) ->setClassMapAuthoritative($authoritative) ->setStrictPsrAutoloader($input->getOption('strict-psr-autoloader')) ->setApcuAutoloader($apcu, $apcuPrefix) ->setUpdate(true) ->setInstall(!$input->getOption('no-install')) ->setUpdateMirrors($updateMirrors) ->setUpdateAllowList($packages) ->setUpdateAllowTransitiveDependencies($updateAllowTransitiveDependencies) ->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input)) ->setPreferStable($input->getOption('prefer-stable')) ->setPreferLowest($input->getOption('prefer-lowest')) ->setTemporaryConstraints($temporaryConstraints) ->setPolicyConfig($this->createPolicyConfig($composer->getConfig(), $input)) ->setAuditConfig($this->createAuditConfig($input)) ->setMinimalUpdate($minimalChanges) ; if ($input->getOption('no-plugins')) { $install->disablePlugins(); } $result = $install->run(); if ($result === 0 && !$input->getOption('lock')) { $bumpAfterUpdate = $input->getOption('bump-after-update'); if (false === $bumpAfterUpdate) { $bumpAfterUpdate = $composer->getConfig()->get('bump-after-update'); } if (false !== $bumpAfterUpdate) { $updatedPackages = []; $lockTransaction = $install->getLockTransaction(); if ($lockTransaction !== null) { foreach ($lockTransaction->getOperations() as $operation) { if ($operation instanceof InstallOperation) { $updatedPackages[] = $operation->getPackage()->getName(); } elseif ($operation instanceof UpdateOperation) { $updatedPackages[] = $operation->getTargetPackage()->getName(); } } } if (\count($updatedPackages) > 0) { $io->writeError('Bumping dependencies'); $bumpCommand = new BumpCommand(); $bumpCommand->setComposer($composer); $result = $bumpCommand->doBump( $io, $bumpAfterUpdate === 'dev', $bumpAfterUpdate === 'no-dev', $input->getOption('dry-run'), $updatedPackages, '--bump-after-update=dev' ); } } } return $result; } private function getPackagesInteractively(IOInterface $io, InputInterface $input, OutputInterface $output, Composer $composer, array $packages): array { if (!$input->isInteractive()) { throw new \InvalidArgumentException('--interactive cannot be used in non-interactive terminals.'); } $platformReqFilter = $this->getPlatformRequirementFilter($input); $stabilityFlags = $composer->getPackage()->getStabilityFlags(); $requires = array_merge( $composer->getPackage()->getRequires(), $composer->getPackage()->getDevRequires() ); $filter = \count($packages) > 0 ? BasePackage::packageNamesToRegexp($packages) : null; $io->writeError('Loading packages that can be updated...'); $autocompleterValues = []; $installedPackages = $composer->getLocker()->isLocked() ? $composer->getLocker()->getLockedRepository(true)->getPackages() : $composer->getRepositoryManager()->getLocalRepository()->getPackages(); $versionSelector = $this->createVersionSelector($composer); foreach ($installedPackages as $package) { if ($filter !== null && !Preg::isMatch($filter, $package->getName())) { continue; } $currentVersion = $package->getPrettyVersion(); $constraint = isset($requires[$package->getName()]) ? $requires[$package->getName()]->getPrettyConstraint() : null; $stability = isset($stabilityFlags[$package->getName()]) ? (string) array_search($stabilityFlags[$package->getName()], BasePackage::STABILITIES, true) : $composer->getPackage()->getMinimumStability(); $latestVersion = $versionSelector->findBestCandidate($package->getName(), $constraint, $stability, $platformReqFilter); if ($latestVersion !== false && ($package->getVersion() !== $latestVersion->getVersion() || $latestVersion->isDev())) { $autocompleterValues[$package->getName()] = '' . $currentVersion . ' => ' . $latestVersion->getPrettyVersion() . ''; } } if (0 === \count($installedPackages)) { foreach ($requires as $req => $constraint) { if (PlatformRepository::isPlatformPackage($req)) { continue; } $autocompleterValues[$req] = ''; } } if (0 === \count($autocompleterValues)) { throw new \RuntimeException('Could not find any package with new versions available'); } $packages = $io->select( 'Select packages: (Select more than one value separated by comma) ', $autocompleterValues, false, 1, 'No package named "%s" is installed.', true ); $table = new Table($output); $table->setHeaders(['Selected packages']); foreach ($packages as $package) { $table->addRow([$package]); } $table->render(); if ($io->askConfirmation(sprintf( 'Would you like to continue and update the above package%s [yes]? ', 1 === count($packages) ? '' : 's' ))) { return $packages; } throw new \RuntimeException('Installation aborted.'); } private function createVersionSelector(Composer $composer): VersionSelector { $repositorySet = new RepositorySet(); $repositorySet->addRepository(new CompositeRepository(array_filter($composer->getRepositoryManager()->getRepositories(), static function (RepositoryInterface $repository) { return !$repository instanceof PlatformRepository; }))); return new VersionSelector($repositorySet); } } setName('validate') ->setDescription('Validates a composer.json and composer.lock') ->setDefinition([ new InputOption('no-check-all', null, InputOption::VALUE_NONE, 'Do not validate requires for overly strict/loose constraints'), new InputOption('check-lock', null, InputOption::VALUE_NONE, 'Check if lock file is up to date (even when config.lock is false)'), new InputOption('no-check-lock', null, InputOption::VALUE_NONE, 'Do not check if lock file is up to date'), new InputOption('no-check-publish', null, InputOption::VALUE_NONE, 'Do not check for publish errors'), new InputOption('no-check-version', null, InputOption::VALUE_NONE, 'Do not report a warning if the version field is present'), new InputOption('with-dependencies', 'A', InputOption::VALUE_NONE, 'Also validate the composer.json of all installed dependencies'), new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code for warnings as well as errors'), new InputArgument('file', InputArgument::OPTIONAL, 'path to composer.json file'), ]) ->setHelp( <<getArgument('file') ?? Factory::getComposerFile(); $io = $this->getIO(); if (!file_exists($file)) { $io->writeError('' . $file . ' not found.'); return 3; } if (!Filesystem::isReadable($file)) { $io->writeError('' . $file . ' is not readable.'); return 3; } $validator = new ConfigValidator($io); $checkAll = $input->getOption('no-check-all') ? 0 : ValidatingArrayLoader::CHECK_ALL; $checkPublish = !$input->getOption('no-check-publish'); $checkLock = !$input->getOption('no-check-lock'); $checkVersion = $input->getOption('no-check-version') ? 0 : ConfigValidator::CHECK_VERSION; $isStrict = $input->getOption('strict'); [$errors, $publishErrors, $warnings] = $validator->validate($file, $checkAll, $checkVersion); $lockErrors = []; $composer = $this->createComposerInstance($input, $io, $file); $checkLock = ($checkLock && $composer->getConfig()->get('lock')) || $input->getOption('check-lock'); $locker = $composer->getLocker(); if ($locker->isLocked() && !$locker->isFresh()) { $lockErrors[] = '- The lock file is not up to date with the latest changes in composer.json, it is recommended that you run `composer update` or `composer update `.'; } if ($locker->isLocked()) { $lockErrors = array_merge($lockErrors, $locker->getMissingRequirementInfo($composer->getPackage(), true)); } $this->outputResult($io, $file, $errors, $warnings, $checkPublish, $publishErrors, $checkLock, $lockErrors, true); $exitCode = count($errors) > 0 ? 2 : (($isStrict && count($warnings) > 0) ? 1 : 0); if ($input->getOption('with-dependencies')) { $localRepo = $composer->getRepositoryManager()->getLocalRepository(); foreach ($localRepo->getPackages() as $package) { $path = $composer->getInstallationManager()->getInstallPath($package); if (null === $path) { continue; } $file = $path . '/composer.json'; if (is_dir($path) && file_exists($file)) { [$errors, $publishErrors, $warnings] = $validator->validate($file, $checkAll, $checkVersion); $this->outputResult($io, $package->getPrettyName(), $errors, $warnings, $checkPublish, $publishErrors); $depCode = count($errors) > 0 ? 2 : (($isStrict && count($warnings) > 0) ? 1 : 0); $exitCode = max($depCode, $exitCode); } } } $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'validate', $input, $output); $eventCode = $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); return max($eventCode, $exitCode); } private function outputResult(IOInterface $io, string $name, array &$errors, array &$warnings, bool $checkPublish = false, array $publishErrors = [], bool $checkLock = false, array $lockErrors = [], bool $printSchemaUrl = false): void { $doPrintSchemaUrl = false; if (\count($errors) > 0) { $io->writeError('' . $name . ' is invalid, the following errors/warnings were found:'); } elseif (\count($publishErrors) > 0 && $checkPublish) { $io->writeError('' . $name . ' is valid for simple usage with Composer but has'); $io->writeError('strict errors that make it unable to be published as a package'); $doPrintSchemaUrl = $printSchemaUrl; } elseif (\count($warnings) > 0) { $io->writeError('' . $name . ' is valid, but with a few warnings'); $doPrintSchemaUrl = $printSchemaUrl; } elseif (\count($lockErrors) > 0) { $io->write('' . $name . ' is valid but your composer.lock has some '.($checkLock ? 'errors' : 'warnings').''); } else { $io->write('' . $name . ' is valid'); } if ($doPrintSchemaUrl) { $io->writeError('See https://getcomposer.org/doc/04-schema.md for details on the schema'); } if (\count($errors) > 0) { $errors = array_map(static function ($err): string { return '- ' . $err; }, $errors); array_unshift($errors, '# General errors'); } if (\count($warnings) > 0) { $warnings = array_map(static function ($err): string { return '- ' . $err; }, $warnings); array_unshift($warnings, '# General warnings'); } $extraWarnings = []; if (\count($publishErrors) > 0 && $checkPublish) { $publishErrors = array_map(static function ($err): string { return '- ' . $err; }, $publishErrors); array_unshift($publishErrors, '# Publish errors'); $errors = array_merge($errors, $publishErrors); } if (\count($lockErrors) > 0) { if ($checkLock) { array_unshift($lockErrors, '# Lock file errors'); $errors = array_merge($errors, $lockErrors); } else { array_unshift($lockErrors, '# Lock file warnings'); $extraWarnings = array_merge($extraWarnings, $lockErrors); } } $messages = [ 'error' => $errors, 'warning' => array_merge($warnings, $extraWarnings), ]; foreach ($messages as $style => $msgs) { foreach ($msgs as $msg) { if (strpos($msg, '#') === 0) { $io->writeError('<' . $style . '>' . $msg . ''); } else { $io->writeError($msg); } } } } } locker = $locker; } public function getLocker(): Locker { return $this->locker; } public function setDownloadManager(DownloadManager $manager): void { $this->downloadManager = $manager; } public function getDownloadManager(): DownloadManager { return $this->downloadManager; } public function setArchiveManager(ArchiveManager $manager): void { $this->archiveManager = $manager; } public function getArchiveManager(): ArchiveManager { return $this->archiveManager; } public function setPluginManager(PluginManager $manager): void { $this->pluginManager = $manager; } public function getPluginManager(): PluginManager { return $this->pluginManager; } public function setAutoloadGenerator(AutoloadGenerator $autoloadGenerator): void { $this->autoloadGenerator = $autoloadGenerator; } public function getAutoloadGenerator(): AutoloadGenerator { return $this->autoloadGenerator; } } 300, 'use-include-path' => false, 'allow-plugins' => [], 'use-parent-dir' => 'prompt', 'preferred-install' => 'dist', 'audit' => ['ignore' => [], 'abandoned' => ListPolicyConfig::AUDIT_FAIL], 'policy' => true, 'notify-on-install' => true, 'github-protocols' => ['https', 'ssh', 'git'], 'gitlab-protocol' => null, 'vendor-dir' => 'vendor', 'bin-dir' => '{$vendor-dir}/bin', 'cache-dir' => '{$home}/cache', 'data-dir' => '{$home}', 'cache-files-dir' => '{$cache-dir}/files', 'cache-repo-dir' => '{$cache-dir}/repo', 'cache-vcs-dir' => '{$cache-dir}/vcs', 'cache-ttl' => 15552000, 'cache-files-ttl' => null, 'cache-files-maxsize' => '300MiB', 'cache-read-only' => false, 'bin-compat' => 'auto', 'discard-changes' => false, 'autoloader-suffix' => null, 'sort-packages' => false, 'optimize-autoloader' => false, 'classmap-authoritative' => false, 'apcu-autoloader' => false, 'prepend-autoloader' => true, 'update-with-minimal-changes' => false, 'github-domains' => ['github.com'], 'bitbucket-expose-hostname' => true, 'disable-tls' => false, 'secure-http' => true, 'secure-svn-domains' => [], 'cafile' => null, 'capath' => null, 'github-expose-hostname' => true, 'gitlab-domains' => ['gitlab.com'], 'store-auths' => 'prompt', 'platform' => [], 'archive-format' => 'tar', 'archive-dir' => '.', 'htaccess-protect' => true, 'use-github-api' => true, 'lock' => true, 'platform-check' => 'php-only', 'bitbucket-oauth' => [], 'github-oauth' => [], 'gitlab-oauth' => [], 'gitlab-token' => [], 'http-basic' => [], 'bearer' => [], 'custom-headers' => [], 'bump-after-update' => false, 'allow-missing-requirements' => false, 'client-certificate' => [], 'forgejo-domains' => ['codeberg.org'], 'forgejo-token' => [], 'source-fallback' => false, ]; public static $defaultRepositories = [ 'packagist.org' => [ 'type' => 'composer', 'url' => 'https://repo.packagist.org', ], ]; private $config; private $baseDir; private $repositories; private $configSource; private $authConfigSource; private $localAuthConfigSource = null; private $useEnvironment; private $warnedHosts = []; private $sslVerifyWarnedHosts = []; private $sourceOfConfigValue = []; public function __construct(bool $useEnvironment = true, ?string $baseDir = null) { $this->config = static::$defaultConfig; $this->repositories = static::$defaultRepositories; $this->useEnvironment = $useEnvironment; $this->baseDir = is_string($baseDir) && '' !== $baseDir ? $baseDir : null; foreach ($this->config as $configKey => $configValue) { $this->setSourceOfConfigValue($configValue, $configKey, self::SOURCE_DEFAULT); } foreach ($this->repositories as $configKey => $configValue) { $this->setSourceOfConfigValue($configValue, 'repositories.' . $configKey, self::SOURCE_DEFAULT); } } public function setBaseDir(?string $baseDir): void { $this->baseDir = $baseDir; } public function setConfigSource(ConfigSourceInterface $source): void { $this->configSource = $source; } public function getConfigSource(): ConfigSourceInterface { return $this->configSource; } public function setAuthConfigSource(ConfigSourceInterface $source): void { $this->authConfigSource = $source; } public function getAuthConfigSource(): ConfigSourceInterface { return $this->authConfigSource; } public function setLocalAuthConfigSource(ConfigSourceInterface $source): void { $this->localAuthConfigSource = $source; } public function getLocalAuthConfigSource(): ?ConfigSourceInterface { return $this->localAuthConfigSource; } public function merge(array $config, string $source = self::SOURCE_UNKNOWN): void { if (!empty($config['config']) && is_array($config['config'])) { foreach ($config['config'] as $key => $val) { if (in_array($key, ['bitbucket-oauth', 'github-oauth', 'gitlab-oauth', 'gitlab-token', 'http-basic', 'bearer', 'client-certificate', 'forgejo-token'], true) && isset($this->config[$key])) { $this->config[$key] = array_merge($this->config[$key], $val); $this->setSourceOfConfigValue($val, $key, $source); } elseif (in_array($key, ['allow-plugins'], true) && isset($this->config[$key]) && is_array($this->config[$key]) && is_array($val)) { $this->config[$key] = array_merge($val, $this->config[$key], $val); $this->setSourceOfConfigValue($val, $key, $source); } elseif (in_array($key, ['gitlab-domains', 'github-domains'], true) && isset($this->config[$key])) { $this->config[$key] = array_unique(array_merge($this->config[$key], $val)); $this->setSourceOfConfigValue($val, $key, $source); } elseif ('preferred-install' === $key && isset($this->config[$key])) { if (is_array($val) || is_array($this->config[$key])) { if (is_string($val)) { $val = ['*' => $val]; } if (is_string($this->config[$key])) { $this->config[$key] = ['*' => $this->config[$key]]; $this->sourceOfConfigValue[$key . '*'] = $source; } $this->config[$key] = array_merge($this->config[$key], $val); $this->setSourceOfConfigValue($val, $key, $source); if (isset($this->config[$key]['*'])) { $wildcard = $this->config[$key]['*']; unset($this->config[$key]['*']); $this->config[$key]['*'] = $wildcard; } } else { $this->config[$key] = $val; $this->setSourceOfConfigValue($val, $key, $source); } } elseif ('audit' === $key) { $currentIgnores = $this->config['audit']['ignore']; $this->config[$key] = array_merge($this->config['audit'], $val); $this->setSourceOfConfigValue($val, $key, $source); $this->config['audit']['ignore'] = array_merge($currentIgnores, $val['ignore'] ?? []); } elseif ('policy' === $key) { if ($val === true) { $val = []; } if ($val === false) { $this->config[$key] = false; } elseif (\is_array($val)) { $current = \is_array($this->config['policy']) ? $this->config['policy'] : []; $deepMergeKeys = ['ignore', 'ignore-id', 'ignore-severity', 'ignore-source']; foreach ($val as $listName => $listConfig) { if (in_array($listName, PolicyConfig::NON_LIST_KEYS, true)) { $current[$listName] = $listConfig; continue; } if ($listConfig === true) { $listConfig = []; } $existing = $current[$listName] ?? null; if ($existing === true) { $existing = []; } if ($listConfig === false) { $current[$listName] = false; } elseif ($existing === null || $existing === false) { $current[$listName] = $listConfig; } elseif (\is_array($existing) && \is_array($listConfig)) { $merged = array_merge($existing, $listConfig); foreach ($deepMergeKeys as $innerKey) { $existingInner = $existing[$innerKey] ?? null; $incomingInner = $listConfig[$innerKey] ?? null; if (\is_array($existingInner) && \is_array($incomingInner)) { $merged[$innerKey] = array_merge($existingInner, $incomingInner); } } $current[$listName] = $merged; } else { $current[$listName] = $listConfig; } } $this->config[$key] = $current; } $this->setSourceOfConfigValue($val, $key, $source); } else { $this->config[$key] = $val; $this->setSourceOfConfigValue($val, $key, $source); } } } if (!empty($config['repositories']) && is_array($config['repositories'])) { $this->repositories = array_reverse($this->repositories, true); $newRepos = array_reverse($config['repositories'], true); foreach ($newRepos as $name => $repository) { if (false === $repository) { $this->disableRepoByName((string) $name); continue; } if (is_array($repository) && 1 === count($repository) && false === current($repository)) { $this->disableRepoByName((string) key($repository)); continue; } if (isset($repository['type'], $repository['url']) && $repository['type'] === 'composer' && Preg::isMatch('{^https?://(?:[a-z0-9-.]+\.)?packagist.org(/|$)}', $repository['url'])) { $this->disableRepoByName('packagist.org'); } if (is_int($name)) { if (!isset($this->repositories[$name])) { $this->repositories[$name] = $repository; } else { $this->repositories[] = $repository; } $this->setSourceOfConfigValue($repository, 'repositories.' . array_search($repository, $this->repositories, true), $source); } else { if ($name === 'packagist') { $this->repositories[$name . '.org'] = $repository; $this->setSourceOfConfigValue($repository, 'repositories.' . $name . '.org', $source); } else { $this->repositories[$name] = $repository; $this->setSourceOfConfigValue($repository, 'repositories.' . $name, $source); } } } $this->repositories = array_reverse($this->repositories, true); } } public function getRepositories(): array { return $this->repositories; } public function get(string $key, int $flags = 0) { switch ($key) { case 'vendor-dir': case 'bin-dir': case 'process-timeout': case 'data-dir': case 'cache-dir': case 'cache-files-dir': case 'cache-repo-dir': case 'cache-vcs-dir': case 'cafile': case 'capath': $env = 'COMPOSER_' . strtoupper(strtr($key, '-', '_')); $val = $this->getComposerEnv($env); if ($val !== false) { $this->setSourceOfConfigValue($val, $key, $env); } if ($key === 'process-timeout') { return max(0, false !== $val ? (int) $val : $this->config[$key]); } $val = rtrim((string) $this->process(false !== $val ? $val : $this->config[$key], $flags), '/\\'); $val = Platform::expandPath($val); if (substr($key, -4) !== '-dir') { return $val; } return (($flags & self::RELATIVE_PATHS) === self::RELATIVE_PATHS) ? $val : $this->realpath($val); case 'cache-read-only': case 'htaccess-protect': $env = 'COMPOSER_' . strtoupper(strtr($key, '-', '_')); $val = $this->getComposerEnv($env); if (false === $val) { $val = $this->config[$key]; } else { $this->setSourceOfConfigValue($val, $key, $env); } return $val !== 'false' && (bool) $val; case 'disable-tls': case 'secure-http': case 'use-github-api': case 'lock': case 'source-fallback': if ($key === 'secure-http' && $this->get('disable-tls') === true) { return false; } return $this->config[$key] !== 'false' && (bool) $this->config[$key]; case 'cache-ttl': return max(0, (int) $this->config[$key]); case 'cache-files-maxsize': if (!Preg::isMatch('/^\s*([0-9.]+)\s*(?:([kmg])(?:i?b)?)?\s*$/i', (string) $this->config[$key], $matches)) { throw new \RuntimeException( "Could not parse the value of '$key': {$this->config[$key]}" ); } $size = (float) $matches[1]; if (isset($matches[2])) { switch (strtolower($matches[2])) { case 'g': $size *= 1024; case 'm': $size *= 1024; case 'k': $size *= 1024; break; } } return max(0, (int) $size); case 'cache-files-ttl': if (isset($this->config[$key])) { return max(0, (int) $this->config[$key]); } return $this->get('cache-ttl'); case 'home': return rtrim($this->process(Platform::expandPath($this->config[$key]), $flags), '/\\'); case 'bin-compat': $value = $this->getComposerEnv('COMPOSER_BIN_COMPAT') ?: $this->config[$key]; if (!in_array($value, ['auto', 'full', 'proxy', 'symlink'])) { throw new \RuntimeException( "Invalid value for 'bin-compat': {$value}. Expected auto, full or proxy" ); } if ($value === 'symlink') { trigger_error('config.bin-compat "symlink" is deprecated since Composer 2.2, use auto, full (for Windows compatibility) or proxy instead.', E_USER_DEPRECATED); } return $value; case 'discard-changes': $env = $this->getComposerEnv('COMPOSER_DISCARD_CHANGES'); if ($env !== false) { if (!in_array($env, ['stash', 'true', 'false', '1', '0'], true)) { throw new \RuntimeException( "Invalid value for COMPOSER_DISCARD_CHANGES: {$env}. Expected 1, 0, true, false or stash" ); } if ('stash' === $env) { return 'stash'; } return $env !== 'false' && (bool) $env; } if (!in_array($this->config[$key], [true, false, 'stash'], true)) { throw new \RuntimeException( "Invalid value for 'discard-changes': {$this->config[$key]}. Expected true, false or stash" ); } return $this->config[$key]; case 'github-protocols': $protos = $this->config['github-protocols']; if ($this->config['secure-http'] && false !== ($index = array_search('git', $protos))) { unset($protos[$index]); } if (reset($protos) === 'http') { throw new \RuntimeException('The http protocol for github is not available anymore, update your config\'s github-protocols to use "https", "git" or "ssh"'); } return $protos; case 'autoloader-suffix': if ($this->config[$key] === '') { return null; } return $this->process($this->config[$key], $flags); case 'audit': $result = $this->config[$key]; $abandonedEnv = $this->getComposerEnv('COMPOSER_AUDIT_ABANDONED'); if (false !== $abandonedEnv) { if (!in_array($abandonedEnv, ListPolicyConfig::AUDITS, true)) { throw new \RuntimeException( "Invalid value for COMPOSER_AUDIT_ABANDONED: {$abandonedEnv}. Expected one of ".implode(', ', ListPolicyConfig::AUDITS)."." ); } $result['abandoned'] = $abandonedEnv; } $blockAbandonedEnv = $this->getComposerEnv('COMPOSER_SECURITY_BLOCKING_ABANDONED'); if (false !== $blockAbandonedEnv) { $result['block-abandoned'] = Platform::getBoolEnv('COMPOSER_SECURITY_BLOCKING_ABANDONED'); } return $result; case 'policy': $policyConfig = $this->config[$key]; $policyEnv = Platform::getBoolEnv('COMPOSER_POLICY'); if (false === $policyEnv) { $policyConfig = false; } elseif (true === $policyEnv && false === $policyConfig) { $policyConfig = true; } return $policyConfig; default: if (!isset($this->config[$key])) { return null; } return $this->process($this->config[$key], $flags); } } public function all(int $flags = 0): array { $all = [ 'repositories' => $this->getRepositories(), ]; foreach (array_keys($this->config) as $key) { $all['config'][$key] = $this->get($key, $flags); } return $all; } public function getSourceOfValue(string $key): string { $this->get($key); return $this->sourceOfConfigValue[$key] ?? self::SOURCE_UNKNOWN; } private function setSourceOfConfigValue($configValue, string $path, string $source): void { $this->sourceOfConfigValue[$path] = $source; if (is_array($configValue)) { foreach ($configValue as $key => $value) { $this->setSourceOfConfigValue($value, $path . '.' . $key, $source); } } } public function raw(): array { return [ 'repositories' => $this->getRepositories(), 'config' => $this->config, ]; } public function has(string $key): bool { return array_key_exists($key, $this->config); } private function process($value, int $flags) { if (!is_string($value)) { return $value; } return Preg::replaceCallback('#\{\$(.+)\}#', function ($match) use ($flags) { return $this->get($match[1], $flags); }, $value); } private function realpath(string $path): string { if (Preg::isMatch('{^(?:/|[a-z]:|[a-z0-9.]+://|\\\\\\\\)}i', $path)) { return $path; } return $this->baseDir !== null ? $this->baseDir . '/' . $path : $path; } private function getComposerEnv(string $var) { if ($this->useEnvironment) { return Platform::getEnv($var); } return false; } private function disableRepoByName(string $name): void { if (isset($this->repositories[$name])) { unset($this->repositories[$name]); } elseif ($name === 'packagist') { unset($this->repositories['packagist.org']); } } public function prohibitUrlByConfig(string $url, ?IOInterface $io = null, array $repoOptions = []): void { if (false === filter_var($url, FILTER_VALIDATE_URL) && !Preg::isMatch('{^https?://}i', $url)) { return; } $scheme = strtolower((string) parse_url($url, PHP_URL_SCHEME)); $hostname = parse_url($url, PHP_URL_HOST); if (in_array($scheme, ['http', 'git', 'ftp', 'svn'])) { if ($this->get('secure-http')) { if ($scheme === 'svn') { if (in_array($hostname, $this->get('secure-svn-domains'), true)) { return; } throw new TransportException("Your configuration does not allow connections to " . Url::sanitize($url) . ". See https://getcomposer.org/doc/06-config.md#secure-svn-domains for details."); } throw new TransportException("Your configuration does not allow connections to " . Url::sanitize($url) . ". See https://getcomposer.org/doc/06-config.md#secure-http for details."); } if ($io !== null) { if (is_string($hostname)) { if (!isset($this->warnedHosts[$hostname])) { $io->writeError("Warning: Accessing $hostname over $scheme which is an insecure protocol."); } $this->warnedHosts[$hostname] = true; } } } if ($io !== null && is_string($hostname) && !isset($this->sslVerifyWarnedHosts[$hostname])) { $warning = null; if (isset($repoOptions['ssl']['verify_peer']) && !(bool) $repoOptions['ssl']['verify_peer']) { $warning = 'verify_peer'; } if (isset($repoOptions['ssl']['verify_peer_name']) && !(bool) $repoOptions['ssl']['verify_peer_name']) { $warning = $warning === null ? 'verify_peer_name' : $warning . ' and verify_peer_name'; } if ($warning !== null) { $io->writeError("Warning: Accessing $hostname with $warning disabled."); $this->sslVerifyWarnedHosts[$hostname] = true; } } } public static function disableProcessTimeout(): void { ProcessExecutor::setTimeout(0); } } file = $file; $this->authConfig = $authConfig; } public function getName(): string { return $this->file->getPath(); } public function addRepository(string $name, $config, bool $append = true): void { $this->manipulateJson('addRepository', static function (&$config, $repo, $repoConfig) use ($append): void { if (!array_is_list($config['repositories'] ?? [])) { $list = []; foreach ($config['repositories'] as $repositoryIndex => $repository) { if (is_string($repositoryIndex) && is_array($repository)) { if (!isset($repository['name'])) { $repository = ['name' => $repositoryIndex] + $repository; } $list[] = $repository; } elseif (is_string($repositoryIndex)) { $list[] = [$repositoryIndex => $repository]; } else { $list[] = $repository; } } $config['repositories'] = $list; } if ($repoConfig === false) { if (isset($config['repositories'])) { foreach ($config['repositories'] as &$repository) { if (($repository['name'] ?? null) === $repo) { $repository = [$repo => $repoConfig]; return; } if ($repository === [$repo => false]) { return; } } unset($repository); } else { $config['repositories'] = []; } $config['repositories'][] = [$repo => $repoConfig]; return; } if (is_array($repoConfig) && $repo !== '' && !isset($repoConfig['name'])) { $repoConfig = ['name' => $repo] + $repoConfig; } $config['repositories'] = array_values(array_filter($config['repositories'] ?? [], static function ($val) use ($repo) { return !isset($val['name']) || $val['name'] !== $repo || $val !== [$repo => false]; })); if ($append) { $config['repositories'][] = $repoConfig; } else { array_unshift($config['repositories'], $repoConfig); } }, $name, $config, $append); } public function insertRepository(string $name, $config, string $referenceName, int $offset = 0): void { $this->manipulateJson('insertRepository', static function (&$config, string $name, $repoConfig, string $referenceName, int $offset): void { if (!array_is_list($config['repositories'] ?? [])) { $list = []; foreach ($config['repositories'] as $repositoryIndex => $repository) { if (is_string($repositoryIndex) && is_array($repository)) { if (!isset($repository['name'])) { $repository = ['name' => $repositoryIndex] + $repository; } $list[] = $repository; } elseif (is_string($repositoryIndex)) { $list[] = [$repositoryIndex => $repository]; } else { $list[] = $repository; } } $config['repositories'] = $list; } $config['repositories'] = array_values(array_filter($config['repositories'] ?? [], static function ($val) use ($name) { return !isset($val['name']) || $val['name'] !== $name || $val !== [$name => false]; })); $indexToInsert = null; foreach ($config['repositories'] as $repositoryIndex => $repository) { if (($repository['name'] ?? null) === $referenceName) { $indexToInsert = $repositoryIndex; break; } if ([$referenceName => false] === $repository) { $indexToInsert = $repositoryIndex; break; } } if ($indexToInsert === null) { throw new \RuntimeException(sprintf('The referenced repository "%s" does not exist.', $referenceName)); } if (is_array($repoConfig) && $name !== '' && !isset($repoConfig['name'])) { $repoConfig = ['name' => $name] + $repoConfig; } array_splice($config['repositories'], $indexToInsert + $offset, 0, [$repoConfig]); }, $name, $config, $referenceName, $offset); } public function setRepositoryUrl(string $name, string $url): void { $this->manipulateJson('setRepositoryUrl', static function (&$config, $name, $url): void { foreach ($config['repositories'] ?? [] as $index => $repository) { if ($name === $index) { $config['repositories'][$index]['url'] = $url; return; } if ($name === ($repository['name'] ?? null)) { $config['repositories'][$index]['url'] = $url; return; } } }, $name, $url); } public function removeRepository(string $name): void { $this->manipulateJson('removeRepository', static function (&$config, $repo): void { if (isset($config['repositories'][$repo])) { unset($config['repositories'][$repo]); } else { $config['repositories'] = array_values(array_filter($config['repositories'] ?? [], static function ($val) use ($repo) { return !isset($val['name']) || $val['name'] !== $repo || $val !== [$repo => false]; })); } if ([] === $config['repositories']) { unset($config['repositories']); } }, $name); } public function addConfigSetting(string $name, $value): void { $authConfig = $this->authConfig; $this->manipulateJson('addConfigSetting', static function (&$config, $key, $val) use ($authConfig): void { if (Preg::isMatch('{^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|bearer|http-basic|custom-headers|forgejo-token|platform)\.}', $key)) { [$key, $host] = explode('.', $key, 2); if ($authConfig) { $config[$key][$host] = $val; } else { $config['config'][$key][$host] = $val; } } elseif (strpos($key, 'policy.') === 0) { if (!isset($config['config']) || !is_array($config['config'])) { $config['config'] = []; } $bits = explode('.', $key); $last = array_pop($bits); $arr = &$config['config']; foreach ($bits as $bit) { if (!isset($arr[$bit]) || !is_array($arr[$bit])) { $arr[$bit] = []; } $arr = &$arr[$bit]; } $arr[$last] = $val; } else { $config['config'][$key] = $val; } }, $name, $value); } public function removeConfigSetting(string $name): void { $authConfig = $this->authConfig; $this->manipulateJson('removeConfigSetting', static function (&$config, $key) use ($authConfig): void { if (Preg::isMatch('{^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|bearer|http-basic|custom-headers|forgejo-token|platform)\.}', $key)) { [$key, $host] = explode('.', $key, 2); if ($authConfig) { unset($config[$key][$host]); } else { unset($config['config'][$key][$host]); } } elseif (strpos($key, 'policy.') === 0) { if (!isset($config['config']) || !is_array($config['config'])) { return; } $bits = explode('.', $key); $last = array_pop($bits); $arr = &$config['config']; foreach ($bits as $bit) { if (!isset($arr[$bit]) || !is_array($arr[$bit])) { return; } $arr = &$arr[$bit]; } unset($arr[$last]); unset($arr); while (count($bits) > 0) { $leafKey = array_pop($bits); $parent = &$config['config']; foreach ($bits as $bit) { if (!isset($parent[$bit])) { break 2; } $parent = &$parent[$bit]; } if (isset($parent[$leafKey]) && $parent[$leafKey] === []) { unset($parent[$leafKey]); } else { break; } unset($parent); } } else { unset($config['config'][$key]); } }, $name); } public function addProperty(string $name, $value): void { $this->manipulateJson('addProperty', static function (&$config, $key, $val): void { if (strpos($key, 'extra.') === 0 || strpos($key, 'scripts.') === 0) { $bits = explode('.', $key); $last = array_pop($bits); $arr = &$config[reset($bits)]; foreach ($bits as $bit) { if (!isset($arr[$bit])) { $arr[$bit] = []; } $arr = &$arr[$bit]; } $arr[$last] = $val; } else { $config[$key] = $val; } }, $name, $value); } public function removeProperty(string $name): void { $this->manipulateJson('removeProperty', static function (&$config, $key): void { if (strpos($key, 'extra.') === 0 || strpos($key, 'scripts.') === 0 || stripos($key, 'autoload.') === 0 || stripos($key, 'autoload-dev.') === 0) { $bits = explode('.', $key); $last = array_pop($bits); $arr = &$config[reset($bits)]; foreach ($bits as $bit) { if (!isset($arr[$bit])) { return; } $arr = &$arr[$bit]; } unset($arr[$last]); } else { unset($config[$key]); } }, $name); } public function addLink(string $type, string $name, string $value): void { $this->manipulateJson('addLink', static function (&$config, $type, $name, $value): void { $config[$type][$name] = $value; }, $type, $name, $value); } public function removeLink(string $type, string $name): void { $this->manipulateJson('removeSubNode', static function (&$config, $type, $name): void { unset($config[$type][$name]); }, $type, $name); $this->manipulateJson('removeMainKeyIfEmpty', static function (&$config, $type): void { if (0 === count($config[$type])) { unset($config[$type]); } }, $type); } private function manipulateJson(string $method, callable $fallback, ...$args): void { if ($this->file->exists()) { if (!is_writable($this->file->getPath())) { throw new \RuntimeException(sprintf('The file "%s" is not writable.', $this->file->getPath())); } if (!Filesystem::isReadable($this->file->getPath())) { throw new \RuntimeException(sprintf('The file "%s" is not readable.', $this->file->getPath())); } $contents = file_get_contents($this->file->getPath()); } elseif ($this->authConfig) { $contents = "{\n}\n"; } else { $contents = "{\n \"config\": {\n }\n}\n"; } $manipulator = new JsonManipulator($contents); $newFile = !$this->file->exists(); if ($this->authConfig && $method === 'addConfigSetting') { $method = 'addSubNode'; [$mainNode, $name] = explode('.', $args[0], 2); $args = [$mainNode, $name, $args[1]]; } elseif ($this->authConfig && $method === 'removeConfigSetting') { $method = 'removeSubNode'; [$mainNode, $name] = explode('.', $args[0], 2); $args = [$mainNode, $name]; } if (call_user_func_array([$manipulator, $method], $args)) { file_put_contents($this->file->getPath(), $manipulator->getContents()); } else { $config = $this->file->read(); $this->arrayUnshiftRef($args, $config); $fallback(...$args); if (isset($config['config']['policy']) && is_array($config['config']['policy'])) { foreach ($config['config']['policy'] as $listName => $listValue) { if ($listValue === []) { $config['config']['policy'][$listName] = new \stdClass; } } if ($config['config']['policy'] === []) { $config['config']['policy'] = new \stdClass; } } foreach (['platform', 'http-basic', 'bearer', 'gitlab-token', 'gitlab-oauth', 'github-oauth', 'custom-headers', 'forgejo-token', 'preferred-install'] as $prop) { if (isset($config['config'][$prop]) && $config['config'][$prop] === []) { $config['config'][$prop] = new \stdClass; } } foreach (['psr-0', 'psr-4'] as $prop) { if (isset($config['autoload'][$prop]) && $config['autoload'][$prop] === []) { $config['autoload'][$prop] = new \stdClass; } if (isset($config['autoload-dev'][$prop]) && $config['autoload-dev'][$prop] === []) { $config['autoload-dev'][$prop] = new \stdClass; } } foreach (['require', 'require-dev', 'conflict', 'provide', 'replace', 'suggest', 'config', 'autoload', 'autoload-dev', 'scripts', 'scripts-descriptions', 'scripts-aliases', 'support'] as $prop) { if (isset($config[$prop]) && $config[$prop] === []) { $config[$prop] = new \stdClass; } } $this->file->write($config); } try { $this->file->validateSchema(JsonFile::LAX_SCHEMA); } catch (JsonValidationException $e) { file_put_contents($this->file->getPath(), $contents); throw new \RuntimeException('Failed to update composer.json with a valid format, reverting to the original content. Please report an issue to us with details (command you run and a copy of your composer.json). '.PHP_EOL.implode(PHP_EOL, $e->getErrors()), 0, $e); } if ($newFile) { Silencer::call('chmod', $this->file->getPath(), 0600); } } private function arrayUnshiftRef(array &$array, &$value): int { $return = array_unshift($array, ''); $array[0] = &$value; return $return; } } setCatchErrors(true); } static $shutdownRegistered = false; if ($version === '') { $version = Composer::getVersion(); } if (function_exists('ini_set') && extension_loaded('xdebug')) { ini_set('xdebug.show_exception_trace', '0'); ini_set('xdebug.scream', '0'); } if (function_exists('date_default_timezone_set') && function_exists('date_default_timezone_get')) { date_default_timezone_set(Silencer::call('date_default_timezone_get')); } $this->io = new NullIO(); if (!$shutdownRegistered) { $shutdownRegistered = true; register_shutdown_function(static function (): void { $lastError = error_get_last(); if ($lastError && $lastError['message'] && (strpos($lastError['message'], 'Allowed memory') !== false || strpos($lastError['message'], 'exceeded memory') !== false )) { echo "\n". 'Check https://getcomposer.org/doc/articles/troubleshooting.md#memory-limit-errors for more info on how to handle out of memory errors.'; } }); } $this->initialWorkingDirectory = getcwd(); parent::__construct($name, $version); } public function __destruct() { } public function run(?InputInterface $input = null, ?OutputInterface $output = null): int { if (null === $output) { $output = Factory::createOutput(); } return parent::run($input, $output); } public function doRun(InputInterface $input, OutputInterface $output): int { $this->disablePluginsByDefault = $input->hasParameterOption('--no-plugins'); $this->disableScriptsByDefault = $input->hasParameterOption('--no-scripts'); static $stdin = null; if (null === $stdin) { $stdin = defined('STDIN') ? STDIN : fopen('php://stdin', 'r'); } if (Platform::getEnv('COMPOSER_TESTS_ARE_RUNNING') !== '1' && (Platform::getEnv('COMPOSER_NO_INTERACTION') || $stdin === false || !Platform::isTty($stdin))) { $input->setInteractive(false); } $io = $this->io = new ConsoleIO($input, $output, new HelperSet([ new QuestionHelper(), ])); ErrorHandler::register($io); if ($input->hasParameterOption('--no-cache')) { $io->writeError('Disabling cache usage', true, IOInterface::DEBUG); Platform::putEnv('COMPOSER_CACHE_DIR', Platform::isWindows() ? 'nul' : '/dev/null'); } $newWorkDir = $this->getNewWorkingDir($input); if (null !== $newWorkDir) { $oldWorkingDir = Platform::getCwd(true); chdir($newWorkDir); $this->initialWorkingDirectory = getcwd(); $cwd = Platform::getCwd(true); $io->writeError('Changed CWD to ' . ($cwd !== '' ? $cwd : $newWorkDir), true, IOInterface::DEBUG); } $commandName = ''; if ($rawCommandName = $this->getCommandNameBeforeBinding($input)) { try { $commandName = $this->find($rawCommandName)->getName(); } catch (CommandNotFoundException $e) { $commandName = false; } catch (\InvalidArgumentException $e) { } } $this->commandName = $commandName; if ( null === $newWorkDir && !in_array($commandName, ['', 'list', 'init', 'about', 'help', 'diagnose', 'self-update', 'global', 'create-project', 'outdated'], true) && !file_exists(Factory::getComposerFile()) && ($useParentDirIfNoJsonAvailable = $this->getUseParentDirConfigValue()) !== false && ($commandName !== 'config' || ($input->hasParameterOption('--file', true) === false && $input->hasParameterOption('-f', true) === false)) && $input->hasParameterOption('--help', true) === false && $input->hasParameterOption('-h', true) === false ) { $dir = dirname(Platform::getCwd(true)); $home = realpath(Platform::getEnv('HOME') ?: Platform::getEnv('USERPROFILE') ?: '/'); while (dirname($dir) !== $dir && $dir !== $home) { if (file_exists($dir.'/'.Factory::getComposerFile())) { if ($useParentDirIfNoJsonAvailable !== true && !$io->isInteractive()) { $io->writeError('No composer.json in current directory, to use the one at '.$dir.' run interactively or set config.use-parent-dir to true'); break; } if ($useParentDirIfNoJsonAvailable === true || $io->askConfirmation('No composer.json in current directory, do you want to use the one at '.$dir.'? [y,n]? ')) { if ($useParentDirIfNoJsonAvailable === true) { $io->writeError('No composer.json in current directory, changing working directory to '.$dir.''); } else { $io->writeError('Always want to use the parent dir? Use "composer config --global use-parent-dir true" to change the default.'); } $oldWorkingDir = Platform::getCwd(true); chdir($dir); } break; } $dir = dirname($dir); } unset($dir, $home); } $needsSudoCheck = !Platform::isWindows() && function_exists('exec') && !Platform::getEnv('COMPOSER_ALLOW_SUPERUSER') && !Platform::isDocker(); $isNonAllowedRoot = false; if ($needsSudoCheck) { $isNonAllowedRoot = $this->isRunningAsRoot(); if ($isNonAllowedRoot) { if ($uid = (int) Platform::getEnv('SUDO_UID')) { Silencer::call('exec', "sudo -u \\#{$uid} sudo -K > /dev/null 2>&1"); } } Silencer::call('exec', 'sudo -K > /dev/null 2>&1'); } $mayNeedPluginCommand = false === $input->hasParameterOption(['--version', '-V']) && ( false === $commandName || in_array($commandName, ['', 'list', 'help'], true) || ($commandName === '_complete' && !$isNonAllowedRoot) ); $mayNeedScriptCommand = $mayNeedPluginCommand || $commandName === 'run-script' || $rawCommandName !== $commandName; if ($mayNeedPluginCommand && !$this->disablePluginsByDefault && !$this->hasPluginCommands) { if ($isNonAllowedRoot) { $io->writeError('Do not run Composer as root/super user! See https://getcomposer.org/root for details'); if ($io->isInteractive() && $io->askConfirmation('Continue as root/super user [yes]? ')) { $isNonAllowedRoot = false; } else { $io->writeError('Aborting as no plugin should be loaded if running as super user is not explicitly allowed'); return 1; } } try { foreach ($this->getPluginCommands() as $command) { if ($this->has($command->getName())) { $io->writeError('Plugin command '.$command->getName().' ('.get_class($command).') would override a Composer command and has been skipped'); } else { method_exists($this, 'addCommand') ? $this->addCommand($command) : $this->add($command); } } } catch (NoSslException $e) { } catch (ParsingException $e) { $details = $e->getDetails(); $file = realpath(Factory::getComposerFile()); $line = null; if ($details && isset($details['line'])) { $line = $details['line']; } $ghe = new GithubActionError($this->io); $ghe->emit($e->getMessage(), $file, $line); throw $e; } $this->hasPluginCommands = true; } if (!$this->disablePluginsByDefault && $isNonAllowedRoot && !$io->isInteractive()) { $io->writeError('Composer plugins have been disabled for safety in this non-interactive session.'); $io->writeError('Set COMPOSER_ALLOW_SUPERUSER=1 if you want to allow plugins to run as root/super user.'); $this->disablePluginsByDefault = true; } $isProxyCommand = false; if ($name = $this->getCommandNameBeforeBinding($input)) { try { $command = $this->find($name); $commandName = $command->getName(); $this->commandName = $commandName; $isProxyCommand = ($command instanceof Command\BaseCommand && $command->isProxyCommand()); } catch (\InvalidArgumentException $e) { } } if (!$isProxyCommand) { $io->writeError(sprintf( 'Running %s (%s) with %s on %s', Composer::getVersion(), Composer::RELEASE_DATE, defined('HHVM_VERSION') ? 'HHVM '.HHVM_VERSION : 'PHP '.PHP_VERSION, function_exists('php_uname') ? php_uname('s') . ' / ' . php_uname('r') : 'Unknown OS' ), true, IOInterface::DEBUG); if (\PHP_VERSION_ID < 70205) { $io->writeError('Composer supports PHP 7.2.5 and above, you will most likely encounter problems with your PHP '.PHP_VERSION.'. Upgrading is strongly recommended but you can use Composer 2.2.x LTS as a fallback.'); } if (XdebugHandler::isXdebugActive() && !Platform::getEnv('COMPOSER_DISABLE_XDEBUG_WARN')) { $io->writeError('Composer is operating slower than normal because you have Xdebug enabled. See https://getcomposer.org/xdebug'); } if (defined('COMPOSER_DEV_WARNING_TIME') && $commandName !== 'self-update' && $commandName !== 'selfupdate' && time() > COMPOSER_DEV_WARNING_TIME) { $io->writeError(sprintf('Warning: This development build of Composer is over 60 days old. It is recommended to update it by running "%s self-update" to get the latest version.', $_SERVER['PHP_SELF'])); } if ($isNonAllowedRoot) { if ($commandName !== 'self-update' && $commandName !== 'selfupdate' && $commandName !== '_complete') { $io->writeError('Do not run Composer as root/super user! See https://getcomposer.org/root for details'); if ($io->isInteractive()) { if (!$io->askConfirmation('Continue as root/super user [yes]? ')) { return 1; } } } } Silencer::call(static function () use ($io): void { $pid = function_exists('getmypid') ? getmypid() . '-' : ''; $tempfile = sys_get_temp_dir() . '/temp-' . $pid . bin2hex(random_bytes(5)); if (!(file_put_contents($tempfile, __FILE__) && (file_get_contents($tempfile) === __FILE__) && unlink($tempfile) && !file_exists($tempfile))) { $io->writeError(sprintf('PHP temp directory (%s) does not exist or is not writable to Composer. Set sys_temp_dir in your php.ini', sys_get_temp_dir())); } }); $file = Factory::getComposerFile(); if ($mayNeedScriptCommand && is_file($file) && Filesystem::isReadable($file) && is_array($composerJson = json_decode(file_get_contents($file), true))) { if (isset($composerJson['scripts']) && is_array($composerJson['scripts'])) { $projectLoaderRegistered = false; foreach ($composerJson['scripts'] as $script => $dummy) { if (!defined('Composer\Script\ScriptEvents::'.str_replace('-', '_', strtoupper($script)))) { if ($this->has($script)) { $io->writeError('A script named '.$script.' would override a Composer command and has been skipped'); } else { $description = 'Runs the '.$script.' script as defined in composer.json'; if (isset($composerJson['scripts-descriptions'][$script])) { $description = $composerJson['scripts-descriptions'][$script]; } $aliases = $composerJson['scripts-aliases'][$script] ?? []; if (!$projectLoaderRegistered) { $projectLoaderRegistered = true; $composer = $this->getComposer(false); if ($composer !== null) { $rootPackage = $composer->getPackage(); $generator = $composer->getAutoloadGenerator(); $packageMap = $generator->buildPackageMap($composer->getInstallationManager(), $rootPackage, []); $map = $generator->parseAutoloads($packageMap, $rootPackage); $loader = $generator->createLoader($map, $composer->getConfig()->get('vendor-dir')); $loader->register(false); } } if (is_string($dummy) && class_exists($dummy) && is_subclass_of($dummy, SymfonyCommand::class)) { if (is_subclass_of($dummy, SingleCommandApplication::class)) { $io->writeError('The script named '.$script.' extends SingleCommandApplication which is not compatible with Composer 2.9+, make sure you extend Symfony\Component\Console\Command instead.'); } $cmd = new $dummy($script); if ($cmd->getName() !== '' && $cmd->getName() !== null && $cmd->getName() !== $script) { $io->writeError('The script named '.$script.' in composer.json has a mismatched name in its class definition. For consistency, either use the same name, or do not define one inside the class.'); $cmd->setName($script); } if ($cmd->getDescription() === '' && is_string($description)) { $cmd->setDescription($description); } } else { $cmd = new Command\ScriptAliasCommand($script, $description, $aliases); } method_exists($this, 'addCommand') ? $this->addCommand($cmd) : $this->add($cmd); } } } } } } try { if ($input->hasParameterOption('--profile')) { $startTime = microtime(true); $this->io->enableDebugging($startTime); } $result = parent::doRun($input, $output); if (true === $input->hasParameterOption(['--version', '-V'], true)) { $io->writeError(sprintf('PHP version %s (%s)', \PHP_VERSION, \PHP_BINARY)); $io->writeError('Run the "diagnose" command to get more detailed diagnostics output.'); } if (isset($oldWorkingDir) && '' !== $oldWorkingDir) { Silencer::call('chdir', $oldWorkingDir); } if (isset($startTime)) { $io->writeError('Memory usage: '.round(memory_get_usage() / 1024 / 1024, 2).'MiB (peak: '.round(memory_get_peak_usage() / 1024 / 1024, 2).'MiB), time: '.round(microtime(true) - $startTime, 2).'s'); } return $result; } catch (ScriptExecutionException $e) { if ($this->getDisablePluginsByDefault() && $this->isRunningAsRoot() && !$this->io->isInteractive()) { $io->writeError('Plugins have been disabled automatically as you are running as root, this may be the cause of the script failure.', true, IOInterface::QUIET); $io->writeError('See also https://getcomposer.org/root', true, IOInterface::QUIET); } return $e->getCode(); } catch (\Throwable $e) { $ghe = new GithubActionError($this->io); $ghe->emit($e->getMessage()); $this->hintCommonErrors($e, $output); if (!method_exists($this, 'setCatchErrors') && !$e instanceof \Exception) { if ($output instanceof ConsoleOutputInterface) { $this->renderThrowable($e, $output->getErrorOutput()); } else { $this->renderThrowable($e, $output); } return max(1, $e->getCode()); } if ($e instanceof TransportException) { $reflProp = new \ReflectionProperty($e, 'code'); (\PHP_VERSION_ID < 80100) and $reflProp->setAccessible(true); $reflProp->setValue($e, Installer::ERROR_TRANSPORT_EXCEPTION); } throw $e; } finally { restore_error_handler(); } } private function getNewWorkingDir(InputInterface $input): ?string { $workingDir = $input->getParameterOption(['--working-dir', '-d'], null, true); if (null !== $workingDir && !is_dir($workingDir)) { throw new RuntimeException('Invalid working directory specified, '.$workingDir.' does not exist.'); } return $workingDir; } private function hintCommonErrors(\Throwable $exception, OutputInterface $output): void { $io = $this->getIO(); if ((get_class($exception) === LogicException::class || $exception instanceof \Error) && $output->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE) { $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); } Silencer::suppress(); try { $composer = $this->getComposer(false, true); if (null !== $composer && function_exists('disk_free_space')) { $config = $composer->getConfig(); $minSpaceFree = 100 * 1024 * 1024; if ((($df = disk_free_space($dir = $config->get('home'))) !== false && $df < $minSpaceFree) || (($df = disk_free_space($dir = $config->get('vendor-dir'))) !== false && $df < $minSpaceFree) || (($df = disk_free_space($dir = sys_get_temp_dir())) !== false && $df < $minSpaceFree) ) { $io->writeError('The disk hosting '.$dir.' has less than 100MiB of free space, this may be the cause of the following exception', true, IOInterface::QUIET); } } } catch (\Exception $e) { } Silencer::restore(); if ($exception instanceof TransportException && str_contains($exception->getMessage(), 'Unable to use a proxy')) { $io->writeError('The following exception indicates your proxy is misconfigured', true, IOInterface::QUIET); $io->writeError('Check https://getcomposer.org/doc/faqs/how-to-use-composer-behind-a-proxy.md for details', true, IOInterface::QUIET); } if (Platform::isWindows() && $exception instanceof TransportException && str_contains($exception->getMessage(), 'unable to get local issuer certificate')) { $avastDetect = glob('C:\Program Files\Avast*'); if (is_array($avastDetect) && count($avastDetect) !== 0) { $io->writeError('The following exception indicates a possible issue with the Avast Firewall', true, IOInterface::QUIET); $io->writeError('Check https://getcomposer.org/local-issuer for details', true, IOInterface::QUIET); } else { $io->writeError('The following exception indicates a possible issue with a Firewall/Antivirus', true, IOInterface::QUIET); $io->writeError('Check https://getcomposer.org/local-issuer for details', true, IOInterface::QUIET); } } if (Platform::isWindows() && false !== strpos($exception->getMessage(), 'The system cannot find the path specified')) { $io->writeError('The following exception may be caused by a stale entry in your cmd.exe AutoRun', true, IOInterface::QUIET); $io->writeError('Check https://getcomposer.org/doc/articles/troubleshooting.md#-the-system-cannot-find-the-path-specified-windows- for details', true, IOInterface::QUIET); } if (false !== strpos($exception->getMessage(), 'fork failed - Cannot allocate memory')) { $io->writeError('The following exception is caused by a lack of memory or swap, or not having swap configured', true, IOInterface::QUIET); $io->writeError('Check https://getcomposer.org/doc/articles/troubleshooting.md#proc-open-fork-failed-errors for details', true, IOInterface::QUIET); } if ($exception instanceof ProcessTimedOutException) { $io->writeError('The following exception is caused by a process timeout', true, IOInterface::QUIET); $io->writeError('Check https://getcomposer.org/doc/06-config.md#process-timeout for details', true, IOInterface::QUIET); } if ($this->getDisablePluginsByDefault() && $this->isRunningAsRoot() && !$this->io->isInteractive()) { $io->writeError('Plugins have been disabled automatically as you are running as root, this may be the cause of the following exception. See also https://getcomposer.org/root', true, IOInterface::QUIET); } elseif ($exception instanceof CommandNotFoundException && $this->getDisablePluginsByDefault()) { $io->writeError('Plugins have been disabled, which may be why some commands are missing, unless you made a typo', true, IOInterface::QUIET); } $hints = HttpDownloader::getExceptionHints($exception); if (null !== $hints && count($hints) > 0) { foreach ($hints as $hint) { $io->writeError($hint, true, IOInterface::QUIET); } } if ( $exception instanceof TransportException && $this->commandName !== 'self-update' && ( false !== strpos($exception->getMessage(), 'curl error 28 ') || false !== strpos($exception->getMessage(), 'Resolving timed out') || false !== strpos($exception->getMessage(), 'Could not resolve host') ) ) { $io->writeError('If you intend to run Composer without connecting to the internet, run the command again prefixed with COMPOSER_DISABLE_NETWORK=1 to make Composer run in offline mode.', true, IOInterface::QUIET); } } public function getComposer(bool $required = true, ?bool $disablePlugins = null, ?bool $disableScripts = null): ?Composer { if (null === $disablePlugins) { $disablePlugins = $this->disablePluginsByDefault; } if (null === $disableScripts) { $disableScripts = $this->disableScriptsByDefault; } if (null === $this->composer) { try { $this->composer = Factory::create(Platform::isInputCompletionProcess() ? new NullIO() : $this->io, null, $disablePlugins, $disableScripts); } catch (\InvalidArgumentException $e) { if ($required) { $this->io->writeError($e->getMessage()); if ($this->areExceptionsCaught()) { exit(1); } throw $e; } } catch (JsonValidationException $e) { if ($required) { throw $e; } } catch (RuntimeException $e) { if ($required) { throw $e; } } } return $this->composer; } public function resetComposer(): void { $this->composer = null; if (method_exists($this->getIO(), 'resetAuthentications')) { $this->getIO()->resetAuthentications(); } } public function getIO(): IOInterface { return $this->io; } public function getHelp(): string { return self::$logo . parent::getHelp(); } protected function getDefaultCommands(): array { return array_merge(parent::getDefaultCommands(), [ new Command\AboutCommand(), new Command\ConfigCommand(), new Command\DependsCommand(), new Command\ProhibitsCommand(), new Command\InitCommand(), new Command\InstallCommand(), new Command\CreateProjectCommand(), new Command\UpdateCommand(), new Command\SearchCommand(), new Command\ValidateCommand(), new Command\AuditCommand(), new Command\ShowCommand(), new Command\SuggestsCommand(), new Command\RequireCommand(), new Command\DumpAutoloadCommand(), new Command\StatusCommand(), new Command\ArchiveCommand(), new Command\DiagnoseCommand(), new Command\RunScriptCommand(), new Command\LicensesCommand(), new Command\GlobalCommand(), new Command\ClearCacheCommand(), new Command\RemoveCommand(), new Command\HomeCommand(), new Command\ExecCommand(), new Command\OutdatedCommand(), new Command\CheckPlatformReqsCommand(), new Command\FundCommand(), new Command\ReinstallCommand(), new Command\BumpCommand(), new Command\RepositoryCommand(), new Command\PolicyCommand(), new Command\SelfUpdateCommand(), ]); } private function getCommandNameBeforeBinding(InputInterface $input): ?string { $input = clone $input; try { $input->bind($this->getDefinition()); } catch (ExceptionInterface $e) { } return $input->getFirstArgument(); } public function getLongVersion(): string { $branchAliasString = ''; if (Composer::BRANCH_ALIAS_VERSION && Composer::BRANCH_ALIAS_VERSION !== '@package_branch_alias_version'.'@') { $branchAliasString = sprintf(' (%s)', Composer::BRANCH_ALIAS_VERSION); } return sprintf( '%s version %s%s %s', $this->getName(), $this->getVersion(), $branchAliasString, Composer::RELEASE_DATE ); } protected function getDefaultInputDefinition(): InputDefinition { $definition = parent::getDefaultInputDefinition(); $definition->addOption(new InputOption('--profile', null, InputOption::VALUE_NONE, 'Display timing and memory usage information')); $definition->addOption(new InputOption('--no-plugins', null, InputOption::VALUE_NONE, 'Whether to disable plugins.')); $definition->addOption(new InputOption('--no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.')); $definition->addOption(new InputOption('--working-dir', '-d', InputOption::VALUE_REQUIRED, 'If specified, use the given directory as working directory.')); $definition->addOption(new InputOption('--no-cache', null, InputOption::VALUE_NONE, 'Prevent use of the cache')); return $definition; } private function getPluginCommands(): array { $commands = []; $composer = $this->getComposer(false, false); if (null === $composer) { $composer = Factory::createGlobal($this->io, $this->disablePluginsByDefault, $this->disableScriptsByDefault); } if (null !== $composer) { $pm = $composer->getPluginManager(); foreach ($pm->getPluginCapabilities('Composer\Plugin\Capability\CommandProvider', ['composer' => $composer, 'io' => $this->io]) as $capability) { $newCommands = $capability->getCommands(); if (!is_array($newCommands)) { throw new \UnexpectedValueException('Plugin capability '.get_class($capability).' failed to return an array from getCommands'); } foreach ($newCommands as $command) { if (!$command instanceof Command\BaseCommand) { throw new \UnexpectedValueException('Plugin capability '.get_class($capability).' returned an invalid value, we expected an array of Composer\Command\BaseCommand objects'); } } $commands = array_merge($commands, $newCommands); } } return $commands; } public function getInitialWorkingDirectory() { return $this->initialWorkingDirectory; } public function getDisablePluginsByDefault(): bool { return $this->disablePluginsByDefault; } public function getDisableScriptsByDefault(): bool { return $this->disableScriptsByDefault; } private function getUseParentDirConfigValue() { $config = Factory::createConfig($this->io); return $config->get('use-parent-dir'); } private function isRunningAsRoot(): bool { return function_exists('posix_getuid') && posix_getuid() === 0; } } io = $io; } public function emit(string $message, ?string $file = null, ?int $line = null): void { if (Platform::getEnv('GITHUB_ACTIONS') && !Platform::getEnv('COMPOSER_TESTS_ARE_RUNNING')) { $message = $this->escapeData($message); if ($file && $line) { $file = $this->escapeProperty($file); $this->io->write("::error file=". $file .",line=". $line ."::". $message); } elseif ($file) { $file = $this->escapeProperty($file); $this->io->write("::error file=". $file ."::". $message); } else { $this->io->write("::error ::". $message); } } } private function escapeData(string $data): string { $data = str_replace("%", '%25', $data); $data = str_replace("\r", '%0D', $data); $data = str_replace("\n", '%0A', $data); return $data; } private function escapeProperty(string $property): string { $property = str_replace("%", '%25', $property); $property = str_replace("\r", '%0D', $property); $property = str_replace("\n", '%0A', $property); $property = str_replace(":", '%3A', $property); $property = str_replace(",", '%2C', $property); return $property; } } 'black', 31 => 'red', 32 => 'green', 33 => 'yellow', 34 => 'blue', 35 => 'magenta', 36 => 'cyan', 37 => 'white', ]; private static $availableBackgroundColors = [ 40 => 'black', 41 => 'red', 42 => 'green', 43 => 'yellow', 44 => 'blue', 45 => 'magenta', 46 => 'cyan', 47 => 'white', ]; private static $availableOptions = [ 1 => 'bold', 4 => 'underscore', ]; public function __construct(array $styles = []) { parent::__construct(true, $styles); } public function format(?string $message): ?string { $formatted = parent::format($message); if ($formatted === null) { return null; } $clearEscapeCodes = '(?:39|49|0|22|24|25|27|28)'; return Preg::replaceCallback("{\033\[([0-9;]+)m(.*?)\033\[(?:".$clearEscapeCodes.";)*?".$clearEscapeCodes."m}s", Closure::fromCallable([$this, 'formatHtml']), $formatted); } private function formatHtml(array $matches): string { assert(is_string($matches[1])); $out = ''.$matches[2].''; } } suggestedValues = $suggestedValues; } public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void { $values = $this->suggestedValues; if ($values instanceof \Closure && !\is_array($values = $values($input, $suggestions))) { throw new LogicException(sprintf('Closure for option "%s" must return an array. Got "%s".', $this->getName(), get_debug_type($values))); } if ([] !== $values) { $suggestions->suggestValues($values); } } } suggestedValues = $suggestedValues; if ([] !== $suggestedValues && !$this->acceptValue()) { throw new LogicException('Cannot set suggested values if the option does not accept a value.'); } } public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void { $values = $this->suggestedValues; if ($values instanceof \Closure && !\is_array($values = $values($input, $suggestions))) { throw new LogicException(sprintf('Closure for argument "%s" must return an array. Got "%s".', $this->getName(), get_debug_type($values))); } if ([] !== $values) { $suggestions->suggestValues($values); } } } pool = $pool; $this->decisionMap = []; } public function decide(int $literal, int $level, Rule $why): void { $this->addDecision($literal, $level); $this->decisionQueue[] = [ self::DECISION_LITERAL => $literal, self::DECISION_REASON => $why, ]; } public function satisfy(int $literal): bool { $packageId = abs($literal); return ( $literal > 0 && isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] > 0 || $literal < 0 && isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] < 0 ); } public function conflict(int $literal): bool { $packageId = abs($literal); return ( (isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] > 0 && $literal < 0) || (isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] < 0 && $literal > 0) ); } public function decided(int $literalOrPackageId): bool { return ($this->decisionMap[abs($literalOrPackageId)] ?? 0) !== 0; } public function undecided(int $literalOrPackageId): bool { return ($this->decisionMap[abs($literalOrPackageId)] ?? 0) === 0; } public function decidedInstall(int $literalOrPackageId): bool { $packageId = abs($literalOrPackageId); return isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] > 0; } public function decisionLevel(int $literalOrPackageId): int { $packageId = abs($literalOrPackageId); if (isset($this->decisionMap[$packageId])) { return abs($this->decisionMap[$packageId]); } return 0; } public function decisionRule(int $literalOrPackageId): Rule { $packageId = abs($literalOrPackageId); foreach ($this->decisionQueue as $decision) { if ($packageId === abs($decision[self::DECISION_LITERAL])) { return $decision[self::DECISION_REASON]; } } throw new \LogicException('Did not find a decision rule using '.$literalOrPackageId); } public function atOffset(int $queueOffset): array { return $this->decisionQueue[$queueOffset]; } public function validOffset(int $queueOffset): bool { return $queueOffset >= 0 && $queueOffset < \count($this->decisionQueue); } public function lastReason(): Rule { return $this->decisionQueue[\count($this->decisionQueue) - 1][self::DECISION_REASON]; } public function lastLiteral(): int { return $this->decisionQueue[\count($this->decisionQueue) - 1][self::DECISION_LITERAL]; } public function reset(): void { while ($decision = array_pop($this->decisionQueue)) { $this->decisionMap[abs($decision[self::DECISION_LITERAL])] = 0; } } public function resetToOffset(int $offset): void { while (\count($this->decisionQueue) > $offset + 1) { $decision = array_pop($this->decisionQueue); $this->decisionMap[abs($decision[self::DECISION_LITERAL])] = 0; } } public function revertLast(): void { $this->decisionMap[abs($this->lastLiteral())] = 0; array_pop($this->decisionQueue); } public function count(): int { return \count($this->decisionQueue); } public function rewind(): void { end($this->decisionQueue); } #[\ReturnTypeWillChange] public function current() { return current($this->decisionQueue); } public function key(): ?int { return key($this->decisionQueue); } public function next(): void { prev($this->decisionQueue); } public function valid(): bool { return false !== current($this->decisionQueue); } public function isEmpty(): bool { return \count($this->decisionQueue) === 0; } protected function addDecision(int $literal, int $level): void { $packageId = abs($literal); $previousDecision = $this->decisionMap[$packageId] ?? 0; if ($previousDecision !== 0) { $literalString = $this->pool->literalToPrettyString($literal, []); $package = $this->pool->literalToPackage($literal); throw new SolverBugException( "Trying to decide $literalString on level $level, even though $package was previously decided as ".$previousDecision."." ); } if ($literal > 0) { $this->decisionMap[$packageId] = $level; } else { $this->decisionMap[$packageId] = -$level; } } public function toString(?Pool $pool = null): string { $decisionMap = $this->decisionMap; ksort($decisionMap); $str = '['; foreach ($decisionMap as $packageId => $level) { $str .= ($pool !== null ? $pool->literalToPackage($packageId) : $packageId).':'.$level.','; } $str .= ']'; return $str; } public function __toString(): string { return $this->toString(); } } preferStable = $preferStable; $this->preferLowest = $preferLowest; $this->preferredVersions = $preferredVersions; $this->preferDevOverPrerelease = (bool) Platform::getEnv('COMPOSER_PREFER_DEV_OVER_PRERELEASE'); } public function versionCompare(PackageInterface $a, PackageInterface $b, string $operator): bool { if ($this->preferStable && ($stabA = $a->getStability()) !== ($stabB = $b->getStability())) { if ($this->preferLowest && $this->preferDevOverPrerelease && 'stable' !== $stabA && 'stable' !== $stabB) { $stabA = 'dev' === $stabA ? 'stable' : $stabA; $stabB = 'dev' === $stabB ? 'stable' : $stabB; } return BasePackage::STABILITIES[$stabA] < BasePackage::STABILITIES[$stabB]; } if (($a->isDev() && str_starts_with($a->getVersion(), 'dev-')) || ($b->isDev() && str_starts_with($b->getVersion(), 'dev-'))) { $constraint = new Constraint($operator, $b->getVersion()); $version = new Constraint('==', $a->getVersion()); return $constraint->matchSpecific($version, true); } return CompilingMatcher::match(new Constraint($operator, $b->getVersion()), Constraint::OP_EQ, $a->getVersion()); } public function selectPreferredPackages(Pool $pool, array $literals, ?string $requiredPackage = null): array { sort($literals); $resultCacheKey = implode(',', $literals).$requiredPackage; $poolId = spl_object_id($pool); if (isset($this->preferredPackageResultCachePerPool[$poolId][$resultCacheKey])) { return $this->preferredPackageResultCachePerPool[$poolId][$resultCacheKey]; } $packages = $this->groupLiteralsByName($pool, $literals); foreach ($packages as &$nameLiterals) { usort($nameLiterals, function ($a, $b) use ($pool, $requiredPackage, $poolId): int { $cacheKey = 'i'.$a.'.'.$b.$requiredPackage; if (isset($this->sortingCachePerPool[$poolId][$cacheKey])) { return $this->sortingCachePerPool[$poolId][$cacheKey]; } return $this->sortingCachePerPool[$poolId][$cacheKey] = $this->compareByPriority($pool, $pool->literalToPackage($a), $pool->literalToPackage($b), $requiredPackage, true); }); } foreach ($packages as &$sortedLiterals) { $sortedLiterals = $this->pruneToBestVersion($pool, $sortedLiterals); $sortedLiterals = $this->pruneRemoteAliases($pool, $sortedLiterals); } $selected = array_merge(...array_values($packages)); usort($selected, function ($a, $b) use ($pool, $requiredPackage, $poolId): int { $cacheKey = $a.'.'.$b.$requiredPackage; if (isset($this->sortingCachePerPool[$poolId][$cacheKey])) { return $this->sortingCachePerPool[$poolId][$cacheKey]; } return $this->sortingCachePerPool[$poolId][$cacheKey] = $this->compareByPriority($pool, $pool->literalToPackage($a), $pool->literalToPackage($b), $requiredPackage); }); return $this->preferredPackageResultCachePerPool[$poolId][$resultCacheKey] = $selected; } protected function groupLiteralsByName(Pool $pool, array $literals): array { $packages = []; foreach ($literals as $literal) { $packageName = $pool->literalToPackage($literal)->getName(); if (!isset($packages[$packageName])) { $packages[$packageName] = []; } $packages[$packageName][] = $literal; } return $packages; } public function compareByPriority(Pool $pool, BasePackage $a, BasePackage $b, ?string $requiredPackage = null, bool $ignoreReplace = false): int { if ($a->getName() === $b->getName()) { $aAliased = $a instanceof AliasPackage; $bAliased = $b instanceof AliasPackage; if ($aAliased && !$bAliased) { return -1; } if (!$aAliased && $bAliased) { return 1; } } if (!$ignoreReplace) { if ($this->replaces($a, $b)) { return 1; } if ($this->replaces($b, $a)) { return -1; } if ($requiredPackage !== null && false !== ($pos = strpos($requiredPackage, '/'))) { $requiredVendor = substr($requiredPackage, 0, $pos); $aIsSameVendor = strpos($a->getName(), $requiredVendor) === 0; $bIsSameVendor = strpos($b->getName(), $requiredVendor) === 0; if ($bIsSameVendor !== $aIsSameVendor) { return $aIsSameVendor ? -1 : 1; } } } if ($a->id === $b->id) { return 0; } return ($a->id < $b->id) ? -1 : 1; } protected function replaces(BasePackage $source, BasePackage $target): bool { foreach ($source->getReplaces() as $link) { if ($link->getTarget() === $target->getName() ) { return true; } } return false; } protected function pruneToBestVersion(Pool $pool, array $literals): array { if ($this->preferredVersions !== null) { $name = $pool->literalToPackage($literals[0])->getName(); if (isset($this->preferredVersions[$name])) { $preferredVersion = $this->preferredVersions[$name]; $bestLiterals = []; foreach ($literals as $literal) { if ($pool->literalToPackage($literal)->getVersion() === $preferredVersion) { $bestLiterals[] = $literal; } } if (\count($bestLiterals) > 0) { return $bestLiterals; } } } $operator = $this->preferLowest ? '<' : '>'; $bestLiterals = [$literals[0]]; $bestPackage = $pool->literalToPackage($literals[0]); foreach ($literals as $i => $literal) { if (0 === $i) { continue; } $package = $pool->literalToPackage($literal); if ($this->versionCompare($package, $bestPackage, $operator)) { $bestPackage = $package; $bestLiterals = [$literal]; } elseif ($this->versionCompare($package, $bestPackage, '==')) { $bestLiterals[] = $literal; } } return $bestLiterals; } protected function pruneRemoteAliases(Pool $pool, array $literals): array { $hasLocalAlias = false; foreach ($literals as $literal) { $package = $pool->literalToPackage($literal); if ($package instanceof AliasPackage && $package->isRootPackageAlias()) { $hasLocalAlias = true; break; } } if (!$hasLocalAlias) { return $literals; } $selected = []; foreach ($literals as $literal) { $package = $pool->literalToPackage($literal); if ($package instanceof AliasPackage && $package->isRootPackageAlias()) { $selected[] = $literal; } } return $selected; } } policyConfig = $policyConfig; $this->filterListAuditor = $filterListAuditor; $this->httpDownloader = $httpDownloader; $this->blockScope = $blockScope; $this->repositories = $repositories; $this->io = $io; } public function filter(Pool $pool, Request $request): Pool { $checkLockedAgainstInstall = $this->blockScope === ListPolicyConfig::BLOCK_SCOPE_UPDATE; $configuredScopeListNames = $this->policyConfig->getActiveBlockFilterListNames($this->blockScope); $installScopeListNames = $checkLockedAgainstInstall ? $this->policyConfig->getActiveBlockFilterListNames(ListPolicyConfig::BLOCK_SCOPE_INSTALL) : []; $unionListNames = array_values(array_unique(array_merge($configuredScopeListNames, $installScopeListNames))); if (count($unionListNames) === 0) { return $pool; } $ignoreUnreachable = $this->policyConfig->ignoreUnreachable->forBlockScope($this->blockScope); if ($checkLockedAgainstInstall) { $ignoreUnreachable = $ignoreUnreachable && $this->policyConfig->ignoreUnreachable->forBlockScope(ListPolicyConfig::BLOCK_SCOPE_INSTALL); } $providerSet = FilterListProviderSet::create($this->policyConfig, $this->repositories, $this->httpDownloader); $fetchResult = $this->fetchFilterListMap($pool, $providerSet, $unionListNames, $ignoreUnreachable); $unionMap = $fetchResult['filter']; if (count($fetchResult['unreachableRepos']) > 0) { $this->warnUnreachable($fetchResult['unreachableRepos']); } if (count($unionMap) === 0) { return $pool; } $configuredScopeMap = self::filterMapByListNames($unionMap, $configuredScopeListNames); $installScopeMap = $checkLockedAgainstInstall ? self::filterMapByListNames($unionMap, $installScopeListNames) : []; $lockedNameVersionMap = $checkLockedAgainstInstall ? $this->buildLockedNameVersionMap($request) : []; $packages = []; $filterListRemovedVersions = []; foreach ($pool->getPackages() as $package) { if (!self::isFilterable($package)) { $packages[] = $package; continue; } if ($checkLockedAgainstInstall && $this->isLockedEquivalent($package, $request, $lockedNameVersionMap)) { $matchingEntries = $this->filterListAuditor->getMatchingBlockEntries( $package, $installScopeMap, $this->policyConfig, ListPolicyConfig::BLOCK_SCOPE_INSTALL ); } else { $matchingEntries = $this->filterListAuditor->getMatchingBlockEntries( $package, $configuredScopeMap, $this->policyConfig, $this->blockScope ); } if (count($matchingEntries) > 0) { foreach ($package->getNames(false) as $packageName) { $filterListRemovedVersions[$packageName][$package->getVersion()] = $matchingEntries; } continue; } $packages[] = $package; } return new Pool($packages, $pool->getUnacceptableFixedOrLockedPackages(), $pool->getAllRemovedVersions(), $pool->getAllRemovedVersionsByPackage(), $pool->getAllSecurityRemovedPackageVersions(), $pool->getAllAbandonedRemovedPackageVersions(), $filterListRemovedVersions); } private function fetchFilterListMap(Pool $pool, FilterListProviderSet $providerSet, array $listNames, bool $ignoreUnreachable): array { $packagesForFilter = []; foreach ($pool->getPackages() as $package) { if (!self::isFilterable($package)) { continue; } $packagesForFilter[] = $package; } return $this->filterListAuditor->collectFilterLists( $packagesForFilter, $providerSet, $listNames, $ignoreUnreachable ); } private function warnUnreachable(array $unreachableRepos): void { $this->io->writeError('Filter list data could not be fetched from some sources (ignored per policy.ignore-unreachable); matches may be incomplete:'); foreach ($unreachableRepos as $repo) { $this->io->writeError(' - ' . $repo); } } private static function filterMapByListNames(array $map, array $listNames): array { if (count($listNames) === 0) { return []; } $allowed = array_flip($listNames); $filtered = []; foreach ($map as $packageName => $entriesByList) { foreach ($entriesByList as $listName => $entries) { if (isset($allowed[$listName])) { $filtered[$packageName][$listName] = $entries; } } } return $filtered; } private static function isFilterable(BasePackage $package): bool { return !($package instanceof RootPackageInterface) && !PlatformRepository::isPlatformPackage($package->getName()); } private function buildLockedNameVersionMap(Request $request): array { $map = []; $lockedRepository = $request->getLockedRepository(); if ($lockedRepository !== null) { foreach ($lockedRepository->getPackages() as $lockedPackage) { $map[$lockedPackage->getName()][$lockedPackage->getVersion()] = true; } } return $map; } private function isLockedEquivalent(BasePackage $package, Request $request, array $lockedNameVersionMap): bool { if ($request->isLockedPackage($package)) { return true; } return isset($lockedNameVersionMap[$package->getName()][$package->getVersion()]); } } literals = $literals; } public function getLiterals(): array { return $this->literals; } public function getHash() { $data = unpack('ihash', (string) hash(\PHP_VERSION_ID > 80100 ? 'xxh3' : 'sha1', implode(',', $this->literals), true)); if (false === $data) { throw new \RuntimeException('Failed unpacking: '.implode(', ', $this->literals)); } return $data['hash']; } public function equals(Rule $rule): bool { return $this->literals === $rule->getLiterals(); } public function isAssertion(): bool { return 1 === \count($this->literals); } public function __toString(): string { $result = $this->isDisabled() ? 'disabled(' : '('; foreach ($this->literals as $i => $literal) { if ($i !== 0) { $result .= '|'; } $result .= $literal; } $result .= ')'; return $result; } } getPackages(), $lockedRepository->getPackages() ); } } presentMap = $presentMap; $this->unlockableMap = $unlockableMap; $this->setResultPackages($pool, $decisions); parent::__construct($this->presentMap, $this->resultPackages['all']); } public function setResultPackages(Pool $pool, Decisions $decisions): void { $this->resultPackages = ['all' => [], 'non-dev' => [], 'dev' => []]; foreach ($decisions as $i => $decision) { $literal = $decision[Decisions::DECISION_LITERAL]; if ($literal > 0) { $package = $pool->literalToPackage($literal); $this->resultPackages['all'][] = $package; if (!isset($this->unlockableMap[$package->id])) { $this->resultPackages['non-dev'][] = $package; } } } } public function setNonDevPackages(LockTransaction $extractionResult): void { $packages = $extractionResult->getNewLockPackages(false); $this->resultPackages['dev'] = $this->resultPackages['non-dev']; $this->resultPackages['non-dev'] = []; foreach ($packages as $package) { foreach ($this->resultPackages['dev'] as $i => $resultPackage) { if ($package->getName() === $resultPackage->getName()) { $this->resultPackages['non-dev'][] = $resultPackage; unset($this->resultPackages['dev'][$i]); } } } } public function getNewLockPackages(bool $devMode, bool $updateMirrors = false): array { $packages = []; foreach ($this->resultPackages[$devMode ? 'dev' : 'non-dev'] as $package) { if ($package instanceof AliasPackage) { continue; } if ($updateMirrors === true && !array_key_exists(spl_object_hash($package), $this->presentMap)) { $package = $this->updateMirrorAndUrls($package); } $packages[] = $package; } return $packages; } private function updateMirrorAndUrls(BasePackage $package): BasePackage { foreach ($this->presentMap as $presentPackage) { if ($package->getName() !== $presentPackage->getName()) { continue; } if ($package->getVersion() !== $presentPackage->getVersion()) { continue; } if ($presentPackage->getSourceReference() === null) { continue; } if ($presentPackage->getSourceType() !== $package->getSourceType()) { continue; } if ($presentPackage instanceof Package) { $presentPackage->setSourceUrl($package->getSourceUrl()); $presentPackage->setSourceMirrors($package->getSourceMirrors()); } if ($presentPackage->getDistType() !== $package->getDistType()) { return $presentPackage; } if ( $package->getDistUrl() !== null && $presentPackage->getDistReference() !== null && Preg::isMatch('{^https?://(?:(?:www\.)?bitbucket\.org|(api\.)?github\.com|(?:www\.)?gitlab\.com)/}i', $package->getDistUrl()) ) { $presentPackage->setDistUrl(Preg::replace('{(?<=/|sha=)[a-f0-9]{40}(?=/|$)}i', $presentPackage->getDistReference(), $package->getDistUrl())); } $presentPackage->setDistMirrors($package->getDistMirrors()); return $presentPackage; } return $package; } public function getAliases(array $aliases): array { $usedAliases = []; foreach ($this->resultPackages['all'] as $package) { if ($package instanceof AliasPackage) { foreach ($aliases as $index => $alias) { if ($alias['package'] === $package->getName()) { $usedAliases[] = $alias; unset($aliases[$index]); } } } } usort($usedAliases, static function ($a, $b): int { return strcmp($a['package'], $b['package']); }); return $usedAliases; } } literals = $literals; } public function getLiterals(): array { return $this->literals; } public function getHash() { $data = unpack('ihash', (string) hash(\PHP_VERSION_ID > 80100 ? 'xxh3' : 'sha1', 'c:'.implode(',', $this->literals), true)); if (false === $data) { throw new \RuntimeException('Failed unpacking: '.implode(', ', $this->literals)); } return $data['hash']; } public function equals(Rule $rule): bool { if ($rule instanceof MultiConflictRule) { return $this->literals === $rule->getLiterals(); } return false; } public function isAssertion(): bool { return false; } public function disable(): void { throw new \RuntimeException("Disabling multi conflict rules is not possible. Please contact composer at https://github.com/composer/composer to let us debug what lead to this situation."); } public function __toString(): string { $result = $this->isDisabled() ? 'disabled(multi(' : '(multi('; foreach ($this->literals as $i => $literal) { if ($i !== 0) { $result .= '|'; } $result .= $literal; } $result .= '))'; return $result; } } package = $package; } public function getPackage(): PackageInterface { return $this->package; } public function show($lock): string { return self::format($this->package, $lock); } public static function format(PackageInterface $package, bool $lock = false): string { return ($lock ? 'Locking ' : 'Installing ').''.$package->getPrettyName().' ('.$package->getFullPrettyVersion().')'; } } package = $package; } public function getPackage(): AliasPackage { return $this->package; } public function show($lock): string { return 'Marking '.$this->package->getPrettyName().' ('.$this->package->getFullPrettyVersion().') as installed, alias of '.$this->package->getAliasOf()->getPrettyName().' ('.$this->package->getAliasOf()->getFullPrettyVersion().')'; } } package = $package; } public function getPackage(): AliasPackage { return $this->package; } public function show($lock): string { return 'Marking '.$this->package->getPrettyName().' ('.$this->package->getFullPrettyVersion().') as uninstalled, alias of '.$this->package->getAliasOf()->getPrettyName().' ('.$this->package->getAliasOf()->getFullPrettyVersion().')'; } } show(false); } } package = $package; } public function getPackage(): PackageInterface { return $this->package; } public function show($lock): string { return self::format($this->package, $lock); } public static function format(PackageInterface $package, bool $lock = false): string { return 'Removing '.$package->getPrettyName().' ('.$package->getFullPrettyVersion().')'; } } initialPackage = $initial; $this->targetPackage = $target; } public function getInitialPackage(): PackageInterface { return $this->initialPackage; } public function getTargetPackage(): PackageInterface { return $this->targetPackage; } public function show($lock): string { return self::format($this->initialPackage, $this->targetPackage, $lock); } public static function format(PackageInterface $initialPackage, PackageInterface $targetPackage, bool $lock = false): string { $fromVersion = $initialPackage->getFullPrettyVersion(); $toVersion = $targetPackage->getFullPrettyVersion(); if ($fromVersion === $toVersion && $initialPackage->getSourceReference() !== $targetPackage->getSourceReference()) { $fromVersion = $initialPackage->getFullPrettyVersion(true, PackageInterface::DISPLAY_SOURCE_REF); $toVersion = $targetPackage->getFullPrettyVersion(true, PackageInterface::DISPLAY_SOURCE_REF); } elseif ($fromVersion === $toVersion && $initialPackage->getDistReference() !== $targetPackage->getDistReference()) { $fromVersion = $initialPackage->getFullPrettyVersion(true, PackageInterface::DISPLAY_DIST_REF); $toVersion = $targetPackage->getFullPrettyVersion(true, PackageInterface::DISPLAY_DIST_REF); } $actionName = VersionParser::isUpgrade($initialPackage->getVersion(), $targetPackage->getVersion()) ? 'Upgrading' : 'Downgrading'; return $actionName.' '.$initialPackage->getPrettyName().' ('.$fromVersion.' => '.$toVersion.')'; } } versionParser = new VersionParser; $this->setPackages($packages); $this->unacceptableFixedOrLockedPackages = $unacceptableFixedOrLockedPackages; $this->removedVersions = $removedVersions; $this->removedVersionsByPackage = $removedVersionsByPackage; $this->securityRemovedVersions = $securityRemovedVersions; $this->abandonedRemovedVersions = $abandonedRemovedVersions; $this->filterListRemovedVersions = $filterListRemovedVersions; } public function getRemovedVersions(string $name, ConstraintInterface $constraint): array { if (!isset($this->removedVersions[$name])) { return []; } $result = []; foreach ($this->removedVersions[$name] as $version => $prettyVersion) { if ($constraint->matches(new Constraint('==', $version))) { $result[$version] = $prettyVersion; } } return $result; } public function getAllRemovedVersions(): array { return $this->removedVersions; } public function getRemovedVersionsByPackage(string $objectHash): array { if (!isset($this->removedVersionsByPackage[$objectHash])) { return []; } return $this->removedVersionsByPackage[$objectHash]; } public function getAllRemovedVersionsByPackage(): array { return $this->removedVersionsByPackage; } public function isSecurityRemovedPackageVersion(string $packageName, ?ConstraintInterface $constraint): bool { foreach ($this->securityRemovedVersions[$packageName] ?? [] as $version => $packageWithSecurityAdvisories) { if ($constraint !== null && $constraint->matches(new Constraint('==', $version))) { return true; } } return false; } public function getSecurityAdvisoryIdentifiersForPackageVersion(string $packageName, ?ConstraintInterface $constraint): array { foreach ($this->securityRemovedVersions[$packageName] ?? [] as $version => $packageWithSecurityAdvisories) { if ($constraint !== null && $constraint->matches(new Constraint('==', $version))) { return array_map(static function ($advisory) { return $advisory->advisoryId; }, $packageWithSecurityAdvisories); } } return []; } public function isAbandonedRemovedPackageVersion(string $packageName, ?ConstraintInterface $constraint): bool { foreach ($this->abandonedRemovedVersions[$packageName] ?? [] as $version => $prettyVersion) { if ($constraint !== null && $constraint->matches(new Constraint('==', $version))) { return true; } } return false; } public function getAllSecurityRemovedPackageVersions(): array { return $this->securityRemovedVersions; } public function getAllAbandonedRemovedPackageVersions(): array { return $this->abandonedRemovedVersions; } public function isFilterListRemovedPackageVersion(string $packageName, ?ConstraintInterface $constraint): bool { foreach ($this->filterListRemovedVersions[$packageName] ?? [] as $version => $entries) { if ($constraint !== null && $constraint->matches(new Constraint('==', $version))) { return true; } } return false; } public function getAllFilterListRemovedPackageVersions(): array { return $this->filterListRemovedVersions; } public function getFilterListEntryForPackageVersion(string $packageName, ?ConstraintInterface $constraint): array { $lists = []; $seen = []; foreach ($this->filterListRemovedVersions[$packageName] ?? [] as $version => $filterListEntries) { if ($constraint !== null && $constraint->matches(new Constraint('==', $version))) { foreach ($filterListEntries as $entry) { $entryKey = spl_object_hash($entry); if (isset($seen[$entryKey])) { continue; } $seen[$entryKey] = true; $url = (bool) $entry->url ? ' (see ' . $entry->url . ')' : ''; $reason = (bool) $entry->reason ? ' reason: ' . $entry->reason : ''; $lists[$entry->listName][] = $url . $reason; } } } $result = []; foreach ($lists as $listName => $listEntries) { $action = $listName === 'malware' ? 'flagged as ' : 'filtered by '; $result[$listName] = $action . $listName . implode(', ', $listEntries); } return $result; } private function setPackages(array $packages): void { $id = 1; foreach ($packages as $package) { $this->packages[] = $package; $package->id = $id++; foreach ($package->getNames() as $provided) { $this->packageByName[$provided][] = $package; } } } public function getPackages(): array { return $this->packages; } public function packageById(int $id): BasePackage { return $this->packages[$id - 1]; } public function count(): int { return \count($this->packages); } public function whatProvides(string $name, ?ConstraintInterface $constraint = null): array { $key = (string) $constraint; if (isset($this->providerCache[$name][$key])) { return $this->providerCache[$name][$key]; } return $this->providerCache[$name][$key] = $this->computeWhatProvides($name, $constraint); } private function computeWhatProvides(string $name, ?ConstraintInterface $constraint = null): array { if (!isset($this->packageByName[$name])) { return []; } $matches = []; foreach ($this->packageByName[$name] as $candidate) { if ($this->match($candidate, $name, $constraint)) { $matches[] = $candidate; } } return $matches; } public function literalToPackage(int $literal): BasePackage { $packageId = abs($literal); return $this->packageById($packageId); } public function literalToPrettyString(int $literal, array $installedMap): string { $package = $this->literalToPackage($literal); if (isset($installedMap[$package->id])) { $prefix = ($literal > 0 ? 'keep' : 'remove'); } else { $prefix = ($literal > 0 ? 'install' : 'don\'t install'); } return $prefix.' '.$package->getPrettyString(); } public function match(BasePackage $candidate, string $name, ?ConstraintInterface $constraint = null): bool { $candidateName = $candidate->getName(); $candidateVersion = $candidate->getVersion(); if ($candidateName === $name) { return $constraint === null || CompilingMatcher::match($constraint, Constraint::OP_EQ, $candidateVersion); } $provides = $candidate->getProvides(); $replaces = $candidate->getReplaces(); if (isset($replaces[0]) || isset($provides[0])) { foreach ($provides as $link) { if ($link->getTarget() === $name && ($constraint === null || $constraint->matches($link->getConstraint()))) { return true; } } foreach ($replaces as $link) { if ($link->getTarget() === $name && ($constraint === null || $constraint->matches($link->getConstraint()))) { return true; } } return false; } if (isset($provides[$name]) && ($constraint === null || $constraint->matches($provides[$name]->getConstraint()))) { return true; } if (isset($replaces[$name]) && ($constraint === null || $constraint->matches($replaces[$name]->getConstraint()))) { return true; } return false; } public function isUnacceptableFixedOrLockedPackage(BasePackage $package): bool { return \in_array($package, $this->unacceptableFixedOrLockedPackages, true); } public function getUnacceptableFixedOrLockedPackages(): array { return $this->unacceptableFixedOrLockedPackages; } public function __toString(): string { $str = "Pool:\n"; foreach ($this->packages as $package) { $str .= '- '.str_pad((string) $package->id, 6, ' ', STR_PAD_LEFT).': '.$package->getName()."\n"; } return $str; } } acceptableStabilities = $acceptableStabilities; $this->stabilityFlags = $stabilityFlags; $this->rootAliases = $rootAliases; $this->rootReferences = $rootReferences; $this->eventDispatcher = $eventDispatcher; $this->poolOptimizer = $poolOptimizer; $this->io = $io; $this->temporaryConstraints = $temporaryConstraints; $this->securityAdvisoryPoolFilter = $securityAdvisoryPoolFilter; $this->filterListPoolFilter = $filterListPoolFilter; } public function setIgnoredTypes(array $types): void { $this->ignoredTypes = $types; } public function setAllowedTypes(?array $types): void { $this->allowedTypes = $types; } public function buildPool(array $repositories, Request $request): Pool { $this->restrictedPackagesList = $request->getRestrictedPackages() !== null ? array_flip($request->getRestrictedPackages()) : null; if (\count($request->getUpdateAllowList()) > 0) { $this->updateAllowList = $request->getUpdateAllowList(); $this->warnAboutNonMatchingUpdateAllowList($request); if (null === $request->getLockedRepository()) { throw new \LogicException('No lock repo present and yet a partial update was requested.'); } foreach ($request->getLockedRepository()->getPackages() as $lockedPackage) { if (!$this->isUpdateAllowed($lockedPackage)) { $this->skippedLoad[$lockedPackage->getName()][] = $lockedPackage; foreach ($lockedPackage->getReplaces() as $link) { $this->skippedLoad[$link->getTarget()][] = $lockedPackage; } if ($lockedPackage->getDistType() === 'path') { $transportOptions = $lockedPackage->getTransportOptions(); if (!isset($transportOptions['symlink']) || $transportOptions['symlink'] !== false) { $this->pathRepoUnlocked[$lockedPackage->getName()] = true; continue; } } $request->lockPackage($lockedPackage); } } } foreach ($request->getFixedOrLockedPackages() as $package) { $this->loadedPackages[$package->getName()] = new MatchAllConstraint(); foreach ($package->getReplaces() as $link) { $this->loadedPackages[$link->getTarget()] = new MatchAllConstraint(); } if ( $package->getRepository() instanceof RootPackageRepository || $package->getRepository() instanceof PlatformRepository || StabilityFilter::isPackageAcceptable($this->acceptableStabilities, $this->stabilityFlags, $package->getNames(), $package->getStability()) ) { $this->loadPackage($request, $repositories, $package, false); } else { $this->unacceptableFixedOrLockedPackages[] = $package; } } foreach ($request->getRequires() as $packageName => $constraint) { if (isset($this->loadedPackages[$packageName])) { continue; } $this->packagesToLoad[$packageName] = $constraint; $this->maxExtendedReqs[$packageName] = true; } foreach ($this->packagesToLoad as $name => $constraint) { if (isset($this->loadedPackages[$name])) { unset($this->packagesToLoad[$name]); } } while (\count($this->packagesToLoad) > 0) { $this->loadPackagesMarkedForLoading($request, $repositories); } if (\count($this->temporaryConstraints) > 0) { foreach ($this->packages as $i => $package) { if ($package instanceof AliasPackage) { continue; } foreach ($package->getNames() as $packageName) { if (!isset($this->temporaryConstraints[$packageName])) { continue; } $constraint = $this->temporaryConstraints[$packageName]; $packageAndAliases = [$i => $package]; if (isset($this->aliasMap[spl_object_hash($package)])) { $packageAndAliases += $this->aliasMap[spl_object_hash($package)]; } $found = false; foreach ($packageAndAliases as $packageOrAlias) { if (CompilingMatcher::match($constraint, Constraint::OP_EQ, $packageOrAlias->getVersion())) { $found = true; } } if (!$found) { foreach ($packageAndAliases as $index => $packageOrAlias) { unset($this->packages[$index]); } } } } } if ($this->eventDispatcher !== null) { $prePoolCreateEvent = new PrePoolCreateEvent( PluginEvents::PRE_POOL_CREATE, $repositories, $request, $this->acceptableStabilities, $this->stabilityFlags, $this->rootAliases, $this->rootReferences, $this->packages, $this->unacceptableFixedOrLockedPackages ); $this->eventDispatcher->dispatch($prePoolCreateEvent->getName(), $prePoolCreateEvent); $this->packages = $prePoolCreateEvent->getPackages(); $this->unacceptableFixedOrLockedPackages = $prePoolCreateEvent->getUnacceptableFixedPackages(); } $pool = new Pool($this->packages, $this->unacceptableFixedOrLockedPackages); $this->aliasMap = []; $this->packagesToLoad = []; $this->loadedPackages = []; $this->loadedPerRepo = []; $this->packages = []; $this->unacceptableFixedOrLockedPackages = []; $this->maxExtendedReqs = []; $this->skippedLoad = []; $this->indexCounter = 0; $this->io->debug('Built pool.'); $pool = $this->runSecurityAdvisoryFilter($pool, $repositories, $request); $pool = $this->runFilterListFilter($pool, $request); $pool = $this->runOptimizer($request, $pool); Intervals::clear(); return $pool; } private function markPackageNameForLoading(Request $request, string $name, ConstraintInterface $constraint): void { if (PlatformRepository::isPlatformPackage($name)) { return; } if (isset($this->maxExtendedReqs[$name])) { return; } $rootRequires = $request->getRequires(); if (isset($rootRequires[$name]) && !Intervals::isSubsetOf($constraint, $rootRequires[$name])) { $constraint = $rootRequires[$name]; } if (!isset($this->loadedPackages[$name])) { if (isset($this->packagesToLoad[$name])) { if (Intervals::isSubsetOf($constraint, $this->packagesToLoad[$name])) { return; } $constraint = Intervals::compactConstraint(MultiConstraint::create([$this->packagesToLoad[$name], $constraint], false)); } $this->packagesToLoad[$name] = $constraint; return; } if (Intervals::isSubsetOf($constraint, $this->loadedPackages[$name])) { return; } $this->packagesToLoad[$name] = Intervals::compactConstraint(MultiConstraint::create([$this->loadedPackages[$name], $constraint], false)); unset($this->loadedPackages[$name]); } private function loadPackagesMarkedForLoading(Request $request, array $repositories): void { foreach ($this->packagesToLoad as $name => $constraint) { if ($this->restrictedPackagesList !== null && !isset($this->restrictedPackagesList[$name])) { unset($this->packagesToLoad[$name]); continue; } $this->loadedPackages[$name] = $constraint; } $packageBatches = array_chunk($this->packagesToLoad, self::LOAD_BATCH_SIZE, true); $this->packagesToLoad = []; foreach ($repositories as $repoIndex => $repository) { if ($repository instanceof PlatformRepository || $repository === $request->getLockedRepository()) { continue; } if (0 === \count($packageBatches)) { break; } foreach ($packageBatches as $batchIndex => $packageBatch) { $result = $repository->loadPackages($packageBatch, $this->acceptableStabilities, $this->stabilityFlags, $this->loadedPerRepo[$repoIndex] ?? []); foreach ($result['namesFound'] as $name) { unset($packageBatches[$batchIndex][$name]); } foreach ($result['packages'] as $package) { $this->loadedPerRepo[$repoIndex][$package->getName()][$package->getVersion()] = $package; if (in_array($package->getType(), $this->ignoredTypes, true) || ($this->allowedTypes !== null && !in_array($package->getType(), $this->allowedTypes, true))) { continue; } $this->loadPackage($request, $repositories, $package, !isset($this->pathRepoUnlocked[$package->getName()])); } } $packageBatches = array_chunk(array_merge(...$packageBatches), self::LOAD_BATCH_SIZE, true); } } private function loadPackage(Request $request, array $repositories, BasePackage $package, bool $propagateUpdate): void { $index = $this->indexCounter++; $this->packages[$index] = $package; if ($package instanceof AliasPackage) { $this->aliasMap[spl_object_hash($package->getAliasOf())][$index] = $package; } $name = $package->getName(); if (isset($this->rootReferences[$name])) { if (!$request->isLockedPackage($package) && !$request->isFixedPackage($package)) { $package->setSourceDistReferences($this->rootReferences[$name]); } } if (($propagateUpdate || isset($this->pathRepoUnlocked[$package->getName()])) && isset($this->rootAliases[$name][$package->getVersion()])) { $alias = $this->rootAliases[$name][$package->getVersion()]; if ($package instanceof AliasPackage) { $basePackage = $package->getAliasOf(); } else { $basePackage = $package; } if ($basePackage instanceof CompletePackage) { $aliasPackage = new CompleteAliasPackage($basePackage, $alias['alias_normalized'], $alias['alias']); } else { $aliasPackage = new AliasPackage($basePackage, $alias['alias_normalized'], $alias['alias']); } $aliasPackage->setRootPackageAlias(true); $newIndex = $this->indexCounter++; $this->packages[$newIndex] = $aliasPackage; $this->aliasMap[spl_object_hash($aliasPackage->getAliasOf())][$newIndex] = $aliasPackage; } foreach ($package->getRequires() as $link) { $require = $link->getTarget(); $linkConstraint = $link->getConstraint(); if (isset($this->skippedLoad[$require])) { if ($propagateUpdate && $request->getUpdateAllowTransitiveDependencies()) { $skippedRootRequires = $this->getSkippedRootRequires($request, $require); if ($request->getUpdateAllowTransitiveRootDependencies() || 0 === \count($skippedRootRequires)) { $this->unlockPackage($request, $repositories, $require); $this->markPackageNameForLoading($request, $require, $linkConstraint); } else { foreach ($skippedRootRequires as $rootRequire) { if (!isset($this->updateAllowWarned[$rootRequire])) { $this->updateAllowWarned[$rootRequire] = true; $this->io->writeError('Dependency '.$rootRequire.' is also a root requirement. Package has not been listed as an update argument, so keeping locked at old version. Use --with-all-dependencies (-W) to include root dependencies.'); } } } } elseif (isset($this->pathRepoUnlocked[$require]) && !isset($this->loadedPackages[$require])) { $this->markPackageNameForLoading($request, $require, $linkConstraint); } } else { $this->markPackageNameForLoading($request, $require, $linkConstraint); } } if ($propagateUpdate && $request->getUpdateAllowTransitiveDependencies()) { foreach ($package->getReplaces() as $link) { $replace = $link->getTarget(); if (isset($this->loadedPackages[$replace], $this->skippedLoad[$replace])) { $skippedRootRequires = $this->getSkippedRootRequires($request, $replace); if ($request->getUpdateAllowTransitiveRootDependencies() || 0 === \count($skippedRootRequires)) { $this->unlockPackage($request, $repositories, $replace); $this->markPackageNameForLoadingIfRequired($request, $replace); } else { foreach ($skippedRootRequires as $rootRequire) { if (!isset($this->updateAllowWarned[$rootRequire])) { $this->updateAllowWarned[$rootRequire] = true; $this->io->writeError('Dependency '.$rootRequire.' is also a root requirement. Package has not been listed as an update argument, so keeping locked at old version. Use --with-all-dependencies (-W) to include root dependencies.'); } } } } } } } private function isRootRequire(Request $request, string $name): bool { $rootRequires = $request->getRequires(); return isset($rootRequires[$name]); } private function getSkippedRootRequires(Request $request, string $name): array { if (!isset($this->skippedLoad[$name])) { return []; } $rootRequires = $request->getRequires(); $matches = []; if (isset($rootRequires[$name])) { return array_map(static function (PackageInterface $package) use ($name): string { if ($name !== $package->getName()) { return $package->getName() .' (via replace of '.$name.')'; } return $package->getName(); }, $this->skippedLoad[$name]); } foreach ($this->skippedLoad[$name] as $packageOrReplacer) { if (isset($rootRequires[$packageOrReplacer->getName()])) { $matches[] = $packageOrReplacer->getName(); } foreach ($packageOrReplacer->getReplaces() as $link) { if (isset($rootRequires[$link->getTarget()])) { if ($name !== $packageOrReplacer->getName()) { $matches[] = $packageOrReplacer->getName() .' (via replace of '.$name.')'; } else { $matches[] = $packageOrReplacer->getName(); } break; } } } return $matches; } private function isUpdateAllowed(BasePackage $package): bool { foreach ($this->updateAllowList as $pattern) { $patternRegexp = BasePackage::packageNameToRegexp($pattern); if (Preg::isMatch($patternRegexp, $package->getName())) { return true; } } return false; } private function warnAboutNonMatchingUpdateAllowList(Request $request): void { if (null === $request->getLockedRepository()) { throw new \LogicException('No lock repo present and yet a partial update was requested.'); } foreach ($this->updateAllowList as $pattern) { $matchedPlatformPackage = false; $patternRegexp = BasePackage::packageNameToRegexp($pattern); foreach ($request->getLockedRepository()->getPackages() as $package) { if (Preg::isMatch($patternRegexp, $package->getName())) { continue 2; } } foreach ($request->getRequires() as $packageName => $constraint) { if (Preg::isMatch($patternRegexp, $packageName)) { if (PlatformRepository::isPlatformPackage($packageName)) { $matchedPlatformPackage = true; continue; } continue 2; } } if ($matchedPlatformPackage) { $this->io->writeError('Pattern "' . $pattern . '" listed for update matches platform packages, but these cannot be updated by Composer.'); } elseif (strpos($pattern, '*') !== false) { $this->io->writeError('Pattern "' . $pattern . '" listed for update does not match any locked packages.'); } else { $this->io->writeError('Package "' . $pattern . '" listed for update is not locked.'); } } } private function unlockPackage(Request $request, array $repositories, string $name): void { foreach ($this->skippedLoad[$name] as $packageOrReplacer) { if ($packageOrReplacer->getName() !== $name && isset($this->skippedLoad[$packageOrReplacer->getName()])) { $replacerName = $packageOrReplacer->getName(); if ($request->getUpdateAllowTransitiveRootDependencies() || (!$this->isRootRequire($request, $name) && !$this->isRootRequire($request, $replacerName))) { $this->unlockPackage($request, $repositories, $replacerName); if ($this->isRootRequire($request, $replacerName)) { $this->markPackageNameForLoading($request, $replacerName, new MatchAllConstraint); } else { foreach ($this->packages as $loadedPackage) { $requires = $loadedPackage->getRequires(); if (isset($requires[$replacerName])) { $this->markPackageNameForLoading($request, $replacerName, $requires[$replacerName]->getConstraint()); } } } } } } if (isset($this->pathRepoUnlocked[$name])) { foreach ($this->packages as $index => $package) { if ($package->getName() === $name) { $this->removeLoadedPackage($request, $repositories, $package, $index); } } } unset($this->skippedLoad[$name], $this->loadedPackages[$name], $this->maxExtendedReqs[$name], $this->pathRepoUnlocked[$name]); foreach ($request->getLockedPackages() as $lockedPackage) { if (!($lockedPackage instanceof AliasPackage) && $lockedPackage->getName() === $name) { if (false !== $index = array_search($lockedPackage, $this->packages, true)) { $request->unlockPackage($lockedPackage); $this->removeLoadedPackage($request, $repositories, $lockedPackage, $index); foreach ($request->getFixedOrLockedPackages() as $fixedOrLockedPackage) { if ($fixedOrLockedPackage === $lockedPackage) { continue; } if (isset($this->skippedLoad[$fixedOrLockedPackage->getName()])) { $requires = $fixedOrLockedPackage->getRequires(); if (isset($requires[$lockedPackage->getName()])) { $this->markPackageNameForLoading($request, $lockedPackage->getName(), $requires[$lockedPackage->getName()]->getConstraint()); } foreach ($lockedPackage->getReplaces() as $replace) { if (isset($requires[$replace->getTarget()], $this->skippedLoad[$replace->getTarget()])) { $this->unlockPackage($request, $repositories, $replace->getTarget()); $this->markPackageNameForLoading($request, $replace->getTarget(), $replace->getConstraint()); } } } } } } } } private function markPackageNameForLoadingIfRequired(Request $request, string $name): void { if ($this->isRootRequire($request, $name)) { $this->markPackageNameForLoading($request, $name, $request->getRequires()[$name]); } foreach ($this->packages as $package) { foreach ($package->getRequires() as $link) { if ($name === $link->getTarget()) { $this->markPackageNameForLoading($request, $link->getTarget(), $link->getConstraint()); } } } } private function removeLoadedPackage(Request $request, array $repositories, BasePackage $package, int $index): void { $repoIndex = array_search($package->getRepository(), $repositories, true); unset($this->loadedPerRepo[$repoIndex][$package->getName()][$package->getVersion()]); unset($this->packages[$index]); if (isset($this->aliasMap[spl_object_hash($package)])) { foreach ($this->aliasMap[spl_object_hash($package)] as $aliasIndex => $aliasPackage) { unset($this->loadedPerRepo[$repoIndex][$aliasPackage->getName()][$aliasPackage->getVersion()]); unset($this->packages[$aliasIndex]); } unset($this->aliasMap[spl_object_hash($package)]); } } private function runOptimizer(Request $request, Pool $pool): Pool { if (null === $this->poolOptimizer) { return $pool; } $this->io->debug('Running pool optimizer.'); $before = microtime(true); $total = \count($pool->getPackages()); $pool = $this->poolOptimizer->optimize($request, $pool); $filtered = $total - \count($pool->getPackages()); if (0 === $filtered) { return $pool; } $this->io->write(sprintf('Pool optimizer completed in %.3f seconds', microtime(true) - $before), true, IOInterface::VERY_VERBOSE); $this->io->write(sprintf( 'Found %s package versions referenced in your dependency graph. %s (%d%%) were optimized away.', number_format($total), number_format($filtered), round(100 / $total * $filtered) ), true, IOInterface::VERY_VERBOSE); return $pool; } private function runFilterListFilter(Pool $pool, Request $request): Pool { if (null === $this->filterListPoolFilter) { return $pool; } $this->io->debug('Running filter list pool filter.'); $before = microtime(true); $total = \count($pool->getPackages()); $pool = $this->filterListPoolFilter->filter($pool, $request); $filtered = $total - \count($pool->getPackages()); if (0 === $filtered) { return $pool; } $this->io->write(sprintf('Filter list pool filter completed in %.3f seconds', microtime(true) - $before), true, IOInterface::VERY_VERBOSE); $this->io->write(sprintf( 'Found %s package versions referenced in your dependency graph. %s (%d%%) were filtered away by dependency policies.', number_format($total), number_format($filtered), round(100 / $total * $filtered) ), true, IOInterface::VERY_VERBOSE); return $pool; } private function runSecurityAdvisoryFilter(Pool $pool, array $repositories, Request $request): Pool { if (null === $this->securityAdvisoryPoolFilter) { return $pool; } $this->io->debug('Running security advisory pool filter.'); $before = microtime(true); $total = \count($pool->getPackages()); $pool = $this->securityAdvisoryPoolFilter->filter($pool, $repositories, $request); $filtered = $total - \count($pool->getPackages()); if (0 === $filtered) { return $pool; } $this->io->write(sprintf('Security advisory pool filter completed in %.3f seconds', microtime(true) - $before), true, IOInterface::VERY_VERBOSE); $this->io->write(sprintf( 'Found %s package versions referenced in your dependency graph. %s (%d%%) were filtered away.', number_format($total), number_format($filtered), round(100 / $total * $filtered) ), true, IOInterface::VERY_VERBOSE); return $pool; } } policy = $policy; } public function optimize(Request $request, Pool $pool): Pool { $this->prepare($request, $pool); $this->optimizeByIdenticalDependencies($request, $pool); $this->optimizeImpossiblePackagesAway($request, $pool); $optimizedPool = $this->applyRemovalsToPool($pool); $this->irremovablePackages = []; $this->requireConstraintsPerPackage = []; $this->conflictConstraintsPerPackage = []; $this->packagesToRemove = []; $this->aliasesPerPackage = []; $this->removedVersionsByPackage = []; return $optimizedPool; } private function prepare(Request $request, Pool $pool): void { $irremovablePackageConstraintGroups = []; foreach ($request->getFixedOrLockedPackages() as $package) { $irremovablePackageConstraintGroups[$package->getName()][] = new Constraint('==', $package->getVersion()); } foreach ($request->getRequires() as $require => $constraint) { $this->extractRequireConstraintsPerPackage($require, $constraint); } foreach ($pool->getPackages() as $package) { foreach ($package->getRequires() as $link) { $this->extractRequireConstraintsPerPackage($link->getTarget(), $link->getConstraint()); } foreach ($package->getConflicts() as $link) { $this->extractConflictConstraintsPerPackage($link->getTarget(), $link->getConstraint()); } if ($package instanceof AliasPackage) { $this->aliasesPerPackage[$package->getAliasOf()->id][] = $package; } } $irremovablePackageConstraints = []; foreach ($irremovablePackageConstraintGroups as $packageName => $constraints) { $irremovablePackageConstraints[$packageName] = 1 === \count($constraints) ? $constraints[0] : new MultiConstraint($constraints, false); } unset($irremovablePackageConstraintGroups); foreach ($pool->getPackages() as $package) { if (!isset($irremovablePackageConstraints[$package->getName()])) { continue; } if (CompilingMatcher::match($irremovablePackageConstraints[$package->getName()], Constraint::OP_EQ, $package->getVersion())) { $this->markPackageIrremovable($package); } } } private function markPackageIrremovable(BasePackage $package): void { $this->irremovablePackages[$package->id] = true; if ($package instanceof AliasPackage) { $this->markPackageIrremovable($package->getAliasOf()); } if (isset($this->aliasesPerPackage[$package->id])) { foreach ($this->aliasesPerPackage[$package->id] as $aliasPackage) { $this->irremovablePackages[$aliasPackage->id] = true; } } } private function applyRemovalsToPool(Pool $pool): Pool { $packages = []; $removedVersions = []; foreach ($pool->getPackages() as $package) { if (!isset($this->packagesToRemove[$package->id])) { $packages[] = $package; } else { $removedVersions[$package->getName()][$package->getVersion()] = $package->getPrettyVersion(); } } $optimizedPool = new Pool($packages, $pool->getUnacceptableFixedOrLockedPackages(), $removedVersions, $this->removedVersionsByPackage, $pool->getAllSecurityRemovedPackageVersions(), $pool->getAllAbandonedRemovedPackageVersions(), $pool->getAllFilterListRemovedPackageVersions()); return $optimizedPool; } private function optimizeByIdenticalDependencies(Request $request, Pool $pool): void { $identicalDefinitionsPerPackage = []; foreach ($pool->getPackages() as $package) { if (isset($this->irremovablePackages[$package->id])) { continue; } $this->markPackageForRemoval($package->id); $dependencyHash = $this->calculateDependencyHash($package); foreach ($package->getNames(false) as $packageName) { if (!isset($this->requireConstraintsPerPackage[$packageName])) { continue; } foreach ($this->requireConstraintsPerPackage[$packageName] as $requireConstraint) { $groupHashParts = []; if (CompilingMatcher::match($requireConstraint, Constraint::OP_EQ, $package->getVersion())) { $groupHashParts[] = 'require:' . (string) $requireConstraint; } if (\count($package->getReplaces()) > 0) { foreach ($package->getReplaces() as $link) { if (CompilingMatcher::match($link->getConstraint(), Constraint::OP_EQ, $package->getVersion())) { $groupHashParts[] = 'require:' . (string) $link->getConstraint(); } } } if (isset($this->conflictConstraintsPerPackage[$packageName])) { foreach ($this->conflictConstraintsPerPackage[$packageName] as $conflictConstraint) { if (CompilingMatcher::match($conflictConstraint, Constraint::OP_EQ, $package->getVersion())) { $groupHashParts[] = 'conflict:' . (string) $conflictConstraint; } } } if (0 === \count($groupHashParts)) { continue; } $groupHash = implode('', $groupHashParts); $identicalDefinitionsPerPackage[$packageName][$groupHash][$dependencyHash][] = $package->id; } } } foreach ($identicalDefinitionsPerPackage as $packageName => $constraintGroups) { foreach ($constraintGroups as $constraintGroup) { foreach ($constraintGroup as $packageIds) { if (1 === \count($packageIds)) { $this->keepPackageInGroup($pool->packageById($packageIds[0]), $pool, $packageName, $packageIds); continue; } foreach ($this->policy->selectPreferredPackages($pool, $packageIds) as $preferredLiteral) { $this->keepPackageInGroup($pool->literalToPackage($preferredLiteral), $pool, $packageName, $packageIds); } } } } } private function calculateDependencyHash(BasePackage $package): string { $hash = ''; $hashRelevantLinks = [ 'requires' => $package->getRequires(), 'conflicts' => $package->getConflicts(), 'replaces' => $package->getReplaces(), 'provides' => $package->getProvides(), ]; foreach ($hashRelevantLinks as $key => $links) { if (0 === \count($links)) { continue; } $hash .= $key . ':'; $subhash = []; foreach ($links as $link) { $subhash[$link->getTarget()] = (string) $link->getConstraint(); } ksort($subhash); foreach ($subhash as $target => $constraint) { $hash .= $target . '@' . $constraint; } } return $hash; } private function markPackageForRemoval(int $id): void { if (isset($this->irremovablePackages[$id])) { throw new \LogicException('Attempted removing a package which was previously marked irremovable'); } $this->packagesToRemove[$id] = true; } private function keepPackageInGroup(BasePackage $package, Pool $pool, string $packageName, array $packageIds): void { $versions = []; foreach ($packageIds as $packageId) { $groupPackage = $pool->packageById($packageId); if ($groupPackage instanceof AliasPackage && $groupPackage->getPrettyVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) { $groupPackage = $groupPackage->getAliasOf(); } $versions[$groupPackage->getVersion()] = $groupPackage->getPrettyVersion(); } $this->recordRemovedVersionsForPackage($package, $packageName, $versions); if (!isset($this->packagesToRemove[$package->id])) { return; } $this->unmarkPackageForRemoval($package); if ($package instanceof AliasPackage) { $this->unmarkPackageForRemoval($package->getAliasOf()); $this->recordRemovedVersionsForPackage($package->getAliasOf(), $packageName, $versions); if (isset($this->aliasesPerPackage[$package->getAliasOf()->id])) { foreach ($this->aliasesPerPackage[$package->getAliasOf()->id] as $aliasPackage) { $this->unmarkPackageForRemoval($aliasPackage); $this->recordRemovedVersionsForPackage($aliasPackage, $packageName, $versions); } } return; } if (isset($this->aliasesPerPackage[$package->id])) { foreach ($this->aliasesPerPackage[$package->id] as $aliasPackage) { $this->unmarkPackageForRemoval($aliasPackage); $this->recordRemovedVersionsForPackage($aliasPackage, $packageName, $versions); } } } private function unmarkPackageForRemoval(BasePackage $package): void { unset($this->packagesToRemove[$package->id]); } private function recordRemovedVersionsForPackage(BasePackage $package, string $packageName, array $versions): void { if (!in_array($packageName, $package->getNames(false), true)) { return; } foreach ($versions as $version => $prettyVersion) { $this->removedVersionsByPackage[spl_object_hash($package)][$version] = $prettyVersion; } } private function optimizeImpossiblePackagesAway(Request $request, Pool $pool): void { if (\count($request->getLockedPackages()) === 0) { return; } $packageIndex = []; foreach ($pool->getPackages() as $package) { $id = $package->id; if (isset($this->irremovablePackages[$id])) { continue; } if (isset($this->aliasesPerPackage[$id]) || $package instanceof AliasPackage) { continue; } if ($request->isFixedPackage($package) || $request->isLockedPackage($package)) { continue; } $packageIndex[$package->getName()][$package->id] = $package; } foreach ($request->getLockedPackages() as $package) { $isUnusedPackage = true; foreach ($package->getNames(false) as $packageName) { if (isset($this->requireConstraintsPerPackage[$packageName])) { $isUnusedPackage = false; break; } } if ($isUnusedPackage) { continue; } foreach ($package->getRequires() as $link) { $require = $link->getTarget(); if (!isset($packageIndex[$require])) { continue; } $linkConstraint = $link->getConstraint(); foreach ($packageIndex[$require] as $id => $requiredPkg) { if (false === CompilingMatcher::match($linkConstraint, Constraint::OP_EQ, $requiredPkg->getVersion())) { $this->markPackageForRemoval($id); unset($packageIndex[$require][$id]); } } } } } private function extractRequireConstraintsPerPackage(string $package, ConstraintInterface $constraint) { foreach ($this->expandDisjunctiveMultiConstraints($constraint) as $expanded) { $this->requireConstraintsPerPackage[$package][(string) $expanded] = $expanded; } } private function extractConflictConstraintsPerPackage(string $package, ConstraintInterface $constraint) { foreach ($this->expandDisjunctiveMultiConstraints($constraint) as $expanded) { $this->conflictConstraintsPerPackage[$package][(string) $expanded] = $expanded; } } private function expandDisjunctiveMultiConstraints(ConstraintInterface $constraint) { $constraint = Intervals::compactConstraint($constraint); if ($constraint instanceof MultiConstraint && $constraint->isDisjunctive()) { return $constraint->getConstraints(); } return [$constraint]; } } addReason(spl_object_hash($rule), $rule); } public function getReasons(): array { return $this->reasons; } public function getPrettyString(RepositorySet $repositorySet, Request $request, Pool $pool, bool $isVerbose, array $installedMap = [], array $learnedPool = []): string { $reasons = array_merge(...array_reverse($this->reasons)); if (\count($reasons) === 1) { reset($reasons); $rule = current($reasons); if ($rule->getReason() === Rule::RULE_ROOT_REQUIRE) { $reasonData = $rule->getReasonData(); $packageName = $reasonData['packageName']; $constraint = $reasonData['constraint']; $packages = $pool->whatProvides($packageName, $constraint); if (\count($packages) === 0) { return "\n ".implode(self::getMissingPackageReason($repositorySet, $request, $pool, $isVerbose, $packageName, $constraint)); } } elseif ($rule->getReason() === Rule::RULE_LOCKED_FILTER_LIST_REMOVED) { $package = $rule->getReasonData()['package']; return "\n ".implode(self::getMissingLockedPackageReason($pool, $package)); } } usort($reasons, function (Rule $rule1, Rule $rule2) use ($pool) { $rule1Prio = $this->getRulePriority($rule1); $rule2Prio = $this->getRulePriority($rule2); if ($rule1Prio !== $rule2Prio) { return $rule2Prio - $rule1Prio; } return $this->getSortableString($pool, $rule1) <=> $this->getSortableString($pool, $rule2); }); return self::formatDeduplicatedRules($reasons, ' ', $repositorySet, $request, $pool, $isVerbose, $installedMap, $learnedPool); } private function getSortableString(Pool $pool, Rule $rule): string { switch ($rule->getReason()) { case Rule::RULE_ROOT_REQUIRE: return $rule->getReasonData()['packageName']; case Rule::RULE_FIXED: case Rule::RULE_LOCKED_FILTER_LIST_REMOVED: return (string) $rule->getReasonData()['package']; case Rule::RULE_PACKAGE_CONFLICT: case Rule::RULE_PACKAGE_REQUIRES: return $rule->getSourcePackage($pool) . '//' . $rule->getReasonData()->getPrettyString($rule->getSourcePackage($pool)); case Rule::RULE_PACKAGE_SAME_NAME: case Rule::RULE_PACKAGE_ALIAS: case Rule::RULE_PACKAGE_INVERSE_ALIAS: return (string) $rule->getReasonData(); case Rule::RULE_LEARNED: return implode('-', $rule->getLiterals()); } throw new \LogicException('Unknown rule type: '.$rule->getReason()); } private function getRulePriority(Rule $rule): int { switch ($rule->getReason()) { case Rule::RULE_FIXED: case Rule::RULE_LOCKED_FILTER_LIST_REMOVED: return 3; case Rule::RULE_ROOT_REQUIRE: return 2; case Rule::RULE_PACKAGE_CONFLICT: case Rule::RULE_PACKAGE_REQUIRES: return 1; case Rule::RULE_PACKAGE_SAME_NAME: case Rule::RULE_LEARNED: case Rule::RULE_PACKAGE_ALIAS: case Rule::RULE_PACKAGE_INVERSE_ALIAS: return 0; } throw new \LogicException('Unknown rule type: '.$rule->getReason()); } public static function formatDeduplicatedRules(array $rules, string $indent, RepositorySet $repositorySet, Request $request, Pool $pool, bool $isVerbose, array $installedMap = [], array $learnedPool = []): string { $messages = []; $templates = []; $parser = new VersionParser; $deduplicatableRuleTypes = [Rule::RULE_PACKAGE_REQUIRES, Rule::RULE_PACKAGE_CONFLICT]; foreach ($rules as $rule) { $message = $rule->getPrettyString($repositorySet, $request, $pool, $isVerbose, $installedMap, $learnedPool); if (in_array($rule->getReason(), $deduplicatableRuleTypes, true) && Preg::isMatchStrictGroups('{^(?P\S+) (?P\S+) (?Prequires|conflicts)}', $message, $m)) { $message = str_replace('%', '%%', $message); $template = Preg::replace('{^\S+ \S+ }', '%s%s ', $message); $messages[] = $template; $templates[$template][$m[1]][$parser->normalize($m[2])] = $m[2]; $sourcePackage = $rule->getSourcePackage($pool); foreach ($pool->getRemovedVersionsByPackage(spl_object_hash($sourcePackage)) as $version => $prettyVersion) { $templates[$template][$m[1]][$version] = $prettyVersion; } } elseif ($message !== '') { $messages[] = $message; } } $result = []; foreach (array_unique($messages) as $message) { if (isset($templates[$message])) { foreach ($templates[$message] as $package => $versions) { uksort($versions, 'version_compare'); if (!$isVerbose) { $versions = self::condenseVersionList($versions, 1); } if (\count($versions) > 1) { $message = Preg::replace('{^(%s%s (?:require|conflict))s}', '$1', $message); $result[] = sprintf($message, $package, '['.implode(', ', $versions).']'); } else { $result[] = sprintf($message, $package, ' '.reset($versions)); } } } else { $result[] = $message; } } return "\n$indent- ".implode("\n$indent- ", $result); } public function isCausedByLock(RepositorySet $repositorySet, Request $request, Pool $pool): bool { foreach ($this->reasons as $sectionRules) { foreach ($sectionRules as $rule) { if ($rule->isCausedByLock($repositorySet, $request, $pool)) { return true; } } } return false; } protected function addReason(string $id, Rule $reason): void { if (!isset($this->reasonSeen[$id])) { $this->reasonSeen[$id] = true; $this->reasons[$this->section][] = $reason; } } public function nextSection(): void { $this->section++; } public static function getMissingPackageReason(RepositorySet $repositorySet, Request $request, Pool $pool, bool $isVerbose, string $packageName, ?ConstraintInterface $constraint = null): array { if (PlatformRepository::isPlatformPackage($packageName)) { if (0 === stripos($packageName, 'php') || $packageName === 'hhvm') { $version = self::getPlatformPackageVersion($pool, $packageName, phpversion()); $msg = "- Root composer.json requires ".$packageName.self::constraintToText($constraint).' but '; if (defined('HHVM_VERSION') || ($packageName === 'hhvm' && count($pool->whatProvides($packageName)) > 0)) { return [$msg, 'your HHVM version does not satisfy that requirement.']; } if ($packageName === 'hhvm') { return [$msg, 'HHVM was not detected on this machine, make sure it is in your PATH.']; } if (null === $version) { return [$msg, 'the '.$packageName.' package is disabled by your platform config. Enable it again with "composer config platform.'.$packageName.' --unset".']; } return [$msg, 'your '.$packageName.' version ('. $version .') does not satisfy that requirement.']; } if (0 === stripos($packageName, 'ext-')) { if (false !== strpos($packageName, ' ')) { return ['- ', "PHP extension ".$packageName.' should be required as '.str_replace(' ', '-', $packageName).'.']; } $ext = substr($packageName, 4); $msg = "- Root composer.json requires PHP extension ".$packageName.self::constraintToText($constraint).' but '; $version = self::getPlatformPackageVersion($pool, $packageName, phpversion($ext) === false ? '0' : phpversion($ext)); if (null === $version) { $providersStr = self::getProvidersList($repositorySet, $packageName, 5); if ($providersStr !== null) { $providersStr = "\n\n Alternatively you can require one of these packages that provide the extension (or parts of it):\n". " Keep in mind that the suggestions are automated and may not be valid or safe to use\n$providersStr"; } if (extension_loaded($ext)) { return [ $msg, 'the '.$packageName.' package is disabled by your platform config. Enable it again with "composer config platform.'.$packageName.' --unset".' . $providersStr, ]; } return [$msg, 'it is missing from your system. Install or enable PHP\'s '.$ext.' extension.' . $providersStr]; } return [$msg, 'it has the wrong version installed ('.$version.').']; } if (0 === stripos($packageName, 'lib-')) { if (strtolower($packageName) === 'lib-icu') { $error = extension_loaded('intl') ? 'it has the wrong version installed, try upgrading the intl extension.' : 'it is missing from your system, make sure the intl extension is loaded.'; return ["- Root composer.json requires linked library ".$packageName.self::constraintToText($constraint).' but ', $error]; } $providersStr = self::getProvidersList($repositorySet, $packageName, 5); if ($providersStr !== null) { $providersStr = "\n\n Alternatively you can require one of these packages that provide the library (or parts of it):\n". " Keep in mind that the suggestions are automated and may not be valid or safe to use\n$providersStr"; } return ["- Root composer.json requires linked library ".$packageName.self::constraintToText($constraint).' but ', 'it has the wrong version installed or is missing from your system, make sure to load the extension providing it.'.$providersStr]; } } $lockedPackage = null; foreach ($request->getLockedPackages() as $package) { if ($package->getName() === $packageName) { $lockedPackage = $package; if ($pool->isUnacceptableFixedOrLockedPackage($package)) { return ["- ", $package->getPrettyName().' is fixed to '.$package->getPrettyVersion().' (lock file version) by a partial update but that version is rejected by your minimum-stability. Make sure you list it as an argument for the update command.']; } break; } } if ($constraint instanceof Constraint && $constraint->getOperator() === Constraint::STR_OP_EQ && Preg::isMatch('{^dev-.*#.*}', $constraint->getPrettyString())) { $newConstraint = Preg::replace('{ +as +([^,\s|]+)$}', '', $constraint->getPrettyString()); $packages = $repositorySet->findPackages($packageName, new MultiConstraint([ new Constraint(Constraint::STR_OP_EQ, $newConstraint), new Constraint(Constraint::STR_OP_EQ, str_replace('#', '+', $newConstraint)), ], false)); if (\count($packages) > 0) { return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).'. The # character in branch names is replaced by a + character. Make sure to require it as "'.str_replace('#', '+', $constraint->getPrettyString()).'".']; } } $packages = $repositorySet->findPackages($packageName, $constraint); if (\count($packages) > 0) { $rootReqs = $repositorySet->getRootRequires(); if (isset($rootReqs[$packageName])) { if (!array_any($packages, static function ($p) use ($rootReqs, $packageName): bool { return $rootReqs[$packageName]->matches(new Constraint('==', $p->getVersion())); })) { return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but '.(self::hasMultipleNames($packages) ? 'these conflict' : 'it conflicts').' with your root composer.json require ('.$rootReqs[$packageName]->getPrettyString().').']; } } $tempReqs = $repositorySet->getTemporaryConstraints(); foreach (reset($packages)->getNames() as $name) { if (isset($tempReqs[$name])) { if (!array_any($packages, static function ($p) use ($tempReqs, $name): bool { return $tempReqs[$name]->matches(new Constraint('==', $p->getVersion())); })) { return ["- Root composer.json requires $name".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but '.(self::hasMultipleNames($packages) ? 'these conflict' : 'it conflicts').' with your temporary update constraint ('.$name.':'.$tempReqs[$name]->getPrettyString().').']; } } } if ($lockedPackage !== null) { $fixedConstraint = new Constraint('==', $lockedPackage->getVersion()); if (!array_any($packages, static function ($p) use ($fixedConstraint): bool { return $fixedConstraint->matches(new Constraint('==', $p->getVersion())); })) { return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but the package is fixed to '.$lockedPackage->getPrettyVersion().' (lock file version) by a partial update and that version does not match. Make sure you list it as an argument for the update command.']; } } if ($pool->isAbandonedRemovedPackageVersion($packageName, $constraint)) { return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but these were not loaded, because they are abandoned and you configured "policy.abandoned.block" to true.']; } if ($pool->isSecurityRemovedPackageVersion($packageName, $constraint)) { $advisories = $repositorySet->getMatchingSecurityAdvisories($packages, false, true); if (isset($advisories['advisories'][$packageName]) && \count($advisories['advisories'][$packageName]) > 0) { $advisoriesList = array_map(static function (SecurityAdvisory $advisory): string { if ($advisory->link !== null && $advisory->link !== '') { return 'link).'>'.$advisory->advisoryId.''; } if (str_starts_with($advisory->advisoryId, 'PKSA-')) { return 'advisoryId).'>'.$advisory->advisoryId.''; } return $advisory->advisoryId; }, $advisories['advisories'][$packageName]); $advisoryIds = array_map(static function (SecurityAdvisory $advisory): string { return $advisory->advisoryId; }, $advisories['advisories'][$packageName]); } else { $advisoryIds = $pool->getSecurityAdvisoryIdentifiersForPackageVersion($packageName, $constraint); $advisoriesList = array_map(static function (string $advisoryId): string { if (str_starts_with($advisoryId, 'PKSA-')) { return ''.$advisoryId.''; } return $advisoryId; }, $advisoryIds); } $hasPackagistAdvisories = true; foreach ($advisoryIds as $advisoryId) { if (!str_starts_with($advisoryId, 'PKSA-')) { $hasPackagistAdvisories = false; break; } } $advisoryDetailsHint = $hasPackagistAdvisories ? ' Go to https://packagist.org/security-advisories/ to find advisory details.' : ' Review the advisory details above for more information.'; return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but these were not loaded, because they are affected by security advisories ("' . implode('", "', $advisoriesList). '").'.$advisoryDetailsHint.' To ignore the advisories, add their IDs to the "policy.advisories.ignore-id" config or add the package to "policy.advisories.ignore". To turn the feature off entirely, you can set "policy.advisories.block" to false.']; } if ($pool->isFilterListRemovedPackageVersion($packageName, $constraint)) { $filters = $pool->getFilterListEntryForPackageVersion($packageName, $constraint); $ignorePaths = implode(' and ', array_map(static function (string $listName): string { return '"policy.' . $listName . '.ignore"'; }, array_keys($filters))); $offPaths = implode(' and ', array_map(static function (string $listName): string { return '"policy.' . $listName . '.block"'; }, array_keys($filters))); return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but these were not loaded, because they were ' . implode(', ', $filters). '. To ignore filters for this package, add the package to the ' . $ignorePaths . ' config. To turn the feature off entirely, you can set ' . $offPaths . ' to false.']; } if (!array_any($packages, static function ($p): bool { return !$p->getRepository() instanceof LockArrayRepository; })) { return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' in the lock file but not in remote repositories, make sure you avoid updating this package to keep the one from the lock file.']; } return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but these were not loaded, likely because '.(self::hasMultipleNames($packages) ? 'they conflict' : 'it conflicts').' with another require.']; } $packages = $repositorySet->findPackages($packageName, $constraint, RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES); if (\count($packages) > 0) { $allReposPackages = $repositorySet->findPackages($packageName, $constraint, RepositorySet::ALLOW_SHADOWED_REPOSITORIES); if (\count($allReposPackages) > 0) { return self::computeCheckForLowerPrioRepo($pool, $isVerbose, $packageName, $packages, $allReposPackages, 'minimum-stability', $constraint); } return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but '.(self::hasMultipleNames($packages) ? 'these do' : 'it does').' not match your minimum-stability.']; } $packages = $repositorySet->findPackages($packageName, null, RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES); if (\count($packages) > 0) { $allReposPackages = $repositorySet->findPackages($packageName, $constraint, RepositorySet::ALLOW_SHADOWED_REPOSITORIES); if (\count($allReposPackages) > 0) { return self::computeCheckForLowerPrioRepo($pool, $isVerbose, $packageName, $packages, $allReposPackages, 'constraint', $constraint); } $suffix = ''; if ($constraint instanceof Constraint && $constraint->getVersion() === 'dev-master') { foreach ($packages as $candidate) { if (in_array($candidate->getVersion(), ['dev-default', 'dev-main'], true)) { $suffix = ' Perhaps dev-master was renamed to '.$candidate->getPrettyVersion().'?'; break; } } } $allReposPackages = $packages; $topPackage = reset($allReposPackages); if ($topPackage instanceof RootPackageInterface) { $suffix = ' See https://getcomposer.org/dep-on-root for details and assistance.'; } return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but '.(self::hasMultipleNames($packages) ? 'these do' : 'it does').' not match the constraint.' . $suffix]; } if (!Preg::isMatch('{^[A-Za-z0-9_./-]+$}', $packageName)) { $illegalChars = Preg::replace('{[A-Za-z0-9_./-]+}', '', $packageName); return ["- Root composer.json requires $packageName, it ", 'could not be found, it looks like its name is invalid, "'.$illegalChars.'" is not allowed in package names.']; } $providersStr = self::getProvidersList($repositorySet, $packageName, 15); if ($providersStr !== null) { return ["- Root composer.json requires $packageName".self::constraintToText($constraint).", it ", "could not be found in any version, but the following packages provide it:\n".$providersStr." Consider requiring one of these to satisfy the $packageName requirement."]; } return ["- Root composer.json requires $packageName, it ", "could not be found in any version, there may be a typo in the package name."]; } public static function getMissingLockedPackageReason(Pool $pool, BasePackage $package): array { $packageName = $package->getName(); $constraint = new Constraint(Constraint::STR_OP_EQ, $package->getVersion()); $prefix = "- Package $packageName ".$package->getPrettyVersion().' (in the lock file) '; if ($pool->isFilterListRemovedPackageVersion($packageName, $constraint)) { $filters = $pool->getFilterListEntryForPackageVersion($packageName, $constraint); $ignorePaths = implode(' and ', array_map(static function (string $listName): string { return '"policy.' . $listName . '.ignore"'; }, array_keys($filters))); $offPaths = implode(' and ', array_map(static function (string $listName): string { return '"policy.' . $listName . '.block"'; }, array_keys($filters))); return [$prefix, 'was not loaded, because it was ' . implode(', ', $filters). '. To ignore filters for this package, add the package to the ' . $ignorePaths . ' config. To turn the feature off entirely, you can set ' . $offPaths . ' to false.']; } throw new \LogicException("Filter list removed locked package must have version removed from pool."); } public static function getPackageList(array $packages, bool $isVerbose, ?Pool $pool = null, ?ConstraintInterface $constraint = null, bool $useRemovedVersionGroup = false): string { $prepared = []; $hasDefaultBranch = []; foreach ($packages as $package) { $prepared[$package->getName()]['name'] = $package->getPrettyName(); $prepared[$package->getName()]['versions'][$package->getVersion()] = $package->getPrettyVersion().($package instanceof AliasPackage ? ' (alias of '.$package->getAliasOf()->getPrettyVersion().')' : ''); if ($pool !== null && $constraint !== null) { foreach ($pool->getRemovedVersions($package->getName(), $constraint) as $version => $prettyVersion) { $prepared[$package->getName()]['versions'][$version] = $prettyVersion; } } if ($pool !== null && $useRemovedVersionGroup) { foreach ($pool->getRemovedVersionsByPackage(spl_object_hash($package)) as $version => $prettyVersion) { $prepared[$package->getName()]['versions'][$version] = $prettyVersion; } } if ($package->isDefaultBranch()) { $hasDefaultBranch[$package->getName()] = true; } } $preparedStrings = []; foreach ($prepared as $name => $package) { if (isset($package['versions'][VersionParser::DEFAULT_BRANCH_ALIAS], $hasDefaultBranch[$name])) { unset($package['versions'][VersionParser::DEFAULT_BRANCH_ALIAS]); } uksort($package['versions'], 'version_compare'); if (!$isVerbose) { $package['versions'] = self::condenseVersionList($package['versions'], 4); } $preparedStrings[] = $package['name'].'['.implode(', ', $package['versions']).']'; } return implode(', ', $preparedStrings); } private static function getPlatformPackageVersion(Pool $pool, string $packageName, string $version): ?string { $available = $pool->whatProvides($packageName); if (\count($available) > 0) { $selected = null; foreach ($available as $pkg) { if ($pkg->getRepository() instanceof PlatformRepository) { $selected = $pkg; break; } } if ($selected === null) { $selected = reset($available); } if ($selected->getName() !== $packageName) { foreach (array_merge(array_values($selected->getProvides()), array_values($selected->getReplaces())) as $link) { if ($link->getTarget() === $packageName) { return $link->getPrettyConstraint().' '.substr($link->getDescription(), 0, -1).'d by '.$selected->getPrettyString(); } } } $version = $selected->getPrettyVersion(); $extra = $selected->getExtra(); if ($selected instanceof CompletePackageInterface && isset($extra['config.platform']) && $extra['config.platform'] === true) { $version .= '; ' . str_replace('Package ', '', (string) $selected->getDescription()); } } else { return null; } return $version; } private static function condenseVersionList(array $versions, int $max, int $maxDev = 16): array { if (count($versions) <= $max) { return array_values($versions); } $filtered = []; $byMajor = []; foreach ($versions as $version => $pretty) { if (0 === stripos((string) $version, 'dev-')) { $byMajor['dev'][] = $pretty; } else { $byMajor[Preg::replace('{^(\d+)\..*}', '$1', (string) $version)][] = $pretty; } } foreach ($byMajor as $majorVersion => $versionsForMajor) { $maxVersions = $majorVersion === 'dev' ? $maxDev : $max; if (count($versionsForMajor) > $maxVersions) { $filtered[] = $versionsForMajor[0]; $filtered[] = '...'; $filtered[] = $versionsForMajor[count($versionsForMajor) - 1]; } else { $filtered = array_merge($filtered, $versionsForMajor); } } return $filtered; } private static function hasMultipleNames(array $packages): bool { $name = null; foreach ($packages as $package) { if ($name === null || $name === $package->getName()) { $name = $package->getName(); } else { return true; } } return false; } private static function computeCheckForLowerPrioRepo(Pool $pool, bool $isVerbose, string $packageName, array $higherRepoPackages, array $allReposPackages, string $reason, ?ConstraintInterface $constraint = null): array { $nextRepoPackages = []; $nextRepo = null; foreach ($allReposPackages as $package) { if ($nextRepo === null || $nextRepo === $package->getRepository()) { $nextRepoPackages[] = $package; $nextRepo = $package->getRepository(); } else { break; } } assert(null !== $nextRepo); if (\count($higherRepoPackages) > 0) { $topPackage = reset($higherRepoPackages); if ($topPackage instanceof RootPackageInterface) { return [ "- Root composer.json requires $packageName".self::constraintToText($constraint).', it is ', 'satisfiable by '.self::getPackageList($nextRepoPackages, $isVerbose, $pool, $constraint).' from '.$nextRepo->getRepoName().' but '.$topPackage->getPrettyName().' '.$topPackage->getPrettyVersion().' is the root package and cannot be modified. See https://getcomposer.org/dep-on-root for details and assistance.', ]; } } if ($nextRepo instanceof LockArrayRepository) { $singular = count($higherRepoPackages) === 1; $suggestion = 'Make sure you either fix the '.$reason.' or avoid updating this package to keep the one present in the lock file ('.self::getPackageList($nextRepoPackages, $isVerbose, $pool, $constraint).').'; if ($nextRepoPackages[0]->getDistType() === 'path') { $transportOptions = $nextRepoPackages[0]->getTransportOptions(); if (!isset($transportOptions['symlink']) || $transportOptions['symlink'] !== false) { $suggestion = 'Make sure you fix the '.$reason.' as packages installed from symlinked path repos are updated even in partial updates and the one from the lock file can thus not be used.'; } } return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found ' . self::getPackageList($higherRepoPackages, $isVerbose, $pool, $constraint).' but ' . ($singular ? 'it does' : 'these do') . ' not match your '.$reason.' and ' . ($singular ? 'is' : 'are') . ' therefore not installable. '.$suggestion, ]; } return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', it is ', 'satisfiable by '.self::getPackageList($nextRepoPackages, $isVerbose, $pool, $constraint).' from '.$nextRepo->getRepoName().' but '.self::getPackageList($higherRepoPackages, $isVerbose, $pool, $constraint).' from '.reset($higherRepoPackages)->getRepository()->getRepoName().' has higher repository priority. The packages from the higher priority repository do not match your '.$reason.' and are therefore not installable. That repository is canonical so the lower priority repo\'s packages are not installable. See https://getcomposer.org/repoprio for details and assistance.']; } protected static function constraintToText(?ConstraintInterface $constraint = null): string { if ($constraint instanceof Constraint && $constraint->getOperator() === Constraint::STR_OP_EQ && !str_starts_with($constraint->getVersion(), 'dev-')) { if (!Preg::isMatch('{^\d+(?:\.\d+)*$}', $constraint->getPrettyString())) { return ' '.$constraint->getPrettyString() .' (exact version match)'; } $versions = [$constraint->getPrettyString()]; for ($i = 3 - substr_count($versions[0], '.'); $i > 0; $i--) { $versions[] = end($versions) . '.0'; } return ' ' . $constraint->getPrettyString() . ' (exact version match: ' . (count($versions) > 1 ? implode(', ', array_slice($versions, 0, -1)) . ' or ' . end($versions) : $versions[0]) . ')'; } return $constraint !== null ? ' '.$constraint->getPrettyString() : ''; } private static function getProvidersList(RepositorySet $repositorySet, string $packageName, int $maxProviders): ?string { $providers = $repositorySet->getProviders($packageName); if (\count($providers) > 0) { $providersStr = implode(array_map(static function ($p): string { $description = $p['description'] !== '' && $p['description'] !== null ? ' '.substr($p['description'], 0, 100) : ''; return ' - '.$p['name'].$description."\n"; }, count($providers) > $maxProviders + 1 ? array_slice($providers, 0, $maxProviders) : $providers)); if (count($providers) > $maxProviders + 1) { $providersStr .= ' ... and '.(count($providers) - $maxProviders).' more.'."\n"; } return $providersStr; } return null; } } lockedRepository = $lockedRepository; } public function requireName(string $packageName, ?ConstraintInterface $constraint = null): void { $packageName = strtolower($packageName); if ($constraint === null) { $constraint = new MatchAllConstraint(); } if (isset($this->requires[$packageName])) { throw new \LogicException('Overwriting requires seems like a bug ('.$packageName.' '.$this->requires[$packageName]->getPrettyString().' => '.$constraint->getPrettyString().', check why it is happening, might be a root alias'); } $this->requires[$packageName] = $constraint; } public function fixPackage(BasePackage $package): void { $this->fixedPackages[spl_object_hash($package)] = $package; } public function lockPackage(BasePackage $package): void { $this->lockedPackages[spl_object_hash($package)] = $package; } public function fixLockedPackage(BasePackage $package): void { $this->fixedPackages[spl_object_hash($package)] = $package; $this->fixedLockedPackages[spl_object_hash($package)] = $package; } public function unlockPackage(BasePackage $package): void { unset($this->lockedPackages[spl_object_hash($package)]); } public function setUpdateAllowList(array $updateAllowList, $updateAllowTransitiveDependencies): void { $this->updateAllowList = $updateAllowList; $this->updateAllowTransitiveDependencies = $updateAllowTransitiveDependencies; } public function getUpdateAllowList(): array { return $this->updateAllowList; } public function getUpdateAllowTransitiveDependencies(): bool { return $this->updateAllowTransitiveDependencies !== self::UPDATE_ONLY_LISTED; } public function getUpdateAllowTransitiveRootDependencies(): bool { return $this->updateAllowTransitiveDependencies === self::UPDATE_LISTED_WITH_TRANSITIVE_DEPS; } public function getRequires(): array { return $this->requires; } public function getFixedPackages(): array { return $this->fixedPackages; } public function isFixedPackage(BasePackage $package): bool { return isset($this->fixedPackages[spl_object_hash($package)]); } public function getLockedPackages(): array { return array_merge($this->lockedPackages, $this->fixedLockedPackages); } public function isLockedPackage(PackageInterface $package): bool { return isset($this->lockedPackages[spl_object_hash($package)]) || isset($this->fixedLockedPackages[spl_object_hash($package)]); } public function getFixedOrLockedPackages(): array { return array_merge($this->fixedPackages, $this->lockedPackages); } public function getPresentMap(bool $packageIds = false): array { $presentMap = []; if ($this->lockedRepository !== null) { foreach ($this->lockedRepository->getPackages() as $package) { $presentMap[$packageIds ? $package->getId() : spl_object_hash($package)] = $package; } } foreach ($this->fixedPackages as $package) { $presentMap[$packageIds ? $package->getId() : spl_object_hash($package)] = $package; } return $presentMap; } public function getFixedPackagesMap(): array { $fixedPackagesMap = []; foreach ($this->fixedPackages as $package) { $fixedPackagesMap[$package->getId()] = $package; } return $fixedPackagesMap; } public function getLockedRepository(): ?LockArrayRepository { return $this->lockedRepository; } public function restrictPackages(array $names): void { $this->restrictedPackages = $names; } public function getRestrictedPackages(): ?array { return $this->restrictedPackages; } } reasonData = $reasonData; $this->bitfield = (0 << self::BITFIELD_DISABLED) | ($reason << self::BITFIELD_REASON) | (255 << self::BITFIELD_TYPE); } abstract public function getLiterals(): array; abstract public function getHash(); abstract public function __toString(): string; abstract public function equals(Rule $rule): bool; public function getReason(): int { return ($this->bitfield & (255 << self::BITFIELD_REASON)) >> self::BITFIELD_REASON; } public function getReasonData() { return $this->reasonData; } public function getRequiredPackage(): ?string { switch ($this->getReason()) { case self::RULE_ROOT_REQUIRE: return $this->getReasonData()['packageName']; case self::RULE_FIXED: case self::RULE_LOCKED_FILTER_LIST_REMOVED: return $this->getReasonData()['package']->getName(); case self::RULE_PACKAGE_REQUIRES: return $this->getReasonData()->getTarget(); } return null; } public function setType($type): void { $this->bitfield = ($this->bitfield & ~(255 << self::BITFIELD_TYPE)) | ((255 & $type) << self::BITFIELD_TYPE); } public function getType(): int { return ($this->bitfield & (255 << self::BITFIELD_TYPE)) >> self::BITFIELD_TYPE; } public function disable(): void { $this->bitfield = ($this->bitfield & ~(255 << self::BITFIELD_DISABLED)) | (1 << self::BITFIELD_DISABLED); } public function enable(): void { $this->bitfield &= ~(255 << self::BITFIELD_DISABLED); } public function isDisabled(): bool { return 0 !== (($this->bitfield & (255 << self::BITFIELD_DISABLED)) >> self::BITFIELD_DISABLED); } public function isEnabled(): bool { return 0 === (($this->bitfield & (255 << self::BITFIELD_DISABLED)) >> self::BITFIELD_DISABLED); } abstract public function isAssertion(): bool; public function isCausedByLock(RepositorySet $repositorySet, Request $request, Pool $pool): bool { if ($this->getReason() === self::RULE_PACKAGE_REQUIRES) { if (PlatformRepository::isPlatformPackage($this->getReasonData()->getTarget())) { return false; } if ($request->getLockedRepository() !== null) { foreach ($request->getLockedRepository()->getPackages() as $package) { if ($package->getName() === $this->getReasonData()->getTarget()) { if ($pool->isUnacceptableFixedOrLockedPackage($package)) { return true; } if (!$this->getReasonData()->getConstraint()->matches(new Constraint('=', $package->getVersion()))) { return true; } if (!$request->isLockedPackage($package)) { return true; } break; } } } } if ($this->getReason() === self::RULE_ROOT_REQUIRE) { if (PlatformRepository::isPlatformPackage($this->getReasonData()['packageName'])) { return false; } if ($request->getLockedRepository() !== null) { foreach ($request->getLockedRepository()->getPackages() as $package) { if ($package->getName() === $this->getReasonData()['packageName']) { if ($pool->isUnacceptableFixedOrLockedPackage($package)) { return true; } if (!$this->getReasonData()['constraint']->matches(new Constraint('=', $package->getVersion()))) { return true; } break; } } } } return false; } public function getSourcePackage(Pool $pool): BasePackage { $literals = $this->getLiterals(); switch ($this->getReason()) { case self::RULE_PACKAGE_CONFLICT: $package1 = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[0])); $package2 = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[1])); $reasonData = $this->getReasonData(); if ($reasonData->getSource() === $package1->getName()) { [$package2, $package1] = [$package1, $package2]; } return $package2; case self::RULE_PACKAGE_REQUIRES: $sourceLiteral = $literals[0]; $sourcePackage = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($sourceLiteral)); return $sourcePackage; default: throw new \LogicException('Not implemented'); } } public function getPrettyString(RepositorySet $repositorySet, Request $request, Pool $pool, bool $isVerbose, array $installedMap = [], array $learnedPool = []): string { $literals = $this->getLiterals(); switch ($this->getReason()) { case self::RULE_ROOT_REQUIRE: $reasonData = $this->getReasonData(); $packageName = $reasonData['packageName']; $constraint = $reasonData['constraint']; $packages = $pool->whatProvides($packageName, $constraint); if (0 === \count($packages)) { return 'No package found to satisfy root composer.json require '.$packageName.' '.$constraint->getPrettyString(); } $packagesNonAlias = array_values(array_filter($packages, static function ($p): bool { return !($p instanceof AliasPackage); })); if (\count($packagesNonAlias) === 1) { $package = $packagesNonAlias[0]; if ($request->isLockedPackage($package)) { return $package->getPrettyName().' is locked to version '.$package->getPrettyVersion()." and an update of this package was not requested."; } } return 'Root composer.json requires '.$packageName.' '.$constraint->getPrettyString().' -> satisfiable by '.$this->formatPackagesUnique($pool, $packages, $isVerbose, $constraint).'.'; case self::RULE_FIXED: $package = $this->deduplicateDefaultBranchAlias($this->getReasonData()['package']); if ($request->isLockedPackage($package)) { return $package->getPrettyName().' is locked to version '.$package->getPrettyVersion().' and an update of this package was not requested.'; } return $package->getPrettyName().' is present at version '.$package->getPrettyVersion() . ' and cannot be modified by Composer'; case self::RULE_LOCKED_FILTER_LIST_REMOVED: $package = $this->deduplicateDefaultBranchAlias($this->getReasonData()['package']); return $package->getPrettyName().' '.$package->getPrettyVersion().' was removed by a dependency policy (e.g. malware) and cannot be installed.'; case self::RULE_PACKAGE_CONFLICT: $package1 = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[0])); $package2 = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[1])); $conflictTarget = $package1->getPrettyString(); $reasonData = $this->getReasonData(); if ($reasonData->getSource() === $package1->getName()) { [$package2, $package1] = [$package1, $package2]; $conflictTarget = $package1->getPrettyName().' '.$reasonData->getPrettyConstraint(); } if ($reasonData->getTarget() !== $package1->getName()) { $provideType = null; $provided = null; foreach ($package1->getProvides() as $provide) { if ($provide->getTarget() === $reasonData->getTarget()) { $provideType = 'provides'; $provided = $provide->getPrettyConstraint(); break; } } foreach ($package1->getReplaces() as $replace) { if ($replace->getTarget() === $reasonData->getTarget()) { $provideType = 'replaces'; $provided = $replace->getPrettyConstraint(); break; } } if (null !== $provideType) { $conflictTarget = $reasonData->getTarget().' '.$reasonData->getPrettyConstraint().' ('.$package1->getPrettyString().' '.$provideType.' '.$reasonData->getTarget().' '.$provided.')'; } } return $package2->getPrettyString().' conflicts with '.$conflictTarget.'.'; case self::RULE_PACKAGE_REQUIRES: assert(\count($literals) > 0); $sourceLiteral = array_shift($literals); $sourcePackage = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($sourceLiteral)); $reasonData = $this->getReasonData(); $requires = []; foreach ($literals as $literal) { $requires[] = $pool->literalToPackage($literal); } $text = $reasonData->getPrettyString($sourcePackage); if (\count($requires) > 0) { $text .= ' -> satisfiable by ' . $this->formatPackagesUnique($pool, $requires, $isVerbose, $reasonData->getConstraint()) . '.'; } else { $targetName = $reasonData->getTarget(); $reason = Problem::getMissingPackageReason($repositorySet, $request, $pool, $isVerbose, $targetName, $reasonData->getConstraint()); return $text . ' -> ' . $reason[1]; } return $text; case self::RULE_PACKAGE_SAME_NAME: $packageNames = []; foreach ($literals as $literal) { $package = $pool->literalToPackage($literal); $packageNames[$package->getName()] = true; } unset($literal); $replacedName = $this->getReasonData(); if (\count($packageNames) > 1) { if (!isset($packageNames[$replacedName])) { $reason = 'They '.(\count($literals) === 2 ? 'both' : 'all').' replace '.$replacedName.' and thus cannot coexist.'; } else { $replacerNames = $packageNames; unset($replacerNames[$replacedName]); $replacerNames = array_keys($replacerNames); if (\count($replacerNames) === 1) { $reason = $replacerNames[0] . ' replaces '; } else { $reason = '['.implode(', ', $replacerNames).'] replace '; } $reason .= $replacedName.' and thus cannot coexist with it.'; } $installedPackages = []; $removablePackages = []; foreach ($literals as $literal) { if (isset($installedMap[abs($literal)])) { $installedPackages[] = $pool->literalToPackage($literal); } else { $removablePackages[] = $pool->literalToPackage($literal); } } if (\count($installedPackages) > 0 && \count($removablePackages) > 0) { return $this->formatPackagesUnique($pool, $removablePackages, $isVerbose, null, true).' cannot be installed as that would require removing '.$this->formatPackagesUnique($pool, $installedPackages, $isVerbose, null, true).'. '.$reason; } return 'Only one of these can be installed: '.$this->formatPackagesUnique($pool, $literals, $isVerbose, null, true).'. '.$reason; } return 'You can only install one version of a package, so only one of these can be installed: ' . $this->formatPackagesUnique($pool, $literals, $isVerbose, null, true) . '.'; case self::RULE_LEARNED: $learnedString = ' (conflict analysis result)'; if (\count($literals) === 1) { $ruleText = $pool->literalToPrettyString($literals[0], $installedMap); } else { $groups = []; foreach ($literals as $literal) { $package = $pool->literalToPackage($literal); if (isset($installedMap[$package->id])) { $group = $literal > 0 ? 'keep' : 'remove'; } else { $group = $literal > 0 ? 'install' : 'don\'t install'; } $groups[$group][] = $this->deduplicateDefaultBranchAlias($package); } $ruleTexts = []; foreach ($groups as $group => $packages) { $ruleTexts[] = $group . (\count($packages) > 1 ? ' one of' : '').' ' . $this->formatPackagesUnique($pool, $packages, $isVerbose); } $ruleText = implode(' | ', $ruleTexts); } return 'Conclusion: '.$ruleText.$learnedString; case self::RULE_PACKAGE_ALIAS: $aliasPackage = $pool->literalToPackage($literals[0]); if ($aliasPackage->getVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) { return ''; } $package = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[1])); return $aliasPackage->getPrettyString() .' is an alias of '.$package->getPrettyString().' and thus requires it to be installed too.'; case self::RULE_PACKAGE_INVERSE_ALIAS: $aliasPackage = $pool->literalToPackage($literals[1]); if ($aliasPackage->getVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) { return ''; } $package = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[0])); return $aliasPackage->getPrettyString() .' is an alias of '.$package->getPrettyString().' and must be installed with it.'; default: $ruleText = ''; foreach ($literals as $i => $literal) { if ($i !== 0) { $ruleText .= '|'; } $ruleText .= $pool->literalToPrettyString($literal, $installedMap); } return '('.$ruleText.')'; } } protected function formatPackagesUnique(Pool $pool, array $literalsOrPackages, bool $isVerbose, ?ConstraintInterface $constraint = null, bool $useRemovedVersionGroup = false): string { $packages = []; foreach ($literalsOrPackages as $package) { $packages[] = \is_object($package) ? $package : $pool->literalToPackage($package); } return Problem::getPackageList($packages, $isVerbose, $pool, $constraint, $useRemovedVersionGroup); } private function deduplicateDefaultBranchAlias(BasePackage $package): BasePackage { if ($package instanceof AliasPackage && $package->getPrettyVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) { $package = $package->getAliasOf(); } return $package; } } literal1 = $literal1; $this->literal2 = $literal2; } else { $this->literal1 = $literal2; $this->literal2 = $literal1; } } public function getLiterals(): array { return [$this->literal1, $this->literal2]; } public function getHash() { return $this->literal1.','.$this->literal2; } public function equals(Rule $rule): bool { if ($rule instanceof self) { if ($this->literal1 !== $rule->literal1) { return false; } if ($this->literal2 !== $rule->literal2) { return false; } return true; } $literals = $rule->getLiterals(); if (2 !== \count($literals)) { return false; } if ($this->literal1 !== $literals[0]) { return false; } if ($this->literal2 !== $literals[1]) { return false; } return true; } public function isAssertion(): bool { return false; } public function __toString(): string { $result = $this->isDisabled() ? 'disabled(' : '('; $result .= $this->literal1 . '|' . $this->literal2 . ')'; return $result; } } 'PACKAGE', self::TYPE_REQUEST => 'REQUEST', self::TYPE_LEARNED => 'LEARNED', ]; protected $rules; protected $nextRuleId = 0; protected $rulesByHash = []; public function __construct() { foreach ($this->getTypes() as $type) { $this->rules[$type] = []; } } public function add(Rule $rule, $type): void { if (!isset(self::TYPES[$type])) { throw new \OutOfBoundsException('Unknown rule type: ' . $type); } $hash = $rule->getHash(); if (isset($this->rulesByHash[$hash])) { $potentialDuplicates = $this->rulesByHash[$hash]; if (\is_array($potentialDuplicates)) { foreach ($potentialDuplicates as $potentialDuplicate) { if ($rule->equals($potentialDuplicate)) { return; } } } else { if ($rule->equals($potentialDuplicates)) { return; } } } if (!isset($this->rules[$type])) { $this->rules[$type] = []; } $this->rules[$type][] = $rule; $this->ruleById[$this->nextRuleId] = $rule; $rule->setType($type); $this->nextRuleId++; if (!isset($this->rulesByHash[$hash])) { $this->rulesByHash[$hash] = $rule; } elseif (\is_array($this->rulesByHash[$hash])) { $this->rulesByHash[$hash][] = $rule; } else { $originalRule = $this->rulesByHash[$hash]; $this->rulesByHash[$hash] = [$originalRule, $rule]; } } public function count(): int { return $this->nextRuleId; } public function ruleById(int $id): Rule { return $this->ruleById[$id]; } public function getRules(): array { return $this->rules; } public function getIterator(): RuleSetIterator { return new RuleSetIterator($this->getRules()); } public function getIteratorFor($types): RuleSetIterator { if (!\is_array($types)) { $types = [$types]; } $allRules = $this->getRules(); $rules = []; foreach ($types as $type) { $rules[$type] = $allRules[$type]; } return new RuleSetIterator($rules); } public function getIteratorWithout($types): RuleSetIterator { if (!\is_array($types)) { $types = [$types]; } $rules = $this->getRules(); foreach ($types as $type) { unset($rules[$type]); } return new RuleSetIterator($rules); } public function getTypes(): array { $types = self::TYPES; return array_keys($types); } public function getPrettyString(?RepositorySet $repositorySet = null, ?Request $request = null, ?Pool $pool = null, bool $isVerbose = false): string { $string = "\n"; foreach ($this->rules as $type => $rules) { $string .= str_pad(self::TYPES[$type], 8, ' ') . ": "; foreach ($rules as $rule) { $string .= ($repositorySet !== null && $request !== null && $pool !== null ? $rule->getPrettyString($repositorySet, $request, $pool, $isVerbose) : $rule)."\n"; } $string .= "\n\n"; } return $string; } public function __toString(): string { return $this->getPrettyString(); } } policy = $policy; $this->pool = $pool; $this->rules = new RuleSet; } protected function createRequireRule(BasePackage $package, array $providers, $reason, $reasonData): ?Rule { $literals = [-$package->id]; foreach ($providers as $provider) { if ($provider === $package) { return null; } $literals[] = $provider->id; } return new GenericRule($literals, $reason, $reasonData); } protected function createInstallOneOfRule(array $packages, $reason, $reasonData): Rule { $literals = []; foreach ($packages as $package) { $literals[] = $package->id; } return new GenericRule($literals, $reason, $reasonData); } protected function createRule2Literals(BasePackage $issuer, BasePackage $provider, $reason, $reasonData): ?Rule { if ($issuer === $provider) { return null; } return new Rule2Literals(-$issuer->id, -$provider->id, $reason, $reasonData); } protected function createMultiConflictRule(array $packages, $reason, $reasonData): Rule { $literals = []; foreach ($packages as $package) { $literals[] = -$package->id; } if (\count($literals) === 2) { return new Rule2Literals($literals[0], $literals[1], $reason, $reasonData); } return new MultiConflictRule($literals, $reason, $reasonData); } private function addRule($type, ?Rule $newRule = null): void { if (null === $newRule) { return; } $this->rules->add($newRule, $type); } protected function addRulesForPackage(BasePackage $package, PlatformRequirementFilterInterface $platformRequirementFilter): void { $workQueue = new \SplQueue; $workQueue->enqueue($package); while (!$workQueue->isEmpty()) { $package = $workQueue->dequeue(); if (isset($this->addedMap[$package->id])) { continue; } $this->addedMap[$package->id] = $package; if (!$package instanceof AliasPackage) { foreach ($package->getNames(false) as $name) { $this->addedPackagesByNames[$name][] = $package; } } else { $workQueue->enqueue($package->getAliasOf()); $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRequireRule($package, [$package->getAliasOf()], Rule::RULE_PACKAGE_ALIAS, $package)); $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRequireRule($package->getAliasOf(), [$package], Rule::RULE_PACKAGE_INVERSE_ALIAS, $package->getAliasOf())); if (!$package->hasSelfVersionRequires()) { continue; } } foreach ($package->getRequires() as $link) { $constraint = $link->getConstraint(); if ($platformRequirementFilter->isIgnored($link->getTarget())) { continue; } elseif ($platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter) { $constraint = $platformRequirementFilter->filterConstraint($link->getTarget(), $constraint); } $possibleRequires = $this->pool->whatProvides($link->getTarget(), $constraint); $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRequireRule($package, $possibleRequires, Rule::RULE_PACKAGE_REQUIRES, $link)); foreach ($possibleRequires as $require) { $workQueue->enqueue($require); } } } } protected function addConflictRules(PlatformRequirementFilterInterface $platformRequirementFilter): void { foreach ($this->addedMap as $package) { foreach ($package->getConflicts() as $link) { if (!isset($this->addedPackagesByNames[$link->getTarget()])) { continue; } $constraint = $link->getConstraint(); if ($platformRequirementFilter->isIgnored($link->getTarget())) { continue; } elseif ($platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter) { $constraint = $platformRequirementFilter->filterConstraint($link->getTarget(), $constraint, false); } $conflicts = $this->pool->whatProvides($link->getTarget(), $constraint); foreach ($conflicts as $conflict) { if (!$conflict instanceof AliasPackage || $conflict->getName() === $link->getTarget()) { $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $conflict, Rule::RULE_PACKAGE_CONFLICT, $link)); } } } } foreach ($this->addedPackagesByNames as $name => $packages) { if (\count($packages) > 1) { $reason = Rule::RULE_PACKAGE_SAME_NAME; $this->addRule(RuleSet::TYPE_PACKAGE, $this->createMultiConflictRule($packages, $reason, $name)); } } } protected function addRulesForRequest(Request $request, PlatformRequirementFilterInterface $platformRequirementFilter): void { foreach ($request->getFixedPackages() as $package) { if ($request->isLockedPackage($package) && $this->pool->isFilterListRemovedPackageVersion($package->getName(), new Constraint(Constraint::STR_OP_EQ, $package->getVersion()))) { continue; } if ($package->id === -1) { if ($this->pool->isUnacceptableFixedOrLockedPackage($package)) { continue; } throw new \LogicException("Fixed package ".$package->getPrettyString()." was not added to solver pool."); } $this->addRulesForPackage($package, $platformRequirementFilter); $rule = $this->createInstallOneOfRule([$package], Rule::RULE_FIXED, [ 'package' => $package, ]); $this->addRule(RuleSet::TYPE_REQUEST, $rule); } foreach ($request->getRequires() as $packageName => $constraint) { if ($platformRequirementFilter->isIgnored($packageName)) { continue; } elseif ($platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter) { $constraint = $platformRequirementFilter->filterConstraint($packageName, $constraint); } $packages = $this->pool->whatProvides($packageName, $constraint); if (\count($packages) > 0) { foreach ($packages as $package) { $this->addRulesForPackage($package, $platformRequirementFilter); } $rule = $this->createInstallOneOfRule($packages, Rule::RULE_ROOT_REQUIRE, [ 'packageName' => $packageName, 'constraint' => $constraint, ]); $this->addRule(RuleSet::TYPE_REQUEST, $rule); } } } protected function addRulesForRootAliases(PlatformRequirementFilterInterface $platformRequirementFilter): void { foreach ($this->pool->getPackages() as $package) { if (!isset($this->addedMap[$package->id]) && $package instanceof AliasPackage && ($package->isRootPackageAlias() || isset($this->addedMap[$package->getAliasOf()->id])) ) { $this->addRulesForPackage($package, $platformRequirementFilter); } } } public function getRulesFor(Request $request, ?PlatformRequirementFilterInterface $platformRequirementFilter = null): RuleSet { $platformRequirementFilter = $platformRequirementFilter ?? PlatformRequirementFilterFactory::ignoreNothing(); $this->addRulesForRequest($request, $platformRequirementFilter); $this->addRulesForRootAliases($platformRequirementFilter); $this->addConflictRules($platformRequirementFilter); $this->addedMap = $this->addedPackagesByNames = []; $rules = $this->rules; $this->rules = new RuleSet; return $rules; } } rules = $rules; $this->types = array_keys($rules); sort($this->types); $this->rewind(); } public function current(): Rule { return $this->rules[$this->currentType][$this->currentOffset]; } public function key(): int { return $this->currentType; } public function next(): void { $this->currentOffset++; if (!isset($this->rules[$this->currentType])) { return; } if ($this->currentOffset >= \count($this->rules[$this->currentType])) { $this->currentOffset = 0; do { $this->currentTypeOffset++; if (!isset($this->types[$this->currentTypeOffset])) { $this->currentType = -1; break; } $this->currentType = $this->types[$this->currentTypeOffset]; } while (0 === \count($this->rules[$this->currentType])); } } public function rewind(): void { $this->currentOffset = 0; $this->currentTypeOffset = -1; $this->currentType = -1; do { $this->currentTypeOffset++; if (!isset($this->types[$this->currentTypeOffset])) { $this->currentType = -1; break; } $this->currentType = $this->types[$this->currentTypeOffset]; } while (0 === \count($this->rules[$this->currentType])); } public function valid(): bool { return isset($this->rules[$this->currentType], $this->rules[$this->currentType][$this->currentOffset]); } } rewind(); for ($i = 0; $i < $offset; $i++, $this->next()); } public function remove(): void { $offset = $this->key(); $this->offsetUnset($offset); $this->seek($offset); } } getRule()->isAssertion()) { return; } if (!$node->getRule() instanceof MultiConflictRule) { foreach ([$node->watch1, $node->watch2] as $literal) { if (!isset($this->watchChains[$literal])) { $this->watchChains[$literal] = new RuleWatchChain; } $this->watchChains[$literal]->unshift($node); } } else { foreach ($node->getRule()->getLiterals() as $literal) { if (!isset($this->watchChains[$literal])) { $this->watchChains[$literal] = new RuleWatchChain; } $this->watchChains[$literal]->unshift($node); } } } public function propagateLiteral(int $decidedLiteral, int $level, Decisions $decisions): ?Rule { $literal = -$decidedLiteral; if (!isset($this->watchChains[$literal])) { return null; } $chain = $this->watchChains[$literal]; $chain->rewind(); while ($chain->valid()) { $node = $chain->current(); if (!$node->getRule() instanceof MultiConflictRule) { $otherWatch = $node->getOtherWatch($literal); if (!$node->getRule()->isDisabled() && !$decisions->satisfy($otherWatch)) { $ruleLiterals = $node->getRule()->getLiterals(); $alternativeLiteral = array_find($ruleLiterals, static function ($ruleLiteral) use ($literal, $otherWatch, $decisions): bool { return $ruleLiteral !== $literal && $ruleLiteral !== $otherWatch && !$decisions->conflict($ruleLiteral); }); if ($alternativeLiteral !== null) { $this->moveWatch($literal, $alternativeLiteral, $node); continue; } if ($decisions->conflict($otherWatch)) { return $node->getRule(); } $decisions->decide($otherWatch, $level, $node->getRule()); } } else { foreach ($node->getRule()->getLiterals() as $otherLiteral) { if ($literal !== $otherLiteral && !$decisions->satisfy($otherLiteral)) { if ($decisions->conflict($otherLiteral)) { return $node->getRule(); } $decisions->decide($otherLiteral, $level, $node->getRule()); } } } $chain->next(); } return null; } protected function moveWatch(int $fromLiteral, int $toLiteral, RuleWatchNode $node): void { if (!isset($this->watchChains[$toLiteral])) { $this->watchChains[$toLiteral] = new RuleWatchChain; } $node->moveWatch($fromLiteral, $toLiteral); $this->watchChains[$fromLiteral]->remove(); $this->watchChains[$toLiteral]->unshift($node); } } rule = $rule; $literals = $rule->getLiterals(); $literalCount = \count($literals); $this->watch1 = $literalCount > 0 ? $literals[0] : 0; $this->watch2 = $literalCount > 1 ? $literals[1] : 0; } public function watch2OnHighest(Decisions $decisions): void { $literals = $this->rule->getLiterals(); if (\count($literals) < 3 || $this->rule instanceof MultiConflictRule) { return; } $watchLevel = 0; foreach ($literals as $literal) { $level = $decisions->decisionLevel($literal); if ($level > $watchLevel) { $this->watch2 = $literal; $watchLevel = $level; } } } public function getRule(): Rule { return $this->rule; } public function getOtherWatch(int $literal): int { if ($this->watch1 === $literal) { return $this->watch2; } return $this->watch1; } public function moveWatch(int $from, int $to): void { if ($this->watch1 === $from) { $this->watch1 = $to; } else { $this->watch2 = $to; } } } auditor = $auditor; $this->policyConfig = $policyConfig; $this->io = $io; } public function filter(Pool $pool, array $repositories, Request $request): Pool { $advisories = $this->policyConfig->advisories; $abandoned = $this->policyConfig->abandoned; if (!$advisories->block) { return $pool; } $repoSet = new RepositorySet(); foreach ($repositories as $repo) { $repoSet->addRepository($repo); } $packagesForAdvisories = []; foreach ($pool->getPackages() as $package) { if (!$package instanceof RootPackageInterface && !PlatformRepository::isPlatformPackage($package->getName()) && !$request->isLockedPackage($package)) { $packagesForAdvisories[] = $package; } } $ignoreListForBlocking = $advisories->getIgnoreListForOperation('block'); $ignoreUnreachableUpdate = $this->policyConfig->ignoreUnreachable->update; $allAdvisories = $repoSet->getMatchingSecurityAdvisories($packagesForAdvisories, true, $ignoreUnreachableUpdate); if ($this->auditor->needsCompleteAdvisoryLoad($allAdvisories['advisories'], $ignoreListForBlocking)) { $allAdvisories = $repoSet->getMatchingSecurityAdvisories($packagesForAdvisories, false, $ignoreUnreachableUpdate); } if ($ignoreUnreachableUpdate && count($allAdvisories['unreachableRepos']) > 0) { $this->io->writeError('Security advisory data could not be fetched from some repositories (ignored per policy.ignore-unreachable); matches may be incomplete:'); foreach ($allAdvisories['unreachableRepos'] as $repo) { $this->io->writeError(' - ' . $repo); } } $advisoryMap = $this->auditor->processAdvisories($allAdvisories['advisories'], $ignoreListForBlocking, $advisories->getIgnoreSeverityForOperation('block'))['advisories']; $ignoreAbandonedForBlocking = $abandoned->getFlatIgnoreForOperation('block'); $packages = []; $securityRemovedVersions = []; $abandonedRemovedVersions = []; foreach ($pool->getPackages() as $package) { if ($abandoned->block && count($this->auditor->filterAbandonedPackages([$package], $ignoreAbandonedForBlocking)) !== 0) { foreach ($package->getNames(false) as $packageName) { $abandonedRemovedVersions[$packageName][$package->getVersion()] = $package->getPrettyVersion(); } continue; } $matchingAdvisories = $this->getMatchingAdvisories($package, $advisoryMap); if (count($matchingAdvisories) > 0) { foreach ($package->getNames(false) as $packageName) { $securityRemovedVersions[$packageName][$package->getVersion()] = $matchingAdvisories; } continue; } $packages[] = $package; } return new Pool($packages, $pool->getUnacceptableFixedOrLockedPackages(), $pool->getAllRemovedVersions(), $pool->getAllRemovedVersionsByPackage(), $securityRemovedVersions, $abandonedRemovedVersions); } private function getMatchingAdvisories(PackageInterface $package, array $advisoryMap): array { if ($package->isDev()) { return []; } $matchingAdvisories = []; foreach ($package->getNames(false) as $packageName) { if (!isset($advisoryMap[$packageName])) { continue; } $packageConstraint = new Constraint(Constraint::STR_OP_EQ, $package->getVersion()); foreach ($advisoryMap[$packageName] as $advisory) { if ($advisory->affectedVersions->matches($packageConstraint)) { $matchingAdvisories[] = $advisory; } } } return $matchingAdvisories; } } io = $io; $this->policy = $policy; $this->pool = $pool; } public function getRuleSetSize(): int { return \count($this->rules); } public function getPool(): Pool { return $this->pool; } private function makeAssertionRuleDecisions(): void { $decisionStart = \count($this->decisions) - 1; $rulesCount = \count($this->rules); for ($ruleIndex = 0; $ruleIndex < $rulesCount; $ruleIndex++) { $rule = $this->rules->ruleById[$ruleIndex]; if (!$rule->isAssertion() || $rule->isDisabled()) { continue; } $literals = $rule->getLiterals(); $literal = $literals[0]; if (!$this->decisions->decided($literal)) { $this->decisions->decide($literal, 1, $rule); continue; } if ($this->decisions->satisfy($literal)) { continue; } if (RuleSet::TYPE_LEARNED === $rule->getType()) { $rule->disable(); continue; } $conflict = $this->decisions->decisionRule($literal); if (RuleSet::TYPE_PACKAGE === $conflict->getType()) { $problem = new Problem(); $problem->addRule($rule); $problem->addRule($conflict); $rule->disable(); $this->problems[] = $problem; continue; } $problem = new Problem(); $problem->addRule($rule); $problem->addRule($conflict); foreach ($this->rules->getIteratorFor(RuleSet::TYPE_REQUEST) as $assertRule) { if ($assertRule->isDisabled() || !$assertRule->isAssertion()) { continue; } $assertRuleLiterals = $assertRule->getLiterals(); $assertRuleLiteral = $assertRuleLiterals[0]; if (abs($literal) !== abs($assertRuleLiteral)) { continue; } $problem->addRule($assertRule); $assertRule->disable(); } $this->problems[] = $problem; $this->decisions->resetToOffset($decisionStart); $ruleIndex = -1; } } protected function setupFixedMap(Request $request): void { $this->fixedMap = []; foreach ($request->getFixedPackages() as $package) { $this->fixedMap[$package->id] = $package; } } protected function checkForRootRequireProblems(Request $request, PlatformRequirementFilterInterface $platformRequirementFilter): void { foreach ($request->getRequires() as $packageName => $constraint) { if ($platformRequirementFilter->isIgnored($packageName)) { continue; } elseif ($platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter) { $constraint = $platformRequirementFilter->filterConstraint($packageName, $constraint); } if (0 === \count($this->pool->whatProvides($packageName, $constraint))) { $problem = new Problem(); $problem->addRule(new GenericRule([], Rule::RULE_ROOT_REQUIRE, ['packageName' => $packageName, 'constraint' => $constraint])); $this->problems[] = $problem; } } } protected function checkForFilterListRemovedLockedPackages(Request $request): void { foreach ($request->getLockedPackages() as $package) { $constraint = new Constraint(Constraint::STR_OP_EQ, $package->getVersion()); if (!$this->pool->isFilterListRemovedPackageVersion($package->getName(), $constraint)) { continue; } $problem = new Problem(); $problem->addRule(new GenericRule([], Rule::RULE_LOCKED_FILTER_LIST_REMOVED, [ 'package' => $package, ])); $this->problems[] = $problem; } } public function solve(Request $request, ?PlatformRequirementFilterInterface $platformRequirementFilter = null): LockTransaction { $platformRequirementFilter = $platformRequirementFilter ?? PlatformRequirementFilterFactory::ignoreNothing(); $this->setupFixedMap($request); $this->io->writeError('Generating rules', true, IOInterface::DEBUG); $ruleSetGenerator = new RuleSetGenerator($this->policy, $this->pool); $this->rules = $ruleSetGenerator->getRulesFor($request, $platformRequirementFilter); unset($ruleSetGenerator); $this->checkForRootRequireProblems($request, $platformRequirementFilter); $this->checkForFilterListRemovedLockedPackages($request); $this->decisions = new Decisions($this->pool); $this->watchGraph = new RuleWatchGraph; foreach ($this->rules as $rule) { $this->watchGraph->insert(new RuleWatchNode($rule)); } $this->makeAssertionRuleDecisions(); $this->io->writeError('Resolving dependencies through SAT', true, IOInterface::DEBUG); $before = microtime(true); $this->runSat(); $this->io->writeError('', true, IOInterface::DEBUG); $this->io->writeError(sprintf('Dependency resolution completed in %.3f seconds', microtime(true) - $before), true, IOInterface::VERBOSE); if (\count($this->problems) > 0) { throw new SolverProblemsException($this->problems, $this->learnedPool); } return new LockTransaction($this->pool, $request->getPresentMap(), $request->getFixedPackagesMap(), $this->decisions); } protected function propagate(int $level): ?Rule { while ($this->decisions->validOffset($this->propagateIndex)) { $decision = $this->decisions->atOffset($this->propagateIndex); $conflict = $this->watchGraph->propagateLiteral( $decision[Decisions::DECISION_LITERAL], $level, $this->decisions ); $this->propagateIndex++; if ($conflict !== null) { return $conflict; } } return null; } private function revert(int $level): void { while (!$this->decisions->isEmpty()) { $literal = $this->decisions->lastLiteral(); if ($this->decisions->undecided($literal)) { break; } $decisionLevel = $this->decisions->decisionLevel($literal); if ($decisionLevel <= $level) { break; } $this->decisions->revertLast(); $this->propagateIndex = \count($this->decisions); } while (\count($this->branches) > 0 && $this->branches[\count($this->branches) - 1][self::BRANCH_LEVEL] >= $level) { array_pop($this->branches); } } private function setPropagateLearn(int $level, int $literal, Rule $rule): int { $level++; $this->decisions->decide($literal, $level, $rule); while (true) { $rule = $this->propagate($level); if (null === $rule) { break; } if ($level === 1) { $this->analyzeUnsolvable($rule); return 0; } [$learnLiteral, $newLevel, $newRule, $why] = $this->analyze($level, $rule); if ($newLevel <= 0 || $newLevel >= $level) { throw new SolverBugException( "Trying to revert to invalid level ".$newLevel." from level ".$level."." ); } $level = $newLevel; $this->revert($level); $this->rules->add($newRule, RuleSet::TYPE_LEARNED); $this->learnedWhy[spl_object_hash($newRule)] = $why; $ruleNode = new RuleWatchNode($newRule); $ruleNode->watch2OnHighest($this->decisions); $this->watchGraph->insert($ruleNode); $this->decisions->decide($learnLiteral, $level, $newRule); } return $level; } private function selectAndInstall(int $level, array $decisionQueue, Rule $rule): int { $literals = $this->policy->selectPreferredPackages($this->pool, $decisionQueue, $rule->getRequiredPackage()); $selectedLiteral = array_shift($literals); if (\count($literals) > 0) { $this->branches[] = [$literals, $level]; } return $this->setPropagateLearn($level, $selectedLiteral, $rule); } protected function analyze(int $level, Rule $rule): array { $analyzedRule = $rule; $ruleLevel = 1; $num = 0; $l1num = 0; $seen = []; $learnedLiteral = null; $otherLearnedLiterals = []; $decisionId = \count($this->decisions); $this->learnedPool[] = []; while (true) { $this->learnedPool[\count($this->learnedPool) - 1][] = $rule; foreach ($rule->getLiterals() as $literal) { if ($rule instanceof MultiConflictRule && !$this->decisions->decided($literal)) { continue; } if ($this->decisions->satisfy($literal)) { continue; } if (isset($seen[abs($literal)])) { continue; } $seen[abs($literal)] = true; $l = $this->decisions->decisionLevel($literal); if (1 === $l) { $l1num++; } elseif ($level === $l) { $num++; } else { $otherLearnedLiterals[] = $literal; if ($l > $ruleLevel) { $ruleLevel = $l; } } } unset($literal); $l1retry = true; while ($l1retry) { $l1retry = false; if (0 === $num && 0 === --$l1num) { break 2; } while (true) { if ($decisionId <= 0) { throw new SolverBugException( "Reached invalid decision id $decisionId while looking through $rule for a literal present in the analyzed rule $analyzedRule." ); } $decisionId--; $decision = $this->decisions->atOffset($decisionId); $literal = $decision[Decisions::DECISION_LITERAL]; if (isset($seen[abs($literal)])) { break; } } unset($seen[abs($literal)]); if (0 !== $num && 0 === --$num) { if ($literal < 0) { $this->testFlagLearnedPositiveLiteral = true; } $learnedLiteral = -$literal; if (0 === $l1num) { break 2; } foreach ($otherLearnedLiterals as $otherLiteral) { unset($seen[abs($otherLiteral)]); } $l1num++; $l1retry = true; } else { $decision = $this->decisions->atOffset($decisionId); $rule = $decision[Decisions::DECISION_REASON]; if ($rule instanceof MultiConflictRule) { foreach ($rule->getLiterals() as $ruleLiteral) { if (!isset($seen[abs($ruleLiteral)]) && $this->decisions->satisfy(-$ruleLiteral)) { $this->learnedPool[\count($this->learnedPool) - 1][] = $rule; $l = $this->decisions->decisionLevel($ruleLiteral); if (1 === $l) { $l1num++; } elseif ($level === $l) { $num++; } else { $otherLearnedLiterals[] = $ruleLiteral; if ($l > $ruleLevel) { $ruleLevel = $l; } } $seen[abs($ruleLiteral)] = true; break; } } $l1retry = true; } } } $decision = $this->decisions->atOffset($decisionId); $rule = $decision[Decisions::DECISION_REASON]; } $why = \count($this->learnedPool) - 1; if (null === $learnedLiteral) { throw new SolverBugException( "Did not find a learnable literal in analyzed rule $analyzedRule." ); } array_unshift($otherLearnedLiterals, $learnedLiteral); $newRule = new GenericRule($otherLearnedLiterals, Rule::RULE_LEARNED, $why); return [$learnedLiteral, $ruleLevel, $newRule, $why]; } private function analyzeUnsolvableRule(Problem $problem, Rule $conflictRule, array &$ruleSeen): void { $why = spl_object_hash($conflictRule); $ruleSeen[$why] = true; if ($conflictRule->getType() === RuleSet::TYPE_LEARNED) { $learnedWhy = $this->learnedWhy[$why]; $problemRules = $this->learnedPool[$learnedWhy]; foreach ($problemRules as $problemRule) { if (!isset($ruleSeen[spl_object_hash($problemRule)])) { $this->analyzeUnsolvableRule($problem, $problemRule, $ruleSeen); } } return; } if ($conflictRule->getType() === RuleSet::TYPE_PACKAGE) { return; } $problem->nextSection(); $problem->addRule($conflictRule); } private function analyzeUnsolvable(Rule $conflictRule): void { $problem = new Problem(); $problem->addRule($conflictRule); $ruleSeen = []; $this->analyzeUnsolvableRule($problem, $conflictRule, $ruleSeen); $this->problems[] = $problem; $seen = []; $literals = $conflictRule->getLiterals(); foreach ($literals as $literal) { if ($this->decisions->satisfy($literal)) { continue; } $seen[abs($literal)] = true; } foreach ($this->decisions as $decision) { $decisionLiteral = $decision[Decisions::DECISION_LITERAL]; if (!isset($seen[abs($decisionLiteral)])) { continue; } $why = $decision[Decisions::DECISION_REASON]; $problem->addRule($why); $this->analyzeUnsolvableRule($problem, $why, $ruleSeen); $literals = $why->getLiterals(); foreach ($literals as $literal) { if ($this->decisions->satisfy($literal)) { continue; } $seen[abs($literal)] = true; } } } private function runSat(): void { $this->propagateIndex = 0; $level = 1; $systemLevel = $level + 1; while (true) { if (1 === $level) { $conflictRule = $this->propagate($level); if (null !== $conflictRule) { $this->analyzeUnsolvable($conflictRule); return; } } if ($level < $systemLevel) { $iterator = $this->rules->getIteratorFor(RuleSet::TYPE_REQUEST); foreach ($iterator as $rule) { if ($rule->isEnabled()) { $decisionQueue = []; $noneSatisfied = true; foreach ($rule->getLiterals() as $literal) { if ($this->decisions->satisfy($literal)) { $noneSatisfied = false; break; } if ($literal > 0 && $this->decisions->undecided($literal)) { $decisionQueue[] = $literal; } } if ($noneSatisfied && \count($decisionQueue) > 0) { $prunedQueue = []; foreach ($decisionQueue as $literal) { if (isset($this->fixedMap[abs($literal)])) { $prunedQueue[] = $literal; } } if (\count($prunedQueue) > 0) { $decisionQueue = $prunedQueue; } } if ($noneSatisfied && \count($decisionQueue) > 0) { $oLevel = $level; $level = $this->selectAndInstall($level, $decisionQueue, $rule); if (0 === $level) { return; } if ($level <= $oLevel) { break; } } } } $systemLevel = $level + 1; $iterator->next(); if ($iterator->valid()) { continue; } } if ($level < $systemLevel) { $systemLevel = $level; } $rulesCount = \count($this->rules); $pass = 1; $this->io->writeError('Looking at all rules.', true, IOInterface::DEBUG); for ($i = 0, $n = 0; $n < $rulesCount; $i++, $n++) { if ($i === $rulesCount) { if (1 === $pass) { $this->io->writeError("Something's changed, looking at all rules again (pass #$pass)", false, IOInterface::DEBUG); } else { $this->io->overwriteError("Something's changed, looking at all rules again (pass #$pass)", false, null, IOInterface::DEBUG); } $i = 0; $pass++; } $rule = $this->rules->ruleById[$i]; $literals = $rule->getLiterals(); if ($rule->isDisabled()) { continue; } $decisionQueue = []; foreach ($literals as $literal) { if ($literal <= 0) { if (!$this->decisions->decidedInstall($literal)) { continue 2; } } else { if ($this->decisions->decidedInstall($literal)) { continue 2; } if ($this->decisions->undecided($literal)) { $decisionQueue[] = $literal; } } } if (\count($decisionQueue) < 2) { continue; } $level = $this->selectAndInstall($level, $decisionQueue, $rule); if (0 === $level) { return; } $rulesCount = \count($this->rules); $n = -1; } if ($level < $systemLevel) { continue; } if (\count($this->branches) > 0) { $lastLiteral = null; $lastLevel = null; $lastBranchIndex = 0; $lastBranchOffset = 0; for ($i = \count($this->branches) - 1; $i >= 0; $i--) { [$literals, $l] = $this->branches[$i]; foreach ($literals as $offset => $literal) { if ($literal > 0 && $this->decisions->decisionLevel($literal) > $l + 1) { $lastLiteral = $literal; $lastBranchIndex = $i; $lastBranchOffset = $offset; $lastLevel = $l; } } } if ($lastLiteral !== null) { assert($lastLevel !== null); unset($this->branches[$lastBranchIndex][self::BRANCH_LITERALS][$lastBranchOffset]); $level = $lastLevel; $this->revert($level); $why = $this->decisions->lastReason(); $level = $this->setPropagateLearn($level, $lastLiteral, $why); if ($level === 0) { return; } continue; } } break; } } } problems = $problems; $this->learnedPool = $learnedPool; parent::__construct('Failed resolving dependencies with '.\count($problems).' problems, call getPrettyString to get formatted details', self::ERROR_DEPENDENCY_RESOLUTION_FAILED); } public function getPrettyString(RepositorySet $repositorySet, Request $request, Pool $pool, bool $isVerbose, bool $isDevExtraction = false): string { $installedMap = $request->getPresentMap(true); $missingExtensions = []; $isCausedByLock = false; $problems = []; foreach ($this->problems as $problem) { $problems[] = $problem->getPrettyString($repositorySet, $request, $pool, $isVerbose, $installedMap, $this->learnedPool)."\n"; $missingExtensions = array_merge($missingExtensions, $this->getExtensionProblems($problem->getReasons())); $isCausedByLock = $isCausedByLock || $problem->isCausedByLock($repositorySet, $request, $pool); } $i = 1; $text = "\n"; foreach (array_unique($problems) as $problem) { $text .= " Problem ".($i++).$problem; } $hints = []; if (!$isDevExtraction && (str_contains($text, 'could not be found') || str_contains($text, 'no matching package found'))) { $hints[] = "Potential causes:\n - A typo in the package name\n - The package is not available in a stable-enough version according to your minimum-stability setting\n see for more details.\n - It's a private package and you forgot to add a custom repository to find it\n\nRead for further common problems."; } if (\count($missingExtensions) > 0) { $hints[] = $this->createExtensionHint($missingExtensions); } if ($isCausedByLock && !$isDevExtraction && !$request->getUpdateAllowTransitiveRootDependencies()) { $hints[] = "Use the option --with-all-dependencies (-W) to allow upgrades, downgrades and removals for packages currently locked to specific versions."; } if (str_contains($text, 'found composer-plugin-api[2.0.0] but it does not match') && str_contains($text, '- ocramius/package-versions')) { $hints[] = "ocramius/package-versions only provides support for Composer 2 in 1.8+, which requires PHP 7.4.\nIf you can not upgrade PHP you can require composer/package-versions-deprecated to resolve this with PHP 7.0+."; } if (!class_exists('PHPUnit\Framework\TestCase', false)) { if (str_contains($text, 'found composer-plugin-api[2.0.0] but it does not match')) { $hints[] = "You are using Composer 2, which some of your plugins seem to be incompatible with. Make sure you update your plugins or report a plugin-issue to ask them to support Composer 2."; } } if (\count($hints) > 0) { $text .= "\n" . implode("\n\n", $hints); } return $text; } public function getProblems(): array { return $this->problems; } private function createExtensionHint(array $missingExtensions): string { $paths = IniHelper::getAll(); if ('' === $paths[0]) { if (count($paths) === 1) { return ''; } array_shift($paths); } $ignoreExtensionsArguments = implode(" ", array_map(static function ($extension) { return "--ignore-platform-req=$extension"; }, array_unique($missingExtensions))); $text = "To enable extensions, verify that they are enabled in your .ini files:\n - "; $text .= implode("\n - ", $paths); $text .= "\nYou can also run `php --ini` in a terminal to see which files are used by PHP in CLI mode."; $text .= "\nAlternatively, you can run Composer with `$ignoreExtensionsArguments` to temporarily ignore these required extensions."; return $text; } private function getExtensionProblems(array $reasonSets): array { $missingExtensions = []; foreach ($reasonSets as $reasonSet) { foreach ($reasonSet as $rule) { $required = $rule->getRequiredPackage(); if (null !== $required && 0 === strpos($required, 'ext-')) { $missingExtensions[$required] = 1; } } } return array_keys($missingExtensions); } } presentPackages = $presentPackages; $this->setResultPackageMaps($resultPackages); $this->operations = $this->calculateOperations(); } public function getOperations(): array { return $this->operations; } private function setResultPackageMaps(array $resultPackages): void { $packageSort = static function (PackageInterface $a, PackageInterface $b): int { if ($a->getName() === $b->getName()) { if ($a instanceof AliasPackage !== $b instanceof AliasPackage) { return $a instanceof AliasPackage ? -1 : 1; } return strcmp($b->getVersion(), $a->getVersion()); } return strcmp($b->getName(), $a->getName()); }; $this->resultPackageMap = []; foreach ($resultPackages as $package) { $this->resultPackageMap[spl_object_hash($package)] = $package; foreach ($package->getNames() as $name) { $this->resultPackagesByName[$name][] = $package; } } uasort($this->resultPackageMap, $packageSort); foreach ($this->resultPackagesByName as $name => $packages) { uasort($this->resultPackagesByName[$name], $packageSort); } } protected function calculateOperations(): array { $operations = []; $presentPackageMap = []; $removeMap = []; $presentAliasMap = []; $removeAliasMap = []; foreach ($this->presentPackages as $package) { if ($package instanceof AliasPackage) { $presentAliasMap[$package->getName().'::'.$package->getVersion()] = $package; $removeAliasMap[$package->getName().'::'.$package->getVersion()] = $package; } else { $presentPackageMap[$package->getName()] = $package; $removeMap[$package->getName()] = $package; } } $stack = $this->getRootPackages(); $visited = []; $processed = []; while (\count($stack) > 0) { $package = array_pop($stack); if (isset($processed[spl_object_hash($package)])) { continue; } if (!isset($visited[spl_object_hash($package)])) { $visited[spl_object_hash($package)] = true; $stack[] = $package; if ($package instanceof AliasPackage) { $stack[] = $package->getAliasOf(); } else { foreach ($package->getRequires() as $link) { $possibleRequires = $this->getProvidersInResult($link); foreach ($possibleRequires as $require) { $stack[] = $require; } } } } elseif (!isset($processed[spl_object_hash($package)])) { $processed[spl_object_hash($package)] = true; if ($package instanceof AliasPackage) { $aliasKey = $package->getName().'::'.$package->getVersion(); if (isset($presentAliasMap[$aliasKey])) { unset($removeAliasMap[$aliasKey]); } else { $operations[] = new Operation\MarkAliasInstalledOperation($package); } } else { if (isset($presentPackageMap[$package->getName()])) { $source = $presentPackageMap[$package->getName()]; if ($package->getVersion() !== $presentPackageMap[$package->getName()]->getVersion() || $package->getDistReference() !== $presentPackageMap[$package->getName()]->getDistReference() || $package->getSourceReference() !== $presentPackageMap[$package->getName()]->getSourceReference() || ( $package instanceof CompletePackageInterface && $presentPackageMap[$package->getName()] instanceof CompletePackageInterface && ( $package->isAbandoned() !== $presentPackageMap[$package->getName()]->isAbandoned() || $package->getReplacementPackage() !== $presentPackageMap[$package->getName()]->getReplacementPackage() ) ) ) { $operations[] = new Operation\UpdateOperation($source, $package); } unset($removeMap[$package->getName()]); } else { $operations[] = new Operation\InstallOperation($package); unset($removeMap[$package->getName()]); } } } } foreach ($removeMap as $name => $package) { array_unshift($operations, new Operation\UninstallOperation($package)); } foreach ($removeAliasMap as $nameVersion => $package) { $operations[] = new Operation\MarkAliasUninstalledOperation($package); } $operations = $this->movePluginsToFront($operations); $operations = $this->moveUninstallsToFront($operations); return $this->operations = $operations; } protected function getRootPackages(): array { $roots = $this->resultPackageMap; foreach ($this->resultPackageMap as $packageHash => $package) { if (!isset($roots[$packageHash])) { continue; } foreach ($package->getRequires() as $link) { $possibleRequires = $this->getProvidersInResult($link); foreach ($possibleRequires as $require) { if ($require !== $package) { unset($roots[spl_object_hash($require)]); } } } } return $roots; } protected function getProvidersInResult(Link $link): array { if (!isset($this->resultPackagesByName[$link->getTarget()])) { return []; } return $this->resultPackagesByName[$link->getTarget()]; } private function movePluginsToFront(array $operations): array { $dlModifyingPluginsNoDeps = []; $dlModifyingPluginsWithDeps = []; $dlModifyingPluginRequires = []; $pluginsNoDeps = []; $pluginsWithDeps = []; $pluginRequires = []; foreach (array_reverse($operations, true) as $idx => $op) { if ($op instanceof Operation\InstallOperation) { $package = $op->getPackage(); } elseif ($op instanceof Operation\UpdateOperation) { $package = $op->getTargetPackage(); } else { continue; } $extra = $package->getExtra(); $isDownloadsModifyingPlugin = $package->getType() === 'composer-plugin' && isset($extra['plugin-modifies-downloads']) && $extra['plugin-modifies-downloads'] === true; if ($isDownloadsModifyingPlugin || \count(array_intersect($package->getNames(), $dlModifyingPluginRequires)) > 0) { $requires = array_filter(array_keys($package->getRequires()), static function ($req): bool { return !PlatformRepository::isPlatformPackage($req); }); if ($isDownloadsModifyingPlugin && 0 === \count($requires)) { array_unshift($dlModifyingPluginsNoDeps, $op); } else { $dlModifyingPluginRequires = array_merge($dlModifyingPluginRequires, $requires); array_unshift($dlModifyingPluginsWithDeps, $op); } unset($operations[$idx]); continue; } $isPlugin = $package->getType() === 'composer-plugin' || $package->getType() === 'composer-installer'; if ($isPlugin || \count(array_intersect($package->getNames(), $pluginRequires)) > 0) { $requires = array_filter(array_keys($package->getRequires()), static function ($req): bool { return !PlatformRepository::isPlatformPackage($req); }); if ($isPlugin && 0 === \count($requires)) { array_unshift($pluginsNoDeps, $op); } else { $pluginRequires = array_merge($pluginRequires, $requires); array_unshift($pluginsWithDeps, $op); } unset($operations[$idx]); } } return array_merge($dlModifyingPluginsNoDeps, $dlModifyingPluginsWithDeps, $pluginsNoDeps, $pluginsWithDeps, $operations); } private function moveUninstallsToFront(array $operations): array { $uninstOps = []; foreach ($operations as $idx => $op) { if ($op instanceof Operation\UninstallOperation || $op instanceof Operation\MarkAliasUninstalledOperation) { $uninstOps[] = $op; unset($operations[$idx]); } } return array_merge($uninstOps, $operations); } } cleanupExecuted[$package->getName()]); return parent::prepare($type, $package, $path, $prevPackage); } public function cleanup(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface { $this->cleanupExecuted[$package->getName()] = true; return parent::cleanup($type, $package, $path, $prevPackage); } public function install(PackageInterface $package, string $path, bool $output = true): PromiseInterface { if ($output) { $this->io->writeError(" - " . InstallOperation::format($package) . $this->getInstallOperationAppendix($package, $path)); } $vendorDir = $this->config->get('vendor-dir'); if (false === strpos($this->filesystem->normalizePath($vendorDir), $this->filesystem->normalizePath($path.DIRECTORY_SEPARATOR))) { $this->filesystem->emptyDirectory($path); } do { $temporaryDir = $vendorDir.'/composer/'.bin2hex(random_bytes(4)); } while (is_dir($temporaryDir)); $this->addCleanupPath($package, $temporaryDir); if (!is_dir($path) || realpath($path) !== Platform::getCwd()) { $this->addCleanupPath($package, $path); } $this->filesystem->ensureDirectoryExists($temporaryDir); $fileName = $this->getFileName($package, $path); $filesystem = $this->filesystem; $cleanup = function () use ($path, $filesystem, $temporaryDir, $package) { $this->clearLastCacheWrite($package); $filesystem->removeDirectory($temporaryDir); if (is_dir($path) && realpath($path) !== Platform::getCwd()) { $filesystem->removeDirectory($path); } $this->removeCleanupPath($package, $temporaryDir); $realpath = realpath($path); if ($realpath !== false) { $this->removeCleanupPath($package, $realpath); } }; try { $promise = $this->extract($package, $fileName, $temporaryDir); } catch (\Exception $e) { $cleanup(); throw $e; } return $promise->then(function () use ($package, $filesystem, $fileName, $temporaryDir, $path): PromiseInterface { if (file_exists($fileName)) { $filesystem->unlink($fileName); } $getFolderContent = static function ($dir): array { $finder = Finder::create() ->ignoreVCS(false) ->ignoreDotFiles(false) ->notName('.DS_Store') ->depth(0) ->in($dir); return iterator_to_array($finder); }; $renameRecursively = null; $renameRecursively = static function ($from, $to) use ($filesystem, $getFolderContent, $package, &$renameRecursively) { $contentDir = $getFolderContent($from); foreach ($contentDir as $file) { $file = (string) $file; if (is_dir($to . '/' . basename($file))) { if (!is_dir($file)) { throw new \RuntimeException('Installing '.$package.' would lead to overwriting the '.$to.'/'.basename($file).' directory with a file from the package, invalid operation.'); } $renameRecursively($file, $to . '/' . basename($file)); } else { $filesystem->rename($file, $to . '/' . basename($file)); } } }; $renameAsOne = false; if (!file_exists($path)) { $renameAsOne = true; } elseif ($filesystem->isDirEmpty($path)) { try { if ($filesystem->removeDirectoryPhp($path)) { $renameAsOne = true; } } catch (\RuntimeException $e) { } } $contentDir = $getFolderContent($temporaryDir); $singleDirAtTopLevel = 1 === count($contentDir) && is_dir((string) reset($contentDir)); if ($renameAsOne) { if ($singleDirAtTopLevel) { $extractedDir = (string) reset($contentDir); } else { $extractedDir = $temporaryDir; } $filesystem->rename($extractedDir, $path); } else { $from = $temporaryDir; if ($singleDirAtTopLevel) { $from = (string) reset($contentDir); } $renameRecursively($from, $path); } $promise = $filesystem->removeDirectoryAsync($temporaryDir); return $promise->then(function () use ($package, $path, $temporaryDir) { $this->removeCleanupPath($package, $temporaryDir); $this->removeCleanupPath($package, $path); }); }, static function ($e) use ($cleanup) { $cleanup(); throw $e; }); } protected function getInstallOperationAppendix(PackageInterface $package, string $path): string { return ': Extracting archive'; } abstract protected function extract(PackageInterface $package, string $file, string $path): PromiseInterface; } io = $io; $this->preferSource = $preferSource; $this->filesystem = $filesystem ?: new Filesystem(); } public function setPreferSource(bool $preferSource): self { $this->preferSource = $preferSource; return $this; } public function setPreferDist(bool $preferDist): self { $this->preferDist = $preferDist; return $this; } public function setPreferences(array $preferences): self { $this->packagePreferences = $preferences; return $this; } public function setSourceFallback(bool $sourceFallback): self { if ($sourceFallback && !$this->sourceFallbackDeprecationWarned) { $this->io->writeError('The source-fallback option is deprecated and will be removed in Composer 2.11 as automatic fallback between dist and source has security implications.'); $this->io->writeError('If you have a legitimate use case and do not want us to remove it in 2.11, please open an issue at https://github.com/composer/composer/issues to let us know.'); $this->sourceFallbackDeprecationWarned = true; } $this->sourceFallback = $sourceFallback; return $this; } public function setDownloader(string $type, DownloaderInterface $downloader): self { $type = strtolower($type); $this->downloaders[$type] = $downloader; return $this; } public function getDownloader(string $type): DownloaderInterface { $type = strtolower($type); if (!isset($this->downloaders[$type])) { throw new \InvalidArgumentException(sprintf('Unknown downloader type: %s. Available types: %s.', $type, implode(', ', array_keys($this->downloaders)))); } return $this->downloaders[$type]; } public function getDownloaderForPackage(PackageInterface $package): ?DownloaderInterface { $installationSource = $package->getInstallationSource(); if ('metapackage' === $package->getType()) { return null; } if ('dist' === $installationSource) { $downloader = $this->getDownloader($package->getDistType()); } elseif ('source' === $installationSource) { $downloader = $this->getDownloader($package->getSourceType()); } else { throw new \InvalidArgumentException( 'Package '.$package.' does not have an installation source set' ); } if ($installationSource !== $downloader->getInstallationSource()) { throw new \LogicException(sprintf( 'Downloader "%s" is a %s type downloader and can not be used to download %s for package %s', get_class($downloader), $downloader->getInstallationSource(), $installationSource, $package )); } return $downloader; } public function getDownloaderType(DownloaderInterface $downloader): string { return array_search($downloader, $this->downloaders); } public function download(PackageInterface $package, string $targetDir, ?PackageInterface $prevPackage = null): PromiseInterface { $targetDir = $this->normalizeTargetDir($targetDir); $this->filesystem->ensureDirectoryExists(dirname($targetDir)); $sources = $this->getAvailableSources($package, $prevPackage); $io = $this->io; $download = function ($retry = false) use (&$sources, $io, $package, $targetDir, &$download, $prevPackage) { $source = array_shift($sources); if ($retry) { $io->writeError(' Now trying to download from ' . $source . ''); } $package->setInstallationSource($source); $downloader = $this->getDownloaderForPackage($package); if (!$downloader) { return \React\Promise\resolve(null); } $handleError = function ($e) use ($sources, $source, $package, $io, $download) { if ($e instanceof \RuntimeException && !$e instanceof IrrecoverableDownloadException) { if (count($sources) === 0 || !$this->sourceFallback) { if (!$this->sourceFallback && count($sources) > 0) { $io->writeError( ' Failed to download '. $package->getPrettyName(). ' from ' . $source . ': '. $e->getMessage().'' ); $io->writeError(' Source fallback is disabled. Not trying alternative sources.'); } throw $e; } $io->writeError( ' Failed to download '. $package->getPrettyName(). ' from ' . $source . ': '. $e->getMessage().'' ); return $download(true); } throw $e; }; try { $result = $downloader->download($package, $targetDir, $prevPackage); } catch (\Exception $e) { return $handleError($e); } $res = $result->then(static function ($res) { return $res; }, $handleError); return $res; }; return $download(); } public function prepare(string $type, PackageInterface $package, string $targetDir, ?PackageInterface $prevPackage = null): PromiseInterface { $targetDir = $this->normalizeTargetDir($targetDir); $downloader = $this->getDownloaderForPackage($package); if ($downloader) { return $downloader->prepare($type, $package, $targetDir, $prevPackage); } return \React\Promise\resolve(null); } public function install(PackageInterface $package, string $targetDir): PromiseInterface { $targetDir = $this->normalizeTargetDir($targetDir); $downloader = $this->getDownloaderForPackage($package); if ($downloader) { return $downloader->install($package, $targetDir); } return \React\Promise\resolve(null); } public function update(PackageInterface $initial, PackageInterface $target, string $targetDir): PromiseInterface { $targetDir = $this->normalizeTargetDir($targetDir); $downloader = $this->getDownloaderForPackage($target); $initialDownloader = $this->getDownloaderForPackage($initial); if (!$initialDownloader && !$downloader) { return \React\Promise\resolve(null); } if (!$downloader) { return $initialDownloader->remove($initial, $targetDir); } $initialType = $this->getDownloaderType($initialDownloader); $targetType = $this->getDownloaderType($downloader); if ($initialType === $targetType) { try { return $downloader->update($initial, $target, $targetDir); } catch (\RuntimeException $e) { if (!$this->io->isInteractive()) { throw $e; } $this->io->writeError(' Update failed ('.$e->getMessage().')'); if (!$this->io->askConfirmation(' Would you like to try reinstalling the package instead [yes]? ')) { throw $e; } } } $promise = $initialDownloader->remove($initial, $targetDir); return $promise->then(function ($res) use ($target, $targetDir): PromiseInterface { return $this->install($target, $targetDir); }); } public function remove(PackageInterface $package, string $targetDir): PromiseInterface { $targetDir = $this->normalizeTargetDir($targetDir); $downloader = $this->getDownloaderForPackage($package); if ($downloader) { return $downloader->remove($package, $targetDir); } return \React\Promise\resolve(null); } public function cleanup(string $type, PackageInterface $package, string $targetDir, ?PackageInterface $prevPackage = null): PromiseInterface { $targetDir = $this->normalizeTargetDir($targetDir); $downloader = $this->getDownloaderForPackage($package); if ($downloader) { return $downloader->cleanup($type, $package, $targetDir, $prevPackage); } return \React\Promise\resolve(null); } protected function resolvePackageInstallPreference(PackageInterface $package): string { foreach ($this->packagePreferences as $pattern => $preference) { $pattern = '{^'.str_replace('\\*', '.*', preg_quote($pattern)).'$}i'; if (Preg::isMatch($pattern, $package->getName())) { if ('dist' === $preference || (!$package->isDev() && 'auto' === $preference)) { return 'dist'; } return 'source'; } } return $package->isDev() ? 'source' : 'dist'; } private function getAvailableSources(PackageInterface $package, ?PackageInterface $prevPackage = null): array { $sourceType = $package->getSourceType(); $distType = $package->getDistType(); $sources = []; if ($sourceType) { $sources[] = 'source'; } if ($distType) { $sources[] = 'dist'; } if (empty($sources)) { throw new \InvalidArgumentException('Package '.$package.' must have a source or dist specified'); } if ( $prevPackage && in_array($prevPackage->getInstallationSource(), $sources, true) && !(!$prevPackage->isDev() && $prevPackage->getInstallationSource() === 'dist' && $package->isDev()) ) { $prevSource = $prevPackage->getInstallationSource(); usort($sources, static function ($a, $b) use ($prevSource): int { return $a === $prevSource ? -1 : 1; }); return $sources; } if (!$this->preferSource && ($this->preferDist || 'dist' === $this->resolvePackageInstallPreference($package))) { $sources = array_reverse($sources); } return $sources; } private function normalizeTargetDir(string $dir): string { if ($dir === '\\' || $dir === '/') { return $dir; } return rtrim($dir, '\\/'); } } io = $io; $this->config = $config; $this->eventDispatcher = $eventDispatcher; $this->httpDownloader = $httpDownloader; $this->cache = $cache; $this->process = $process ?? new ProcessExecutor($io); $this->filesystem = $filesystem ?? new Filesystem($this->process); if ($this->cache !== null && $this->cache->gcIsNecessary()) { $this->io->writeError('Running cache garbage collection', true, IOInterface::VERY_VERBOSE); $this->cache->gc($config->get('cache-files-ttl'), $config->get('cache-files-maxsize')); } } public function getInstallationSource(): string { return 'dist'; } public function download(PackageInterface $package, string $path, ?PackageInterface $prevPackage = null, bool $output = true): PromiseInterface { if (null === $package->getDistUrl()) { throw new \InvalidArgumentException('The given package is missing url information'); } $cacheKeyGenerator = static function (PackageInterface $package, $key): string { $cacheKey = hash('sha1', $key); return $package->getName().'/'.$cacheKey.'.'.$package->getDistType(); }; $retries = 3; $distUrls = $package->getDistUrls(); $urls = []; foreach ($distUrls as $index => $url) { $processedUrl = $this->processUrl($package, $url); $urls[$index] = [ 'base' => $url, 'processed' => $processedUrl, 'cacheKey' => $cacheKeyGenerator($package, $processedUrl), ]; } assert(count($urls) > 0); $fileName = $this->getFileName($package, $path); $this->filesystem->ensureDirectoryExists($path); $this->filesystem->ensureDirectoryExists(dirname($fileName)); $accept = null; $reject = null; $download = function () use ($output, $cacheKeyGenerator, $package, $fileName, &$urls, &$accept, &$reject) { $url = reset($urls); $index = key($urls); if ($this->eventDispatcher !== null) { $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->httpDownloader, $url['processed'], 'package', $package); $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); if ($preFileDownloadEvent->getCustomCacheKey() !== null) { $url['cacheKey'] = $cacheKeyGenerator($package, $preFileDownloadEvent->getCustomCacheKey()); } elseif ($preFileDownloadEvent->getProcessedUrl() !== $url['processed']) { $url['cacheKey'] = $cacheKeyGenerator($package, $preFileDownloadEvent->getProcessedUrl()); } $url['processed'] = $preFileDownloadEvent->getProcessedUrl(); } $urls[$index] = $url; $checksum = $package->getDistSha1Checksum(); $cacheKey = $url['cacheKey']; if ($this->cache !== null && ($checksum === null || $checksum === '' || $checksum === $this->cache->sha1($cacheKey)) && $this->cache->copyTo($cacheKey, $fileName)) { if ($output) { $this->io->writeError(" - Loading " . $package->getName() . " (" . $package->getFullPrettyVersion() . ") from cache", true, IOInterface::VERY_VERBOSE); } if (!$this->cache->isReadOnly()) { $this->lastCacheWrites[$package->getName()] = $cacheKey; } $result = \React\Promise\resolve($fileName); } else { if ($output) { $this->io->writeError(" - Downloading " . $package->getName() . " (" . $package->getFullPrettyVersion() . ")"); } $result = $this->httpDownloader->addCopy($url['processed'], $fileName, $package->getTransportOptions()) ->then($accept, $reject); } return $result->then(function ($result) use ($fileName, $checksum, $url, $package): string { if (null === $result) { return $fileName; } if (!file_exists($fileName)) { throw new \UnexpectedValueException(UrlUtil::sanitize($url['base']).' could not be saved to '.$fileName.', make sure the' .' directory is writable and you have internet connectivity'); } if ($checksum !== null && $checksum !== '' && hash_file('sha1', $fileName) !== $checksum) { throw new \UnexpectedValueException('The checksum verification of the file failed (downloaded from '.UrlUtil::sanitize($url['base']).')'); } if ($this->eventDispatcher !== null) { $postFileDownloadEvent = new PostFileDownloadEvent(PluginEvents::POST_FILE_DOWNLOAD, $fileName, $checksum, $url['processed'], 'package', $package); $this->eventDispatcher->dispatch($postFileDownloadEvent->getName(), $postFileDownloadEvent); } return $fileName; }); }; $accept = function (Response $response) use ($package, $fileName, &$urls): string { $url = reset($urls); $cacheKey = $url['cacheKey']; $fileSize = @filesize($fileName); if (false === $fileSize) { $fileSize = $response->getHeader('Content-Length') ?? '?'; } FileDownloader::$downloadMetadata[$package->getName()] = $fileSize; if (Platform::getEnv('GITHUB_ACTIONS') !== false && Platform::getEnv('COMPOSER_TESTS_ARE_RUNNING') === false) { FileDownloader::$responseHeaders[$package->getName()] = $response->getHeaders(); } if ($this->cache !== null && !$this->cache->isReadOnly()) { $this->lastCacheWrites[$package->getName()] = $cacheKey; $this->cache->copyFrom($cacheKey, $fileName); } $response->collect(); return $fileName; }; $reject = function ($e) use (&$urls, $download, $fileName, $package, &$retries) { if (file_exists($fileName)) { $this->filesystem->unlink($fileName); } $this->clearLastCacheWrite($package); if ($e instanceof IrrecoverableDownloadException) { throw $e; } if ($e instanceof MaxFileSizeExceededException) { throw $e; } if ($e instanceof TransportException) { if (0 !== $e->getCode() && !in_array($e->getCode(), [500, 502, 503, 504], true)) { $retries = 0; } } if ($e instanceof TransportException && $e->getStatusCode() === 499) { $retries = 0; $urls = []; } if ($retries > 0) { usleep(500000); $retries--; return $download(); } array_shift($urls); if (\count($urls) > 0) { if ($this->io->isDebug()) { $this->io->writeError(' Failed downloading '.$package->getName().': ['.get_class($e).'] '.$e->getCode().': '.$e->getMessage()); $this->io->writeError(' Trying the next URL for '.$package->getName()); } else { $this->io->writeError(' Failed downloading '.$package->getName().', trying the next URL ('.$e->getCode().': '.$e->getMessage().')'); } $retries = 3; usleep(100000); return $download(); } throw $e; }; return $download(); } public function prepare(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface { return \React\Promise\resolve(null); } public function cleanup(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface { $fileName = $this->getFileName($package, $path); if (file_exists($fileName)) { $this->filesystem->unlink($fileName); } $dirsToCleanUp = [ $path, $this->config->get('vendor-dir').'/'.explode('/', $package->getPrettyName())[0], $this->config->get('vendor-dir').'/composer/', $this->config->get('vendor-dir'), ]; if (isset($this->additionalCleanupPaths[$package->getName()])) { foreach ($this->additionalCleanupPaths[$package->getName()] as $pathToClean) { $this->filesystem->remove($pathToClean); } } foreach ($dirsToCleanUp as $dir) { if (is_dir($dir) && $this->filesystem->isDirEmpty($dir) && realpath($dir) !== Platform::getCwd()) { $this->filesystem->removeDirectoryPhp($dir); } } return \React\Promise\resolve(null); } public function install(PackageInterface $package, string $path, bool $output = true): PromiseInterface { if ($output) { $this->io->writeError(" - " . InstallOperation::format($package)); } $vendorDir = $this->config->get('vendor-dir'); if (false === strpos($this->filesystem->normalizePath($vendorDir), $this->filesystem->normalizePath($path.DIRECTORY_SEPARATOR))) { $this->filesystem->emptyDirectory($path); } $this->filesystem->ensureDirectoryExists($path); $this->filesystem->rename($this->getFileName($package, $path), $path . '/' . $this->getDistPath($package, PATHINFO_BASENAME)); foreach ($package->getBinaries() as $bin) { if (file_exists($path . '/' . $bin) && !is_executable($path . '/' . $bin)) { Silencer::call('chmod', $path . '/' . $bin, 0777 & ~umask()); } } return \React\Promise\resolve(null); } protected function getDistPath(PackageInterface $package, int $component): string { return pathinfo((string) parse_url(strtr((string) $package->getDistUrl(), '\\', '/'), PHP_URL_PATH), $component); } protected function clearLastCacheWrite(PackageInterface $package): void { if ($this->cache !== null && isset($this->lastCacheWrites[$package->getName()])) { $this->cache->remove($this->lastCacheWrites[$package->getName()]); unset($this->lastCacheWrites[$package->getName()]); } } protected function addCleanupPath(PackageInterface $package, string $path): void { $this->additionalCleanupPaths[$package->getName()][] = $path; } protected function removeCleanupPath(PackageInterface $package, string $path): void { if (isset($this->additionalCleanupPaths[$package->getName()])) { $idx = array_search($path, $this->additionalCleanupPaths[$package->getName()], true); if (false !== $idx) { unset($this->additionalCleanupPaths[$package->getName()][$idx]); } } } public function update(PackageInterface $initial, PackageInterface $target, string $path): PromiseInterface { $this->io->writeError(" - " . UpdateOperation::format($initial, $target) . $this->getInstallOperationAppendix($target, $path)); $promise = $this->remove($initial, $path, false); return $promise->then(function () use ($target, $path): PromiseInterface { return $this->install($target, $path, false); }); } public function remove(PackageInterface $package, string $path, bool $output = true): PromiseInterface { if ($output) { $this->io->writeError(" - " . UninstallOperation::format($package)); } $promise = $this->filesystem->removeDirectoryAsync($path); return $promise->then(static function ($result) use ($path): void { if (!$result) { throw new \RuntimeException('Could not completely delete '.$path.', aborting.'); } }); } protected function getFileName(PackageInterface $package, string $path): string { $extension = $this->getDistPath($package, PATHINFO_EXTENSION); if ($extension === '') { $extension = $package->getDistType(); } return rtrim($this->config->get('vendor-dir') . '/composer/tmp-' . hash('md5', $package . spl_object_hash($package)) . '.' . $extension, '.'); } protected function getInstallOperationAppendix(PackageInterface $package, string $path): string { return ''; } protected function processUrl(PackageInterface $package, string $url): string { if (!extension_loaded('openssl') && 0 === strpos($url, 'https:')) { throw new \RuntimeException('You must enable the openssl extension to download files via https'); } if ($package->getDistReference() !== null) { $url = UrlUtil::updateDistReference($this->config, $url, $package->getDistReference()); } return $url; } public function getLocalChanges(PackageInterface $package, string $path): ?string { $prevIO = $this->io; $this->io = new NullIO; $this->io->loadConfiguration($this->config); $e = null; $output = ''; $targetDir = Filesystem::trimTrailingSlash($path); try { if (is_dir($targetDir.'_compare')) { $this->filesystem->removeDirectory($targetDir.'_compare'); } $promise = $this->download($package, $targetDir.'_compare', null, false); $promise->then(null, static function ($ex) use (&$e) { $e = $ex; }); $this->httpDownloader->wait(); if ($e !== null) { throw $e; } $promise = $this->install($package, $targetDir.'_compare', false); $promise->then(null, static function ($ex) use (&$e) { $e = $ex; }); $this->process->wait(); if ($e !== null) { throw $e; } $comparer = new Comparer(); $comparer->setSource($targetDir.'_compare'); $comparer->setUpdate($targetDir); $comparer->doCompare(); $output = $comparer->getChangedAsString(true); $this->filesystem->removeDirectory($targetDir.'_compare'); } catch (\Exception $e) { } $this->io = $prevIO; if ($e !== null) { if ($this->io->isDebug()) { throw $e; } return 'Failed to detect changes: ['.get_class($e).'] '.$e->getMessage(); } $output = trim($output); return strlen($output) > 0 ? $output : null; } } config->prohibitUrlByConfig($url, $this->io); $repoFile = $path . '.fossil'; $realPath = Platform::realpath($path); $this->io->writeError("Cloning ".$package->getSourceReference()); $this->execute(['fossil', 'clone', '--', $url, $repoFile]); $this->execute(['fossil', 'open', '--nested', '--', $repoFile], $realPath); $this->execute(['fossil', 'update', '--', (string) $package->getSourceReference()], $realPath); return \React\Promise\resolve(null); } protected function doUpdate(PackageInterface $initial, PackageInterface $target, string $path, string $url): PromiseInterface { $this->config->prohibitUrlByConfig($url, $this->io); $this->io->writeError(" Updating to ".$target->getSourceReference()); if (!$this->hasMetadataRepository($path)) { throw new RuntimeException('The .fslckout file is missing from '.$path.', see https://getcomposer.org/commit-deps for more information'); } $realPath = Platform::realpath($path); $this->execute(['fossil', 'pull'], $realPath); $this->execute(['fossil', 'up', '--', (string) $target->getSourceReference()], $realPath); return \React\Promise\resolve(null); } public function getLocalChanges(PackageInterface $package, string $path): ?string { if (!$this->hasMetadataRepository($path)) { return null; } $this->process->execute(['fossil', 'changes'], $output, Platform::realpath($path)); $output = trim($output); return strlen($output) > 0 ? $output : null; } protected function getCommitLogs(string $fromReference, string $toReference, string $path): string { $this->execute(['fossil', 'timeline', '-t', 'ci', '-W', '0', '-n', '0', 'before', $toReference], Platform::realpath($path), $output); $log = ''; $match = '/\d\d:\d\d:\d\d\s+\[' . $toReference . '\]/'; foreach ($this->process->splitLines($output) as $line) { if (Preg::isMatch($match, $line)) { break; } $log .= $line; } return $log; } private function execute(array $command, ?string $cwd = null, ?string &$output = null): void { if (0 !== $this->process->execute($command, $output, $cwd)) { throw new RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput()); } } protected function hasMetadataRepository(string $path): bool { return is_file($path . '/.fslckout') || is_file($path . '/_FOSSIL_'); } } gitUtil = new GitUtil($this->io, $this->config, $this->process, $this->filesystem); } protected function doDownload(PackageInterface $package, string $path, string $url, ?PackageInterface $prevPackage = null): PromiseInterface { if (Filesystem::isLocalPath($url)) { return \React\Promise\resolve(null); } GitUtil::cleanEnv($this->process); $cachePath = $this->config->get('cache-vcs-dir').'/'.Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($url)).'/'; $gitVersion = GitUtil::getVersion($this->process); if ($gitVersion && version_compare($gitVersion, '2.3.0-rc0', '>=') && Cache::isUsable($cachePath)) { $this->io->writeError(" - Syncing " . $package->getName() . " (" . $package->getFullPrettyVersion() . ") into cache"); $this->io->writeError(sprintf(' Cloning to cache at %s', $cachePath), true, IOInterface::DEBUG); $ref = $package->getSourceReference(); if ($this->gitUtil->fetchRefOrSyncMirror($url, $cachePath, $ref, $package->getPrettyVersion()) && is_dir($cachePath)) { $this->cachedPackages[$package->getId()][$ref] = true; } } elseif (null === $gitVersion) { throw new \RuntimeException('git was not found in your PATH, skipping source download'); } return \React\Promise\resolve(null); } protected function doInstall(PackageInterface $package, string $path, string $url): PromiseInterface { GitUtil::cleanEnv($this->process); $path = $this->normalizePath($path); $cachePath = $this->config->get('cache-vcs-dir').'/'.Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($url)).'/'; $ref = $package->getSourceReference(); if (!empty($this->cachedPackages[$package->getId()][$ref])) { $msg = "Cloning ".$this->getShortHash($ref).' from cache'; $cloneFlags = ['--dissociate', '--reference', $cachePath]; $transportOptions = $package->getTransportOptions(); if (isset($transportOptions['git']['single_use_clone']) && $transportOptions['git']['single_use_clone']) { $cloneFlags = []; } $commands = [ array_merge(['git', 'clone', '--no-checkout', $cachePath, $path], $cloneFlags), ['git', 'remote', 'set-url', 'origin', '--', '%sanitizedUrl%'], ['git', 'remote', 'add', 'composer', '--', '%sanitizedUrl%'], ]; } else { $msg = "Cloning ".$this->getShortHash($ref); $commands = [ array_merge(['git', 'clone', '--no-checkout', '--', '%url%', $path]), ['git', 'remote', 'add', 'composer', '--', '%url%'], ['git', 'fetch', 'composer'], ['git', 'remote', 'set-url', 'origin', '--', '%sanitizedUrl%'], ['git', 'remote', 'set-url', 'composer', '--', '%sanitizedUrl%'], ]; if (Platform::getEnv('COMPOSER_DISABLE_NETWORK')) { throw new \RuntimeException('The required git reference for '.$package->getName().' is not in cache and network is disabled, aborting'); } } $this->io->writeError($msg); $this->gitUtil->runCommands($commands, $url, $path, true); $sourceUrl = $package->getSourceUrl(); if ($url !== $sourceUrl && $sourceUrl !== null) { $this->updateOriginUrl($path, $sourceUrl); } else { $this->setPushUrl($path, $url); } if ($newRef = $this->updateToCommit($package, $path, (string) $ref, $package->getPrettyVersion())) { if ($package->getDistReference() === $package->getSourceReference()) { $package->setDistReference($newRef); } $package->setSourceReference($newRef); } return \React\Promise\resolve(null); } protected function doUpdate(PackageInterface $initial, PackageInterface $target, string $path, string $url): PromiseInterface { GitUtil::cleanEnv($this->process); $path = $this->normalizePath($path); if (!$this->hasMetadataRepository($path)) { throw new \RuntimeException('The .git directory is missing from '.$path.', see https://getcomposer.org/commit-deps for more information'); } $cachePath = $this->config->get('cache-vcs-dir').'/'.Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($url)).'/'; $ref = $target->getSourceReference(); if (!empty($this->cachedPackages[$target->getId()][$ref])) { $msg = "Checking out ".$this->getShortHash($ref).' from cache'; $remoteUrl = $cachePath; } else { $msg = "Checking out ".$this->getShortHash($ref); $remoteUrl = '%url%'; if (Platform::getEnv('COMPOSER_DISABLE_NETWORK')) { throw new \RuntimeException('The required git reference for '.$target->getName().' is not in cache and network is disabled, aborting'); } } $this->io->writeError($msg); if (0 !== $this->process->execute(['git', 'rev-parse', '--quiet', '--verify', $ref.'^{commit}'], $output, $path)) { $commands = [ ['git', 'remote', 'set-url', 'composer', '--', $remoteUrl], ['git', 'fetch', 'composer'], ['git', 'fetch', '--tags', 'composer'], ]; $this->gitUtil->runCommands($commands, $url, $path); } $command = ['git', 'remote', 'set-url', 'composer', '--', '%sanitizedUrl%']; $this->gitUtil->runCommands([$command], $url, $path); if ($newRef = $this->updateToCommit($target, $path, (string) $ref, $target->getPrettyVersion())) { if ($target->getDistReference() === $target->getSourceReference()) { $target->setDistReference($newRef); } $target->setSourceReference($newRef); } $updateOriginUrl = false; if ( 0 === $this->process->execute(['git', 'remote', '-v'], $output, $path) && Preg::isMatch('{^origin\s+(?P\S+)}m', $output, $originMatch) && Preg::isMatch('{^composer\s+(?P\S+)}m', $output, $composerMatch) ) { if ($originMatch['url'] === $composerMatch['url'] && $composerMatch['url'] !== $target->getSourceUrl()) { $updateOriginUrl = true; } } if ($updateOriginUrl && $target->getSourceUrl() !== null) { $this->updateOriginUrl($path, $target->getSourceUrl()); } return \React\Promise\resolve(null); } public function getLocalChanges(PackageInterface $package, string $path): ?string { GitUtil::cleanEnv($this->process); if (!$this->hasMetadataRepository($path)) { return null; } $command = ['git', 'status', '--porcelain', '--untracked-files=no']; if (0 !== $this->process->execute($command, $output, $path)) { throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput()); } $output = trim($output); return strlen($output) > 0 ? $output : null; } public function getUnpushedChanges(PackageInterface $package, string $path): ?string { GitUtil::cleanEnv($this->process); $path = $this->normalizePath($path); if (!$this->hasMetadataRepository($path)) { return null; } $command = ['git', 'show-ref', '--head', '-d']; if (0 !== $this->process->execute($command, $output, $path)) { throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput()); } $refs = trim($output); if (!Preg::isMatchStrictGroups('{^([a-f0-9]+) HEAD$}mi', $refs, $match)) { return null; } $headRef = $match[1]; if (!Preg::isMatchAllStrictGroups('{^'.preg_quote($headRef).' refs/heads/(.+)$}mi', $refs, $matches)) { return null; } $candidateBranches = $matches[1]; $branch = $candidateBranches[0]; $unpushedChanges = null; $branchNotFoundError = false; for ($i = 0; $i <= 1; $i++) { $remoteBranches = []; foreach ($candidateBranches as $candidate) { if (Preg::isMatchAllStrictGroups('{^[a-f0-9]+ refs/remotes/((?:[^/]+)/'.preg_quote($candidate).')$}mi', $refs, $matches)) { foreach ($matches[1] as $match) { $branch = $candidate; $remoteBranches[] = $match; } break; } } if (count($remoteBranches) === 0) { $unpushedChanges = 'Branch ' . $branch . ' could not be found on any remote and appears to be unpushed'; $branchNotFoundError = true; } else { if ($branchNotFoundError) { $unpushedChanges = null; } foreach ($remoteBranches as $remoteBranch) { $command = ['git', 'diff', '--name-status', $remoteBranch.'...'.$branch, '--']; if (0 !== $this->process->execute($command, $output, $path)) { throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput()); } $output = trim($output); if ($unpushedChanges === null || strlen($output) < strlen($unpushedChanges)) { $unpushedChanges = $output; } } } if ($unpushedChanges && $i === 0) { $this->process->execute(['git', 'fetch', '--all'], $output, $path); $command = ['git', 'show-ref', '--head', '-d']; if (0 !== $this->process->execute($command, $output, $path)) { throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput()); } $refs = trim($output); } if (!$unpushedChanges) { break; } } return $unpushedChanges; } protected function cleanChanges(PackageInterface $package, string $path, bool $update): PromiseInterface { GitUtil::cleanEnv($this->process); $path = $this->normalizePath($path); $unpushed = $this->getUnpushedChanges($package, $path); if ($unpushed && ($this->io->isInteractive() || $this->config->get('discard-changes') !== true)) { throw new \RuntimeException('Source directory ' . $path . ' has unpushed changes on the current branch: '."\n".$unpushed); } if (null === ($changes = $this->getLocalChanges($package, $path))) { return \React\Promise\resolve(null); } if (!$this->io->isInteractive()) { $discardChanges = $this->config->get('discard-changes'); if (true === $discardChanges) { return $this->discardChanges($path); } if ('stash' === $discardChanges) { if (!$update) { return parent::cleanChanges($package, $path, $update); } return $this->stashChanges($path); } return parent::cleanChanges($package, $path, $update); } $changes = array_map(static function ($elem): string { return ' '.$elem; }, Preg::split('{\s*\r?\n\s*}', $changes)); $this->io->writeError(' '.$package->getPrettyName().' has modified files:'); $this->io->writeError(array_slice($changes, 0, 10)); if (count($changes) > 10) { $this->io->writeError(' ' . (count($changes) - 10) . ' more files modified, choose "v" to view the full list'); } while (true) { switch ($this->io->ask(' Discard changes [y,n,v,d,'.($update ? 's,' : '').'?]? ', '?')) { case 'y': $this->discardChanges($path); break 2; case 's': if (!$update) { goto help; } $this->stashChanges($path); break 2; case 'n': throw new \RuntimeException('Update aborted'); case 'v': $this->io->writeError($changes); break; case 'd': $this->viewDiff($path); break; case '?': default: help : $this->io->writeError([ ' y - discard changes and apply the '.($update ? 'update' : 'uninstall'), ' n - abort the '.($update ? 'update' : 'uninstall').' and let you manually clean things up', ' v - view modified files', ' d - view local modifications (diff)', ]); if ($update) { $this->io->writeError(' s - stash changes and try to reapply them after the update'); } $this->io->writeError(' ? - print help'); break; } } return \React\Promise\resolve(null); } protected function reapplyChanges(string $path): void { $path = $this->normalizePath($path); if (!empty($this->hasStashedChanges[$path])) { unset($this->hasStashedChanges[$path]); $this->io->writeError(' Re-applying stashed changes'); if (0 !== $this->process->execute(['git', 'stash', 'pop'], $output, $path)) { throw new \RuntimeException("Failed to apply stashed changes:\n\n".$this->process->getErrorOutput()); } } unset($this->hasDiscardedChanges[$path]); } protected function updateToCommit(PackageInterface $package, string $path, string $reference, string $prettyVersion): ?string { $force = !empty($this->hasDiscardedChanges[$path]) || !empty($this->hasStashedChanges[$path]) ? ['-f'] : []; $branch = Preg::replace('{(?:^dev-|(?:\.x)?-dev$)}i', '', $prettyVersion); $execute = function (array $command) use (&$output, $path) { $output = ''; return 0 === $this->process->execute($command, $output, $path); }; $branches = null; if ($execute(['git', 'branch', '-r'])) { $branches = $output; } $gitRef = $reference; if (!Preg::isMatch('{^[a-f0-9]{40}$}', $reference) && null !== $branches && Preg::isMatch('{^\s+composer/'.preg_quote($reference).'$}m', $branches) ) { $command1 = array_merge(['git', 'checkout'], $force, ['-B', $branch, 'composer/'.$reference, '--']); $command2 = ['git', 'reset', '--hard', 'composer/'.$reference, '--']; if ($execute($command1) && $execute($command2)) { return null; } } if (Preg::isMatch('{^[a-f0-9]{40}$}', $reference)) { if (null !== $branches && !Preg::isMatch('{^\s+composer/'.preg_quote($branch).'$}m', $branches) && Preg::isMatch('{^\s+composer/v'.preg_quote($branch).'$}m', $branches)) { $branch = 'v' . $branch; } $command = ['git', 'checkout', $branch, '--']; $fallbackCommand = array_merge(['git', 'checkout'], $force, ['-B', $branch, 'composer/'.$branch, '--']); $resetCommand = ['git', 'reset', '--hard', $reference, '--']; if (($execute($command) || $execute($fallbackCommand)) && $execute($resetCommand)) { return null; } } $command1 = array_merge(['git', 'checkout'], $force, [$gitRef, '--']); $command2 = ['git', 'reset', '--hard', $gitRef, '--']; if ($execute($command1) && $execute($command2)) { return null; } $exceptionExtra = ''; if (false !== strpos($this->process->getErrorOutput(), $reference)) { $this->io->writeError(' '.$reference.' is gone (history was rewritten?)'); $exceptionExtra = "\nIt looks like the commit hash is not available in the repository, maybe ".($package->isDev() ? 'the commit was removed from the branch' : 'the tag was recreated').'? Run "composer update '.$package->getPrettyName().'" to resolve this.'; } $command = implode(' ', $command1). ' && '.implode(' ', $command2); throw new \RuntimeException(Url::sanitize('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput() . $exceptionExtra)); } protected function updateOriginUrl(string $path, string $url): void { $this->process->execute(['git', 'remote', 'set-url', 'origin', '--', $url], $output, $path); $this->setPushUrl($path, $url); } protected function setPushUrl(string $path, string $url): void { if (Preg::isMatch('{^(?:https?|git)://'.GitUtil::getGitHubDomainsRegex($this->config).'/([^/]+)/([^/]+?)(?:\.git)?$}', $url, $match)) { $protocols = $this->config->get('github-protocols'); $pushUrl = 'git@'.$match[1].':'.$match[2].'/'.$match[3].'.git'; if (!in_array('ssh', $protocols, true)) { $pushUrl = 'https://' . $match[1] . '/'.$match[2].'/'.$match[3].'.git'; } $cmd = ['git', 'remote', 'set-url', '--push', 'origin', '--', $pushUrl]; $this->process->execute($cmd, $ignoredOutput, $path); } } protected function getCommitLogs(string $fromReference, string $toReference, string $path): string { $path = $this->normalizePath($path); $command = GitUtil::buildRevListCommand($this->process, array_merge(['--format=%h - %an: %s', $fromReference.'..'.$toReference], GitUtil::getNoShowSignatureFlags($this->process))); if (0 !== $this->process->execute($command, $output, $path)) { throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput()); } return GitUtil::parseRevListOutput($output, $this->process); } protected function discardChanges(string $path): PromiseInterface { $path = $this->normalizePath($path); if (0 !== $this->process->execute(['git', 'clean', '-df'], $output, $path)) { throw new \RuntimeException("Could not reset changes\n\n:".$output); } if (0 !== $this->process->execute(['git', 'reset', '--hard'], $output, $path)) { throw new \RuntimeException("Could not reset changes\n\n:".$output); } $this->hasDiscardedChanges[$path] = true; return \React\Promise\resolve(null); } protected function stashChanges(string $path): PromiseInterface { $path = $this->normalizePath($path); if (0 !== $this->process->execute(['git', 'stash', '--include-untracked'], $output, $path)) { throw new \RuntimeException("Could not stash changes\n\n:".$output); } $this->hasStashedChanges[$path] = true; return \React\Promise\resolve(null); } protected function viewDiff(string $path): void { $path = $this->normalizePath($path); if (0 !== $this->process->execute(['git', 'diff', 'HEAD'], $output, $path)) { throw new \RuntimeException("Could not view diff\n\n:".$output); } $this->io->writeError($output); } protected function normalizePath(string $path): string { if (Platform::isWindows() && strlen($path) > 0) { $basePath = $path; $removed = []; while (!is_dir($basePath) && $basePath !== '\\') { array_unshift($removed, basename($basePath)); $basePath = dirname($basePath); } if ($basePath === '\\') { return $path; } $path = rtrim(realpath($basePath) . '/' . implode('/', $removed), '/'); } return $path; } protected function hasMetadataRepository(string $path): bool { $path = $this->normalizePath($path); return is_dir($path.'/.git'); } protected function getShortHash(string $reference): string { if (!$this->io->isVerbose() && Preg::isMatch('{^[0-9a-f]{40}$}', $reference)) { return substr($reference, 0, 10); } return $reference; } } getDistUrl(), '\\', '/'), PHP_URL_PATH), PATHINFO_FILENAME); $targetFilepath = $path . DIRECTORY_SEPARATOR . $filename; if (!Platform::isWindows()) { $command = ['sh', '-c', 'gzip -cd -- "$0" > "$1"', $file, $targetFilepath]; if (0 === $this->process->execute($command, $ignoredOutput)) { return \React\Promise\resolve(null); } if (extension_loaded('zlib')) { $this->extractUsingExt($file, $targetFilepath); return \React\Promise\resolve(null); } $processError = 'Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput(); throw new \RuntimeException($processError); } $this->extractUsingExt($file, $targetFilepath); return \React\Promise\resolve(null); } private function extractUsingExt(string $file, string $targetFilepath): void { $archiveFile = gzopen($file, 'rb'); $targetFile = fopen($targetFilepath, 'wb'); while ($string = gzread($archiveFile, 4096)) { fwrite($targetFile, $string, Platform::strlen($string)); } gzclose($archiveFile); fclose($targetFile); } } process)) { throw new \RuntimeException('hg was not found in your PATH, skipping source download'); } return \React\Promise\resolve(null); } protected function doInstall(PackageInterface $package, string $path, string $url): PromiseInterface { $hgUtils = new HgUtils($this->io, $this->config, $this->process); $cloneCommand = static function (string $url) use ($path): array { return ['hg', 'clone', '--', $url, $path]; }; $hgUtils->runCommand($cloneCommand, $url, $path); $command = ['hg', 'up', '--', (string) $package->getSourceReference()]; if (0 !== $this->process->execute($command, $ignoredOutput, realpath($path))) { throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput()); } return \React\Promise\resolve(null); } protected function doUpdate(PackageInterface $initial, PackageInterface $target, string $path, string $url): PromiseInterface { $hgUtils = new HgUtils($this->io, $this->config, $this->process); $ref = $target->getSourceReference(); $this->io->writeError(" Updating to ".$target->getSourceReference()); if (!$this->hasMetadataRepository($path)) { throw new \RuntimeException('The .hg directory is missing from '.$path.', see https://getcomposer.org/commit-deps for more information'); } $command = static function ($url): array { return ['hg', 'pull', '--', $url]; }; $hgUtils->runCommand($command, $url, $path); $command = static function () use ($ref): array { return ['hg', 'up', '--', $ref]; }; $hgUtils->runCommand($command, $url, $path); return \React\Promise\resolve(null); } public function getLocalChanges(PackageInterface $package, string $path): ?string { if (!is_dir($path.'/.hg')) { return null; } $this->process->execute(['hg', 'st'], $output, realpath($path)); $output = trim($output); return strlen($output) > 0 ? $output : null; } protected function getCommitLogs(string $fromReference, string $toReference, string $path): string { $command = ['hg', 'log', '-r', $fromReference.':'.$toReference, '--style', 'compact']; if (0 !== $this->process->execute($command, $output, realpath($path))) { throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput()); } return $output; } protected function hasMetadataRepository(string $path): bool { return is_dir($path . '/.hg'); } } getDistUrl(); if (null === $url) { throw new \RuntimeException('The package '.$package->getPrettyName().' has no dist url configured, cannot download.'); } $realUrl = realpath($url); if (false === $realUrl || !file_exists($realUrl) || !is_dir($realUrl)) { throw new \RuntimeException(sprintf( 'Source path "%s" is not found for package %s', $url, $package->getName() )); } if (realpath($path) === $realUrl) { return \React\Promise\resolve(null); } if (strpos(realpath($path) . DIRECTORY_SEPARATOR, $realUrl . DIRECTORY_SEPARATOR) === 0) { throw new \RuntimeException(sprintf( 'Package %s cannot install to "%s" inside its source at "%s"', $package->getName(), realpath($path), $realUrl )); } return \React\Promise\resolve(null); } public function install(PackageInterface $package, string $path, bool $output = true): PromiseInterface { $path = Filesystem::trimTrailingSlash($path); $url = $package->getDistUrl(); if (null === $url) { throw new \RuntimeException('The package '.$package->getPrettyName().' has no dist url configured, cannot install.'); } $realUrl = realpath($url); if (false === $realUrl) { throw new \RuntimeException('Failed to realpath '.$url); } if (realpath($path) === $realUrl) { if ($output) { $this->io->writeError(" - " . InstallOperation::format($package) . $this->getInstallOperationAppendix($package, $path)); } return \React\Promise\resolve(null); } $transportOptions = $package->getTransportOptions() + ['relative' => true]; [$currentStrategy, $allowedStrategies] = $this->computeAllowedStrategies($transportOptions); $symfonyFilesystem = new SymfonyFilesystem(); $this->filesystem->removeDirectory($path); if ($output) { $this->io->writeError(" - " . InstallOperation::format($package).': ', false); } $isFallback = false; if (self::STRATEGY_SYMLINK === $currentStrategy) { try { if (Platform::isWindows()) { if ($output) { $this->io->writeError(sprintf('Junctioning from %s', $url), false); } $this->filesystem->junction($realUrl, $path); } else { $path = rtrim($path, "/"); if ($output) { $this->io->writeError(sprintf('Symlinking from %s', $url), false); } if ($transportOptions['relative'] === true) { $absolutePath = $path; if (!$this->filesystem->isAbsolutePath($absolutePath)) { $absolutePath = Platform::getCwd() . DIRECTORY_SEPARATOR . $path; } $shortestPath = $this->filesystem->findShortestPath($absolutePath, $realUrl, false, true); $symfonyFilesystem->symlink($shortestPath.'/', $path); } else { $symfonyFilesystem->symlink($realUrl.'/', $path); } } } catch (IOException $e) { if (in_array(self::STRATEGY_MIRROR, $allowedStrategies, true)) { if ($output) { $this->io->writeError(''); $this->io->writeError(' Symlink failed, fallback to use mirroring!'); } $currentStrategy = self::STRATEGY_MIRROR; $isFallback = true; } else { throw new \RuntimeException(sprintf('Symlink from "%s" to "%s" failed!', $realUrl, $path)); } } } if (self::STRATEGY_MIRROR === $currentStrategy) { $realUrl = $this->filesystem->normalizePath($realUrl); if ($output) { $this->io->writeError(sprintf('%sMirroring from %s', $isFallback ? ' ' : '', $url), false); } $iterator = new ArchivableFilesFinder($realUrl, []); $symfonyFilesystem->mirror($realUrl, $path, $iterator); } if ($output) { $this->io->writeError(''); } return \React\Promise\resolve(null); } public function remove(PackageInterface $package, string $path, bool $output = true): PromiseInterface { $path = Filesystem::trimTrailingSlash($path); if (Platform::isWindows() && $this->filesystem->isJunction($path)) { if ($output) { $this->io->writeError(" - " . UninstallOperation::format($package).", source is still present in $path"); } if (!$this->filesystem->removeJunction($path)) { $this->io->writeError(" Could not remove junction at " . $path . " - is another process locking it?"); throw new \RuntimeException('Could not reliably remove junction for package ' . $package->getName()); } return \React\Promise\resolve(null); } $url = $package->getDistUrl(); if (null === $url) { throw new \RuntimeException('The package '.$package->getPrettyName().' has no dist url configured, cannot remove.'); } $fs = new Filesystem; $absPath = $fs->isAbsolutePath($path) ? $path : Platform::getCwd() . '/' . $path; $absDistUrl = $fs->isAbsolutePath($url) ? $url : Platform::getCwd() . '/' . $url; if ($fs->normalizePath($absPath) === $fs->normalizePath($absDistUrl)) { if ($output) { $this->io->writeError(" - " . UninstallOperation::format($package).", source is still present in $path"); } return \React\Promise\resolve(null); } return parent::remove($package, $path, $output); } public function getVcsReference(PackageInterface $package, string $path): ?string { $path = Filesystem::trimTrailingSlash($path); $parser = new VersionParser; $guesser = new VersionGuesser($this->config, $this->process, $parser, $this->io); $dumper = new ArrayDumper; $packageConfig = $dumper->dump($package); $packageVersion = $guesser->guessVersion($packageConfig, $path); if ($packageVersion !== null) { return $packageVersion['commit']; } return null; } protected function getInstallOperationAppendix(PackageInterface $package, string $path): string { $url = $package->getDistUrl(); if (null === $url) { throw new \RuntimeException('The package '.$package->getPrettyName().' has no dist url configured, cannot install.'); } $realUrl = realpath($url); if (false === $realUrl) { throw new \RuntimeException('Failed to realpath '.$url); } if (realpath($path) === $realUrl) { return ': Source already present'; } [$currentStrategy] = $this->computeAllowedStrategies($package->getTransportOptions()); if ($currentStrategy === self::STRATEGY_SYMLINK) { if (Platform::isWindows()) { return ': Junctioning from '.$package->getDistUrl(); } return ': Symlinking from '.$package->getDistUrl(); } return ': Mirroring from '.$package->getDistUrl(); } private function computeAllowedStrategies(array $transportOptions): array { $currentStrategy = self::STRATEGY_SYMLINK; $allowedStrategies = [self::STRATEGY_SYMLINK, self::STRATEGY_MIRROR]; $mirrorPathRepos = Platform::getEnv('COMPOSER_MIRROR_PATH_REPOS'); if ((bool) $mirrorPathRepos) { $currentStrategy = self::STRATEGY_MIRROR; } $symlinkOption = $transportOptions['symlink'] ?? null; if (true === $symlinkOption) { $currentStrategy = self::STRATEGY_SYMLINK; $allowedStrategies = [self::STRATEGY_SYMLINK]; } elseif (false === $symlinkOption) { $currentStrategy = self::STRATEGY_MIRROR; $allowedStrategies = [self::STRATEGY_MIRROR]; } if (Platform::isWindows() && self::STRATEGY_SYMLINK === $currentStrategy && !$this->safeJunctions()) { if (!in_array(self::STRATEGY_MIRROR, $allowedStrategies, true)) { throw new \RuntimeException('You are on an old Windows / old PHP combo which does not allow Composer to use junctions/symlinks and this path repository has symlink:true in its options so copying is not allowed'); } $currentStrategy = self::STRATEGY_MIRROR; $allowedStrategies = [self::STRATEGY_MIRROR]; } if (!Platform::isWindows() && self::STRATEGY_SYMLINK === $currentStrategy && !function_exists('symlink')) { if (!in_array(self::STRATEGY_MIRROR, $allowedStrategies, true)) { throw new \RuntimeException('Your PHP has the symlink() function disabled which does not allow Composer to use symlinks and this path repository has symlink:true in its options so copying is not allowed'); } $currentStrategy = self::STRATEGY_MIRROR; $allowedStrategies = [self::STRATEGY_MIRROR]; } return [$currentStrategy, $allowedStrategies]; } private function safeJunctions(): bool { return function_exists('proc_open') && (PHP_WINDOWS_VERSION_MAJOR > 6 || (PHP_WINDOWS_VERSION_MAJOR === 6 && PHP_WINDOWS_VERSION_MINOR >= 1)); } } getSourceReference(); $label = $this->getLabelFromSourceReference((string) $ref); $this->io->writeError('Cloning ' . $ref); $this->initPerforce($package, $path, $url); $this->perforce->setStream($ref); $this->perforce->p4Login(); $this->perforce->writeP4ClientSpec(); $this->perforce->connectClient(); $this->perforce->syncCodeBase($label); $this->perforce->cleanupClientSpec(); return \React\Promise\resolve(null); } private function getLabelFromSourceReference(string $ref): ?string { $pos = strpos($ref, '@'); if (false !== $pos) { return substr($ref, $pos + 1); } return null; } public function initPerforce(PackageInterface $package, string $path, string $url): void { if (!empty($this->perforce)) { $this->perforce->initializePath($path); return; } $repository = $package->getRepository(); $repoConfig = null; if ($repository instanceof VcsRepository) { $repoConfig = $this->getRepoConfig($repository); } $this->perforce = Perforce::create($repoConfig, $url, $path, $this->process, $this->io); } private function getRepoConfig(VcsRepository $repository): array { return $repository->getRepoConfig(); } protected function doUpdate(PackageInterface $initial, PackageInterface $target, string $path, string $url): PromiseInterface { return $this->doInstall($target, $path, $url); } public function getLocalChanges(PackageInterface $package, string $path): ?string { $this->io->writeError('Perforce driver does not check for local changes before overriding'); return null; } protected function getCommitLogs(string $fromReference, string $toReference, string $path): string { return $this->perforce->getCommitLogs($fromReference, $toReference); } public function setPerforce(Perforce $perforce): void { $this->perforce = $perforce; } protected function hasMetadataRepository(string $path): bool { return true; } } extractTo($path, null, true); return \React\Promise\resolve(null); } } /dev/null && chmod -R u+w "$1"', $file, $path]; if (0 === $this->process->execute($command, $ignoredOutput)) { return \React\Promise\resolve(null); } $processError = 'Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput(); } if (!class_exists('RarArchive')) { $iniMessage = IniHelper::getMessage(); $error = "Could not decompress the archive, enable the PHP rar extension or install unrar.\n" . $iniMessage . "\n" . $processError; if (!Platform::isWindows()) { $error = "Could not decompress the archive, enable the PHP rar extension.\n" . $iniMessage; } throw new \RuntimeException($error); } $rarArchive = RarArchive::open($file); if (false === $rarArchive) { throw new \UnexpectedValueException('Could not open RAR archive: ' . $file); } $entries = $rarArchive->getEntries(); if (false === $entries) { throw new \RuntimeException('Could not retrieve RAR archive entries'); } foreach ($entries as $entry) { if (false === $entry->extract($path)) { throw new \RuntimeException('Could not extract entry'); } } $rarArchive->close(); return \React\Promise\resolve(null); } } io, $this->config, $this->process); if (null === $util->binaryVersion()) { throw new \RuntimeException('svn was not found in your PATH, skipping source download'); } return \React\Promise\resolve(null); } protected function doInstall(PackageInterface $package, string $path, string $url): PromiseInterface { SvnUtil::cleanEnv(); $ref = $package->getSourceReference(); $repo = $package->getRepository(); if ($repo instanceof VcsRepository) { $repoConfig = $repo->getRepoConfig(); if (array_key_exists('svn-cache-credentials', $repoConfig)) { $this->cacheCredentials = (bool) $repoConfig['svn-cache-credentials']; } } $this->io->writeError(" Checking out ".$package->getSourceReference()); $this->execute($package, $url, ['svn', 'co'], sprintf("%s/%s", $url, $ref), null, $path); return \React\Promise\resolve(null); } protected function doUpdate(PackageInterface $initial, PackageInterface $target, string $path, string $url): PromiseInterface { SvnUtil::cleanEnv(); $ref = $target->getSourceReference(); if (!$this->hasMetadataRepository($path)) { throw new \RuntimeException('The .svn directory is missing from '.$path.', see https://getcomposer.org/commit-deps for more information'); } $util = new SvnUtil($url, $this->io, $this->config, $this->process); $flags = []; if (version_compare($util->binaryVersion(), '1.7.0', '>=')) { $flags[] = '--ignore-ancestry'; } $this->io->writeError(" Checking out " . $ref); $this->execute($target, $url, array_merge(['svn', 'switch'], $flags), sprintf("%s/%s", $url, $ref), $path); return \React\Promise\resolve(null); } public function getLocalChanges(PackageInterface $package, string $path): ?string { if (!$this->hasMetadataRepository($path)) { return null; } $this->process->execute(['svn', 'status', '--ignore-externals'], $output, $path); return Preg::isMatch('{^ *[^X ] +}m', $output) ? $output : null; } protected function execute(PackageInterface $package, string $baseUrl, array $command, string $url, ?string $cwd = null, ?string $path = null): string { $util = new SvnUtil($baseUrl, $this->io, $this->config, $this->process); $util->setCacheCredentials($this->cacheCredentials); try { return $util->execute($command, $url, $cwd, $path, $this->io->isVerbose()); } catch (\RuntimeException $e) { throw new \RuntimeException( $package->getPrettyName().' could not be downloaded, '.$e->getMessage() ); } } protected function cleanChanges(PackageInterface $package, string $path, bool $update): PromiseInterface { if (null === ($changes = $this->getLocalChanges($package, $path))) { return \React\Promise\resolve(null); } if (!$this->io->isInteractive()) { if (true === $this->config->get('discard-changes')) { return $this->discardChanges($path); } return parent::cleanChanges($package, $path, $update); } $changes = array_map(static function ($elem): string { return ' '.$elem; }, Preg::split('{\s*\r?\n\s*}', $changes)); $countChanges = count($changes); $this->io->writeError(sprintf(' '.$package->getPrettyName().' has modified file%s:', $countChanges === 1 ? '' : 's')); $this->io->writeError(array_slice($changes, 0, 10)); if ($countChanges > 10) { $remainingChanges = $countChanges - 10; $this->io->writeError( sprintf( ' '.$remainingChanges.' more file%s modified, choose "v" to view the full list', $remainingChanges === 1 ? '' : 's' ) ); } while (true) { switch ($this->io->ask(' Discard changes [y,n,v,?]? ', '?')) { case 'y': $this->discardChanges($path); break 2; case 'n': throw new \RuntimeException('Update aborted'); case 'v': $this->io->writeError($changes); break; case '?': default: $this->io->writeError([ ' y - discard changes and apply the '.($update ? 'update' : 'uninstall'), ' n - abort the '.($update ? 'update' : 'uninstall').' and let you manually clean things up', ' v - view modified files', ' ? - print help', ]); break; } } return \React\Promise\resolve(null); } protected function getCommitLogs(string $fromReference, string $toReference, string $path): string { if (Preg::isMatch('{@(\d+)$}', $fromReference) && Preg::isMatch('{@(\d+)$}', $toReference)) { $command = ['svn', 'info', '--non-interactive', '--xml', '--', $path]; if (0 !== $this->process->execute($command, $output, $path)) { throw new \RuntimeException( 'Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput() ); } $urlPattern = '#(.*)#'; if (Preg::isMatchStrictGroups($urlPattern, $output, $matches)) { $baseUrl = $matches[1]; } else { throw new \RuntimeException( 'Unable to determine svn url for path '. $path ); } $fromRevision = Preg::replace('{.*@(\d+)$}', '$1', $fromReference); $toRevision = Preg::replace('{.*@(\d+)$}', '$1', $toReference); $command = ['svn', 'log', '-r', $fromRevision.':'.$toRevision, '--incremental']; $util = new SvnUtil($baseUrl, $this->io, $this->config, $this->process); $util->setCacheCredentials($this->cacheCredentials); try { return $util->executeLocal($command, $path, null, $this->io->isVerbose()); } catch (\RuntimeException $e) { throw new \RuntimeException( 'Failed to execute ' . implode(' ', $command) . "\n\n".$e->getMessage() ); } } return "Could not retrieve changes between $fromReference and $toReference due to missing revision information"; } protected function discardChanges(string $path): PromiseInterface { if (0 !== $this->process->execute(['svn', 'revert', '-R', '.'], $output, $path)) { throw new \RuntimeException("Could not reset changes\n\n:".$this->process->getErrorOutput()); } return \React\Promise\resolve(null); } protected function hasMetadataRepository(string $path): bool { return is_dir($path.'/.svn'); } } extractTo($path, null, true); return \React\Promise\resolve(null); } } headers = $headers; } public function getHeaders(): ?array { return $this->headers; } public function setResponse(?string $response): void { $this->response = $response; } public function getResponse(): ?string { return $this->response; } public function setStatusCode($statusCode): void { $this->statusCode = $statusCode; } public function getStatusCode(): ?int { return $this->statusCode; } public function getResponseInfo(): array { return $this->responseInfo; } public function setResponseInfo(array $responseInfo): void { $this->responseInfo = $responseInfo; } } io = $io; $this->config = $config; $this->process = $process ?? new ProcessExecutor($io); $this->filesystem = $fs ?? new Filesystem($this->process); } public function getInstallationSource(): string { return 'source'; } public function download(PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface { if (!$package->getSourceReference()) { throw new \InvalidArgumentException('Package '.$package->getPrettyName().' is missing reference information'); } $urls = $this->prepareUrls($package->getSourceUrls()); while ($url = array_shift($urls)) { try { return $this->doDownload($package, $path, $url, $prevPackage); } catch (\Exception $e) { if ($e instanceof \PHPUnit\Framework\Exception) { throw $e; } if ($this->io->isDebug()) { $this->io->writeError('Failed: ['.get_class($e).'] '.$e->getMessage()); } elseif (count($urls)) { $this->io->writeError(' Failed, trying the next URL'); } if (!count($urls)) { throw $e; } } } return \React\Promise\resolve(null); } public function prepare(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface { if ($type === 'update') { $this->cleanChanges($prevPackage, $path, true); $this->hasCleanedChanges[$prevPackage->getUniqueName()] = true; } elseif ($type === 'install') { $this->filesystem->emptyDirectory($path); } elseif ($type === 'uninstall') { $this->cleanChanges($package, $path, false); } return \React\Promise\resolve(null); } public function cleanup(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface { if ($type === 'update' && isset($this->hasCleanedChanges[$prevPackage->getUniqueName()])) { $this->reapplyChanges($path); unset($this->hasCleanedChanges[$prevPackage->getUniqueName()]); } return \React\Promise\resolve(null); } public function install(PackageInterface $package, string $path): PromiseInterface { if (!$package->getSourceReference()) { throw new \InvalidArgumentException('Package '.$package->getPrettyName().' is missing reference information'); } $this->io->writeError(" - " . InstallOperation::format($package).': ', false); $urls = $this->prepareUrls($package->getSourceUrls()); while ($url = array_shift($urls)) { try { $this->doInstall($package, $path, $url); break; } catch (\Exception $e) { if ($e instanceof \PHPUnit\Framework\Exception) { throw $e; } if ($this->io->isDebug()) { $this->io->writeError('Failed: ['.get_class($e).'] '.$e->getMessage()); } elseif (count($urls)) { $this->io->writeError(' Failed, trying the next URL'); } if (!count($urls)) { throw $e; } } } return \React\Promise\resolve(null); } public function update(PackageInterface $initial, PackageInterface $target, string $path): PromiseInterface { if (!$target->getSourceReference()) { throw new \InvalidArgumentException('Package '.$target->getPrettyName().' is missing reference information'); } $this->io->writeError(" - " . UpdateOperation::format($initial, $target).': ', false); $urls = $this->prepareUrls($target->getSourceUrls()); $exception = null; while ($url = array_shift($urls)) { try { $this->doUpdate($initial, $target, $path, $url); $exception = null; break; } catch (\Exception $exception) { if ($exception instanceof \PHPUnit\Framework\Exception) { throw $exception; } if ($this->io->isDebug()) { $this->io->writeError('Failed: ['.get_class($exception).'] '.$exception->getMessage()); } elseif (count($urls)) { $this->io->writeError(' Failed, trying the next URL'); } } } if (!$exception && $this->io->isVerbose() && $this->hasMetadataRepository($path)) { $message = 'Pulling in changes:'; $logs = $this->getCommitLogs($initial->getSourceReference(), $target->getSourceReference(), $path); if ('' === trim($logs)) { $message = 'Rolling back changes:'; $logs = $this->getCommitLogs($target->getSourceReference(), $initial->getSourceReference(), $path); } if ('' !== trim($logs)) { $logs = implode("\n", array_map(static function ($line): string { return ' ' . $line; }, explode("\n", $logs))); $logs = str_replace('<', '\<', $logs); $this->io->writeError(' '.$message); $this->io->writeError($logs); } } if (!$urls && $exception) { throw $exception; } return \React\Promise\resolve(null); } public function remove(PackageInterface $package, string $path): PromiseInterface { $this->io->writeError(" - " . UninstallOperation::format($package)); $promise = $this->filesystem->removeDirectoryAsync($path); return $promise->then(static function (bool $result) use ($path) { if (!$result) { throw new \RuntimeException('Could not completely delete '.$path.', aborting.'); } }); } public function getVcsReference(PackageInterface $package, string $path): ?string { $parser = new VersionParser; $guesser = new VersionGuesser($this->config, $this->process, $parser, $this->io); $dumper = new ArrayDumper; $packageConfig = $dumper->dump($package); if ($packageVersion = $guesser->guessVersion($packageConfig, $path)) { return $packageVersion['commit']; } return null; } protected function cleanChanges(PackageInterface $package, string $path, bool $update): PromiseInterface { if (null !== $this->getLocalChanges($package, $path)) { throw new \RuntimeException('Source directory ' . $path . ' has uncommitted changes.'); } return \React\Promise\resolve(null); } protected function reapplyChanges(string $path): void { } abstract protected function doDownload(PackageInterface $package, string $path, string $url, ?PackageInterface $prevPackage = null): PromiseInterface; abstract protected function doInstall(PackageInterface $package, string $path, string $url): PromiseInterface; abstract protected function doUpdate(PackageInterface $initial, PackageInterface $target, string $path, string $url): PromiseInterface; abstract protected function getCommitLogs(string $fromReference, string $toReference, string $path): string; abstract protected function hasMetadataRepository(string $path): bool; private function prepareUrls(array $urls): array { foreach ($urls as $index => $url) { if (Filesystem::isLocalPath($url)) { $fileProtocol = 'file://'; $isFileProtocol = false; if (0 === strpos($url, $fileProtocol)) { $url = substr($url, strlen($fileProtocol)); $isFileProtocol = true; } if (false !== strpos($url, '%')) { $url = rawurldecode($url); } $urls[$index] = realpath($url); if ($isFileProtocol) { $urls[$index] = $fileProtocol . $urls[$index]; } } } return $urls; } } process->execute($command, $ignoredOutput)) { return \React\Promise\resolve(null); } $processError = 'Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput(); throw new \RuntimeException($processError); } } find('7z', null, ['C:\Program Files\7-Zip']))) { self::$unzipCommands[] = ['7z', $cmd, 'x', '-bb0', '-y', '%file%', '-o%path%']; } if (null !== ($cmd = $finder->find('unzip'))) { self::$unzipCommands[] = ['unzip', $cmd, '-qq', '%file%', '-d', '%path%']; } if (!Platform::isWindows() && null !== ($cmd = $finder->find('7z'))) { self::$unzipCommands[] = ['7z', $cmd, 'x', '-bb0', '-y', '%file%', '-o%path%']; } elseif (!Platform::isWindows() && null !== ($cmd = $finder->find('7zz'))) { self::$unzipCommands[] = ['7zz', $cmd, 'x', '-bb0', '-y', '%file%', '-o%path%']; } elseif (!Platform::isWindows() && null !== ($cmd = $finder->find('7za'))) { self::$unzipCommands[] = ['7za', $cmd, 'x', '-bb0', '-y', '%file%', '-o%path%']; } } $procOpenMissing = false; if (!function_exists('proc_open')) { self::$unzipCommands = []; $procOpenMissing = true; } if (null === self::$hasZipArchive) { self::$hasZipArchive = class_exists('ZipArchive'); } if (!self::$hasZipArchive && !self::$unzipCommands) { $iniMessage = IniHelper::getMessage(); if ($procOpenMissing) { $error = "The zip extension is missing and unzip/7z commands cannot be called as proc_open is disabled, skipping.\n" . $iniMessage; } else { $error = "The zip extension and unzip/7z commands are both missing, skipping.\n" . $iniMessage; } throw new \RuntimeException($error); } if (null === self::$isWindows) { self::$isWindows = Platform::isWindows(); if (!self::$isWindows && !self::$unzipCommands) { if ($procOpenMissing) { $this->io->writeError("proc_open is disabled so 'unzip' and '7z' commands cannot be used, zip files are being unpacked using the PHP zip extension."); $this->io->writeError("This may cause invalid reports of corrupted archives. Besides, any UNIX permissions (e.g. executable) defined in the archives will be lost."); $this->io->writeError("Enabling proc_open and installing 'unzip' or '7z' (21.01+) may remediate them."); } else { $this->io->writeError("As there is no 'unzip' nor '7z' command installed zip files are being unpacked using the PHP zip extension."); $this->io->writeError("This may cause invalid reports of corrupted archives. Besides, any UNIX permissions (e.g. executable) defined in the archives will be lost."); $this->io->writeError("Installing 'unzip' or '7z' (21.01+) may remediate them."); } } } return parent::download($package, $path, $prevPackage, $output); } private function extractWithSystemUnzip(PackageInterface $package, string $file, string $path): PromiseInterface { static $warned7ZipLinux = false; $isLastChance = !self::$hasZipArchive; if (0 === \count(self::$unzipCommands)) { return $this->extractWithZipArchive($package, $file, $path); } $commandSpec = reset(self::$unzipCommands); $executable = $commandSpec[0]; $command = array_slice($commandSpec, 1); $map = [ '%file%' => strtr($file, '/', DIRECTORY_SEPARATOR), '%path%' => strtr($path, '/', DIRECTORY_SEPARATOR), ]; $command = array_map(static function ($value) use ($map) { return strtr($value, $map); }, $command); if (!$warned7ZipLinux && !Platform::isWindows() && in_array($executable, ['7z', '7zz', '7za'], true)) { $warned7ZipLinux = true; if (0 === $this->process->execute([$commandSpec[1]], $output)) { if (Preg::isMatchStrictGroups('{^\s*7-Zip(?: \[64\])? ([0-9.]+)}', $output, $match) && version_compare($match[1], '21.01', '<')) { $this->io->writeError(' Unzipping using '.$executable.' '.$match[1].' may result in incorrect file permissions. Install '.$executable.' 21.01+ or unzip to ensure you get correct permissions.'); } } } $io = $this->io; $tryFallback = function (\Throwable $processError) use ($isLastChance, $io, $file, $path, $package, $executable): PromiseInterface { if ($isLastChance) { throw $processError; } if (str_contains($processError->getMessage(), 'zip bomb')) { throw $processError; } if (!is_file($file)) { $io->writeError(' '.$processError->getMessage().''); $io->writeError(' This most likely is due to a custom installer plugin not handling the returned Promise from the downloader'); $io->writeError(' See https://github.com/composer/installers/commit/5006d0c28730ade233a8f42ec31ac68fb1c5c9bb for an example fix'); } else { $io->writeError(' '.$processError->getMessage().''); $io->writeError(' The archive may contain identical file names with different capitalization (which fails on case insensitive filesystems)'); $io->writeError(' Unzip with '.$executable.' command failed, falling back to ZipArchive class'); if (Platform::getEnv('GITHUB_ACTIONS') !== false && Platform::getEnv('COMPOSER_TESTS_ARE_RUNNING') === false) { $io->writeError(' Additional debug info, please report to https://github.com/composer/composer/issues/11148 if you see this:'); $io->writeError('File size: '.@filesize($file)); $io->writeError('File SHA1: '.hash_file('sha1', $file)); $io->writeError('First 100 bytes (hex): '.bin2hex(substr((string) file_get_contents($file), 0, 100))); $io->writeError('Last 100 bytes (hex): '.bin2hex(substr((string) file_get_contents($file), -100))); if (strlen((string) $package->getDistUrl()) > 0) { $io->writeError('Origin URL: '.$this->processUrl($package, (string) $package->getDistUrl())); $io->writeError('Response Headers: '.json_encode(FileDownloader::$responseHeaders[$package->getName()] ?? [])); } } } return $this->extractWithZipArchive($package, $file, $path); }; try { $promise = $this->process->executeAsync($command); return $promise->then(function (Process $process) use ($tryFallback, $command, $package, $file) { if (!$process->isSuccessful()) { if (isset($this->cleanupExecuted[$package->getName()])) { throw new \RuntimeException('Failed to extract '.$package->getName().' as the installation was aborted by another package operation.'); } $output = $process->getErrorOutput(); $output = str_replace(', '.$file.'.zip or '.$file.'.ZIP', '', $output); return $tryFallback(new \RuntimeException('Failed to extract '.$package->getName().': ('.$process->getExitCode().') '.implode(' ', $command)."\n\n".$output)); } }); } catch (\Throwable $e) { return $tryFallback($e); } } private function extractWithZipArchive(PackageInterface $package, string $file, string $path): PromiseInterface { $processError = null; $zipArchive = $this->zipArchiveObject ?: new ZipArchive(); try { if (!file_exists($file) || ($filesize = filesize($file)) === false || $filesize === 0) { $retval = -1; } else { $retval = $zipArchive->open($file); } if (true === $retval) { $totalSize = 0; $archiveSize = filesize($file); $totalFiles = $zipArchive->count(); if ($totalFiles > 0) { $inspectAll = false; $filesToInspect = min($totalFiles, 5); for ($i = 0; $i < $filesToInspect; $i++) { $stat = $zipArchive->statIndex($inspectAll ? $i : random_int(0, $totalFiles - 1)); if ($stat === false) { continue; } $totalSize += $stat['size']; if (!$inspectAll && $stat['size'] > $stat['comp_size'] * 200) { $totalSize = 0; $inspectAll = true; $i = -1; $filesToInspect = $totalFiles; } } if ($archiveSize !== false && $totalSize > $archiveSize * 100 && $totalSize > 50 * 1024 * 1024) { throw new \RuntimeException('Invalid zip file for "'.$package->getName().'" with compression ratio >99% (possible zip bomb)'); } } $extractResult = $zipArchive->extractTo($path); if (true === $extractResult) { $zipArchive->close(); return \React\Promise\resolve(null); } $processError = new \RuntimeException(rtrim("There was an error extracting the ZIP file for \"{$package->getName()}\", it is either corrupted or using an invalid format.\n")); } else { $processError = new \UnexpectedValueException(rtrim($this->getErrorMessage($retval, $file)."\n"), $retval); } } catch (\ErrorException $e) { $processError = new \RuntimeException('The archive for "'.$package->getName().'" may contain identical file names with different capitalization (which fails on case insensitive filesystems): '.$e->getMessage(), 0, $e); } catch (\Throwable $e) { $processError = $e; } throw $processError; } protected function extract(PackageInterface $package, string $file, string $path): PromiseInterface { return $this->extractWithSystemUnzip($package, $file, $path); } protected function getErrorMessage(int $retval, string $file): string { switch ($retval) { case ZipArchive::ER_EXISTS: return sprintf("File '%s' already exists.", $file); case ZipArchive::ER_INCONS: return sprintf("Zip archive '%s' is inconsistent.", $file); case ZipArchive::ER_INVAL: return sprintf("Invalid argument (%s)", $file); case ZipArchive::ER_MEMORY: return sprintf("Malloc failure (%s)", $file); case ZipArchive::ER_NOENT: return sprintf("No such zip file: '%s'", $file); case ZipArchive::ER_NOZIP: return sprintf("'%s' is not a zip archive.", $file); case ZipArchive::ER_OPEN: return sprintf("Can't open zip file: %s", $file); case ZipArchive::ER_READ: return sprintf("Zip read error (%s)", $file); case ZipArchive::ER_SEEK: return sprintf("Zip seek error (%s)", $file); case -1: return sprintf("'%s' is a corrupted zip archive (0 bytes), try again.", $file); default: return sprintf("'%s' is not a valid zip archive, got error code: %s", $file, $retval); } } } name = $name; $this->args = $args; $this->flags = $flags; } public function getName(): string { return $this->name; } public function getArguments(): array { return $this->args; } public function getFlags(): array { return $this->flags; } public function isPropagationStopped(): bool { return $this->propagationStopped; } public function stopPropagation(): void { $this->propagationStopped = true; } } composer = $composer; $this->io = $io; $this->process = $process ?? new ProcessExecutor($io); $this->eventStack = []; $this->skipScripts = array_values(array_filter( array_map('trim', explode(',', (string) Platform::getEnv('COMPOSER_SKIP_SCRIPTS'))), static function ($val) { return $val !== ''; } )); } public function setRunScripts(bool $runScripts = true): self { $this->runScripts = $runScripts; return $this; } public function dispatch(?string $eventName, ?Event $event = null): int { if (null === $event) { if (null === $eventName) { throw new \InvalidArgumentException('If no $event is passed in to '.__METHOD__.' you have to pass in an $eventName, got null.'); } $event = new Event($eventName); } return $this->doDispatch($event); } public function dispatchScript(string $eventName, bool $devMode = false, array $additionalArgs = [], array $flags = []): int { assert($this->composer instanceof Composer, new \LogicException('This should only be reached with a fully loaded Composer')); return $this->doDispatch(new ScriptEvent($eventName, $this->composer, $this->io, $devMode, $additionalArgs, $flags)); } public function dispatchPackageEvent(string $eventName, bool $devMode, RepositoryInterface $localRepo, array $operations, OperationInterface $operation): int { assert($this->composer instanceof Composer, new \LogicException('This should only be reached with a fully loaded Composer')); return $this->doDispatch(new PackageEvent($eventName, $this->composer, $this->io, $devMode, $localRepo, $operations, $operation)); } public function dispatchInstallerEvent(string $eventName, bool $devMode, bool $executeOperations, Transaction $transaction): int { assert($this->composer instanceof Composer, new \LogicException('This should only be reached with a fully loaded Composer')); return $this->doDispatch(new InstallerEvent($eventName, $this->composer, $this->io, $devMode, $executeOperations, $transaction)); } protected function doDispatch(Event $event) { if (Platform::getEnv('COMPOSER_DEBUG_EVENTS')) { $details = null; if ($event instanceof PackageEvent) { $details = (string) $event->getOperation(); } elseif ($event instanceof CommandEvent) { $details = $event->getCommandName(); } elseif ($event instanceof PreCommandRunEvent) { $details = $event->getCommand(); } $this->io->writeError('Dispatching '.$event->getName().''.($details ? ' ('.$details.')' : '').' event'); } $listeners = $this->getListeners($event); $this->pushEvent($event); $autoloadersBefore = spl_autoload_functions(); try { $returnMax = 0; foreach ($listeners as $callable) { $return = 0; $this->ensureBinDirIsInPath(); $additionalArgs = $event->getArguments(); if (is_string($callable) && str_contains($callable, '@no_additional_args')) { $callable = Preg::replace('{ ?@no_additional_args}', '', $callable); $additionalArgs = []; } $formattedEventNameWithArgs = $event->getName() . ($additionalArgs !== [] ? ' (' . implode(', ', $additionalArgs) . ')' : ''); if (!is_string($callable)) { $this->makeAutoloader($event, $callable); if (!is_callable($callable)) { $className = is_object($callable[0]) ? get_class($callable[0]) : $callable[0]; throw new \RuntimeException('Subscriber '.$className.'::'.$callable[1].' for event '.$event->getName().' is not callable, make sure the function is defined and public'); } if (is_array($callable) && (is_string($callable[0]) || is_object($callable[0])) && is_string($callable[1])) { $this->io->writeError(sprintf('> %s: %s', $formattedEventNameWithArgs, (is_object($callable[0]) ? get_class($callable[0]) : $callable[0]).'->'.$callable[1]), true, IOInterface::VERBOSE); } $return = false === $callable($event) ? 1 : 0; } elseif ($this->isComposerScript($callable)) { $this->io->writeError(sprintf('> %s: %s', $formattedEventNameWithArgs, $callable), true, IOInterface::VERBOSE); $script = explode(' ', substr($callable, 1)); $scriptName = $script[0]; unset($script[0]); $index = array_search('@additional_args', $script, true); if ($index !== false) { $args = array_splice($script, $index, 0, $additionalArgs); } else { $args = array_merge($script, $additionalArgs); } $flags = $event->getFlags(); if (isset($flags['script-alias-input'])) { $argsString = implode(' ', array_map(static function ($arg) { return ProcessExecutor::escape($arg); }, $script)); $flags['script-alias-input'] = $argsString . ' ' . $flags['script-alias-input']; unset($argsString); } if (strpos($callable, '@composer ') === 0) { $exec = $this->getPhpExecCommand() . ' ' . ProcessExecutor::escape(Platform::getEnv('COMPOSER_BINARY')) . ' ' . implode(' ', $args); if (0 !== ($exitCode = $this->executeTty($exec))) { $this->io->writeError(sprintf('Script %s handling the %s event returned with error code '.$exitCode.'', $callable, $event->getName()), true, IOInterface::QUIET); throw new ScriptExecutionException('Error Output: '.$this->process->getErrorOutput(), $exitCode); } } else { if (!$this->getListeners(new Event($scriptName))) { $this->io->writeError(sprintf('You made a reference to a non-existent script %s', $callable), true, IOInterface::QUIET); } try { $scriptEvent = new ScriptEvent($scriptName, $event->getComposer(), $event->getIO(), $event->isDevMode(), $args, $flags); $scriptEvent->setOriginatingEvent($event); $return = $this->dispatch($scriptName, $scriptEvent); } catch (ScriptExecutionException $e) { $this->io->writeError(sprintf('Script %s was called via %s', $callable, $event->getName()), true, IOInterface::QUIET); throw $e; } } } elseif ($this->isPhpScript($callable)) { $className = substr($callable, 0, strpos($callable, '::')); $methodName = substr($callable, strpos($callable, '::') + 2); $this->makeAutoloader($event, $callable); if (!class_exists($className)) { $this->io->writeError('Class '.$className.' is not autoloadable, can not call '.$event->getName().' script', true, IOInterface::QUIET); continue; } if (!is_callable($callable)) { $this->io->writeError('Method '.$callable.' is not callable, can not call '.$event->getName().' script', true, IOInterface::QUIET); continue; } try { $return = false === $this->executeEventPhpScript($className, $methodName, $event) ? 1 : 0; } catch (\Exception $e) { $message = "Script %s handling the %s event terminated with an exception"; $this->io->writeError(''.sprintf($message, $callable, $event->getName()).'', true, IOInterface::QUIET); throw $e; } } elseif ($this->isCommandClass($callable)) { $className = $callable; $this->makeAutoloader($event, [$callable, 'run']); if (!class_exists($className)) { $this->io->writeError('Class '.$className.' is not autoloadable, can not call '.$event->getName().' script', true, IOInterface::QUIET); continue; } if (!is_a($className, Command::class, true)) { $this->io->writeError('Class '.$className.' does not extend '.Command::class.', can not call '.$event->getName().' script', true, IOInterface::QUIET); continue; } if (defined('Composer\Script\ScriptEvents::'.str_replace('-', '_', strtoupper($event->getName())))) { $this->io->writeError('You cannot bind '.$event->getName().' to a Command class, use a non-reserved name', true, IOInterface::QUIET); continue; } $app = new Application(); $app->setCatchExceptions(false); if (method_exists($app, 'setCatchErrors')) { $app->setCatchErrors(false); } $app->setAutoExit(false); $cmd = new $className($event->getName()); if (method_exists($app, 'addCommand')) { $app->addCommand($cmd); } else { $app->add($cmd); } $app->setDefaultCommand((string) $cmd->getName(), true); try { $args = implode(' ', array_map(static function ($arg) { return ProcessExecutor::escape($arg); }, $additionalArgs)); if ($this->io instanceof ConsoleIO) { $reflProp = new \ReflectionProperty($this->io, 'output'); if (\PHP_VERSION_ID < 80100) { $reflProp->setAccessible(true); } $output = $reflProp->getValue($this->io); } else { $output = new ConsoleOutput(); } $return = $app->run(new StringInput($event->getFlags()['script-alias-input'] ?? $args), $output); } catch (\Exception $e) { $message = "Script %s handling the %s event terminated with an exception"; $this->io->writeError(''.sprintf($message, $callable, $event->getName()).'', true, IOInterface::QUIET); throw $e; } } else { $args = implode(' ', array_map(['Composer\Util\ProcessExecutor', 'escape'], $additionalArgs)); if (strpos($callable, '@putenv ') === 0) { $exec = $callable; } else { if (str_contains($callable, '@additional_args')) { $exec = str_replace('@additional_args', $args, $callable); } else { $exec = $callable . ($args === '' ? '' : ' '.$args); } } if ($this->io->isVerbose()) { $this->io->writeError(sprintf('> %s: %s', $event->getName(), $exec)); } elseif ($this->eventNeedsToOutput($event)) { $this->io->writeError(sprintf('> %s', $exec)); } $possibleLocalBinaries = $this->composer->getPackage()->getBinaries(); if (count($possibleLocalBinaries) > 0) { foreach ($possibleLocalBinaries as $localExec) { if (Preg::isMatch('{\b'.preg_quote($callable).'$}', $localExec)) { $caller = BinaryInstaller::determineBinaryCaller($localExec); $exec = Preg::replace('{^'.preg_quote($callable).'}', $caller . ' ' . $localExec, $exec); break; } } } if (strpos($exec, '@putenv ') === 0) { if (false === strpos($exec, '=')) { Platform::clearEnv(substr($exec, 8)); } else { [$var, $value] = explode('=', substr($exec, 8), 2); Platform::putEnv($var, $value); } continue; } if (strpos($exec, '@php ') === 0) { $pathAndArgs = substr($exec, 5); if (Platform::isWindows()) { $pathAndArgs = Preg::replaceCallback('{^\S+}', static function ($path) { return str_replace('/', '\\', $path[0]); }, $pathAndArgs); } $matched = Preg::isMatchStrictGroups('{^[^\'"\s/\\\\]+}', $pathAndArgs, $match); if ($matched && !file_exists($match[0])) { $finder = new ExecutableFinder; if ($pathToExec = $finder->find($match[0])) { if (Platform::isWindows()) { $execWithoutExt = Preg::replace('{\.(exe|bat|cmd|com)$}i', '', $pathToExec); if (file_exists($execWithoutExt)) { $pathToExec = $execWithoutExt; } unset($execWithoutExt); } $pathAndArgs = $pathToExec . substr($pathAndArgs, strlen($match[0])); } } $exec = $this->getPhpExecCommand() . ' ' . $pathAndArgs; } else { $finder = new PhpExecutableFinder(); $phpPath = $finder->find(false); if ($phpPath) { Platform::putEnv('PHP_BINARY', $phpPath); } if (Platform::isWindows()) { $exec = Preg::replaceCallback('{^\S+}', static function ($path) { return str_replace('/', '\\', $path[0]); }, $exec); } } if (strpos($exec, 'composer ') === 0) { $exec = $this->getPhpExecCommand() . ' ' . ProcessExecutor::escape(Platform::getEnv('COMPOSER_BINARY')) . substr($exec, 8); } if (0 !== ($exitCode = $this->executeTty($exec))) { $this->io->writeError(sprintf('Script %s handling the %s event returned with error code '.$exitCode.'', $callable, $event->getName()), true, IOInterface::QUIET); throw new ScriptExecutionException('Error Output: '.$this->process->getErrorOutput(), $exitCode); } } $returnMax = max($returnMax, $return); if ($event->isPropagationStopped()) { break; } } } finally { $this->popEvent(); $knownIdentifiers = []; foreach ($autoloadersBefore as $key => $cb) { $knownIdentifiers[$this->getCallbackIdentifier($cb)] = ['key' => $key, 'callback' => $cb]; } foreach (spl_autoload_functions() as $cb) { if (isset($knownIdentifiers[$this->getCallbackIdentifier($cb)]) && $knownIdentifiers[$this->getCallbackIdentifier($cb)]['key'] === 0) { break; } if ($cb instanceof ClassLoader) { $cb->unregister(); $cb->register(false); } else { spl_autoload_unregister($cb); spl_autoload_register($cb); } } } return $returnMax; } protected function executeTty(string $exec): int { if ($this->io->isInteractive()) { return $this->process->executeTty($exec); } return $this->process->execute($exec); } protected function getPhpExecCommand(): string { $finder = new PhpExecutableFinder(); $phpPath = $finder->find(false); if (!$phpPath) { throw new \RuntimeException('Failed to locate PHP binary to execute '.$phpPath); } $phpArgs = $finder->findArguments(); $phpArgs = \count($phpArgs) > 0 ? ' ' . implode(' ', $phpArgs) : ''; $allowUrlFOpenFlag = ' -d allow_url_fopen=' . ProcessExecutor::escape(ini_get('allow_url_fopen')); $disableFunctionsFlag = ' -d disable_functions=' . ProcessExecutor::escape(ini_get('disable_functions')); $memoryLimitFlag = ' -d memory_limit=' . ProcessExecutor::escape(ini_get('memory_limit')); return ProcessExecutor::escape($phpPath) . $phpArgs . $allowUrlFOpenFlag . $disableFunctionsFlag . $memoryLimitFlag; } protected function executeEventPhpScript(string $className, string $methodName, Event $event) { if ($this->io->isVerbose()) { $this->io->writeError(sprintf('> %s: %s::%s', $event->getName(), $className, $methodName)); } elseif ($this->eventNeedsToOutput($event)) { $this->io->writeError(sprintf('> %s::%s', $className, $methodName)); } return $className::$methodName($event); } private function eventNeedsToOutput(Event $event): bool { if ($event->getName() === '__exec_command') { return false; } if (($event->getFlags()['script-alias-input'] ?? null) !== null) { return false; } return true; } public function addListener(string $eventName, $listener, int $priority = 0): void { $this->listeners[$eventName][$priority][] = $listener; } public function removeListener($listener): void { foreach ($this->listeners as $eventName => $priorities) { foreach ($priorities as $priority => $listeners) { foreach ($listeners as $index => $candidate) { if ($listener === $candidate || (is_array($candidate) && is_object($listener) && $candidate[0] === $listener)) { unset($this->listeners[$eventName][$priority][$index]); } } } } } public function addSubscriber(EventSubscriberInterface $subscriber): void { foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { if (is_string($params)) { $this->addListener($eventName, [$subscriber, $params]); } elseif (is_string($params[0])) { $this->addListener($eventName, [$subscriber, $params[0]], $params[1] ?? 0); } else { foreach ($params as $listener) { $this->addListener($eventName, [$subscriber, $listener[0]], $listener[1] ?? 0); } } } } protected function getListeners(Event $event): array { $scriptListeners = $this->runScripts ? $this->getScriptListeners($event) : []; if (!isset($this->listeners[$event->getName()][0])) { $this->listeners[$event->getName()][0] = []; } krsort($this->listeners[$event->getName()]); $listeners = $this->listeners; $listeners[$event->getName()][0] = array_merge($listeners[$event->getName()][0], $scriptListeners); return array_merge(...$listeners[$event->getName()]); } public function hasEventListeners(Event $event): bool { $listeners = $this->getListeners($event); return count($listeners) > 0; } protected function getScriptListeners(Event $event): array { $package = $this->composer->getPackage(); $scripts = $package->getScripts(); if (empty($scripts[$event->getName()])) { return []; } if (in_array($event->getName(), $this->skipScripts, true)) { $this->io->writeError('Skipped script listeners for '.$event->getName().' because of COMPOSER_SKIP_SCRIPTS', true, IOInterface::VERBOSE); return []; } return $scripts[$event->getName()]; } protected function isPhpScript(string $callable): bool { return false === strpos($callable, ' ') && false !== strpos($callable, '::'); } protected function isCommandClass(string $callable): bool { return str_contains($callable, '\\') && !str_contains($callable, ' ') && str_ends_with($callable, 'Command'); } protected function isComposerScript(string $callable): bool { return str_starts_with($callable, '@') && !str_starts_with($callable, '@php ') && !str_starts_with($callable, '@putenv '); } protected function pushEvent(Event $event): int { $eventName = $event->getName(); if (in_array($eventName, $this->eventStack)) { throw new \RuntimeException(sprintf("Circular call to script handler '%s' detected", $eventName)); } return array_push($this->eventStack, $eventName); } protected function popEvent(): ?string { return array_pop($this->eventStack); } private function ensureBinDirIsInPath(): void { $pathEnv = 'PATH'; if (!isset($_SERVER[$pathEnv]) && isset($_SERVER['Path'])) { $pathEnv = 'Path'; } $binDir = $this->composer->getConfig()->get('bin-dir'); if (is_dir($binDir)) { $binDir = realpath($binDir); $pathValue = (string) Platform::getEnv($pathEnv); if (!Preg::isMatch('{(^|'.PATH_SEPARATOR.')'.preg_quote($binDir).'($|'.PATH_SEPARATOR.')}', $pathValue)) { Platform::putEnv($pathEnv, $binDir.PATH_SEPARATOR.$pathValue); } } } private function getCallbackIdentifier($cb): string { if (is_string($cb)) { return 'fn:'.$cb; } if (is_object($cb)) { return 'obj:'.spl_object_hash($cb); } if (is_array($cb)) { return 'array:'.(is_string($cb[0]) ? $cb[0] : get_class($cb[0]) .'#'.spl_object_hash($cb[0])).'::'.$cb[1]; } return 'unsupported'; } private function makeAutoloader(Event $event, $callable): void { if (!$this->composer instanceof Composer) { return; } if (is_array($callable)) { if (is_string($callable[0])) { $callableKey = $callable[0] . '::' . $callable[1]; } else { $callableKey = get_class($callable[0]).'::'.$callable[1]; } } elseif (is_string($callable)) { $callableKey = $callable; } elseif ($callable instanceof \Closure) { $callableKey = 'closure'; } else { $callableKey = 'unknown'; } if (isset($this->previousListeners[$callableKey])) { return; } $this->previousListeners[$callableKey] = true; $package = $this->composer->getPackage(); $packages = $this->composer->getRepositoryManager()->getLocalRepository()->getCanonicalPackages(); $generator = $this->composer->getAutoloadGenerator(); $hash = implode(',', array_map(static function (PackageInterface $p) { return $p->getName().'/'.$p->getVersion(); }, $packages)); if ($event instanceof ScriptEvent || $event instanceof PackageEvent || $event instanceof InstallerEvent) { $generator->setDevMode($event->isDevMode()); $hash .= $event->isDevMode() ? '/dev' : ''; } $hash = hash('sha256', $hash); if ($this->previousHash === $hash) { return; } $this->previousHash = $hash; $packageMap = $generator->buildPackageMap($this->composer->getInstallationManager(), $package, $packages); $map = $generator->parseAutoloads($packageMap, $package); if ($this->loader !== null) { $this->loader->unregister(); } $this->loader = $generator->createLoader($map, $this->composer->getConfig()->get('vendor-dir')); $this->loader->register(false); } } merge([ 'config' => [ 'home' => $home, 'cache-dir' => self::getCacheDir($home), 'data-dir' => self::getDataDir($home), ], ], Config::SOURCE_DEFAULT); $file = new JsonFile($config->get('home').'/config.json'); if ($file->exists()) { if ($io instanceof IOInterface) { $io->writeError('Loading config file ' . $file->getPath(), true, IOInterface::DEBUG); } self::validateJsonSchema($io, $file); $config->merge($file->read(), $file->getPath()); } $config->setConfigSource(new JsonConfigSource($file)); $htaccessProtect = $config->get('htaccess-protect'); if ($htaccessProtect) { $dirs = [$config->get('home'), $config->get('cache-dir'), $config->get('data-dir')]; foreach ($dirs as $dir) { if (!file_exists($dir . '/.htaccess')) { if (!is_dir($dir)) { Silencer::call('mkdir', $dir, 0777, true); } Silencer::call('file_put_contents', $dir . '/.htaccess', 'Deny from all'); } } } $file = new JsonFile($config->get('home').'/auth.json'); if ($file->exists()) { if ($io instanceof IOInterface) { $io->writeError('Loading config file ' . $file->getPath(), true, IOInterface::DEBUG); } self::validateJsonSchema($io, $file, JsonFile::AUTH_SCHEMA); $config->merge(['config' => $file->read()], $file->getPath()); } $config->setAuthConfigSource(new JsonConfigSource($file, true)); self::loadComposerAuthEnv($config, $io); return $config; } public static function getComposerFile(): string { $env = Platform::getEnv('COMPOSER'); if (is_string($env)) { $env = trim($env); if ('' !== $env) { if (is_dir($env)) { throw new \RuntimeException('The COMPOSER environment variable is set to '.$env.' which is a directory, this variable should point to a composer.json or be left unset.'); } return $env; } } return './composer.json'; } public static function getLockFile(string $composerFile): string { return "json" === pathinfo($composerFile, PATHINFO_EXTENSION) ? substr($composerFile, 0, -4).'lock' : $composerFile . '.lock'; } public static function createAdditionalStyles(): array { return [ 'highlight' => new OutputFormatterStyle('red'), 'warning' => new OutputFormatterStyle('black', 'yellow'), ]; } public static function createOutput(): ConsoleOutput { $styles = self::createAdditionalStyles(); $formatter = new OutputFormatter(false, $styles); return new ConsoleOutput(ConsoleOutput::VERBOSITY_NORMAL, null, $formatter); } public function createComposer(IOInterface $io, $localConfig = null, $disablePlugins = false, ?string $cwd = null, bool $fullLoad = true, bool $disableScripts = false) { if (is_string($localConfig) && is_file($localConfig) && null === $cwd) { $cwd = dirname($localConfig); } $cwd = $cwd ?? Platform::getCwd(true); if (null === $localConfig) { $localConfig = static::getComposerFile(); } $localConfigSource = Config::SOURCE_UNKNOWN; if (is_string($localConfig)) { $composerFile = $localConfig; $file = new JsonFile($localConfig, null, $io); if (!$file->exists()) { if ($localConfig === './composer.json' || $localConfig === 'composer.json') { $message = 'Composer could not find a composer.json file in '.$cwd; } else { $message = 'Composer could not find the config file: '.$localConfig; } $instructions = $fullLoad ? 'To initialize a project, please create a composer.json file. See https://getcomposer.org/basic-usage' : ''; throw new \InvalidArgumentException($message.PHP_EOL.$instructions); } if (!Platform::isInputCompletionProcess()) { try { $file->validateSchema(JsonFile::LAX_SCHEMA); } catch (JsonValidationException $e) { $errors = ' - ' . implode(PHP_EOL . ' - ', $e->getErrors()); $message = $e->getMessage() . ':' . PHP_EOL . $errors; throw new JsonValidationException($message); } } $localConfig = $file->read(); $localConfigSource = $file->getPath(); } $config = static::createConfig($io, $cwd); $isGlobal = $localConfigSource !== Config::SOURCE_UNKNOWN && realpath($config->get('home')) === realpath(dirname($localConfigSource)); $config->merge($localConfig, $localConfigSource); if (isset($composerFile)) { $io->writeError('Loading config file ' . $composerFile .' ('.realpath($composerFile).')', true, IOInterface::DEBUG); $config->setConfigSource(new JsonConfigSource(new JsonFile(realpath($composerFile), null, $io))); $localAuthFile = new JsonFile(dirname(realpath($composerFile)) . '/auth.json', null, $io); if ($localAuthFile->exists()) { $io->writeError('Loading config file ' . $localAuthFile->getPath(), true, IOInterface::DEBUG); self::validateJsonSchema($io, $localAuthFile, JsonFile::AUTH_SCHEMA); $config->merge(['config' => $localAuthFile->read()], $localAuthFile->getPath()); $config->setLocalAuthConfigSource(new JsonConfigSource($localAuthFile, true)); } } self::loadComposerAuthEnv($config, $io); $vendorDir = $config->get('vendor-dir'); $composer = $fullLoad ? new Composer() : new PartialComposer(); $composer->setConfig($config); if ($isGlobal) { $composer->setGlobal(); } if ($fullLoad) { $io->loadConfiguration($config); if (false === $disablePlugins && false === $disableScripts && !class_exists('Composer\InstalledVersions', false) && file_exists($installedVersionsPath = $config->get('vendor-dir').'/composer/installed.php')) { if (class_exists('Composer\InstalledVersions')) { FilesystemRepository::safelyLoadInstalledVersions($installedVersionsPath); } } } $httpDownloader = self::createHttpDownloader($io, $config); $process = new ProcessExecutor($io); $loop = new Loop($httpDownloader, $process); $composer->setLoop($loop); $dispatcher = new EventDispatcher($composer, $io, $process); $dispatcher->setRunScripts(!$disableScripts); $composer->setEventDispatcher($dispatcher); $rm = RepositoryFactory::manager($io, $config, $httpDownloader, $dispatcher, $process); $composer->setRepositoryManager($rm); if (!$fullLoad && !isset($localConfig['version'])) { $localConfig['version'] = '1.0.0'; } $parser = new VersionParser; $guesser = new VersionGuesser($config, $process, $parser, $io); $loader = $this->loadRootPackage($rm, $config, $parser, $guesser, $io); $package = $loader->load($localConfig, 'Composer\Package\RootPackage', $cwd); $composer->setPackage($package); $this->addLocalRepository($io, $rm, $vendorDir, $package, $process); $im = $this->createInstallationManager($loop, $io, $dispatcher); $composer->setInstallationManager($im); if ($composer instanceof Composer) { $dm = $this->createDownloadManager($io, $config, $httpDownloader, $process, $dispatcher); $composer->setDownloadManager($dm); $generator = new AutoloadGenerator($dispatcher, $io); $composer->setAutoloadGenerator($generator); $am = $this->createArchiveManager($config, $dm, $loop); $composer->setArchiveManager($am); } $this->createDefaultInstallers($im, $composer, $io, $process); if ($composer instanceof Composer && isset($composerFile)) { $lockFile = self::getLockFile($composerFile); if (!$config->get('lock') && file_exists($lockFile)) { $io->writeError(''.$lockFile.' is present but ignored as the "lock" config option is disabled.'); } $locker = new Package\Locker($io, new JsonFile($config->get('lock') ? $lockFile : Platform::getDevNull(), null, $io), $im, file_get_contents($composerFile), $process); $composer->setLocker($locker); } elseif ($composer instanceof Composer) { $locker = new Package\Locker($io, new JsonFile(Platform::getDevNull(), null, $io), $im, JsonFile::encode($localConfig), $process); $composer->setLocker($locker); } if ($composer instanceof Composer) { $globalComposer = null; if (!$composer->isGlobal()) { $globalComposer = $this->createGlobalComposer($io, $config, $disablePlugins, $disableScripts); } $pm = $this->createPluginManager($io, $composer, $globalComposer, $disablePlugins); $composer->setPluginManager($pm); if ($composer->isGlobal()) { $pm->setRunningInGlobalDir(true); } $pm->loadInstalledPlugins(); } if ($fullLoad) { $initEvent = new Event(PluginEvents::INIT); $composer->getEventDispatcher()->dispatch($initEvent->getName(), $initEvent); $this->purgePackages($rm->getLocalRepository(), $im); } return $composer; } public static function createGlobal(IOInterface $io, bool $disablePlugins = false, bool $disableScripts = false): ?Composer { $factory = new static(); return $factory->createGlobalComposer($io, static::createConfig($io), $disablePlugins, $disableScripts, true); } protected function addLocalRepository(IOInterface $io, RepositoryManager $rm, string $vendorDir, RootPackageInterface $rootPackage, ?ProcessExecutor $process = null): void { $fs = null; if ($process) { $fs = new Filesystem($process); } $rm->setLocalRepository(new Repository\InstalledFilesystemRepository(new JsonFile($vendorDir.'/composer/installed.json', null, $io), true, $rootPackage, $fs)); } protected function createGlobalComposer(IOInterface $io, Config $config, $disablePlugins, bool $disableScripts, bool $fullLoad = false): ?PartialComposer { $disablePlugins = $disablePlugins === 'global' || $disablePlugins === true; $composer = null; try { $composer = $this->createComposer($io, $config->get('home') . '/composer.json', $disablePlugins, $config->get('home'), $fullLoad, $disableScripts); } catch (\Exception $e) { $io->writeError('Failed to initialize global composer: '.$e->getMessage(), true, IOInterface::DEBUG); } return $composer; } public function createDownloadManager(IOInterface $io, Config $config, HttpDownloader $httpDownloader, ProcessExecutor $process, ?EventDispatcher $eventDispatcher = null): Downloader\DownloadManager { $cache = null; if ($config->get('cache-files-ttl') > 0) { $cache = new Cache($io, $config->get('cache-files-dir'), 'a-z0-9_./'); $cache->setReadOnly($config->get('cache-read-only')); } $fs = new Filesystem($process); $dm = new Downloader\DownloadManager($io, false, $fs); switch ($preferred = $config->get('preferred-install')) { case 'dist': $dm->setPreferDist(true); break; case 'source': $dm->setPreferSource(true); break; case 'auto': default: break; } if (is_array($preferred)) { $dm->setPreferences($preferred); } if ($config->get('source-fallback')) { $dm->setSourceFallback(true); } $dm->setDownloader('git', new Downloader\GitDownloader($io, $config, $process, $fs)); $dm->setDownloader('svn', new Downloader\SvnDownloader($io, $config, $process, $fs)); $dm->setDownloader('fossil', new Downloader\FossilDownloader($io, $config, $process, $fs)); $dm->setDownloader('hg', new Downloader\HgDownloader($io, $config, $process, $fs)); $dm->setDownloader('perforce', new Downloader\PerforceDownloader($io, $config, $process, $fs)); $dm->setDownloader('zip', new Downloader\ZipDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process)); $dm->setDownloader('rar', new Downloader\RarDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process)); $dm->setDownloader('tar', new Downloader\TarDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process)); $dm->setDownloader('gzip', new Downloader\GzipDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process)); $dm->setDownloader('xz', new Downloader\XzDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process)); $dm->setDownloader('phar', new Downloader\PharDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process)); $dm->setDownloader('file', new Downloader\FileDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process)); $dm->setDownloader('path', new Downloader\PathDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process)); return $dm; } public function createArchiveManager(Config $config, Downloader\DownloadManager $dm, Loop $loop) { $am = new Archiver\ArchiveManager($dm, $loop); if (class_exists(ZipArchive::class)) { $am->addArchiver(new Archiver\ZipArchiver); } if (class_exists(Phar::class)) { $am->addArchiver(new Archiver\PharArchiver); } return $am; } protected function createPluginManager(IOInterface $io, Composer $composer, ?PartialComposer $globalComposer = null, $disablePlugins = false): Plugin\PluginManager { return new Plugin\PluginManager($io, $composer, $globalComposer, $disablePlugins); } public function createInstallationManager(Loop $loop, IOInterface $io, ?EventDispatcher $eventDispatcher = null): Installer\InstallationManager { return new Installer\InstallationManager($loop, $io, $eventDispatcher); } protected function createDefaultInstallers(Installer\InstallationManager $im, PartialComposer $composer, IOInterface $io, ?ProcessExecutor $process = null): void { $fs = new Filesystem($process); $binaryInstaller = new Installer\BinaryInstaller($io, rtrim($composer->getConfig()->get('bin-dir'), '/'), $composer->getConfig()->get('bin-compat'), $fs, rtrim($composer->getConfig()->get('vendor-dir'), '/')); $im->addInstaller(new Installer\LibraryInstaller($io, $composer, null, $fs, $binaryInstaller)); $im->addInstaller(new Installer\PluginInstaller($io, $composer, $fs, $binaryInstaller)); $im->addInstaller(new Installer\MetapackageInstaller($io)); } protected function purgePackages(InstalledRepositoryInterface $repo, Installer\InstallationManager $im): void { foreach ($repo->getPackages() as $package) { if (!$im->isPackageInstalled($repo, $package)) { $repo->removePackage($package); } } } protected function loadRootPackage(RepositoryManager $rm, Config $config, VersionParser $parser, VersionGuesser $guesser, IOInterface $io): Package\Loader\RootPackageLoader { return new Package\Loader\RootPackageLoader($rm, $config, $parser, $guesser, $io); } public static function create(IOInterface $io, $config = null, $disablePlugins = false, bool $disableScripts = false): Composer { $factory = new static(); if ($config !== null && $config !== self::getComposerFile() && $disablePlugins === false) { $disablePlugins = 'local'; } return $factory->createComposer($io, $config, $disablePlugins, null, true, $disableScripts); } public static function createHttpDownloader(IOInterface $io, Config $config, array $options = []): HttpDownloader { static $warned = false; $disableTls = false; if (isset($_SERVER['argv']) && in_array('disable-tls', $_SERVER['argv']) && (in_array('conf', $_SERVER['argv']) || in_array('config', $_SERVER['argv']))) { $warned = true; $disableTls = !extension_loaded('openssl'); } elseif ($config->get('disable-tls') === true) { if (!$warned) { $io->writeError('You are running Composer with SSL/TLS protection disabled.'); } $warned = true; $disableTls = true; } elseif (!extension_loaded('openssl')) { throw new Exception\NoSslException('The openssl extension is required for SSL/TLS protection but is not available. ' . 'If you can not enable the openssl extension, you can disable this error, at your own risk, by setting the \'disable-tls\' option to true.'); } $httpDownloaderOptions = []; if ($disableTls === false) { if ('' !== $config->get('cafile')) { $httpDownloaderOptions['ssl']['cafile'] = $config->get('cafile'); } if ('' !== $config->get('capath')) { $httpDownloaderOptions['ssl']['capath'] = $config->get('capath'); } $httpDownloaderOptions = array_replace_recursive($httpDownloaderOptions, $options); } try { $httpDownloader = new HttpDownloader($io, $config, $httpDownloaderOptions, $disableTls); } catch (TransportException $e) { if (false !== strpos($e->getMessage(), 'cafile')) { $io->write('Unable to locate a valid CA certificate file. You must set a valid \'cafile\' option.'); $io->write('A valid CA certificate file is required for SSL/TLS protection.'); $io->write('You can disable this error, at your own risk, by setting the \'disable-tls\' option to true.'); } throw $e; } return $httpDownloader; } private static function loadComposerAuthEnv(Config $config, ?IOInterface $io): void { $composerAuthEnv = Platform::getEnv('COMPOSER_AUTH'); if (false === $composerAuthEnv || '' === $composerAuthEnv) { return; } $authData = json_decode($composerAuthEnv); if (null === $authData) { throw new UnexpectedValueException('COMPOSER_AUTH environment variable is malformed, should be a valid JSON object'); } if ($io instanceof IOInterface) { $io->writeError('Loading auth config from COMPOSER_AUTH', true, IOInterface::DEBUG); } self::validateJsonSchema($io, $authData, JsonFile::AUTH_SCHEMA, 'COMPOSER_AUTH'); $authData = json_decode($composerAuthEnv, true); if (null !== $authData) { $config->merge(['config' => $authData], 'COMPOSER_AUTH'); } } private static function useXdg(): bool { foreach (array_keys($_SERVER) as $key) { if (strpos((string) $key, 'XDG_') === 0) { return true; } } if (Silencer::call('is_dir', '/etc/xdg')) { return true; } return false; } private static function getUserDir(): string { $home = Platform::getEnv('HOME'); if (!$home) { throw new \RuntimeException('The HOME or COMPOSER_HOME environment variable must be set for composer to run correctly'); } return rtrim(strtr($home, '\\', '/'), '/'); } private static function validateJsonSchema(?IOInterface $io, $fileOrData, int $schema = JsonFile::LAX_SCHEMA, ?string $source = null): void { if (Platform::isInputCompletionProcess()) { return; } try { if ($fileOrData instanceof JsonFile) { $fileOrData->validateSchema($schema); } else { if (null === $source) { throw new \InvalidArgumentException('$source is required to be provided if $fileOrData is arbitrary data'); } JsonFile::validateJsonSchema($source, $fileOrData, $schema); } } catch (JsonValidationException $e) { $msg = $e->getMessage().', this may result in errors and should be resolved:'.PHP_EOL.' - '.implode(PHP_EOL.' - ', $e->getErrors()); if ($io instanceof IOInterface) { $io->writeError(''.$msg.''); } else { throw new UnexpectedValueException($msg); } } } } isIgnored($req); } } ignoreRegex = BasePackage::packageNamesToRegexp($ignoreAll); $this->ignoreUpperBoundRegex = BasePackage::packageNamesToRegexp($ignoreUpperBound); } public function isIgnored(string $req): bool { if (!PlatformRepository::isPlatformPackage($req)) { return false; } return Preg::isMatch($this->ignoreRegex, $req); } public function isUpperBoundIgnored(string $req): bool { if (!PlatformRepository::isPlatformPackage($req)) { return false; } return $this->isIgnored($req) || Preg::isMatch($this->ignoreUpperBoundRegex, $req); } public function filterConstraint(string $req, ConstraintInterface $constraint, bool $allowUpperBoundOverride = true): ConstraintInterface { if (!PlatformRepository::isPlatformPackage($req)) { return $constraint; } if (!$allowUpperBoundOverride || !Preg::isMatch($this->ignoreUpperBoundRegex, $req)) { return $constraint; } if (Preg::isMatch($this->ignoreRegex, $req)) { return new MatchAllConstraint; } $intervals = Intervals::get($constraint); $last = end($intervals['numeric']); if ($last !== false && (string) $last->getEnd() !== (string) Interval::untilPositiveInfinity()) { $constraint = new MultiConstraint([$constraint, new Constraint('>=', $last->getEnd()->getVersion())], false); } return $constraint; } } metadata = $metadata; $this->lists = $lists; $this->summaryUrl = $summaryUrl; $this->apiUrl = $apiUrl; } public static function fromData(array $data, ?\Closure $canonicalizeUrl = null): self { $lists = []; if (isset($data['lists']) && is_array($data['lists'])) { foreach ($data['lists'] as $name => $config) { if (!is_string($name) || !is_array($config) || !(bool) ($config['enabled'] ?? false)) { continue; } $lists[] = $name; } } $lists = array_values(array_filter($lists, static function (string $name): bool { if (in_array($name, PolicyConfig::RESERVED_NAMES, true) || in_array($name, PolicyConfig::FUTURE_RESERVED_NAMES, true)) { return false; } foreach (PolicyConfig::FUTURE_RESERVED_PREFIXES as $prefix) { if (str_starts_with($name, $prefix)) { return false; } } return true; })); $summaryUrl = null; if (isset($data['summary-url']) && is_string($data['summary-url']) && $data['summary-url'] !== '') { $summaryUrl = $canonicalizeUrl !== null ? $canonicalizeUrl($data['summary-url']) : $data['summary-url']; } $apiUrl = null; if (isset($data['api-url']) && is_string($data['api-url']) && $data['api-url'] !== '') { $apiUrl = $canonicalizeUrl !== null ? $canonicalizeUrl($data['api-url']) : $data['api-url']; } return new self( (bool) ($data['metadata'] ?? false), $lists, $summaryUrl, $apiUrl ); } } httpDownloader = $httpDownloader; } public function postPurls(string $url, array $packageConstraintMap, array $configuredLists): Response { $purls = array_map(static function (string $packageName): string { return 'pkg://composer/' . $packageName; }, array_keys($packageConstraintMap)); $body = [ 'packages' => $purls, 'lists' => $configuredLists, ]; $options = []; $options['http']['method'] = 'POST'; $options['http']['header'][] = 'Content-type: application/json'; $options['http']['timeout'] = 10; $options['http']['content'] = json_encode($body); return $this->httpDownloader->get($url, $options); } } getMatchingFilterLists($packages, $configuredLists, $ignoreUnreachable); $filter = $result['filter']; $unreachableRepos = $result['unreachableRepos']; $filterListMap = []; foreach ($filter as $entries) { foreach ($entries as $entry) { $filterListMap[$entry->packageName][$entry->listName][] = $entry; } } ksort($filterListMap); return ['filter' => $filterListMap, 'unreachableRepos' => $unreachableRepos]; } public function getMatchingAuditEntries(PackageInterface $package, array $filterListMap, PolicyConfig $policyConfig): array { return $this->matchingEntries($package, $filterListMap, $policyConfig->getActiveAuditFilterLists(), 'audit'); } public function getMatchingBlockEntries(PackageInterface $package, array $filterListMap, PolicyConfig $policyConfig, string $blockScope): array { return $this->matchingEntries($package, $filterListMap, $policyConfig->getActiveBlockFilterLists($blockScope), 'block'); } private function matchingEntries(PackageInterface $package, array $filterListMap, array $activeListConfigs, string $operation): array { if ($package instanceof RootPackageInterface || count($filterListMap) === 0) { return []; } if (isset($activeListConfigs[MalwarePolicyConfig::NAME]) && $activeListConfigs[MalwarePolicyConfig::NAME] instanceof MalwarePolicyConfig) { $filterListMap = $this->applyMalwareIgnoreSource($filterListMap, $activeListConfigs[MalwarePolicyConfig::NAME], $operation); } $matchingEntries = []; $allPackageNames = []; foreach ($activeListConfigs as $activeListConfig) { $allPackageNames = array_merge($allPackageNames, array_keys($activeListConfig->getIgnoreForOperation($operation))); } $allIgnoredPackageNamesRegex = BasePackage::packageNamesToRegexp($allPackageNames); $packageConstraint = new Constraint(Constraint::STR_OP_EQ, $package->getVersion()); foreach ($package->getNames(false) as $packageName) { if (!isset($filterListMap[$packageName])) { continue; } $packageEntries = array_intersect_key($filterListMap[$packageName], $activeListConfigs); if (Preg::isMatch($allIgnoredPackageNamesRegex, $packageName)) { $unfilteredEntries = []; foreach ($packageEntries as $listName => $entries) { if ($this->isPackageIgnored($packageName, $packageConstraint, $activeListConfigs[$listName], $operation)) { continue; } $unfilteredEntries[$listName] = $entries; } $packageEntries = $unfilteredEntries; } foreach ($packageEntries as $entries) { foreach ($entries as $entry) { if ($entry->constraint->matches($packageConstraint)) { $matchingEntries[] = $entry; } } } } return $matchingEntries; } private function isPackageIgnored(string $packageName, ConstraintInterface $packageConstraint, ListPolicyConfig $listConfig, string $operation): bool { foreach ($listConfig->getIgnoreForOperation($operation) as $ignorePackageRules) { foreach ($ignorePackageRules as $ignorePackageRule) { if (Preg::isMatch($ignorePackageRule->packageNameRegex, $packageName) && $ignorePackageRule->constraint->matches($packageConstraint)) { return true; } } } return false; } private function applyMalwareIgnoreSource(array $filterListMap, MalwarePolicyConfig $malwarePolicyConfig, string $operation): array { $ignoreSource = $malwarePolicyConfig->ignoreSource; if (count($ignoreSource) === 0) { return $filterListMap; } foreach ($filterListMap as $packageName => $entries) { if (!isset($entries[MalwarePolicyConfig::NAME])) { continue; } $packageEntries = []; foreach ($entries[MalwarePolicyConfig::NAME] as $malwareEntry) { if (!in_array($malwareEntry->source, $ignoreSource, true)) { $packageEntries[] = $malwareEntry; } } if (count($packageEntries) > 0) { $filterListMap[$packageName][MalwarePolicyConfig::NAME] = $packageEntries; } else { unset($filterListMap[$packageName][MalwarePolicyConfig::NAME]); if (count($filterListMap[$packageName]) === 0) { unset($filterListMap[$packageName]); } } } return $filterListMap; } } packageName = $packageName; $this->listName = $listName; $this->constraint = $constraint; $this->url = $url; $this->reason = $reason; $this->id = $id; $this->source = $source; } public static function create(string $listName, array $data, VersionParser $parser): self { $constraint = $parser->parseConstraints($data['constraint']); return new self( $data['package'], $constraint, $listName, $data['url'] ?? null, $data['reason'] ?? null, $data['id'] ?? null, $data['source'] ?? null ); } } versionParser = $versionParser ?? new VersionParser(); } public function build(array $rawByList, array $packageConstraintMap, ?string $defaultPackage = null): array { $result = []; foreach ($rawByList as $listName => $entries) { if (!is_string($listName) || !is_array($entries)) { continue; } foreach ($entries as $data) { if (!is_array($data)) { continue; } if (!isset($data['constraint'])) { continue; } if (!isset($data['package'])) { if ($defaultPackage === null) { continue; } $data['package'] = $defaultPackage; } $entry = FilterListEntry::create($listName, $data, $this->versionParser); if (!isset($packageConstraintMap[$entry->packageName])) { continue; } if (!$entry->constraint->matches($packageConstraintMap[$entry->packageName])) { continue; } $result[$listName][] = $entry; } } return $result; } } hasFilter()) { $providers[] = $repository; } } catch (\Composer\Downloader\TransportException $e) { $unreachableRepoExceptions[] = $e; } } $this->providers = $providers; $this->unreachableRepoExceptions = $unreachableRepoExceptions; } public static function create(PolicyConfig $config, array $repositories, HttpDownloader $httpDownloader): self { $sources = []; foreach ($config->getCustomListsWithSources() as $listConfig) { $sources = array_merge($sources, $listConfig->sources); } return new FilterListProviderSet( array_values($repositories), array_map( static function (UrlSource $source) use ($httpDownloader) { return new UrlSourceFilterListProvider($httpDownloader, $source); }, $sources ) ); } public function getMatchingFilterLists(array $packages, array $configuredLists, bool $ignoreUnreachable = false): array { $map = []; foreach ($packages as $package) { if ($package instanceof AliasPackage && $package->isRootPackageAlias()) { continue; } if (isset($map[$package->getName()])) { $map[$package->getName()] = new MultiConstraint([new Constraint('=', $package->getVersion()), $map[$package->getName()]], false); } else { $map[$package->getName()] = new Constraint('=', $package->getVersion()); } } $unreachableRepos = []; $filters = $this->getFilterListEntriesForConstraints($map, $configuredLists, $ignoreUnreachable, $unreachableRepos); return ['filter' => $filters, 'unreachableRepos' => $unreachableRepos]; } private function getFilterListEntriesForConstraints(array $packageConstraintMap, array $configuredLists, bool $ignoreUnreachable = false, array &$unreachableRepos = []): array { foreach ($this->unreachableRepoExceptions as $e) { if (!$ignoreUnreachable) { throw $e; } $unreachableRepos[] = $e->getMessage(); } $filters = []; foreach ($this->providers as $provider) { $providerLists = $provider->getFilterLists(); $relevantLists = array_values(array_intersect($configuredLists, $providerLists)); if ([] === $relevantLists) { continue; } try { $result = $provider->getFilter($packageConstraintMap, $relevantLists); $repoFilter = $result['filter']; foreach ($repoFilter as $listName => $entries) { if (!in_array($listName, $configuredLists, true) || !in_array($listName, $providerLists, true)) { continue; } foreach ($entries as $entry) { if (!isset($packageConstraintMap[$entry->packageName])) { continue; } if (!$entry->constraint->matches($packageConstraintMap[$entry->packageName])) { continue; } $filters[$listName][] = $entry; } } } catch (\Composer\Downloader\TransportException $e) { if (!$ignoreUnreachable) { throw $e; } $unreachableRepos[] = $e->getMessage(); } } ksort($filters); return $filters; } } apiClient = new FilterListApiClient($httpDownloader); $this->entryBuilder = new FilterListEntryBuilder(); $this->source = $source; } public function hasFilter(): bool { return true; } public function getFilter(array $packageConstraintMap, array $configuredLists): array { $response = $this->apiClient->postPurls($this->source->url, $packageConstraintMap, [$this->source->listName]); $decoded = $response->decodeJson(); $entries = isset($decoded['filter']) && is_array($decoded['filter']) ? $decoded['filter'] : []; $rawByList = [$this->source->listName => $entries]; return ['filter' => $this->entryBuilder->build($rawByList, $packageConstraintMap)]; } public function getFilterLists(): array { return [$this->source->listName]; } } validateUrlSource($listName, $source); } throw new \RuntimeException('Unsupported source type "'.$source['type'].'". Only "url" is currently supported.'); } private function validateUrlSource(string $listName, array $source): UrlSource { if (!isset($source['url']) || !is_string($source['url'])) { throw new \RuntimeException('Source configuration is missing a string "url" field.'); } if (str_starts_with($source['url'], 'https://') === false) { throw new \RuntimeException('Source URL for policy list "'.$listName.'" must start with "https://"; got "'.$source['url'].'".'); } return new UrlSource($listName, $source['url']); } } listName = $name; $this->url = $url; } } authentications; } public function resetAuthentications() { $this->authentications = []; } public function hasAuthentication($repositoryName) { return isset($this->authentications[$repositoryName]); } public function getAuthentication($repositoryName) { if (isset($this->authentications[$repositoryName])) { return $this->authentications[$repositoryName]; } return ['username' => null, 'password' => null]; } public function setAuthentication($repositoryName, $username, $password = null) { $this->authentications[$repositoryName] = ['username' => $username, 'password' => $password]; } public function writeRaw($messages, bool $newline = true, int $verbosity = self::NORMAL) { $this->write($messages, $newline, $verbosity); } public function writeErrorRaw($messages, bool $newline = true, int $verbosity = self::NORMAL) { $this->writeError($messages, $newline, $verbosity); } protected function checkAndSetAuthentication(string $repositoryName, string $username, ?string $password = null) { if ($this->hasAuthentication($repositoryName)) { $auth = $this->getAuthentication($repositoryName); if ($auth['username'] === $username && $auth['password'] === $password) { return; } $this->writeError( sprintf( "Warning: You should avoid overwriting already defined auth settings for %s.", $repositoryName ) ); } $this->setAuthentication($repositoryName, $username, $password); } public function loadConfiguration(Config $config) { $bitbucketOauth = $config->get('bitbucket-oauth'); $githubOauth = $config->get('github-oauth'); $gitlabOauth = $config->get('gitlab-oauth'); $gitlabToken = $config->get('gitlab-token'); $forgejoToken = $config->get('forgejo-token'); $httpBasic = $config->get('http-basic'); $bearerToken = $config->get('bearer'); $customHeaders = $config->get('custom-headers'); $clientCertificate = $config->get('client-certificate'); foreach ($bitbucketOauth as $domain => $cred) { $this->checkAndSetAuthentication($domain, $cred['consumer-key'], $cred['consumer-secret']); } foreach ($githubOauth as $domain => $token) { if ($domain !== 'github.com' && !in_array($domain, $config->get('github-domains'), true)) { $this->debug($domain.' is not in the configured github-domains, adding it implicitly as authentication is configured for this domain'); $config->merge(['config' => ['github-domains' => array_merge($config->get('github-domains'), [$domain])]], 'implicit-due-to-auth'); } $this->checkAndSetAuthentication($domain, $token, 'x-oauth-basic'); } foreach ($gitlabOauth as $domain => $token) { if ($domain !== 'gitlab.com' && !in_array($domain, $config->get('gitlab-domains'), true)) { $this->debug($domain.' is not in the configured gitlab-domains, adding it implicitly as authentication is configured for this domain'); $config->merge(['config' => ['gitlab-domains' => array_merge($config->get('gitlab-domains'), [$domain])]], 'implicit-due-to-auth'); } $token = is_array($token) ? $token["token"] : $token; $this->checkAndSetAuthentication($domain, $token, 'oauth2'); } foreach ($gitlabToken as $domain => $token) { if ($domain !== 'gitlab.com' && !in_array($domain, $config->get('gitlab-domains'), true)) { $this->debug($domain.' is not in the configured gitlab-domains, adding it implicitly as authentication is configured for this domain'); $config->merge(['config' => ['gitlab-domains' => array_merge($config->get('gitlab-domains'), [$domain])]], 'implicit-due-to-auth'); } $username = is_array($token) ? $token["username"] : $token; $password = is_array($token) ? $token["token"] : 'private-token'; $this->checkAndSetAuthentication($domain, $username, $password); } foreach ($forgejoToken as $domain => $cred) { if (!in_array($domain, $config->get('forgejo-domains'), true)) { $this->debug($domain.' is not in the configured forgejo-domains, adding it implicitly as authentication is configured for this domain'); $config->merge(['config' => ['forgejo-domains' => array_merge($config->get('forgejo-domains'), [$domain])]], 'implicit-due-to-auth'); } $this->checkAndSetAuthentication($domain, $cred['username'], $cred['token']); } foreach ($httpBasic as $domain => $cred) { $this->checkAndSetAuthentication($domain, $cred['username'], $cred['password']); } foreach ($bearerToken as $domain => $token) { $this->checkAndSetAuthentication($domain, $token, 'bearer'); } foreach ($customHeaders as $domain => $headers) { if ($headers !== null) { $this->checkAndSetAuthentication($domain, (string) json_encode($headers), 'custom-headers'); } } foreach ($clientCertificate as $domain => $cred) { $sslOptions = array_filter( [ 'local_cert' => $cred['local_cert'] ?? null, 'local_pk' => $cred['local_pk'] ?? null, 'passphrase' => $cred['passphrase'] ?? null, ], static function (?string $value): bool { return $value !== null; } ); if (!isset($sslOptions['local_cert'])) { $this->writeError( sprintf( 'Warning: Client certificate configuration is missing key `local_cert` for %s.', $domain ) ); continue; } $this->checkAndSetAuthentication($domain, 'client-certificate', (string) json_encode($sslOptions)); } ProcessExecutor::setTimeout($config->get('process-timeout')); } public function emergency($message, array $context = []): void { $this->log(LogLevel::EMERGENCY, $message, $context); } public function alert($message, array $context = []): void { $this->log(LogLevel::ALERT, $message, $context); } public function critical($message, array $context = []): void { $this->log(LogLevel::CRITICAL, $message, $context); } public function error($message, array $context = []): void { $this->log(LogLevel::ERROR, $message, $context); } public function warning($message, array $context = []): void { $this->log(LogLevel::WARNING, $message, $context); } public function notice($message, array $context = []): void { $this->log(LogLevel::NOTICE, $message, $context); } public function info($message, array $context = []): void { $this->log(LogLevel::INFO, $message, $context); } public function debug($message, array $context = []): void { $this->log(LogLevel::DEBUG, $message, $context); } public function log($level, $message, array $context = []): void { $message = (string) $message; if ($context !== []) { $json = Silencer::call('json_encode', $context, JSON_INVALID_UTF8_IGNORE | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); if ($json !== false) { $message .= ' ' . $json; } } if (in_array($level, [LogLevel::EMERGENCY, LogLevel::ALERT, LogLevel::CRITICAL, LogLevel::ERROR])) { $this->writeError(''.$message.''); } elseif ($level === LogLevel::WARNING) { $this->writeError(''.$message.''); } elseif ($level === LogLevel::NOTICE) { $this->writeError(''.$message.'', true, self::VERBOSE); } elseif ($level === LogLevel::INFO) { $this->writeError(''.$message.'', true, self::VERY_VERBOSE); } else { $this->writeError($message, true, self::DEBUG); } } } setInteractive(false); $stream = fopen('php://memory', 'rw'); if ($stream === false) { throw new \RuntimeException('Unable to open memory output stream'); } $output = new StreamOutput($stream, $verbosity, $formatter !== null ? $formatter->isDecorated() : false, $formatter); parent::__construct($input, $output, new HelperSet([ new QuestionHelper(), ])); } public function getOutput(): string { assert($this->output instanceof StreamOutput); fseek($this->output->getStream(), 0); $output = (string) stream_get_contents($this->output->getStream()); $output = Preg::replaceCallback("{(?<=^|\n|\x08)(.+?)(\x08+)}", static function ($matches): string { $pre = strip_tags($matches[1]); if (strlen($pre) === strlen($matches[2])) { return ''; } return rtrim($matches[1])."\n"; }, $output); return $output; } public function setUserInputs(array $inputs): void { if (!$this->input instanceof StreamableInputInterface) { throw new \RuntimeException('Setting the user inputs requires at least the version 3.2 of the symfony/console component.'); } $this->input->setStream($this->createStream($inputs)); $this->input->setInteractive(true); } private function createStream(array $inputs) { $stream = fopen('php://memory', 'r+'); if ($stream === false) { throw new \RuntimeException('Unable to open memory output stream'); } foreach ($inputs as $input) { fwrite($stream, $input.PHP_EOL); } rewind($stream); return $stream; } } input = $input; $this->output = $output; $this->helperSet = $helperSet; $this->verbosityMap = [ self::QUIET => OutputInterface::VERBOSITY_QUIET, self::NORMAL => OutputInterface::VERBOSITY_NORMAL, self::VERBOSE => OutputInterface::VERBOSITY_VERBOSE, self::VERY_VERBOSE => OutputInterface::VERBOSITY_VERY_VERBOSE, self::DEBUG => OutputInterface::VERBOSITY_DEBUG, ]; } public function enableDebugging(float $startTime) { $this->startTime = $startTime; } public function enableTimestamps(string $format = DATE_RFC3339_EXTENDED) { $this->sendTimestamps = $format; } public function isInteractive() { return $this->input->isInteractive(); } public function isDecorated() { return $this->output->isDecorated(); } public function isVerbose() { return $this->output->isVerbose(); } public function isVeryVerbose() { return $this->output->isVeryVerbose(); } public function isDebug() { return $this->output->isDebug(); } public function write($messages, bool $newline = true, int $verbosity = self::NORMAL) { $messages = self::sanitize($messages); $this->doWrite($messages, $newline, false, $verbosity); } public function writeError($messages, bool $newline = true, int $verbosity = self::NORMAL) { $messages = self::sanitize($messages); $this->doWrite($messages, $newline, true, $verbosity); } public function writeRaw($messages, bool $newline = true, int $verbosity = self::NORMAL) { $this->doWrite($messages, $newline, false, $verbosity, true); } public function writeErrorRaw($messages, bool $newline = true, int $verbosity = self::NORMAL) { $this->doWrite($messages, $newline, true, $verbosity, true); } private function doWrite($messages, bool $newline, bool $stderr, int $verbosity, bool $raw = false): void { $sfVerbosity = $this->verbosityMap[$verbosity]; if ($sfVerbosity > $this->output->getVerbosity()) { return; } if ($raw) { $sfVerbosity |= OutputInterface::OUTPUT_RAW; } if (null !== $this->startTime) { $memoryUsage = memory_get_usage() / 1024 / 1024; $timeSpent = microtime(true) - $this->startTime; $messages = array_map(static function ($message) use ($memoryUsage, $timeSpent): string { return sprintf('[%.1fMiB/%.2fs] %s', $memoryUsage, $timeSpent, $message); }, (array) $messages); } if ($this->sendTimestamps !== false) { $messages = array_map(function ($message): string { return sprintf('[%s] %s', (new \DateTime())->format($this->sendTimestamps), $message); }, (array) $messages); } if (true === $stderr && $this->output instanceof ConsoleOutputInterface) { $this->output->getErrorOutput()->write($messages, $newline, $sfVerbosity); $this->lastMessageErr = implode($newline ? "\n" : '', (array) $messages); return; } $this->output->write($messages, $newline, $sfVerbosity); $this->lastMessage = implode($newline ? "\n" : '', (array) $messages); } public function overwrite($messages, bool $newline = true, ?int $size = null, int $verbosity = self::NORMAL) { $this->doOverwrite($messages, $newline, $size, false, $verbosity); } public function overwriteError($messages, bool $newline = true, ?int $size = null, int $verbosity = self::NORMAL) { $this->doOverwrite($messages, $newline, $size, true, $verbosity); } private function doOverwrite($messages, bool $newline, ?int $size, bool $stderr, int $verbosity): void { $messages = implode($newline ? "\n" : '', (array) $messages); if (!isset($size)) { $size = strlen(strip_tags($stderr ? $this->lastMessageErr : $this->lastMessage)); } $this->doWrite(str_repeat("\x08", $size), false, $stderr, $verbosity); $this->doWrite($messages, false, $stderr, $verbosity); $fill = $size - strlen(strip_tags($messages)); if ($fill > 0) { $this->doWrite(str_repeat(' ', $fill), false, $stderr, $verbosity); $this->doWrite(str_repeat("\x08", $fill), false, $stderr, $verbosity); } if ($newline) { $this->doWrite('', true, $stderr, $verbosity); } if ($stderr) { $this->lastMessageErr = $messages; } else { $this->lastMessage = $messages; } } public function getProgressBar(int $max = 0) { return new ProgressBar($this->getErrorOutput(), $max); } public function ask($question, $default = null) { $helper = $this->helperSet->get('question'); $question = new Question(self::sanitize($question), is_string($default) ? self::sanitize($default) : $default); return $helper->ask($this->input, $this->getErrorOutput(), $question); } public function askConfirmation($question, $default = true) { $helper = $this->helperSet->get('question'); $question = new StrictConfirmationQuestion(self::sanitize($question), is_string($default) ? self::sanitize($default) : $default); return $helper->ask($this->input, $this->getErrorOutput(), $question); } public function askAndValidate($question, $validator, $attempts = null, $default = null) { $helper = $this->helperSet->get('question'); $question = new Question(self::sanitize($question), is_string($default) ? self::sanitize($default) : $default); $question->setValidator($validator); $question->setMaxAttempts($attempts); return $helper->ask($this->input, $this->getErrorOutput(), $question); } public function askAndHideAnswer($question) { $helper = $this->helperSet->get('question'); $question = new Question(self::sanitize($question)); $question->setHidden(true); return $helper->ask($this->input, $this->getErrorOutput(), $question); } public function select($question, $choices, $default, $attempts = false, $errorMessage = 'Value "%s" is invalid', $multiselect = false) { $helper = $this->helperSet->get('question'); $question = new ChoiceQuestion(self::sanitize($question), self::sanitize($choices), is_string($default) ? self::sanitize($default) : $default); $question->setMaxAttempts($attempts ?: null); $question->setErrorMessage($errorMessage); $question->setMultiselect($multiselect); $result = $helper->ask($this->input, $this->getErrorOutput(), $question); $isAssoc = (bool) \count(array_filter(array_keys($choices), 'is_string')); if ($isAssoc) { return $result; } if (!is_array($result)) { return (string) array_search($result, $choices, true); } $results = []; foreach ($choices as $index => $choice) { if (in_array($choice, $result, true)) { $results[] = (string) $index; } } return $results; } public function getTable(): Table { return new Table($this->output); } private function getErrorOutput(): OutputInterface { if ($this->output instanceof ConsoleOutputInterface) { return $this->output->getErrorOutput(); } return $this->output; } public static function sanitize($messages, bool $allowNewlines = true) { $escapePattern = '\x1B\[[\x30-\x3F]*[\x20-\x2F]*[\x40-\x7E]|\x1B\].*?(?:\x1B\\\\|\x07)|\x1B.'; $pattern = $allowNewlines ? "{{$escapePattern}|[\x01-\x09\x0B\x0C\x0E-\x1A]|\r(?!\n)}u" : "{{$escapePattern}|[\x01-\x1A]}u"; if (is_string($messages)) { $messages = self::ensureValidUtf8($messages); return Preg::replace($pattern, '', $messages); } $sanitized = []; foreach ($messages as $key => $message) { $message = self::ensureValidUtf8($message); $sanitized[$key] = Preg::replace($pattern, '', $message); } return $sanitized; } private static function ensureValidUtf8(string $string): string { if (function_exists('mb_check_encoding') && mb_check_encoding($string, 'UTF-8')) { return $string; } if (function_exists('mb_convert_encoding')) { return (string) mb_convert_encoding($string, 'UTF-8', 'UTF-8'); } if (function_exists('iconv')) { $cleaned = @iconv('UTF-8', 'UTF-8//TRANSLIT', $string); if ($cleaned !== false) { return $cleaned; } } return $string; } } io = $io; $this->config = $config; $this->package = $package; $this->downloadManager = $downloadManager; $this->repositoryManager = $repositoryManager; $this->locker = $locker; $this->installationManager = $installationManager; $this->eventDispatcher = $eventDispatcher; $this->autoloadGenerator = $autoloadGenerator; $this->suggestedPackagesReporter = new SuggestedPackagesReporter($this->io); $this->platformRequirementFilter = PlatformRequirementFilterFactory::ignoreNothing(); $this->writeLock = $config->get('lock'); } public function run(): int { gc_collect_cycles(); gc_disable(); if ($this->updateAllowList !== null && $this->updateMirrors) { throw new \RuntimeException("The installer options updateMirrors and updateAllowList are mutually exclusive."); } $isFreshInstall = $this->repositoryManager->getLocalRepository()->isFresh(); if (!$this->update && !$this->locker->isLocked()) { $updateFallbackMessage = 'Updating dependencies to latest instead of installing from lock file. See https://getcomposer.org/install for more information.'; if ($this->writeLock === false) { $this->io->writeError(sprintf('Lockfile creation is disabled in config. %s', $updateFallbackMessage)); } else { $this->io->writeError(sprintf('No composer.lock file present. %s', $updateFallbackMessage)); } $this->update = true; } if ($this->dryRun) { $this->verbose = true; $this->runScripts = false; $this->executeOperations = false; $this->writeLock = false; $this->dumpAutoloader = false; $this->mockLocalRepositories($this->repositoryManager); } if ($this->downloadOnly) { $this->dumpAutoloader = false; } if ($this->update && !$this->install) { $this->dumpAutoloader = false; } if ($this->runScripts) { Platform::putEnv('COMPOSER_DEV_MODE', $this->devMode ? '1' : '0'); $eventName = $this->update ? ScriptEvents::PRE_UPDATE_CMD : ScriptEvents::PRE_INSTALL_CMD; $this->eventDispatcher->dispatchScript($eventName, $this->devMode); } $this->downloadManager->setPreferSource($this->preferSource); $this->downloadManager->setPreferDist($this->preferDist); $localRepo = $this->repositoryManager->getLocalRepository(); try { if ($this->update) { $res = $this->doUpdate($localRepo, $this->install); } else { $res = $this->doInstall($localRepo); } if ($res !== 0) { return $res; } } catch (\Exception $e) { if ($this->executeOperations && $this->install && $this->config->get('notify-on-install')) { $this->installationManager->notifyInstalls($this->io); } throw $e; } if ($this->executeOperations && $this->install && $this->config->get('notify-on-install')) { $this->installationManager->notifyInstalls($this->io); } if ($this->update) { $installedRepo = new InstalledRepository([ $this->locker->getLockedRepository($this->devMode), $this->createPlatformRepo(false), new RootPackageRepository(clone $this->package), ]); if ($isFreshInstall) { $this->suggestedPackagesReporter->addSuggestionsFromPackage($this->package); } $this->suggestedPackagesReporter->outputMinimalistic($installedRepo); } $lockedRepository = $this->locker->getLockedRepository(true); foreach ($lockedRepository->getPackages() as $package) { if (!$package instanceof CompletePackage || !$package->isAbandoned()) { continue; } $replacement = is_string($package->getReplacementPackage()) ? 'Use ' . $package->getReplacementPackage() . ' instead' : 'No replacement was suggested'; $this->io->writeError( sprintf( "Package %s is abandoned, you should avoid using it. %s.", $package->getPrettyName(), $replacement ) ); } if ($this->dumpAutoloader) { if ($this->optimizeAutoloader) { $this->io->writeError('Generating optimized autoload files'); } else { $this->io->writeError('Generating autoload files'); } $this->autoloadGenerator->setClassMapAuthoritative($this->classMapAuthoritative); $this->autoloadGenerator->setApcu($this->apcuAutoloader, $this->apcuAutoloaderPrefix); $this->autoloadGenerator->setRunScripts($this->runScripts); $this->autoloadGenerator->setPlatformRequirementFilter($this->platformRequirementFilter); $classMap = $this ->autoloadGenerator ->dump( $this->config, $localRepo, $this->package, $this->installationManager, 'composer', $this->optimizeAutoloader, null, $this->locker ); if ($this->strictPsrAutoloader && count($classMap->getPsrViolations()) > 0) { return self::ERROR_PSR_AUTOLOAD_VIOLATION; } } if ($this->install && $this->executeOperations) { foreach ($localRepo->getPackages() as $package) { $this->installationManager->ensureBinariesPresence($package); } } $fundEnv = Platform::getEnv('COMPOSER_FUND'); $showFunding = true; if (is_numeric($fundEnv)) { $showFunding = intval($fundEnv) !== 0; } if ($showFunding) { $fundingCount = 0; foreach ($localRepo->getPackages() as $package) { if ($package instanceof CompletePackageInterface && !$package instanceof AliasPackage && $package->getFunding()) { $fundingCount++; } } if ($fundingCount > 0) { $this->io->writeError([ sprintf( "%d package%s you are using %s looking for funding.", $fundingCount, 1 === $fundingCount ? '' : 's', 1 === $fundingCount ? 'is' : 'are' ), 'Use the `composer fund` command to find out more!', ]); } } if ($this->runScripts) { $eventName = $this->update ? ScriptEvents::POST_UPDATE_CMD : ScriptEvents::POST_INSTALL_CMD; $this->eventDispatcher->dispatchScript($eventName, $this->devMode); } if (!defined('HHVM_VERSION')) { gc_enable(); } $auditConfig = $this->getAuditConfig(); if ($auditConfig->audit) { if ($this->update && !$this->install) { $packages = $lockedRepository->getCanonicalPackages(); $target = 'locked'; } else { $packages = $localRepo->getCanonicalPackages(); $target = 'installed'; } if (count($packages) > 0) { try { $auditor = new Auditor(); $repoSet = new RepositorySet(); foreach ($this->repositoryManager->getRepositories() as $repo) { $repoSet->addRepository($repo); } $policyConfig = $this->getPolicyConfig(); $filterListProviderSet = $policyConfig->enabled ? FilterListProviderSet::create($policyConfig, $this->repositoryManager->getRepositories(), $this->repositoryManager->getHttpDownloader()) : null; return $auditor->audit($this->io, $repoSet, $policyConfig, $packages, $auditConfig->auditFormat, true, $filterListProviderSet) > 0 && $this->errorOnAudit ? self::ERROR_AUDIT_FAILED : 0; } catch (TransportException $e) { $this->io->error('Failed to audit '.$target.' packages.'); if ($this->io->isVerbose()) { $this->io->error('['.get_class($e).'] '.$e->getMessage()); } } } else { $this->io->writeError('No '.$target.' packages - skipping audit.'); } } return 0; } protected function doUpdate(InstalledRepositoryInterface $localRepo, bool $doInstall): int { $platformRepo = $this->createPlatformRepo(true); $aliases = $this->getRootAliases(true); $lockedRepository = null; try { if ($this->locker->isLocked()) { $lockedRepository = $this->locker->getLockedRepository(true); } } catch (\Seld\JsonLint\ParsingException $e) { if ($this->updateAllowList !== null || $this->updateMirrors) { throw $e; } } if (($this->updateAllowList !== null || $this->updateMirrors) && !$lockedRepository) { $this->io->writeError('Cannot update ' . ($this->updateMirrors ? 'lock file information' : 'only a partial set of packages') . ' without a lock file present. Run `composer update` to generate a lock file.', true, IOInterface::QUIET); return self::ERROR_NO_LOCK_FILE_FOR_PARTIAL_UPDATE; } $this->io->writeError('Loading composer repositories with package information'); $policy = $this->createPolicy(true, $lockedRepository); $repositorySet = $this->createRepositorySet(true, $platformRepo, $aliases); $repositories = $this->repositoryManager->getRepositories(); foreach ($repositories as $repository) { $repositorySet->addRepository($repository); } if ($lockedRepository) { $repositorySet->addRepository($lockedRepository); } $request = $this->createRequest($this->fixedRootPackage, $platformRepo, $lockedRepository); $this->requirePackagesForUpdate($request, $lockedRepository, true); if ($this->updateAllowList !== null) { $request->setUpdateAllowList($this->updateAllowList, $this->updateAllowTransitiveDependencies); } $pool = $repositorySet->createPool($request, $this->io, $this->eventDispatcher, $this->createPoolOptimizer($policy), $this->ignoredTypes, $this->allowedTypes, $this->createSecurityAuditPoolFilter(), $this->createFilterListPoolFilter(ListPolicyConfig::BLOCK_SCOPE_UPDATE)); $this->io->writeError('Updating dependencies'); $solver = new Solver($policy, $pool, $this->io); try { $this->lockTransaction = $solver->solve($request, $this->platformRequirementFilter); $ruleSetSize = $solver->getRuleSetSize(); $solver = null; } catch (SolverProblemsException $e) { $err = 'Your requirements could not be resolved to an installable set of packages.'; $prettyProblem = $e->getPrettyString($repositorySet, $request, $pool, $this->io->isVerbose()); $this->io->writeError(''. $err .'', true, IOInterface::QUIET); $this->io->writeError($prettyProblem); if (!$this->devMode) { $this->io->writeError('Running update with --no-dev does not mean require-dev is ignored, it just means the packages will not be installed. If dev requirements are blocking the update you have to resolve those problems.', true, IOInterface::QUIET); } $ghe = new GithubActionError($this->io); $ghe->emit($err."\n".$prettyProblem); return max(self::ERROR_GENERIC_FAILURE, $e->getCode()); } $this->io->writeError("Analyzed ".count($pool)." packages to resolve dependencies", true, IOInterface::VERBOSE); $this->io->writeError("Analyzed ".$ruleSetSize." rules to resolve dependencies", true, IOInterface::VERBOSE); $pool = null; if (!$this->lockTransaction->getOperations()) { $this->io->writeError('Nothing to modify in lock file'); if ($this->minimalUpdate && $this->updateAllowList === null && $this->locker->isFresh()) { $this->io->writeError('The --minimal-changes option should be used with package arguments or after modifying composer.json requirements, otherwise it will likely not yield any dependency changes.'); } } $exitCode = $this->extractDevPackages($this->lockTransaction, $platformRepo, $aliases, $policy, $lockedRepository); if ($exitCode !== 0) { return $exitCode; } Semver\CompilingMatcher::clear(); $platformReqs = $this->extractPlatformRequirements($this->package->getRequires()); $platformDevReqs = $this->extractPlatformRequirements($this->package->getDevRequires()); $installsUpdates = $uninstalls = []; if ($this->lockTransaction->getOperations()) { $installNames = $updateNames = $uninstallNames = []; foreach ($this->lockTransaction->getOperations() as $operation) { if ($operation instanceof InstallOperation) { $installsUpdates[] = $operation; $installNames[] = $operation->getPackage()->getPrettyName().':'.$operation->getPackage()->getFullPrettyVersion(); } elseif ($operation instanceof UpdateOperation) { if ($this->updateMirrors && $operation->getInitialPackage()->getName() === $operation->getTargetPackage()->getName() && $operation->getInitialPackage()->getVersion() === $operation->getTargetPackage()->getVersion() ) { continue; } $installsUpdates[] = $operation; $updateNames[] = $operation->getTargetPackage()->getPrettyName().':'.$operation->getTargetPackage()->getFullPrettyVersion(); } elseif ($operation instanceof UninstallOperation) { $uninstalls[] = $operation; $uninstallNames[] = $operation->getPackage()->getPrettyName(); } } if ($this->config->get('lock')) { $this->io->writeError(sprintf( "Lock file operations: %d install%s, %d update%s, %d removal%s", count($installNames), 1 === count($installNames) ? '' : 's', count($updateNames), 1 === count($updateNames) ? '' : 's', count($uninstalls), 1 === count($uninstalls) ? '' : 's' )); if ($installNames) { $this->io->writeError("Installs: ".implode(', ', $installNames), true, IOInterface::VERBOSE); } if ($updateNames) { $this->io->writeError("Updates: ".implode(', ', $updateNames), true, IOInterface::VERBOSE); } if ($uninstalls) { $this->io->writeError("Removals: ".implode(', ', $uninstallNames), true, IOInterface::VERBOSE); } } } $sortByName = static function ($a, $b): int { if ($a instanceof UpdateOperation) { $a = $a->getTargetPackage()->getName(); } else { $a = $a->getPackage()->getName(); } if ($b instanceof UpdateOperation) { $b = $b->getTargetPackage()->getName(); } else { $b = $b->getPackage()->getName(); } return strcmp($a, $b); }; usort($uninstalls, $sortByName); usort($installsUpdates, $sortByName); foreach (array_merge($uninstalls, $installsUpdates) as $operation) { if ($operation instanceof InstallOperation) { $this->suggestedPackagesReporter->addSuggestionsFromPackage($operation->getPackage()); } if ($this->config->get('lock') && (false === strpos($operation->getOperationType(), 'Alias') || $this->io->isDebug())) { $sourceRepo = ''; if ($this->io->isVeryVerbose() && false === strpos($operation->getOperationType(), 'Alias')) { $operationPkg = ($operation instanceof UpdateOperation ? $operation->getTargetPackage() : $operation->getPackage()); if ($operationPkg->getRepository() !== null) { $sourceRepo = ' from ' . $operationPkg->getRepository()->getRepoName(); } } $this->io->writeError(' - ' . $operation->show(true) . $sourceRepo); } } $updatedLock = $this->locker->setLockData( $this->lockTransaction->getNewLockPackages(false, $this->updateMirrors), $this->lockTransaction->getNewLockPackages(true, $this->updateMirrors), $platformReqs, $platformDevReqs, $this->lockTransaction->getAliases($aliases), $this->package->getMinimumStability(), $this->package->getStabilityFlags(), $this->preferStable || $this->package->getPreferStable(), $this->preferLowest, $this->config->get('platform') ?: [], $this->writeLock && $this->executeOperations ); if ($updatedLock && $this->writeLock && $this->executeOperations) { $this->io->writeError('Writing lock file'); } if ($doInstall) { return $this->doInstall($localRepo, true); } return 0; } protected function extractDevPackages(LockTransaction $lockTransaction, PlatformRepository $platformRepo, array $aliases, PolicyInterface $policy, ?LockArrayRepository $lockedRepository = null): int { if (!$this->package->getDevRequires()) { return 0; } $resultRepo = new ArrayRepository([]); $loader = new ArrayLoader(null, true); $dumper = new ArrayDumper(); foreach ($lockTransaction->getNewLockPackages(false) as $pkg) { $resultRepo->addPackage($loader->load($dumper->dump($pkg))); } $repositorySet = $this->createRepositorySet(true, $platformRepo, $aliases); $repositorySet->addRepository($resultRepo); $request = $this->createRequest($this->fixedRootPackage, $platformRepo); $this->requirePackagesForUpdate($request, $lockedRepository, false); $pool = $repositorySet->createPoolWithAllPackages(); $solver = new Solver($policy, $pool, $this->io); try { $nonDevLockTransaction = $solver->solve($request, $this->platformRequirementFilter); $solver = null; } catch (SolverProblemsException $e) { $err = 'Unable to find a compatible set of packages based on your non-dev requirements alone.'; $prettyProblem = $e->getPrettyString($repositorySet, $request, $pool, $this->io->isVerbose(), true); $this->io->writeError(''. $err .'', true, IOInterface::QUIET); $this->io->writeError('Your requirements can be resolved successfully when require-dev packages are present.'); $this->io->writeError('You may need to move packages from require-dev or some of their dependencies to require.'); $this->io->writeError($prettyProblem); $ghe = new GithubActionError($this->io); $ghe->emit($err."\n".$prettyProblem); return $e->getCode(); } $lockTransaction->setNonDevPackages($nonDevLockTransaction); return 0; } protected function doInstall(InstalledRepositoryInterface $localRepo, bool $alreadySolved = false): int { if ($this->config->get('lock')) { $this->io->writeError('Installing dependencies from lock file'.($this->devMode ? ' (including require-dev)' : '').''); } $lockedRepository = $this->locker->getLockedRepository($this->devMode); if (!$alreadySolved) { $this->io->writeError('Verifying lock file contents can be installed on current platform.'); $platformRepo = $this->createPlatformRepo(false); $policy = $this->createPolicy(false); $repositorySet = $this->createRepositorySet(false, $platformRepo, [], $lockedRepository); $repositorySet->addRepository($lockedRepository); $request = $this->createRequest($this->fixedRootPackage, $platformRepo, $lockedRepository); if (!$this->locker->isFresh()) { $this->io->writeError('Warning: The lock file is not up to date with the latest changes in composer.json. You may be getting outdated dependencies. It is recommended that you run `composer update` or `composer update `.', true, IOInterface::QUIET); } $missingRequirementInfo = $this->locker->getMissingRequirementInfo($this->package, $this->devMode); if ($missingRequirementInfo !== []) { $this->io->writeError($missingRequirementInfo); if (!$this->config->get('allow-missing-requirements')) { return self::ERROR_LOCK_FILE_INVALID; } } foreach ($lockedRepository->getPackages() as $package) { $request->fixLockedPackage($package); } $rootRequires = $this->package->getRequires(); if ($this->devMode) { $rootRequires = array_merge($rootRequires, $this->package->getDevRequires()); } foreach ($rootRequires as $link) { if (PlatformRepository::isPlatformPackage($link->getTarget())) { $request->requireName($link->getTarget(), $link->getConstraint()); } } foreach ($this->locker->getPlatformRequirements($this->devMode) as $link) { if (!isset($rootRequires[$link->getTarget()])) { $request->requireName($link->getTarget(), $link->getConstraint()); } } unset($rootRequires, $link); $pool = $repositorySet->createPool($request, $this->io, $this->eventDispatcher, null, $this->ignoredTypes, $this->allowedTypes, null, $this->createFilterListPoolFilter(ListPolicyConfig::BLOCK_SCOPE_INSTALL)); $solver = new Solver($policy, $pool, $this->io); try { $lockTransaction = $solver->solve($request, $this->platformRequirementFilter); $solver = null; if (0 !== count($lockTransaction->getOperations())) { $this->io->writeError('Your lock file cannot be installed on this system without changes. Please run composer update.', true, IOInterface::QUIET); return self::ERROR_LOCK_FILE_INVALID; } } catch (SolverProblemsException $e) { $err = 'Your lock file does not contain a compatible set of packages. Please run composer update.'; $prettyProblem = $e->getPrettyString($repositorySet, $request, $pool, $this->io->isVerbose()); $this->io->writeError(''. $err .'', true, IOInterface::QUIET); $this->io->writeError($prettyProblem); $ghe = new GithubActionError($this->io); $ghe->emit($err."\n".$prettyProblem); return max(self::ERROR_GENERIC_FAILURE, $e->getCode()); } } $localRepoTransaction = new LocalRepoTransaction($lockedRepository, $localRepo); $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_OPERATIONS_EXEC, $this->devMode, $this->executeOperations, $localRepoTransaction); $installs = $updates = $uninstalls = []; foreach ($localRepoTransaction->getOperations() as $operation) { if ($operation instanceof InstallOperation) { $installs[] = $operation->getPackage()->getPrettyName().':'.$operation->getPackage()->getFullPrettyVersion(); } elseif ($operation instanceof UpdateOperation) { $updates[] = $operation->getTargetPackage()->getPrettyName().':'.$operation->getTargetPackage()->getFullPrettyVersion(); } elseif ($operation instanceof UninstallOperation) { $uninstalls[] = $operation->getPackage()->getPrettyName(); } } if ($installs === [] && $updates === [] && $uninstalls === []) { $this->io->writeError('Nothing to install, update or remove'); } else { $this->io->writeError(sprintf( "Package operations: %d install%s, %d update%s, %d removal%s", count($installs), 1 === count($installs) ? '' : 's', count($updates), 1 === count($updates) ? '' : 's', count($uninstalls), 1 === count($uninstalls) ? '' : 's' )); if ($installs) { $this->io->writeError("Installs: ".implode(', ', $installs), true, IOInterface::VERBOSE); } if ($updates) { $this->io->writeError("Updates: ".implode(', ', $updates), true, IOInterface::VERBOSE); } if ($uninstalls) { $this->io->writeError("Removals: ".implode(', ', $uninstalls), true, IOInterface::VERBOSE); } } if ($this->executeOperations) { $localRepo->setDevPackageNames($this->locker->getDevPackageNames()); $this->installationManager->execute($localRepo, $localRepoTransaction->getOperations(), $this->devMode, $this->runScripts, $this->downloadOnly); if (count($localRepoTransaction->getOperations()) > 0) { $vendorDir = $this->config->get('vendor-dir'); if (is_dir($vendorDir)) { @touch($vendorDir); } } } else { foreach ($localRepoTransaction->getOperations() as $operation) { if (false === strpos($operation->getOperationType(), 'Alias') || $this->io->isDebug()) { $this->io->writeError(' - ' . $operation->show(false)); } } } return 0; } protected function createPlatformRepo(bool $forUpdate): PlatformRepository { if ($forUpdate) { $platformOverrides = $this->config->get('platform') ?: []; } else { $platformOverrides = $this->locker->getPlatformOverrides(); } return new PlatformRepository([], $platformOverrides); } private function createRepositorySet(bool $forUpdate, PlatformRepository $platformRepo, array $rootAliases = [], ?RepositoryInterface $lockedRepository = null): RepositorySet { if ($forUpdate) { $minimumStability = $this->package->getMinimumStability(); $stabilityFlags = $this->package->getStabilityFlags(); $requires = array_merge($this->package->getRequires(), $this->package->getDevRequires()); } else { $minimumStability = $this->locker->getMinimumStability(); $stabilityFlags = $this->locker->getStabilityFlags(); $requires = []; foreach ($lockedRepository->getPackages() as $package) { $constraint = new Constraint('=', $package->getVersion()); $constraint->setPrettyString($package->getPrettyVersion()); $requires[$package->getName()] = $constraint; } } $rootRequires = []; foreach ($requires as $req => $constraint) { if ($constraint instanceof Link) { $constraint = $constraint->getConstraint(); } if ($this->platformRequirementFilter->isIgnored($req)) { continue; } elseif ($this->platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter) { $constraint = $this->platformRequirementFilter->filterConstraint($req, $constraint); } $rootRequires[$req] = $constraint; } $this->fixedRootPackage = clone $this->package; $this->fixedRootPackage->setRequires([]); $this->fixedRootPackage->setDevRequires([]); $stabilityFlags[$this->package->getName()] = BasePackage::STABILITIES[VersionParser::parseStability($this->package->getVersion())]; $repositorySet = new RepositorySet($minimumStability, $stabilityFlags, $rootAliases, $this->package->getReferences(), $rootRequires, $this->temporaryConstraints); $repositorySet->addRepository(new RootPackageRepository($this->fixedRootPackage)); $repositorySet->addRepository($platformRepo); if ($this->additionalFixedRepository) { $additionalFixedRepositories = $this->additionalFixedRepository; if ($additionalFixedRepositories instanceof CompositeRepository) { $additionalFixedRepositories = $additionalFixedRepositories->getRepositories(); } else { $additionalFixedRepositories = [$additionalFixedRepositories]; } foreach ($additionalFixedRepositories as $additionalFixedRepository) { if ($additionalFixedRepository instanceof InstalledRepository || $additionalFixedRepository instanceof InstalledRepositoryInterface) { $repositorySet->allowInstalledRepositories(); break; } } $repositorySet->addRepository($this->additionalFixedRepository); } return $repositorySet; } private function createPolicy(bool $forUpdate, ?LockArrayRepository $lockedRepo = null): DefaultPolicy { $preferStable = null; $preferLowest = null; if (!$forUpdate) { $preferStable = $this->locker->getPreferStable(); $preferLowest = $this->locker->getPreferLowest(); } if (null === $preferStable) { $preferStable = $this->preferStable || $this->package->getPreferStable(); } if (null === $preferLowest) { $preferLowest = $this->preferLowest; } $preferredVersions = null; if ($forUpdate && $this->minimalUpdate && $lockedRepo !== null) { $preferredVersions = []; foreach ($lockedRepo->getPackages() as $pkg) { if ($pkg instanceof AliasPackage || ($this->updateAllowList !== null && in_array($pkg->getName(), $this->updateAllowList, true))) { continue; } $preferredVersions[$pkg->getName()] = $pkg->getVersion(); } } return new DefaultPolicy($preferStable, $preferLowest, $preferredVersions); } private function createRequest(RootPackageInterface $rootPackage, PlatformRepository $platformRepo, ?LockArrayRepository $lockedRepository = null): Request { $request = new Request($lockedRepository); $request->fixPackage($rootPackage); if ($rootPackage instanceof RootAliasPackage) { $request->fixPackage($rootPackage->getAliasOf()); } $fixedPackages = $platformRepo->getPackages(); if ($this->additionalFixedRepository) { $fixedPackages = array_merge($fixedPackages, $this->additionalFixedRepository->getPackages()); } $provided = $rootPackage->getProvides(); foreach ($fixedPackages as $package) { if ($package->getRepository() !== $platformRepo || !isset($provided[$package->getName()]) || !$provided[$package->getName()]->getConstraint()->matches(new Constraint('=', $package->getVersion())) ) { $request->fixPackage($package); } } return $request; } private function requirePackagesForUpdate(Request $request, ?LockArrayRepository $lockedRepository = null, bool $includeDevRequires = true): void { if ($this->updateMirrors) { $excludedPackages = []; if (!$includeDevRequires) { $excludedPackages = array_flip($this->locker->getDevPackageNames()); } foreach ($lockedRepository->getPackages() as $lockedPackage) { if (!$lockedPackage instanceof AliasPackage && !isset($excludedPackages[$lockedPackage->getName()])) { $request->requireName($lockedPackage->getName(), new Constraint('==', $lockedPackage->getVersion())); } } } else { $links = $this->package->getRequires(); if ($includeDevRequires) { $links = array_merge($links, $this->package->getDevRequires()); } foreach ($links as $link) { $request->requireName($link->getTarget(), $link->getConstraint()); } } } private function getRootAliases(bool $forUpdate): array { if ($forUpdate) { $aliases = $this->package->getAliases(); } else { $aliases = $this->locker->getAliases(); } return $aliases; } private function extractPlatformRequirements(array $links): array { $platformReqs = []; foreach ($links as $link) { if (PlatformRepository::isPlatformPackage($link->getTarget())) { $platformReqs[$link->getTarget()] = $link->getPrettyConstraint(); } } return $platformReqs; } private function mockLocalRepositories(RepositoryManager $rm): void { $packages = []; foreach ($rm->getLocalRepository()->getPackages() as $package) { $packages[(string) $package] = clone $package; } foreach ($packages as $key => $package) { if ($package instanceof AliasPackage) { $alias = (string) $package->getAliasOf(); $className = get_class($package); $packages[$key] = new $className($packages[$alias], $package->getVersion(), $package->getPrettyVersion()); } } $rm->setLocalRepository( new InstalledArrayRepository($packages) ); } private function createPoolOptimizer(PolicyInterface $policy): ?PoolOptimizer { if ('0' === Platform::getEnv('COMPOSER_POOL_OPTIMIZER')) { $this->io->write('Pool Optimizer was disabled for debugging purposes.', true, IOInterface::DEBUG); return null; } return new PoolOptimizer($policy); } private function getPolicyConfig(): PolicyConfig { if (null === $this->policyConfig) { $this->policyConfig = PolicyConfig::fromConfig($this->config); } return $this->policyConfig; } private function getAuditConfig(): AuditConfig { if (null === $this->auditConfig) { $this->auditConfig = new AuditConfig($this->audit, $this->auditFormat); } return $this->auditConfig; } private function createSecurityAuditPoolFilter(): ?SecurityAdvisoryPoolFilter { $policyConfig = $this->getPolicyConfig(); if ($policyConfig->enabled && $policyConfig->advisories->shouldBlock('update') && !$this->updateMirrors) { return new SecurityAdvisoryPoolFilter(new Auditor(), $policyConfig, $this->io); } return null; } private function createFilterListPoolFilter(string $blockScope): ?FilterListPoolFilter { $policyConfig = $this->getPolicyConfig(); if (!$policyConfig->enabled) { return null; } $hasUpdateLists = \count($policyConfig->getActiveBlockFilterLists(ListPolicyConfig::BLOCK_SCOPE_UPDATE)) > 0; $hasInstallLists = \count($policyConfig->getActiveBlockFilterLists(ListPolicyConfig::BLOCK_SCOPE_INSTALL)) > 0; if ($blockScope === ListPolicyConfig::BLOCK_SCOPE_INSTALL && !$hasInstallLists) { return null; } if ($blockScope === ListPolicyConfig::BLOCK_SCOPE_UPDATE && !$hasUpdateLists && !$hasInstallLists) { return null; } return new FilterListPoolFilter($policyConfig, new FilterListAuditor(), $this->repositoryManager->getHttpDownloader(), $blockScope, $this->repositoryManager->getRepositories(), $this->io); } public static function create(IOInterface $io, Composer $composer): self { return new static( $io, $composer->getConfig(), $composer->getPackage(), $composer->getDownloadManager(), $composer->getRepositoryManager(), $composer->getLocker(), $composer->getInstallationManager(), $composer->getEventDispatcher(), $composer->getAutoloadGenerator() ); } public function setIgnoredTypes(array $types): self { $this->ignoredTypes = $types; return $this; } public function setAllowedTypes(?array $types): self { $this->allowedTypes = $types; return $this; } public function setAdditionalFixedRepository(RepositoryInterface $additionalFixedRepository): self { $this->additionalFixedRepository = $additionalFixedRepository; return $this; } public function setTemporaryConstraints(array $constraints): self { $this->temporaryConstraints = $constraints; return $this; } public function setDryRun(bool $dryRun = true): self { $this->dryRun = $dryRun; return $this; } public function isDryRun(): bool { return $this->dryRun; } public function setDownloadOnly(bool $downloadOnly = true): self { $this->downloadOnly = $downloadOnly; return $this; } public function setPreferSource(bool $preferSource = true): self { $this->preferSource = $preferSource; return $this; } public function setPreferDist(bool $preferDist = true): self { $this->preferDist = $preferDist; return $this; } public function setOptimizeAutoloader(bool $optimizeAutoloader): self { $this->optimizeAutoloader = $optimizeAutoloader; if (!$this->optimizeAutoloader) { $this->setClassMapAuthoritative(false); } return $this; } public function setClassMapAuthoritative(bool $classMapAuthoritative): self { $this->classMapAuthoritative = $classMapAuthoritative; if ($this->classMapAuthoritative) { $this->setOptimizeAutoloader(true); } return $this; } public function setApcuAutoloader(bool $apcuAutoloader, ?string $apcuAutoloaderPrefix = null): self { $this->apcuAutoloader = $apcuAutoloader; $this->apcuAutoloaderPrefix = $apcuAutoloaderPrefix; return $this; } public function setStrictPsrAutoloader(bool $strictPsr): self { $this->strictPsrAutoloader = $strictPsr; return $this; } public function setUpdate(bool $update): self { $this->update = $update; return $this; } public function setInstall(bool $install): self { $this->install = $install; return $this; } public function setDevMode(bool $devMode = true): self { $this->devMode = $devMode; return $this; } public function setDumpAutoloader(bool $dumpAutoloader = true): self { $this->dumpAutoloader = $dumpAutoloader; return $this; } public function setRunScripts(bool $runScripts = true): self { $this->runScripts = $runScripts; return $this; } public function setConfig(Config $config): self { $this->config = $config; return $this; } public function setVerbose(bool $verbose = true): self { $this->verbose = $verbose; return $this; } public function isVerbose(): bool { return $this->verbose; } public function setIgnorePlatformRequirements($ignorePlatformReqs): self { trigger_error('Installer::setIgnorePlatformRequirements is deprecated since Composer 2.2, use setPlatformRequirementFilter instead.', E_USER_DEPRECATED); return $this->setPlatformRequirementFilter(PlatformRequirementFilterFactory::fromBoolOrList($ignorePlatformReqs)); } public function setPlatformRequirementFilter(PlatformRequirementFilterInterface $platformRequirementFilter): self { $this->platformRequirementFilter = $platformRequirementFilter; return $this; } public function setUpdateMirrors(bool $updateMirrors): self { $this->updateMirrors = $updateMirrors; return $this; } public function setUpdateAllowList(array $packages): self { if (count($packages) === 0) { $this->updateAllowList = null; } else { $this->updateAllowList = array_values(array_unique(array_map('strtolower', $packages))); } return $this; } public function setUpdateAllowTransitiveDependencies(int $updateAllowTransitiveDependencies): self { if (!in_array($updateAllowTransitiveDependencies, [Request::UPDATE_ONLY_LISTED, Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE, Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS], true)) { throw new \RuntimeException("Invalid value for updateAllowTransitiveDependencies supplied"); } $this->updateAllowTransitiveDependencies = $updateAllowTransitiveDependencies; return $this; } public function setPreferStable(bool $preferStable = true): self { $this->preferStable = $preferStable; return $this; } public function setPreferLowest(bool $preferLowest = true): self { $this->preferLowest = $preferLowest; return $this; } public function setMinimalUpdate(bool $minimalUpdate = true): self { $this->minimalUpdate = $minimalUpdate; return $this; } public function setWriteLock(bool $writeLock = true): self { $this->writeLock = $writeLock; return $this; } public function setExecuteOperations(bool $executeOperations = true): self { $this->executeOperations = $executeOperations; return $this; } public function setAudit(bool $audit): self { $this->audit = $audit; $this->auditConfig = null; return $this; } public function setErrorOnAudit(bool $errorOnAudit): self { $this->errorOnAudit = $errorOnAudit; return $this; } public function setAuditFormat(string $auditFormat): self { $this->auditFormat = $auditFormat; $this->auditConfig = null; return $this; } public function setAuditConfig(AuditConfig $auditConfig): self { $this->auditConfig = $auditConfig; return $this; } public function setPolicyConfig(PolicyConfig $policyConfig): self { $this->policyConfig = $policyConfig; return $this; } public function disablePlugins(): self { $this->installationManager->disablePlugins(); return $this; } public function setSuggestedPackagesReporter(SuggestedPackagesReporter $suggestedPackagesReporter): self { $this->suggestedPackagesReporter = $suggestedPackagesReporter; return $this; } public function getLockTransaction(): ?LockTransaction { return $this->lockTransaction; } } binDir = $binDir; $this->binCompat = $binCompat; $this->io = $io; $this->filesystem = $filesystem ?: new Filesystem(); $this->vendorDir = $vendorDir; } public function installBinaries(PackageInterface $package, string $installPath, bool $warnOnOverwrite = true): void { $binaries = $this->getBinaries($package); if (!$binaries) { return; } Platform::workaroundFilesystemIssues(); foreach ($binaries as $bin) { $binPath = $installPath.'/'.$bin; if (!file_exists($binPath)) { $this->io->writeError(' Skipped installation of bin '.$bin.' for package '.$package->getName().': file not found in package'); continue; } if (is_dir($binPath)) { $this->io->writeError(' Skipped installation of bin '.$bin.' for package '.$package->getName().': found a directory at that path'); continue; } if (!$this->filesystem->isAbsolutePath($binPath)) { $binPath = realpath($binPath); } $this->initializeBinDir(); $link = $this->binDir.'/'.basename($bin); if (file_exists($link)) { if (!is_link($link)) { if ($warnOnOverwrite) { $this->io->writeError(' Skipped installation of bin '.$bin.' for package '.$package->getName().': name conflicts with an existing file'); } continue; } if (realpath($link) === realpath($binPath)) { $this->filesystem->unlink($link); } } $binCompat = $this->binCompat; if ($binCompat === "auto" && (Platform::isWindows() || Platform::isWindowsSubsystemForLinux())) { $binCompat = 'full'; } if ($binCompat === "full") { $this->installFullBinaries($binPath, $link, $bin, $package); } else { $this->installUnixyProxyBinaries($binPath, $link); } Silencer::call('chmod', $binPath, 0777 & ~umask()); } } public function removeBinaries(PackageInterface $package): void { $this->initializeBinDir(); $binaries = $this->getBinaries($package); if (!$binaries) { return; } foreach ($binaries as $bin) { $link = $this->binDir.'/'.basename($bin); if (is_link($link) || file_exists($link)) { $this->filesystem->unlink($link); } if (is_file($link.'.bat')) { $this->filesystem->unlink($link.'.bat'); } } if (is_dir($this->binDir) && $this->filesystem->isDirEmpty($this->binDir)) { Silencer::call('rmdir', $this->binDir); } } public static function determineBinaryCaller(string $bin): string { if ('.bat' === substr($bin, -4) || '.exe' === substr($bin, -4)) { return 'call'; } $handle = fopen($bin, 'r'); $line = fgets($handle); fclose($handle); if (Preg::isMatchStrictGroups('{^#!/(?:usr/bin/env )?(?:[^/]+/)*(.+)$}m', (string) $line, $match)) { return trim($match[1]); } return 'php'; } protected function getBinaries(PackageInterface $package): array { return $package->getBinaries(); } protected function installFullBinaries(string $binPath, string $link, string $bin, PackageInterface $package): void { if ('.bat' !== substr($binPath, -4)) { $this->installUnixyProxyBinaries($binPath, $link); $link .= '.bat'; if (file_exists($link)) { $this->io->writeError(' Skipped installation of bin '.$bin.'.bat proxy for package '.$package->getName().': a .bat proxy was already installed'); } } if (!file_exists($link)) { file_put_contents($link, $this->generateWindowsProxyCode($binPath, $link)); Silencer::call('chmod', $link, 0777 & ~umask()); } } protected function installUnixyProxyBinaries(string $binPath, string $link): void { file_put_contents($link, $this->generateUnixyProxyCode($binPath, $link)); Silencer::call('chmod', $link, 0777 & ~umask()); } protected function initializeBinDir(): void { $this->filesystem->ensureDirectoryExists($this->binDir); $this->binDir = realpath($this->binDir); } protected function generateWindowsProxyCode(string $bin, string $link): string { $binPath = $this->filesystem->findShortestPath($link, $bin); $caller = self::determineBinaryCaller($bin); if ($caller === 'php') { return "@ECHO OFF\r\n". "setlocal DISABLEDELAYEDEXPANSION\r\n". "SET BIN_TARGET=%~dp0/".trim(ProcessExecutor::escape(basename($link, '.bat')), '"\'')."\r\n". "SET COMPOSER_RUNTIME_BIN_DIR=%~dp0\r\n". "{$caller} \"%BIN_TARGET%\" %*\r\n"; } return "@ECHO OFF\r\n". "setlocal DISABLEDELAYEDEXPANSION\r\n". "SET BIN_TARGET=%~dp0/".trim(ProcessExecutor::escape($binPath), '"\'')."\r\n". "SET COMPOSER_RUNTIME_BIN_DIR=%~dp0\r\n". "{$caller} \"%BIN_TARGET%\" %*\r\n"; } protected function generateUnixyProxyCode(string $bin, string $link): string { $binPath = $this->filesystem->findShortestPath($link, $bin); $binDir = ProcessExecutor::escape(dirname($binPath)); $binFile = basename($binPath); $binContents = (string) file_get_contents($bin, false, null, 0, 500); if (Preg::isMatch('{^(#!.*\r?\n)?[\r\n\t ]*<\?php}', $binContents, $match)) { $proxyCode = $match[1] === null ? '#!/usr/bin/env php' : trim($match[1]); $binPathExported = $this->filesystem->findShortestPathCode($link, $bin, false, true); $streamProxyCode = $streamHint = ''; $globalsCode = '$GLOBALS[\'_composer_bin_dir\'] = __DIR__;'."\n"; $phpunitHack1 = $phpunitHack2 = ''; if ($this->vendorDir !== null) { $vendorDirReal = realpath($this->vendorDir); if ($vendorDirReal === false) { $vendorDirReal = $this->vendorDir; } $globalsCode .= '$GLOBALS[\'_composer_autoload_path\'] = ' . $this->filesystem->findShortestPathCode($link, $vendorDirReal . '/autoload.php', false, true).";\n"; } if ($this->filesystem->normalizePath($bin) === $this->filesystem->normalizePath($this->vendorDir.'/phpunit/phpunit/phpunit')) { $globalsCode .= '$GLOBALS[\'__PHPUNIT_ISOLATION_EXCLUDE_LIST\'] = $GLOBALS[\'__PHPUNIT_ISOLATION_BLACKLIST\'] = array(realpath('.$binPathExported.'));'."\n"; $phpunitHack1 = "'phpvfscomposer://'."; $phpunitHack2 = ' $data = str_replace(\'__DIR__\', var_export(dirname($this->realpath), true), $data); $data = str_replace(\'__FILE__\', var_export($this->realpath, true), $data);'; } if (trim($match[0]) !== 'realpath = realpath(\$opened_path) ?: \$opened_path; \$opened_path = $phpunitHack1\$this->realpath; \$this->handle = fopen(\$this->realpath, \$mode); \$this->position = 0; return (bool) \$this->handle; } public function stream_read(\$count) { \$data = fread(\$this->handle, \$count); if (\$this->position === 0) { \$data = preg_replace('{^#!.*\\r?\\n}', '', \$data); }$phpunitHack2 \$this->position += strlen(\$data); return \$data; } public function stream_cast(\$castAs) { return \$this->handle; } public function stream_close() { fclose(\$this->handle); } public function stream_lock(\$operation) { return \$operation ? flock(\$this->handle, \$operation) : true; } public function stream_seek(\$offset, \$whence) { if (0 === fseek(\$this->handle, \$offset, \$whence)) { \$this->position = ftell(\$this->handle); return true; } return false; } public function stream_tell() { return \$this->position; } public function stream_eof() { return feof(\$this->handle); } public function stream_stat() { return array(); } public function stream_set_option(\$option, \$arg1, \$arg2) { return true; } public function url_stat(\$path, \$flags) { \$path = substr(\$path, 17); if (file_exists(\$path)) { return stat(\$path); } return false; } } } if ( (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) ) { return include("phpvfscomposer://" . $binPathExported); } } STREAMPROXY; } return $proxyCode . "\n" . << /dev/null) if [ -z "\$self" ]; then self="\$selfArg" fi dir=\$(cd "\${self%[/\\\\]*}" > /dev/null; cd $binDir && pwd) if [ -d /proc/cygdrive ]; then case \$(which php) in \$(readlink -n /proc/cygdrive)/*) # We are in Cygwin using Windows php, so the path must be translated dir=\$(cygpath -m "\$dir"); ;; esac fi export COMPOSER_RUNTIME_BIN_DIR="\$(cd "\${self%[/\\\\]*}" > /dev/null; pwd)" # If bash is sourcing this file, we have to source the target as well bashSource="\$BASH_SOURCE" if [ -n "\$bashSource" ]; then if [ "\$bashSource" != "\$0" ]; then source "\${dir}/$binFile" "\$@" return fi fi exec "\${dir}/$binFile" "\$@" PROXY; } } loop = $loop; $this->io = $io; $this->eventDispatcher = $eventDispatcher; } public function reset(): void { $this->notifiablePackages = []; FileDownloader::$downloadMetadata = []; } public function addInstaller(InstallerInterface $installer): void { array_unshift($this->installers, $installer); $this->cache = []; } public function removeInstaller(InstallerInterface $installer): void { if (false !== ($key = array_search($installer, $this->installers, true))) { array_splice($this->installers, $key, 1); $this->cache = []; } } public function disablePlugins(): void { foreach ($this->installers as $i => $installer) { if (!$installer instanceof PluginInstaller) { continue; } $installer->disablePlugins(); } } public function getInstaller(string $type): InstallerInterface { $type = strtolower($type); if (isset($this->cache[$type])) { return $this->cache[$type]; } foreach ($this->installers as $installer) { if ($installer->supports($type)) { return $this->cache[$type] = $installer; } } throw new \InvalidArgumentException('Unknown installer type: '.$type); } public function isPackageInstalled(InstalledRepositoryInterface $repo, PackageInterface $package): bool { if ($package instanceof AliasPackage) { return $repo->hasPackage($package) && $this->isPackageInstalled($repo, $package->getAliasOf()); } return $this->getInstaller($package->getType())->isInstalled($repo, $package); } public function ensureBinariesPresence(PackageInterface $package): void { try { $installer = $this->getInstaller($package->getType()); } catch (\InvalidArgumentException $e) { return; } if ($installer instanceof BinaryPresenceInterface) { $installer->ensureBinariesPresence($package); } } public function execute(InstalledRepositoryInterface $repo, array $operations, bool $devMode = true, bool $runScripts = true, bool $downloadOnly = false): void { $cleanupPromises = []; $signalHandler = SignalHandler::create([SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP], function (string $signal, SignalHandler $handler) use (&$cleanupPromises) { $this->io->writeError('Received '.$signal.', aborting', true, IOInterface::DEBUG); $this->runCleanup($cleanupPromises); $handler->exitWithLastSignal(); }); try { $batches = []; $batch = []; foreach ($operations as $index => $operation) { if ($operation instanceof UpdateOperation || $operation instanceof InstallOperation) { $package = $operation instanceof UpdateOperation ? $operation->getTargetPackage() : $operation->getPackage(); if ($package->getType() === 'composer-plugin') { $extra = $package->getExtra(); if (isset($extra['plugin-modifies-downloads']) && $extra['plugin-modifies-downloads'] === true) { if (count($batch) > 0) { $batches[] = $batch; } $batches[] = [$index => $operation]; $batch = []; continue; } } } $batch[$index] = $operation; } if (count($batch) > 0) { $batches[] = $batch; } foreach ($batches as $batchToExecute) { $this->downloadAndExecuteBatch($repo, $batchToExecute, $cleanupPromises, $devMode, $runScripts, $downloadOnly, $operations); } } catch (\Exception $e) { $this->runCleanup($cleanupPromises); throw $e; } finally { $signalHandler->unregister(); } if ($downloadOnly) { return; } $repo->write($devMode, $this); } private function downloadAndExecuteBatch(InstalledRepositoryInterface $repo, array $operations, array &$cleanupPromises, bool $devMode, bool $runScripts, bool $downloadOnly, array $allOperations): void { $promises = []; foreach ($operations as $index => $operation) { $opType = $operation->getOperationType(); if (!in_array($opType, ['update', 'install', 'uninstall'], true)) { continue; } if ($opType === 'update') { $package = $operation->getTargetPackage(); $initialPackage = $operation->getInitialPackage(); } else { $package = $operation->getPackage(); $initialPackage = null; } $installer = $this->getInstaller($package->getType()); $cleanupPromises[$index] = static function () use ($opType, $installer, $package, $initialPackage): ?PromiseInterface { if (null === $package->getInstallationSource()) { return \React\Promise\resolve(null); } return $installer->cleanup($opType, $package, $initialPackage); }; if ($opType !== 'uninstall') { $promise = $installer->download($package, $initialPackage); if (null !== $promise) { $promises[] = $promise; } } } if (count($promises) > 0) { $this->waitOnPromises($promises); } if ($downloadOnly) { $this->runCleanup($cleanupPromises); return; } $batches = []; $batch = []; foreach ($operations as $index => $operation) { if ($operation instanceof InstallOperation || $operation instanceof UpdateOperation) { $package = $operation instanceof UpdateOperation ? $operation->getTargetPackage() : $operation->getPackage(); if ($package->getType() === 'composer-plugin' || $package->getType() === 'composer-installer') { if (count($batch) > 0) { $batches[] = $batch; } $batches[] = [$index => $operation]; $batch = []; continue; } } $batch[$index] = $operation; } if (count($batch) > 0) { $batches[] = $batch; } foreach ($batches as $batchToExecute) { $this->executeBatch($repo, $batchToExecute, $cleanupPromises, $devMode, $runScripts, $allOperations); } } private function executeBatch(InstalledRepositoryInterface $repo, array $operations, array $cleanupPromises, bool $devMode, bool $runScripts, array $allOperations): void { $promises = []; $postExecCallbacks = []; foreach ($operations as $index => $operation) { $opType = $operation->getOperationType(); if (!in_array($opType, ['update', 'install', 'uninstall'], true)) { if ($this->io->isDebug()) { $this->io->writeError(' - ' . $operation->show(false)); } $this->{$opType}($repo, $operation); continue; } if ($opType === 'update') { $package = $operation->getTargetPackage(); $initialPackage = $operation->getInitialPackage(); } else { $package = $operation->getPackage(); $initialPackage = null; } $installer = $this->getInstaller($package->getType()); $eventName = [ 'install' => PackageEvents::PRE_PACKAGE_INSTALL, 'update' => PackageEvents::PRE_PACKAGE_UPDATE, 'uninstall' => PackageEvents::PRE_PACKAGE_UNINSTALL, ][$opType]; if ($runScripts && $this->eventDispatcher !== null) { $this->eventDispatcher->dispatchPackageEvent($eventName, $devMode, $repo, $allOperations, $operation); } $dispatcher = $this->eventDispatcher; $io = $this->io; $promise = $installer->prepare($opType, $package, $initialPackage); if (!$promise instanceof PromiseInterface) { $promise = \React\Promise\resolve(null); } $promise = $promise->then(function () use ($opType, $repo, $operation) { return $this->{$opType}($repo, $operation); })->then($cleanupPromises[$index]) ->then(function () use ($devMode, $repo): void { $repo->write($devMode, $this); }, static function ($e) use ($opType, $package, $io): void { $io->writeError(' ' . ucfirst($opType) .' of '.$package->getPrettyName().' failed'); throw $e; }); $eventName = [ 'install' => PackageEvents::POST_PACKAGE_INSTALL, 'update' => PackageEvents::POST_PACKAGE_UPDATE, 'uninstall' => PackageEvents::POST_PACKAGE_UNINSTALL, ][$opType]; if ($runScripts && $dispatcher !== null) { $postExecCallbacks[] = static function () use ($dispatcher, $eventName, $devMode, $repo, $allOperations, $operation): void { $dispatcher->dispatchPackageEvent($eventName, $devMode, $repo, $allOperations, $operation); }; } $promises[] = $promise; } if (count($promises) > 0) { $this->waitOnPromises($promises); } Platform::workaroundFilesystemIssues(); foreach ($postExecCallbacks as $cb) { $cb(); } } private function waitOnPromises(array $promises): void { $progress = null; if ( $this->outputProgress && $this->io instanceof ConsoleIO && !((bool) Platform::getEnv('CI')) && !$this->io->isDebug() && count($promises) > 1 ) { $progress = $this->io->getProgressBar(); } $this->loop->wait($promises, $progress); if ($progress !== null) { $progress->clear(); if (!$this->io->isDecorated()) { $this->io->writeError(''); } } } public function download(PackageInterface $package): ?PromiseInterface { $installer = $this->getInstaller($package->getType()); $promise = $installer->cleanup("install", $package); return $promise; } public function install(InstalledRepositoryInterface $repo, InstallOperation $operation): ?PromiseInterface { $package = $operation->getPackage(); $installer = $this->getInstaller($package->getType()); $promise = $installer->install($repo, $package); $this->markForNotification($package); return $promise; } public function update(InstalledRepositoryInterface $repo, UpdateOperation $operation): ?PromiseInterface { $initial = $operation->getInitialPackage(); $target = $operation->getTargetPackage(); $initialType = $initial->getType(); $targetType = $target->getType(); if ($initialType === $targetType) { $installer = $this->getInstaller($initialType); $promise = $installer->update($repo, $initial, $target); $this->markForNotification($target); } else { $promise = $this->getInstaller($initialType)->uninstall($repo, $initial); if (!$promise instanceof PromiseInterface) { $promise = \React\Promise\resolve(null); } $installer = $this->getInstaller($targetType); $promise = $promise->then(static function () use ($installer, $repo, $target): PromiseInterface { $promise = $installer->install($repo, $target); if ($promise instanceof PromiseInterface) { return $promise; } return \React\Promise\resolve(null); }); } return $promise; } public function uninstall(InstalledRepositoryInterface $repo, UninstallOperation $operation): ?PromiseInterface { $package = $operation->getPackage(); $installer = $this->getInstaller($package->getType()); return $installer->uninstall($repo, $package); } public function markAliasInstalled(InstalledRepositoryInterface $repo, MarkAliasInstalledOperation $operation): void { $package = $operation->getPackage(); if (!$repo->hasPackage($package)) { $repo->addPackage(clone $package); } } public function markAliasUninstalled(InstalledRepositoryInterface $repo, MarkAliasUninstalledOperation $operation): void { $package = $operation->getPackage(); $repo->removePackage($package); } public function getInstallPath(PackageInterface $package): ?string { $installer = $this->getInstaller($package->getType()); return $installer->getInstallPath($package); } public function setOutputProgress(bool $outputProgress): void { $this->outputProgress = $outputProgress; } public function notifyInstalls(IOInterface $io): void { $promises = []; try { foreach ($this->notifiablePackages as $repoUrl => $packages) { if (str_contains($repoUrl, '%package%')) { foreach ($packages as $package) { $url = str_replace('%package%', $package->getPrettyName(), $repoUrl); $params = [ 'version' => $package->getPrettyVersion(), 'version_normalized' => $package->getVersion(), ]; $opts = [ 'retry-auth-failure' => false, 'http' => [ 'method' => 'POST', 'header' => ['Content-type: application/x-www-form-urlencoded'], 'content' => http_build_query($params, '', '&'), 'timeout' => 3, ], ]; $promises[] = $this->loop->getHttpDownloader()->add($url, $opts); } continue; } $postData = ['downloads' => []]; foreach ($packages as $package) { $packageNotification = [ 'name' => $package->getPrettyName(), 'version' => $package->getVersion(), ]; if (strpos($repoUrl, 'packagist.org/') !== false) { if (isset(FileDownloader::$downloadMetadata[$package->getName()])) { $packageNotification['downloaded'] = FileDownloader::$downloadMetadata[$package->getName()]; } else { $packageNotification['downloaded'] = false; } } $postData['downloads'][] = $packageNotification; } $opts = [ 'retry-auth-failure' => false, 'http' => [ 'method' => 'POST', 'header' => ['Content-Type: application/json'], 'content' => json_encode($postData), 'timeout' => 6, ], ]; $promises[] = $this->loop->getHttpDownloader()->add($repoUrl, $opts); } $this->loop->wait($promises); } catch (\Exception $e) { } $this->reset(); } private function markForNotification(PackageInterface $package): void { if ($package->getNotificationUrl() !== null) { $this->notifiablePackages[$package->getNotificationUrl()][$package->getName()] = $package; } } private function runCleanup(array $cleanupPromises): void { $promises = []; $this->loop->abortJobs(); foreach ($cleanupPromises as $cleanup) { $promises[] = new \React\Promise\Promise(static function ($resolve) use ($cleanup): void { $promise = $cleanup(); if (!$promise instanceof PromiseInterface) { $resolve(null); } else { $promise->then(static function () use ($resolve): void { $resolve(null); }); } }); } if (count($promises) > 0) { $this->loop->wait($promises); } } } composer = $composer; $this->io = $io; $this->devMode = $devMode; $this->executeOperations = $executeOperations; $this->transaction = $transaction; } public function getComposer(): Composer { return $this->composer; } public function getIO(): IOInterface { return $this->io; } public function isDevMode(): bool { return $this->devMode; } public function isExecutingOperations(): bool { return $this->executeOperations; } public function getTransaction(): ?Transaction { return $this->transaction; } } composer = $composer; $this->downloadManager = $composer instanceof Composer ? $composer->getDownloadManager() : null; $this->io = $io; $this->type = $type; $this->filesystem = $filesystem ?: new Filesystem(); $this->vendorDir = rtrim($composer->getConfig()->get('vendor-dir'), '/'); $this->binaryInstaller = $binaryInstaller ?: new BinaryInstaller($this->io, rtrim($composer->getConfig()->get('bin-dir'), '/'), $composer->getConfig()->get('bin-compat'), $this->filesystem, $this->vendorDir); } public function supports(string $packageType) { return $packageType === $this->type || null === $this->type; } public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package) { if (!$repo->hasPackage($package)) { return false; } $installPath = $this->getInstallPath($package); if (Filesystem::isReadable($installPath)) { return true; } if (Platform::isWindows() && $this->filesystem->isJunction($installPath)) { return true; } if (is_link($installPath)) { if (realpath($installPath) === false) { return false; } return true; } return false; } public function download(PackageInterface $package, ?PackageInterface $prevPackage = null) { $this->initializeVendorDir(); $downloadPath = $this->getInstallPath($package); return $this->getDownloadManager()->download($package, $downloadPath, $prevPackage); } public function prepare($type, PackageInterface $package, ?PackageInterface $prevPackage = null) { $this->initializeVendorDir(); $downloadPath = $this->getInstallPath($package); return $this->getDownloadManager()->prepare($type, $package, $downloadPath, $prevPackage); } public function cleanup($type, PackageInterface $package, ?PackageInterface $prevPackage = null) { $this->initializeVendorDir(); $downloadPath = $this->getInstallPath($package); return $this->getDownloadManager()->cleanup($type, $package, $downloadPath, $prevPackage); } public function install(InstalledRepositoryInterface $repo, PackageInterface $package) { $this->initializeVendorDir(); $downloadPath = $this->getInstallPath($package); if (!Filesystem::isReadable($downloadPath) && $repo->hasPackage($package)) { $this->binaryInstaller->removeBinaries($package); } $promise = $this->installCode($package); if (!$promise instanceof PromiseInterface) { $promise = \React\Promise\resolve(null); } $binaryInstaller = $this->binaryInstaller; $installPath = $this->getInstallPath($package); return $promise->then(static function () use ($binaryInstaller, $installPath, $package, $repo): void { $binaryInstaller->installBinaries($package, $installPath); if (!$repo->hasPackage($package)) { $repo->addPackage(clone $package); } }); } public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) { if (!$repo->hasPackage($initial)) { throw new \InvalidArgumentException('Package is not installed: '.$initial); } $this->initializeVendorDir(); $this->binaryInstaller->removeBinaries($initial); $promise = $this->updateCode($initial, $target); if (!$promise instanceof PromiseInterface) { $promise = \React\Promise\resolve(null); } $binaryInstaller = $this->binaryInstaller; $installPath = $this->getInstallPath($target); return $promise->then(static function () use ($binaryInstaller, $installPath, $target, $initial, $repo): void { $binaryInstaller->installBinaries($target, $installPath); $repo->removePackage($initial); if (!$repo->hasPackage($target)) { $repo->addPackage(clone $target); } }); } public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) { if (!$repo->hasPackage($package)) { throw new \InvalidArgumentException('Package is not installed: '.$package); } $promise = $this->removeCode($package); if (!$promise instanceof PromiseInterface) { $promise = \React\Promise\resolve(null); } $binaryInstaller = $this->binaryInstaller; $downloadPath = $this->getPackageBasePath($package); $filesystem = $this->filesystem; return $promise->then(static function () use ($binaryInstaller, $filesystem, $downloadPath, $package, $repo): void { $binaryInstaller->removeBinaries($package); $repo->removePackage($package); if (strpos($package->getName(), '/')) { $packageVendorDir = dirname($downloadPath); if (is_dir($packageVendorDir) && $filesystem->isDirEmpty($packageVendorDir)) { Silencer::call('rmdir', $packageVendorDir); } } }); } public function getInstallPath(PackageInterface $package) { $this->initializeVendorDir(); $basePath = ($this->vendorDir ? $this->vendorDir.'/' : '') . $package->getPrettyName(); $targetDir = $package->getTargetDir(); return $basePath . ($targetDir ? '/'.$targetDir : ''); } public function ensureBinariesPresence(PackageInterface $package) { $this->binaryInstaller->installBinaries($package, $this->getInstallPath($package), false); } protected function getPackageBasePath(PackageInterface $package) { $installPath = $this->getInstallPath($package); $targetDir = $package->getTargetDir(); if ($targetDir) { return Preg::replace('{/*'.str_replace('/', '/+', preg_quote($targetDir)).'/?$}', '', $installPath); } return $installPath; } protected function installCode(PackageInterface $package) { $downloadPath = $this->getInstallPath($package); return $this->getDownloadManager()->install($package, $downloadPath); } protected function updateCode(PackageInterface $initial, PackageInterface $target) { $initialDownloadPath = $this->getInstallPath($initial); $targetDownloadPath = $this->getInstallPath($target); if ($targetDownloadPath !== $initialDownloadPath) { if (strpos($initialDownloadPath, $targetDownloadPath) === 0 || strpos($targetDownloadPath, $initialDownloadPath) === 0 ) { $promise = $this->removeCode($initial); if (!$promise instanceof PromiseInterface) { $promise = \React\Promise\resolve(null); } return $promise->then(function () use ($target): PromiseInterface { $promise = $this->installCode($target); if ($promise instanceof PromiseInterface) { return $promise; } return \React\Promise\resolve(null); }); } $this->filesystem->rename($initialDownloadPath, $targetDownloadPath); } return $this->getDownloadManager()->update($initial, $target, $targetDownloadPath); } protected function removeCode(PackageInterface $package) { $downloadPath = $this->getPackageBasePath($package); return $this->getDownloadManager()->remove($package, $downloadPath); } protected function initializeVendorDir() { $this->filesystem->ensureDirectoryExists($this->vendorDir); $this->vendorDir = realpath($this->vendorDir); } protected function getDownloadManager(): DownloadManager { assert($this->downloadManager instanceof DownloadManager, new \LogicException(self::class.' should be initialized with a fully loaded Composer instance to be able to install/... packages')); return $this->downloadManager; } } io = $io; } public function supports(string $packageType) { return $packageType === 'metapackage'; } public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package) { return $repo->hasPackage($package); } public function download(PackageInterface $package, ?PackageInterface $prevPackage = null) { return \React\Promise\resolve(null); } public function prepare($type, PackageInterface $package, ?PackageInterface $prevPackage = null) { return \React\Promise\resolve(null); } public function cleanup($type, PackageInterface $package, ?PackageInterface $prevPackage = null) { return \React\Promise\resolve(null); } public function install(InstalledRepositoryInterface $repo, PackageInterface $package) { $this->io->writeError(" - " . InstallOperation::format($package)); $repo->addPackage(clone $package); return \React\Promise\resolve(null); } public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) { if (!$repo->hasPackage($initial)) { throw new \InvalidArgumentException('Package is not installed: '.$initial); } $this->io->writeError(" - " . UpdateOperation::format($initial, $target)); $repo->removePackage($initial); $repo->addPackage(clone $target); return \React\Promise\resolve(null); } public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) { if (!$repo->hasPackage($package)) { throw new \InvalidArgumentException('Package is not installed: '.$package); } $this->io->writeError(" - " . UninstallOperation::format($package)); $repo->removePackage($package); return \React\Promise\resolve(null); } public function getInstallPath(PackageInterface $package) { return null; } } hasPackage($package); } public function download(PackageInterface $package, ?PackageInterface $prevPackage = null) { return \React\Promise\resolve(null); } public function prepare($type, PackageInterface $package, ?PackageInterface $prevPackage = null) { return \React\Promise\resolve(null); } public function cleanup($type, PackageInterface $package, ?PackageInterface $prevPackage = null) { return \React\Promise\resolve(null); } public function install(InstalledRepositoryInterface $repo, PackageInterface $package) { if (!$repo->hasPackage($package)) { $repo->addPackage(clone $package); } return \React\Promise\resolve(null); } public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) { if (!$repo->hasPackage($initial)) { throw new \InvalidArgumentException('Package is not installed: '.$initial); } $repo->removePackage($initial); if (!$repo->hasPackage($target)) { $repo->addPackage(clone $target); } return \React\Promise\resolve(null); } public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) { if (!$repo->hasPackage($package)) { throw new \InvalidArgumentException('Package is not installed: '.$package); } $repo->removePackage($package); return \React\Promise\resolve(null); } public function getInstallPath(PackageInterface $package) { $targetDir = $package->getTargetDir(); return $package->getPrettyName() . ($targetDir ? '/'.$targetDir : ''); } } composer = $composer; $this->io = $io; $this->devMode = $devMode; $this->localRepo = $localRepo; $this->operations = $operations; $this->operation = $operation; } public function getComposer(): Composer { return $this->composer; } public function getIO(): IOInterface { return $this->io; } public function isDevMode(): bool { return $this->devMode; } public function getLocalRepo(): RepositoryInterface { return $this->localRepo; } public function getOperations(): array { return $this->operations; } public function getOperation(): OperationInterface { return $this->operation; } } getPluginManager()->disablePlugins(); } public function prepare($type, PackageInterface $package, ?PackageInterface $prevPackage = null) { if (($type === 'install' || $type === 'update') && !$this->getPluginManager()->arePluginsDisabled('local')) { $this->getPluginManager()->isPluginAllowed($package->getName(), false, true === ($package->getExtra()['plugin-optional'] ?? false)); } return parent::prepare($type, $package, $prevPackage); } public function download(PackageInterface $package, ?PackageInterface $prevPackage = null) { $extra = $package->getExtra(); if (empty($extra['class'])) { throw new \UnexpectedValueException('Error while installing '.$package->getPrettyName().', composer-plugin packages should have a class defined in their extra key to be usable.'); } return parent::download($package, $prevPackage); } public function install(InstalledRepositoryInterface $repo, PackageInterface $package) { $promise = parent::install($repo, $package); if (!$promise instanceof PromiseInterface) { $promise = \React\Promise\resolve(null); } return $promise->then(function () use ($package, $repo): void { try { Platform::workaroundFilesystemIssues(); $this->getPluginManager()->registerPackage($package, true); } catch (\Exception $e) { $this->rollbackInstall($e, $repo, $package); } }); } public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) { $promise = parent::update($repo, $initial, $target); if (!$promise instanceof PromiseInterface) { $promise = \React\Promise\resolve(null); } return $promise->then(function () use ($initial, $target, $repo): void { try { Platform::workaroundFilesystemIssues(); $this->getPluginManager()->deactivatePackage($initial); $this->getPluginManager()->registerPackage($target, true); } catch (\Exception $e) { $this->rollbackInstall($e, $repo, $target); } }); } public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) { $this->getPluginManager()->uninstallPackage($package); return parent::uninstall($repo, $package); } private function rollbackInstall(\Exception $e, InstalledRepositoryInterface $repo, PackageInterface $package): void { $this->io->writeError('Plugin initialization failed ('.$e->getMessage().'), uninstalling plugin'); parent::uninstall($repo, $package); throw $e; } protected function getPluginManager(): PluginManager { assert($this->composer instanceof Composer, new \LogicException(self::class.' should be initialized with a fully loaded Composer instance.')); $pluginManager = $this->composer->getPluginManager(); return $pluginManager; } } installPath = rtrim(strtr($installPath, '\\', '/'), '/').'/'; $this->downloadManager = $dm; $this->filesystem = $fs; } public function supports(string $packageType): bool { return true; } public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package): bool { return false; } public function download(PackageInterface $package, ?PackageInterface $prevPackage = null): ?PromiseInterface { $installPath = $this->installPath; if (file_exists($installPath) && !$this->filesystem->isDirEmpty($installPath)) { throw new \InvalidArgumentException("Project directory $installPath is not empty."); } if (!is_dir($installPath)) { mkdir($installPath, 0777, true); } return $this->downloadManager->download($package, $installPath, $prevPackage); } public function prepare($type, PackageInterface $package, ?PackageInterface $prevPackage = null): ?PromiseInterface { return $this->downloadManager->prepare($type, $package, $this->installPath, $prevPackage); } public function cleanup($type, PackageInterface $package, ?PackageInterface $prevPackage = null): ?PromiseInterface { return $this->downloadManager->cleanup($type, $package, $this->installPath, $prevPackage); } public function install(InstalledRepositoryInterface $repo, PackageInterface $package): ?PromiseInterface { return $this->downloadManager->install($package, $this->installPath); } public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target): ?PromiseInterface { throw new \InvalidArgumentException("not supported"); } public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package): ?PromiseInterface { throw new \InvalidArgumentException("not supported"); } public function getInstallPath(PackageInterface $package): string { return $this->installPath; } } io = $io; } public function getPackages(): array { return $this->suggestedPackages; } public function addPackage(string $source, string $target, string $reason): SuggestedPackagesReporter { $this->suggestedPackages[] = [ 'source' => $source, 'target' => $target, 'reason' => $reason, ]; return $this; } public function addSuggestionsFromPackage(PackageInterface $package): SuggestedPackagesReporter { $source = $package->getPrettyName(); foreach ($package->getSuggests() as $target => $reason) { $this->addPackage( $source, $target, $reason ); } return $this; } public function output(int $mode, ?InstalledRepository $installedRepo = null, ?PackageInterface $onlyDependentsOf = null): void { $suggestedPackages = $this->getFilteredSuggestions($installedRepo, $onlyDependentsOf); $suggesters = []; $suggested = []; foreach ($suggestedPackages as $suggestion) { $suggesters[$suggestion['source']][$suggestion['target']] = $suggestion['reason']; $suggested[$suggestion['target']][$suggestion['source']] = $suggestion['reason']; } ksort($suggesters); ksort($suggested); if ($mode & self::MODE_LIST) { foreach (array_keys($suggested) as $name) { $this->io->write(sprintf('%s', $name)); } return; } if ($mode & self::MODE_BY_PACKAGE) { foreach ($suggesters as $suggester => $suggestions) { $this->io->write(sprintf('%s suggests:', $suggester)); foreach ($suggestions as $suggestion => $reason) { $this->io->write(sprintf(' - %s' . ($reason ? ': %s' : ''), $suggestion, $this->escapeOutput($reason))); } $this->io->write(''); } } if ($mode & self::MODE_BY_SUGGESTION) { if ($mode & self::MODE_BY_PACKAGE) { $this->io->write(str_repeat('-', 78)); } foreach ($suggested as $suggestion => $suggesters) { $this->io->write(sprintf('%s is suggested by:', $suggestion)); foreach ($suggesters as $suggester => $reason) { $this->io->write(sprintf(' - %s' . ($reason ? ': %s' : ''), $suggester, $this->escapeOutput($reason))); } $this->io->write(''); } } if ($onlyDependentsOf) { $allSuggestedPackages = $this->getFilteredSuggestions($installedRepo); $diff = count($allSuggestedPackages) - count($suggestedPackages); if ($diff) { $this->io->write(''.$diff.' additional suggestions by transitive dependencies can be shown with --all'); } } } public function outputMinimalistic(?InstalledRepository $installedRepo = null, ?PackageInterface $onlyDependentsOf = null): void { $suggestedPackages = $this->getFilteredSuggestions($installedRepo, $onlyDependentsOf); if ($suggestedPackages) { $this->io->writeError(''.count($suggestedPackages).' package suggestions were added by new dependencies, use `composer suggest` to see details.'); } } private function getFilteredSuggestions(?InstalledRepository $installedRepo = null, ?PackageInterface $onlyDependentsOf = null): array { $suggestedPackages = $this->getPackages(); $installedNames = []; if (null !== $installedRepo && !empty($suggestedPackages)) { foreach ($installedRepo->getPackages() as $package) { $installedNames = array_merge( $installedNames, $package->getNames() ); } } $sourceFilter = []; if ($onlyDependentsOf) { $sourceFilter = array_map(static function ($link): string { return $link->getTarget(); }, array_merge($onlyDependentsOf->getRequires(), $onlyDependentsOf->getDevRequires())); $sourceFilter[] = $onlyDependentsOf->getName(); } $suggestions = []; foreach ($suggestedPackages as $suggestion) { if (in_array($suggestion['target'], $installedNames) || (\count($sourceFilter) > 0 && !in_array($suggestion['source'], $sourceFilter))) { continue; } $suggestions[] = $suggestion; } return $suggestions; } private function escapeOutput(string $string): string { return OutputFormatter::escape( $this->removeControlCharacters($string) ); } private function removeControlCharacters(string $string): string { return Preg::replace( '/[[:cntrl:]]/', '', str_replace("\n", ' ', $string) ); } } path = $path; if (null === $httpDownloader && Preg::isMatch('{^https?://}i', $path)) { throw new \InvalidArgumentException('http urls require a HttpDownloader instance to be passed'); } $this->httpDownloader = $httpDownloader; $this->io = $io; } public function getPath(): string { return $this->path; } public function exists(): bool { return is_file($this->path); } public function read() { try { if ($this->httpDownloader) { $json = $this->httpDownloader->get($this->path)->getBody(); } else { if (!Filesystem::isReadable($this->path)) { throw new \RuntimeException('The file "'.$this->path.'" is not readable.'); } if ($this->io && $this->io->isDebug()) { $realpathInfo = ''; $realpath = realpath($this->path); if (false !== $realpath && $realpath !== $this->path) { $realpathInfo = ' (' . $realpath . ')'; } $this->io->writeError('Reading ' . $this->path . $realpathInfo); } $json = file_get_contents($this->path); } } catch (TransportException $e) { throw new \RuntimeException($e->getMessage(), 0, $e); } catch (\Exception $e) { throw new \RuntimeException('Could not read '.$this->path."\n\n".$e->getMessage()); } if ($json === false) { throw new \RuntimeException('Could not read '.$this->path); } $this->indent = self::detectIndenting($json); return static::parseJson($json, $this->path); } public function write(array $hash, int $options = JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) { if ($this->path === 'php://memory') { file_put_contents($this->path, static::encode($hash, $options, $this->indent)); return; } $dir = dirname($this->path); if (!is_dir($dir)) { if (file_exists($dir)) { throw new \UnexpectedValueException( realpath($dir).' exists and is not a directory.' ); } if (!@mkdir($dir, 0777, true)) { throw new \UnexpectedValueException( $dir.' does not exist and could not be created.' ); } } $retries = 3; while ($retries--) { try { $this->filePutContentsIfModified($this->path, static::encode($hash, $options, $this->indent). ($options & JSON_PRETTY_PRINT ? "\n" : '')); break; } catch (\Exception $e) { if ($retries > 0) { usleep(500000); continue; } throw $e; } } } private function filePutContentsIfModified(string $path, string $content) { $currentContent = @file_get_contents($path); if (false === $currentContent || $currentContent !== $content) { return file_put_contents($path, $content); } return 0; } public function validateSchema(int $schema = self::STRICT_SCHEMA, ?string $schemaFile = null): bool { if (!Filesystem::isReadable($this->path)) { throw new \RuntimeException('The file "'.$this->path.'" is not readable.'); } $content = file_get_contents($this->path); $data = json_decode($content); if (null === $data && 'null' !== $content) { self::validateSyntax($content, $this->path); } return self::validateJsonSchema($this->path, $data, $schema, $schemaFile); } public static function validateJsonSchema(string $source, $data, int $schema, ?string $schemaFile = null): bool { $isComposerSchemaFile = false; if (null === $schemaFile) { if ($schema === self::LOCK_SCHEMA) { $schemaFile = self::LOCK_SCHEMA_PATH; } else { $isComposerSchemaFile = true; $schemaFile = self::COMPOSER_SCHEMA_PATH; } } if (false === strpos($schemaFile, '://')) { $schemaFile = 'file://' . $schemaFile; } $schemaData = (object) ['$ref' => $schemaFile, '$schema' => "https://json-schema.org/draft-04/schema#"]; if ($schema === self::STRICT_SCHEMA && $isComposerSchemaFile) { $schemaData = json_decode((string) file_get_contents($schemaFile)); $schemaData->additionalProperties = false; $schemaData->required = ['name', 'description']; } elseif ($schema === self::AUTH_SCHEMA && $isComposerSchemaFile) { $schemaData = (object) ['$ref' => $schemaFile.'#/properties/config', '$schema' => "https://json-schema.org/draft-04/schema#"]; } $validator = new Validator(); $data = json_decode((string) json_encode($data)); $validator->validate($data, $schemaData); if (!$validator->isValid()) { $errors = []; foreach ($validator->getErrors() as $error) { $errors[] = ($error['property'] ? $error['property'].' : ' : '').$error['message']; } throw new JsonValidationException('"'.$source.'" does not match the expected JSON schema', $errors); } return true; } public static function encode($data, int $options = \JSON_UNESCAPED_SLASHES | \JSON_PRETTY_PRINT | \JSON_UNESCAPED_UNICODE, string $indent = self::INDENT_DEFAULT): string { $json = json_encode($data, $options); if (false === $json) { self::throwEncodeError(json_last_error()); } if (($options & JSON_PRETTY_PRINT) > 0 && $indent !== self::INDENT_DEFAULT) { return Preg::replaceCallback( '#^ {4,}#m', static function ($match) use ($indent): string { return str_repeat($indent, (int) (strlen($match[0]) / 4)); }, $json ); } return $json; } private static function throwEncodeError(int $code): void { switch ($code) { case JSON_ERROR_DEPTH: $msg = 'Maximum stack depth exceeded'; break; case JSON_ERROR_STATE_MISMATCH: $msg = 'Underflow or the modes mismatch'; break; case JSON_ERROR_CTRL_CHAR: $msg = 'Unexpected control character found'; break; case JSON_ERROR_UTF8: $msg = 'Malformed UTF-8 characters, possibly incorrectly encoded'; break; default: $msg = 'Unknown error'; } throw new \RuntimeException('JSON encoding failed: '.$msg); } public static function parseJson(?string $json, ?string $file = null) { if (null === $json) { return null; } $data = json_decode($json, true); if (null === $data && JSON_ERROR_NONE !== json_last_error()) { if ($file !== null && str_ends_with($file, '.lock') && str_contains($json, '"content-hash"')) { $replaced = Preg::replace( '{\r?\n<<<<<<< [^\r\n]+\r?\n\s+"content-hash": *"[0-9a-f]+", *\r?\n(?:\|{7} [^\r\n]+\r?\n\s+"content-hash": *"[0-9a-f]+", *\r?\n)?=======\r?\n\s+"content-hash": *"[0-9a-f]+", *\r?\n>>>>>>> [^\r\n]+(\r?\n)}', ' "content-hash": "VCS merge conflict detected. Please run `composer update --lock`.",$1', $json, 1, $count ); if ($count === 1) { $data = json_decode($replaced, true); if ($data !== null) { return $data; } } } self::validateSyntax($json, $file); } return $data; } protected static function validateSyntax(string $json, ?string $file = null): bool { $parser = new JsonParser(); $result = $parser->lint($json); if (null === $result) { if (defined('JSON_ERROR_UTF8') && JSON_ERROR_UTF8 === json_last_error()) { if ($file === null) { throw new \UnexpectedValueException('The input is not UTF-8, could not parse as JSON'); } else { throw new \UnexpectedValueException('"' . $file . '" is not UTF-8, could not parse as JSON'); } } return true; } if ($file === null) { throw new ParsingException( 'The input does not contain valid JSON' . "\n" . $result->getMessage(), $result->getDetails() ); } else { throw new ParsingException( '"' . $file . '" does not contain valid JSON' . "\n" . $result->getMessage(), $result->getDetails() ); } } public static function detectIndenting(?string $json): string { if (Preg::isMatchStrictGroups('#^([ \t]+)"#m', $json ?? '', $match)) { return $match[1]; } return self::INDENT_DEFAULT; } } = $code) { return $match[0]; } return str_repeat('\\', $l - 1) . mb_convert_encoding( pack('H*', $match[2]), 'UTF-8', 'UCS-2BE' ); } return $match[0]; }, $buffer); } $result .= $buffer.$char; $buffer = ''; continue; } if (':' === $char) { $char .= ' '; } elseif ('}' === $char || ']' === $char) { $pos--; $prevChar = substr($json, $i - 1, 1); if ('{' !== $prevChar && '[' !== $prevChar) { $result .= $newLine; $result .= str_repeat($indentStr, $pos); } else { $result = rtrim($result); } } $result .= $char; if (',' === $char || '{' === $char || '[' === $char) { $result .= $newLine; if ('{' === $char || '[' === $char) { $pos++; } $result .= str_repeat($indentStr, $pos); } } return $result; } } -? (?= [1-9]|0(?!\d) ) \d++ (?:\.\d++)? (?:[eE] [+-]?+ \d++)? ) (? true | false | null ) (? " (?:[^"\\\\]*+ | \\\\ ["\\\\bfnrt\/] | \\\\ u [0-9A-Fa-f]{4} )* " ) (? \[ (?: (?&json) \s*+ (?: , (?&json) \s*+ )*+ )?+ \s*+ \] ) (? \s*+ (?&string) \s*+ : (?&json) \s*+ ) (? \{ (?: (?&pair) (?: , (?&pair) )*+ )?+ \s*+ \} ) (? \s*+ (?: (?&number) | (?&boolean) | (?&string) | (?&array) | (?&object) ) ) )'; private $contents; private $newline; private $indent; public function __construct(string $contents) { $contents = trim($contents); if ($contents === '') { $contents = '{}'; } if (!Preg::isMatch('#^\{(.*)\}$#s', $contents)) { throw new \InvalidArgumentException('The json file must be an object ({})'); } $this->newline = false !== strpos($contents, "\r\n") ? "\r\n" : "\n"; $this->contents = $contents === '{}' ? '{' . $this->newline . '}' : $contents; $this->detectIndenting(); } public function getContents(): string { return $this->contents . $this->newline; } public function addLink(string $type, string $package, string $constraint, bool $sortPackages = false): bool { $decoded = JsonFile::parseJson($this->contents); if (!isset($decoded[$type])) { return $this->addMainKey($type, [$package => $constraint]); } $regex = '{'.self::DEFINES.'^(?P\s*\{\s*(?:(?&string)\s*:\s*(?&json)\s*,\s*)*?)'. '(?P'.preg_quote(JsonFile::encode($type)).'\s*:\s*)(?P(?&json))(?P.*)}sx'; if (!Preg::isMatch($regex, $this->contents, $matches)) { return false; } assert(is_string($matches['start'])); assert(is_string($matches['value'])); assert(is_string($matches['end'])); $links = $matches['value']; $packageRegex = str_replace('/', '\\\\?/', preg_quote($package)); $regex = '{'.self::DEFINES.'"(?P'.$packageRegex.')"(\s*:\s*)(?&string)}ix'; if (Preg::isMatch($regex, $links, $packageMatches)) { assert(is_string($packageMatches['package'])); $existingPackage = $packageMatches['package']; $packageRegex = str_replace('/', '\\\\?/', preg_quote($existingPackage)); $links = Preg::replaceCallback('{'.self::DEFINES.'"'.$packageRegex.'"(?P\s*:\s*)(?&string)}ix', static function ($m) use ($existingPackage, $constraint): string { return JsonFile::encode(str_replace('\\/', '/', $existingPackage)) . $m['separator'] . '"' . $constraint . '"'; }, $links); } else { if (Preg::isMatchStrictGroups('#^\s*\{\s*\S+.*?(\s*\}\s*)$#s', $links, $match)) { $links = Preg::replace( '{'.preg_quote($match[1]).'$}', addcslashes(',' . $this->newline . $this->indent . $this->indent . JsonFile::encode($package).': '.JsonFile::encode($constraint) . $match[1], '\\$'), $links ); } else { $links = '{' . $this->newline . $this->indent . $this->indent . JsonFile::encode($package).': '.JsonFile::encode($constraint) . $this->newline . $this->indent . '}'; } } if (true === $sortPackages) { $requirements = json_decode($links, true); $this->sortPackages($requirements); $links = $this->format($requirements); } $this->contents = $matches['start'] . $matches['property'] . $links . $matches['end']; return true; } private function sortPackages(array &$packages = []): void { $prefix = static function ($requirement): string { if (PlatformRepository::isPlatformPackage($requirement)) { return Preg::replace( [ '/^php/', '/^hhvm/', '/^ext/', '/^lib/', '/^\D/', ], [ '0-$0', '1-$0', '2-$0', '3-$0', '4-$0', ], $requirement ); } return '5-'.$requirement; }; uksort($packages, static function ($a, $b) use ($prefix): int { return strnatcmp($prefix($a), $prefix($b)); }); } public function addRepository(string $name, $config, bool $append = true): bool { if ("" !== $name && !$this->doRemoveRepository($name)) { return false; } if (!$this->doConvertRepositoriesFromAssocToList()) { return false; } if (is_array($config) && !is_numeric($name) && '' !== $name) { $config = ['name' => $name] + $config; } elseif ($config === false) { $config = [$name => $config]; } return $this->addListItem('repositories', $config, $append); } private function doConvertRepositoriesFromAssocToList(): bool { $decoded = json_decode($this->contents, false); if (($decoded->repositories ?? null) instanceof \stdClass) { $entriesToRevert = array_reverse(array_keys((array) $decoded->repositories)); foreach ($entriesToRevert as $entryKey) { if (!$this->removeSubNode('repositories', (string) $entryKey)) { return false; } } $this->changeEmptyMainKeyFromAssocToList('repositories'); foreach (((array) $decoded->repositories) as $repositoryName => $repository) { if (!$repository instanceof \stdClass) { if (!$this->addListItem('repositories', [$repositoryName => $repository], true)) { return false; } } elseif (is_numeric($repositoryName)) { if (!$this->addListItem('repositories', $repository, true)) { return false; } } else { $repository = (array) $repository; $repository = ['name' => $repositoryName] + $repository; if (!$this->addListItem('repositories', $repository, true)) { return false; } } } } return true; } public function setRepositoryUrl(string $name, string $url): bool { $decoded = JsonFile::parseJson($this->contents); $repositoryIndex = null; foreach ($decoded['repositories'] ?? [] as $index => $repository) { if ($name === $index) { $repositoryIndex = $index; break; } if ($name === ($repository['name'] ?? null)) { $repositoryIndex = $index; break; } } if (null === $repositoryIndex) { return false; } $listRegex = null; if (is_int($repositoryIndex)) { $listRegex = '{'.self::DEFINES.'^(?P\s*\{\s*(?:(?&string)\s*:\s*(?&json)\s*,\s*)*?"repositories"\s*:\s*\[\s*((?&json)\s*+,\s*+){' . max(0, $repositoryIndex) . '})(?P(?&object))(?P.*)}sx'; } $objectRegex = '{'.self::DEFINES.'^(?P\s*\{\s*(?:(?&string)\s*:\s*(?&json)\s*,\s*)*?"repositories"\s*:\s*\{\s*(?:(?&string)\s*:\s*(?&json)\s*,\s*)*?' . preg_quote(JsonFile::encode($repositoryIndex)) . '\s*:\s*)(?P(?&object))(?P.*)}sx'; $matches = null; if (($listRegex !== null && Preg::isMatch($listRegex, $this->contents, $matches)) || Preg::isMatch($objectRegex, $this->contents, $matches)) { assert(isset($matches['start']) && is_string($matches['start'])); assert(isset($matches['repository']) && is_string($matches['repository'])); assert(isset($matches['end']) && is_string($matches['end'])); if (false === @json_decode($matches['repository'])) { return false; } $repositoryRegex = '{'.self::DEFINES.'^(?P\s*\{\s*(?:(?&string)\s*:\s*(?&json)\s*,\s*)*?"url"\s*:\s*)(?P(?&string))(?P.*)}sx'; $this->contents = $matches['start'] . Preg::replaceCallback($repositoryRegex, static function (array $repositoryMatches) use ($url): string { return $repositoryMatches['start'] . JsonFile::encode($url) . $repositoryMatches['end']; }, $matches['repository']) . $matches['end']; return true; } return false; } public function insertRepository(string $name, $config, string $referenceName, int $offset = 0): bool { if ("" !== $name && !$this->doRemoveRepository($name)) { return false; } if (!$this->doConvertRepositoriesFromAssocToList()) { return false; } $indexToInsert = null; $decoded = JsonFile::parseJson($this->contents); foreach ($decoded['repositories'] as $repositoryIndex => $repository) { if (($repository['name'] ?? null) === $referenceName) { $indexToInsert = $repositoryIndex; break; } if ($repositoryIndex === $referenceName) { $indexToInsert = $repositoryIndex; break; } if ([$referenceName => false] === $repository) { $indexToInsert = $repositoryIndex; break; } } if ($indexToInsert === null) { return false; } if (is_array($config) && !is_numeric($name) && '' !== $name) { $config = ['name' => $name] + $config; } elseif ($config === false) { $config = ['name' => $config]; } return $this->insertListItem('repositories', $config, $indexToInsert + $offset); } public function removeRepository(string $name): bool { return $this->doRemoveRepository($name) && $this->removeMainKeyIfEmpty('repositories'); } private function doRemoveRepository(string $name): bool { $decoded = json_decode($this->contents, false); $isAssoc = ($decoded->repositories ?? null) instanceof \stdClass; foreach ((array) ($decoded->repositories ?? []) as $repositoryIndex => $repository) { if ($repositoryIndex === $name && $isAssoc) { if (!$this->removeSubNode('repositories', $repositoryIndex)) { return false; } break; } if (($repository->name ?? null) === $name) { if ($isAssoc) { if (!$this->removeSubNode('repositories', (string) $repositoryIndex)) { return false; } } else { if (!$this->removeListItem('repositories', (int) $repositoryIndex)) { return false; } } break; } if ($isAssoc) { if ($name === $repositoryIndex && false === $repository) { if (!$this->removeSubNode('repositories', $repositoryIndex)) { return false; } return true; } } else { $repositoryAsArray = (array) $repository; if (false === ($repositoryAsArray[$name] ?? null) && 1 === count($repositoryAsArray)) { if (!$this->removeListItem('repositories', (int) $repositoryIndex)) { return false; } return true; } } } return true; } public function addConfigSetting(string $name, $value): bool { if (strpos($name, 'policy.') === 0 && substr_count($name, '.') >= 2) { [$listName, $fieldName] = explode('.', substr($name, 7), 2); if (str_contains($fieldName, '.')) { return false; } $listBlock = $this->getCurrentPolicyListBlock($listName); $listBlock[$fieldName] = $value; return $this->addSubNode('config', 'policy.'.$listName, $listBlock); } return $this->addSubNode('config', $name, $value); } public function removeConfigSetting(string $name): bool { if (strpos($name, 'policy.') === 0 && substr_count($name, '.') >= 2) { [$listName, $fieldName] = explode('.', substr($name, 7), 2); if (str_contains($fieldName, '.')) { return false; } $listBlock = $this->getCurrentPolicyListBlock($listName); if (!array_key_exists($fieldName, $listBlock)) { return true; } unset($listBlock[$fieldName]); if (count($listBlock) === 0) { return $this->removeConfigSetting('policy.'.$listName); } return $this->addSubNode('config', 'policy.'.$listName, $listBlock); } return $this->removeSubNode('config', $name); } private function getCurrentPolicyListBlock(string $listName): array { $decoded = JsonFile::parseJson($this->contents); if (!is_array($decoded)) { return []; } $policySection = $decoded['config']['policy'] ?? null; if (!is_array($policySection)) { return []; } $listBlock = $policySection[$listName] ?? []; return is_array($listBlock) ? $listBlock : []; } public function addProperty(string $name, $value): bool { if (strpos($name, 'suggest.') === 0) { return $this->addSubNode('suggest', substr($name, 8), $value); } if (strpos($name, 'extra.') === 0) { return $this->addSubNode('extra', substr($name, 6), $value); } if (strpos($name, 'scripts.') === 0) { return $this->addSubNode('scripts', substr($name, 8), $value); } return $this->addMainKey($name, $value); } public function removeProperty(string $name): bool { if (strpos($name, 'suggest.') === 0) { return $this->removeSubNode('suggest', substr($name, 8)); } if (strpos($name, 'extra.') === 0) { return $this->removeSubNode('extra', substr($name, 6)); } if (strpos($name, 'scripts.') === 0) { return $this->removeSubNode('scripts', substr($name, 8)); } if (strpos($name, 'autoload.') === 0) { return $this->removeSubNode('autoload', substr($name, 9)); } if (strpos($name, 'autoload-dev.') === 0) { return $this->removeSubNode('autoload-dev', substr($name, 13)); } return $this->removeMainKey($name); } public function addSubNode(string $mainNode, string $name, $value, bool $append = true): bool { $decoded = JsonFile::parseJson($this->contents); $subName = null; if (in_array($mainNode, ['config', 'extra', 'scripts']) && false !== strpos($name, '.')) { [$name, $subName] = explode('.', $name, 2); } if (!isset($decoded[$mainNode])) { if ($subName !== null) { $this->addMainKey($mainNode, [$name => [$subName => $value]]); } else { $this->addMainKey($mainNode, [$name => $value]); } return true; } $nodeRegex = '{'.self::DEFINES.'^(?P \s* \{ \s* (?: (?&string) \s* : (?&json) \s* , \s* )*?'. preg_quote(JsonFile::encode($mainNode)).'\s*:\s*)(?P(?&object))(?P.*)}sx'; try { if (!Preg::isMatch($nodeRegex, $this->contents, $match)) { return false; } } catch (\RuntimeException $e) { if ($e->getCode() === PREG_BACKTRACK_LIMIT_ERROR) { return false; } throw $e; } assert(is_string($match['start'])); assert(is_string($match['content'])); assert(is_string($match['end'])); $children = $match['content']; if (!@json_decode($children)) { return false; } $childRegex = '{'.self::DEFINES.'(?P"'.preg_quote($name).'"\s*:\s*)(?P(?&json))(?P,?)}x'; if (Preg::isMatch($childRegex, $children, $matches)) { $children = Preg::replaceCallback($childRegex, function ($matches) use ($subName, $value): string { if ($subName !== null && is_string($matches['content'])) { $curVal = json_decode($matches['content'], true); if (!is_array($curVal)) { $curVal = []; } $curVal[$subName] = $value; $value = $curVal; } return $matches['start'] . $this->format($value, 1) . $matches['end']; }, $children); } elseif (Preg::isMatch('#^\{(?P\s*?)(?P\S+.*?)?(?P\s*)\}$#s', $children, $match)) { $whitespace = $match['trailingspace']; if (null !== $match['content']) { if ($subName !== null) { $value = [$subName => $value]; } if ($append) { $children = Preg::replace( '#'.$whitespace.'}$#', addcslashes(',' . $this->newline . $this->indent . $this->indent . JsonFile::encode($name).': '.$this->format($value, 1) . $whitespace . '}', '\\$'), $children ); } else { $whitespace = $match['leadingspace']; $children = Preg::replace( '#^{'.$whitespace.'#', addcslashes('{' . $whitespace . JsonFile::encode($name).': '.$this->format($value, 1) . ',' . $this->newline . $this->indent . $this->indent, '\\$'), $children ); } } else { if ($subName !== null) { $value = [$subName => $value]; } $children = '{' . $this->newline . $this->indent . $this->indent . JsonFile::encode($name).': '.$this->format($value, 1) . $whitespace . '}'; } } else { throw new \LogicException('Nothing matched above for: '.$children); } $this->contents = Preg::replaceCallback($nodeRegex, static function ($m) use ($children): string { return $m['start'] . $children . $m['end']; }, $this->contents); return true; } public function removeSubNode(string $mainNode, string $name): bool { $decoded = JsonFile::parseJson($this->contents); if (empty($decoded[$mainNode])) { return true; } $nodeRegex = '{'.self::DEFINES.'^(?P \s* \{ \s* (?: (?&string) \s* : (?&json) \s* , \s* )*?'. preg_quote(JsonFile::encode($mainNode)).'\s*:\s*)(?P(?&object))(?P.*)}sx'; try { if (!Preg::isMatch($nodeRegex, $this->contents, $match)) { return false; } } catch (\RuntimeException $e) { if ($e->getCode() === PREG_BACKTRACK_LIMIT_ERROR) { return false; } throw $e; } assert(is_string($match['start'])); assert(is_string($match['content'])); assert(is_string($match['end'])); $children = $match['content']; if (!@json_decode($children, true)) { return false; } $subName = null; if (in_array($mainNode, ['config', 'extra', 'scripts']) && false !== strpos($name, '.')) { [$name, $subName] = explode('.', $name, 2); } if (!isset($decoded[$mainNode][$name]) || ($subName && !isset($decoded[$mainNode][$name][$subName]))) { return true; } $keyRegex = str_replace('/', '\\\\?/', preg_quote($name)); if (Preg::isMatch('{"'.$keyRegex.'"\s*:}i', $children)) { if (Preg::isMatchAll('{'.self::DEFINES.'"'.$keyRegex.'"\s*:\s*(?:(?&json))}x', $children, $matches)) { $bestMatch = ''; foreach ($matches[0] as $match) { assert(is_string($match)); if (strlen($bestMatch) < strlen($match)) { $bestMatch = $match; } } $childrenClean = Preg::replace('{,\s*'.preg_quote($bestMatch).'}i', '', $children, -1, $count); if (1 !== $count) { $childrenClean = Preg::replace('{'.preg_quote($bestMatch).'\s*,?\s*}i', '', $childrenClean, -1, $count); if (1 !== $count) { return false; } } } } else { $childrenClean = $children; } if (!isset($childrenClean)) { throw new \InvalidArgumentException("JsonManipulator: \$childrenClean is not defined. Please report at https://github.com/composer/composer/issues/new."); } unset($match); if (Preg::isMatch('#^\{\s*?(?P\S+.*?)?(?P\s*)\}$#s', $childrenClean, $match)) { if (null === $match['content']) { $newline = $this->newline; $indent = $this->indent; $this->contents = Preg::replaceCallback($nodeRegex, static function ($matches) use ($indent, $newline): string { return $matches['start'] . '{' . $newline . $indent . '}' . $matches['end']; }, $this->contents); if ($subName !== null) { $curVal = json_decode($children, true); unset($curVal[$name][$subName]); if ($curVal[$name] === []) { $curVal[$name] = new \ArrayObject(); } $this->addSubNode($mainNode, $name, $curVal[$name]); } return true; } } $this->contents = Preg::replaceCallback($nodeRegex, function ($matches) use ($name, $subName, $childrenClean): string { assert(is_string($matches['content'])); if ($subName !== null) { $curVal = json_decode($matches['content'], true); unset($curVal[$name][$subName]); if ($curVal[$name] === []) { $curVal[$name] = new \ArrayObject(); } $childrenClean = $this->format($curVal, 0, true); } return $matches['start'] . $childrenClean . $matches['end']; }, $this->contents); return true; } public function addListItem(string $mainNode, $value, bool $append = true): bool { $decoded = JsonFile::parseJson($this->contents); if (!isset($decoded[$mainNode])) { if (!$this->addMainKey($mainNode, [])) { return false; } } $nodeRegex = '{'.self::DEFINES.'^(?P \s* \{ \s* (?: (?&string) \s* : (?&json) \s* , \s* )*?'. preg_quote(JsonFile::encode($mainNode)).'\s*:\s*)(?P(?&array))(?P.*)}sx'; try { if (!Preg::isMatch($nodeRegex, $this->contents, $match)) { return false; } } catch (\RuntimeException $e) { if ($e->getCode() === PREG_BACKTRACK_LIMIT_ERROR) { return false; } throw $e; } assert(is_string($match['start'])); assert(is_string($match['content'])); assert(is_string($match['end'])); $children = $match['content']; if (false === @json_decode($children)) { return false; } if (Preg::isMatch('#^\[(?P\s*?)(?P\S+.*?)?(?P\s*)\]$#s', $children, $match)) { $leadingWhitespace = $match['leadingspace']; $whitespace = $match['trailingspace']; $leadingItemWhitespace = $this->newline . $this->indent . $this->indent; $trailingItemWhitespace = $whitespace; $itemDepth = 1; if (!str_contains($whitespace, $this->newline)) { $leadingItemWhitespace = $leadingWhitespace; $trailingItemWhitespace = $leadingWhitespace; $itemDepth = 0; } if (null !== $match['content']) { if ($append) { $children = Preg::replace( '#'.$whitespace.']$#', addcslashes(',' . $leadingItemWhitespace . $this->format($value, $itemDepth) . $trailingItemWhitespace . ']', '\\$'), $children ); } else { $whitespace = $match['leadingspace']; $children = Preg::replace( '#^\['.$whitespace.'#', addcslashes('[' . $whitespace . $this->format($value, $itemDepth) . ',' . $leadingItemWhitespace, '\\$'), $children ); } } else { $children = '[' . $leadingItemWhitespace . $this->format($value, $itemDepth) . $trailingItemWhitespace . ']'; } } else { throw new \LogicException('Nothing matched above for: '.$children); } $this->contents = Preg::replaceCallback($nodeRegex, static function ($m) use ($children): string { return $m['start'] . $children . $m['end']; }, $this->contents); return true; } public function insertListItem(string $mainNode, $value, int $index): bool { if ($index < 0) { throw new \InvalidArgumentException('Index can only be positive integer'); } if ($index === 0) { return $this->addListItem($mainNode, $value, false); } $decoded = JsonFile::parseJson($this->contents); if (!isset($decoded[$mainNode])) { if (!$this->addMainKey($mainNode, [])) { return false; } } if (count($decoded[$mainNode]) === $index) { return $this->addListItem($mainNode, $value, true); } $nodeRegex = '{'.self::DEFINES.'^(?P \s* \{ \s* (?: (?&string) \s* : (?&json) \s* , \s* )*?'. preg_quote(JsonFile::encode($mainNode)).'\s*:\s*)(?P(?&array))(?P.*)}sx'; try { if (!Preg::isMatch($nodeRegex, $this->contents, $match)) { return false; } } catch (\RuntimeException $e) { if ($e->getCode() === PREG_BACKTRACK_LIMIT_ERROR) { return false; } throw $e; } assert(is_string($match['start'])); assert(is_string($match['content'])); assert(is_string($match['end'])); $children = $match['content']; if (false === @json_decode($children)) { return false; } $listSkipToItemRegex = '{'.self::DEFINES.'^(?P\[\s*((?&json)\s*+,\s*?){' . max(0, $index) . '})(?P(\s*))(?P.*)}sx'; $children = Preg::replaceCallback($listSkipToItemRegex, function ($m) use ($value): string { return $m['start'] . $m['space_before_item'] . $this->format($value, 1) . ',' . $m['space_before_item'] . $m['end']; }, $children); $this->contents = Preg::replaceCallback($nodeRegex, static function ($m) use ($children): string { return $m['start'] . $children . $m['end']; }, $this->contents); return true; } public function removeListItem(string $mainNode, int $nodeIndex): bool { if ($nodeIndex < 0) { return true; } $decoded = JsonFile::parseJson($this->contents); if ([] === $decoded[$mainNode]) { return true; } $nodeRegex = '{'.self::DEFINES.'^(?P \s* \{ \s* (?: (?&string) \s* : (?&json) \s* , \s* )*?'. preg_quote(JsonFile::encode($mainNode)).'\s*:\s*)(?P(?&array))(?P.*)}sx'; try { if (!Preg::isMatch($nodeRegex, $this->contents, $match)) { return false; } } catch (\RuntimeException $e) { if ($e->getCode() === PREG_BACKTRACK_LIMIT_ERROR) { return false; } throw $e; } assert(is_string($match['start'])); assert(is_string($match['content'])); assert(is_string($match['end'])); $children = $match['content']; if (false === @json_decode($children, true)) { return false; } if (!isset($decoded[$mainNode][$nodeIndex])) { return true; } $contentRegex = '(?&json)'; if ($nodeIndex > 1) { $startRegex = '(?&json)\s*+(?:,(?&json)\s*+){' . ($nodeIndex - 1) . '}'; $contentRegex = '\s*+,?\s*+' . $contentRegex; $endRegex = '(?:(\s*+,\s*+(?&json))*(?:\s*+(?&json))?)\s*+'; } elseif ($nodeIndex > 0) { $startRegex = '(?&json)\s*+'; $contentRegex = '\s*+,?\s*+' . $contentRegex; $endRegex = '(?:(\s*+,\s*+(?&json))*(?:\s*+(?&json))?)\s*+'; } else { $startRegex = '\s*+'; $contentRegex = $contentRegex . '\s*+,?\s*+'; $endRegex = '(?:((?&json)\s*+,\s*+)*(?:\s*+(?&json))?)\s*+'; } if (Preg::isMatch('{'.self::DEFINES.'(?P\[' . $startRegex . ')(?P' . $contentRegex . ')(?P' . $endRegex . '\])}sx', $children, $childMatch)) { $this->contents = $match['start'] . $childMatch['start'] . $childMatch['end'] . $match['end']; return true; } return false; } public function addMainKey(string $key, $content): bool { $decoded = JsonFile::parseJson($this->contents); $content = $this->format($content); $regex = '{'.self::DEFINES.'^(?P\s*\{\s*(?:(?&string)\s*:\s*(?&json)\s*,\s*)*?)'. '(?P'.preg_quote(JsonFile::encode($key)).'\s*:\s*(?&json))(?P.*)}sx'; if (isset($decoded[$key]) && Preg::isMatch($regex, $this->contents, $matches)) { if (!@json_decode('{'.$matches['key'].'}')) { return false; } $this->contents = $matches['start'] . JsonFile::encode($key).': '.$content . $matches['end']; return true; } if (Preg::isMatch('#[^{\s](\s*)\}$#', $this->contents, $match)) { $this->contents = Preg::replace( '#'.$match[1].'\}$#', addcslashes(',' . $this->newline . $this->indent . JsonFile::encode($key). ': '. $content . $this->newline . '}', '\\$'), $this->contents ); return true; } $this->contents = Preg::replace( '#\}$#', addcslashes($this->indent . JsonFile::encode($key). ': '.$content . $this->newline . '}', '\\$'), $this->contents ); return true; } public function removeMainKey(string $key): bool { $decoded = JsonFile::parseJson($this->contents); if (!array_key_exists($key, $decoded)) { return true; } $regex = '{'.self::DEFINES.'^(?P\s*\{\s*(?:(?&string)\s*:\s*(?&json)\s*,\s*)*?)'. '(?P'.preg_quote(JsonFile::encode($key)).'\s*:\s*(?&json))\s*,?\s*(?P.*)}sx'; if (Preg::isMatch($regex, $this->contents, $matches)) { assert(is_string($matches['start'])); assert(is_string($matches['removal'])); assert(is_string($matches['end'])); if (!@json_decode('{'.$matches['removal'].'}')) { return false; } if (Preg::isMatchStrictGroups('#,\s*$#', $matches['start']) && Preg::isMatch('#^\}$#', $matches['end'])) { $matches['start'] = rtrim(Preg::replace('#,(\s*)$#', '$1', $matches['start']), $this->indent); } $this->contents = $matches['start'] . $matches['end']; if (Preg::isMatch('#^\{\s*\}\s*$#', $this->contents)) { $this->contents = "{\n}"; } return true; } return false; } public function changeEmptyMainKeyFromAssocToList(string $key): bool { $decoded = JsonFile::parseJson($this->contents); if (!array_key_exists($key, $decoded)) { return true; } $regex = '{'.self::DEFINES.'^(?P\s*\{\s*(?:(?&string)\s*:\s*(?&json)\s*,\s*)*?'.preg_quote(JsonFile::encode($key)).'\s*:\s*)(?P\{(?P\s*+)\})(?P\s*,?\s*.*)}sx'; if (Preg::isMatch($regex, $this->contents, $matches)) { assert(is_string($matches['start'])); assert(is_string($matches['removal'])); assert(is_string($matches['end'])); if (false === @json_decode($matches['removal'])) { return false; } $this->contents = $matches['start'] . '[' . $matches['removal_space'] . ']' . $matches['end']; return true; } return false; } public function removeMainKeyIfEmpty(string $key): bool { $decoded = JsonFile::parseJson($this->contents); if (!array_key_exists($key, $decoded)) { return true; } if (is_array($decoded[$key]) && count($decoded[$key]) === 0) { return $this->removeMainKey($key); } return true; } public function format($data, int $depth = 0, bool $wasObject = false): string { if ($data instanceof \stdClass || $data instanceof \ArrayObject) { $data = (array) $data; $wasObject = true; } if (is_array($data)) { if (\count($data) === 0) { return $wasObject ? '{' . $this->newline . str_repeat($this->indent, $depth + 1) . '}' : '[]'; } if (array_is_list($data)) { foreach ($data as $key => $val) { $data[$key] = $this->format($val, $depth + 1); } return '['.implode(', ', $data).']'; } $out = '{' . $this->newline; $elems = []; foreach ($data as $key => $val) { $elems[] = str_repeat($this->indent, $depth + 2) . JsonFile::encode((string) $key). ': '.$this->format($val, $depth + 1); } return $out . implode(','.$this->newline, $elems) . $this->newline . str_repeat($this->indent, $depth + 1) . '}'; } return JsonFile::encode($data); } protected function detectIndenting(): void { $this->indent = JsonFile::detectIndenting($this->contents); } } errors = $errors; parent::__construct((string) $message, 0, $previous); } public function getErrors(): array { return $this->errors; } } $conf) { $type = $this->parseType($conf, $prop); $this->properties[$prop] = $type; } } public function getClass(): string { return Config::class; } public function isMethodSupported(MethodReflection $methodReflection): bool { return strtolower($methodReflection->getName()) === 'get'; } public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type { $args = $methodCall->getArgs(); if (count($args) < 1) { return null; } $keyType = $scope->getType($args[0]->value); if (method_exists($keyType, 'getConstantStrings')) { $strings = $keyType->getConstantStrings(); } else { $strings = TypeUtils::getConstantStrings($keyType); } if ($strings !== []) { $types = []; foreach ($strings as $string) { if (!isset($this->properties[$string->getValue()])) { return null; } $types[] = $this->properties[$string->getValue()]; } return TypeCombinator::union(...$types); } return null; } private function parseType(array $def, string $path): Type { if (isset($def['type'])) { $types = []; foreach ((array) $def['type'] as $type) { switch ($type) { case 'integer': if (in_array($path, ['process-timeout', 'cache-ttl', 'cache-files-ttl', 'cache-files-maxsize'], true)) { $types[] = IntegerRangeType::createAllGreaterThanOrEqualTo(0); } else { $types[] = new IntegerType(); } break; case 'string': if ($path === 'cache-files-maxsize') { } elseif ($path === 'discard-changes') { $types[] = new ConstantStringType('stash'); } elseif ($path === 'use-parent-dir') { $types[] = new ConstantStringType('prompt'); } elseif ($path === 'store-auths') { $types[] = new ConstantStringType('prompt'); } elseif ($path === 'platform-check') { $types[] = new ConstantStringType('php-only'); } elseif ($path === 'github-protocols') { $types[] = new UnionType([new ConstantStringType('git'), new ConstantStringType('https'), new ConstantStringType('ssh'), new ConstantStringType('http')]); } elseif (str_starts_with($path, 'preferred-install')) { $types[] = new UnionType([new ConstantStringType('source'), new ConstantStringType('dist'), new ConstantStringType('auto')]); } else { $types[] = new StringType(); } break; case 'boolean': if ($path === 'platform.additionalProperties') { $types[] = new ConstantBooleanType(false); } else { $types[] = new BooleanType(); } break; case 'object': $addlPropType = null; if (isset($def['additionalProperties'])) { $addlPropType = $this->parseType($def['additionalProperties'], $path.'.additionalProperties'); } if (isset($def['properties'])) { $keyNames = []; $valTypes = []; $optionalKeys = []; $propIndex = 0; foreach ($def['properties'] as $propName => $propdef) { $keyNames[] = new ConstantStringType($propName); $valType = $this->parseType($propdef, $path.'.'.$propName); if (!isset($def['required']) || !in_array($propName, $def['required'], true)) { $valType = TypeCombinator::addNull($valType); $optionalKeys[] = $propIndex; } $valTypes[] = $valType; $propIndex++; } if ($addlPropType !== null) { $types[] = new ArrayType(TypeCombinator::union(new StringType(), ...$keyNames), TypeCombinator::union($addlPropType, ...$valTypes)); } else { $types[] = new ConstantArrayType($keyNames, $valTypes, [0], $optionalKeys); } } else { $types[] = new ArrayType(new StringType(), $addlPropType ?? new MixedType()); } break; case 'array': if (isset($def['items'])) { $valType = $this->parseType($def['items'], $path.'.items'); } else { $valType = new MixedType(); } $types[] = new ArrayType(new IntegerType(), $valType); break; default: $types[] = new MixedType(); } } $type = TypeCombinator::union(...$types); } elseif (isset($def['enum'])) { $type = TypeCombinator::union(...array_map(static function (string $value): ConstantStringType { return new ConstantStringType($value); }, $def['enum'])); } else { $type = new MixedType(); } if ($path === 'allow-plugins' && time() < strtotime('2022-07-01')) { $type = TypeCombinator::addNull($type); } if (in_array($path, ['autoloader-suffix', 'gitlab-protocol'], true)) { $type = TypeCombinator::addNull($type); } return $type; } } getName()) === 'getreasondata'; } public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type { $reasonType = $scope->getType(new MethodCall($methodCall->var, new Identifier('getReason'))); $types = [ Rule::RULE_ROOT_REQUIRE => new ConstantArrayType([new ConstantStringType('packageName'), new ConstantStringType('constraint')], [new StringType, new ObjectType(ConstraintInterface::class)]), Rule::RULE_FIXED => new ConstantArrayType([new ConstantStringType('package')], [new ObjectType(BasePackage::class)]), Rule::RULE_LOCKED_FILTER_LIST_REMOVED => new ConstantArrayType([new ConstantStringType('package')], [new ObjectType(BasePackage::class)]), Rule::RULE_PACKAGE_CONFLICT => new ObjectType(Link::class), Rule::RULE_PACKAGE_REQUIRES => new ObjectType(Link::class), Rule::RULE_PACKAGE_SAME_NAME => TypeCombinator::intersect(new StringType, new AccessoryNonEmptyStringType()), Rule::RULE_LEARNED => new IntegerType(), Rule::RULE_PACKAGE_ALIAS => new ObjectType(BasePackage::class), Rule::RULE_PACKAGE_INVERSE_ALIAS => new ObjectType(BasePackage::class), ]; $matched = []; foreach ($types as $const => $type) { if ($reasonType->isSuperTypeOf(new ConstantIntegerType($const))->yes()) { $matched[] = $type; } } if (\count($matched) > 0) { return \count($matched) === 1 ? $matched[0] : TypeCombinator::union(...$matched); } return TypeCombinator::union(...$types); } } getName()); $this->version = $version; $this->prettyVersion = $prettyVersion; $this->aliasOf = $aliasOf; $this->stability = VersionParser::parseStability($version); $this->dev = $this->stability === 'dev'; foreach (Link::$TYPES as $type) { $links = $aliasOf->{'get' . ucfirst($type)}(); $this->{$type} = $this->replaceSelfVersionDependencies($links, $type); } } public function getAliasOf() { return $this->aliasOf; } public function getVersion(): string { return $this->version; } public function getStability(): string { return $this->stability; } public function getPrettyVersion(): string { return $this->prettyVersion; } public function isDev(): bool { return $this->dev; } public function getRequires(): array { return $this->requires; } public function getConflicts(): array { return $this->conflicts; } public function getProvides(): array { return $this->provides; } public function getReplaces(): array { return $this->replaces; } public function getDevRequires(): array { return $this->devRequires; } public function setRootPackageAlias(bool $value): void { $this->rootPackageAlias = $value; } public function isRootPackageAlias(): bool { return $this->rootPackageAlias; } protected function replaceSelfVersionDependencies(array $links, $linkType): array { $prettyVersion = $this->prettyVersion; if ($prettyVersion === VersionParser::DEFAULT_BRANCH_ALIAS) { $prettyVersion = $this->aliasOf->getPrettyVersion(); } if (\in_array($linkType, [Link::TYPE_CONFLICT, Link::TYPE_PROVIDE, Link::TYPE_REPLACE], true)) { $newLinks = []; foreach ($links as $link) { if ('self.version' === $link->getPrettyConstraint()) { $newLinks[] = new Link($link->getSource(), $link->getTarget(), $constraint = new Constraint('=', $this->version), $linkType, $prettyVersion); $constraint->setPrettyString($prettyVersion); } } $links = array_merge($links, $newLinks); } else { foreach ($links as $index => $link) { if ('self.version' === $link->getPrettyConstraint()) { if ($linkType === Link::TYPE_REQUIRE) { $this->hasSelfVersionRequires = true; } $links[$index] = new Link($link->getSource(), $link->getTarget(), $constraint = new Constraint('=', $this->version), $linkType, $prettyVersion); $constraint->setPrettyString($prettyVersion); } } } return $links; } public function hasSelfVersionRequires(): bool { return $this->hasSelfVersionRequires; } public function __toString(): string { return parent::__toString().' ('.($this->rootPackageAlias ? 'root ' : ''). 'alias of '.$this->aliasOf->getVersion().')'; } public function getType(): string { return $this->aliasOf->getType(); } public function getTargetDir(): ?string { return $this->aliasOf->getTargetDir(); } public function getExtra(): array { return $this->aliasOf->getExtra(); } public function setInstallationSource(?string $type): void { $this->aliasOf->setInstallationSource($type); } public function getInstallationSource(): ?string { return $this->aliasOf->getInstallationSource(); } public function getSourceType(): ?string { return $this->aliasOf->getSourceType(); } public function getSourceUrl(): ?string { return $this->aliasOf->getSourceUrl(); } public function getSourceUrls(): array { return $this->aliasOf->getSourceUrls(); } public function getSourceReference(): ?string { return $this->aliasOf->getSourceReference(); } public function setSourceReference(?string $reference): void { $this->aliasOf->setSourceReference($reference); } public function setSourceMirrors(?array $mirrors): void { $this->aliasOf->setSourceMirrors($mirrors); } public function getSourceMirrors(): ?array { return $this->aliasOf->getSourceMirrors(); } public function getDistType(): ?string { return $this->aliasOf->getDistType(); } public function getDistUrl(): ?string { return $this->aliasOf->getDistUrl(); } public function getDistUrls(): array { return $this->aliasOf->getDistUrls(); } public function getDistReference(): ?string { return $this->aliasOf->getDistReference(); } public function setDistReference(?string $reference): void { $this->aliasOf->setDistReference($reference); } public function getDistSha1Checksum(): ?string { return $this->aliasOf->getDistSha1Checksum(); } public function setTransportOptions(array $options): void { $this->aliasOf->setTransportOptions($options); } public function getTransportOptions(): array { return $this->aliasOf->getTransportOptions(); } public function setDistMirrors(?array $mirrors): void { $this->aliasOf->setDistMirrors($mirrors); } public function getDistMirrors(): ?array { return $this->aliasOf->getDistMirrors(); } public function getAutoload(): array { return $this->aliasOf->getAutoload(); } public function getDevAutoload(): array { return $this->aliasOf->getDevAutoload(); } public function getIncludePaths(): array { return $this->aliasOf->getIncludePaths(); } public function getPhpExt(): ?array { return $this->aliasOf->getPhpExt(); } public function getReleaseDate(): ?\DateTimeInterface { return $this->aliasOf->getReleaseDate(); } public function getBinaries(): array { return $this->aliasOf->getBinaries(); } public function getSuggests(): array { return $this->aliasOf->getSuggests(); } public function getNotificationUrl(): ?string { return $this->aliasOf->getNotificationUrl(); } public function isDefaultBranch(): bool { return $this->aliasOf->isDefaultBranch(); } public function setDistUrl(?string $url): void { $this->aliasOf->setDistUrl($url); } public function setDistType(?string $type): void { $this->aliasOf->setDistType($type); } public function setSourceDistReferences(string $reference): void { $this->aliasOf->setSourceDistReferences($reference); } } getInnerIterator()->current(); if ($file->isDir()) { $this->dirs[] = (string) $file; return false; } return true; } public function addEmptyDir(PharData $phar, string $sources): void { foreach ($this->dirs as $filepath) { $localname = str_replace($sources . "/", '', $filepath); $phar->addEmptyDir($localname); } } } normalizePath($sourcesRealPath); if ($ignoreFilters) { $filters = []; } else { $filters = [ new GitExcludeFilter($sources), new ComposerExcludeFilter($sources, $excludes), ]; } $this->finder = new Finder(); $filter = static function (\SplFileInfo $file) use ($sources, $filters, $fs): bool { $realpath = $file->getRealPath(); if ($realpath === false) { return false; } if ($file->isLink() && strpos($realpath, $sources) !== 0) { return false; } $relativePath = Preg::replace( '#^'.preg_quote($sources, '#').'#', '', $fs->normalizePath($realpath) ); $exclude = false; foreach ($filters as $filter) { $exclude = $filter->filter($relativePath, $exclude); } return !$exclude; }; $this->finder ->in($sources) ->filter($filter) ->ignoreVCS(true) ->ignoreDotFiles(false) ->sortByName(); parent::__construct($this->finder->getIterator()); } public function accept(): bool { $current = $this->getInnerIterator()->current(); if (!$current->isDir()) { return true; } $iterator = new FilesystemIterator((string) $current, FilesystemIterator::SKIP_DOTS); return !$iterator->valid(); } } downloadManager = $downloadManager; $this->loop = $loop; } public function addArchiver(ArchiverInterface $archiver): void { $this->archivers[] = $archiver; } public function setOverwriteFiles(bool $overwriteFiles): self { $this->overwriteFiles = $overwriteFiles; return $this; } public function getPackageFilenameParts(CompletePackageInterface $package): array { $baseName = $package->getArchiveName(); if (null === $baseName) { $baseName = Preg::replace('#[^a-z0-9-_]#i', '-', $package->getName()); } $parts = [ 'base' => $baseName, ]; $distReference = $package->getDistReference(); if (null !== $distReference && Preg::isMatch('{^[a-f0-9]{40}$}', $distReference)) { $parts['dist_reference'] = $distReference; $parts['dist_type'] = $package->getDistType(); } else { $parts['version'] = $package->getPrettyVersion(); $parts['dist_reference'] = $distReference; } $sourceReference = $package->getSourceReference(); if (null !== $sourceReference) { $parts['source_reference'] = substr(hash('sha1', $sourceReference), 0, 6); } $parts = array_filter($parts, static function (?string $part) { return $part !== null; }); foreach ($parts as $key => $part) { $parts[$key] = str_replace('/', '-', $part); } return $parts; } public function getPackageFilenameFromParts(array $parts): string { return implode('-', $parts); } public function getPackageFilename(CompletePackageInterface $package): string { return $this->getPackageFilenameFromParts($this->getPackageFilenameParts($package)); } public function archive(CompletePackageInterface $package, string $format, string $targetDir, ?string $fileName = null, bool $ignoreFilters = false): string { if (empty($format)) { throw new \InvalidArgumentException('Format must be specified'); } $usableArchiver = null; foreach ($this->archivers as $archiver) { if ($archiver->supports($format, $package->getSourceType())) { $usableArchiver = $archiver; break; } } if (null === $usableArchiver) { throw new \RuntimeException(sprintf('No archiver found to support %s format', $format)); } $filesystem = new Filesystem(); if ($package instanceof RootPackageInterface) { $sourcePath = realpath('.'); } else { $sourcePath = sys_get_temp_dir().'/composer_archive'.bin2hex(random_bytes(5)); $filesystem->ensureDirectoryExists($sourcePath); try { $promise = $this->downloadManager->download($package, $sourcePath); SyncHelper::await($this->loop, $promise); $promise = $this->downloadManager->install($package, $sourcePath); SyncHelper::await($this->loop, $promise); } catch (\Exception $e) { $filesystem->removeDirectory($sourcePath); throw $e; } if (file_exists($composerJsonPath = $sourcePath.'/composer.json')) { $jsonFile = new JsonFile($composerJsonPath); $jsonData = $jsonFile->read(); if (!empty($jsonData['archive']['name'])) { $package->setArchiveName($jsonData['archive']['name']); } if (!empty($jsonData['archive']['exclude'])) { $package->setArchiveExcludes($jsonData['archive']['exclude']); } } } $supportedFormats = $this->getSupportedFormats(); $packageNameParts = null === $fileName ? $this->getPackageFilenameParts($package) : ['base' => $fileName]; $packageName = $this->getPackageFilenameFromParts($packageNameParts); $excludePatterns = $this->buildExcludePatterns($packageNameParts, $supportedFormats); $filesystem->ensureDirectoryExists($targetDir); $target = realpath($targetDir).'/'.$packageName.'.'.$format; $filesystem->ensureDirectoryExists(dirname($target)); if (!$this->overwriteFiles && file_exists($target)) { return $target; } $tempTarget = sys_get_temp_dir().'/composer_archive'.bin2hex(random_bytes(5)).'.'.$format; $filesystem->ensureDirectoryExists(dirname($tempTarget)); $archivePath = $usableArchiver->archive( $sourcePath, $tempTarget, $format, array_merge($excludePatterns, $package->getArchiveExcludes()), $ignoreFilters ); $filesystem->rename($archivePath, $target); if (!$package instanceof RootPackageInterface) { $filesystem->removeDirectory($sourcePath); } $filesystem->remove($tempTarget); return $target; } private function buildExcludePatterns(array $parts, array $formats): array { $base = $parts['base']; if (count($parts) > 1) { $base .= '-*'; } $patterns = []; foreach ($formats as $format) { $patterns[] = "$base.$format"; } return $patterns; } private function getSupportedFormats(): array { $formats = []; foreach ($this->archivers as $archiver) { $items = []; switch (get_class($archiver)) { case ZipArchiver::class: $items = ['zip']; break; case PharArchiver::class: $items = ['zip', 'tar', 'tar.gz', 'tar.bz2']; break; } $formats = array_merge($formats, $items); } return array_unique($formats); } } sourcePath = $sourcePath; $this->excludePatterns = []; } public function filter(string $relativePath, bool $exclude): bool { foreach ($this->excludePatterns as $patternData) { [$pattern, $negate, $stripLeadingSlash] = $patternData; if ($stripLeadingSlash) { $path = substr($relativePath, 1); } else { $path = $relativePath; } try { if (Preg::isMatch($pattern, $path)) { $exclude = !$negate; } } catch (\RuntimeException $e) { } } return $exclude; } protected function parseLines(array $lines, callable $lineParser): array { return array_filter( array_map( static function ($line) use ($lineParser) { $line = trim($line); if (!$line || 0 === strpos($line, '#')) { return null; } return $lineParser($line); }, $lines ), static function ($pattern): bool { return $pattern !== null; } ); } protected function generatePatterns(array $rules): array { $patterns = []; foreach ($rules as $rule) { $patterns[] = $this->generatePattern($rule); } return $patterns; } protected function generatePattern(string $rule): array { $negate = false; $pattern = ''; if ($rule !== '' && $rule[0] === '!') { $negate = true; $rule = ltrim($rule, '!'); } $firstSlashPosition = strpos($rule, '/'); if (0 === $firstSlashPosition) { $pattern = '^/'; } elseif (false === $firstSlashPosition || strlen($rule) - 1 === $firstSlashPosition) { $pattern = '/'; } $rule = trim($rule, '/'); $rule = substr(Finder\Glob::toRegex($rule), 2, -2); return ['{'.$pattern.$rule.'(?=$|/)}', $negate, false]; } } excludePatterns = $this->generatePatterns($excludeRules); } } excludePatterns = array_merge( $this->excludePatterns, $this->parseLines( file($sourcePath.'/.gitattributes'), [$this, 'parseGitAttributesLine'] ) ); } } public function parseGitAttributesLine(string $line): ?array { $parts = Preg::split('#\s+#', $line); if (count($parts) === 2 && $parts[1] === 'export-ignore') { return $this->generatePattern($parts[0]); } if (count($parts) === 2 && $parts[1] === '-export-ignore') { return $this->generatePattern('!'.$parts[0]); } return null; } } \Phar::ZIP, 'tar' => \Phar::TAR, 'tar.gz' => \Phar::TAR, 'tar.bz2' => \Phar::TAR, ]; protected static $compressFormats = [ 'tar.gz' => \Phar::GZ, 'tar.bz2' => \Phar::BZ2, ]; public function archive(string $sources, string $target, string $format, array $excludes = [], bool $ignoreFilters = false): string { $sources = realpath($sources); if (file_exists($target)) { unlink($target); } try { $filename = substr($target, 0, strrpos($target, $format) - 1); if (isset(static::$compressFormats[$format])) { $target = $filename . '.tar'; } $phar = new PharData( $target, \FilesystemIterator::KEY_AS_PATHNAME | \FilesystemIterator::CURRENT_AS_FILEINFO, '', static::$formats[$format] ); $files = new ArchivableFilesFinder($sources, $excludes, $ignoreFilters); $filesOnly = new ArchivableFilesFilter($files); $phar->buildFromIterator($filesOnly, $sources); $filesOnly->addEmptyDir($phar, $sources); if (!file_exists($target)) { $target = $filename . '.' . $format; unset($phar); if ($format === 'tar') { file_put_contents($target, str_repeat("\0", 10240)); } elseif ($format === 'zip') { $eocd = pack( 'VvvvvVVv', 0x06054b50, 0, 0, 0, 0, 0, 0, 0 ); file_put_contents($target, $eocd); } elseif ($format === 'tar.gz' || $format === 'tar.bz2') { if (!PharData::canCompress(static::$compressFormats[$format])) { throw new \RuntimeException(sprintf('Can not compress to %s format', $format)); } if ($format === 'tar.gz' && function_exists('gzcompress')) { file_put_contents($target, gzcompress(str_repeat("\0", 10240))); } elseif ($format === 'tar.bz2' && function_exists('bzcompress')) { file_put_contents($target, bzcompress(str_repeat("\0", 10240))); } } return $target; } if (isset(static::$compressFormats[$format])) { if (!PharData::canCompress(static::$compressFormats[$format])) { throw new \RuntimeException(sprintf('Can not compress to %s format', $format)); } unlink($target); $phar->compress(static::$compressFormats[$format]); $target = $filename . '.' . $format; } return $target; } catch (\UnexpectedValueException $e) { $message = sprintf( "Could not create archive '%s' from '%s': %s", $target, $sources, $e->getMessage() ); throw new \RuntimeException($message, $e->getCode(), $e); } } public function supports(string $format, ?string $sourceType): bool { return isset(static::$formats[$format]); } } true, ]; public function archive(string $sources, string $target, string $format, array $excludes = [], bool $ignoreFilters = false): string { $fs = new Filesystem(); $sourcesRealpath = realpath($sources); if (false !== $sourcesRealpath) { $sources = $sourcesRealpath; } unset($sourcesRealpath); $sources = $fs->normalizePath($sources); $zip = new ZipArchive(); $res = $zip->open($target, ZipArchive::CREATE); if ($res === true) { $files = new ArchivableFilesFinder($sources, $excludes, $ignoreFilters); foreach ($files as $file) { $filepath = $file->getPathname(); $relativePath = $file->getRelativePathname(); if (Platform::isWindows()) { $relativePath = strtr($relativePath, '\\', '/'); } if ($file->isDir()) { $zip->addEmptyDir($relativePath); } else { $zip->addFile($filepath, $relativePath); } if (method_exists($zip, 'setExternalAttributesName')) { $perms = fileperms($filepath); $zip->setExternalAttributesName($relativePath, ZipArchive::OPSYS_UNIX, $perms << 16); } } if ($zip->close()) { if (!file_exists($target)) { $eocd = pack( 'VvvvvVVv', 0x06054b50, 0, 0, 0, 0, 0, 0, 0 ); file_put_contents($target, $eocd); } return $target; } } $message = sprintf( "Could not create archive '%s' from '%s': %s", $target, $sources, $zip->getStatusString() ); throw new \RuntimeException($message); } public function supports(string $format, ?string $sourceType): bool { return isset(static::$formats[$format]) && $this->compressionAvailable(); } private function compressionAvailable(): bool { return class_exists('ZipArchive'); } } ['description' => 'requires', 'method' => Link::TYPE_REQUIRE], 'conflict' => ['description' => 'conflicts', 'method' => Link::TYPE_CONFLICT], 'provide' => ['description' => 'provides', 'method' => Link::TYPE_PROVIDE], 'replace' => ['description' => 'replaces', 'method' => Link::TYPE_REPLACE], 'require-dev' => ['description' => 'requires (for development)', 'method' => Link::TYPE_DEV_REQUIRE], ]; public const STABILITY_STABLE = 0; public const STABILITY_RC = 5; public const STABILITY_BETA = 10; public const STABILITY_ALPHA = 15; public const STABILITY_DEV = 20; public const STABILITIES = [ 'stable' => self::STABILITY_STABLE, 'RC' => self::STABILITY_RC, 'beta' => self::STABILITY_BETA, 'alpha' => self::STABILITY_ALPHA, 'dev' => self::STABILITY_DEV, ]; public static $stabilities = self::STABILITIES; public $id; protected $name; protected $prettyName; protected $repository = null; public function __construct(string $name) { $this->prettyName = $name; $this->name = strtolower($name); $this->id = -1; } public function getName(): string { return $this->name; } public function getPrettyName(): string { return $this->prettyName; } public function getNames($provides = true): array { $names = [ $this->getName() => true, ]; if ($provides) { foreach ($this->getProvides() as $link) { $names[$link->getTarget()] = true; } } foreach ($this->getReplaces() as $link) { $names[$link->getTarget()] = true; } return array_keys($names); } public function setId(int $id): void { $this->id = $id; } public function getId(): int { return $this->id; } public function setRepository(RepositoryInterface $repository): void { if ($this->repository && $repository !== $this->repository) { throw new \LogicException(sprintf( 'Package "%s" cannot be added to repository "%s" as it is already in repository "%s".', $this->getPrettyName(), $repository->getRepoName(), $this->repository->getRepoName() )); } $this->repository = $repository; } public function getRepository(): ?RepositoryInterface { return $this->repository; } public function isPlatform(): bool { return $this->getRepository() instanceof PlatformRepository; } public function getUniqueName(): string { return $this->getName().'-'.$this->getVersion(); } public function equals(PackageInterface $package): bool { $self = $this; if ($this instanceof AliasPackage) { $self = $this->getAliasOf(); } if ($package instanceof AliasPackage) { $package = $package->getAliasOf(); } return $package === $self; } public function __toString(): string { return $this->getUniqueName(); } public function getPrettyString(): string { return $this->getPrettyName().' '.$this->getPrettyVersion(); } public function getFullPrettyVersion(bool $truncate = true, int $displayMode = PackageInterface::DISPLAY_SOURCE_REF_IF_DEV): string { if ( $displayMode === PackageInterface::DISPLAY_SOURCE_REF_IF_DEV && ( !$this->isDev() || ( !\in_array($this->getSourceType(), ['hg', 'git']) && ((string) $this->getSourceType() !== '' || (string) $this->getDistReference() === '') ) ) ) { return $this->getPrettyVersion(); } switch ($displayMode) { case PackageInterface::DISPLAY_SOURCE_REF_IF_DEV: $reference = (string) $this->getSourceReference() !== '' ? $this->getSourceReference() : $this->getDistReference(); break; case PackageInterface::DISPLAY_SOURCE_REF: $reference = $this->getSourceReference(); break; case PackageInterface::DISPLAY_DIST_REF: $reference = $this->getDistReference(); break; default: throw new \UnexpectedValueException('Display mode '.$displayMode.' is not supported'); } if (null === $reference) { return $this->getPrettyVersion(); } if ($truncate && \strlen($reference) === 40 && $this->getSourceType() !== 'svn') { return $this->getPrettyVersion() . ' ' . substr($reference, 0, 7); } return $this->getPrettyVersion() . ' ' . $reference; } public function getStabilityPriority(): int { return self::STABILITIES[$this->getStability()]; } public function __clone() { $this->repository = null; $this->id = -1; } public static function packageNameToRegexp(string $allowPattern, string $wrap = '{^%s$}i'): string { $cleanedAllowPattern = str_replace('\\*', '.*', preg_quote($allowPattern)); return sprintf($wrap, $cleanedAllowPattern); } public static function packageNamesToRegexp(array $packageNames, string $wrap = '{^(?:%s)$}iD'): string { $packageNames = array_map( static function ($packageName): string { return BasePackage::packageNameToRegexp($packageName, '%s'); }, $packageNames ); return sprintf($wrap, implode('|', $packageNames)); } } source = $source; } public function setUpdate(string $update): void { $this->update = $update; } public function getChanged(bool $explicated = false) { $changed = $this->changed; if (!count($changed)) { return false; } if ($explicated) { foreach ($changed as $sectionKey => $itemSection) { foreach ($itemSection as $itemKey => $item) { $changed[$sectionKey][$itemKey] = $item.' ('.$sectionKey.')'; } } } return $changed; } public function getChangedAsString(bool $toString = false, bool $explicated = false): string { $changed = $this->getChanged($explicated); if (false === $changed) { return ''; } $strings = []; foreach ($changed as $sectionKey => $itemSection) { foreach ($itemSection as $itemKey => $item) { $strings[] = $item."\r\n"; } } return trim(implode("\r\n", $strings)); } public function doCompare(): void { $source = []; $destination = []; $this->changed = []; $currentDirectory = Platform::getCwd(); chdir($this->source); $source = $this->doTree('.', $source); if (!is_array($source)) { return; } chdir($currentDirectory); chdir($this->update); $destination = $this->doTree('.', $destination); if (!is_array($destination)) { exit; } chdir($currentDirectory); foreach ($source as $dir => $value) { foreach ($value as $file => $hash) { if (isset($destination[$dir][$file])) { if ($hash !== $destination[$dir][$file]) { $this->changed['changed'][] = $dir.'/'.$file; } } else { $this->changed['removed'][] = $dir.'/'.$file; } } } foreach ($destination as $dir => $value) { foreach ($value as $file => $hash) { if (!isset($source[$dir][$file])) { $this->changed['added'][] = $dir.'/'.$file; } } } } private function doTree(string $dir, array &$array) { if ($dh = opendir($dir)) { while ($file = readdir($dh)) { if ($file !== '.' && $file !== '..') { if (is_link($dir.'/'.$file)) { $array[$dir][$file] = readlink($dir.'/'.$file); } elseif (is_dir($dir.'/'.$file)) { if (!count($array)) { $array[0] = 'Temp'; } if (!$this->doTree($dir.'/'.$file, $array)) { return false; } } elseif (is_file($dir.'/'.$file) && filesize($dir.'/'.$file)) { $array[$dir][$file] = hash_file(\PHP_VERSION_ID > 80100 ? 'xxh3' : 'sha1', $dir.'/'.$file); } } } if (count($array) > 1 && isset($array['0'])) { unset($array['0']); } return $array; } return false; } } aliasOf; } public function getScripts(): array { return $this->aliasOf->getScripts(); } public function setScripts(array $scripts): void { $this->aliasOf->setScripts($scripts); } public function getRepositories(): array { return $this->aliasOf->getRepositories(); } public function setRepositories(array $repositories): void { $this->aliasOf->setRepositories($repositories); } public function getLicense(): array { return $this->aliasOf->getLicense(); } public function setLicense(array $license): void { $this->aliasOf->setLicense($license); } public function getKeywords(): array { return $this->aliasOf->getKeywords(); } public function setKeywords(array $keywords): void { $this->aliasOf->setKeywords($keywords); } public function getDescription(): ?string { return $this->aliasOf->getDescription(); } public function setDescription(?string $description): void { $this->aliasOf->setDescription($description); } public function getHomepage(): ?string { return $this->aliasOf->getHomepage(); } public function setHomepage(?string $homepage): void { $this->aliasOf->setHomepage($homepage); } public function getAuthors(): array { return $this->aliasOf->getAuthors(); } public function setAuthors(array $authors): void { $this->aliasOf->setAuthors($authors); } public function getSupport(): array { return $this->aliasOf->getSupport(); } public function setSupport(array $support): void { $this->aliasOf->setSupport($support); } public function getFunding(): array { return $this->aliasOf->getFunding(); } public function setFunding(array $funding): void { $this->aliasOf->setFunding($funding); } public function isAbandoned(): bool { return $this->aliasOf->isAbandoned(); } public function getReplacementPackage(): ?string { return $this->aliasOf->getReplacementPackage(); } public function setAbandoned($abandoned): void { $this->aliasOf->setAbandoned($abandoned); } public function getArchiveName(): ?string { return $this->aliasOf->getArchiveName(); } public function setArchiveName(?string $name): void { $this->aliasOf->setArchiveName($name); } public function getArchiveExcludes(): array { return $this->aliasOf->getArchiveExcludes(); } public function setArchiveExcludes(array $excludes): void { $this->aliasOf->setArchiveExcludes($excludes); } } scripts = $scripts; } public function getScripts(): array { return $this->scripts; } public function setRepositories(array $repositories): void { $this->repositories = $repositories; } public function getRepositories(): array { return $this->repositories; } public function setLicense(array $license): void { $this->license = $license; } public function getLicense(): array { return $this->license; } public function setKeywords(array $keywords): void { $this->keywords = $keywords; } public function getKeywords(): array { return $this->keywords; } public function setAuthors(array $authors): void { $this->authors = $authors; } public function getAuthors(): array { return $this->authors; } public function setDescription(?string $description): void { $this->description = $description; } public function getDescription(): ?string { return $this->description; } public function setHomepage(?string $homepage): void { $this->homepage = $homepage; } public function getHomepage(): ?string { return $this->homepage; } public function setSupport(array $support): void { $this->support = $support; } public function getSupport(): array { return $this->support; } public function setFunding(array $funding): void { $this->funding = $funding; } public function getFunding(): array { return $this->funding; } public function isAbandoned(): bool { return (bool) $this->abandoned; } public function setAbandoned($abandoned): void { $this->abandoned = $abandoned; } public function getReplacementPackage(): ?string { return \is_string($this->abandoned) ? $this->abandoned : null; } public function setArchiveName(?string $name): void { $this->archiveName = $name; } public function getArchiveName(): ?string { return $this->archiveName; } public function setArchiveExcludes(array $excludes): void { $this->archiveExcludes = $excludes; } public function getArchiveExcludes(): array { return $this->archiveExcludes; } } 'bin', 'type', 'extra', 'installationSource' => 'installation-source', 'autoload', 'devAutoload' => 'autoload-dev', 'notificationUrl' => 'notification-url', 'includePaths' => 'include-path', 'phpExt' => 'php-ext', ]; $data = []; $data['name'] = $package->getPrettyName(); $data['version'] = $package->getPrettyVersion(); $data['version_normalized'] = $package->getVersion(); if ($package->getTargetDir() !== null) { $data['target-dir'] = $package->getTargetDir(); } if ($package->getSourceType() !== null) { $data['source']['type'] = $package->getSourceType(); $data['source']['url'] = $package->getSourceUrl(); if (null !== ($value = $package->getSourceReference())) { $data['source']['reference'] = $value; } if ($mirrors = $package->getSourceMirrors()) { $data['source']['mirrors'] = $mirrors; } } if ($package->getDistType() !== null) { $data['dist']['type'] = $package->getDistType(); $data['dist']['url'] = $package->getDistUrl(); if (null !== ($value = $package->getDistReference())) { $data['dist']['reference'] = $value; } if (null !== ($value = $package->getDistSha1Checksum())) { $data['dist']['shasum'] = $value; } if ($mirrors = $package->getDistMirrors()) { $data['dist']['mirrors'] = $mirrors; } } foreach (BasePackage::$supportedLinkTypes as $type => $opts) { $links = $package->{'get'.ucfirst($opts['method'])}(); if (\count($links) === 0) { continue; } foreach ($links as $link) { $data[$type][$link->getTarget()] = $link->getPrettyConstraint(); } ksort($data[$type]); } $packages = $package->getSuggests(); if (\count($packages) > 0) { ksort($packages); $data['suggest'] = $packages; } if ($package->getReleaseDate() instanceof \DateTimeInterface) { $data['time'] = $package->getReleaseDate()->format(DATE_RFC3339); } if ($package->isDefaultBranch()) { $data['default-branch'] = true; } $data = $this->dumpValues($package, $keys, $data); if ($package instanceof CompletePackageInterface) { if ($package->getArchiveName()) { $data['archive']['name'] = $package->getArchiveName(); } if ($package->getArchiveExcludes()) { $data['archive']['exclude'] = $package->getArchiveExcludes(); } $keys = [ 'scripts', 'license', 'authors', 'description', 'homepage', 'keywords', 'repositories', 'support', 'funding', ]; $data = $this->dumpValues($package, $keys, $data); if (isset($data['keywords']) && \is_array($data['keywords'])) { sort($data['keywords']); } if ($package->isAbandoned()) { $data['abandoned'] = $package->getReplacementPackage() ?: true; } } if ($package instanceof RootPackageInterface) { $minimumStability = $package->getMinimumStability(); if ($minimumStability !== '') { $data['minimum-stability'] = $minimumStability; } } if (\count($package->getTransportOptions()) > 0) { $data['transport-options'] = $package->getTransportOptions(); } return $data; } private function dumpValues(PackageInterface $package, array $keys, array $data): array { foreach ($keys as $method => $key) { if (is_numeric($method)) { $method = $key; } $getter = 'get'.ucfirst($method); $value = $package->{$getter}(); if (null !== $value && !(\is_array($value) && 0 === \count($value))) { $data[$key] = $value; } } return $data; } } source = strtolower($source); $this->target = strtolower($target); $this->constraint = $constraint; $this->description = self::TYPE_DEV_REQUIRE === $description ? 'requires (for development)' : $description; $this->prettyConstraint = $prettyConstraint; } public function getDescription(): string { return $this->description; } public function getSource(): string { return $this->source; } public function getTarget(): string { return $this->target; } public function getConstraint(): ConstraintInterface { return $this->constraint; } public function getPrettyConstraint(): string { if (null === $this->prettyConstraint) { throw new \UnexpectedValueException(sprintf('Link %s has been misconfigured and had no prettyConstraint given.', $this)); } return $this->prettyConstraint; } public function __toString(): string { return $this->source.' '.$this->description.' '.$this->target.' ('.$this->constraint.')'; } public function getPrettyString(PackageInterface $sourcePackage): string { return $sourcePackage->getPrettyString().' '.$this->description.' '.$this->target.' '.$this->constraint->getPrettyString(); } } versionParser = $parser; $this->loadOptions = $loadOptions; } public function load(array $config, string $class = 'Composer\Package\CompletePackage'): BasePackage { if ($class !== 'Composer\Package\CompletePackage' && $class !== 'Composer\Package\RootPackage') { trigger_error('The $class arg is deprecated, please reach out to Composer maintainers ASAP if you still need this.', E_USER_DEPRECATED); } $package = $this->createObject($config, $class); foreach (BasePackage::$supportedLinkTypes as $type => $opts) { if (!isset($config[$type]) || !is_array($config[$type])) { continue; } $method = 'set'.ucfirst($opts['method']); $package->{$method}( $this->parseLinks( $package->getName(), $package->getPrettyVersion(), $opts['method'], $config[$type] ) ); } $package = $this->configureObject($package, $config); return $package; } public function loadPackages(array $versions): array { $packages = []; $linkCache = []; foreach ($versions as $version) { $package = $this->createObject($version, 'Composer\Package\CompletePackage'); $this->configureCachedLinks($linkCache, $package, $version); $package = $this->configureObject($package, $version); $packages[] = $package; } return $packages; } private function createObject(array $config, string $class): CompletePackage { if (!isset($config['name'])) { throw new \UnexpectedValueException('Unknown package has no name defined ('.json_encode($config).').'); } if (!isset($config['version']) || !is_scalar($config['version'])) { throw new \UnexpectedValueException('Package '.$config['name'].' has no version defined.'); } if (!is_string($config['version'])) { $config['version'] = (string) $config['version']; } if (isset($config['version_normalized']) && is_string($config['version_normalized'])) { $version = $config['version_normalized']; if ($version === VersionParser::DEFAULT_BRANCH_ALIAS) { $version = $this->versionParser->normalize($config['version']); } } else { try { $version = $this->versionParser->normalize($config['version']); } catch (\UnexpectedValueException $e) { throw new \UnexpectedValueException( 'Failed to normalize version for package "'.$config['name'].'": '.$e->getMessage(), $e->getCode(), $e ); } } return new $class($config['name'], $version, $config['version']); } private function configureObject(PackageInterface $package, array $config): BasePackage { if (!$package instanceof CompletePackage) { throw new \LogicException('ArrayLoader expects instances of the Composer\Package\CompletePackage class to function correctly'); } $package->setType(isset($config['type']) ? strtolower($config['type']) : 'library'); if (isset($config['target-dir'])) { $package->setTargetDir($config['target-dir']); } if (isset($config['extra']) && \is_array($config['extra'])) { $package->setExtra($config['extra']); } if (isset($config['bin'])) { if (!\is_array($config['bin'])) { $config['bin'] = [$config['bin']]; } foreach ($config['bin'] as $key => $bin) { $config['bin'][$key] = ltrim($bin, '/'); } $package->setBinaries($config['bin']); } if (isset($config['installation-source'])) { $package->setInstallationSource($config['installation-source']); } if (isset($config['default-branch']) && $config['default-branch'] === true) { $package->setIsDefaultBranch(true); } if (isset($config['source'])) { if (!isset($config['source']['type'], $config['source']['url'], $config['source']['reference'])) { throw new \UnexpectedValueException(sprintf( "Package %s's source key should be specified as {\"type\": ..., \"url\": ..., \"reference\": ...},\n%s given.", $config['name'], json_encode($config['source']) )); } $package->setSourceType($config['source']['type']); $package->setSourceUrl($config['source']['url']); $package->setSourceReference(isset($config['source']['reference']) ? (string) $config['source']['reference'] : null); if (isset($config['source']['mirrors'])) { $package->setSourceMirrors($config['source']['mirrors']); } } if (isset($config['dist'])) { if (!isset($config['dist']['type'], $config['dist']['url'])) { throw new \UnexpectedValueException(sprintf( "Package %s's dist key should be specified as ". "{\"type\": ..., \"url\": ..., \"reference\": ..., \"shasum\": ...},\n%s given.", $config['name'], json_encode($config['dist']) )); } $package->setDistType($config['dist']['type']); $package->setDistUrl($config['dist']['url']); $package->setDistReference(isset($config['dist']['reference']) ? (string) $config['dist']['reference'] : null); $package->setDistSha1Checksum($config['dist']['shasum'] ?? null); if (isset($config['dist']['mirrors'])) { $package->setDistMirrors($config['dist']['mirrors']); } } if (isset($config['suggest']) && \is_array($config['suggest'])) { foreach ($config['suggest'] as $target => $reason) { if ('self.version' === trim($reason)) { $config['suggest'][$target] = $package->getPrettyVersion(); } } $package->setSuggests($config['suggest']); } if (isset($config['autoload'])) { $package->setAutoload($config['autoload']); } if (isset($config['autoload-dev'])) { $package->setDevAutoload($config['autoload-dev']); } if (isset($config['include-path'])) { $package->setIncludePaths($config['include-path']); } if (isset($config['php-ext'])) { $package->setPhpExt($config['php-ext']); } if (!empty($config['time'])) { $time = Preg::isMatch('/^\d++$/D', $config['time']) ? '@'.$config['time'] : $config['time']; try { $date = new \DateTime($time, new \DateTimeZone('UTC')); $package->setReleaseDate($date); } catch (\Exception $e) { } } if (!empty($config['notification-url'])) { $package->setNotificationUrl($config['notification-url']); } if ($package instanceof CompletePackageInterface) { if (!empty($config['archive']['name'])) { $package->setArchiveName($config['archive']['name']); } if (!empty($config['archive']['exclude'])) { $package->setArchiveExcludes($config['archive']['exclude']); } if (isset($config['scripts']) && \is_array($config['scripts'])) { foreach ($config['scripts'] as $event => $listeners) { $config['scripts'][$event] = (array) $listeners; } foreach (['composer', 'php', 'putenv'] as $reserved) { if (isset($config['scripts'][$reserved])) { trigger_error('The `'.$reserved.'` script name is reserved for internal use, please avoid defining it', E_USER_DEPRECATED); } } $package->setScripts($config['scripts']); } if (!empty($config['description']) && \is_string($config['description'])) { $package->setDescription($config['description']); } if (!empty($config['homepage']) && \is_string($config['homepage'])) { $package->setHomepage($config['homepage']); } if (!empty($config['keywords']) && \is_array($config['keywords'])) { $package->setKeywords(array_map('strval', $config['keywords'])); } if (!empty($config['license'])) { $package->setLicense(\is_array($config['license']) ? $config['license'] : [$config['license']]); } if (!empty($config['authors']) && \is_array($config['authors'])) { $package->setAuthors($config['authors']); } if (isset($config['support']) && \is_array($config['support'])) { $package->setSupport($config['support']); } if (!empty($config['funding']) && \is_array($config['funding'])) { $package->setFunding($config['funding']); } if (isset($config['abandoned'])) { $package->setAbandoned($config['abandoned']); } } if ($this->loadOptions && isset($config['transport-options'])) { $package->setTransportOptions($config['transport-options']); } if ($aliasNormalized = $this->getBranchAlias($config)) { $prettyAlias = Preg::replace('{(\.9{7})+}', '.x', $aliasNormalized); if ($package instanceof RootPackage) { return new RootAliasPackage($package, $aliasNormalized, $prettyAlias); } return new CompleteAliasPackage($package, $aliasNormalized, $prettyAlias); } return $package; } private function configureCachedLinks(array &$linkCache, PackageInterface $package, array $config): void { $name = $package->getName(); $prettyVersion = $package->getPrettyVersion(); foreach (BasePackage::$supportedLinkTypes as $type => $opts) { if (isset($config[$type])) { $method = 'set'.ucfirst($opts['method']); $links = []; foreach ($config[$type] as $prettyTarget => $constraint) { $target = strtolower($prettyTarget); if ($target === $name) { continue; } if ($constraint === 'self.version') { $links[$target] = $this->createLink($name, $prettyVersion, $opts['method'], $target, $constraint); } else { if (!isset($linkCache[$name][$type][$target][$constraint])) { $linkCache[$name][$type][$target][$constraint] = [$target, $this->createLink($name, $prettyVersion, $opts['method'], $target, $constraint)]; } [$target, $link] = $linkCache[$name][$type][$target][$constraint]; $links[$target] = $link; } } $package->{$method}($links); } } } public function parseLinks(string $source, string $sourceVersion, string $description, array $links): array { $res = []; foreach ($links as $target => $constraint) { if (!is_string($constraint)) { continue; } $target = strtolower((string) $target); $res[$target] = $this->createLink($source, $sourceVersion, $description, $target, $constraint); } return $res; } private function createLink(string $source, string $sourceVersion, string $description, string $target, string $prettyConstraint): Link { if (!\is_string($prettyConstraint)) { throw new \UnexpectedValueException('Link constraint in '.$source.' '.$description.' > '.$target.' should be a string, got '.\get_debug_type($prettyConstraint) . ' (' . var_export($prettyConstraint, true) . ')'); } if ('self.version' === $prettyConstraint) { $constraint = $sourceVersion; } else { $constraint = $prettyConstraint; } try { $parsedConstraint = $this->versionParser->parseConstraints($constraint); } catch (\UnexpectedValueException $e) { throw new \UnexpectedValueException( 'Link constraint in '.$source.' '.$description.' > '.$target.' should be a valid version constraint, got "'.$constraint.'"', $e->getCode(), $e ); } return new Link($source, $target, $parsedConstraint, $description, $prettyConstraint); } public function getBranchAlias(array $config): ?string { if (!isset($config['version']) || !is_scalar($config['version'])) { throw new \UnexpectedValueException('no/invalid version defined'); } if (!is_string($config['version'])) { $config['version'] = (string) $config['version']; } if (strpos($config['version'], 'dev-') !== 0 && '-dev' !== substr($config['version'], -4)) { return null; } if (isset($config['extra']['branch-alias']) && \is_array($config['extra']['branch-alias'])) { foreach ($config['extra']['branch-alias'] as $sourceBranch => $targetBranch) { $sourceBranch = (string) $sourceBranch; if ('-dev' !== substr($targetBranch, -4)) { continue; } if ($targetBranch === VersionParser::DEFAULT_BRANCH_ALIAS) { $validatedTargetBranch = VersionParser::DEFAULT_BRANCH_ALIAS; } else { $validatedTargetBranch = $this->versionParser->normalizeBranch(substr($targetBranch, 0, -4)); } if ('-dev' !== substr($validatedTargetBranch, -4)) { continue; } if (strtolower($config['version']) !== strtolower($sourceBranch)) { continue; } if (($sourcePrefix = $this->versionParser->parseNumericAliasPrefix($sourceBranch)) && ($targetPrefix = $this->versionParser->parseNumericAliasPrefix($targetBranch)) && (stripos($targetPrefix, $sourcePrefix) !== 0) ) { continue; } return $validatedTargetBranch; } } if ( isset($config['default-branch']) && $config['default-branch'] === true && false === $this->versionParser->parseNumericAliasPrefix(Preg::replace('{^v}', '', $config['version'])) ) { return VersionParser::DEFAULT_BRANCH_ALIAS; } return null; } } errors = $errors; $this->warnings = $warnings; $this->data = $data; parent::__construct("Invalid package information: \n".implode("\n", array_merge($errors, $warnings))); } public function getData(): array { return $this->data; } public function getErrors(): array { return $this->errors; } public function getWarnings(): array { return $this->warnings; } } loader = $loader; } public function load($json): BasePackage { if ($json instanceof JsonFile) { $config = $json->read(); } elseif (file_exists($json)) { $config = JsonFile::parseJson(file_get_contents($json), $json); } elseif (is_string($json)) { $config = JsonFile::parseJson($json); } else { throw new \InvalidArgumentException(sprintf( "JsonLoader: Unknown \$json parameter %s. Please report at https://github.com/composer/composer/issues/new.", get_debug_type($json) )); } return $this->loader->load($config); } } manager = $manager; $this->config = $config; if (null === $versionGuesser) { $processExecutor = new ProcessExecutor($io); $processExecutor->enableAsync(); $versionGuesser = new VersionGuesser($config, $processExecutor, $this->versionParser); } $this->versionGuesser = $versionGuesser; $this->io = $io; } public function load(array $config, string $class = 'Composer\Package\RootPackage', ?string $cwd = null): BasePackage { if ($class !== 'Composer\Package\RootPackage') { trigger_error('The $class arg is deprecated, please reach out to Composer maintainers ASAP if you still need this.', E_USER_DEPRECATED); } if (!isset($config['name'])) { $config['name'] = '__root__'; } elseif ($err = ValidatingArrayLoader::hasPackageNamingError($config['name'])) { throw new \RuntimeException('Your package name '.$err); } $autoVersioned = false; if (!isset($config['version'])) { $commit = null; if (Platform::getEnv('COMPOSER_ROOT_VERSION')) { $config['version'] = $this->versionGuesser->getRootVersionFromEnv(); } else { $versionData = $this->versionGuesser->guessVersion($config, $cwd ?? Platform::getCwd(true)); if ($versionData) { $config['version'] = $versionData['pretty_version']; $config['version_normalized'] = $versionData['version']; $commit = $versionData['commit']; } } if (!isset($config['version'])) { if ($this->io !== null && $config['name'] !== '__root__' && 'project' !== ($config['type'] ?? '')) { $this->io->warning( sprintf( "Composer could not detect the root package (%s) version, defaulting to '1.0.0'. See https://getcomposer.org/root-version", $config['name'] ) ); } $config['version'] = '1.0.0'; $autoVersioned = true; } if ($commit) { $config['source'] = [ 'type' => '', 'url' => '', 'reference' => $commit, ]; $config['dist'] = [ 'type' => '', 'url' => '', 'reference' => $commit, ]; } } $package = parent::load($config, $class); if ($package instanceof RootAliasPackage) { $realPackage = $package->getAliasOf(); } else { $realPackage = $package; } if (!$realPackage instanceof RootPackage) { throw new \LogicException('Expecting a Composer\Package\RootPackage at this point'); } if ($autoVersioned) { $realPackage->replaceVersion($realPackage->getVersion(), RootPackage::DEFAULT_PRETTY_VERSION); } if (isset($config['minimum-stability'])) { $realPackage->setMinimumStability(VersionParser::normalizeStability($config['minimum-stability'])); } $aliases = []; $stabilityFlags = []; $references = []; foreach (['require', 'require-dev'] as $linkType) { if (isset($config[$linkType])) { $linkInfo = BasePackage::$supportedLinkTypes[$linkType]; $method = 'get'.ucfirst($linkInfo['method']); $links = []; foreach ($realPackage->{$method}() as $link) { $links[$link->getTarget()] = $link->getConstraint()->getPrettyString(); } $aliases = $this->extractAliases($links, $aliases); $stabilityFlags = self::extractStabilityFlags($links, $realPackage->getMinimumStability(), $stabilityFlags); $references = self::extractReferences($links, $references); if (isset($links[$config['name']])) { throw new \RuntimeException(sprintf('Root package \'%s\' cannot require itself in its composer.json' . PHP_EOL . 'Did you accidentally name your root package after an external package?', $config['name'])); } } } foreach (array_keys(BasePackage::$supportedLinkTypes) as $linkType) { if (isset($config[$linkType])) { foreach ($config[$linkType] as $linkName => $constraint) { if ($err = ValidatingArrayLoader::hasPackageNamingError($linkName, true)) { throw new \RuntimeException($linkType.'.'.$err); } } } } $realPackage->setAliases($aliases); $realPackage->setStabilityFlags($stabilityFlags); $realPackage->setReferences($references); if (isset($config['prefer-stable'])) { $realPackage->setPreferStable((bool) $config['prefer-stable']); } if (isset($config['config'])) { $realPackage->setConfig($config['config']); } $repos = RepositoryFactory::defaultRepos(null, $this->config, $this->manager); foreach ($repos as $repo) { $this->manager->addRepository($repo); } $realPackage->setRepositories($this->config->getRepositories()); return $package; } private function extractAliases(array $requires, array $aliases): array { foreach ($requires as $reqName => $reqVersion) { if (Preg::isMatchStrictGroups('{(?:^|\| *|, *)([^,\s#|]+)(?:#[^ ]+)? +as +([^,\s|]+)(?:$| *\|| *,)}', $reqVersion, $match)) { $aliases[] = [ 'package' => strtolower($reqName), 'version' => $this->versionParser->normalize($match[1], $reqVersion), 'alias' => $match[2], 'alias_normalized' => $this->versionParser->normalize($match[2], $reqVersion), ]; } elseif (strpos($reqVersion, ' as ') !== false) { throw new \UnexpectedValueException('Invalid alias definition in "'.$reqName.'": "'.$reqVersion.'". Aliases should be in the form "exact-version as other-exact-version".'); } } return $aliases; } public static function extractStabilityFlags(array $requires, string $minimumStability, array $stabilityFlags): array { $stabilities = BasePackage::STABILITIES; $minimumStability = $stabilities[$minimumStability]; foreach ($requires as $reqName => $reqVersion) { $constraints = []; $orSplit = Preg::split('{\s*\|\|?\s*}', trim($reqVersion)); foreach ($orSplit as $orConstraint) { $andSplit = Preg::split('{(?< ,]) *(? $stability) { continue; } $stabilityFlags[$name] = $stability; $matched = true; } } if ($matched) { continue; } foreach ($constraints as $constraint) { $reqVersion = Preg::replace('{^([^,\s@]+) as .+$}', '$1', $constraint); if (Preg::isMatch('{^[^,\s@]+$}', $reqVersion) && 'stable' !== ($stabilityName = VersionParser::parseStability($reqVersion))) { $name = strtolower($reqName); $stability = $stabilities[$stabilityName]; if ((isset($stabilityFlags[$name]) && $stabilityFlags[$name] > $stability) || ($minimumStability > $stability)) { continue; } $stabilityFlags[$name] = $stability; } } } return $stabilityFlags; } public static function extractReferences(array $requires, array $references): array { foreach ($requires as $reqName => $reqVersion) { $reqVersion = Preg::replace('{^([^,\s@]+) as .+$}', '$1', $reqVersion); if (Preg::isMatchStrictGroups('{^[^,\s@]+?#([a-f0-9]+)$}', $reqVersion, $match) && 'dev' === VersionParser::parseStability($reqVersion)) { $name = strtolower($reqName); $references[$name] = $match[1]; } } return $references; } } loader = $loader; $this->versionParser = $parser ?? new VersionParser(); $this->flags = $flags; if ($strictName !== true) { trigger_error('$strictName must be set to true in ValidatingArrayLoader\'s constructor as of 2.2, and it will be removed in 3.0', E_USER_DEPRECATED); } } public function load(array $config, string $class = 'Composer\Package\CompletePackage'): BasePackage { $this->errors = []; $this->warnings = []; $this->config = $config; $this->validateString('name', true); if (isset($config['name']) && null !== ($err = self::hasPackageNamingError($config['name']))) { $this->errors[] = 'name : '.$err; } if (isset($this->config['version'])) { if (!is_scalar($this->config['version'])) { $this->validateString('version'); } else { if (!is_string($this->config['version'])) { $this->config['version'] = (string) $this->config['version']; } try { $this->versionParser->normalize($this->config['version']); } catch (\Exception $e) { $this->errors[] = 'version : invalid value ('.$this->config['version'].'): '.$e->getMessage(); unset($this->config['version']); } } } if (isset($this->config['config']['platform'])) { foreach ((array) $this->config['config']['platform'] as $key => $platform) { if (false === $platform) { continue; } if (!is_string($platform)) { $this->errors[] = 'config.platform.' . $key . ' : invalid value ('.get_debug_type($platform).' '.var_export($platform, true).'): expected string or false'; continue; } try { $this->versionParser->normalize($platform); } catch (\Exception $e) { $this->errors[] = 'config.platform.' . $key . ' : invalid value ('.$platform.'): '.$e->getMessage(); } } } $this->validateRegex('type', '[A-Za-z0-9-]+'); $this->validateString('target-dir'); $this->validateArray('extra'); if (isset($this->config['bin'])) { if (is_string($this->config['bin'])) { $this->validateString('bin'); } else { $this->validateFlatArray('bin'); } } $this->validateArray('scripts'); $this->validateString('description'); $this->validateUrl('homepage'); $this->validateFlatArray('keywords', '[\p{N}\p{L} ._-]+'); $releaseDate = null; $this->validateString('time'); if (isset($this->config['time'])) { try { $releaseDate = new \DateTime($this->config['time'], new \DateTimeZone('UTC')); } catch (\Exception $e) { $this->errors[] = 'time : invalid value ('.$this->config['time'].'): '.$e->getMessage(); unset($this->config['time']); } } if (isset($this->config['license'])) { if (is_array($this->config['license']) || is_string($this->config['license'])) { $licenses = (array) $this->config['license']; foreach ($licenses as $index => $license) { if (!is_string($license)) { $this->warnings[] = sprintf( 'License %s should be a string.', json_encode($license) ); unset($licenses[$index]); } } if (null === $releaseDate || $releaseDate->getTimestamp() >= strtotime('-8days')) { $licenseValidator = new SpdxLicenses(); foreach ($licenses as $license) { if ('proprietary' === $license) { continue; } $licenseToValidate = str_replace('proprietary', 'MIT', $license); if (!$licenseValidator->validate($licenseToValidate)) { if ($licenseValidator->validate(trim($licenseToValidate))) { $this->warnings[] = sprintf( 'License %s must not contain extra spaces, make sure to trim it.', json_encode($license) ); } else { $this->warnings[] = sprintf( 'License %s is not a valid SPDX license identifier, see https://spdx.org/licenses/ if you use an open license.' . PHP_EOL . 'If the software is closed-source, you may use "proprietary" as license.', json_encode($license) ); } } } } $this->config['license'] = array_values($licenses); } else { $this->warnings[] = sprintf( 'License must be a string or array of strings, got %s.', json_encode($this->config['license']) ); unset($this->config['license']); } } if ($this->validateArray('authors')) { foreach ($this->config['authors'] as $key => $author) { if (!is_array($author)) { $this->errors[] = 'authors.'.$key.' : should be an array, '.get_debug_type($author).' given'; unset($this->config['authors'][$key]); continue; } foreach (['homepage', 'email', 'name', 'role'] as $authorData) { if (isset($author[$authorData]) && !is_string($author[$authorData])) { $this->errors[] = 'authors.'.$key.'.'.$authorData.' : invalid value, must be a string'; unset($this->config['authors'][$key][$authorData]); } } if (isset($author['homepage']) && !$this->filterUrl($author['homepage'])) { $this->warnings[] = 'authors.'.$key.'.homepage : invalid value ('.$author['homepage'].'), must be an http/https URL'; unset($this->config['authors'][$key]['homepage']); } if (isset($author['email']) && false === filter_var($author['email'], FILTER_VALIDATE_EMAIL)) { $this->warnings[] = 'authors.'.$key.'.email : invalid value ('.$author['email'].'), must be a valid email address'; unset($this->config['authors'][$key]['email']); } if (\count($this->config['authors'][$key]) === 0) { unset($this->config['authors'][$key]); } } if (\count($this->config['authors']) === 0) { unset($this->config['authors']); } } if ($this->validateArray('support') && !empty($this->config['support'])) { foreach (['issues', 'forum', 'wiki', 'source', 'email', 'irc', 'docs', 'rss', 'chat', 'security'] as $key) { if (isset($this->config['support'][$key]) && !is_string($this->config['support'][$key])) { $this->errors[] = 'support.'.$key.' : invalid value, must be a string'; unset($this->config['support'][$key]); } } if (isset($this->config['support']['email']) && !filter_var($this->config['support']['email'], FILTER_VALIDATE_EMAIL)) { $this->warnings[] = 'support.email : invalid value ('.$this->config['support']['email'].'), must be a valid email address'; unset($this->config['support']['email']); } if (isset($this->config['support']['irc']) && !$this->filterUrl($this->config['support']['irc'], ['irc', 'ircs'])) { $this->warnings[] = 'support.irc : invalid value ('.$this->config['support']['irc'].'), must be a irc:/// or ircs:// URL'; unset($this->config['support']['irc']); } foreach (['issues', 'forum', 'wiki', 'source', 'docs', 'chat', 'security'] as $key) { if (isset($this->config['support'][$key]) && !$this->filterUrl($this->config['support'][$key])) { $this->warnings[] = 'support.'.$key.' : invalid value ('.$this->config['support'][$key].'), must be an http/https URL'; unset($this->config['support'][$key]); } } if (empty($this->config['support'])) { unset($this->config['support']); } } if ($this->validateArray('funding') && !empty($this->config['funding'])) { foreach ($this->config['funding'] as $key => $fundingOption) { if (!is_array($fundingOption)) { $this->errors[] = 'funding.'.$key.' : should be an array, '.get_debug_type($fundingOption).' given'; unset($this->config['funding'][$key]); continue; } foreach (['type', 'url'] as $fundingData) { if (isset($fundingOption[$fundingData]) && !is_string($fundingOption[$fundingData])) { $this->errors[] = 'funding.'.$key.'.'.$fundingData.' : invalid value, must be a string'; unset($this->config['funding'][$key][$fundingData]); } } if (isset($fundingOption['url']) && !$this->filterUrl($fundingOption['url'])) { $this->warnings[] = 'funding.'.$key.'.url : invalid value ('.$fundingOption['url'].'), must be an http/https URL'; unset($this->config['funding'][$key]['url']); } if (empty($this->config['funding'][$key])) { unset($this->config['funding'][$key]); } } if (empty($this->config['funding'])) { unset($this->config['funding']); } } if (isset($this->config['php-ext']) && $this->validateArray('php-ext')) { if (!in_array($this->config['type'] ?? '', ['php-ext', 'php-ext-zend'], true)) { $this->errors[] = 'php-ext can only be set by packages of type "php-ext" or "php-ext-zend" which must be C extensions'; unset($this->config['php-ext']); } $phpExt = &$this->config['php-ext']; if (isset($phpExt['extension-name']) && !is_string($phpExt['extension-name'])) { $this->errors[] = 'php-ext.extension-name : should be a string, '.get_debug_type($phpExt['extension-name']).' given'; unset($phpExt['extension-name']); } if (isset($phpExt['priority']) && !is_int($phpExt['priority'])) { $this->errors[] = 'php-ext.priority : should be an integer, '.get_debug_type($phpExt['priority']).' given'; unset($phpExt['priority']); } if (isset($phpExt['support-zts']) && !is_bool($phpExt['support-zts'])) { $this->errors[] = 'php-ext.support-zts : should be a boolean, '.get_debug_type($phpExt['support-zts']).' given'; unset($phpExt['support-zts']); } if (isset($phpExt['support-nts']) && !is_bool($phpExt['support-nts'])) { $this->errors[] = 'php-ext.support-nts : should be a boolean, '.get_debug_type($phpExt['support-nts']).' given'; unset($phpExt['support-nts']); } if (isset($phpExt['build-path']) && !is_string($phpExt['build-path']) && !is_null($phpExt['build-path'])) { $this->errors[] = 'php-ext.build-path : should be a string or null, '.get_debug_type($phpExt['build-path']).' given'; unset($phpExt['build-path']); } if (isset($phpExt['download-url-method'])) { if (!is_array($phpExt['download-url-method']) && !is_string($phpExt['download-url-method'])) { $this->errors[] = 'php-ext.download-url-method : should be an array or a string, '.get_debug_type($phpExt['download-url-method']).' given'; unset($phpExt['download-url-method']); } else { $validDownloadUrlMethods = ['composer-default', 'pre-packaged-source', 'pre-packaged-binary']; $definedDownloadUrlMethods = is_array($phpExt['download-url-method']) ? $phpExt['download-url-method'] : [$phpExt['download-url-method']]; if ([] === $definedDownloadUrlMethods) { $this->errors[] = 'php-ext.download-url-method : must contain at least one element'; unset($phpExt['download-url-method']); } else { foreach ($definedDownloadUrlMethods as $key => $downloadUrlMethod) { if (!is_string($downloadUrlMethod)) { $this->errors[] = 'php-ext.download-url-method.'.$key.' : should be a string, '.get_debug_type($downloadUrlMethod).' given'; unset($phpExt['download-url-method']); } elseif (!in_array($downloadUrlMethod, $validDownloadUrlMethods, true)) { $this->errors[] = 'php-ext.download-url-method.'.$key.' : invalid value ('.$downloadUrlMethod.'), must be one of ' . implode(', ', $validDownloadUrlMethods); unset($phpExt['download-url-method']); } } } } } if (isset($phpExt['os-families']) && isset($phpExt['os-families-exclude'])) { $this->errors[] = 'php-ext : os-families and os-families-exclude cannot both be specified'; unset($phpExt['os-families'], $phpExt['os-families-exclude']); } else { $validOsFamilies = ['windows', 'bsd', 'darwin', 'solaris', 'linux', 'unknown']; foreach (['os-families', 'os-families-exclude'] as $fieldName) { if (isset($phpExt[$fieldName])) { if (!is_array($phpExt[$fieldName])) { $this->errors[] = 'php-ext.'.$fieldName.' : should be an array, '.get_debug_type($phpExt[$fieldName]).' given'; unset($phpExt[$fieldName]); } elseif ([] === $phpExt[$fieldName]) { $this->errors[] = 'php-ext.'.$fieldName.' : must contain at least one element'; unset($phpExt[$fieldName]); } else { foreach ($phpExt[$fieldName] as $key => $osFamily) { if (!is_string($osFamily)) { $this->errors[] = 'php-ext.'.$fieldName.'.'.$key.' : should be a string, '.get_debug_type($osFamily).' given'; unset($phpExt[$fieldName][$key]); } elseif (!in_array($osFamily, $validOsFamilies, true)) { $this->errors[] = 'php-ext.'.$fieldName.'.'.$key.' : invalid value ('.$osFamily.'), must be one of '.implode(', ', $validOsFamilies); unset($phpExt[$fieldName][$key]); } } if ([] === $phpExt[$fieldName]) { unset($phpExt[$fieldName]); } } } } } if (isset($phpExt['configure-options'])) { if (!is_array($phpExt['configure-options'])) { $this->errors[] = 'php-ext.configure-options : should be an array, '.get_debug_type($phpExt['configure-options']).' given'; unset($phpExt['configure-options']); } else { foreach ($phpExt['configure-options'] as $key => $option) { if (!is_array($option)) { $this->errors[] = 'php-ext.configure-options.'.$key.' : should be an array, '.get_debug_type($option).' given'; unset($phpExt['configure-options'][$key]); continue; } if (!isset($option['name'])) { $this->errors[] = 'php-ext.configure-options.'.$key.'.name : must be present'; unset($phpExt['configure-options'][$key]); continue; } if (!is_string($option['name'])) { $this->errors[] = 'php-ext.configure-options.'.$key.'.name : should be a string, '.get_debug_type($option['name']).' given'; unset($phpExt['configure-options'][$key]); continue; } if (isset($option['needs-value']) && !is_bool($option['needs-value'])) { $this->errors[] = 'php-ext.configure-options.'.$key.'.needs-value : should be a boolean, '.get_debug_type($option['needs-value']).' given'; unset($phpExt['configure-options'][$key]['needs-value']); } if (isset($option['description']) && !is_string($option['description'])) { $this->errors[] = 'php-ext.configure-options.'.$key.'.description : should be a string, '.get_debug_type($option['description']).' given'; unset($phpExt['configure-options'][$key]['description']); } } if ([] === $phpExt['configure-options']) { unset($phpExt['configure-options']); } } } if ([] === $phpExt) { unset($this->config['php-ext']); } unset($phpExt); } $unboundConstraint = new Constraint('=', '10000000-dev'); foreach (array_keys(BasePackage::$supportedLinkTypes) as $linkType) { if ($this->validateArray($linkType) && isset($this->config[$linkType])) { foreach ($this->config[$linkType] as $package => $constraint) { $package = (string) $package; if (isset($this->config['name']) && 0 === strcasecmp($package, $this->config['name'])) { $this->errors[] = $linkType.'.'.$package.' : a package cannot set a '.$linkType.' on itself'; unset($this->config[$linkType][$package]); continue; } if ($err = self::hasPackageNamingError($package, true)) { $this->warnings[] = $linkType.'.'.$err; } elseif (!Preg::isMatch('{^[A-Za-z0-9_./-]+$}', $package)) { $this->errors[] = $linkType.'.'.$package.' : invalid key, package names must be strings containing only [A-Za-z0-9_./-]'; } if (!is_string($constraint)) { $this->errors[] = $linkType.'.'.$package.' : invalid value, must be a string containing a version constraint'; unset($this->config[$linkType][$package]); } elseif ('self.version' !== $constraint) { try { $linkConstraint = $this->versionParser->parseConstraints($constraint); } catch (\Exception $e) { $this->errors[] = $linkType.'.'.$package.' : invalid version constraint ('.$e->getMessage().')'; unset($this->config[$linkType][$package]); continue; } if ( ($this->flags & self::CHECK_UNBOUND_CONSTRAINTS) && 'require' === $linkType && $linkConstraint->matches($unboundConstraint) && !PlatformRepository::isPlatformPackage($package) ) { $this->warnings[] = $linkType.'.'.$package.' : unbound version constraints ('.$constraint.') should be avoided'; } elseif ( ($this->flags & self::CHECK_STRICT_CONSTRAINTS) && 'require' === $linkType && $linkConstraint instanceof Constraint && in_array($linkConstraint->getOperator(), ['==', '='], true) && (new Constraint('>=', '1.0.0.0-dev'))->matches($linkConstraint) ) { $this->warnings[] = $linkType.'.'.$package.' : exact version constraints ('.$constraint.') should be avoided if the package follows semantic versioning'; } $compacted = Intervals::compactConstraint($linkConstraint); if ($compacted instanceof MatchNoneConstraint) { $this->warnings[] = $linkType.'.'.$package.' : this version constraint cannot possibly match anything ('.$constraint.')'; } } if ($linkType === 'conflict' && isset($this->config['replace']) && $keys = array_intersect_key($this->config['replace'], $this->config['conflict'])) { $this->errors[] = $linkType.'.'.$package.' : you cannot conflict with a package that is also replaced, as replace already creates an implicit conflict rule'; unset($this->config[$linkType][$package]); } } } } if ($this->validateArray('suggest') && isset($this->config['suggest'])) { foreach ($this->config['suggest'] as $package => $description) { if (!is_string($description)) { $this->errors[] = 'suggest.'.$package.' : invalid value, must be a string describing why the package is suggested'; unset($this->config['suggest'][$package]); } } } if ($this->validateString('minimum-stability') && isset($this->config['minimum-stability'])) { if (!isset(BasePackage::STABILITIES[strtolower($this->config['minimum-stability'])]) && $this->config['minimum-stability'] !== 'RC') { $this->errors[] = 'minimum-stability : invalid value ('.$this->config['minimum-stability'].'), must be one of '.implode(', ', array_keys(BasePackage::STABILITIES)); unset($this->config['minimum-stability']); } } if ($this->validateArray('autoload') && isset($this->config['autoload'])) { $types = ['psr-0', 'psr-4', 'classmap', 'files', 'exclude-from-classmap']; foreach ($this->config['autoload'] as $type => $typeConfig) { if (!in_array($type, $types)) { $this->errors[] = 'autoload : invalid value ('.$type.'), must be one of '.implode(', ', $types); unset($this->config['autoload'][$type]); } if ($type === 'psr-4') { foreach ($typeConfig as $namespace => $dirs) { if ($namespace !== '' && '\\' !== substr((string) $namespace, -1)) { $this->errors[] = 'autoload.psr-4 : invalid value ('.$namespace.'), namespaces must end with a namespace separator, should be '.$namespace.'\\\\'; } } } } } if (isset($this->config['autoload']['psr-4']) && isset($this->config['target-dir'])) { $this->errors[] = 'target-dir : this can not be used together with the autoload.psr-4 setting, remove target-dir to upgrade to psr-4'; unset($this->config['autoload']['psr-4']); } foreach (['source', 'dist'] as $srcType) { if ($this->validateArray($srcType) && !empty($this->config[$srcType])) { if (!isset($this->config[$srcType]['type'])) { $this->errors[] = $srcType . '.type : must be present'; } if (!isset($this->config[$srcType]['url'])) { $this->errors[] = $srcType . '.url : must be present'; } if ($srcType === 'source' && !isset($this->config[$srcType]['reference'])) { $this->errors[] = $srcType . '.reference : must be present'; } if (isset($this->config[$srcType]['type']) && !is_string($this->config[$srcType]['type'])) { $this->errors[] = $srcType . '.type : should be a string, '.get_debug_type($this->config[$srcType]['type']).' given'; } if (isset($this->config[$srcType]['url']) && !is_string($this->config[$srcType]['url'])) { $this->errors[] = $srcType . '.url : should be a string, '.get_debug_type($this->config[$srcType]['url']).' given'; } if (isset($this->config[$srcType]['reference']) && !is_string($this->config[$srcType]['reference']) && !is_int($this->config[$srcType]['reference'])) { $this->errors[] = $srcType . '.reference : should be a string or int, '.get_debug_type($this->config[$srcType]['reference']).' given'; } if (isset($this->config[$srcType]['reference']) && Preg::isMatch('{^\s*-}', (string) $this->config[$srcType]['reference'])) { $this->errors[] = $srcType . '.reference : must not start with a "-", "'.$this->config[$srcType]['reference'].'" given'; } if (isset($this->config[$srcType]['url']) && Preg::isMatch('{^\s*-}', (string) $this->config[$srcType]['url'])) { $this->errors[] = $srcType . '.url : must not start with a "-", "'.$this->config[$srcType]['url'].'" given'; } } } $this->validateFlatArray('include-path'); $this->validateArray('transport-options'); if (isset($this->config['extra']['branch-alias'])) { if (!is_array($this->config['extra']['branch-alias'])) { $this->errors[] = 'extra.branch-alias : must be an array of versions => aliases'; } else { foreach ($this->config['extra']['branch-alias'] as $sourceBranch => $targetBranch) { if (!is_string($targetBranch)) { $this->warnings[] = 'extra.branch-alias.'.$sourceBranch.' : the target branch ('.json_encode($targetBranch).') must be a string, "'.get_debug_type($targetBranch).'" received.'; unset($this->config['extra']['branch-alias'][$sourceBranch]); continue; } if ('-dev' !== substr($targetBranch, -4)) { $this->warnings[] = 'extra.branch-alias.'.$sourceBranch.' : the target branch ('.$targetBranch.') must end in -dev'; unset($this->config['extra']['branch-alias'][$sourceBranch]); continue; } $validatedTargetBranch = $this->versionParser->normalizeBranch(substr($targetBranch, 0, -4)); if ('-dev' !== substr($validatedTargetBranch, -4)) { $this->warnings[] = 'extra.branch-alias.'.$sourceBranch.' : the target branch ('.$targetBranch.') must be a parseable number like 2.0-dev'; unset($this->config['extra']['branch-alias'][$sourceBranch]); continue; } if (($sourcePrefix = $this->versionParser->parseNumericAliasPrefix($sourceBranch)) && ($targetPrefix = $this->versionParser->parseNumericAliasPrefix($targetBranch)) && (stripos($targetPrefix, $sourcePrefix) !== 0) ) { $this->warnings[] = 'extra.branch-alias.'.$sourceBranch.' : the target branch ('.$targetBranch.') is not a valid numeric alias for this version'; unset($this->config['extra']['branch-alias'][$sourceBranch]); } } } } if ($this->errors) { throw new InvalidPackageException($this->errors, $this->warnings, $config); } $package = $this->loader->load($this->config, $class); $this->config = []; return $package; } public function getWarnings(): array { return $this->warnings; } public function getErrors(): array { return $this->errors; } public static function hasPackageNamingError(string $name, bool $isLink = false): ?string { if (PlatformRepository::isPlatformPackage($name)) { return null; } if (!Preg::isMatch('{^[a-z0-9](?:[_.-]?[a-z0-9]++)*+/[a-z0-9](?:(?:[_.]|-{1,2})?[a-z0-9]++)*+$}iD', $name)) { return $name.' is invalid, it should have a vendor name, a forward slash, and a package name. The vendor and package name can be words separated by -, . or _. The complete name should match "^[a-z0-9]([_.-]?[a-z0-9]+)*/[a-z0-9](([_.]?|-{0,2})[a-z0-9]+)*$".'; } $reservedNames = ['nul', 'con', 'prn', 'aux', 'com1', 'com2', 'com3', 'com4', 'com5', 'com6', 'com7', 'com8', 'com9', 'lpt1', 'lpt2', 'lpt3', 'lpt4', 'lpt5', 'lpt6', 'lpt7', 'lpt8', 'lpt9']; $bits = explode('/', strtolower($name)); if (in_array($bits[0], $reservedNames, true) || in_array($bits[1], $reservedNames, true)) { return $name.' is reserved, package and vendor names can not match any of: '.implode(', ', $reservedNames).'.'; } if (Preg::isMatch('{\.json$}', $name)) { return $name.' is invalid, package names can not end in .json, consider renaming it or perhaps using a -json suffix instead.'; } if (Preg::isMatch('{[A-Z]}', $name)) { if ($isLink) { return $name.' is invalid, it should not contain uppercase characters. Please use '.strtolower($name).' instead.'; } $suggestName = Preg::replace('{(?:([a-z])([A-Z])|([A-Z])([A-Z][a-z]))}', '\\1\\3-\\2\\4', $name); $suggestName = strtolower($suggestName); return $name.' is invalid, it should not contain uppercase characters. We suggest using '.$suggestName.' instead.'; } return null; } private function validateRegex(string $property, string $regex, bool $mandatory = false): bool { if (!$this->validateString($property, $mandatory)) { return false; } if (!Preg::isMatch('{^'.$regex.'$}u', $this->config[$property])) { $message = $property.' : invalid value ('.$this->config[$property].'), must match '.$regex; if ($mandatory) { $this->errors[] = $message; } else { $this->warnings[] = $message; } unset($this->config[$property]); return false; } return true; } private function validateString(string $property, bool $mandatory = false): bool { if (isset($this->config[$property]) && !is_string($this->config[$property])) { $this->errors[] = $property.' : should be a string, '.get_debug_type($this->config[$property]).' given'; unset($this->config[$property]); return false; } if (!isset($this->config[$property]) || trim($this->config[$property]) === '') { if ($mandatory) { $this->errors[] = $property.' : must be present'; } unset($this->config[$property]); return false; } return true; } private function validateArray(string $property, bool $mandatory = false): bool { if (isset($this->config[$property]) && !is_array($this->config[$property])) { $this->errors[] = $property.' : should be an array, '.get_debug_type($this->config[$property]).' given'; unset($this->config[$property]); return false; } if (!isset($this->config[$property]) || !count($this->config[$property])) { if ($mandatory) { $this->errors[] = $property.' : must be present and contain at least one element'; } unset($this->config[$property]); return false; } return true; } private function validateFlatArray(string $property, ?string $regex = null, bool $mandatory = false): bool { if (!$this->validateArray($property, $mandatory)) { return false; } $pass = true; foreach ($this->config[$property] as $key => $value) { if (!is_string($value) && !is_numeric($value)) { $this->errors[] = $property.'.'.$key.' : must be a string or int, '.get_debug_type($value).' given'; unset($this->config[$property][$key]); $pass = false; continue; } if ($regex && !Preg::isMatch('{^'.$regex.'$}u', (string) $value)) { $this->warnings[] = $property.'.'.$key.' : invalid value ('.$value.'), must match '.$regex; unset($this->config[$property][$key]); $pass = false; } } return $pass; } private function validateUrl(string $property, bool $mandatory = false): bool { if (!$this->validateString($property, $mandatory)) { return false; } if (!$this->filterUrl($this->config[$property])) { $this->warnings[] = $property.' : invalid value ('.$this->config[$property].'), must be an http/https URL'; unset($this->config[$property]); return false; } return true; } private function filterUrl($value, array $schemes = ['http', 'https']): bool { if ($value === '') { return true; } $bits = parse_url($value); if (empty($bits['scheme']) || empty($bits['host'])) { return false; } if (!in_array($bits['scheme'], $schemes, true)) { return false; } return true; } } lockFile = $lockFile; $this->installationManager = $installationManager; $this->hash = hash('md5', $composerFileContents); $this->contentHash = self::getContentHash($composerFileContents); $this->loader = new ArrayLoader(null, true); $this->dumper = new ArrayDumper(); $this->process = $process ?? new ProcessExecutor($io); } public function getJsonFile(): JsonFile { return $this->lockFile; } public static function getContentHash(string $composerFileContents): string { $content = JsonFile::parseJson($composerFileContents, 'composer.json'); $relevantKeys = [ 'name', 'version', 'require', 'require-dev', 'conflict', 'replace', 'provide', 'minimum-stability', 'prefer-stable', 'repositories', 'extra', ]; $relevantContent = []; foreach (array_intersect($relevantKeys, array_keys($content)) as $key) { $relevantContent[$key] = $content[$key]; } if (isset($content['config']['platform'])) { $relevantContent['config']['platform'] = $content['config']['platform']; } ksort($relevantContent); return hash('md5', JsonFile::encode($relevantContent, 0)); } public function isLocked(): bool { if (!$this->virtualFileWritten && !$this->lockFile->exists()) { return false; } $data = $this->getLockData(); return isset($data['packages']); } public function isFresh(): bool { $lock = $this->lockFile->read(); if (!empty($lock['content-hash'])) { return $this->contentHash === $lock['content-hash']; } if (!empty($lock['hash'])) { return $this->hash === $lock['hash']; } return false; } public function getLockedRepository(bool $withDevReqs = false): LockArrayRepository { $lockData = $this->getLockData(); $packages = new LockArrayRepository(); $lockedPackages = $lockData['packages']; if ($withDevReqs) { if (isset($lockData['packages-dev'])) { $lockedPackages = array_merge($lockedPackages, $lockData['packages-dev']); } else { throw new \RuntimeException('The lock file does not contain require-dev information, run install with the --no-dev option or delete it and run composer update to generate a new lock file.'); } } if (empty($lockedPackages)) { return $packages; } if (isset($lockedPackages[0]['name'])) { $packageByName = []; foreach ($lockedPackages as $info) { $package = $this->loader->load($info); $packages->addPackage($package); $packageByName[$package->getName()] = $package; if ($package instanceof AliasPackage) { $packageByName[$package->getAliasOf()->getName()] = $package->getAliasOf(); } } if (isset($lockData['aliases'])) { foreach ($lockData['aliases'] as $alias) { if (isset($packageByName[$alias['package']])) { $aliasPkg = new CompleteAliasPackage($packageByName[$alias['package']], $alias['alias_normalized'], $alias['alias']); $aliasPkg->setRootPackageAlias(true); $packages->addPackage($aliasPkg); } } } return $packages; } throw new \RuntimeException('Your composer.lock is invalid. Run "composer update" to generate a new one.'); } public function getDevPackageNames(): array { $names = []; $lockData = $this->getLockData(); if (isset($lockData['packages-dev'])) { foreach ($lockData['packages-dev'] as $package) { $names[] = strtolower($package['name']); } } return $names; } public function getPlatformRequirements(bool $withDevReqs = false): array { $lockData = $this->getLockData(); $requirements = []; if (!empty($lockData['platform'])) { $requirements = $this->loader->parseLinks( '__root__', '1.0.0', Link::TYPE_REQUIRE, $lockData['platform'] ?? [] ); } if ($withDevReqs && !empty($lockData['platform-dev'])) { $devRequirements = $this->loader->parseLinks( '__root__', '1.0.0', Link::TYPE_REQUIRE, $lockData['platform-dev'] ?? [] ); $requirements = array_merge($requirements, $devRequirements); } return $requirements; } public function getMinimumStability(): string { $lockData = $this->getLockData(); return $lockData['minimum-stability'] ?? 'stable'; } public function getStabilityFlags(): array { $lockData = $this->getLockData(); return $lockData['stability-flags'] ?? []; } public function getPreferStable(): ?bool { $lockData = $this->getLockData(); return $lockData['prefer-stable'] ?? null; } public function getPreferLowest(): ?bool { $lockData = $this->getLockData(); return $lockData['prefer-lowest'] ?? null; } public function getPlatformOverrides(): array { $lockData = $this->getLockData(); return $lockData['platform-overrides'] ?? []; } public function getAliases(): array { $lockData = $this->getLockData(); return $lockData['aliases'] ?? []; } public function getPluginApi() { $lockData = $this->getLockData(); return $lockData['plugin-api-version'] ?? '1.1.0'; } public function getLockData(): array { if (null !== $this->lockDataCache) { return $this->lockDataCache; } if (!$this->lockFile->exists()) { throw new \LogicException('No lockfile found. Unable to read locked packages'); } return $this->lockDataCache = $this->lockFile->read(); } public function setLockData(array $packages, ?array $devPackages, array $platformReqs, array $platformDevReqs, array $aliases, string $minimumStability, array $stabilityFlags, bool $preferStable, bool $preferLowest, array $platformOverrides, bool $write = true): bool { $aliases = array_map(static function ($alias): array { if (in_array($alias['version'], ['dev-master', 'dev-trunk', 'dev-default'], true)) { $alias['version'] = VersionParser::DEFAULT_BRANCH_ALIAS; } return $alias; }, $aliases); $lock = [ '_readme' => ['This file locks the dependencies of your project to a known state', 'Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies', 'This file is @gener'.'ated automatically', ], 'content-hash' => $this->contentHash, 'packages' => $this->lockPackages($packages), 'packages-dev' => null, 'aliases' => $aliases, 'minimum-stability' => $minimumStability, 'stability-flags' => $stabilityFlags, 'prefer-stable' => $preferStable, 'prefer-lowest' => $preferLowest, ]; if (null !== $devPackages) { $lock['packages-dev'] = $this->lockPackages($devPackages); } $lock['platform'] = $platformReqs; $lock['platform-dev'] = $platformDevReqs; if (\count($platformOverrides) > 0) { $lock['platform-overrides'] = $platformOverrides; } $lock['plugin-api-version'] = PluginInterface::PLUGIN_API_VERSION; $lock = $this->fixupJsonDataType($lock); try { $isLocked = $this->isLocked(); } catch (ParsingException $e) { $isLocked = false; } if (!$isLocked || $lock !== $this->getLockData()) { if ($write) { $this->lockFile->write($lock); $this->lockDataCache = null; $this->virtualFileWritten = false; } else { $this->virtualFileWritten = true; $this->lockDataCache = JsonFile::parseJson(JsonFile::encode($lock)); } return true; } return false; } public function updateHash(JsonFile $composerJson, ?callable $dataProcessor = null): void { $contents = file_get_contents($composerJson->getPath()); if (false === $contents) { throw new \RuntimeException('Unable to read '.$composerJson->getPath().' contents to update the lock file hash.'); } $lockMtime = filemtime($this->lockFile->getPath()); $lockData = $this->lockFile->read(); $lockData['content-hash'] = Locker::getContentHash($contents); if ($dataProcessor !== null) { $lockData = $dataProcessor($lockData); } $this->lockFile->write($this->fixupJsonDataType($lockData)); $this->lockDataCache = null; $this->virtualFileWritten = false; if (is_int($lockMtime)) { @touch($this->lockFile->getPath(), $lockMtime); } } private function fixupJsonDataType(array $lockData): array { foreach (['stability-flags', 'platform', 'platform-dev'] as $key) { if (isset($lockData[$key]) && is_array($lockData[$key]) && \count($lockData[$key]) === 0) { $lockData[$key] = new \stdClass(); } } if (is_array($lockData['stability-flags'])) { ksort($lockData['stability-flags']); } return $lockData; } private function lockPackages(array $packages): array { $locked = []; foreach ($packages as $package) { if ($package instanceof AliasPackage) { continue; } $name = $package->getPrettyName(); $version = $package->getPrettyVersion(); if (!$name || !$version) { throw new \LogicException(sprintf( 'Package "%s" has no version or name and can not be locked', $package )); } $spec = $this->dumper->dump($package); unset($spec['version_normalized']); $time = $spec['time'] ?? null; unset($spec['time']); if ($package->isDev() && $package->getInstallationSource() === 'source') { $time = $this->getPackageTime($package) ?: $time; } if (null !== $time) { $spec['time'] = $time; } unset($spec['installation-source']); $locked[] = $spec; } usort($locked, static function ($a, $b) { $comparison = strcmp($a['name'], $b['name']); if (0 !== $comparison) { return $comparison; } return strcmp($a['version'], $b['version']); }); return $locked; } private function getPackageTime(PackageInterface $package): ?string { if (!function_exists('proc_open')) { return null; } $path = $this->installationManager->getInstallPath($package); if ($path === null) { return null; } $path = realpath($path); $sourceType = $package->getSourceType(); $datetime = null; if ($path && in_array($sourceType, ['git', 'hg'])) { $sourceRef = $package->getSourceReference() ?: $package->getDistReference(); switch ($sourceType) { case 'git': GitUtil::cleanEnv($this->process); $command = GitUtil::buildRevListCommand($this->process, array_merge(['-n1', '--format=%ct', (string) $sourceRef], GitUtil::getNoShowSignatureFlags($this->process))); if (0 === $this->process->execute($command, $output, $path)) { $output = trim(GitUtil::parseRevListOutput($output, $this->process)); if (Preg::isMatch('{^\s*\d+\s*$}', $output)) { $datetime = new \DateTime('@'.trim($output), new \DateTimeZone('UTC')); } } break; case 'hg': if (0 === $this->process->execute(['hg', 'log', '--template', '{date|hgdate}', '-r', (string) $sourceRef], $output, $path) && Preg::isMatch('{^\s*(\d+)\s*}', $output, $match)) { $datetime = new \DateTime('@'.$match[1], new \DateTimeZone('UTC')); } break; } } return $datetime ? $datetime->format(DATE_RFC3339) : null; } public function getMissingRequirementInfo(RootPackageInterface $package, bool $includeDev): array { $missingRequirementInfo = []; $missingRequirements = false; $sets = [['repo' => $this->getLockedRepository(false), 'method' => 'getRequires', 'description' => 'Required']]; if ($includeDev === true) { $sets[] = ['repo' => $this->getLockedRepository(true), 'method' => 'getDevRequires', 'description' => 'Required (in require-dev)']; } $rootRepo = new RootPackageRepository(clone $package); foreach ($sets as $set) { $installedRepo = new InstalledRepository([$set['repo'], $rootRepo]); foreach (call_user_func([$package, $set['method']]) as $link) { if (PlatformRepository::isPlatformPackage($link->getTarget())) { continue; } if ($link->getPrettyConstraint() === 'self.version') { continue; } if ($installedRepo->findPackagesWithReplacersAndProviders($link->getTarget(), $link->getConstraint()) === []) { $results = $installedRepo->findPackagesWithReplacersAndProviders($link->getTarget()); if ($results !== []) { $provider = reset($results); $description = $provider->getPrettyVersion(); if ($provider->getName() !== $link->getTarget()) { foreach (['getReplaces' => 'replaced as %s by %s', 'getProvides' => 'provided as %s by %s'] as $method => $text) { foreach (call_user_func([$provider, $method]) as $providerLink) { if ($providerLink->getTarget() === $link->getTarget()) { $description = sprintf($text, $providerLink->getPrettyConstraint(), $provider->getPrettyName().' '.$provider->getPrettyVersion()); break 2; } } } } $missingRequirementInfo[] = '- ' . $set['description'].' package "' . $link->getTarget() . '" is in the lock file as "'.$description.'" but that does not satisfy your constraint "'.$link->getPrettyConstraint().'".'; } else { $missingRequirementInfo[] = '- ' . $set['description'].' package "' . $link->getTarget() . '" is not present in the lock file.'; } $missingRequirements = true; } } } if ($missingRequirements) { $missingRequirementInfo[] = 'This usually happens when composer files are incorrectly merged or the composer.json file is manually edited.'; $missingRequirementInfo[] = 'Read more about correctly resolving merge conflicts https://getcomposer.org/doc/articles/resolving-merge-conflicts.md'; $missingRequirementInfo[] = 'and prefer using the "require" command over editing the composer.json file directly https://getcomposer.org/doc/03-cli.md#require-r'; } return $missingRequirementInfo; } } version = $version; $this->prettyVersion = $prettyVersion; $this->stability = VersionParser::parseStability($version); $this->dev = $this->stability === 'dev'; } public function isDev(): bool { return $this->dev; } public function setType(string $type): void { $this->type = $type; } public function getType(): string { return $this->type ?: 'library'; } public function getStability(): string { return $this->stability; } public function setTargetDir(?string $targetDir): void { $this->targetDir = $targetDir; } public function getTargetDir(): ?string { if (null === $this->targetDir) { return null; } return ltrim(Preg::replace('{ (?:^|[\\\\/]+) \.\.? (?:[\\\\/]+|$) (?:\.\.? (?:[\\\\/]+|$) )*}x', '/', $this->targetDir), '/'); } public function setExtra(array $extra): void { $this->extra = $extra; } public function getExtra(): array { return $this->extra; } public function setBinaries(array $binaries): void { $this->binaries = $binaries; } public function getBinaries(): array { return $this->binaries; } public function setInstallationSource(?string $type): void { $this->installationSource = $type; } public function getInstallationSource(): ?string { return $this->installationSource; } public function setSourceType(?string $type): void { $this->sourceType = $type; } public function getSourceType(): ?string { return $this->sourceType; } public function setSourceUrl(?string $url): void { $this->sourceUrl = $url; } public function getSourceUrl(): ?string { return $this->sourceUrl; } public function setSourceReference(?string $reference): void { $this->sourceReference = $reference; } public function getSourceReference(): ?string { return $this->sourceReference; } public function setSourceMirrors(?array $mirrors): void { $this->sourceMirrors = $mirrors; } public function getSourceMirrors(): ?array { return $this->sourceMirrors; } public function getSourceUrls(): array { return $this->getUrls($this->sourceUrl, $this->sourceMirrors, $this->sourceReference, $this->sourceType, 'source'); } public function setDistType(?string $type): void { $this->distType = $type === '' ? null : $type; } public function getDistType(): ?string { return $this->distType; } public function setDistUrl(?string $url): void { $this->distUrl = $url === '' ? null : $url; } public function getDistUrl(): ?string { return $this->distUrl; } public function setDistReference(?string $reference): void { $this->distReference = $reference; } public function getDistReference(): ?string { return $this->distReference; } public function setDistSha1Checksum(?string $sha1checksum): void { $this->distSha1Checksum = $sha1checksum; } public function getDistSha1Checksum(): ?string { return $this->distSha1Checksum; } public function setDistMirrors(?array $mirrors): void { $this->distMirrors = $mirrors; } public function getDistMirrors(): ?array { return $this->distMirrors; } public function getDistUrls(): array { return $this->getUrls($this->distUrl, $this->distMirrors, $this->distReference, $this->distType, 'dist'); } public function getTransportOptions(): array { return $this->transportOptions; } public function setTransportOptions(array $options): void { $this->transportOptions = $options; } public function getVersion(): string { return $this->version; } public function getPrettyVersion(): string { return $this->prettyVersion; } public function setReleaseDate(?\DateTimeInterface $releaseDate): void { $this->releaseDate = $releaseDate; } public function getReleaseDate(): ?\DateTimeInterface { return $this->releaseDate; } public function setRequires(array $requires): void { if (isset($requires[0])) { $requires = $this->convertLinksToMap($requires, 'setRequires'); } $this->requires = $requires; } public function getRequires(): array { return $this->requires; } public function setConflicts(array $conflicts): void { if (isset($conflicts[0])) { $conflicts = $this->convertLinksToMap($conflicts, 'setConflicts'); } $this->conflicts = $conflicts; } public function getConflicts(): array { return $this->conflicts; } public function setProvides(array $provides): void { if (isset($provides[0])) { $provides = $this->convertLinksToMap($provides, 'setProvides'); } $this->provides = $provides; } public function getProvides(): array { return $this->provides; } public function setReplaces(array $replaces): void { if (isset($replaces[0])) { $replaces = $this->convertLinksToMap($replaces, 'setReplaces'); } $this->replaces = $replaces; } public function getReplaces(): array { return $this->replaces; } public function setDevRequires(array $devRequires): void { if (isset($devRequires[0])) { $devRequires = $this->convertLinksToMap($devRequires, 'setDevRequires'); } $this->devRequires = $devRequires; } public function getDevRequires(): array { return $this->devRequires; } public function setSuggests(array $suggests): void { $this->suggests = $suggests; } public function getSuggests(): array { return $this->suggests; } public function setAutoload(array $autoload): void { $this->autoload = $autoload; } public function getAutoload(): array { return $this->autoload; } public function setDevAutoload(array $devAutoload): void { $this->devAutoload = $devAutoload; } public function getDevAutoload(): array { return $this->devAutoload; } public function setIncludePaths(array $includePaths): void { $this->includePaths = $includePaths; } public function getIncludePaths(): array { return $this->includePaths; } public function setPhpExt(?array $phpExt): void { $this->phpExt = $phpExt; } public function getPhpExt(): ?array { return $this->phpExt; } public function setNotificationUrl(string $notificationUrl): void { $this->notificationUrl = $notificationUrl; } public function getNotificationUrl(): ?string { return $this->notificationUrl; } public function setIsDefaultBranch(bool $defaultBranch): void { $this->isDefaultBranch = $defaultBranch; } public function isDefaultBranch(): bool { return $this->isDefaultBranch; } public function setSourceDistReferences(string $reference): void { $this->setSourceReference($reference); if ( $this->getDistUrl() !== null && Preg::isMatch('{^https?://(?:(?:www\.)?bitbucket\.org|(api\.)?github\.com|(?:www\.)?gitlab\.com)/}i', $this->getDistUrl()) ) { $this->setDistReference($reference); $this->setDistUrl(Preg::replace('{(?<=/|sha=)[a-f0-9]{40}(?=/|$)}i', $reference, $this->getDistUrl())); } elseif ($this->getDistReference()) { $this->setDistReference($reference); } } public function replaceVersion(string $version, string $prettyVersion): void { $this->version = $version; $this->prettyVersion = $prettyVersion; $this->stability = VersionParser::parseStability($version); $this->dev = $this->stability === 'dev'; } protected function getUrls(?string $url, ?array $mirrors, ?string $ref, ?string $type, string $urlType): array { if (!$url) { return []; } if ($urlType === 'dist' && false !== strpos($url, '%')) { $url = ComposerMirror::processUrl($url, $this->name, $this->version, $ref, $type, $this->prettyVersion); } $urls = [$url]; if ($mirrors) { foreach ($mirrors as $mirror) { if ($urlType === 'dist') { $mirrorUrl = ComposerMirror::processUrl($mirror['url'], $this->name, $this->version, $ref, $type, $this->prettyVersion); } elseif ($urlType === 'source' && $type === 'git') { $mirrorUrl = ComposerMirror::processGitUrl($mirror['url'], $this->name, $url, $type); } elseif ($urlType === 'source' && $type === 'hg') { $mirrorUrl = ComposerMirror::processHgUrl($mirror['url'], $this->name, $url, $type); } else { continue; } if (!\in_array($mirrorUrl, $urls)) { $func = $mirror['preferred'] ? 'array_unshift' : 'array_push'; $func($urls, $mirrorUrl); } } } return $urls; } private function convertLinksToMap(array $links, string $source): array { trigger_error('Package::'.$source.' must be called with a map of lowercased package name => Link object, got a indexed array, this is deprecated and you should fix your usage.'); $newLinks = []; foreach ($links as $link) { $newLinks[$link->getTarget()] = $link; } return $newLinks; } } aliasOf; } public function getAliases(): array { return $this->aliasOf->getAliases(); } public function getMinimumStability(): string { return $this->aliasOf->getMinimumStability(); } public function getStabilityFlags(): array { return $this->aliasOf->getStabilityFlags(); } public function getReferences(): array { return $this->aliasOf->getReferences(); } public function getPreferStable(): bool { return $this->aliasOf->getPreferStable(); } public function getConfig(): array { return $this->aliasOf->getConfig(); } public function setRequires(array $requires): void { $this->requires = $this->replaceSelfVersionDependencies($requires, Link::TYPE_REQUIRE); $this->aliasOf->setRequires($requires); } public function setDevRequires(array $devRequires): void { $this->devRequires = $this->replaceSelfVersionDependencies($devRequires, Link::TYPE_DEV_REQUIRE); $this->aliasOf->setDevRequires($devRequires); } public function setConflicts(array $conflicts): void { $this->conflicts = $this->replaceSelfVersionDependencies($conflicts, Link::TYPE_CONFLICT); $this->aliasOf->setConflicts($conflicts); } public function setProvides(array $provides): void { $this->provides = $this->replaceSelfVersionDependencies($provides, Link::TYPE_PROVIDE); $this->aliasOf->setProvides($provides); } public function setReplaces(array $replaces): void { $this->replaces = $this->replaceSelfVersionDependencies($replaces, Link::TYPE_REPLACE); $this->aliasOf->setReplaces($replaces); } public function setAutoload(array $autoload): void { $this->aliasOf->setAutoload($autoload); } public function setDevAutoload(array $devAutoload): void { $this->aliasOf->setDevAutoload($devAutoload); } public function setStabilityFlags(array $stabilityFlags): void { $this->aliasOf->setStabilityFlags($stabilityFlags); } public function setMinimumStability(string $minimumStability): void { $this->aliasOf->setMinimumStability($minimumStability); } public function setPreferStable(bool $preferStable): void { $this->aliasOf->setPreferStable($preferStable); } public function setConfig(array $config): void { $this->aliasOf->setConfig($config); } public function setReferences(array $references): void { $this->aliasOf->setReferences($references); } public function setAliases(array $aliases): void { $this->aliasOf->setAliases($aliases); } public function setSuggests(array $suggests): void { $this->aliasOf->setSuggests($suggests); } public function setExtra(array $extra): void { $this->aliasOf->setExtra($extra); } public function __clone() { parent::__clone(); $this->aliasOf = clone $this->aliasOf; } } minimumStability = $minimumStability; } public function getMinimumStability(): string { return $this->minimumStability; } public function setStabilityFlags(array $stabilityFlags): void { $this->stabilityFlags = $stabilityFlags; } public function getStabilityFlags(): array { return $this->stabilityFlags; } public function setPreferStable(bool $preferStable): void { $this->preferStable = $preferStable; } public function getPreferStable(): bool { return $this->preferStable; } public function setConfig(array $config): void { $this->config = $config; } public function getConfig(): array { return $this->config; } public function setReferences(array $references): void { $this->references = $references; } public function getReferences(): array { return $this->references; } public function setAliases(array $aliases): void { $this->aliases = $aliases; } public function getAliases(): array { return $this->aliases; } } getPrettyString(); if (str_starts_with($constraint->getPrettyString(), 'dev-')) { return $prettyConstraint; } $version = $package->getVersion(); if (str_starts_with($package->getVersion(), 'dev-')) { $loader = new ArrayLoader($parser); $dumper = new ArrayDumper(); $extra = $loader->getBranchAlias($dumper->dump($package)); if (null === $extra || $extra === VersionParser::DEFAULT_BRANCH_ALIAS) { return $prettyConstraint; } $version = $extra; } $intervals = Intervals::get($constraint); if (\count($intervals['branches']['names']) > 0) { return $prettyConstraint; } $major = Preg::replace('{^([1-9][0-9]*|0\.\d+).*}', '$1', $version); $versionWithoutSuffix = Preg::replace('{(?:\.(?:0|9999999))+(-dev)?$}', '', $version); $newPrettyConstraint = '^'.$versionWithoutSuffix; if (!Preg::isMatch('{^\^\d+(\.\d+)*$}', $newPrettyConstraint)) { return $prettyConstraint; } $pattern = '{ (?<=,|\ |\||^) # leading separator (?P \^v?'.$major.'(?:\.\d+)* # e.g. ^2.anything | ~v?'.$major.'(?:\.\d+){1,3} # e.g. ~2.2 or ~2.2.2 or ~2.2.2.2 | v?'.$major.'(?:\.[*x])+ # e.g. 2.* or 2.*.* or 2.x.x.x etc | >=v?\d(?:\.\d+)* # e.g. >=2 or >=1.2 etc | \* # full wildcard ) (?=,|$|\ |\||@) # trailing separator }x'; if (Preg::isMatchAllWithOffsets($pattern, $prettyConstraint, $matches)) { $modified = $prettyConstraint; foreach (array_reverse($matches['constraint']) as $match) { assert(is_string($match[0])); $suffix = ''; if (substr_count($match[0], '.') === 2 && substr_count($versionWithoutSuffix, '.') === 1) { $suffix = '.0'; } if (str_starts_with($match[0], '~') && substr_count($match[0], '.') !== 1) { $versionBits = explode('.', $versionWithoutSuffix); $versionBits = array_pad($versionBits, substr_count($match[0], '.') + 1, '0'); $replacement = '~'.implode('.', array_slice($versionBits, 0, substr_count($match[0], '.') + 1)); } elseif ($match[0] === '*' || str_starts_with($match[0], '>=')) { $replacement = '>='.$versionWithoutSuffix.$suffix; } else { $replacement = $newPrettyConstraint.$suffix; } $modified = substr_replace($modified, $replacement, $match[1], Platform::strlen($match[0])); } $newConstraint = $parser->parseConstraints($modified); if (Intervals::isSubsetOf($newConstraint, $constraint) && Intervals::isSubsetOf($constraint, $newConstraint)) { return $prettyConstraint; } return $modified; } return $prettyConstraint; } } config = $config; $this->process = $process; $this->versionParser = $versionParser; $this->io = $io; } public function guessVersion(array $packageConfig, string $path): ?array { if (!function_exists('proc_open')) { return null; } if (Platform::isInputCompletionProcess()) { return null; } $versionData = $this->guessGitVersion($packageConfig, $path); if (null !== $versionData['version']) { return $this->postprocess($versionData); } $versionData = $this->guessHgVersion($packageConfig, $path); if (null !== $versionData && null !== $versionData['version']) { return $this->postprocess($versionData); } $versionData = $this->guessFossilVersion($path); if (null !== $versionData['version']) { return $this->postprocess($versionData); } $versionData = $this->guessSvnVersion($packageConfig, $path); if (null !== $versionData && null !== $versionData['version']) { return $this->postprocess($versionData); } return null; } private function postprocess(array $versionData): array { if (!empty($versionData['feature_version']) && $versionData['feature_version'] === $versionData['version'] && $versionData['feature_pretty_version'] === $versionData['pretty_version']) { unset($versionData['feature_version'], $versionData['feature_pretty_version']); } if ('-dev' === substr($versionData['version'], -4) && Preg::isMatch('{\.9{7}}', $versionData['version'])) { $versionData['pretty_version'] = Preg::replace('{(\.9{7})+}', '.x', $versionData['version']); } if (!empty($versionData['feature_version']) && '-dev' === substr($versionData['feature_version'], -4) && Preg::isMatch('{\.9{7}}', $versionData['feature_version'])) { $versionData['feature_pretty_version'] = Preg::replace('{(\.9{7})+}', '.x', $versionData['feature_version']); } return $versionData; } private function guessGitVersion(array $packageConfig, string $path): array { GitUtil::cleanEnv($this->process); $commit = null; $version = null; $prettyVersion = null; $featureVersion = null; $featurePrettyVersion = null; $isDetached = false; if (0 === $this->process->execute(['git', 'branch', '-a', '--no-color', '--no-abbrev', '-v'], $output, $path)) { $branches = []; $isFeatureBranch = false; foreach ($this->process->splitLines($output) as $branch) { if ($branch && Preg::isMatchStrictGroups('{^(?:\* ) *(\(no branch\)|\(detached from \S+\)|\(HEAD detached at \S+\)|\S+) *([a-f0-9]+) .*$}', $branch, $match)) { if ( $match[1] === '(no branch)' || strpos($match[1], '(detached ') === 0 || strpos($match[1], '(HEAD detached at') === 0 ) { $version = 'dev-' . $match[2]; $prettyVersion = $version; $isFeatureBranch = true; $isDetached = true; } else { $version = $this->versionParser->normalizeBranch($match[1]); $prettyVersion = 'dev-' . $match[1]; $isFeatureBranch = $this->isFeatureBranch($packageConfig, $match[1]); } $commit = $match[2]; } if ($branch && !Preg::isMatchStrictGroups('{^ *.+/HEAD }', $branch)) { if (Preg::isMatchStrictGroups('{^(?:\* )? *((?:remotes/(?:origin|upstream)/)?[^\s/]+) *([a-f0-9]+) .*$}', $branch, $match)) { $branches[] = $match[1]; } } } if ($isFeatureBranch) { $featureVersion = $version; $featurePrettyVersion = $prettyVersion; $result = $this->guessFeatureVersion($packageConfig, $version, $branches, ['git', 'rev-list', '%candidate%..%branch%'], $path); $version = $result['version']; $prettyVersion = $result['pretty_version']; } } GitUtil::checkForRepoOwnershipError($this->process->getErrorOutput(), $path, $this->io); if (!$version || $isDetached) { $result = $this->versionFromGitTags($path); if ($result) { $version = $result['version']; $prettyVersion = $result['pretty_version']; $featureVersion = null; $featurePrettyVersion = null; } } if (null === $commit) { $command = GitUtil::buildRevListCommand($this->process, array_merge(['--format=%H', '-n1', 'HEAD'], GitUtil::getNoShowSignatureFlags($this->process))); if (0 === $this->process->execute($command, $output, $path)) { $commit = trim(GitUtil::parseRevListOutput($output, $this->process)) ?: null; } } if ($featureVersion) { return ['version' => $version, 'commit' => $commit, 'pretty_version' => $prettyVersion, 'feature_version' => $featureVersion, 'feature_pretty_version' => $featurePrettyVersion]; } return ['version' => $version, 'commit' => $commit, 'pretty_version' => $prettyVersion]; } private function versionFromGitTags(string $path): ?array { if (0 === $this->process->execute(['git', 'describe', '--exact-match', '--tags'], $output, $path)) { try { $version = $this->versionParser->normalize(trim($output)); return ['version' => $version, 'pretty_version' => trim($output)]; } catch (\Exception $e) { } } return null; } private function guessHgVersion(array $packageConfig, string $path): ?array { if (0 === $this->process->execute(['hg', 'branch'], $output, $path)) { $branch = trim($output); $version = $this->versionParser->normalizeBranch($branch); $isFeatureBranch = 0 === strpos($version, 'dev-'); if (VersionParser::DEFAULT_BRANCH_ALIAS === $version) { return ['version' => $version, 'commit' => null, 'pretty_version' => 'dev-'.$branch]; } if (!$isFeatureBranch) { return ['version' => $version, 'commit' => null, 'pretty_version' => $version]; } $io = new NullIO(); $driver = new HgDriver(['url' => $path], $io, $this->config, new HttpDownloader($io, $this->config), $this->process); $branches = array_map('strval', array_keys($driver->getBranches())); $result = $this->guessFeatureVersion($packageConfig, $version, $branches, ['hg', 'log', '-r', 'not ancestors(\'%candidate%\') and ancestors(\'%branch%\')', '--template', '"{node}\\n"'], $path); $result['commit'] = ''; $result['feature_version'] = $version; $result['feature_pretty_version'] = $version; return $result; } return null; } private function guessFeatureVersion(array $packageConfig, ?string $version, array $branches, array $scmCmdline, string $path): array { $prettyVersion = $version; if (!isset($packageConfig['extra']['branch-alias'][$version]) || strpos(json_encode($packageConfig), '"self.version"') ) { $branch = Preg::replace('{^dev-}', '', $version); $length = PHP_INT_MAX; if (!$this->isFeatureBranch($packageConfig, $branch)) { return ['version' => $version, 'pretty_version' => $prettyVersion]; } usort($branches, static function ($a, $b): int { $aRemote = 0 === strpos($a, 'remotes/'); $bRemote = 0 === strpos($b, 'remotes/'); if ($aRemote !== $bRemote) { return $aRemote ? 1 : -1; } return strnatcasecmp($b, $a); }); $promises = []; $this->process->setMaxJobs(30); try { $lastIndex = -1; foreach ($branches as $index => $candidate) { $candidateVersion = Preg::replace('{^remotes/\S+/}', '', $candidate); if ($candidate === $branch || $this->isFeatureBranch($packageConfig, $candidateVersion)) { continue; } $cmdLine = array_map(static function (string $component) use ($candidate, $branch) { return str_replace(['%candidate%', '%branch%'], [$candidate, $branch], $component); }, $scmCmdline); $promises[] = $this->process->executeAsync($cmdLine, $path)->then(function (Process $process) use (&$lastIndex, $index, &$length, &$version, &$prettyVersion, $candidateVersion, &$promises): void { if (!$process->isSuccessful()) { return; } $output = $process->getOutput(); if (strlen($output) < $length || (strlen($output) === $length && $lastIndex < $index)) { $lastIndex = $index; $length = strlen($output); $version = $this->versionParser->normalizeBranch($candidateVersion); $prettyVersion = 'dev-' . $candidateVersion; if ($length === 0) { foreach ($promises as $promise) { \React\Promise\resolve($promise)->cancel(); } } } }); } $this->process->wait(); } finally { $this->process->resetMaxJobs(); } } return ['version' => $version, 'pretty_version' => $prettyVersion]; } private function isFeatureBranch(array $packageConfig, ?string $branchName): bool { $nonFeatureBranches = ''; if (!empty($packageConfig['non-feature-branches'])) { $nonFeatureBranches = implode('|', $packageConfig['non-feature-branches']); } return !Preg::isMatch('{^(' . $nonFeatureBranches . '|master|main|latest|next|current|support|tip|trunk|default|develop|\d+\..+)$}', $branchName, $match); } private function guessFossilVersion(string $path): array { $version = null; $prettyVersion = null; if (0 === $this->process->execute(['fossil', 'branch', 'list'], $output, $path)) { $branch = trim($output); $version = $this->versionParser->normalizeBranch($branch); $prettyVersion = 'dev-' . $branch; } if (0 === $this->process->execute(['fossil', 'tag', 'list'], $output, $path)) { try { $version = $this->versionParser->normalize(trim($output)); $prettyVersion = trim($output); } catch (\Exception $e) { } } return ['version' => $version, 'commit' => '', 'pretty_version' => $prettyVersion]; } private function guessSvnVersion(array $packageConfig, string $path): ?array { SvnUtil::cleanEnv(); if (0 === $this->process->execute(['svn', 'info', '--xml'], $output, $path)) { $trunkPath = isset($packageConfig['trunk-path']) ? preg_quote($packageConfig['trunk-path'], '#') : 'trunk'; $branchesPath = isset($packageConfig['branches-path']) ? preg_quote($packageConfig['branches-path'], '#') : 'branches'; $tagsPath = isset($packageConfig['tags-path']) ? preg_quote($packageConfig['tags-path'], '#') : 'tags'; $urlPattern = '#.*/(' . $trunkPath . '|(' . $branchesPath . '|' . $tagsPath . ')/(.*))#'; if (Preg::isMatch($urlPattern, $output, $matches)) { if (isset($matches[2], $matches[3]) && ($branchesPath === $matches[2] || $tagsPath === $matches[2])) { $version = $this->versionParser->normalizeBranch($matches[3]); $prettyVersion = 'dev-' . $matches[3]; return ['version' => $version, 'commit' => '', 'pretty_version' => $prettyVersion]; } assert(is_string($matches[1])); $prettyVersion = trim($matches[1]); if ($prettyVersion === 'trunk') { $version = 'dev-trunk'; } else { $version = $this->versionParser->normalize($prettyVersion); } return ['version' => $version, 'commit' => '', 'pretty_version' => $prettyVersion]; } } return null; } public function getRootVersionFromEnv(): string { $version = Platform::getEnv('COMPOSER_ROOT_VERSION'); if (!is_string($version) || $version === '') { throw new \RuntimeException('COMPOSER_ROOT_VERSION not set or empty'); } if (Preg::isMatch('{^(\d+(?:\.\d+)*)-dev$}i', $version, $match)) { $version = $match[1].'.x-dev'; } return $version; } } $name, 'version' => $version]; } else { $result[] = ['name' => $pair]; } } return $result; } public static function isUpgrade(string $normalizedFrom, string $normalizedTo): bool { if ($normalizedFrom === $normalizedTo) { return true; } if (in_array($normalizedFrom, ['dev-master', 'dev-trunk', 'dev-default'], true)) { $normalizedFrom = VersionParser::DEFAULT_BRANCH_ALIAS; } if (in_array($normalizedTo, ['dev-master', 'dev-trunk', 'dev-default'], true)) { $normalizedTo = VersionParser::DEFAULT_BRANCH_ALIAS; } if (strpos($normalizedFrom, 'dev-') === 0 || strpos($normalizedTo, 'dev-') === 0) { return true; } $sorted = Semver::sort([$normalizedTo, $normalizedFrom]); return $sorted[0] === $normalizedFrom; } } repositorySet = $repositorySet; if ($platformRepo) { foreach ($platformRepo->getPackages() as $package) { $this->platformConstraints[$package->getName()][] = new Constraint('==', $package->getVersion()); } } } public function findBestCandidate(string $packageName, ?string $targetPackageVersion = null, string $preferredStability = 'stable', $platformRequirementFilter = null, int $repoSetFlags = 0, ?IOInterface $io = null, $showWarnings = true) { if (!isset(BasePackage::STABILITIES[$preferredStability])) { throw new \UnexpectedValueException('Expected a valid stability name as 3rd argument, got '.$preferredStability); } if (null === $platformRequirementFilter) { $platformRequirementFilter = PlatformRequirementFilterFactory::ignoreNothing(); } elseif (!($platformRequirementFilter instanceof PlatformRequirementFilterInterface)) { trigger_error('VersionSelector::findBestCandidate with ignored platform reqs as bool|array is deprecated since Composer 2.2, use an instance of PlatformRequirementFilterInterface instead.', E_USER_DEPRECATED); $platformRequirementFilter = PlatformRequirementFilterFactory::fromBoolOrList($platformRequirementFilter); } $constraint = $targetPackageVersion ? $this->getParser()->parseConstraints($targetPackageVersion) : null; $candidates = $this->repositorySet->findPackages(strtolower($packageName), $constraint, $repoSetFlags); $minPriority = BasePackage::STABILITIES[$preferredStability]; usort($candidates, static function (PackageInterface $a, PackageInterface $b) use ($minPriority) { $aPriority = $a->getStabilityPriority(); $bPriority = $b->getStabilityPriority(); if ($minPriority < $aPriority && $bPriority < $aPriority) { return 1; } if ($minPriority < $aPriority && $aPriority < $bPriority) { return -1; } if ($minPriority >= $aPriority && $minPriority < $bPriority) { return -1; } return version_compare($b->getVersion(), $a->getVersion()); }); if (count($this->platformConstraints) > 0 && !($platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter)) { $alreadyWarnedNames = []; $alreadySeenNames = []; foreach ($candidates as $pkg) { $reqs = $pkg->getRequires(); $skip = false; foreach ($reqs as $name => $link) { if (!PlatformRepository::isPlatformPackage($name) || $platformRequirementFilter->isIgnored($name)) { continue; } if (isset($this->platformConstraints[$name])) { foreach ($this->platformConstraints[$name] as $providedConstraint) { if ($link->getConstraint()->matches($providedConstraint)) { continue 2; } if ($platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter && $platformRequirementFilter->isUpperBoundIgnored($name)) { $filteredConstraint = $platformRequirementFilter->filterConstraint($name, $link->getConstraint()); if ($filteredConstraint->matches($providedConstraint)) { continue 2; } } } $reason = 'is not satisfied by your platform'; } else { $reason = 'is missing from your platform'; } $isLatestVersion = !isset($alreadySeenNames[$pkg->getName()]); $alreadySeenNames[$pkg->getName()] = true; if ($io !== null && ($showWarnings === true || (is_callable($showWarnings) && $showWarnings($pkg)))) { $isFirstWarning = !isset($alreadyWarnedNames[$pkg->getName().'/'.$link->getTarget()]); $alreadyWarnedNames[$pkg->getName().'/'.$link->getTarget()] = true; $latest = $isLatestVersion ? "'s latest version" : ''; $io->writeError( 'Cannot use '.$pkg->getPrettyName().$latest.' '.$pkg->getPrettyVersion().' as it '.$link->getDescription().' '.$link->getTarget().' '.$link->getPrettyConstraint().' which '.$reason.'.', true, $isFirstWarning ? IOInterface::NORMAL : IOInterface::VERBOSE ); } $skip = true; } if ($skip) { continue; } $package = $pkg; break; } } else { $package = count($candidates) > 0 ? $candidates[0] : null; } if (!isset($package)) { return false; } if ($package instanceof AliasPackage && $package->getVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) { $package = $package->getAliasOf(); } return $package; } public function findRecommendedRequireVersion(PackageInterface $package): string { if (0 === strpos($package->getName(), 'ext-')) { $phpVersion = PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION; $extVersion = implode('.', array_slice(explode('.', $package->getVersion()), 0, 3)); if ($phpVersion === $extVersion) { return '*'; } } $version = $package->getVersion(); if (!$package->isDev()) { return $this->transformVersion($version, $package->getPrettyVersion(), $package->getStability()); } $loader = new ArrayLoader($this->getParser()); $dumper = new ArrayDumper(); $extra = $loader->getBranchAlias($dumper->dump($package)); if ($extra && $extra !== VersionParser::DEFAULT_BRANCH_ALIAS) { $extra = Preg::replace('{^(\d+\.\d+\.\d+)(\.9999999)-dev$}', '$1.0', $extra, -1, $count); if ($count > 0) { $extra = str_replace('.9999999', '.0', $extra); return $this->transformVersion($extra, $extra, 'dev'); } } return $package->getPrettyVersion(); } private function transformVersion(string $version, string $prettyVersion, string $stability): string { $semanticVersionParts = explode('.', $version); if (count($semanticVersionParts) === 4 && Preg::isMatch('{^\d+\D?}', $semanticVersionParts[3])) { if ($semanticVersionParts[0] === '0') { unset($semanticVersionParts[3]); } else { unset($semanticVersionParts[2], $semanticVersionParts[3]); } $version = implode('.', $semanticVersionParts); } else { return $prettyVersion; } if ($stability !== 'stable') { $version .= '@'.$stability; } return '^' . $version; } private function getParser(): VersionParser { if ($this->parser === null) { $this->parser = new VersionParser(); } return $this->parser; } } package = $package; } public function getPackage(): RootPackageInterface { return $this->package; } public function setConfig(Config $config): void { $this->config = $config; } public function getConfig(): Config { return $this->config; } public function setLoop(Loop $loop): void { $this->loop = $loop; } public function getLoop(): Loop { return $this->loop; } public function setRepositoryManager(RepositoryManager $manager): void { $this->repositoryManager = $manager; } public function getRepositoryManager(): RepositoryManager { return $this->repositoryManager; } public function setInstallationManager(InstallationManager $manager): void { $this->installationManager = $manager; } public function getInstallationManager(): InstallationManager { return $this->installationManager; } public function setEventDispatcher(EventDispatcher $eventDispatcher): void { $this->eventDispatcher = $eventDispatcher; } public function getEventDispatcher(): EventDispatcher { return $this->eventDispatcher; } public function isGlobal(): bool { return $this->global; } public function setGlobal(): void { $this->global = true; } } executableFinder = $executableFinder; $this->processExecutor = $processExecutor; } public function reset(): void { self::$hhvmVersion = null; } public function getVersion(): ?string { if (null !== self::$hhvmVersion) { return self::$hhvmVersion ?: null; } self::$hhvmVersion = defined('HHVM_VERSION') ? HHVM_VERSION : null; if (self::$hhvmVersion === null && !Platform::isWindows()) { self::$hhvmVersion = false; $this->executableFinder = $this->executableFinder ?: new ExecutableFinder(); $hhvmPath = $this->executableFinder->find('hhvm'); if ($hhvmPath !== null) { $this->processExecutor = $this->processExecutor ?? new ProcessExecutor(); $exitCode = $this->processExecutor->execute([$hhvmPath, '--php', '-d', 'hhvm.jit=0', '-r', 'echo HHVM_VERSION;'], self::$hhvmVersion); if ($exitCode !== 0) { self::$hhvmVersion = false; } } } return self::$hhvmVersion ?: null; } } newInstanceArgs($arguments); } public function getExtensions(): array { return get_loaded_extensions(); } public function getExtensionVersion(string $extension): string { $version = phpversion($extension); if ($version === false) { $version = '0'; } return $version; } public function getExtensionInfo(string $extension): string { $reflector = new \ReflectionExtension($extension); ob_start(); $reflector->info(); $info = (string) ob_get_clean(); if ('cli' === PHP_SAPI) { return $info; } return self::parseHtmlExtensionInfo($info); } public static function parseHtmlExtensionInfo(string $html): string { $result = []; if ((bool) Preg::match('~

\s*]*>([^<]+)\s*

~i', $html, $matches)) { $result[] = trim(html_entity_decode($matches[1])); $result[] = ''; } if ((bool) Preg::matchAll('~\s*\s*(.*?)\s*\s*\s*(.*?)\s*\s*~is', $html, $matches)) { $count = min(\count($matches[1]), \count($matches[2])); for ($i = 0; $i < $count; $i++) { $key = trim(html_entity_decode(strip_tags($matches[1][$i]))); $value = trim(html_entity_decode(strip_tags($matches[2][$i]))); $result[] = $key . ' => ' . $value; } } return implode("\n", $result); } } [0-9.]+)(?[a-z]{0,2})(?(?:-?(?:dev|pre|alpha|beta|rc|fips)[\d]*)*)(?:-\w+)?(?: \(.+?\))?$/', $opensslVersion, $matches)) { return null; } $patch = ''; if (version_compare($matches['version'], '3.0.0', '<')) { $patch = '.'.self::convertAlphaVersionToIntVersion($matches['patch']); } $isFips = strpos($matches['suffix'], 'fips') !== false; $suffix = strtr('-'.ltrim($matches['suffix'], '-'), ['-fips' => '', '-pre' => '-alpha']); return rtrim($matches['version'].$patch.$suffix, '-'); } public static function parseLibjpeg(string $libjpegVersion): ?string { if (!Preg::isMatchStrictGroups('/^(?\d+)(?[a-z]*)$/', $libjpegVersion, $matches)) { return null; } return $matches['major'].'.'.self::convertAlphaVersionToIntVersion($matches['minor']); } public static function parseZoneinfoVersion(string $zoneinfoVersion): ?string { if (!Preg::isMatchStrictGroups('/^(?\d{4})(?[a-z]*)$/', $zoneinfoVersion, $matches)) { return null; } return $matches['year'].'.'.self::convertAlphaVersionToIntVersion($matches['revision']); } private static function convertAlphaVersionToIntVersion(string $alpha): int { return strlen($alpha) * (-ord('a') + 1) + array_sum(array_map('ord', str_split($alpha))); } public static function convertLibxpmVersionId(int $versionId): string { return self::convertVersionId($versionId, 100); } public static function convertOpenldapVersionId(int $versionId): string { return self::convertVersionId($versionId, 100); } private static function convertVersionId(int $versionId, int $base): string { return sprintf( '%d.%d.%d', $versionId / ($base * $base), (int) ($versionId / $base) % $base, $versionId % $base ); } } commandName = $commandName; $this->input = $input; $this->output = $output; } public function getInput(): InputInterface { return $this->input; } public function getOutput(): OutputInterface { return $this->output; } public function getCommandName(): string { return $this->commandName; } } io = $io; $this->composer = $composer; $this->globalComposer = $globalComposer; $this->versionParser = new VersionParser(); $this->disablePlugins = $disablePlugins; $this->allowPluginRules = $this->parseAllowedPlugins($composer->getConfig()->get('allow-plugins'), $composer->getLocker()); $this->allowGlobalPluginRules = $this->parseAllowedPlugins($globalComposer !== null ? $globalComposer->getConfig()->get('allow-plugins') : false); } public function setRunningInGlobalDir(bool $runningInGlobalDir): void { $this->runningInGlobalDir = $runningInGlobalDir; } public function loadInstalledPlugins(): void { if (!$this->arePluginsDisabled('local')) { $repo = $this->composer->getRepositoryManager()->getLocalRepository(); $this->loadRepository($repo, false, $this->composer->getPackage()); } if ($this->globalComposer !== null && !$this->arePluginsDisabled('global')) { $this->loadRepository($this->globalComposer->getRepositoryManager()->getLocalRepository(), true); } } public function deactivateInstalledPlugins(): void { if (!$this->arePluginsDisabled('local')) { $repo = $this->composer->getRepositoryManager()->getLocalRepository(); $this->deactivateRepository($repo, false); } if ($this->globalComposer !== null && !$this->arePluginsDisabled('global')) { $this->deactivateRepository($this->globalComposer->getRepositoryManager()->getLocalRepository(), true); } } public function getPlugins(): array { return $this->plugins; } public function getRegisteredPlugins(): array { return array_keys($this->registeredPlugins); } public function getGlobalComposer(): ?PartialComposer { return $this->globalComposer; } public function registerPackage(PackageInterface $package, bool $failOnMissingClasses = false, bool $isGlobalPlugin = false): void { if ($this->arePluginsDisabled($isGlobalPlugin ? 'global' : 'local')) { $this->io->writeError('The "'.$package->getName().'" plugin was not loaded as plugins are disabled.'); return; } if ($package->getType() === 'composer-plugin') { $requiresComposer = null; foreach ($package->getRequires() as $link) { if ('composer-plugin-api' === $link->getTarget()) { $requiresComposer = $link->getConstraint(); break; } } if (!$requiresComposer) { throw new \RuntimeException("Plugin ".$package->getName()." is missing a require statement for a version of the composer-plugin-api package."); } $currentPluginApiVersion = $this->getPluginApiVersion(); $currentPluginApiConstraint = new Constraint('==', $this->versionParser->normalize($currentPluginApiVersion)); if ($requiresComposer->getPrettyString() === $this->getPluginApiVersion()) { $this->io->writeError('The "' . $package->getName() . '" plugin requires composer-plugin-api '.$this->getPluginApiVersion().', this *WILL* break in the future and it should be fixed ASAP (require ^'.$this->getPluginApiVersion().' instead for example).'); } elseif (!$requiresComposer->matches($currentPluginApiConstraint)) { $this->io->writeError('The "' . $package->getName() . '" plugin '.($isGlobalPlugin || $this->runningInGlobalDir ? '(installed globally) ' : '').'was skipped because it requires a Plugin API version ("' . $requiresComposer->getPrettyString() . '") that does not match your Composer installation ("' . $currentPluginApiVersion . '"). You may need to run composer update with the "--no-plugins" option.'); return; } if ($package->getName() === 'symfony/flex' && Preg::isMatch('{^[0-9.]+$}', $package->getVersion()) && version_compare($package->getVersion(), '1.9.8', '<')) { $this->io->writeError('The "' . $package->getName() . '" plugin '.($isGlobalPlugin || $this->runningInGlobalDir ? '(installed globally) ' : '').'was skipped because it is not compatible with Composer 2+. Make sure to update it to version 1.9.8 or greater.'); return; } } if (!$this->isPluginAllowed($package->getName(), $isGlobalPlugin, true === ($package->getExtra()['plugin-optional'] ?? false))) { $this->io->writeError('Skipped loading "'.$package->getName() . '" '.($isGlobalPlugin || $this->runningInGlobalDir ? '(installed globally) ' : '').'as it is not in config.allow-plugins', true, IOInterface::DEBUG); return; } $oldInstallerPlugin = ($package->getType() === 'composer-installer'); if (isset($this->registeredPlugins[$package->getName()])) { return; } $extra = $package->getExtra(); if (empty($extra['class'])) { throw new \UnexpectedValueException('Error while installing '.$package->getPrettyName().', composer-plugin packages should have a class defined in their extra key to be usable.'); } $classes = is_array($extra['class']) ? $extra['class'] : [$extra['class']]; $localRepo = $this->composer->getRepositoryManager()->getLocalRepository(); $globalRepo = $this->globalComposer !== null ? $this->globalComposer->getRepositoryManager()->getLocalRepository() : null; $rootPackage = clone $this->composer->getPackage(); $rootPackageAutoloads = $rootPackage->getAutoload(); $rootPackageAutoloads['files'] = []; $rootPackage->setAutoload($rootPackageAutoloads); $rootPackageAutoloads = $rootPackage->getDevAutoload(); $rootPackageAutoloads['files'] = []; $rootPackage->setDevAutoload($rootPackageAutoloads); unset($rootPackageAutoloads); $rootPackageRepo = new RootPackageRepository($rootPackage); $installedRepo = new InstalledRepository([$localRepo, $rootPackageRepo]); if ($globalRepo) { $installedRepo->addRepository($globalRepo); } $autoloadPackages = [$package->getName() => $package]; $autoloadPackages = $this->collectDependencies($installedRepo, $autoloadPackages, $package); $generator = $this->composer->getAutoloadGenerator(); $autoloads = []; if ($this->shouldAutoloadPackage($rootPackage, '')) { $autoloads[] = [$rootPackage, '']; } foreach ($autoloadPackages as $autoloadPackage) { if ($autoloadPackage === $rootPackage) { continue; } $installPath = $this->getInstallPath($autoloadPackage, $globalRepo && $globalRepo->hasPackage($autoloadPackage)); if ($installPath === null) { continue; } if ($this->shouldAutoloadPackage($autoloadPackage, $installPath) || $autoloadPackage === $package) { $autoloads[] = [$autoloadPackage, $installPath]; } } if (\count($autoloads) === 0) { throw new \LogicException('At least the plugin package should always be autoloaded for the code below to work'); } $map = $generator->parseAutoloads($autoloads, $rootPackage); $classLoader = $generator->createLoader($map, $this->composer->getConfig()->get('vendor-dir')); $classLoader->register(false); foreach ($map['files'] as $fileIdentifier => $file) { if ($fileIdentifier === '7e9bd612cc444b3eed788ebbe46263a0') { continue; } \Composer\Autoload\composerRequire($fileIdentifier, $file); } foreach ($classes as $class) { if (class_exists($class, false)) { $class = trim($class, '\\'); $path = $classLoader->findFile($class); $code = file_get_contents($path); $separatorPos = strrpos($class, '\\'); $className = $class; if ($separatorPos) { $className = substr($class, $separatorPos + 1); } $code = Preg::replace('{^((?:(?:final|readonly)\s+)*(?:\s*))class\s+('.preg_quote($className).')}mi', '$1class $2_composer_tmp'.self::$classCounter, $code, 1); $code = strtr($code, [ '__FILE__' => var_export($path, true), '__DIR__' => var_export(dirname($path), true), '__CLASS__' => var_export($class, true), ]); $code = Preg::replace('/^\s*<\?(php)?/i', '', $code, 1); eval($code); $class .= '_composer_tmp'.self::$classCounter; self::$classCounter++; } if ($oldInstallerPlugin) { if (!is_a($class, 'Composer\Installer\InstallerInterface', true)) { throw new \RuntimeException('Could not activate plugin "'.$package->getName().'" as "'.$class.'" does not implement Composer\Installer\InstallerInterface'); } $this->io->writeError('Loading "'.$package->getName() . '" '.($isGlobalPlugin || $this->runningInGlobalDir ? '(installed globally) ' : '').'which is a legacy composer-installer built for Composer 1.x, it is likely to cause issues as you are running Composer 2.x.'); $installer = new $class($this->io, $this->composer); $this->composer->getInstallationManager()->addInstaller($installer); $this->registeredPlugins[$package->getName()][] = $installer; } elseif (class_exists($class)) { if (!is_a($class, 'Composer\Plugin\PluginInterface', true)) { throw new \RuntimeException('Could not activate plugin "'.$package->getName().'" as "'.$class.'" does not implement Composer\Plugin\PluginInterface'); } $plugin = new $class(); $this->addPlugin($plugin, $isGlobalPlugin, $package); $this->registeredPlugins[$package->getName()][] = $plugin; } elseif ($failOnMissingClasses) { throw new \UnexpectedValueException('Plugin '.$package->getName().' could not be initialized, class not found: '.$class); } } } private function shouldAutoloadPackage(PackageInterface $package, string $path): bool { $key = $package->getName().':'.$package->getVersion().':'.$path; if (isset($this->autoloadedPackages[$key])) { return false; } $this->autoloadedPackages[$key] = true; return true; } public function deactivatePackage(PackageInterface $package): void { if (!isset($this->registeredPlugins[$package->getName()])) { return; } $plugins = $this->registeredPlugins[$package->getName()]; foreach ($plugins as $plugin) { if ($plugin instanceof InstallerInterface) { $this->composer->getInstallationManager()->removeInstaller($plugin); } else { $this->removePlugin($plugin); } } unset($this->registeredPlugins[$package->getName()]); } public function uninstallPackage(PackageInterface $package): void { if (!isset($this->registeredPlugins[$package->getName()])) { return; } $plugins = $this->registeredPlugins[$package->getName()]; foreach ($plugins as $plugin) { if ($plugin instanceof InstallerInterface) { $this->composer->getInstallationManager()->removeInstaller($plugin); } else { $this->removePlugin($plugin); $this->uninstallPlugin($plugin); } } unset($this->registeredPlugins[$package->getName()]); } protected function getPluginApiVersion(): string { return PluginInterface::PLUGIN_API_VERSION; } public function addPlugin(PluginInterface $plugin, bool $isGlobalPlugin = false, ?PackageInterface $sourcePackage = null): void { if ($this->arePluginsDisabled($isGlobalPlugin ? 'global' : 'local')) { return; } if ($sourcePackage === null) { trigger_error('Calling PluginManager::addPlugin without $sourcePackage is deprecated, if you are using this please get in touch with us to explain the use case', E_USER_DEPRECATED); } elseif (!$this->isPluginAllowed($sourcePackage->getName(), $isGlobalPlugin, true === ($sourcePackage->getExtra()['plugin-optional'] ?? false))) { $this->io->writeError('Skipped loading "'.get_class($plugin).' from '.$sourcePackage->getName() . '" '.($isGlobalPlugin || $this->runningInGlobalDir ? '(installed globally) ' : '').' as it is not in config.allow-plugins', true, IOInterface::DEBUG); return; } $details = []; if ($sourcePackage) { $details[] = 'from '.$sourcePackage->getName(); } if ($isGlobalPlugin || $this->runningInGlobalDir) { $details[] = 'installed globally'; } $this->io->writeError('Loading plugin '.get_class($plugin).($details ? ' ('.implode(', ', $details).')' : ''), true, IOInterface::DEBUG); $this->plugins[] = $plugin; $plugin->activate($this->composer, $this->io); if ($plugin instanceof EventSubscriberInterface) { $this->composer->getEventDispatcher()->addSubscriber($plugin); } } public function removePlugin(PluginInterface $plugin): void { $index = array_search($plugin, $this->plugins, true); if ($index === false) { return; } $this->io->writeError('Unloading plugin '.get_class($plugin), true, IOInterface::DEBUG); unset($this->plugins[$index]); $plugin->deactivate($this->composer, $this->io); $this->composer->getEventDispatcher()->removeListener($plugin); } public function uninstallPlugin(PluginInterface $plugin): void { $this->io->writeError('Uninstalling plugin '.get_class($plugin), true, IOInterface::DEBUG); $plugin->uninstall($this->composer, $this->io); } private function loadRepository(RepositoryInterface $repo, bool $isGlobalRepo, ?RootPackageInterface $rootPackage = null): void { $packages = $repo->getPackages(); $weights = []; foreach ($packages as $package) { if ($package->getType() === 'composer-plugin') { $extra = $package->getExtra(); if ($package->getName() === 'composer/installers' || true === ($extra['plugin-modifies-install-path'] ?? false)) { $weights[$package->getName()] = -10000; } } } $sortedPackages = PackageSorter::sortPackages($packages, $weights); if (!$isGlobalRepo) { $requiredPackages = RepositoryUtils::filterRequiredPackages($packages, $rootPackage, true); } foreach ($sortedPackages as $package) { if (!($package instanceof CompletePackage)) { continue; } if (!in_array($package->getType(), ['composer-plugin', 'composer-installer'], true)) { continue; } if ( !$isGlobalRepo && !in_array($package, $requiredPackages, true) && !$this->isPluginAllowed($package->getName(), false, true, false) ) { $this->io->writeError('The "'.$package->getName().'" plugin was not loaded as it is not listed in allow-plugins and is not required by the root package anymore.'); continue; } if ('composer-plugin' === $package->getType()) { $this->registerPackage($package, false, $isGlobalRepo); } elseif ('composer-installer' === $package->getType()) { $this->registerPackage($package, false, $isGlobalRepo); } } } private function deactivateRepository(RepositoryInterface $repo, bool $isGlobalRepo): void { $packages = $repo->getPackages(); $sortedPackages = array_reverse(PackageSorter::sortPackages($packages)); foreach ($sortedPackages as $package) { if (!($package instanceof CompletePackage)) { continue; } if ('composer-plugin' === $package->getType()) { $this->deactivatePackage($package); } elseif ('composer-installer' === $package->getType()) { $this->deactivatePackage($package); } } } private function collectDependencies(InstalledRepository $installedRepo, array $collected, PackageInterface $package): array { foreach ($package->getRequires() as $requireLink) { foreach ($installedRepo->findPackagesWithReplacersAndProviders($requireLink->getTarget()) as $requiredPackage) { if (!isset($collected[$requiredPackage->getName()])) { $collected[$requiredPackage->getName()] = $requiredPackage; $collected = $this->collectDependencies($installedRepo, $collected, $requiredPackage); } } } return $collected; } private function getInstallPath(PackageInterface $package, bool $global = false): ?string { if (!$global) { return $this->composer->getInstallationManager()->getInstallPath($package); } assert(null !== $this->globalComposer); return $this->globalComposer->getInstallationManager()->getInstallPath($package); } protected function getCapabilityImplementationClassName(PluginInterface $plugin, string $capability): ?string { if (!($plugin instanceof Capable)) { return null; } $capabilities = (array) $plugin->getCapabilities(); if (!empty($capabilities[$capability]) && is_string($capabilities[$capability]) && trim($capabilities[$capability])) { return trim($capabilities[$capability]); } if ( array_key_exists($capability, $capabilities) && (empty($capabilities[$capability]) || !is_string($capabilities[$capability]) || !trim($capabilities[$capability])) ) { throw new \UnexpectedValueException('Plugin '.get_class($plugin).' provided invalid capability class name(s), got '.var_export($capabilities[$capability], true)); } return null; } public function getPluginCapability(PluginInterface $plugin, $capabilityClassName, array $ctorArgs = []): ?Capability { if ($capabilityClass = $this->getCapabilityImplementationClassName($plugin, $capabilityClassName)) { if (!class_exists($capabilityClass)) { throw new \RuntimeException("Cannot instantiate Capability, as class $capabilityClass from plugin ".get_class($plugin)." does not exist."); } $ctorArgs['plugin'] = $plugin; $capabilityObj = new $capabilityClass($ctorArgs); if (!$capabilityObj instanceof Capability || !$capabilityObj instanceof $capabilityClassName) { throw new \RuntimeException( 'Class ' . $capabilityClass . ' must implement both Composer\Plugin\Capability\Capability and '. $capabilityClassName . '.' ); } return $capabilityObj; } return null; } public function getPluginCapabilities($capabilityClassName, array $ctorArgs = []): array { $capabilities = []; foreach ($this->getPlugins() as $plugin) { $capability = $this->getPluginCapability($plugin, $capabilityClassName, $ctorArgs); if (null !== $capability) { $capabilities[] = $capability; } } return $capabilities; } private function parseAllowedPlugins($allowPluginsConfig, ?Locker $locker = null): ?array { if ([] === $allowPluginsConfig && $locker !== null && $locker->isLocked() && version_compare($locker->getPluginApi(), '2.2.0', '<')) { return null; } if (true === $allowPluginsConfig) { return ['{}' => true]; } if (false === $allowPluginsConfig) { return ['{}' => false]; } $rules = []; foreach ($allowPluginsConfig as $pattern => $allow) { $rules[BasePackage::packageNameToRegexp($pattern)] = $allow; } return $rules; } public function arePluginsDisabled($type) { return $this->disablePlugins === true || $this->disablePlugins === $type; } public function disablePlugins(): void { $this->disablePlugins = true; } public function isPluginAllowed(string $package, bool $isGlobalPlugin, bool $optional = false, bool $prompt = true): bool { if ($isGlobalPlugin) { $rules = &$this->allowGlobalPluginRules; } else { $rules = &$this->allowPluginRules; } if ($rules === null) { if (!$this->io->isInteractive()) { throw new \RuntimeException( 'Your composer.lock was generated before the allow-plugins security feature was introduced and your composer.json does not define allow-plugins. ' . 'Run "composer update --lock" locally and commit the updated composer.lock, then add an explicit allow-plugins section to composer.json. ' . 'See https://getcomposer.org/allow-plugins' ); } $rules = []; } foreach ($rules as $pattern => $allow) { if (Preg::isMatch($pattern, $package)) { return $allow === true; } } if ($package === 'composer/package-versions-deprecated') { return false; } if ($this->io->isInteractive() && $prompt) { $composer = $isGlobalPlugin && $this->globalComposer !== null ? $this->globalComposer : $this->composer; $this->io->writeError(''.$package.($isGlobalPlugin || $this->runningInGlobalDir ? ' (installed globally)' : '').' contains a Composer plugin which is currently not in your allow-plugins config. See https://getcomposer.org/allow-plugins'); $attempts = 0; while (true) { $default = '?'; if ($attempts > 5) { $this->io->writeError('Too many failed prompts, aborting.'); break; } switch ($answer = $this->io->ask('Do you trust "'.$package.'" to execute code and wish to enable it now? (writes "allow-plugins" to composer.json) [y,n,d,?] ', $default)) { case 'y': case 'n': case 'd': $allow = $answer === 'y'; $rules[BasePackage::packageNameToRegexp($package)] = $allow; if ($answer === 'y' || $answer === 'n') { $allowPlugins = $composer->getConfig()->get('allow-plugins'); if (is_array($allowPlugins)) { $allowPlugins[$package] = $allow; if ($composer->getConfig()->get('sort-packages')) { ksort($allowPlugins); } $composer->getConfig()->getConfigSource()->addConfigSetting('allow-plugins', $allowPlugins); $composer->getConfig()->merge(['config' => ['allow-plugins' => $allowPlugins]]); } } return $allow; case '?': default: $attempts++; $this->io->writeError([ 'y - add package to allow-plugins in composer.json and let it run immediately', 'n - add package (as disallowed) to allow-plugins in composer.json to suppress further prompts', 'd - discard this, do not change composer.json and do not allow the plugin to run', '? - print help', ]); break; } } } elseif ($optional) { return false; } throw new PluginBlockedException( $package.($isGlobalPlugin || $this->runningInGlobalDir ? ' (installed globally)' : '').' contains a Composer plugin which is blocked by your allow-plugins config. You may add it to the list if you consider it safe.'.PHP_EOL. 'You can run "composer '.($isGlobalPlugin || $this->runningInGlobalDir ? 'global ' : '').'config --no-plugins allow-plugins.'.$package.' [true|false]" to enable it (true) or disable it explicitly and suppress this exception (false)'.PHP_EOL. 'See https://getcomposer.org/allow-plugins' ); } } fileName = $fileName; $this->checksum = $checksum; $this->url = $url; $this->context = $context; $this->type = $type; } public function getFileName(): ?string { return $this->fileName; } public function getChecksum(): ?string { return $this->checksum; } public function getUrl(): string { return $this->url; } public function getContext() { return $this->context; } public function getPackage(): ?PackageInterface { trigger_error('PostFileDownloadEvent::getPackage is deprecated since Composer 2.1, use getContext instead.', E_USER_DEPRECATED); $context = $this->getContext(); return $context instanceof PackageInterface ? $context : null; } public function getType(): string { return $this->type; } } input = $input; $this->command = $command; } public function getInput(): InputInterface { return $this->input; } public function getCommand(): string { return $this->command; } } httpDownloader = $httpDownloader; $this->processedUrl = $processedUrl; $this->type = $type; $this->context = $context; } public function getHttpDownloader(): HttpDownloader { return $this->httpDownloader; } public function getProcessedUrl(): string { return $this->processedUrl; } public function setProcessedUrl(string $processedUrl): void { $this->processedUrl = $processedUrl; } public function getCustomCacheKey(): ?string { return $this->customCacheKey; } public function setCustomCacheKey(?string $customCacheKey): void { $this->customCacheKey = $customCacheKey; } public function getType(): string { return $this->type; } public function getContext() { return $this->context; } public function getTransportOptions(): array { return $this->transportOptions; } public function setTransportOptions(array $options): void { $this->transportOptions = $options; } } repositories = $repositories; $this->request = $request; $this->acceptableStabilities = $acceptableStabilities; $this->stabilityFlags = $stabilityFlags; $this->rootAliases = $rootAliases; $this->rootReferences = $rootReferences; $this->packages = $packages; $this->unacceptableFixedPackages = $unacceptableFixedPackages; } public function getRepositories(): array { return $this->repositories; } public function getRequest(): Request { return $this->request; } public function getAcceptableStabilities(): array { return $this->acceptableStabilities; } public function getStabilityFlags(): array { return $this->stabilityFlags; } public function getRootAliases(): array { return $this->rootAliases; } public function getRootReferences(): array { return $this->rootReferences; } public function getPackages(): array { return $this->packages; } public function getUnacceptableFixedPackages(): array { return $this->unacceptableFixedPackages; } public function setPackages(array $packages): void { $this->packages = $packages; } public function setUnacceptableFixedPackages(array $packages): void { $this->unacceptableFixedPackages = $packages; } } audit, $this->ignore ); } public function withAudit(string $audit) { return new static( $this->block, $audit, $this->ignore ); } public static function fromRawConfig(array $policyConfig, array $auditConfig, VersionParser $parser): self { if (!isset($policyConfig['abandoned']) && $auditConfig !== []) { return new self( $auditConfig['block-abandoned'] ?? false, $auditConfig['abandoned'] ?? ListPolicyConfig::AUDIT_FAIL, self::parseLegacyIgnoreWithApply($auditConfig['ignore-abandoned'] ?? []) ); } $abandonedConfig = $policyConfig['abandoned'] ?? []; if ($abandonedConfig === false) { return self::disabled(); } if (!is_array($abandonedConfig)) { $abandonedConfig = []; } return new self( (bool) ($abandonedConfig['block'] ?? false), $abandonedConfig['audit'] ?? self::AUDIT_FAIL, IgnorePackageRule::parseIgnoreMap($abandonedConfig['ignore'] ?? [], $parser) ); } public static function disabled(): self { return new self( false, self::AUDIT_IGNORE, [] ); } } ignoreId = $ignoreId; $this->ignoreSeverity = $ignoreSeverity; } public function getIgnoreIdForOperation(string $operation): array { $result = []; foreach ($this->ignoreId as $id => $rule) { if (($operation === 'block' && $rule->onBlock) || ($operation === 'audit' && $rule->onAudit)) { $result[$id] = $rule->reason; } } return $result; } public function getIgnoreListForOperation(string $operation): array { $result = $this->getIgnoreIdForOperation($operation); foreach ($this->getFlatIgnoreForOperation($operation) as $packageName => $reason) { $result[$packageName] = static::mergeReason($result, $packageName, $reason); } return $result; } public function getIgnoreSeverityForOperation(string $operation): array { $result = []; foreach ($this->ignoreSeverity as $severity => $rule) { if (($operation === 'block' && $rule->onBlock) || ($operation === 'audit' && $rule->onAudit)) { $result[$severity] = $rule->reason; } } return $result; } public function withBlockingDisabled() { return new static( false, $this->audit, $this->ignore, $this->ignoreId, $this->ignoreSeverity ); } public function withAudit(string $audit) { return new static( $this->block, $audit, $this->ignore, $this->ignoreId, $this->ignoreSeverity ); } public function withIgnoreSeverity(array $severities) { $ignoreSeverity = $this->ignoreSeverity; foreach ($severities as $severity) { if (!isset($ignoreSeverity[$severity])) { $ignoreSeverity[$severity] = new IgnoreSeverityRule($severity, null, false, true); } } return new static( $this->block, $this->audit, $this->ignore, $this->ignoreId, $ignoreSeverity ); } public static function fromRawConfig(array $policyConfig, array $auditConfig, VersionParser $parser): self { if (!isset($policyConfig['advisories']) && $auditConfig !== []) { $legacyIgnore = parent::parseLegacyAuditIgnore($auditConfig['ignore'] ?? [], $parser); return new self( $auditConfig['block-insecure'] ?? true, self::AUDIT_FAIL, $legacyIgnore['packages'] ?? [], $legacyIgnore['ids'] ?? [], self::parseLegacySeverityWithApply($auditConfig['ignore-severity'] ?? []) ); } $advisoryConfig = $policyConfig['advisories'] ?? []; if ($advisoryConfig === false) { return self::disabled(); } if (!is_array($advisoryConfig)) { $advisoryConfig = []; } return new self( (bool) ($advisoryConfig['block'] ?? true), $advisoryConfig['audit'] ?? self::AUDIT_FAIL, IgnorePackageRule::parseIgnoreMap($advisoryConfig['ignore'] ?? [], $parser), IgnoreIdRule::parseIgnoreIdMap($advisoryConfig['ignore-id'] ?? []), IgnoreSeverityRule::parseIgnoreSeverityMap($advisoryConfig['ignore-severity'] ?? []) ); } public static function disabled(): self { return new self( false, self::AUDIT_IGNORE, [], [], [] ); } private static function parseLegacySeverityWithApply(array $config): array { $result = []; foreach ($config as $key => $value) { $severity = is_int($key) ? (string) $value : $key; $parsed = self::parseLegacySingleIgnore($key, $value); $result[$severity] = new IgnoreSeverityRule($severity, $parsed['reason'], $parsed['onBlock'], $parsed['onAudit']); } return $result; } } sources = $sources; } public function withBlockingDisabled() { return new static( $this->name, false, $this->audit, $this->ignore, $this->sources ); } public function withAudit(string $audit) { return new static( $this->name, $this->block, $audit, $this->ignore, $this->sources ); } public static function fromRawConfig(string $listName, $listConfig, VersionParser $parser): self { if ($listConfig === false) { return self::disabled($listName); } if ($listConfig === true) { $listConfig = []; } if (!is_array($listConfig)) { return self::disabled($listName); } $sources = []; $sourceValidator = new SourceValidator(); foreach ($listConfig['sources'] ?? [] as $sourceConfig) { if (is_array($sourceConfig)) { $sources[] = $sourceValidator->validate($listName, $sourceConfig); } } return new self( $listName, (bool) ($listConfig['block'] ?? true), $listConfig['audit'] ?? self::AUDIT_FAIL, IgnorePackageRule::parseIgnoreMap($listConfig['ignore'] ?? [], $parser), $sources ); } public static function disabled(string $listName): self { return new static( $listName, false, self::AUDIT_IGNORE, [], [] ); } } id = $id; $this->reason = $reason; $this->onBlock = $onBlock; $this->onAudit = $onAudit; } public static function parseIgnoreIdMap(array $config): array { $rules = []; foreach ($config as $key => $value) { if (is_int($key)) { if (!is_string($value)) { throw new \UnexpectedValueException(sprintf( 'Invalid ignore-id entry at index %d: expected an advisory ID string, got %s.', $key, get_debug_type($value) )); } $rules[$value] = new self($value); continue; } if ($value === null) { $rules[$key] = new self($key); continue; } if (is_string($value)) { $rules[$key] = new self($key, $value); continue; } if (is_array($value)) { $rules[$key] = new self( $key, $value['reason'] ?? null, $value['on-block'] ?? true, $value['on-audit'] ?? true ); continue; } throw new \UnexpectedValueException(sprintf( 'Invalid ignore-id entry for "%s": value of type %s is not a supported shape.' . ' Expected null, a reason string, or a rule object.', $key, get_debug_type($value) )); } return $rules; } } packageName = $packageName; $this->constraint = $constraint; $this->reason = $reason; $this->onBlock = $onBlock; $this->onAudit = $onAudit; $this->packageNameRegex = BasePackage::packageNameToRegexp($packageName); } public static function parseIgnoreMap(array $config, ?VersionParser $parser = null): array { if ($parser === null) { $parser = new VersionParser(); } $rules = []; foreach ($config as $key => $value) { if ($value === null) { $rules[$key][] = new self((string) $key, new MatchAllConstraint()); continue; } if (is_string($value) && !is_int($key)) { $rules[$key][] = new self($key, new MatchAllConstraint(), $value); continue; } if (is_int($key) && is_string($value)) { $rules[$value][] = new self($value, new MatchAllConstraint()); continue; } if (is_array($value) && !is_int($key) && isset($value[0])) { foreach ($value as $ruleConfig) { if (!is_array($ruleConfig)) { throw new \UnexpectedValueException(sprintf( 'Invalid ignore rule for "%s": expected an object, got %s.', $key, get_debug_type($ruleConfig) )); } $rules[$key][] = self::fromRuleObject($key, $ruleConfig, $parser); } continue; } if (is_array($value) && !is_int($key)) { $rules[$key][] = self::fromRuleObject($key, $value, $parser); continue; } throw new \UnexpectedValueException(sprintf( 'Invalid ignore entry at key "%s": value of type %s is not a supported shape.' . ' Expected null, a reason string, a rule object, or a list of rule objects.', $key, get_debug_type($value) )); } return $rules; } private static function fromRuleObject(string $packageName, array $config, VersionParser $parser): self { $constraint = isset($config['constraint']) ? $parser->parseConstraints((string) $config['constraint']) : new MatchAllConstraint(); return new self( $packageName, $constraint, $config['reason'] ?? null, $config['on-block'] ?? true, $config['on-audit'] ?? true ); } public static function filterByOperation(array $rules, string $operation): array { $filtered = []; foreach ($rules as $packageName => $ruleList) { foreach ($ruleList as $rule) { if (($operation === 'block' && $rule->onBlock) || ($operation === 'audit' && $rule->onAudit)) { $filtered[$packageName][] = $rule; } } } return $filtered; } } severity = $severity; $this->reason = $reason; $this->onBlock = $onBlock; $this->onAudit = $onAudit; } public static function parseIgnoreSeverityMap(array $config): array { $rules = []; foreach ($config as $key => $value) { if (is_int($key)) { if (!is_string($value)) { throw new \UnexpectedValueException(sprintf( 'Invalid ignore-severity entry at index %d: expected a severity string, got %s.', $key, get_debug_type($value) )); } $rules[$value] = new self($value); continue; } if ($value === null) { $rules[$key] = new self($key); continue; } if (is_string($value)) { $rules[$key] = new self($key, $value); continue; } if (is_array($value)) { $rules[$key] = new self( $key, $value['reason'] ?? null, $value['on-block'] ?? true, $value['on-audit'] ?? true ); continue; } throw new \UnexpectedValueException(sprintf( 'Invalid ignore-severity entry for "%s": value of type %s is not a supported shape.' . ' Expected null, a reason string, or a rule object.', $key, get_debug_type($value) )); } return $rules; } } audit = $audit; $this->install = $install; $this->update = $update; } public function forBlockScope(string $blockScope): bool { return $blockScope === ListPolicyConfig::BLOCK_SCOPE_INSTALL ? $this->install : $this->update; } public static function default(): self { return new self(false, true, true); } public static function all(): self { return new self(true, true, true); } public static function none(): self { return new self(false, false, false); } public function with(string ...$scopes): self { if (count($scopes) === 0) { throw new \InvalidArgumentException('At least one scope is required.'); } $audit = $this->audit; $install = $this->install; $update = $this->update; foreach ($scopes as $scope) { if (!in_array($scope, self::SCOPES, true)) { throw new \InvalidArgumentException(sprintf('Unknown scope "%s". Expected one of %s.', $scope, implode(', ', self::SCOPES))); } if ($scope === 'audit') { $audit = true; } elseif ($scope === 'install') { $install = true; } elseif ($scope === 'update') { $update = true; } } return new self($audit, $install, $update); } public static function fromRawPolicyConfig(array $config): self { if (!isset($config['ignore-unreachable'])) { return self::default(); } if (is_array($config['ignore-unreachable'])) { return new self( in_array('audit', $config['ignore-unreachable'], true), in_array('install', $config['ignore-unreachable'], true), in_array('update', $config['ignore-unreachable'], true) ); } return $config['ignore-unreachable'] ? self::all() : self::none(); } public static function fromRawAuditConfig(array $auditConfig): self { if (isset($auditConfig['ignore-unreachable']) && $auditConfig['ignore-unreachable']) { return new self(true, false, false); } return self::default(); } } name = $name; $this->block = $block; $this->audit = $audit; $this->ignore = $ignore; } protected function supportsInstallBlockScope(): bool { return false; } public function shouldBlock(string $blockScope): bool { if (!$this->block) { return false; } if ($blockScope === self::BLOCK_SCOPE_INSTALL && !$this->supportsInstallBlockScope()) { return false; } return true; } public function getIgnoreForOperation(string $operation): array { return IgnorePackageRule::filterByOperation($this->ignore, $operation); } public function getFlatIgnoreForOperation(string $operation): array { $result = []; foreach ($this->ignore as $packageName => $rules) { foreach ($rules as $rule) { if (($operation === 'block' && $rule->onBlock) || ($operation === 'audit' && $rule->onAudit)) { $result[$packageName] = self::mergeReason($result, $packageName, $rule->reason); } } } return $result; } protected static function mergeReason(array $map, string $key, ?string $newReason): ?string { if (!array_key_exists($key, $map)) { return $newReason; } $existing = $map[$key]; if ($newReason === null) { return $existing; } if ($existing === null) { return $newReason; } if ($existing === $newReason) { return $existing; } $parts = array_map('trim', explode(';', $existing)); if (in_array($newReason, $parts, true)) { return $existing; } return $existing.'; '.$newReason; } abstract public function withBlockingDisabled(); abstract public function withAudit(string $audit); protected static function parseLegacyAuditIgnore(array $config, VersionParser $parser): array { $packages = []; $ids = []; foreach ($config as $key => $value) { $id = is_int($key) ? (string) $value : $key; $isPackageName = strpos($id, '/') !== false; $parsed = self::parseLegacySingleIgnore($key, $value); if ($isPackageName) { $packages[$id][] = new IgnorePackageRule( $id, new MatchAllConstraint(), $parsed['reason'], $parsed['onBlock'], $parsed['onAudit'] ); } else { $ids[$id] = new IgnoreIdRule($id, $parsed['reason'], $parsed['onBlock'], $parsed['onAudit']); } } return ['packages' => $packages, 'ids' => $ids]; } protected static function parseLegacyIgnoreWithApply(array $config): array { $result = []; foreach ($config as $key => $value) { $packageName = is_int($key) ? (string) $value : $key; $parsed = self::parseLegacySingleIgnore($key, $value); $result[$packageName] = [new IgnorePackageRule($packageName, new MatchAllConstraint(), $parsed['reason'], $parsed['onBlock'], $parsed['onAudit'])]; } return $result; } protected static function parseLegacySingleIgnore($key, $value): array { $reason = null; $onBlock = true; $onAudit = true; if (is_int($key) && is_string($value)) { } elseif (is_string($value)) { $reason = $value; } elseif (is_array($value)) { $apply = $value['apply'] ?? 'all'; $reason = $value['reason'] ?? null; if (!in_array($apply, ['audit', 'block', 'all'], true)) { throw new \InvalidArgumentException(sprintf( "Invalid 'apply' value for '%s': %s. Expected 'audit', 'block', or 'all'.", (string) $key, is_string($apply) ? $apply : get_debug_type($apply) )); } $onBlock = in_array($apply, ['block', 'all'], true); $onAudit = in_array($apply, ['audit', 'all'], true); } return ['reason' => $reason, 'onBlock' => $onBlock, 'onAudit' => $onAudit]; } } blockScope = $blockScope; $this->ignoreSource = $ignoreSource; } public function shouldBlock(string $blockScope): bool { if (!parent::shouldBlock($blockScope)) { return false; } return $this->blockScope === self::BLOCK_SCOPE_ALL || $this->blockScope === $blockScope; } public function withBlockingDisabled() { return new static( false, $this->audit, $this->blockScope, $this->ignore, $this->ignoreSource ); } public function withAudit(string $audit) { return new static( $this->block, $audit, $this->blockScope, $this->ignore, $this->ignoreSource ); } public static function fromRawConfig(array $policyConfig, VersionParser $parser): self { $malwareConfig = $policyConfig['malware'] ?? []; if ($malwareConfig === false) { return self::disabled(); } if (!is_array($malwareConfig)) { $malwareConfig = []; } return new self( (bool) ($malwareConfig['block'] ?? true), $malwareConfig['audit'] ?? self::AUDIT_FAIL, $malwareConfig['block-scope'] ?? self::BLOCK_SCOPE_ALL, IgnorePackageRule::parseIgnoreMap($malwareConfig['ignore'] ?? [], $parser), $malwareConfig['ignore-source'] ?? [] ); } public static function disabled(): self { return new self( false, self::AUDIT_IGNORE, self::BLOCK_SCOPE_ALL, [], [] ); } } enabled = $enabled; $this->advisories = $advisories; $this->malware = $malware; $this->abandoned = $abandoned; $this->customLists = $customLists; $this->ignoreUnreachable = $ignoreUnreachable; } public static function getFutureReservedListNameError(string $listName): ?string { foreach (self::FUTURE_RESERVED_PREFIXES as $prefix) { if (str_starts_with($listName, $prefix)) { return sprintf('"%s" starts with reserved prefix "%s".', $listName, $prefix); } } if (in_array($listName, self::FUTURE_RESERVED_NAMES, true)) { return sprintf('"%s" is reserved for future use.', $listName); } return null; } private static function assertCustomListNameAllowed(string $listName): void { if (in_array($listName, self::RESERVED_NAMES, true)) { throw new \UnexpectedValueException(sprintf( 'Invalid custom dependency policy name "%s": this name is reserved for a built-in dependency policy.', $listName )); } $error = self::getFutureReservedListNameError($listName); if ($error !== null) { throw new \UnexpectedValueException('Invalid custom dependency policy name: '.$error); } } public static function fromConfig(Config $config): self { $policyRaw = $config->get('policy'); $auditRaw = $config->get('audit'); $parser = new VersionParser(); if ($policyRaw === false) { return new self( false, AdvisoriesPolicyConfig::disabled(), MalwarePolicyConfig::disabled(), AbandonedPolicyConfig::disabled(), [], IgnoreUnreachable::all() ); } $policyConfig = is_array($policyRaw) ? $policyRaw : []; $auditConfig = is_array($auditRaw) ? $auditRaw : []; $advisories = AdvisoriesPolicyConfig::fromRawConfig($policyConfig, $auditConfig, $parser); $malware = MalwarePolicyConfig::fromRawConfig($policyConfig, $parser); $abandoned = AbandonedPolicyConfig::fromRawConfig($policyConfig, $auditConfig, $parser); $customLists = []; foreach ($policyConfig as $listName => $listConfig) { if (in_array($listName, self::BUILTIN_LIST_NAMES, true) || in_array($listName, self::NON_LIST_KEYS, true)) { continue; } self::assertCustomListNameAllowed((string) $listName); $customLists[$listName] = CustomListPolicyConfig::fromRawConfig($listName, $listConfig, $parser); } $ignoreUnreachable = IgnoreUnreachable::default(); if (isset($policyConfig['ignore-unreachable'])) { $ignoreUnreachable = IgnoreUnreachable::fromRawPolicyConfig($policyConfig); } elseif (isset($auditConfig['ignore-unreachable'])) { $ignoreUnreachable = IgnoreUnreachable::fromRawAuditConfig($auditConfig); } $advisoriesBlockOverride = Platform::getBoolEnv('COMPOSER_POLICY_ADVISORIES_BLOCK'); if (null !== $advisoriesBlockOverride) { $advisories = new AdvisoriesPolicyConfig( $advisoriesBlockOverride, $advisories->audit, $advisories->ignore, $advisories->ignoreId, $advisories->ignoreSeverity ); } $malwareBlockOverride = Platform::getBoolEnv('COMPOSER_POLICY_MALWARE_BLOCK'); if (null !== $malwareBlockOverride) { $malware = new MalwarePolicyConfig( $malwareBlockOverride, $malware->audit, $malware->blockScope, $malware->ignore, $malware->ignoreSource ); } $abandonedBlockOverride = Platform::getBoolEnv('COMPOSER_POLICY_ABANDONED_BLOCK') ?? Platform::getBoolEnv('COMPOSER_SECURITY_BLOCKING_ABANDONED'); if ($abandonedBlockOverride !== null) { $abandoned = new AbandonedPolicyConfig( $abandonedBlockOverride, $abandoned->audit, $abandoned->ignore ); } $auditAbandonedEnv = Platform::getEnv('COMPOSER_AUDIT_ABANDONED'); if (false !== $auditAbandonedEnv) { $allowed = [ListPolicyConfig::AUDIT_IGNORE, ListPolicyConfig::AUDIT_REPORT, ListPolicyConfig::AUDIT_FAIL]; if (!in_array($auditAbandonedEnv, $allowed, true)) { throw new \RuntimeException( "Invalid value for COMPOSER_AUDIT_ABANDONED: {$auditAbandonedEnv}. Expected one of ".implode(', ', $allowed)."." ); } $abandoned = new AbandonedPolicyConfig( $abandoned->block, $auditAbandonedEnv, $abandoned->ignore ); } return new self( true, $advisories, $malware, $abandoned, $customLists, $ignoreUnreachable ); } public function getAllLists(): array { return array_merge([ 'advisories' => $this->advisories, 'malware' => $this->malware, 'abandoned' => $this->abandoned, ], $this->customLists); } public function getActiveAuditFilterLists(): array { $lists = []; foreach ($this->filterableLists() as $name => $list) { if ($list->audit !== ListPolicyConfig::AUDIT_IGNORE) { $lists[$name] = $list; } } return $lists; } public function getActiveBlockFilterLists(string $blockScope): array { $lists = []; foreach ($this->filterableLists() as $name => $list) { if ($list->shouldBlock($blockScope)) { $lists[$name] = $list; } } return $lists; } public function getActiveAuditFilterListNames(): array { return array_keys($this->getActiveAuditFilterLists()); } public function getActiveBlockFilterListNames(string $blockScope): array { return array_keys($this->getActiveBlockFilterLists($blockScope)); } private function filterableLists(): array { $allLists = $this->getAllLists(); unset($allLists['abandoned'], $allLists['advisories']); return $allLists; } public function getCustomListsWithSources(): array { return array_filter($this->customLists, static function (CustomListPolicyConfig $list): bool { return count($list->sources) > 0; }); } public function withBlockingDisabled(): self { $customLists = []; foreach ($this->customLists as $name => $list) { $customLists[$name] = $list->withBlockingDisabled(); } return new self( $this->enabled, $this->advisories->withBlockingDisabled(), $this->malware->withBlockingDisabled(), $this->abandoned->withBlockingDisabled(), $customLists, $this->ignoreUnreachable ); } public function withIgnoreUnreachable(string ...$scopes): self { return new self( $this->enabled, $this->advisories, $this->malware, $this->abandoned, $this->customLists, $this->ignoreUnreachable->with(...$scopes) ); } public function withIgnoreSeverity(array $severities): self { return new self( $this->enabled, $this->advisories->withIgnoreSeverity($severities), $this->malware, $this->abandoned, $this->customLists, $this->ignoreUnreachable ); } public function withAudit(?string $abandoned) { return new self( $this->enabled, $this->advisories, $this->malware, $this->abandoned->withAudit($abandoned !== null ? $abandoned : $this->abandoned->audit), $this->customLists, $this->ignoreUnreachable ); } } trueAnswerRegex = $trueAnswerRegex; $this->falseAnswerRegex = $falseAnswerRegex; $this->setNormalizer($this->getDefaultNormalizer()); $this->setValidator($this->getDefaultValidator()); } private function getDefaultNormalizer(): callable { $default = $this->getDefault(); $trueRegex = $this->trueAnswerRegex; $falseRegex = $this->falseAnswerRegex; return static function ($answer) use ($default, $trueRegex, $falseRegex) { if (is_bool($answer)) { return $answer; } if (empty($answer) && !empty($default)) { return $default; } if (Preg::isMatch($trueRegex, $answer)) { return true; } if (Preg::isMatch($falseRegex, $answer)) { return false; } return null; }; } private function getDefaultValidator(): callable { return static function ($answer): bool { if (!is_bool($answer)) { throw new InvalidArgumentException('Please answer yes, y, no, or n.'); } return $answer; }; } } addPackage($package); } } public function getRepoName() { return 'array repo (defining '.$this->count().' package'.($this->count() > 1 ? 's' : '').')'; } public function loadPackages(array $packageNameMap, array $acceptableStabilities, array $stabilityFlags, array $alreadyLoaded = []) { $packages = $this->getPackages(); $result = []; $namesFound = []; foreach ($packages as $package) { if (array_key_exists($package->getName(), $packageNameMap)) { if ( (!$packageNameMap[$package->getName()] || $packageNameMap[$package->getName()]->matches(new Constraint('==', $package->getVersion()))) && StabilityFilter::isPackageAcceptable($acceptableStabilities, $stabilityFlags, $package->getNames(), $package->getStability()) && !isset($alreadyLoaded[$package->getName()][$package->getVersion()]) ) { $result[spl_object_hash($package)] = $package; if ($package instanceof AliasPackage && !isset($result[spl_object_hash($package->getAliasOf())])) { $result[spl_object_hash($package->getAliasOf())] = $package->getAliasOf(); } } $namesFound[$package->getName()] = true; } } foreach ($packages as $package) { if ($package instanceof AliasPackage) { if (isset($result[spl_object_hash($package->getAliasOf())])) { $result[spl_object_hash($package)] = $package; } } } return ['namesFound' => array_keys($namesFound), 'packages' => $result]; } public function findPackage(string $name, $constraint) { $name = strtolower($name); if (!$constraint instanceof ConstraintInterface) { $versionParser = new VersionParser(); $constraint = $versionParser->parseConstraints($constraint); } foreach ($this->getPackages() as $package) { if ($name === $package->getName()) { $pkgConstraint = new Constraint('==', $package->getVersion()); if ($constraint->matches($pkgConstraint)) { return $package; } } } return null; } public function findPackages(string $name, $constraint = null) { $name = strtolower($name); $packages = []; if (null !== $constraint && !$constraint instanceof ConstraintInterface) { $versionParser = new VersionParser(); $constraint = $versionParser->parseConstraints($constraint); } foreach ($this->getPackages() as $package) { if ($name === $package->getName()) { if (null === $constraint || $constraint->matches(new Constraint('==', $package->getVersion()))) { $packages[] = $package; } } } return $packages; } public function search(string $query, int $mode = 0, ?string $type = null) { if ($mode === self::SEARCH_FULLTEXT) { $regex = '{(?:'.implode('|', Preg::split('{\s+}', preg_quote($query))).')}i'; } else { $regex = '{(?:'.implode('|', Preg::split('{\s+}', $query)).')}i'; } $matches = []; foreach ($this->getPackages() as $package) { $name = $package->getName(); if ($mode === self::SEARCH_VENDOR) { [$name] = explode('/', $name); } if (isset($matches[$name])) { continue; } if (null !== $type && $package->getType() !== $type) { continue; } if (Preg::isMatch($regex, $name) || ($mode === self::SEARCH_FULLTEXT && $package instanceof CompletePackageInterface && Preg::isMatch($regex, implode(' ', (array) $package->getKeywords()) . ' ' . $package->getDescription())) ) { if ($mode === self::SEARCH_VENDOR) { $matches[$name] = [ 'name' => $name, 'description' => null, ]; } else { $matches[$name] = [ 'name' => $package->getPrettyName(), 'description' => $package instanceof CompletePackageInterface ? $package->getDescription() : null, ]; if ($package instanceof CompletePackageInterface && $package->isAbandoned()) { $matches[$name]['abandoned'] = $package->getReplacementPackage() ?: true; } } } } return array_values($matches); } public function hasPackage(PackageInterface $package) { if ($this->packageMap === null) { $this->packageMap = []; foreach ($this->getPackages() as $repoPackage) { $this->packageMap[$repoPackage->getUniqueName()] = $repoPackage; } } return isset($this->packageMap[$package->getUniqueName()]); } public function addPackage(PackageInterface $package) { if (!$package instanceof BasePackage) { throw new \InvalidArgumentException('Only subclasses of BasePackage are supported'); } if (null === $this->packages) { $this->initialize(); } $package->setRepository($this); $this->packages[] = $package; if ($package instanceof AliasPackage) { $aliasedPackage = $package->getAliasOf(); if (null === $aliasedPackage->getRepository()) { $this->addPackage($aliasedPackage); } } $this->packageMap = null; } public function getProviders(string $packageName) { $result = []; foreach ($this->getPackages() as $candidate) { if (isset($result[$candidate->getName()])) { continue; } foreach ($candidate->getProvides() as $link) { if ($packageName === $link->getTarget()) { $result[$candidate->getName()] = [ 'name' => $candidate->getName(), 'description' => $candidate instanceof CompletePackageInterface ? $candidate->getDescription() : null, 'type' => $candidate->getType(), ]; continue 2; } } } return $result; } protected function createAliasPackage(BasePackage $package, string $alias, string $prettyAlias) { while ($package instanceof AliasPackage) { $package = $package->getAliasOf(); } if ($package instanceof CompletePackage) { return new CompleteAliasPackage($package, $alias, $prettyAlias); } return new AliasPackage($package, $alias, $prettyAlias); } public function removePackage(PackageInterface $package) { $packageId = $package->getUniqueName(); foreach ($this->getPackages() as $key => $repoPackage) { if ($packageId === $repoPackage->getUniqueName()) { array_splice($this->packages, $key, 1); $this->packageMap = null; return; } } } public function getPackages() { if (null === $this->packages) { $this->initialize(); } if (null === $this->packages) { throw new \LogicException('initialize failed to initialize the packages array'); } return $this->packages; } public function count(): int { if (null === $this->packages) { $this->initialize(); } return count($this->packages); } protected function initialize() { $this->packages = []; } } loader = new ArrayLoader(); $this->lookup = Platform::expandPath($repoConfig['url']); $this->io = $io; $this->repoConfig = $repoConfig; } public function getRepoName() { return 'artifact repo ('.$this->lookup.')'; } public function getRepoConfig() { return $this->repoConfig; } protected function initialize() { parent::initialize(); $this->scanDirectory($this->lookup); } private function scanDirectory(string $path): void { $io = $this->io; $directory = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::FOLLOW_SYMLINKS); $iterator = new \RecursiveIteratorIterator($directory); $regex = new \RegexIterator($iterator, '/^.+\.(zip|tar|gz|tgz)$/i'); foreach ($regex as $file) { if (!$file->isFile()) { continue; } $package = $this->getComposerInformation($file); if (!$package) { $io->writeError("File {$file->getBasename()} doesn't seem to hold a package", true, IOInterface::VERBOSE); continue; } $template = 'Found package %s (%s) in file %s'; $io->writeError(sprintf($template, $package->getName(), $package->getPrettyVersion(), $file->getBasename()), true, IOInterface::VERBOSE); $this->addPackage($package); } } private function getComposerInformation(\SplFileInfo $file): ?BasePackage { $json = null; $fileType = null; $fileExtension = pathinfo($file->getPathname(), PATHINFO_EXTENSION); if (in_array($fileExtension, ['gz', 'tar', 'tgz'], true)) { $fileType = 'tar'; } elseif ($fileExtension === 'zip') { $fileType = 'zip'; } else { throw new \RuntimeException('Files with "'.$fileExtension.'" extensions aren\'t supported. Only ZIP and TAR/TAR.GZ/TGZ archives are supported.'); } try { if ($fileType === 'tar') { $json = Tar::getComposerJson($file->getPathname()); } else { $json = Zip::getComposerJson($file->getPathname()); } } catch (\Exception $exception) { $this->io->write('Failed loading package '.$file->getPathname().': '.$exception->getMessage(), false, IOInterface::VERBOSE); } if (null === $json) { return null; } $package = JsonFile::parseJson($json, $file->getPathname().'#composer.json'); $package['dist'] = [ 'type' => $fileType, 'url' => strtr($file->getPathname(), '\\', '/'), 'shasum' => hash_file('sha1', $file->getRealPath()), ]; try { $package = $this->loader->load($package); } catch (\UnexpectedValueException $e) { throw new \UnexpectedValueException('Failed loading package in '.$file.': '.$e->getMessage(), 0, $e); } return $package; } } getPackages(); $packagesByName = []; foreach ($packages as $package) { if (!isset($packagesByName[$package->getName()]) || $packagesByName[$package->getName()] instanceof AliasPackage) { $packagesByName[$package->getName()] = $package; } } $canonicalPackages = []; foreach ($packagesByName as $package) { while ($package instanceof AliasPackage) { $package = $package->getAliasOf(); } $canonicalPackages[] = $package; } return $canonicalPackages; } } allowSslDowngrade = true; } $this->options = $repoConfig['options']; $this->url = $repoConfig['url']; if (Preg::isMatch('{^(?Phttps?)://packagist\.org/?$}i', $this->url, $match)) { $this->url = $match['proto'].'://repo.packagist.org'; } $baseUrl = rtrim(Preg::replace('{(?:/[^/\\\\]+\.json)?(?:[?#].*)?$}', '', $this->url), '/'); assert($baseUrl !== ''); $this->baseUrl = $baseUrl; $this->io = $io; $this->cache = new Cache($io, $config->get('cache-repo-dir').'/'.Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($this->url)), 'a-z0-9.$~_'); $this->cache->setReadOnly($config->get('cache-read-only')); $this->versionParser = new VersionParser(); $this->loader = new ArrayLoader($this->versionParser); $this->httpDownloader = $httpDownloader; $this->eventDispatcher = $eventDispatcher; $this->repoConfig = $repoConfig; $this->loop = new Loop($this->httpDownloader); $this->userFilterConfig = self::parseUserFilterConfig($repoConfig['filter'] ?? null); } private static function parseUserFilterConfig($rawFilter) { if ($rawFilter === false) { return false; } if ($rawFilter === null || $rawFilter === true) { return []; } if (!is_array($rawFilter)) { throw new \UnexpectedValueException('Repository "filter" must be a boolean or an object mapping advertised list names to false.'); } $parsed = []; foreach ($rawFilter as $listName => $value) { if (!is_string($listName) || $listName === '') { throw new \UnexpectedValueException('Repository "filter" keys must be non-empty list-name strings.'); } if ($value === true) { continue; } if ($value !== false) { throw new \UnexpectedValueException(sprintf('Repository "filter" entry for "%s" must be a boolean; got %s.', $listName, var_export($value, true))); } $parsed[$listName] = false; } return $parsed; } public function getRepoName() { return 'composer repo ('.Url::sanitize($this->url).')'; } public function getRepoConfig() { return $this->repoConfig; } public function findPackage(string $name, $constraint) { $hasProviders = $this->hasProviders(); $name = strtolower($name); if (!$constraint instanceof ConstraintInterface) { $constraint = $this->versionParser->parseConstraints($constraint); } if ($this->lazyProvidersUrl) { if ($this->hasPartialPackages() && isset($this->partialPackagesByName[$name])) { return $this->filterPackages($this->whatProvides($name), $constraint, true); } if ($this->hasAvailablePackageList && !$this->lazyProvidersRepoContains($name)) { return null; } $packages = $this->loadAsyncPackages([$name => $constraint]); if (count($packages['packages']) > 0) { return reset($packages['packages']); } return null; } if ($hasProviders) { foreach ($this->getProviderNames() as $providerName) { if ($name === $providerName) { return $this->filterPackages($this->whatProvides($providerName), $constraint, true); } } return null; } return parent::findPackage($name, $constraint); } public function findPackages(string $name, $constraint = null) { $hasProviders = $this->hasProviders(); $name = strtolower($name); if (null !== $constraint && !$constraint instanceof ConstraintInterface) { $constraint = $this->versionParser->parseConstraints($constraint); } if ($this->lazyProvidersUrl) { if ($this->hasPartialPackages() && isset($this->partialPackagesByName[$name])) { return $this->filterPackages($this->whatProvides($name), $constraint); } if ($this->hasAvailablePackageList && !$this->lazyProvidersRepoContains($name)) { return []; } $result = $this->loadAsyncPackages([$name => $constraint]); return $result['packages']; } if ($hasProviders) { foreach ($this->getProviderNames() as $providerName) { if ($name === $providerName) { return $this->filterPackages($this->whatProvides($providerName), $constraint); } } return []; } return parent::findPackages($name, $constraint); } private function filterPackages(array $packages, ?ConstraintInterface $constraint = null, bool $returnFirstMatch = false) { if (null === $constraint) { if ($returnFirstMatch) { return reset($packages); } return $packages; } $filteredPackages = []; foreach ($packages as $package) { $pkgConstraint = new Constraint('==', $package->getVersion()); if ($constraint->matches($pkgConstraint)) { if ($returnFirstMatch) { return $package; } $filteredPackages[] = $package; } } if ($returnFirstMatch) { return null; } return $filteredPackages; } public function getPackages() { $hasProviders = $this->hasProviders(); if ($this->lazyProvidersUrl) { if (is_array($this->availablePackages) && !$this->availablePackagePatterns) { $packageMap = []; foreach ($this->availablePackages as $name) { $packageMap[$name] = new MatchAllConstraint(); } $result = $this->loadAsyncPackages($packageMap); return array_values($result['packages']); } if ($this->hasPartialPackages()) { if (!is_array($this->partialPackagesByName)) { throw new \LogicException('hasPartialPackages failed to initialize $this->partialPackagesByName'); } return $this->createPackages($this->partialPackagesByName, 'packages.json inline packages'); } throw new \LogicException('Composer repositories that have lazy providers and no available-packages list can not load the complete list of packages, use getPackageNames instead.'); } if ($hasProviders) { throw new \LogicException('Composer repositories that have providers can not load the complete list of packages, use getPackageNames instead.'); } return parent::getPackages(); } public function getPackageNames(?string $packageFilter = null) { $hasProviders = $this->hasProviders(); $filterResults = static function (array $results): array { return $results; } ; if (null !== $packageFilter && '' !== $packageFilter) { $packageFilterRegex = BasePackage::packageNameToRegexp($packageFilter); $filterResults = static function (array $results) use ($packageFilterRegex): array { return Preg::grep($packageFilterRegex, $results); } ; } if ($this->lazyProvidersUrl) { if (is_array($this->availablePackages)) { return $filterResults(array_keys($this->availablePackages)); } if ($this->listUrl) { return $this->loadPackageList($packageFilter); } if ($this->hasPartialPackages() && $this->partialPackagesByName !== null) { return $filterResults(array_keys($this->partialPackagesByName)); } return []; } if ($hasProviders) { return $filterResults($this->getProviderNames()); } $names = []; foreach ($this->getPackages() as $package) { $names[] = $package->getPrettyName(); } return $filterResults($names); } private function getVendorNames(): array { $cacheKey = 'vendor-list.txt'; $cacheAge = $this->cache->getAge($cacheKey); if (false !== $cacheAge && $cacheAge < 600 && ($cachedData = $this->cache->read($cacheKey)) !== false) { $cachedData = explode("\n", $cachedData); return $cachedData; } $names = $this->getPackageNames(); $uniques = []; foreach ($names as $name) { $uniques[explode('/', $name, 2)[0]] = true; } $vendors = array_keys($uniques); if (!$this->cache->isReadOnly()) { $this->cache->write($cacheKey, implode("\n", $vendors)); } return $vendors; } private function loadPackageList(?string $packageFilter = null): array { if (null === $this->listUrl) { throw new \LogicException('Make sure to call loadRootServerFile before loadPackageList'); } $url = $this->listUrl; if (is_string($packageFilter) && $packageFilter !== '') { $url .= '?filter='.urlencode($packageFilter); $result = $this->httpDownloader->get($url, $this->options)->decodeJson(); return $result['packageNames']; } $cacheKey = 'package-list.txt'; $cacheAge = $this->cache->getAge($cacheKey); if (false !== $cacheAge && $cacheAge < 600 && ($cachedData = $this->cache->read($cacheKey)) !== false) { $cachedData = explode("\n", $cachedData); return $cachedData; } $result = $this->httpDownloader->get($url, $this->options)->decodeJson(); if (!$this->cache->isReadOnly()) { $this->cache->write($cacheKey, implode("\n", $result['packageNames'])); } return $result['packageNames']; } public function loadPackages(array $packageNameMap, array $acceptableStabilities, array $stabilityFlags, array $alreadyLoaded = []) { $hasProviders = $this->hasProviders(); if (!$hasProviders && !$this->hasPartialPackages() && null === $this->lazyProvidersUrl) { return parent::loadPackages($packageNameMap, $acceptableStabilities, $stabilityFlags, $alreadyLoaded); } $packages = []; $namesFound = []; if ($hasProviders || $this->hasPartialPackages()) { foreach ($packageNameMap as $name => $constraint) { $matches = []; if (!$hasProviders && !isset($this->partialPackagesByName[$name])) { continue; } $candidates = $this->whatProvides($name, $acceptableStabilities, $stabilityFlags, $alreadyLoaded); foreach ($candidates as $candidate) { if ($candidate->getName() !== $name) { throw new \LogicException('whatProvides should never return a package with a different name than the requested one'); } $namesFound[$name] = true; if (!$constraint || $constraint->matches(new Constraint('==', $candidate->getVersion()))) { $matches[spl_object_hash($candidate)] = $candidate; if ($candidate instanceof AliasPackage && !isset($matches[spl_object_hash($candidate->getAliasOf())])) { $matches[spl_object_hash($candidate->getAliasOf())] = $candidate->getAliasOf(); } } } foreach ($candidates as $candidate) { if ($candidate instanceof AliasPackage) { if (isset($matches[spl_object_hash($candidate->getAliasOf())])) { $matches[spl_object_hash($candidate)] = $candidate; } } } $packages = array_merge($packages, $matches); unset($packageNameMap[$name]); } } if ($this->lazyProvidersUrl && count($packageNameMap)) { if ($this->hasAvailablePackageList) { foreach ($packageNameMap as $name => $constraint) { if (!$this->lazyProvidersRepoContains(strtolower($name))) { unset($packageNameMap[$name]); } } } $result = $this->loadAsyncPackages($packageNameMap, $acceptableStabilities, $stabilityFlags, $alreadyLoaded); $packages = array_merge($packages, $result['packages']); $namesFound = array_merge($namesFound, $result['namesFound']); } return ['namesFound' => array_keys($namesFound), 'packages' => $packages]; } public function search(string $query, int $mode = 0, ?string $type = null) { $this->loadRootServerFile(600); if ($this->searchUrl && $mode === self::SEARCH_FULLTEXT) { $url = str_replace(['%query%', '%type%'], [urlencode($query), $type], $this->searchUrl); $search = $this->httpDownloader->get($url, $this->options)->decodeJson(); if (empty($search['results'])) { return []; } $results = []; foreach ($search['results'] as $result) { if (!empty($result['virtual'])) { continue; } $results[] = $result; } return $results; } if ($mode === self::SEARCH_VENDOR) { $results = []; $regex = '{(?:'.implode('|', Preg::split('{\s+}', $query)).')}i'; $vendorNames = $this->getVendorNames(); foreach (Preg::grep($regex, $vendorNames) as $name) { $results[] = ['name' => $name, 'description' => '']; } return $results; } if ($this->hasProviders() || $this->lazyProvidersUrl) { if (Preg::isMatchStrictGroups('{^\^(?P(?P[a-z0-9_.-]+)/[a-z0-9_.-]*)\*?$}i', $query, $match) && $this->listUrl !== null) { $url = $this->listUrl . '?vendor='.urlencode($match['vendor']).'&filter='.urlencode($match['query'].'*'); $result = $this->httpDownloader->get($url, $this->options)->decodeJson(); $results = []; foreach ($result['packageNames'] as $name) { $results[] = ['name' => $name, 'description' => '']; } return $results; } $results = []; $regex = '{(?:'.implode('|', Preg::split('{\s+}', $query)).')}i'; $packageNames = $this->getPackageNames(); foreach (Preg::grep($regex, $packageNames) as $name) { $results[] = ['name' => $name, 'description' => '']; } return $results; } return parent::search($query, $mode); } public function hasSecurityAdvisories(): bool { $this->loadRootServerFile(600); return $this->securityAdvisoryConfig !== null && ($this->securityAdvisoryConfig['metadata'] || $this->securityAdvisoryConfig['api-url'] !== null); } public function getSecurityAdvisories(array $packageConstraintMap, bool $allowPartialAdvisories = false): array { $this->loadRootServerFile(600); if (null === $this->securityAdvisoryConfig) { return ['namesFound' => [], 'advisories' => []]; } $advisories = []; $namesFound = []; $apiUrl = $this->securityAdvisoryConfig['api-url']; if ($this->hasAvailablePackageList) { foreach ($packageConstraintMap as $name => $constraint) { if (!$this->lazyProvidersRepoContains(strtolower($name))) { unset($packageConstraintMap[$name]); } } } $parser = new VersionParser(); $create = function (array $data, string $name) use ($parser, $allowPartialAdvisories, &$packageConstraintMap): ?PartialSecurityAdvisory { $advisory = PartialSecurityAdvisory::create($name, $data, $parser); if (!$allowPartialAdvisories && !$advisory instanceof SecurityAdvisory) { throw new \RuntimeException('Advisory for '.$name.' could not be loaded as a full advisory from '.$this->getRepoName() . PHP_EOL . var_export($data, true)); } if (!$advisory->affectedVersions->matches($packageConstraintMap[$name])) { return null; } return $advisory; }; if ($this->securityAdvisoryConfig['metadata'] && ($allowPartialAdvisories || $apiUrl === null)) { $promises = []; foreach ($packageConstraintMap as $name => $constraint) { $name = strtolower($name); if (PlatformRepository::isPlatformPackage($name) || '__root__' === $name) { continue; } $promises[] = $this->startCachedAsyncDownload($name, $name) ->then(static function (array $spec) use (&$advisories, &$namesFound, &$packageConstraintMap, $name, $create): void { [$response] = $spec; if (!isset($response['security-advisories']) || !is_array($response['security-advisories'])) { return; } $namesFound[$name] = true; if (count($response['security-advisories']) > 0) { $advisories[$name] = array_values(array_filter(array_map( static function ($data) use ($name, $create) { return $create($data, $name); }, $response['security-advisories'] ))); } unset($packageConstraintMap[$name]); }); } $this->loop->wait($promises); } if ($apiUrl !== null && count($packageConstraintMap) > 0) { $options = $this->options; $options['http']['method'] = 'POST'; if (isset($options['http']['header'])) { $options['http']['header'] = (array) $options['http']['header']; } $options['http']['header'][] = 'Content-type: application/x-www-form-urlencoded'; $options['http']['timeout'] = 10; $options['http']['content'] = http_build_query(['packages' => array_keys($packageConstraintMap)]); $response = $this->httpDownloader->get($apiUrl, $options); $warned = false; foreach ($response->decodeJson()['advisories'] as $name => $list) { if (!isset($packageConstraintMap[$name])) { if (!$warned) { $this->io->writeError(''.$this->getRepoName().' returned names which were not requested in response to the security-advisories API. '.$name.' was not requested but is present in the response. Requested names were: '.implode(', ', array_keys($packageConstraintMap)).''); $warned = true; } continue; } if (count($list) > 0) { $advisories[$name] = array_values(array_filter(array_map( static function ($data) use ($name, $create) { return $create($data, $name); }, $list ))); } $namesFound[$name] = true; } } return ['namesFound' => array_keys($namesFound), 'advisories' => array_filter($advisories, static function ($adv): bool { return \count($adv) > 0; })]; } public function hasFilter(): bool { if ($this->userFilterConfig === false) { return false; } $this->loadRootServerFile(600); return $this->filterConfig !== null && $this->filterConfig->metadata; } public function getFilter(array $packageConstraintMap, array $configuredLists): array { $this->loadRootServerFile(600); if ($this->hasAvailablePackageList) { foreach ($packageConstraintMap as $name => $constraint) { if (!$this->lazyProvidersRepoContains(strtolower($name))) { unset($packageConstraintMap[$name]); } } } if ($this->filterConfig !== null && $this->filterConfig->apiUrl !== null && $this->freshMetadataUrls === []) { $response = $this->getFilterApiClient()->postPurls( $this->filterConfig->apiUrl, $packageConstraintMap, $configuredLists ); $decoded = $response->decodeJson(); if (!isset($decoded['filter']) || !is_array($decoded['filter'])) { throw new TransportException('Filter api-url '.$this->filterConfig->apiUrl.' returned an unexpected response for '.$this->getRepoName(), 0); } return ['filter' => $this->getFilterEntryBuilder()->build($decoded['filter'], $packageConstraintMap)]; } if ($this->filterConfig !== null && $this->filterConfig->summaryUrl !== null && $this->freshMetadataUrls === []) { $summary = $this->loadFilterSummary(); $candidates = []; foreach ($configuredLists as $listName) { if (!isset($summary[$listName])) { continue; } foreach ($summary[$listName] as $packageName => $summaryConstraint) { if (!isset($packageConstraintMap[$packageName])) { continue; } if (!$packageConstraintMap[$packageName] instanceof MatchAllConstraint && !$this->versionParser->parseConstraints($summaryConstraint)->matches($packageConstraintMap[$packageName])) { continue; } $candidates[$packageName] = true; } } $packageConstraintMap = array_intersect_key($packageConstraintMap, $candidates); } $entryBuilder = $this->getFilterEntryBuilder(); $filter = []; $promises = []; foreach ($packageConstraintMap as $name => $constraint) { $name = strtolower($name); if (PlatformRepository::isPlatformPackage($name) || '__root__' === $name) { continue; } $promises[] = $this->startCachedAsyncDownload($name, $name) ->then(static function (array $spec) use (&$filter, $name, $entryBuilder, $packageConstraintMap): void { [$response] = $spec; if (!isset($response['filter']) || !is_array($response['filter'])) { return; } foreach ($entryBuilder->build($response['filter'], $packageConstraintMap, $name) as $listName => $entries) { foreach ($entries as $entry) { $filter[$listName][] = $entry; } } }); } $this->loop->wait($promises); return ['filter' => $filter]; } private function getFilterApiClient(): FilterListApiClient { if ($this->filterApiClient === null) { $this->filterApiClient = new FilterListApiClient($this->httpDownloader); } return $this->filterApiClient; } private function getFilterEntryBuilder(): FilterListEntryBuilder { if ($this->filterEntryBuilder === null) { $this->filterEntryBuilder = new FilterListEntryBuilder($this->versionParser); } return $this->filterEntryBuilder; } private function loadFilterSummary(): array { if ($this->filterConfig === null || $this->filterConfig->summaryUrl === null) { return []; } $cacheKey = 'filter-summary.json'; $contents = null; $lastModified = null; $cached = $this->cache->read($cacheKey); if ($cached !== false) { $cached = json_decode($cached, true); if (is_array($cached)) { $contents = $cached; $lastModified = $cached['last-modified'] ?? null; } } $data = null; $promise = $this->asyncFetchFile($this->filterConfig->summaryUrl, $cacheKey, $lastModified) ->then(static function ($response) use (&$data, $contents): void { if (true === $response) { $data = $contents; } else { $data = $response; } }); $this->loop->wait([$promise]); if (!is_array($data) || !isset($data['filter']) || !is_array($data['filter'])) { throw new TransportException('Filter summary URL '.$this->filterConfig->summaryUrl.' returned 404 for '.$this->getRepoName(), 404); } $summary = []; foreach ($data['filter'] as $listName => $packages) { if (!is_string($listName) || !is_array($packages)) { throw new \UnexpectedValueException('Invalid filter summary received from '.$this->getRepoName().': list "'.(is_string($listName) ? $listName : '').'" must map to an object of package => constraint'); } foreach ($packages as $packageName => $constraintString) { if (!is_string($packageName) || !is_string($constraintString)) { throw new \UnexpectedValueException('Invalid filter summary received from '.$this->getRepoName().': list "'.$listName.'" entries must be strings'); } $summary[$listName][strtolower($packageName)] = $constraintString; } } return $summary; } public function getFilterLists(): array { if ($this->userFilterConfig === false) { return []; } $this->loadRootServerFile(600); if ($this->filterConfig === null) { return []; } $skipped = $this->userFilterConfig; if ($skipped === []) { return $this->filterConfig->lists; } return array_values(array_filter( $this->filterConfig->lists, static function (string $listName) use ($skipped): bool { return !isset($skipped[$listName]); } )); } public function getProviders(string $packageName) { $this->loadRootServerFile(); $result = []; if ($this->providersApiUrl) { try { $apiResult = $this->httpDownloader->get(str_replace('%package%', $packageName, $this->providersApiUrl), $this->options)->decodeJson(); } catch (TransportException $e) { if ($e->getStatusCode() === 404) { return $result; } throw $e; } foreach ($apiResult['providers'] as $provider) { $result[$provider['name']] = $provider; } return $result; } if ($this->hasPartialPackages()) { if (!is_array($this->partialPackagesByName)) { throw new \LogicException('hasPartialPackages failed to initialize $this->partialPackagesByName'); } foreach ($this->partialPackagesByName as $versions) { foreach ($versions as $candidate) { if (isset($result[$candidate['name']]) || !isset($candidate['provide'][$packageName])) { continue; } $result[$candidate['name']] = [ 'name' => $candidate['name'], 'description' => $candidate['description'] ?? '', 'type' => $candidate['type'] ?? '', ]; } } } if ($this->packages) { $result = array_merge($result, parent::getProviders($packageName)); } return $result; } private function getProviderNames(): array { $this->loadRootServerFile(); if (null === $this->providerListing) { $data = $this->loadRootServerFile(); if (is_array($data)) { $this->loadProviderListings($data); } } if ($this->lazyProvidersUrl) { return []; } if (null !== $this->providersUrl && null !== $this->providerListing) { return array_keys($this->providerListing); } return []; } protected function configurePackageTransportOptions(PackageInterface $package): void { foreach ($package->getDistUrls() as $url) { if (strpos($url, $this->baseUrl) === 0) { $package->setTransportOptions($this->options); return; } } } private function hasProviders(): bool { $this->loadRootServerFile(); return $this->hasProviders; } private function whatProvides(string $name, ?array $acceptableStabilities = null, ?array $stabilityFlags = null, array $alreadyLoaded = []): array { $packagesSource = null; if (!$this->hasPartialPackages() || !isset($this->partialPackagesByName[$name])) { if (PlatformRepository::isPlatformPackage($name) || '__root__' === $name) { return []; } if (null === $this->providerListing) { $data = $this->loadRootServerFile(); if (is_array($data)) { $this->loadProviderListings($data); } } $useLastModifiedCheck = false; if ($this->lazyProvidersUrl && !isset($this->providerListing[$name])) { $hash = null; $url = str_replace('%package%', $name, $this->lazyProvidersUrl); $cacheKey = 'provider-'.strtr($name, '/', '$').'.json'; $useLastModifiedCheck = true; } elseif ($this->providersUrl) { if (!isset($this->providerListing[$name])) { return []; } $hash = $this->providerListing[$name]['sha256']; $url = str_replace(['%package%', '%hash%'], [$name, $hash], $this->providersUrl); $cacheKey = 'provider-'.strtr($name, '/', '$').'.json'; } else { return []; } $packages = null; if (!$useLastModifiedCheck && $hash && $this->cache->sha256($cacheKey) === $hash) { $packages = json_decode($this->cache->read($cacheKey), true); $packagesSource = 'cached file ('.$cacheKey.' originating from '.Url::sanitize($url).')'; } elseif ($useLastModifiedCheck) { if ($contents = $this->cache->read($cacheKey)) { $contents = json_decode($contents, true); if (isset($alreadyLoaded[$name])) { $packages = $contents; $packagesSource = 'cached file ('.$cacheKey.' originating from '.Url::sanitize($url).')'; } elseif (isset($contents['last-modified'])) { $response = $this->fetchFileIfLastModified($url, $cacheKey, $contents['last-modified']); $packages = true === $response ? $contents : $response; $packagesSource = true === $response ? 'cached file ('.$cacheKey.' originating from '.Url::sanitize($url).')' : 'downloaded file ('.Url::sanitize($url).')'; } } } if (!$packages) { try { $packages = $this->fetchFile($url, $cacheKey, $hash, $useLastModifiedCheck); $packagesSource = 'downloaded file ('.Url::sanitize($url).')'; } catch (TransportException $e) { if ($this->lazyProvidersUrl && in_array($e->getStatusCode(), [404, 499], true)) { $packages = ['packages' => []]; $packagesSource = 'not-found file ('.Url::sanitize($url).')'; if ($e->getStatusCode() === 499) { $this->io->error('' . $e->getMessage() . ''); } } else { throw $e; } } } $loadingPartialPackage = false; } else { $packages = ['packages' => ['versions' => $this->partialPackagesByName[$name]]]; $packagesSource = 'root file ('.Url::sanitize($this->getPackagesJsonUrl()).')'; $loadingPartialPackage = true; } $result = []; $versionsToLoad = []; foreach ($packages['packages'] as $versions) { foreach ($versions as $version) { $normalizedName = strtolower($version['name']); if ($normalizedName !== $name) { continue; } if (!$loadingPartialPackage && $this->hasPartialPackages() && isset($this->partialPackagesByName[$normalizedName])) { continue; } if (!isset($versionsToLoad[$version['uid']])) { if (!isset($version['version_normalized'])) { $version['version_normalized'] = $this->versionParser->normalize($version['version']); } elseif ($version['version_normalized'] === VersionParser::DEFAULT_BRANCH_ALIAS) { $version['version_normalized'] = $this->versionParser->normalize($version['version']); } if (isset($alreadyLoaded[$name][$version['version_normalized']])) { continue; } if ($this->isVersionAcceptable(null, $normalizedName, $version, $acceptableStabilities, $stabilityFlags)) { $versionsToLoad[$version['uid']] = $version; } } } } $loadedPackages = $this->createPackages($versionsToLoad, $packagesSource); $uids = array_keys($versionsToLoad); foreach ($loadedPackages as $index => $package) { $package->setRepository($this); $uid = $uids[$index]; if ($package instanceof AliasPackage) { $aliased = $package->getAliasOf(); $aliased->setRepository($this); $result[$uid] = $aliased; $result[$uid.'-alias'] = $package; } else { $result[$uid] = $package; } } return $result; } protected function initialize() { parent::initialize(); $repoData = $this->loadDataFromServer(); foreach ($this->createPackages($repoData, 'root file ('.Url::sanitize($this->getPackagesJsonUrl()).')') as $package) { $this->addPackage($package); } } public function addPackage(PackageInterface $package) { parent::addPackage($package); $this->configurePackageTransportOptions($package); } private function loadAsyncPackages(array $packageNames, ?array $acceptableStabilities = null, ?array $stabilityFlags = null, array $alreadyLoaded = []): array { $this->loadRootServerFile(); $packages = []; $namesFound = []; $promises = []; if (null === $this->lazyProvidersUrl) { throw new \LogicException('loadAsyncPackages only supports v2 protocol composer repos with a metadata-url'); } foreach ($packageNames as $name => $constraint) { if ($acceptableStabilities === null || $stabilityFlags === null || StabilityFilter::isPackageAcceptable($acceptableStabilities, $stabilityFlags, [$name], 'dev')) { $packageNames[$name.'~dev'] = $constraint; } if (isset($acceptableStabilities['dev']) && count($acceptableStabilities) === 1 && count($stabilityFlags) === 0) { unset($packageNames[$name]); } } foreach ($packageNames as $name => $constraint) { $name = strtolower($name); $realName = Preg::replace('{~dev$}', '', $name); if (PlatformRepository::isPlatformPackage($realName) || '__root__' === $realName) { continue; } $promises[] = $this->startCachedAsyncDownload($name, $realName) ->then(function (array $spec) use (&$packages, &$namesFound, $realName, $constraint, $acceptableStabilities, $stabilityFlags, $alreadyLoaded): void { [$response, $packagesSource] = $spec; if (null === $response || !isset($response['packages'][$realName])) { return; } $versions = $response['packages'][$realName]; if (isset($response['minified']) && $response['minified'] === 'composer/2.0') { $versions = MetadataMinifier::expand($versions); } $namesFound[$realName] = true; $versionsToLoad = []; foreach ($versions as $version) { if (!isset($version['version_normalized'])) { $version['version_normalized'] = $this->versionParser->normalize($version['version']); } elseif ($version['version_normalized'] === VersionParser::DEFAULT_BRANCH_ALIAS) { $version['version_normalized'] = $this->versionParser->normalize($version['version']); } if (isset($alreadyLoaded[$realName][$version['version_normalized']])) { continue; } if ($this->isVersionAcceptable($constraint, $realName, $version, $acceptableStabilities, $stabilityFlags)) { $versionsToLoad[] = $version; } } $loadedPackages = $this->createPackages($versionsToLoad, $packagesSource); foreach ($loadedPackages as $package) { $package->setRepository($this); $packages[spl_object_hash($package)] = $package; if ($package instanceof AliasPackage && !isset($packages[spl_object_hash($package->getAliasOf())])) { $package->getAliasOf()->setRepository($this); $packages[spl_object_hash($package->getAliasOf())] = $package->getAliasOf(); } } }); } $this->loop->wait($promises); return ['namesFound' => $namesFound, 'packages' => $packages]; } private function startCachedAsyncDownload(string $fileName, ?string $packageName = null): PromiseInterface { if (null === $this->lazyProvidersUrl) { throw new \LogicException('startCachedAsyncDownload only supports v2 protocol composer repos with a metadata-url'); } $name = strtolower($fileName); $packageName = $packageName ?? $name; $url = str_replace('%package%', $name, $this->lazyProvidersUrl); $cacheKey = 'provider-'.strtr($name, '/', '~').'.json'; $lastModified = null; if ($contents = $this->cache->read($cacheKey)) { $contents = json_decode($contents, true); $lastModified = $contents['last-modified'] ?? null; } return $this->asyncFetchFile($url, $cacheKey, $lastModified) ->then(static function ($response) use ($url, $cacheKey, $contents, $packageName): array { $packagesSource = 'downloaded file ('.Url::sanitize($url).')'; if (true === $response) { $packagesSource = 'cached file ('.$cacheKey.' originating from '.Url::sanitize($url).')'; $response = $contents; } if (!isset($response['packages'][$packageName]) && !isset($response['security-advisories']) && !isset($response['filter'])) { return [null, $packagesSource]; } return [$response, $packagesSource]; }); } private function isVersionAcceptable(?ConstraintInterface $constraint, string $name, array $versionData, ?array $acceptableStabilities = null, ?array $stabilityFlags = null): bool { $versions = [$versionData['version_normalized']]; if ($alias = $this->loader->getBranchAlias($versionData)) { $versions[] = $alias; } foreach ($versions as $version) { if (null !== $acceptableStabilities && null !== $stabilityFlags && !StabilityFilter::isPackageAcceptable($acceptableStabilities, $stabilityFlags, [$name], VersionParser::parseStability($version))) { continue; } if ($constraint && !CompilingMatcher::match($constraint, Constraint::OP_EQ, $version)) { continue; } return true; } return false; } private function getPackagesJsonUrl(): string { $jsonUrlParts = parse_url(strtr($this->url, '\\', '/')); if (isset($jsonUrlParts['path']) && false !== strpos($jsonUrlParts['path'], '.json')) { return $this->url; } return $this->url . '/packages.json'; } protected function loadRootServerFile(?int $rootMaxAge = null) { if (null !== $this->rootData) { return $this->rootData; } if (!extension_loaded('openssl') && strpos($this->url, 'https') === 0) { throw new \RuntimeException('You must enable the openssl extension in your php.ini to load information from '.Url::sanitize($this->url)); } if ($cachedData = $this->cache->read('packages.json')) { $cachedData = json_decode($cachedData, true); if ($rootMaxAge !== null && ($age = $this->cache->getAge('packages.json')) !== false && $age <= $rootMaxAge) { $data = $cachedData; } elseif (isset($cachedData['last-modified'])) { $response = $this->fetchFileIfLastModified($this->getPackagesJsonUrl(), 'packages.json', $cachedData['last-modified']); $data = true === $response ? $cachedData : $response; } } if (!isset($data)) { $data = $this->fetchFile($this->getPackagesJsonUrl(), 'packages.json', null, true); } if (!empty($data['notify-batch'])) { $this->notifyUrl = $this->canonicalizeUrl($data['notify-batch']); } elseif (!empty($data['notify'])) { $this->notifyUrl = $this->canonicalizeUrl($data['notify']); } if (!empty($data['search'])) { $this->searchUrl = $this->canonicalizeUrl($data['search']); } if (!empty($data['mirrors'])) { foreach ($data['mirrors'] as $mirror) { if (!empty($mirror['git-url'])) { $this->sourceMirrors['git'][] = ['url' => $mirror['git-url'], 'preferred' => !empty($mirror['preferred'])]; } if (!empty($mirror['hg-url'])) { $this->sourceMirrors['hg'][] = ['url' => $mirror['hg-url'], 'preferred' => !empty($mirror['preferred'])]; } if (!empty($mirror['dist-url'])) { $this->distMirrors[] = [ 'url' => $this->canonicalizeUrl($mirror['dist-url']), 'preferred' => !empty($mirror['preferred']), ]; } } } if (!empty($data['providers-lazy-url'])) { $this->lazyProvidersUrl = $this->canonicalizeUrl($data['providers-lazy-url']); $this->hasProviders = true; $this->hasPartialPackages = !empty($data['packages']) && is_array($data['packages']); } if (!empty($data['metadata-url'])) { $this->lazyProvidersUrl = $this->canonicalizeUrl($data['metadata-url']); $this->providersUrl = null; $this->hasProviders = false; $this->hasPartialPackages = !empty($data['packages']) && is_array($data['packages']); $this->allowSslDowngrade = false; if (!empty($data['available-packages'])) { $availPackages = array_map('strtolower', $data['available-packages']); $this->availablePackages = array_combine($availPackages, $availPackages); $this->hasAvailablePackageList = true; } if (!empty($data['available-package-patterns'])) { $this->availablePackagePatterns = array_map(static function ($pattern): string { return BasePackage::packageNameToRegexp($pattern); }, $data['available-package-patterns']); $this->hasAvailablePackageList = true; } unset($data['providers-url'], $data['providers'], $data['providers-includes']); if (isset($data['security-advisories']) && is_array($data['security-advisories'])) { $this->securityAdvisoryConfig = [ 'metadata' => $data['security-advisories']['metadata'] ?? false, 'api-url' => isset($data['security-advisories']['api-url']) && is_string($data['security-advisories']['api-url']) ? $this->canonicalizeUrl($data['security-advisories']['api-url']) : null, ]; if ($this->securityAdvisoryConfig['api-url'] === null && !$this->hasAvailablePackageList) { throw new \UnexpectedValueException('Invalid security advisory configuration on '.$this->getRepoName().': If the repository does not provide a security-advisories.api-url then available-packages or available-package-patterns are required to be provided for performance reason.'); } } if (isset($data['filter']) && is_array($data['filter'])) { $this->filterConfig = ComposerRepositoryFilterInformation::fromData($data['filter'], \Closure::fromCallable([$this, 'canonicalizeUrl'])); } } if ($this->allowSslDowngrade) { $this->url = str_replace('https://', 'http://', $this->url); $this->baseUrl = str_replace('https://', 'http://', $this->baseUrl); } if (!empty($data['providers-url'])) { $this->providersUrl = $this->canonicalizeUrl($data['providers-url']); $this->hasProviders = true; } if (!empty($data['list'])) { $this->listUrl = $this->canonicalizeUrl($data['list']); } if (!empty($data['providers']) || !empty($data['providers-includes'])) { $this->hasProviders = true; } if (!empty($data['providers-api'])) { $this->providersApiUrl = $this->canonicalizeUrl($data['providers-api']); } return $this->rootData = $data; } private function canonicalizeUrl(string $url): string { if (strlen($url) === 0) { throw new \InvalidArgumentException('Expected a string with a value and not an empty string'); } if (str_starts_with($url, '/')) { if (Preg::isMatch('{^[^:]++://[^/]*+}', $this->url, $matches)) { return $matches[0] . $url; } return $this->url; } return $url; } private function loadDataFromServer(): array { $data = $this->loadRootServerFile(); if (true === $data) { throw new \LogicException('loadRootServerFile should not return true during initialization'); } return $this->loadIncludes($data); } private function hasPartialPackages(): bool { if ($this->hasPartialPackages && null === $this->partialPackagesByName) { $this->initializePartialPackages(); } return $this->hasPartialPackages; } private function loadProviderListings($data): void { if (isset($data['providers'])) { if (!is_array($this->providerListing)) { $this->providerListing = []; } $this->providerListing = array_merge($this->providerListing, $data['providers']); } if ($this->providersUrl && isset($data['provider-includes'])) { $includes = $data['provider-includes']; foreach ($includes as $include => $metadata) { $url = $this->baseUrl . '/' . str_replace('%hash%', $metadata['sha256'], $include); $cacheKey = str_replace(['%hash%','$'], '', $include); if ($this->cache->sha256($cacheKey) === $metadata['sha256']) { $includedData = json_decode($this->cache->read($cacheKey), true); } else { $includedData = $this->fetchFile($url, $cacheKey, $metadata['sha256']); } $this->loadProviderListings($includedData); } } } private function loadIncludes(array $data): array { $packages = []; if (!isset($data['packages']) && !isset($data['includes'])) { foreach ($data as $pkg) { if (isset($pkg['versions']) && is_array($pkg['versions'])) { foreach ($pkg['versions'] as $metadata) { $packages[] = $metadata; } } } return $packages; } if (isset($data['packages'])) { foreach ($data['packages'] as $package => $versions) { $packageName = strtolower((string) $package); foreach ($versions as $version => $metadata) { $packages[] = $metadata; if (!$this->displayedWarningAboutNonMatchingPackageIndex && $packageName !== strtolower((string) ($metadata['name'] ?? ''))) { $this->displayedWarningAboutNonMatchingPackageIndex = true; $this->io->writeError(sprintf("Warning: the packages key '%s' doesn't match the name defined in the package metadata '%s' in repository %s", $package, $metadata['name'] ?? '', $this->baseUrl)); } } } } if (isset($data['includes'])) { foreach ($data['includes'] as $include => $metadata) { if (isset($metadata['sha1']) && $this->cache->sha1((string) $include) === $metadata['sha1']) { $includedData = json_decode($this->cache->read((string) $include), true); } else { $includedData = $this->fetchFile($include); } $packages = array_merge($packages, $this->loadIncludes($includedData)); } } return $packages; } private function createPackages(array $packages, ?string $source = null): array { if (!$packages) { return []; } try { foreach ($packages as &$data) { if (!isset($data['notification-url'])) { $data['notification-url'] = $this->notifyUrl; } } $packageInstances = $this->loader->loadPackages($packages); foreach ($packageInstances as $package) { if (isset($this->sourceMirrors[$package->getSourceType()])) { $package->setSourceMirrors($this->sourceMirrors[$package->getSourceType()]); } $package->setDistMirrors($this->distMirrors); $this->configurePackageTransportOptions($package); } return $packageInstances; } catch (\Exception $e) { throw new \RuntimeException('Could not load packages in '.$this->getRepoName().($source ? ' from '.$source : '').': ['.get_class($e).'] '.$e->getMessage(), 0, $e); } } protected function fetchFile(string $filename, ?string $cacheKey = null, ?string $sha256 = null, bool $storeLastModifiedTime = false) { if ('' === $filename) { throw new \InvalidArgumentException('$filename should not be an empty string'); } if (null === $cacheKey) { $cacheKey = $filename; $filename = $this->baseUrl.'/'.$filename; } if (($pos = strpos($filename, '$')) && Preg::isMatch('{^https?://}i', $filename)) { $filename = substr($filename, 0, $pos) . '%24' . substr($filename, $pos + 1); } $retries = 3; while ($retries--) { try { $options = $this->options; if ($this->eventDispatcher) { $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->httpDownloader, $filename, 'metadata', ['repository' => $this]); $preFileDownloadEvent->setTransportOptions($this->options); $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); $filename = $preFileDownloadEvent->getProcessedUrl(); $options = $preFileDownloadEvent->getTransportOptions(); } $response = $this->httpDownloader->get($filename, $options); $json = (string) $response->getBody(); if ($sha256 && $sha256 !== hash('sha256', $json)) { if ($this->allowSslDowngrade) { $this->url = str_replace('http://', 'https://', $this->url); $this->baseUrl = str_replace('http://', 'https://', $this->baseUrl); $filename = str_replace('http://', 'https://', $filename); } if ($retries > 0) { usleep(100000); continue; } throw new RepositorySecurityException('The contents of '.$filename.' do not match its signature. This could indicate a man-in-the-middle attack or e.g. antivirus software corrupting files. Try running composer again and report this if you think it is a mistake.'); } if ($this->eventDispatcher) { $postFileDownloadEvent = new PostFileDownloadEvent(PluginEvents::POST_FILE_DOWNLOAD, null, $sha256, $filename, 'metadata', ['response' => $response, 'repository' => $this]); $this->eventDispatcher->dispatch($postFileDownloadEvent->getName(), $postFileDownloadEvent); } $data = $response->decodeJson(); HttpDownloader::outputWarnings($this->io, $this->url, $data); if ($cacheKey && !$this->cache->isReadOnly()) { if ($storeLastModifiedTime) { $lastModifiedDate = $response->getHeader('last-modified'); if ($lastModifiedDate) { $data['last-modified'] = $lastModifiedDate; $json = JsonFile::encode($data, 0); } } $this->cache->write($cacheKey, $json); } $response->collect(); break; } catch (\Exception $e) { if ($e instanceof \LogicException) { throw $e; } if ($e instanceof TransportException && $e->getStatusCode() === 404) { throw $e; } if ($e instanceof RepositorySecurityException) { throw $e; } if ($cacheKey && ($contents = $this->cache->read($cacheKey))) { if (!$this->degradedMode) { $this->io->writeError(''.Url::sanitize($this->url).' could not be fully loaded ('.$e->getMessage().'), package information was loaded from the local cache and may be out of date'); } $this->degradedMode = true; $data = JsonFile::parseJson($contents, $this->cache->getRoot().$cacheKey); break; } throw $e; } } if (!isset($data)) { throw new \LogicException("ComposerRepository: Undefined \$data. Please report at https://github.com/composer/composer/issues/new."); } return $data; } private function fetchFileIfLastModified(string $filename, string $cacheKey, string $lastModifiedTime) { if ('' === $filename) { throw new \InvalidArgumentException('$filename should not be an empty string'); } try { $options = $this->options; if ($this->eventDispatcher) { $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->httpDownloader, $filename, 'metadata', ['repository' => $this]); $preFileDownloadEvent->setTransportOptions($this->options); $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); $filename = $preFileDownloadEvent->getProcessedUrl(); $options = $preFileDownloadEvent->getTransportOptions(); } if (isset($options['http']['header'])) { $options['http']['header'] = (array) $options['http']['header']; } $options['http']['header'][] = 'If-Modified-Since: '.$lastModifiedTime; $response = $this->httpDownloader->get($filename, $options); $json = (string) $response->getBody(); if ($json === '' && $response->getStatusCode() === 304) { return true; } if ($this->eventDispatcher) { $postFileDownloadEvent = new PostFileDownloadEvent(PluginEvents::POST_FILE_DOWNLOAD, null, null, $filename, 'metadata', ['response' => $response, 'repository' => $this]); $this->eventDispatcher->dispatch($postFileDownloadEvent->getName(), $postFileDownloadEvent); } $data = $response->decodeJson(); HttpDownloader::outputWarnings($this->io, $this->url, $data); $lastModifiedDate = $response->getHeader('last-modified'); $response->collect(); if ($lastModifiedDate) { $data['last-modified'] = $lastModifiedDate; $json = JsonFile::encode($data, 0); } if (!$this->cache->isReadOnly()) { $this->cache->write($cacheKey, $json); } return $data; } catch (\Exception $e) { if ($e instanceof \LogicException) { throw $e; } if ($e instanceof TransportException && $e->getStatusCode() === 404) { throw $e; } if (!$this->degradedMode) { $this->io->writeError(''.Url::sanitize($this->url).' could not be fully loaded ('.$e->getMessage().'), package information was loaded from the local cache and may be out of date'); } $this->degradedMode = true; return true; } } private function asyncFetchFile(string $filename, string $cacheKey, ?string $lastModifiedTime = null): PromiseInterface { if ('' === $filename) { throw new \InvalidArgumentException('$filename should not be an empty string'); } if (isset($this->packagesNotFoundCache[$filename])) { return \React\Promise\resolve(['packages' => []]); } if (isset($this->freshMetadataUrls[$filename]) && $lastModifiedTime) { $promise = \React\Promise\resolve(true); return $promise; } $httpDownloader = $this->httpDownloader; $options = $this->options; if ($this->eventDispatcher) { $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->httpDownloader, $filename, 'metadata', ['repository' => $this]); $preFileDownloadEvent->setTransportOptions($this->options); $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); $filename = $preFileDownloadEvent->getProcessedUrl(); $options = $preFileDownloadEvent->getTransportOptions(); } if ($lastModifiedTime) { if (isset($options['http']['header'])) { $options['http']['header'] = (array) $options['http']['header']; } $options['http']['header'][] = 'If-Modified-Since: '.$lastModifiedTime; } $io = $this->io; $url = $this->url; $cache = $this->cache; $degradedMode = &$this->degradedMode; $eventDispatcher = $this->eventDispatcher; $accept = function ($response) use ($io, $url, $filename, $cache, $cacheKey, $eventDispatcher) { if ($response->getStatusCode() === 404) { $this->packagesNotFoundCache[$filename] = true; return ['packages' => []]; } $json = (string) $response->getBody(); if ($json === '' && $response->getStatusCode() === 304) { $this->freshMetadataUrls[$filename] = true; return true; } if ($eventDispatcher) { $postFileDownloadEvent = new PostFileDownloadEvent(PluginEvents::POST_FILE_DOWNLOAD, null, null, $filename, 'metadata', ['response' => $response, 'repository' => $this]); $eventDispatcher->dispatch($postFileDownloadEvent->getName(), $postFileDownloadEvent); } $data = $response->decodeJson(); HttpDownloader::outputWarnings($io, $url, $data); $lastModifiedDate = $response->getHeader('last-modified'); $response->collect(); if ($lastModifiedDate) { $data['last-modified'] = $lastModifiedDate; $json = JsonFile::encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); } if (!$cache->isReadOnly()) { $cache->write($cacheKey, $json); } $this->freshMetadataUrls[$filename] = true; return $data; }; $reject = function ($e) use ($filename, $accept, $io, $url, &$degradedMode, $lastModifiedTime) { if ($e instanceof TransportException && $e->getStatusCode() === 404) { $this->packagesNotFoundCache[$filename] = true; return false; } if (!$degradedMode) { $io->writeError(''.Url::sanitize($url).' could not be fully loaded ('.$e->getMessage().'), package information was loaded from the local cache and may be out of date'); } $degradedMode = true; if ($lastModifiedTime) { return $accept(new Response(['url' => $url], 304, [], '')); } if ($e instanceof TransportException && $e->getStatusCode() === 499) { return $accept(new Response(['url' => $url], 404, [], '')); } throw $e; }; return $httpDownloader->add($filename, $options)->then($accept, $reject); } private function initializePartialPackages(): void { $rootData = $this->loadRootServerFile(); if ($rootData === true) { return; } $this->partialPackagesByName = []; foreach ($rootData['packages'] as $package => $versions) { foreach ($versions as $version) { $versionPackageName = strtolower((string) ($version['name'] ?? '')); $this->partialPackagesByName[$versionPackageName][] = $version; if (!$this->displayedWarningAboutNonMatchingPackageIndex && $versionPackageName !== strtolower($package)) { $this->io->writeError(sprintf("Warning: the packages key '%s' doesn't match the name defined in the package metadata '%s' in repository %s", $package, $version['name'] ?? '', $this->baseUrl)); $this->displayedWarningAboutNonMatchingPackageIndex = true; } } } $this->rootData = true; } protected function lazyProvidersRepoContains(string $name) { if (!$this->hasAvailablePackageList) { throw new \LogicException('lazyProvidersRepoContains should not be called unless hasAvailablePackageList is true'); } if (is_array($this->availablePackages) && isset($this->availablePackages[$name])) { return true; } if (is_array($this->availablePackagePatterns)) { foreach ($this->availablePackagePatterns as $providerRegex) { if (Preg::isMatch($providerRegex, $name)) { return true; } } } return false; } } repositories = []; foreach ($repositories as $repo) { $this->addRepository($repo); } } public function getRepoName(): string { return 'composite repo ('.implode(', ', array_map(static function ($repo): string { return $repo->getRepoName(); }, $this->repositories)).')'; } public function getRepositories(): array { return $this->repositories; } public function hasPackage(PackageInterface $package): bool { foreach ($this->repositories as $repository) { if ($repository->hasPackage($package)) { return true; } } return false; } public function findPackage($name, $constraint): ?BasePackage { foreach ($this->repositories as $repository) { $package = $repository->findPackage($name, $constraint); if (null !== $package) { return $package; } } return null; } public function findPackages($name, $constraint = null): array { $packages = []; foreach ($this->repositories as $repository) { $packages[] = $repository->findPackages($name, $constraint); } return $packages ? array_merge(...$packages) : []; } public function loadPackages(array $packageNameMap, array $acceptableStabilities, array $stabilityFlags, array $alreadyLoaded = []): array { $packages = []; $namesFound = []; foreach ($this->repositories as $repository) { $result = $repository->loadPackages($packageNameMap, $acceptableStabilities, $stabilityFlags, $alreadyLoaded); $packages[] = $result['packages']; $namesFound[] = $result['namesFound']; } return [ 'packages' => $packages ? array_merge(...$packages) : [], 'namesFound' => $namesFound ? array_unique(array_merge(...$namesFound)) : [], ]; } public function search(string $query, int $mode = 0, ?string $type = null, ?IOInterface $io = null): array { $matches = []; foreach ($this->repositories as $repository) { $results = $repository->search($query, $mode, $type); if ($io !== null) { $io->writeError('Searched '.$repository->getRepoName().', found '.count($results).' result(s)', true, IOInterface::VERY_VERBOSE); } $matches[] = $results; } return \count($matches) > 0 ? array_merge(...$matches) : []; } public function getPackages(): array { $packages = []; foreach ($this->repositories as $repository) { $packages[] = $repository->getPackages(); } return $packages ? array_merge(...$packages) : []; } public function getProviders($packageName): array { $results = []; foreach ($this->repositories as $repository) { $results[] = $repository->getProviders($packageName); } return $results ? array_merge(...$results) : []; } public function removePackage(PackageInterface $package): void { foreach ($this->repositories as $repository) { if ($repository instanceof WritableRepositoryInterface) { $repository->removePackage($package); } } } public function count(): int { $total = 0; foreach ($this->repositories as $repository) { $total += $repository->count(); } return $total; } public function addRepository(RepositoryInterface $repository): void { if ($repository instanceof self) { foreach ($repository->getRepositories() as $repo) { $this->addRepository($repo); } } else { $this->repositories[] = $repository; } } } file = $repositoryFile; $this->dumpVersions = $dumpVersions; $this->rootPackage = $rootPackage; $this->filesystem = $filesystem ?: new Filesystem; if ($dumpVersions && !$rootPackage) { throw new \InvalidArgumentException('Expected a root package instance if $dumpVersions is true'); } } public function getDevMode() { return $this->devMode; } protected function initialize() { parent::initialize(); if (!$this->file->exists()) { return; } try { $data = $this->file->read(); if (isset($data['packages'])) { $packages = $data['packages']; } else { $packages = $data; } if (isset($data['dev-package-names'])) { $this->setDevPackageNames($data['dev-package-names']); } if (isset($data['dev'])) { $this->devMode = $data['dev']; } if (!is_array($packages)) { throw new \UnexpectedValueException('Could not parse package list from the repository'); } } catch (\Exception $e) { throw new InvalidRepositoryException('Invalid repository data in '.$this->file->getPath().', packages could not be loaded: ['.get_class($e).'] '.$e->getMessage()); } $loader = new ArrayLoader(null, true); foreach ($packages as $packageData) { $package = $loader->load($packageData); $this->addPackage($package); } } public function reload() { $this->packages = null; $this->initialize(); } public function write(bool $devMode, InstallationManager $installationManager) { $data = ['packages' => [], 'dev' => $devMode, 'dev-package-names' => []]; $dumper = new ArrayDumper(); $repoDir = dirname($this->file->getPath()); $this->filesystem->ensureDirectoryExists($repoDir); $repoDir = $this->filesystem->normalizePath(realpath($repoDir)); $installPaths = []; foreach ($this->getCanonicalPackages() as $package) { $pkgArray = $dumper->dump($package); $path = $installationManager->getInstallPath($package); $installPath = null; if ('' !== $path && null !== $path) { $normalizedPath = $this->filesystem->normalizePath($this->filesystem->isAbsolutePath($path) ? $path : Platform::getCwd() . '/' . $path); $installPath = $this->filesystem->findShortestPath($repoDir, $normalizedPath, true); } $installPaths[$package->getName()] = $installPath; $pkgArray['install-path'] = $installPath; $data['packages'][] = $pkgArray; if (in_array($package->getName(), $this->devPackageNames, true)) { $data['dev-package-names'][] = $package->getName(); } } sort($data['dev-package-names']); usort($data['packages'], static function ($a, $b): int { return strcmp($a['name'], $b['name']); }); $this->file->write($data); if ($this->dumpVersions) { $versions = $this->generateInstalledVersions($installationManager, $installPaths, $devMode, $repoDir); $this->filesystem->filePutContentsIfModified($repoDir.'/installed.php', 'dumpToPhpCode($versions) . ';'."\n"); $installedVersionsClass = file_get_contents(__DIR__.'/../InstalledVersions.php'); if ($installedVersionsClass !== false) { $this->filesystem->filePutContentsIfModified($repoDir.'/InstalledVersions.php', $installedVersionsClass); \Composer\InstalledVersions::reload($versions); try { $reflProp = new \ReflectionProperty(\Composer\InstalledVersions::class, 'selfDir'); (\PHP_VERSION_ID < 80100) and $reflProp->setAccessible(true); $reflProp->setValue(null, strtr($repoDir, '\\', '/')); $reflProp = new \ReflectionProperty(\Composer\InstalledVersions::class, 'installedIsLocalDir'); (\PHP_VERSION_ID < 80100) and $reflProp->setAccessible(true); $reflProp->setValue(null, true); } catch (\ReflectionException $e) { if (!Preg::isMatch('{Property .*? does not exist}i', $e->getMessage())) { throw $e; } } } } } public static function safelyLoadInstalledVersions(string $path): bool { $installedVersionsData = @file_get_contents($path); $pattern = <<<'REGEX' {(?(DEFINE) (? -? \s*+ \d++ (?:\.\d++)? ) (? true | false | null ) (? (?&string) (?: \s*+ \. \s*+ (?&string))*+ ) (? (?: " (?:[^"\\$]*+ | \\ ["\\0] )* " | ' (?:[^'\\]*+ | \\ ['\\] )* ' ) ) (? array\( \s*+ (?: (?:(?&number)|(?&strings)) \s*+ => \s*+ (?: (?:__DIR__ \s*+ \. \s*+)? (?&strings) | (?&value) ) \s*+, \s*+ )*+ \s*+ \) ) (? (?: (?&number) | (?&boolean) | (?&strings) | (?&array) ) ) ) ^<\?php\s++return\s++(?&array)\s*+;$}ix REGEX; if (is_string($installedVersionsData) && Preg::isMatch($pattern, trim($installedVersionsData))) { \Composer\InstalledVersions::reload(eval('?>'.Preg::replace('{=>\s*+__DIR__\s*+\.\s*+([\'"])}', '=> '.var_export(dirname($path), true).' . $1', $installedVersionsData))); return true; } return false; } private function dumpToPhpCode(array $array = [], int $level = 0): string { $lines = "array(\n"; $level++; foreach ($array as $key => $value) { $lines .= str_repeat(' ', $level); $lines .= is_int($key) ? $key . ' => ' : var_export($key, true) . ' => '; if (is_array($value)) { if (!empty($value)) { $lines .= $this->dumpToPhpCode($value, $level); } else { $lines .= "array(),\n"; } } elseif ($key === 'install_path' && is_string($value)) { if ($this->filesystem->isAbsolutePath($value)) { $lines .= var_export($value, true) . ",\n"; } else { $lines .= "__DIR__ . " . var_export('/' . $value, true) . ",\n"; } } elseif (is_string($value)) { $lines .= var_export($value, true) . ",\n"; } elseif (is_bool($value)) { $lines .= ($value ? 'true' : 'false') . ",\n"; } elseif (is_null($value)) { $lines .= "null,\n"; } else { throw new \UnexpectedValueException('Unexpected type '.get_debug_type($value)); } } $lines .= str_repeat(' ', $level - 1) . ')' . ($level - 1 === 0 ? '' : ",\n"); return $lines; } private function generateInstalledVersions(InstallationManager $installationManager, array $installPaths, bool $devMode, string $repoDir): array { $devPackages = array_flip($this->devPackageNames); $packages = $this->getPackages(); if (null === $this->rootPackage) { throw new \LogicException('It should not be possible to dump packages if no root package is given'); } $packages[] = $rootPackage = $this->rootPackage; while ($rootPackage instanceof RootAliasPackage) { $rootPackage = $rootPackage->getAliasOf(); $packages[] = $rootPackage; } $versions = [ 'root' => $this->dumpRootPackage($rootPackage, $installPaths, $devMode, $repoDir, $devPackages), 'versions' => [], ]; foreach ($packages as $package) { if ($package instanceof AliasPackage) { continue; } $versions['versions'][$package->getName()] = $this->dumpInstalledPackage($package, $installPaths, $repoDir, $devPackages); } foreach ($packages as $package) { $isDevPackage = isset($devPackages[$package->getName()]); foreach ($package->getReplaces() as $replace) { if (PlatformRepository::isPlatformPackage($replace->getTarget())) { continue; } if (!isset($versions['versions'][$replace->getTarget()]['dev_requirement'])) { $versions['versions'][$replace->getTarget()]['dev_requirement'] = $isDevPackage; } elseif (!$isDevPackage) { $versions['versions'][$replace->getTarget()]['dev_requirement'] = false; } $replaced = $replace->getPrettyConstraint(); if ($replaced === 'self.version') { $replaced = $package->getPrettyVersion(); } if (!isset($versions['versions'][$replace->getTarget()]['replaced']) || !in_array($replaced, $versions['versions'][$replace->getTarget()]['replaced'], true)) { $versions['versions'][$replace->getTarget()]['replaced'][] = $replaced; } } foreach ($package->getProvides() as $provide) { if (PlatformRepository::isPlatformPackage($provide->getTarget())) { continue; } if (!isset($versions['versions'][$provide->getTarget()]['dev_requirement'])) { $versions['versions'][$provide->getTarget()]['dev_requirement'] = $isDevPackage; } elseif (!$isDevPackage) { $versions['versions'][$provide->getTarget()]['dev_requirement'] = false; } $provided = $provide->getPrettyConstraint(); if ($provided === 'self.version') { $provided = $package->getPrettyVersion(); } if (!isset($versions['versions'][$provide->getTarget()]['provided']) || !in_array($provided, $versions['versions'][$provide->getTarget()]['provided'], true)) { $versions['versions'][$provide->getTarget()]['provided'][] = $provided; } } } foreach ($packages as $package) { if (!$package instanceof AliasPackage) { continue; } $versions['versions'][$package->getName()]['aliases'][] = $package->getPrettyVersion(); if ($package instanceof RootPackageInterface) { $versions['root']['aliases'][] = $package->getPrettyVersion(); } } ksort($versions['versions']); ksort($versions); foreach ($versions['versions'] as $name => $version) { foreach (['aliases', 'replaced', 'provided'] as $key) { if (isset($versions['versions'][$name][$key])) { sort($versions['versions'][$name][$key], SORT_NATURAL); } } } return $versions; } private function dumpInstalledPackage(PackageInterface $package, array $installPaths, string $repoDir, array $devPackages): array { $reference = null; if ($package->getInstallationSource()) { $reference = $package->getInstallationSource() === 'source' ? $package->getSourceReference() : $package->getDistReference(); } if (null === $reference) { $reference = ($package->getSourceReference() ?: $package->getDistReference()) ?: null; } if ($package instanceof RootPackageInterface) { $to = $this->filesystem->normalizePath(realpath(Platform::getCwd())); $installPath = $this->filesystem->findShortestPath($repoDir, $to, true); } else { $installPath = $installPaths[$package->getName()]; } $data = [ 'pretty_version' => $package->getPrettyVersion(), 'version' => $package->getVersion(), 'reference' => $reference, 'type' => $package->getType(), 'install_path' => $installPath, 'aliases' => [], 'dev_requirement' => isset($devPackages[$package->getName()]), ]; return $data; } private function dumpRootPackage(RootPackageInterface $package, array $installPaths, bool $devMode, string $repoDir, array $devPackages) { $data = $this->dumpInstalledPackage($package, $installPaths, $repoDir, $devPackages); return [ 'name' => $package->getName(), 'pretty_version' => $data['pretty_version'], 'version' => $data['version'], 'reference' => $data['reference'], 'type' => $data['type'], 'install_path' => $data['install_path'], 'aliases' => $data['aliases'], 'dev' => $devMode, ]; } } getRepoName().' should be an array'); } $this->only = BasePackage::packageNamesToRegexp($options['only']); } if (isset($options['exclude'])) { if (!is_array($options['exclude'])) { throw new \InvalidArgumentException('"exclude" key for repository '.$repo->getRepoName().' should be an array'); } $this->exclude = BasePackage::packageNamesToRegexp($options['exclude']); } if ($this->exclude && $this->only) { throw new \InvalidArgumentException('Only one of "only" and "exclude" can be specified for repository '.$repo->getRepoName()); } if (isset($options['canonical'])) { if (!is_bool($options['canonical'])) { throw new \InvalidArgumentException('"canonical" key for repository '.$repo->getRepoName().' should be a boolean'); } $this->canonical = $options['canonical']; } $this->repo = $repo; } public function getRepoName(): string { return $this->repo->getRepoName(); } public function getRepository(): RepositoryInterface { return $this->repo; } public function hasPackage(PackageInterface $package): bool { return $this->repo->hasPackage($package); } public function findPackage($name, $constraint): ?BasePackage { if (!$this->isAllowed($name)) { return null; } return $this->repo->findPackage($name, $constraint); } public function findPackages($name, $constraint = null): array { if (!$this->isAllowed($name)) { return []; } return $this->repo->findPackages($name, $constraint); } public function loadPackages(array $packageNameMap, array $acceptableStabilities, array $stabilityFlags, array $alreadyLoaded = []): array { foreach ($packageNameMap as $name => $constraint) { if (!$this->isAllowed($name)) { unset($packageNameMap[$name]); } } if (!$packageNameMap) { return ['namesFound' => [], 'packages' => []]; } $result = $this->repo->loadPackages($packageNameMap, $acceptableStabilities, $stabilityFlags, $alreadyLoaded); if (!$this->canonical) { $result['namesFound'] = []; } return $result; } public function search(string $query, int $mode = 0, ?string $type = null): array { $result = []; foreach ($this->repo->search($query, $mode, $type) as $package) { if ($this->isAllowed($package['name'])) { $result[] = $package; } } return $result; } public function getPackages(): array { $result = []; foreach ($this->repo->getPackages() as $package) { if ($this->isAllowed($package->getName())) { $result[] = $package; } } return $result; } public function getProviders($packageName): array { $result = []; foreach ($this->repo->getProviders($packageName) as $name => $provider) { if ($this->isAllowed($provider['name'])) { $result[$name] = $provider; } } return $result; } public function count(): int { if ($this->repo->count() > 0) { return count($this->getPackages()); } return 0; } public function hasSecurityAdvisories(): bool { if (!$this->repo instanceof AdvisoryProviderInterface) { return false; } return $this->repo->hasSecurityAdvisories(); } public function getSecurityAdvisories(array $packageConstraintMap, bool $allowPartialAdvisories = false): array { if (!$this->repo instanceof AdvisoryProviderInterface) { return ['namesFound' => [], 'advisories' => []]; } foreach ($packageConstraintMap as $name => $constraint) { if (!$this->isAllowed($name)) { unset($packageConstraintMap[$name]); } } return $this->repo->getSecurityAdvisories($packageConstraintMap, $allowPartialAdvisories); } public function hasFilter(): bool { if (!$this->repo instanceof FilterListProviderInterface) { return false; } return $this->repo->hasFilter(); } public function getFilter(array $packageConstraintMap, array $configuredLists): array { if (!$this->repo instanceof FilterListProviderInterface) { return ['filter' => []]; } foreach ($packageConstraintMap as $name => $constraint) { if (!$this->isAllowed($name)) { unset($packageConstraintMap[$name]); } } return $this->repo->getFilter($packageConstraintMap, $configuredLists); } public function getFilterLists(): array { if (!$this->repo instanceof FilterListProviderInterface) { return []; } return $this->repo->getFilterLists(); } private function isAllowed(string $name): bool { if (!$this->only && !$this->exclude) { return true; } if ($this->only) { return Preg::isMatch($this->only, $name); } if ($this->exclude === null) { return true; } return !Preg::isMatch($this->exclude, $name); } } count() === 0; } } file->exists(); } } parseConstraints($constraint); } $matches = []; foreach ($this->getRepositories() as $repo) { foreach ($repo->getPackages() as $candidate) { if ($name === $candidate->getName()) { if (null === $constraint || $constraint->matches(new Constraint('==', $candidate->getVersion()))) { $matches[] = $candidate; } continue; } foreach (array_merge($candidate->getProvides(), $candidate->getReplaces()) as $link) { if ( $name === $link->getTarget() && ($constraint === null || $constraint->matches($link->getConstraint())) ) { $matches[] = $candidate; continue 2; } } } } return $matches; } public function getDependents($needle, ?ConstraintInterface $constraint = null, bool $invert = false, bool $recurse = true, ?array $packagesFound = null): array { $needles = array_map('strtolower', (array) $needle); $results = []; if (null === $packagesFound) { $packagesFound = $needles; } $rootPackage = null; foreach ($this->getPackages() as $package) { if ($package instanceof RootPackageInterface) { $rootPackage = $package; break; } } foreach ($this->getPackages() as $package) { $links = $package->getRequires(); $packagesInTree = $packagesFound; if (!$invert) { $links += $package->getReplaces(); foreach ($package->getReplaces() as $link) { foreach ($needles as $needle) { if ($link->getSource() === $needle) { if ($constraint === null || ($link->getConstraint()->matches($constraint) === true)) { if (in_array($link->getTarget(), $packagesInTree)) { $results[] = [$package, $link, false]; continue; } $packagesInTree[] = $link->getTarget(); $dependents = $recurse ? $this->getDependents($link->getTarget(), null, false, true, $packagesInTree) : []; $results[] = [$package, $link, $dependents]; $needles[] = $link->getTarget(); } } } } unset($needle); } if ($package instanceof RootPackageInterface) { $links += $package->getDevRequires(); } foreach ($links as $link) { foreach ($needles as $needle) { if ($link->getTarget() === $needle) { if ($constraint === null || ($link->getConstraint()->matches($constraint) === !$invert)) { if (in_array($link->getSource(), $packagesInTree)) { $results[] = [$package, $link, false]; continue; } $packagesInTree[] = $link->getSource(); $dependents = $recurse ? $this->getDependents($link->getSource(), null, false, true, $packagesInTree) : []; $results[] = [$package, $link, $dependents]; } } } } if ($invert && in_array($package->getName(), $needles, true)) { foreach ($package->getConflicts() as $link) { foreach ($this->findPackages($link->getTarget()) as $pkg) { $version = new Constraint('=', $pkg->getVersion()); if ($link->getConstraint()->matches($version) === $invert) { $results[] = [$package, $link, false]; } } } } foreach ($package->getConflicts() as $link) { if (in_array($link->getTarget(), $needles, true)) { foreach ($this->findPackages($link->getTarget()) as $pkg) { $version = new Constraint('=', $pkg->getVersion()); if ($link->getConstraint()->matches($version) === $invert) { $results[] = [$package, $link, false]; } } } } if ($invert && $constraint && in_array($package->getName(), $needles, true) && $constraint->matches(new Constraint('=', $package->getVersion()))) { foreach ($package->getRequires() as $link) { if (PlatformRepository::isPlatformPackage($link->getTarget())) { if ($this->findPackage($link->getTarget(), $link->getConstraint())) { continue; } $platformPkg = $this->findPackage($link->getTarget(), '*'); $description = $platformPkg ? 'but '.$platformPkg->getPrettyVersion().' is installed' : 'but it is missing'; $results[] = [$package, new Link($package->getName(), $link->getTarget(), new MatchAllConstraint, Link::TYPE_REQUIRE, $link->getPrettyConstraint().' '.$description), false]; continue; } foreach ($this->getPackages() as $pkg) { if (!in_array($link->getTarget(), $pkg->getNames())) { continue; } $version = new Constraint('=', $pkg->getVersion()); if ($link->getTarget() !== $pkg->getName()) { foreach (array_merge($pkg->getReplaces(), $pkg->getProvides()) as $prov) { if ($link->getTarget() === $prov->getTarget()) { $version = $prov->getConstraint(); break; } } } if (!$link->getConstraint()->matches($version)) { if ($rootPackage) { foreach (array_merge($rootPackage->getRequires(), $rootPackage->getDevRequires()) as $rootReq) { if (in_array($rootReq->getTarget(), $pkg->getNames()) && !$rootReq->getConstraint()->matches($link->getConstraint())) { $results[] = [$package, $link, false]; $results[] = [$rootPackage, $rootReq, false]; continue 3; } } $results[] = [$package, $link, false]; $results[] = [$rootPackage, new Link($rootPackage->getName(), $link->getTarget(), new MatchAllConstraint, Link::TYPE_DOES_NOT_REQUIRE, 'but ' . $pkg->getPrettyVersion() . ' is installed'), false]; } else { $results[] = [$package, $link, false]; } } continue 2; } } } } ksort($results); return $results; } public function getRepoName(): string { return 'installed repo ('.implode(', ', array_map(static function ($repo): string { return $repo->getRepoName(); }, $this->getRepositories())).')'; } public function addRepository(RepositoryInterface $repository): void { if ( $repository instanceof LockArrayRepository || $repository instanceof InstalledRepositoryInterface || $repository instanceof RootPackageRepository || $repository instanceof PlatformRepository ) { parent::addRepository($repository); return; } throw new \LogicException('An InstalledRepository can not contain a repository of type '.get_class($repository).' ('.$repository->getRepoName().')'); } } config = $config['package']; if (!is_numeric(key($this->config))) { $this->config = [$this->config]; } $this->securityAdvisories = $config['security-advisories'] ?? []; $this->filter = $config['filter'] ?? []; } protected function initialize(): void { parent::initialize(); $loader = new ValidatingArrayLoader(new ArrayLoader(null, true), true); foreach ($this->config as $package) { try { $package = $loader->load($package); } catch (\Exception $e) { throw new InvalidRepositoryException('A repository of type "package" contains an invalid package definition: '.$e->getMessage()."\n\nInvalid package definition:\n".json_encode($package)); } $this->addPackage($package); } } public function getRepoName(): string { return Preg::replace('{^array }', 'package ', parent::getRepoName()); } public function hasSecurityAdvisories(): bool { return count($this->securityAdvisories) > 0; } public function getSecurityAdvisories(array $packageConstraintMap, bool $allowPartialAdvisories = false): array { $parser = new VersionParser(); $advisories = []; foreach ($this->securityAdvisories as $packageName => $packageAdvisories) { if (isset($packageConstraintMap[$packageName])) { $advisories[$packageName] = array_values(array_filter(array_map(function (array $data) use ($packageName, $allowPartialAdvisories, $packageConstraintMap, $parser) { $advisory = PartialSecurityAdvisory::create($packageName, $data, $parser); if (!$allowPartialAdvisories && !$advisory instanceof SecurityAdvisory) { throw new \RuntimeException('Advisory for '.$packageName.' could not be loaded as a full advisory from '.$this->getRepoName() . PHP_EOL . var_export($data, true)); } if (!$advisory->affectedVersions->matches($packageConstraintMap[$packageName])) { return null; } return $advisory; }, $packageAdvisories))); } } return ['advisories' => array_filter($advisories, static function ($adv): bool { return \count($adv) > 0; }), 'namesFound' => array_keys($advisories)]; } public function hasFilter(): bool { return count($this->filter) > 0; } public function getFilter(array $packageConstraintMap, array $configuredLists): array { $parser = new VersionParser(); $filter = []; foreach ($this->filter as $listName => $listEntries) { foreach ($listEntries as $data) { $filterEntry = FilterListEntry::create($listName, $data, $parser); if (isset($packageConstraintMap[$filterEntry->packageName])) { $filter[$listName][] = $filterEntry; } } } return ['filter' => $filter]; } public function getFilterLists(): array { return array_keys($this->filter); } } loader = new ArrayLoader(null, true); $this->url = Platform::expandPath($repoConfig['url']); $this->process = $process ?? new ProcessExecutor($io); $this->versionGuesser = new VersionGuesser($config, $this->process, new VersionParser(), $io); $this->repoConfig = $repoConfig; $this->options = $repoConfig['options'] ?? []; if (!isset($this->options['relative'])) { $filesystem = new Filesystem(); $this->options['relative'] = !$filesystem->isAbsolutePath($this->url); } parent::__construct(); } public function getRepoName(): string { return 'path repo ('.Url::sanitize($this->repoConfig['url']).')'; } public function getRepoConfig(): array { return $this->repoConfig; } protected function initialize(): void { parent::initialize(); $urlMatches = $this->getUrlMatches(); if (empty($urlMatches)) { if (Preg::isMatch('{[*{}]}', $this->url)) { $url = $this->url; while (Preg::isMatch('{[*{}]}', $url)) { $url = dirname($url); } if (is_dir($url)) { return; } } throw new \RuntimeException('The `url` supplied for the path (' . Url::sanitize($this->url) . ') repository does not exist'); } foreach ($urlMatches as $url) { $path = realpath($url) . DIRECTORY_SEPARATOR; $composerFilePath = $path.'composer.json'; if (!file_exists($composerFilePath)) { continue; } $json = file_get_contents($composerFilePath); $package = JsonFile::parseJson($json, $composerFilePath); $package['dist'] = [ 'type' => 'path', 'url' => $url, ]; $reference = $this->options['reference'] ?? 'auto'; if ('none' === $reference) { $package['dist']['reference'] = null; } elseif ('config' === $reference || 'auto' === $reference) { $package['dist']['reference'] = hash('sha1', $json . serialize($this->options)); } $package['transport-options'] = array_intersect_key($this->options, ['symlink' => true, 'relative' => true]); if (isset($package['name'], $this->options['versions'][$package['name']])) { $package['version'] = $this->options['versions'][$package['name']]; } if (!isset($package['version']) && ($rootVersion = Platform::getEnv('COMPOSER_ROOT_VERSION'))) { if ( 0 === $this->process->execute(['git', 'rev-parse', 'HEAD'], $ref1, $path) && 0 === $this->process->execute(['git', 'rev-parse', 'HEAD'], $ref2) && $ref1 === $ref2 ) { $package['version'] = $this->versionGuesser->getRootVersionFromEnv(); } } $output = ''; $command = GitUtil::buildRevListCommand($this->process, array_merge(['-n1', '--format=%H', 'HEAD'], GitUtil::getNoShowSignatureFlags($this->process))); if ('auto' === $reference && is_dir($path . DIRECTORY_SEPARATOR . '.git') && 0 === $this->process->execute($command, $output, $path)) { $package['dist']['reference'] = trim(GitUtil::parseRevListOutput($output, $this->process)); } if (!isset($package['version'])) { $versionData = $this->versionGuesser->guessVersion($package, $path); if (is_array($versionData) && $versionData['pretty_version']) { if (!empty($versionData['feature_pretty_version'])) { $package['version'] = $versionData['feature_pretty_version']; $this->addPackage($this->loader->load($package)); } $package['version'] = $versionData['pretty_version']; } else { $package['version'] = 'dev-main'; } } try { $this->addPackage($this->loader->load($package)); } catch (\Exception $e) { throw new \RuntimeException('Failed loading the package in '.$composerFilePath, 0, $e); } } } private function getUrlMatches(): array { $flags = GLOB_MARK | GLOB_ONLYDIR; if (defined('GLOB_BRACE')) { $flags |= GLOB_BRACE; } elseif (strpos($this->url, '{') !== false || strpos($this->url, '}') !== false) { throw new \RuntimeException('The operating system does not support GLOB_BRACE which is required for the url '. Url::sanitize($this->url)); } return array_map(static function ($val): string { return rtrim(str_replace(DIRECTORY_SEPARATOR, '/', $val), '/'); }, glob($this->url, $flags)); } } runtime = $runtime ?: new Runtime(); $this->hhvmDetector = $hhvmDetector ?: new HhvmDetector(); foreach ($overrides as $name => $version) { if (!is_string($version) && false !== $version) { throw new \UnexpectedValueException('config.platform.'.$name.' should be a string or false, but got '.get_debug_type($version).' '.var_export($version, true)); } if ($name === 'php' && $version === false) { throw new \UnexpectedValueException('config.platform.'.$name.' cannot be set to false as you cannot disable php entirely.'); } $this->overrides[strtolower($name)] = ['name' => $name, 'version' => $version]; } parent::__construct($packages); } public function getRepoName(): string { return 'platform repo'; } public function isPlatformPackageDisabled(string $name): bool { return isset($this->disabledPackages[$name]); } public function getDisabledPackages(): array { return $this->disabledPackages; } protected function initialize(): void { parent::initialize(); $libraries = []; $this->versionParser = new VersionParser(); foreach ($this->overrides as $override) { if (!self::isPlatformPackage($override['name'])) { throw new \InvalidArgumentException('Invalid platform package name in config.platform: '.$override['name']); } if ($override['version'] !== false) { $this->addOverriddenPackage($override); } } $prettyVersion = Composer::getVersion(); $version = $this->versionParser->normalize($prettyVersion); $composer = new CompletePackage('composer', $version, $prettyVersion); $composer->setDescription('Composer package'); $this->addPackage($composer); $prettyVersion = PluginInterface::PLUGIN_API_VERSION; $version = $this->versionParser->normalize($prettyVersion); $composerPluginApi = new CompletePackage('composer-plugin-api', $version, $prettyVersion); $composerPluginApi->setDescription('The Composer Plugin API'); $this->addPackage($composerPluginApi); $prettyVersion = Composer::RUNTIME_API_VERSION; $version = $this->versionParser->normalize($prettyVersion); $composerRuntimeApi = new CompletePackage('composer-runtime-api', $version, $prettyVersion); $composerRuntimeApi->setDescription('The Composer Runtime API'); $this->addPackage($composerRuntimeApi); try { $prettyVersion = $this->runtime->getConstant('PHP_VERSION'); $version = $this->versionParser->normalize($prettyVersion); } catch (\UnexpectedValueException $e) { $prettyVersion = Preg::replace('#^([^~+-]+).*$#', '$1', $this->runtime->getConstant('PHP_VERSION')); $version = $this->versionParser->normalize($prettyVersion); } $php = new CompletePackage('php', $version, $prettyVersion); $php->setDescription('The PHP interpreter'); $this->addPackage($php); if ($this->runtime->getConstant('PHP_DEBUG')) { $phpdebug = new CompletePackage('php-debug', $version, $prettyVersion); $phpdebug->setDescription('The PHP interpreter, with debugging symbols'); $this->addPackage($phpdebug); } if ($this->runtime->hasConstant('PHP_ZTS') && $this->runtime->getConstant('PHP_ZTS')) { $phpzts = new CompletePackage('php-zts', $version, $prettyVersion); $phpzts->setDescription('The PHP interpreter, with Zend Thread Safety'); $this->addPackage($phpzts); } if ($this->runtime->getConstant('PHP_INT_SIZE') === 8) { $php64 = new CompletePackage('php-64bit', $version, $prettyVersion); $php64->setDescription('The PHP interpreter, 64bit'); $this->addPackage($php64); } if ($this->runtime->hasConstant('AF_INET6') || Silencer::call([$this->runtime, 'invoke'], 'inet_pton', ['::']) !== false) { $phpIpv6 = new CompletePackage('php-ipv6', $version, $prettyVersion); $phpIpv6->setDescription('The PHP interpreter, with IPv6 support'); $this->addPackage($phpIpv6); } $loadedExtensions = $this->runtime->getExtensions(); foreach ($loadedExtensions as $name) { if (in_array($name, ['standard', 'Core'])) { continue; } $this->addExtension($name, $this->runtime->getExtensionVersion($name)); } if (!in_array('xdebug', $loadedExtensions, true) && ($prettyVersion = XdebugHandler::getSkippedVersion())) { $this->addExtension('xdebug', $prettyVersion); } foreach ($loadedExtensions as $name) { switch ($name) { case 'amqp': $info = $this->runtime->getExtensionInfo($name); if (Preg::isMatch('/^librabbitmq version => (?.+)$/im', $info, $librabbitmqMatches)) { $this->addLibrary($libraries, $name.'-librabbitmq', $librabbitmqMatches['version'], 'AMQP librabbitmq version'); } if (Preg::isMatchStrictGroups('/^AMQP protocol version => (?.+)$/im', $info, $protocolMatches)) { $this->addLibrary($libraries, $name.'-protocol', str_replace('-', '.', $protocolMatches['version']), 'AMQP protocol version'); } break; case 'bz2': $info = $this->runtime->getExtensionInfo($name); if (Preg::isMatch('/^BZip2 Version => (?.*),/im', $info, $matches)) { $this->addLibrary($libraries, $name, $matches['version']); } break; case 'curl': $curlVersion = $this->runtime->invoke('curl_version'); $this->addLibrary($libraries, $name, $curlVersion['version']); $info = $this->runtime->getExtensionInfo($name); if (Preg::isMatchStrictGroups('{^SSL Version => (?[^/]+)/(?.+)$}im', $info, $sslMatches)) { $library = strtolower($sslMatches['library']); if ($library === 'openssl') { $parsedVersion = Version::parseOpenssl($sslMatches['version'], $isFips); $this->addLibrary($libraries, $name.'-openssl'.($isFips ? '-fips' : ''), $parsedVersion, 'curl OpenSSL version ('.$parsedVersion.')', [], $isFips ? ['curl-openssl'] : []); } else { if (str_starts_with($library, '(securetransport)') && Preg::isMatch('{^\(securetransport\) ([a-z0-9]+)}', $library, $securetransportMatches)) { $shortlib = 'securetransport'; $sslLib = 'curl-'.$securetransportMatches[1]; } else { $shortlib = $library; $sslLib = 'curl-openssl'; } $this->addLibrary($libraries, $name.'-'.$shortlib, $sslMatches['version'], 'curl '.$library.' version ('.$sslMatches['version'].')', [$sslLib]); } } if (Preg::isMatchStrictGroups('{^libSSH Version => (?[^/]+)/(?.+?)(?:/.*)?$}im', $info, $sshMatches)) { $this->addLibrary($libraries, $name.'-'.strtolower($sshMatches['library']), $sshMatches['version'], 'curl '.$sshMatches['library'].' version'); } if (Preg::isMatchStrictGroups('{^ZLib Version => (?.+)$}im', $info, $zlibMatches)) { $this->addLibrary($libraries, $name.'-zlib', $zlibMatches['version'], 'curl zlib version'); } break; case 'date': $info = $this->runtime->getExtensionInfo($name); if (Preg::isMatchStrictGroups('/^timelib version => (?.+)$/im', $info, $timelibMatches)) { $this->addLibrary($libraries, $name.'-timelib', $timelibMatches['version'], 'date timelib version'); } if (Preg::isMatchStrictGroups('/^Timezone Database => (?internal|external)$/im', $info, $zoneinfoSourceMatches)) { $external = $zoneinfoSourceMatches['source'] === 'external'; if (Preg::isMatchStrictGroups('/^"Olson" Timezone Database Version => (?.+?)(?:\.system)?$/im', $info, $zoneinfoMatches)) { if ($external && in_array('timezonedb', $loadedExtensions, true)) { $this->addLibrary($libraries, 'timezonedb-zoneinfo', $zoneinfoMatches['version'], 'zoneinfo ("Olson") database for date (replaced by timezonedb)', [$name.'-zoneinfo']); } else { $this->addLibrary($libraries, $name.'-zoneinfo', $zoneinfoMatches['version'], 'zoneinfo ("Olson") database for date'); } } } break; case 'fileinfo': $info = $this->runtime->getExtensionInfo($name); if (Preg::isMatch('/^libmagic => (?.+)$/im', $info, $magicMatches)) { $this->addLibrary($libraries, $name.'-libmagic', $magicMatches['version'], 'fileinfo libmagic version'); } break; case 'gd': $this->addLibrary($libraries, $name, $this->runtime->getConstant('GD_VERSION')); $info = $this->runtime->getExtensionInfo($name); if (Preg::isMatchStrictGroups('/^libJPEG Version => (?.+?)(?: compatible)?$/im', $info, $libjpegMatches)) { $this->addLibrary($libraries, $name.'-libjpeg', Version::parseLibjpeg($libjpegMatches['version']), 'libjpeg version for gd'); } if (Preg::isMatchStrictGroups('/^libPNG Version => (?.+)$/im', $info, $libpngMatches)) { $this->addLibrary($libraries, $name.'-libpng', $libpngMatches['version'], 'libpng version for gd'); } if (Preg::isMatchStrictGroups('/^FreeType Version => (?.+)$/im', $info, $freetypeMatches)) { $this->addLibrary($libraries, $name.'-freetype', $freetypeMatches['version'], 'freetype version for gd'); } if (Preg::isMatchStrictGroups('/^libXpm Version => (?\d+)$/im', $info, $libxpmMatches)) { $this->addLibrary($libraries, $name.'-libxpm', Version::convertLibxpmVersionId((int) $libxpmMatches['versionId']), 'libxpm version for gd'); } break; case 'gmp': $this->addLibrary($libraries, $name, $this->runtime->getConstant('GMP_VERSION')); break; case 'iconv': $this->addLibrary($libraries, $name, $this->runtime->getConstant('ICONV_VERSION')); break; case 'intl': $info = $this->runtime->getExtensionInfo($name); $description = 'The ICU unicode and globalization support library'; if ($this->runtime->hasConstant('INTL_ICU_VERSION')) { $this->addLibrary($libraries, 'icu', $this->runtime->getConstant('INTL_ICU_VERSION'), $description); } elseif (Preg::isMatch('/^ICU version => (?.+)$/im', $info, $matches)) { $this->addLibrary($libraries, 'icu', $matches['version'], $description); } if (Preg::isMatchStrictGroups('/^ICU TZData version => (?.*)$/im', $info, $zoneinfoMatches) && null !== ($version = Version::parseZoneinfoVersion($zoneinfoMatches['version']))) { $this->addLibrary($libraries, 'icu-zoneinfo', $version, 'zoneinfo ("Olson") database for icu'); } if ($this->runtime->hasClass('ResourceBundle')) { $resourceBundle = $this->runtime->invoke(['ResourceBundle', 'create'], ['root', 'ICUDATA', false]); if ($resourceBundle !== null) { $this->addLibrary($libraries, 'icu-cldr', $resourceBundle->get('Version'), 'ICU CLDR project version'); } } if ($this->runtime->hasClass('IntlChar')) { $this->addLibrary($libraries, 'icu-unicode', implode('.', array_slice($this->runtime->invoke(['IntlChar', 'getUnicodeVersion']), 0, 3)), 'ICU unicode version'); } break; case 'imagick': $imageMagickVersion = $this->runtime->construct('Imagick')->getVersion(); if (Preg::isMatch('/^ImageMagick (?[\d.]+)(?:-(?\d+))?/', $imageMagickVersion['versionString'], $matches)) { $version = $matches['version']; if (isset($matches['patch'])) { $version .= '.'.$matches['patch']; } $this->addLibrary($libraries, $name.'-imagemagick', $version, null, ['imagick']); } break; case 'ldap': $info = $this->runtime->getExtensionInfo($name); if (Preg::isMatchStrictGroups('/^Vendor Version => (?\d+)$/im', $info, $matches) && Preg::isMatchStrictGroups('/^Vendor Name => (?.+)$/im', $info, $vendorMatches)) { $this->addLibrary($libraries, $name.'-'.strtolower($vendorMatches['vendor']), Version::convertOpenldapVersionId((int) $matches['versionId']), $vendorMatches['vendor'].' version of ldap'); } break; case 'libxml': $libxmlProvides = array_map(static function ($extension): string { return $extension . '-libxml'; }, array_intersect($loadedExtensions, ['dom', 'simplexml', 'xml', 'xmlreader', 'xmlwriter'])); $this->addLibrary($libraries, $name, $this->runtime->getConstant('LIBXML_DOTTED_VERSION'), 'libxml library version', [], $libxmlProvides); break; case 'mbstring': $info = $this->runtime->getExtensionInfo($name); if (Preg::isMatch('/^libmbfl version => (?.+)$/im', $info, $libmbflMatches)) { $this->addLibrary($libraries, $name.'-libmbfl', $libmbflMatches['version'], 'mbstring libmbfl version'); } if ($this->runtime->hasConstant('MB_ONIGURUMA_VERSION')) { $this->addLibrary($libraries, $name.'-oniguruma', $this->runtime->getConstant('MB_ONIGURUMA_VERSION'), 'mbstring oniguruma version'); } elseif (Preg::isMatch('/^(?:oniguruma|Multibyte regex \(oniguruma\)) version => (?.+)$/im', $info, $onigurumaMatches)) { $this->addLibrary($libraries, $name.'-oniguruma', $onigurumaMatches['version'], 'mbstring oniguruma version'); } break; case 'memcached': $info = $this->runtime->getExtensionInfo($name); if (Preg::isMatch('/^libmemcached version => (?.+)$/im', $info, $matches)) { $this->addLibrary($libraries, $name.'-libmemcached', $matches['version'], 'libmemcached version'); } break; case 'openssl': if (Preg::isMatchStrictGroups('{^(?:OpenSSL|LibreSSL)?\s*(?\S+)}i', $this->runtime->getConstant('OPENSSL_VERSION_TEXT'), $matches)) { $parsedVersion = Version::parseOpenssl($matches['version'], $isFips); $this->addLibrary($libraries, $name.($isFips ? '-fips' : ''), $parsedVersion, $this->runtime->getConstant('OPENSSL_VERSION_TEXT'), [], $isFips ? [$name] : []); } break; case 'pcre': $this->addLibrary($libraries, $name, Preg::replace('{^(\S+).*}', '$1', $this->runtime->getConstant('PCRE_VERSION'))); $info = $this->runtime->getExtensionInfo($name); if (Preg::isMatchStrictGroups('/^PCRE Unicode Version => (?.+)$/im', $info, $pcreUnicodeMatches)) { $this->addLibrary($libraries, $name.'-unicode', $pcreUnicodeMatches['version'], 'PCRE Unicode version support'); } break; case 'mysqlnd': case 'pdo_mysql': $info = $this->runtime->getExtensionInfo($name); if (Preg::isMatchStrictGroups('/^(?:Client API version|Version) => mysqlnd (?.+?) /mi', $info, $matches)) { $this->addLibrary($libraries, $name.'-mysqlnd', $matches['version'], 'mysqlnd library version for '.$name); } break; case 'mongodb': $info = $this->runtime->getExtensionInfo($name); if (Preg::isMatchStrictGroups('/^libmongoc bundled version => (?.+)$/im', $info, $libmongocMatches)) { $this->addLibrary($libraries, $name.'-libmongoc', $libmongocMatches['version'], 'libmongoc version of mongodb'); } if (Preg::isMatchStrictGroups('/^libbson bundled version => (?.+)$/im', $info, $libbsonMatches)) { $this->addLibrary($libraries, $name.'-libbson', $libbsonMatches['version'], 'libbson version of mongodb'); } break; case 'pgsql': if ($this->runtime->hasConstant('PGSQL_LIBPQ_VERSION')) { $this->addLibrary($libraries, 'pgsql-libpq', $this->runtime->getConstant('PGSQL_LIBPQ_VERSION'), 'libpq for pgsql'); break; } case 'pdo_pgsql': $info = $this->runtime->getExtensionInfo($name); if (Preg::isMatch('/^PostgreSQL\(libpq\) Version => (?.*)$/im', $info, $matches)) { $this->addLibrary($libraries, $name.'-libpq', $matches['version'], 'libpq for '.$name); } break; case 'pq': $info = $this->runtime->getExtensionInfo($name); if (Preg::isMatch('/^libpq => (?.+) => (?.+)$/im', $info, $matches)) { $this->addLibrary($libraries, $name.'-libpq', $matches['linked'], 'libpq for '.$name); } break; case 'rdkafka': if ($this->runtime->hasConstant('RD_KAFKA_VERSION')) { $libRdKafkaVersionInt = $this->runtime->getConstant('RD_KAFKA_VERSION'); $this->addLibrary($libraries, $name.'-librdkafka', sprintf('%d.%d.%d', ($libRdKafkaVersionInt & 0x7F000000) >> 24, ($libRdKafkaVersionInt & 0x00FF0000) >> 16, ($libRdKafkaVersionInt & 0x0000FF00) >> 8), 'librdkafka for '.$name); } break; case 'libsodium': case 'sodium': if ($this->runtime->hasConstant('SODIUM_LIBRARY_VERSION')) { $this->addLibrary($libraries, 'libsodium', $this->runtime->getConstant('SODIUM_LIBRARY_VERSION')); $this->addLibrary($libraries, 'libsodium', $this->runtime->getConstant('SODIUM_LIBRARY_VERSION')); } break; case 'sqlite3': case 'pdo_sqlite': $info = $this->runtime->getExtensionInfo($name); if (Preg::isMatch('/^SQLite Library => (?.+)$/im', $info, $matches)) { $this->addLibrary($libraries, $name.'-sqlite', $matches['version']); } break; case 'ssh2': $info = $this->runtime->getExtensionInfo($name); if (Preg::isMatch('/^libssh2 version => (?.+)$/im', $info, $matches)) { $this->addLibrary($libraries, $name.'-libssh2', $matches['version']); } break; case 'xsl': $this->addLibrary($libraries, 'libxslt', $this->runtime->getConstant('LIBXSLT_DOTTED_VERSION'), null, ['xsl']); $info = $this->runtime->getExtensionInfo('xsl'); if (Preg::isMatch('/^libxslt compiled against libxml Version => (?.+)$/im', $info, $matches)) { $this->addLibrary($libraries, 'libxslt-libxml', $matches['version'], 'libxml version libxslt is compiled against'); } break; case 'yaml': $info = $this->runtime->getExtensionInfo('yaml'); if (Preg::isMatch('/^LibYAML Version => (?.+)$/im', $info, $matches)) { $this->addLibrary($libraries, $name.'-libyaml', $matches['version'], 'libyaml version of yaml'); } break; case 'zip': if ($this->runtime->hasConstant('LIBZIP_VERSION', 'ZipArchive')) { $this->addLibrary($libraries, $name.'-libzip', $this->runtime->getConstant('LIBZIP_VERSION', 'ZipArchive'), null, ['zip']); } break; case 'zlib': if ($this->runtime->hasConstant('ZLIB_VERSION')) { $this->addLibrary($libraries, $name, $this->runtime->getConstant('ZLIB_VERSION')); } elseif (Preg::isMatch('/^Linked Version => (?.+)$/im', $this->runtime->getExtensionInfo($name), $matches)) { $this->addLibrary($libraries, $name, $matches['version']); } break; default: break; } } $hhvmVersion = $this->hhvmDetector->getVersion(); if ($hhvmVersion) { try { $prettyVersion = $hhvmVersion; $version = $this->versionParser->normalize($prettyVersion); } catch (\UnexpectedValueException $e) { $prettyVersion = Preg::replace('#^([^~+-]+).*$#', '$1', $hhvmVersion); $version = $this->versionParser->normalize($prettyVersion); } $hhvm = new CompletePackage('hhvm', $version, $prettyVersion); $hhvm->setDescription('The HHVM Runtime (64bit)'); $this->addPackage($hhvm); } } public function addPackage(PackageInterface $package): void { if (!$package instanceof CompletePackage) { throw new \UnexpectedValueException('Expected CompletePackage but got '.get_class($package)); } if (isset($this->overrides[$package->getName()])) { if ($this->overrides[$package->getName()]['version'] === false) { $this->addDisabledPackage($package); return; } $overrider = $this->findPackage($package->getName(), '*'); if ($package->getVersion() === $overrider->getVersion()) { $actualText = 'same as actual'; } else { $actualText = 'actual: '.$package->getPrettyVersion(); } if ($overrider instanceof CompletePackageInterface) { $overrider->setDescription($overrider->getDescription().', '.$actualText); } return; } if (isset($this->overrides['php']) && 0 === strpos($package->getName(), 'php-')) { $overrider = $this->addOverriddenPackage($this->overrides['php'], $package->getPrettyName()); if ($package->getVersion() === $overrider->getVersion()) { $actualText = 'same as actual'; } else { $actualText = 'actual: '.$package->getPrettyVersion(); } $overrider->setDescription($overrider->getDescription().', '.$actualText); return; } parent::addPackage($package); } private function addOverriddenPackage(array $override, ?string $name = null): CompletePackage { $version = $this->versionParser->normalize($override['version']); $package = new CompletePackage($name ?: $override['name'], $version, $override['version']); $package->setDescription('Package overridden via config.platform'); $package->setExtra(['config.platform' => true]); parent::addPackage($package); if ($package->getName() === 'php') { self::$lastSeenPlatformPhp = implode('.', array_slice(explode('.', $package->getVersion()), 0, 3)); } return $package; } private function addDisabledPackage(CompletePackage $package): void { $package->setDescription($package->getDescription().'. Package disabled via config.platform'); $package->setExtra(['config.platform' => true]); $this->disabledPackages[$package->getName()] = $package; } private function addExtension(string $name, string $prettyVersion): void { $extraDescription = null; try { $version = $this->versionParser->normalize($prettyVersion); } catch (\UnexpectedValueException $e) { $extraDescription = ' (actual version: '.$prettyVersion.')'; if (Preg::isMatchStrictGroups('{^(\d+\.\d+\.\d+(?:\.\d+)?)}', $prettyVersion, $match)) { $prettyVersion = $match[1]; } else { $prettyVersion = '0'; } $version = $this->versionParser->normalize($prettyVersion); } $packageName = $this->buildPackageName($name); $ext = new CompletePackage($packageName, $version, $prettyVersion); $ext->setDescription('The '.$name.' PHP extension'.$extraDescription); $ext->setType('php-ext'); if ($name === 'uuid') { $ext->setReplaces([ 'lib-uuid' => new Link('ext-uuid', 'lib-uuid', new Constraint('=', $version), Link::TYPE_REPLACE, $ext->getPrettyVersion()), ]); } $this->addPackage($ext); } private function buildPackageName(string $name): string { return 'ext-' . str_replace(' ', '-', strtolower($name)); } private function addLibrary(array &$libraries, string $name, ?string $prettyVersion, ?string $description = null, array $replaces = [], array $provides = []): void { if (null === $prettyVersion) { return; } try { $version = $this->versionParser->normalize($prettyVersion); } catch (\UnexpectedValueException $e) { return; } if (isset($libraries['lib-'.$name])) { return; } $libraries['lib-'.$name] = true; if ($description === null) { $description = 'The '.$name.' library'; } $lib = new CompletePackage('lib-'.$name, $version, $prettyVersion); $lib->setDescription($description); $replaceLinks = []; foreach ($replaces as $replace) { $replace = strtolower($replace); $replaceLinks[$replace] = new Link('lib-'.$name, 'lib-'.$replace, new Constraint('=', $version), Link::TYPE_REPLACE, $lib->getPrettyVersion()); } $provideLinks = []; foreach ($provides as $provide) { $provide = strtolower($provide); $provideLinks[$provide] = new Link('lib-'.$name, 'lib-'.$provide, new Constraint('=', $version), Link::TYPE_PROVIDE, $lib->getPrettyVersion()); } $lib->setReplaces($replaceLinks); $lib->setProvides($provideLinks); $this->addPackage($lib); } public static function isPlatformPackage(string $name): bool { static $cache = []; if (isset($cache[$name])) { return $cache[$name]; } return $cache[$name] = Preg::isMatch(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name); } public static function getPlatformPhpVersion(): ?string { return self::$lastSeenPlatformPhp; } public function search(string $query, int $mode = 0, ?string $type = null): array { if ($mode === self::SEARCH_VENDOR) { return []; } return parent::search($query, $mode, $type); } } 'composer', 'url' => $repository]; } elseif ("json" === pathinfo($repository, PATHINFO_EXTENSION)) { $json = new JsonFile($repository, Factory::createHttpDownloader($io, $config)); $data = $json->read(); if (!empty($data['packages']) || !empty($data['includes']) || !empty($data['provider-includes'])) { $repoConfig = ['type' => 'composer', 'url' => 'file://' . strtr(realpath($repository), '\\', '/')]; } elseif ($allowFilesystem) { $repoConfig = ['type' => 'filesystem', 'json' => $json]; } else { throw new \InvalidArgumentException("Invalid repository URL ($repository) given. This file does not contain a valid composer repository."); } } elseif (strpos($repository, '{') === 0) { $repoConfig = JsonFile::parseJson($repository); } else { throw new \InvalidArgumentException("Invalid repository url (".Url::sanitize($repository).") given. Has to be a .json file, an http url or a JSON object."); } return $repoConfig; } public static function fromString(IOInterface $io, Config $config, string $repository, bool $allowFilesystem = false, ?RepositoryManager $rm = null): RepositoryInterface { $repoConfig = static::configFromString($io, $config, $repository, $allowFilesystem); return static::createRepo($io, $config, $repoConfig, $rm); } public static function createRepo(IOInterface $io, Config $config, array $repoConfig, ?RepositoryManager $rm = null): RepositoryInterface { if (!$rm) { @trigger_error('Not passing a repository manager when calling createRepo is deprecated since Composer 2.3.6', E_USER_DEPRECATED); $rm = static::manager($io, $config); } $repos = self::createRepos($rm, [$repoConfig]); return reset($repos); } public static function defaultRepos(?IOInterface $io = null, ?Config $config = null, ?RepositoryManager $rm = null): array { if (null === $rm) { @trigger_error('Not passing a repository manager when calling defaultRepos is deprecated since Composer 2.3.6, use defaultReposWithDefaultManager() instead if you cannot get a manager.', E_USER_DEPRECATED); } if (null === $config) { $config = Factory::createConfig($io); } if (null !== $io) { $io->loadConfiguration($config); } if (null === $rm) { if (null === $io) { throw new \InvalidArgumentException('This function requires either an IOInterface or a RepositoryManager'); } $rm = static::manager($io, $config, Factory::createHttpDownloader($io, $config)); } return self::createRepos($rm, $config->getRepositories()); } public static function manager(IOInterface $io, Config $config, ?HttpDownloader $httpDownloader = null, ?EventDispatcher $eventDispatcher = null, ?ProcessExecutor $process = null): RepositoryManager { if ($httpDownloader === null) { $httpDownloader = Factory::createHttpDownloader($io, $config); } if ($process === null) { $process = new ProcessExecutor($io); $process->enableAsync(); } $rm = new RepositoryManager($io, $config, $httpDownloader, $eventDispatcher, $process); $rm->setRepositoryClass('composer', 'Composer\Repository\ComposerRepository'); $rm->setRepositoryClass('vcs', 'Composer\Repository\VcsRepository'); $rm->setRepositoryClass('package', 'Composer\Repository\PackageRepository'); $rm->setRepositoryClass('pear', 'Composer\Repository\PearRepository'); $rm->setRepositoryClass('git', 'Composer\Repository\VcsRepository'); $rm->setRepositoryClass('bitbucket', 'Composer\Repository\VcsRepository'); $rm->setRepositoryClass('git-bitbucket', 'Composer\Repository\VcsRepository'); $rm->setRepositoryClass('github', 'Composer\Repository\VcsRepository'); $rm->setRepositoryClass('gitlab', 'Composer\Repository\VcsRepository'); $rm->setRepositoryClass('svn', 'Composer\Repository\VcsRepository'); $rm->setRepositoryClass('fossil', 'Composer\Repository\VcsRepository'); $rm->setRepositoryClass('perforce', 'Composer\Repository\VcsRepository'); $rm->setRepositoryClass('hg', 'Composer\Repository\VcsRepository'); $rm->setRepositoryClass('artifact', 'Composer\Repository\ArtifactRepository'); $rm->setRepositoryClass('path', 'Composer\Repository\PathRepository'); return $rm; } public static function defaultReposWithDefaultManager(IOInterface $io): array { $manager = RepositoryFactory::manager($io, $config = Factory::createConfig($io)); $io->loadConfiguration($config); return RepositoryFactory::defaultRepos($io, $config, $manager); } private static function createRepos(RepositoryManager $rm, array $repoConfigs): array { $repos = []; foreach ($repoConfigs as $index => $repo) { if (is_string($repo)) { throw new \UnexpectedValueException('"repositories" should be an array of repository definitions, only a single repository was given'); } if (!is_array($repo)) { throw new \UnexpectedValueException('Repository "'.$index.'" ('.json_encode($repo).') should be an array, '.get_debug_type($repo).' given'); } if (!isset($repo['type'])) { throw new \UnexpectedValueException('Repository "'.$index.'" ('.json_encode($repo).') must have a type defined'); } $name = self::generateRepositoryName($index, $repo, $repos); if ($repo['type'] === 'filesystem') { $repos[$name] = new FilesystemRepository($repo['json']); } else { $repos[$name] = $rm->createRepository($repo['type'], $repo, (string) $index); } } return $repos; } public static function generateRepositoryName($index, array $repo, array $existingRepos): string { $name = is_int($index) && isset($repo['url']) ? Preg::replace('{^https?://}i', '', $repo['url']) : (string) $index; while (isset($existingRepos[$name])) { $name .= '2'; } return $name; } } io = $io; $this->config = $config; $this->httpDownloader = $httpDownloader; $this->eventDispatcher = $eventDispatcher; $this->process = $process ?? new ProcessExecutor($io); } public function findPackage(string $name, $constraint): ?PackageInterface { foreach ($this->repositories as $repository) { if ($package = $repository->findPackage($name, $constraint)) { return $package; } } return null; } public function findPackages(string $name, $constraint): array { $packages = []; foreach ($this->getRepositories() as $repository) { $packages = array_merge($packages, $repository->findPackages($name, $constraint)); } return $packages; } public function addRepository(RepositoryInterface $repository): void { $this->repositories[] = $repository; } public function prependRepository(RepositoryInterface $repository): void { array_unshift($this->repositories, $repository); } public function createRepository(string $type, array $config, ?string $name = null): RepositoryInterface { if (!isset($this->repositoryClasses[$type])) { throw new \InvalidArgumentException('Repository type is not registered: '.$type); } if (isset($config['packagist']) && false === $config['packagist']) { $this->io->writeError('Repository "'.$name.'" ('.json_encode($config).') has a packagist key which should be in its own repository definition'); } $class = $this->repositoryClasses[$type]; if (isset($config['only']) || isset($config['exclude']) || isset($config['canonical'])) { $filterConfig = $config; unset($config['only'], $config['exclude'], $config['canonical']); } $repository = new $class($config, $this->io, $this->config, $this->httpDownloader, $this->eventDispatcher, $this->process); if (isset($filterConfig)) { $repository = new FilterRepository($repository, $filterConfig); } return $repository; } public function setRepositoryClass(string $type, $class): void { $this->repositoryClasses[$type] = $class; } public function getRepositories(): array { return $this->repositories; } public function setLocalRepository(InstalledRepositoryInterface $repository): void { $this->localRepository = $repository; } public function getLocalRepository(): InstalledRepositoryInterface { return $this->localRepository; } public function getHttpDownloader(): HttpDownloader { return $this->httpDownloader; } } rootAliases = self::getRootAliasesPerPackage($rootAliases); $this->rootReferences = $rootReferences; $this->acceptableStabilities = []; foreach (BasePackage::STABILITIES as $stability => $value) { if ($value <= BasePackage::STABILITIES[$minimumStability]) { $this->acceptableStabilities[$stability] = $value; } } $this->stabilityFlags = $stabilityFlags; $this->rootRequires = $rootRequires; foreach ($rootRequires as $name => $constraint) { if (PlatformRepository::isPlatformPackage($name)) { unset($this->rootRequires[$name]); } } $this->temporaryConstraints = $temporaryConstraints; } public function allowInstalledRepositories(bool $allow = true): void { $this->allowInstalledRepositories = $allow; } public function getRootRequires(): array { return $this->rootRequires; } public function getTemporaryConstraints(): array { return $this->temporaryConstraints; } public function addRepository(RepositoryInterface $repo): void { if ($this->locked) { throw new \RuntimeException("Pool has already been created from this repository set, it cannot be modified anymore."); } if ($repo instanceof CompositeRepository) { $repos = $repo->getRepositories(); } else { $repos = [$repo]; } foreach ($repos as $repo) { $this->repositories[] = $repo; } } public function findPackages(string $name, ?ConstraintInterface $constraint = null, int $flags = 0): array { $ignoreStability = ($flags & self::ALLOW_UNACCEPTABLE_STABILITIES) !== 0; $loadFromAllRepos = ($flags & self::ALLOW_SHADOWED_REPOSITORIES) !== 0; $packages = []; if ($loadFromAllRepos) { foreach ($this->repositories as $repository) { $packages[] = $repository->findPackages($name, $constraint) ?: []; } } else { foreach ($this->repositories as $repository) { $result = $repository->loadPackages([$name => $constraint], $ignoreStability ? BasePackage::STABILITIES : $this->acceptableStabilities, $ignoreStability ? [] : $this->stabilityFlags); $packages[] = $result['packages']; foreach ($result['namesFound'] as $nameFound) { if ($name === $nameFound) { break 2; } } } } $candidates = $packages ? array_merge(...$packages) : []; if ($ignoreStability || !$loadFromAllRepos) { return $candidates; } $result = []; foreach ($candidates as $candidate) { if ($this->isPackageAcceptable($candidate->getNames(), $candidate->getStability())) { $result[] = $candidate; } } return $result; } public function getSecurityAdvisories(array $packageNames, bool $allowPartialAdvisories, bool $ignoreUnreachable = false): array { $map = []; foreach ($packageNames as $name) { $map[$name] = new MatchAllConstraint(); } $unreachableRepos = []; $advisories = $this->getSecurityAdvisoriesForConstraints($map, $allowPartialAdvisories, $ignoreUnreachable, $unreachableRepos); return ['advisories' => $advisories, 'unreachableRepos' => $unreachableRepos]; } public function getMatchingSecurityAdvisories(array $packages, bool $allowPartialAdvisories = false, bool $ignoreUnreachable = false): array { $map = []; foreach ($packages as $package) { if ($package instanceof AliasPackage && $package->isRootPackageAlias()) { continue; } if (isset($map[$package->getName()])) { $map[$package->getName()] = new MultiConstraint([new Constraint('=', $package->getVersion()), $map[$package->getName()]], false); } else { $map[$package->getName()] = new Constraint('=', $package->getVersion()); } } $unreachableRepos = []; $advisories = $this->getSecurityAdvisoriesForConstraints($map, $allowPartialAdvisories, $ignoreUnreachable, $unreachableRepos); return ['advisories' => $advisories, 'unreachableRepos' => $unreachableRepos]; } private function getSecurityAdvisoriesForConstraints(array $packageConstraintMap, bool $allowPartialAdvisories, bool $ignoreUnreachable = false, array &$unreachableRepos = []): array { $repoAdvisories = []; foreach ($this->repositories as $repository) { try { if (!$repository instanceof AdvisoryProviderInterface || !$repository->hasSecurityAdvisories()) { continue; } $repoAdvisories[] = $repository->getSecurityAdvisories($packageConstraintMap, $allowPartialAdvisories)['advisories']; } catch (\Composer\Downloader\TransportException $e) { if (!$ignoreUnreachable) { throw $e; } $unreachableRepos[] = $e->getMessage(); } } $advisories = count($repoAdvisories) > 0 ? array_merge_recursive([], ...$repoAdvisories) : []; ksort($advisories); return $advisories; } public function getProviders(string $packageName): array { $providers = []; foreach ($this->repositories as $repository) { if ($repoProviders = $repository->getProviders($packageName)) { $providers = array_merge($providers, $repoProviders); } } return $providers; } public function isPackageAcceptable(array $names, string $stability): bool { return StabilityFilter::isPackageAcceptable($this->acceptableStabilities, $this->stabilityFlags, $names, $stability); } public function createPool(Request $request, IOInterface $io, ?EventDispatcher $eventDispatcher = null, ?PoolOptimizer $poolOptimizer = null, array $ignoredTypes = [], ?array $allowedTypes = null, ?SecurityAdvisoryPoolFilter $securityAdvisoryPoolFilter = null, ?FilterListPoolFilter $filterListPoolFilter = null): Pool { $poolBuilder = new PoolBuilder($this->acceptableStabilities, $this->stabilityFlags, $this->rootAliases, $this->rootReferences, $io, $eventDispatcher, $poolOptimizer, $this->temporaryConstraints, $securityAdvisoryPoolFilter, $filterListPoolFilter); $poolBuilder->setIgnoredTypes($ignoredTypes); $poolBuilder->setAllowedTypes($allowedTypes); foreach ($this->repositories as $repo) { if (($repo instanceof InstalledRepositoryInterface || $repo instanceof InstalledRepository) && !$this->allowInstalledRepositories) { throw new \LogicException('The pool can not accept packages from an installed repository'); } } $this->locked = true; return $poolBuilder->buildPool($this->repositories, $request); } public function createPoolWithAllPackages(): Pool { foreach ($this->repositories as $repo) { if (($repo instanceof InstalledRepositoryInterface || $repo instanceof InstalledRepository) && !$this->allowInstalledRepositories) { throw new \LogicException('The pool can not accept packages from an installed repository'); } } $this->locked = true; $packages = []; foreach ($this->repositories as $repository) { foreach ($repository->getPackages() as $package) { $packages[] = $package; if (isset($this->rootAliases[$package->getName()][$package->getVersion()])) { $alias = $this->rootAliases[$package->getName()][$package->getVersion()]; while ($package instanceof AliasPackage) { $package = $package->getAliasOf(); } if ($package instanceof CompletePackage) { $aliasPackage = new CompleteAliasPackage($package, $alias['alias_normalized'], $alias['alias']); } else { $aliasPackage = new AliasPackage($package, $alias['alias_normalized'], $alias['alias']); } $aliasPackage->setRootPackageAlias(true); $packages[] = $aliasPackage; } } } return new Pool($packages); } public function createPoolForPackage(string $packageName, ?LockArrayRepository $lockedRepo = null): Pool { return $this->createPoolForPackages([$packageName], $lockedRepo); } public function createPoolForPackages(array $packageNames, ?LockArrayRepository $lockedRepo = null): Pool { $request = new Request($lockedRepo); $allowedPackages = []; foreach ($packageNames as $packageName) { if (PlatformRepository::isPlatformPackage($packageName)) { throw new \LogicException('createPoolForPackage(s) can not be used for platform packages, as they are never loaded by the PoolBuilder which expects them to be fixed. Use createPoolWithAllPackages or pass in a proper request with the platform packages you need fixed in it.'); } $request->requireName($packageName); $allowedPackages[] = strtolower($packageName); } if (count($allowedPackages) > 0) { $request->restrictPackages($allowedPackages); } return $this->createPool($request, new NullIO()); } private static function getRootAliasesPerPackage(array $aliases): array { $normalizedAliases = []; foreach ($aliases as $alias) { $normalizedAliases[$alias['package']][$alias['version']] = [ 'alias' => $alias['alias'], 'alias_normalized' => $alias['alias_normalized'], ]; } return $normalizedAliases; } } getRequires(); if ($includeRequireDev) { $requires = array_merge($requires, $requirer->getDevRequires()); } foreach ($packages as $candidate) { foreach ($candidate->getNames() as $name) { if (isset($requires[$name])) { if (!in_array($candidate, $bucket, true)) { $bucket[] = $candidate; $bucket = self::filterRequiredPackages($packages, $candidate, false, $bucket); } break; } } } return $bucket; } public static function flattenRepositories(RepositoryInterface $repo, bool $unwrapFilterRepos = true): array { if ($unwrapFilterRepos && $repo instanceof FilterRepository) { $repo = $repo->getRepository(); } if (!$repo instanceof CompositeRepository) { return [$repo]; } $repos = []; foreach ($repo->getRepositories() as $r) { foreach (self::flattenRepositories($r, $unwrapFilterRepos) as $r2) { $repos[] = $r2; } } return $repos; } } forgejoUrl = ForgejoUrl::create($this->url); $this->originUrl = $this->forgejoUrl->originUrl; $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.$this->originUrl.'/'.$this->forgejoUrl->owner.'/'.$this->forgejoUrl->repository); $this->cache->setReadOnly($this->config->get('cache-read-only')); $this->fetchRepositoryData(); } public function getFileContent(string $file, string $identifier): ?string { if ($this->gitDriver !== null) { return $this->gitDriver->getFileContent($file, $identifier); } $resource = $this->forgejoUrl->apiUrl.'/contents/' . $file . '?ref='.urlencode($identifier); $resource = $this->getContents($resource)->decodeJson(); if ((!isset($resource['content']) || $resource['content'] === '') && $resource['encoding'] === 'none' && isset($resource['git_url'])) { $resource = $this->getContents($resource['git_url'])->decodeJson(); } if (!isset($resource['content']) || $resource['encoding'] !== 'base64' || false === ($content = base64_decode($resource['content'], true))) { throw new \RuntimeException('Could not retrieve ' . $file . ' for '.$identifier); } return $content; } public function getChangeDate(string $identifier): ?\DateTimeImmutable { if ($this->gitDriver !== null) { return $this->gitDriver->getChangeDate($identifier); } $resource = $this->forgejoUrl->apiUrl.'/git/commits/'.urlencode($identifier).'?verification=false&files=false'; $commit = $this->getContents($resource)->decodeJson(); return new \DateTimeImmutable($commit['commit']['committer']['date']); } public function getRootIdentifier(): string { if ($this->gitDriver !== null) { return $this->gitDriver->getRootIdentifier(); } return $this->repositoryData->defaultBranch; } public function getBranches(): array { if ($this->gitDriver !== null) { return $this->gitDriver->getBranches(); } if (null === $this->branches) { $branches = []; $resource = $this->forgejoUrl->apiUrl.'/branches?per_page=100'; do { $response = $this->getContents($resource); $branchData = $response->decodeJson(); foreach ($branchData as $branch) { $branches[$branch['name']] = $branch['commit']['id']; } $resource = $this->getNextPage($response); } while ($resource); $this->branches = $branches; } return $this->branches; } public function getTags(): array { if ($this->gitDriver !== null) { return $this->gitDriver->getTags(); } if (null === $this->tags) { $tags = []; $resource = $this->forgejoUrl->apiUrl.'/tags?per_page=100'; do { $response = $this->getContents($resource); $tagsData = $response->decodeJson(); foreach ($tagsData as $tag) { $tags[$tag['name']] = $tag['commit']['sha']; } $resource = $this->getNextPage($response); } while ($resource); $this->tags = $tags; } return $this->tags; } public function getDist(string $identifier): ?array { $url = $this->forgejoUrl->apiUrl.'/archive/'.$identifier.'.zip'; return ['type' => 'zip', 'url' => $url, 'reference' => $identifier, 'shasum' => '']; } public function getComposerInformation(string $identifier): ?array { if ($this->gitDriver !== null) { return $this->gitDriver->getComposerInformation($identifier); } if (!isset($this->infoCache[$identifier])) { if ($this->shouldCache($identifier) && false !== ($res = $this->cache->read($identifier))) { $composer = JsonFile::parseJson($res); } else { $composer = $this->getBaseComposerInformation($identifier); if ($this->shouldCache($identifier)) { $this->cache->write($identifier, JsonFile::encode($composer, \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES)); } } if ($composer !== null) { if (isset($composer['support']) && !is_array($composer['support'])) { $composer['support'] = []; } if (!isset($composer['support']['source'])) { if (false !== ($label = array_search($identifier, $this->getTags(), true))) { $composer['support']['source'] = $this->repositoryData->htmlUrl.'/tag/' . $label; } elseif (false !== ($label = array_search($identifier, $this->getBranches(), true))) { $composer['support']['source'] = $this->repositoryData->htmlUrl.'/branch/'.$label; } else { $composer['support']['source'] = $this->repositoryData->htmlUrl.'/commit/'.$identifier; } } if (!isset($composer['support']['issues']) && $this->repositoryData->hasIssues) { $composer['support']['issues'] = $this->repositoryData->htmlUrl.'/issues'; } if (!isset($composer['abandoned']) && $this->repositoryData->isArchived) { $composer['abandoned'] = true; } } $this->infoCache[$identifier] = $composer; } return $this->infoCache[$identifier]; } public function getSource(string $identifier): array { if ($this->gitDriver !== null) { return $this->gitDriver->getSource($identifier); } return ['type' => 'git', 'url' => $this->getUrl(), 'reference' => $identifier]; } public function getUrl(): string { if ($this->gitDriver !== null) { return $this->gitDriver->getUrl(); } return $this->repositoryData->isPrivate ? $this->repositoryData->sshUrl : $this->repositoryData->httpCloneUrl; } public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool { $forgejoUrl = ForgejoUrl::tryFrom($url); if ($forgejoUrl === null) { return false; } if (!in_array(strtolower($forgejoUrl->originUrl), $config->get('forgejo-domains'), true)) { return false; } if (!extension_loaded('openssl')) { $io->writeError('Skipping Forgejo driver for '.Url::sanitize($url).' because the OpenSSL PHP extension is missing.', true, IOInterface::VERBOSE); return false; } return true; } protected function setupGitDriver(string $url): void { $this->gitDriver = new GitDriver( ['url' => $url], $this->io, $this->config, $this->httpDownloader, $this->process ); $this->gitDriver->initialize(); } private function fetchRepositoryData(): void { if ($this->repositoryData !== null) { return; } $data = $this->getContents($this->forgejoUrl->apiUrl, true)->decodeJson(); if (null === $data && null !== $this->gitDriver) { return; } $this->repositoryData = ForgejoRepositoryData::fromRemoteData($data); } protected function getNextPage(Response $response): ?string { $header = $response->getHeader('link'); if ($header === null) { return null; } $links = explode(',', $header); foreach ($links as $link) { if (Preg::isMatch('{<(.+?)>; *rel="next"}', $link, $match)) { return $match[1]; } } return null; } protected function getContents(string $url, bool $fetchingRepoData = false): Response { $forgejo = new Forgejo($this->io, $this->config, $this->httpDownloader); try { return parent::getContents($url); } catch (TransportException $e) { switch ($e->getCode()) { case 401: case 403: case 404: case 429: if (!$fetchingRepoData) { throw $e; } if (!$this->io->isInteractive()) { $this->attemptCloneFallback(); return new Response(['url' => 'dummy'], 200, [], 'null'); } if ( !$this->io->hasAuthentication($this->originUrl) && $forgejo->authorizeOAuthInteractively($this->forgejoUrl->originUrl, $e->getCode() === 429 ? 'API limit exhausted. Enter your Forgejo credentials to get a larger API limit ('.Url::sanitize($this->url).')' : null) ) { return parent::getContents($url); } throw $e; default: throw $e; } } } protected function attemptCloneFallback(): bool { try { $this->setupGitDriver($this->forgejoUrl->generateSshUrl()); return true; } catch (\RuntimeException $e) { $this->gitDriver = null; $this->io->writeError('Failed to clone the '.$this->forgejoUrl->generateSshUrl().' repository, try running in interactive mode so that you can enter your Forgejo credentials'); throw $e; } } } checkFossil(); $this->config->prohibitUrlByConfig($this->url, $this->io); if (Filesystem::isLocalPath($this->url) && is_dir($this->url)) { $this->checkoutDir = $this->url; } else { if (!Cache::isUsable($this->config->get('cache-repo-dir')) || !Cache::isUsable($this->config->get('cache-vcs-dir'))) { throw new \RuntimeException('FossilDriver requires a usable cache directory, and it looks like you set it to be disabled'); } $localName = Preg::replace('{[^a-z0-9]}i', '-', $this->url); $this->repoFile = $this->config->get('cache-repo-dir') . '/' . $localName . '.fossil'; $this->checkoutDir = $this->config->get('cache-vcs-dir') . '/' . $localName . '/'; $this->updateLocalRepo(); } $this->getTags(); $this->getBranches(); } protected function checkFossil(): void { if (0 !== $this->process->execute(['fossil', 'version'], $ignoredOutput)) { throw new \RuntimeException("fossil was not found, check that it is installed and in your PATH env.\n\n" . $this->process->getErrorOutput()); } } protected function updateLocalRepo(): void { assert($this->repoFile !== null); $fs = new Filesystem(); $fs->ensureDirectoryExists($this->checkoutDir); if (!is_writable(dirname($this->checkoutDir))) { throw new \RuntimeException('Can not clone '.Url::sanitize($this->url).' to access package information. The "'.$this->checkoutDir.'" directory is not writable by the current user.'); } if (is_file($this->repoFile) && is_dir($this->checkoutDir) && 0 === $this->process->execute(['fossil', 'info'], $output, $this->checkoutDir)) { if (0 !== $this->process->execute(['fossil', 'pull'], $output, $this->checkoutDir)) { $this->io->writeError('Failed to update '.Url::sanitize($this->url).', package information from this repository may be outdated ('.$this->process->getErrorOutput().')'); } } else { $fs->removeDirectory($this->checkoutDir); $fs->remove($this->repoFile); $fs->ensureDirectoryExists($this->checkoutDir); if (0 !== $this->process->execute(['fossil', 'clone', '--', $this->url, $this->repoFile], $output)) { $output = $this->process->getErrorOutput(); throw new \RuntimeException('Failed to clone '.Url::sanitize($this->url).' to repository ' . $this->repoFile . "\n\n" .$output); } if (0 !== $this->process->execute(['fossil', 'open', '--nested', '--', $this->repoFile], $output, $this->checkoutDir)) { $output = $this->process->getErrorOutput(); throw new \RuntimeException('Failed to open repository '.$this->repoFile.' in ' . $this->checkoutDir . "\n\n" .$output); } } } public function getRootIdentifier(): string { if (null === $this->rootIdentifier) { $this->rootIdentifier = 'trunk'; } return $this->rootIdentifier; } public function getUrl(): string { return $this->url; } public function getSource(string $identifier): array { return ['type' => 'fossil', 'url' => $this->getUrl(), 'reference' => $identifier]; } public function getDist(string $identifier): ?array { return null; } public function getFileContent(string $file, string $identifier): ?string { if (isset($identifier[0]) && $identifier[0] === '-') { throw new \RuntimeException('Invalid fossil identifier detected. Identifier must not start with a -, given: ' . $identifier); } $this->process->execute(['fossil', 'cat', '-r', $identifier, '--', $file], $content, $this->checkoutDir); if ('' === trim($content)) { return null; } return $content; } public function getChangeDate(string $identifier): ?\DateTimeImmutable { $this->process->execute(['fossil', 'finfo', '-b', '-n', '1', 'composer.json'], $output, $this->checkoutDir); [, $date] = explode(' ', trim($output), 3); return new \DateTimeImmutable($date, new \DateTimeZone('UTC')); } public function getTags(): array { if (null === $this->tags) { $tags = []; $this->process->execute(['fossil', 'tag', 'list'], $output, $this->checkoutDir); foreach ($this->process->splitLines($output) as $tag) { $tags[$tag] = $tag; } $this->tags = $tags; } return $this->tags; } public function getBranches(): array { if (null === $this->branches) { $branches = []; $this->process->execute(['fossil', 'branch', 'list'], $output, $this->checkoutDir); foreach ($this->process->splitLines($output) as $branch) { $branch = trim(Preg::replace('/^\*/', '', trim($branch))); $branches[$branch] = $branch; } $this->branches = $branches; } return $this->branches; } public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool { if (Preg::isMatch('#(^(?:https?|ssh)://(?:[^@]@)?(?:chiselapp\.com|fossil\.))#i', $url)) { return true; } if (Preg::isMatch('!/fossil/|\.fossil!', $url)) { return true; } if (Filesystem::isLocalPath($url)) { $url = Filesystem::getPlatformPath($url); if (!is_dir($url)) { return false; } $process = new ProcessExecutor($io); if ($process->execute(['fossil', 'info'], $output, $url) === 0) { return true; } } return false; } } url, $match)) { throw new \InvalidArgumentException(sprintf('The Bitbucket repository URL %s is invalid. It must be the HTTPS URL of a Bitbucket repository.', Url::sanitize($this->url))); } $this->owner = $match[1]; $this->repository = $match[2]; $this->originUrl = 'bitbucket.org'; $this->cache = new Cache( $this->io, implode('/', [ $this->config->get('cache-repo-dir'), $this->originUrl, $this->owner, $this->repository, ]) ); $this->cache->setReadOnly($this->config->get('cache-read-only')); } public function getUrl(): string { if ($this->fallbackDriver) { return $this->fallbackDriver->getUrl(); } return $this->cloneHttpsUrl; } protected function getRepoData(): bool { $resource = sprintf( 'https://api.bitbucket.org/2.0/repositories/%s/%s?%s', $this->owner, $this->repository, http_build_query( ['fields' => '-project,-owner'], '', '&' ) ); $repoData = $this->fetchWithOAuthCredentials($resource, true)->decodeJson(); if ($this->fallbackDriver) { return false; } $this->parseCloneUrls($repoData['links']['clone']); $this->hasIssues = !empty($repoData['has_issues']); $this->branchesUrl = $repoData['links']['branches']['href']; $this->tagsUrl = $repoData['links']['tags']['href']; $this->homeUrl = $repoData['links']['html']['href']; $this->website = $repoData['website']; $this->vcsType = $repoData['scm']; $this->repoData = $repoData; return true; } public function getComposerInformation(string $identifier): ?array { if ($this->fallbackDriver) { return $this->fallbackDriver->getComposerInformation($identifier); } if (!isset($this->infoCache[$identifier])) { if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier)) { $composer = JsonFile::parseJson($res); } else { $composer = $this->getBaseComposerInformation($identifier); if ($this->shouldCache($identifier)) { $this->cache->write($identifier, JsonFile::encode($composer, \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES)); } } if ($composer !== null) { if (isset($composer['support']) && !is_array($composer['support'])) { $composer['support'] = []; } if (!isset($composer['support']['source'])) { $label = array_search( $identifier, $this->getTags() ) ?: array_search( $identifier, $this->getBranches() ) ?: $identifier; if (array_key_exists($label, $tags = $this->getTags())) { $hash = $tags[$label]; } elseif (array_key_exists($label, $branches = $this->getBranches())) { $hash = $branches[$label]; } if (!isset($hash)) { $composer['support']['source'] = sprintf( 'https://%s/%s/%s/src', $this->originUrl, $this->owner, $this->repository ); } else { $composer['support']['source'] = sprintf( 'https://%s/%s/%s/src/%s/?at=%s', $this->originUrl, $this->owner, $this->repository, $hash, $label ); } } if (!isset($composer['support']['issues']) && $this->hasIssues) { $composer['support']['issues'] = sprintf( 'https://%s/%s/%s/issues', $this->originUrl, $this->owner, $this->repository ); } if (!isset($composer['homepage'])) { $composer['homepage'] = empty($this->website) ? $this->homeUrl : $this->website; } } $this->infoCache[$identifier] = $composer; } return $this->infoCache[$identifier]; } public function getFileContent(string $file, string $identifier): ?string { if ($this->fallbackDriver) { return $this->fallbackDriver->getFileContent($file, $identifier); } if (strpos($identifier, '/') !== false) { $branches = $this->getBranches(); if (isset($branches[$identifier])) { $identifier = $branches[$identifier]; } } $resource = sprintf( 'https://api.bitbucket.org/2.0/repositories/%s/%s/src/%s/%s', $this->owner, $this->repository, $identifier, $file ); return $this->fetchWithOAuthCredentials($resource)->getBody(); } public function getChangeDate(string $identifier): ?\DateTimeImmutable { if ($this->fallbackDriver) { return $this->fallbackDriver->getChangeDate($identifier); } if (strpos($identifier, '/') !== false) { $branches = $this->getBranches(); if (isset($branches[$identifier])) { $identifier = $branches[$identifier]; } } $resource = sprintf( 'https://api.bitbucket.org/2.0/repositories/%s/%s/commit/%s?fields=date', $this->owner, $this->repository, $identifier ); $commit = $this->fetchWithOAuthCredentials($resource)->decodeJson(); return new \DateTimeImmutable($commit['date']); } public function getSource(string $identifier): array { if ($this->fallbackDriver) { return $this->fallbackDriver->getSource($identifier); } return ['type' => $this->vcsType, 'url' => $this->getUrl(), 'reference' => $identifier]; } public function getDist(string $identifier): ?array { if ($this->fallbackDriver) { return $this->fallbackDriver->getDist($identifier); } $url = sprintf( 'https://bitbucket.org/%s/%s/get/%s.zip', $this->owner, $this->repository, $identifier ); return ['type' => 'zip', 'url' => $url, 'reference' => $identifier, 'shasum' => '']; } public function getTags(): array { if ($this->fallbackDriver) { return $this->fallbackDriver->getTags(); } if (null === $this->tags) { $tags = []; $resource = sprintf( '%s?%s', $this->tagsUrl, http_build_query( [ 'pagelen' => 100, 'fields' => 'values.name,values.target.hash,next', 'sort' => '-target.date', ], '', '&' ) ); $hasNext = true; while ($hasNext) { $tagsData = $this->fetchWithOAuthCredentials($resource)->decodeJson(); foreach ($tagsData['values'] as $data) { $tags[$data['name']] = $data['target']['hash']; } if (empty($tagsData['next'])) { $hasNext = false; } else { $resource = $tagsData['next']; } } $this->tags = $tags; } return $this->tags; } public function getBranches(): array { if ($this->fallbackDriver) { return $this->fallbackDriver->getBranches(); } if (null === $this->branches) { $branches = []; $resource = sprintf( '%s?%s', $this->branchesUrl, http_build_query( [ 'pagelen' => 100, 'fields' => 'values.name,values.target.hash,values.heads,next', 'sort' => '-target.date', ], '', '&' ) ); $hasNext = true; while ($hasNext) { $branchData = $this->fetchWithOAuthCredentials($resource)->decodeJson(); foreach ($branchData['values'] as $data) { $branches[$data['name']] = $data['target']['hash']; } if (empty($branchData['next'])) { $hasNext = false; } else { $resource = $branchData['next']; } } $this->branches = $branches; } return $this->branches; } protected function fetchWithOAuthCredentials(string $url, bool $fetchingRepoData = false): Response { try { return parent::getContents($url); } catch (TransportException $e) { $bitbucketUtil = new Bitbucket($this->io, $this->config, $this->process, $this->httpDownloader); if (in_array($e->getCode(), [403, 404], true) || (401 === $e->getCode() && strpos($e->getMessage(), 'Could not authenticate against') === 0)) { if (!$this->io->hasAuthentication($this->originUrl) && $bitbucketUtil->authorizeOAuth($this->originUrl) ) { return parent::getContents($url); } if (!$this->io->isInteractive() && $fetchingRepoData) { $this->attemptCloneFallback(); return new Response(['url' => 'dummy'], 200, [], 'null'); } } throw $e; } } protected function generateSshUrl(): string { return 'git@' . $this->originUrl . ':' . $this->owner.'/'.$this->repository.'.git'; } protected function attemptCloneFallback(): bool { try { $this->setupFallbackDriver($this->generateSshUrl()); return true; } catch (\RuntimeException $e) { $this->fallbackDriver = null; $this->io->writeError( 'Failed to clone the ' . $this->generateSshUrl() . ' repository, try running in interactive mode' . ' so that you can enter your Bitbucket OAuth consumer credentials' ); throw $e; } } protected function setupFallbackDriver(string $url): void { $this->fallbackDriver = new GitDriver( ['url' => $url], $this->io, $this->config, $this->httpDownloader, $this->process ); $this->fallbackDriver->initialize(); } protected function parseCloneUrls(array $cloneLinks): void { foreach ($cloneLinks as $cloneLink) { if ($cloneLink['name'] === 'https') { $this->cloneHttpsUrl = Preg::replace('/https:\/\/([^@]+@)?/', 'https://', $cloneLink['href']); } } } public function getRootIdentifier(): string { if ($this->fallbackDriver) { return $this->fallbackDriver->getRootIdentifier(); } if (null === $this->rootIdentifier) { if (!$this->getRepoData()) { if (!$this->fallbackDriver) { throw new \LogicException('A fallback driver should be setup if getRepoData returns false'); } return $this->fallbackDriver->getRootIdentifier(); } if ($this->vcsType !== 'git') { throw new \RuntimeException( Url::sanitize($this->url).' does not appear to be a git repository, use '. $this->cloneHttpsUrl.' but remember that Bitbucket no longer supports the mercurial repositories. '. 'https://bitbucket.org/blog/sunsetting-mercurial-support-in-bitbucket' ); } $this->rootIdentifier = $this->repoData['mainbranch']['name'] ?? 'master'; } return $this->rootIdentifier; } public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool { if (!Preg::isMatch('#^https?://bitbucket\.org/([^/]+)/([^/]+?)(\.git|/?)?$#i', $url)) { return false; } if (!extension_loaded('openssl')) { $io->writeError('Skipping Bitbucket git driver for '.Url::sanitize($url).' because the OpenSSL PHP extension is missing.', true, IOInterface::VERBOSE); return false; } return true; } } url)) { $this->url = Preg::replace('{[\\/]\.git/?$}', '', $this->url); if (!is_dir($this->url)) { throw new \RuntimeException('Failed to read package information from '.Url::sanitize($this->url).' as the path does not exist'); } $this->repoDir = $this->url; $cacheUrl = realpath($this->url); } else { if (!Cache::isUsable($this->config->get('cache-vcs-dir'))) { throw new \RuntimeException('GitDriver requires a usable cache directory, and it looks like you set it to be disabled'); } $this->repoDir = $this->config->get('cache-vcs-dir') . '/' . Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($this->url)) . '/'; GitUtil::cleanEnv($this->process); $fs = new Filesystem(); $fs->ensureDirectoryExists(dirname($this->repoDir)); if (!is_writable(dirname($this->repoDir))) { throw new \RuntimeException('Can not clone '.Url::sanitize($this->url).' to access package information. The "'.dirname($this->repoDir).'" directory is not writable by the current user.'); } if (Preg::isMatch('{^ssh://[^@]+@[^:]+:[^0-9]+}', $this->url)) { throw new \InvalidArgumentException('The source URL '.Url::sanitize($this->url).' is invalid, ssh URLs should have a port number after ":".'."\n".'Use ssh://git@example.com:22/path or just git@example.com:path if you do not want to provide a password or custom port.'); } $gitUtil = new GitUtil($this->io, $this->config, $this->process, $fs); if (!$gitUtil->syncMirror($this->url, $this->repoDir)) { if (!is_dir($this->repoDir)) { throw new \RuntimeException('Failed to clone '.Url::sanitize($this->url).' to read package information from it'); } $this->io->writeError('Failed to update '.Url::sanitize($this->url).', package information from this repository may be outdated'); } $cacheUrl = $this->url; } $this->getTags(); $this->getBranches(); $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($cacheUrl))); $this->cache->setReadOnly($this->config->get('cache-read-only')); } public function getRootIdentifier(): string { if (null === $this->rootIdentifier) { $this->rootIdentifier = 'master'; $gitUtil = new GitUtil($this->io, $this->config, $this->process, new Filesystem()); if (!Filesystem::isLocalPath($this->url)) { $defaultBranch = $gitUtil->getMirrorDefaultBranch($this->url, $this->repoDir, false); if ($defaultBranch !== null) { return $this->rootIdentifier = $defaultBranch; } } $this->process->execute(['git', 'branch', '--no-color'], $output, $this->repoDir); $branches = $this->process->splitLines($output); if (!in_array('* master', $branches)) { foreach ($branches as $branch) { if ($branch && Preg::isMatchStrictGroups('{^\* +(\S+)}', $branch, $match)) { $this->rootIdentifier = $match[1]; break; } } } } return $this->rootIdentifier; } public function getUrl(): string { return $this->url; } public function getSource(string $identifier): array { return ['type' => 'git', 'url' => $this->getUrl(), 'reference' => $identifier]; } public function getDist(string $identifier): ?array { return null; } public function getFileContent(string $file, string $identifier): ?string { if (isset($identifier[0]) && $identifier[0] === '-') { throw new \RuntimeException('Invalid git identifier detected. Identifier must not start with a -, given: ' . $identifier); } $this->process->execute(['git', 'show', $identifier.':'.$file], $content, $this->repoDir); if (trim($content) === '') { return null; } return $content; } public function getChangeDate(string $identifier): ?\DateTimeImmutable { if (isset($identifier[0]) && $identifier[0] === '-') { throw new \RuntimeException('Invalid git identifier detected. Identifier must not start with a -, given: ' . $identifier); } $command = GitUtil::buildRevListCommand($this->process, ['-n1', '--format=%at', $identifier]); $this->process->execute($command, $output, $this->repoDir); return new \DateTimeImmutable('@'.trim(GitUtil::parseRevListOutput($output, $this->process)), new \DateTimeZone('UTC')); } public function getTags(): array { if (null === $this->tags) { $this->tags = []; $this->process->execute(['git', 'show-ref', '--tags', '--dereference'], $output, $this->repoDir); foreach ($this->process->splitLines($output) as $tag) { if ($tag !== '' && Preg::isMatch('{^([a-f0-9]{40}) refs/tags/(\S+?)(\^\{\})?$}', $tag, $match)) { $this->tags[$match[2]] = $match[1]; } } } return $this->tags; } public function getBranches(): array { if (null === $this->branches) { $branches = []; $this->process->execute(['git', 'branch', '--no-color', '--no-abbrev', '-v'], $output, $this->repoDir); foreach ($this->process->splitLines($output) as $branch) { if ($branch !== '' && !Preg::isMatch('{^ *[^/]+/HEAD }', $branch)) { if (Preg::isMatchStrictGroups('{^(?:\* )? *(\S+) *([a-f0-9]+)(?: .*)?$}', $branch, $match) && $match[1][0] !== '-') { $branches[$match[1]] = $match[2]; } } } $this->branches = $branches; } return $this->branches; } public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool { if (Preg::isMatch('#(^git://|\.git/?$|git(?:olite)?@|//git\.|//github.com/)#i', $url)) { return true; } if (Filesystem::isLocalPath($url)) { $url = Filesystem::getPlatformPath($url); if (!is_dir($url)) { return false; } $process = new ProcessExecutor($io); if ($process->execute(['git', 'tag'], $output, $url) === 0) { return true; } GitUtil::checkForRepoOwnershipError($process->getErrorOutput(), $url); } if (!$deep) { return false; } $process = new ProcessExecutor($io); $gitUtil = new GitUtil($io, $config, $process, new Filesystem()); GitUtil::cleanEnv($process); try { $gitUtil->runCommands([['git', 'ls-remote', '--heads', '--', '%url%']], $url, sys_get_temp_dir()); } catch (\RuntimeException $e) { return false; } return true; } } url, $match)) { throw new \InvalidArgumentException(sprintf('The GitHub repository URL %s is invalid.', Url::sanitize($this->url))); } $this->owner = $match[3]; $this->repository = $match[4]; $this->originUrl = strtolower($match[1] ?? (string) $match[2]); if ($this->originUrl === 'www.github.com') { $this->originUrl = 'github.com'; } $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.$this->originUrl.'/'.$this->owner.'/'.$this->repository); $this->cache->setReadOnly($this->config->get('cache-read-only')); if (isset($this->repoConfig['allow-git-fallback']) && $this->repoConfig['allow-git-fallback'] === false) { $this->allowGitFallback = false; } if ($this->config->get('use-github-api') === false || (isset($this->repoConfig['no-api']) && $this->repoConfig['no-api'])) { $this->setupGitDriver($this->url); return; } $this->fetchRootIdentifier(); } public function getRepositoryUrl(): string { return 'https://'.$this->originUrl.'/'.$this->owner.'/'.$this->repository; } public function getRootIdentifier(): string { if ($this->gitDriver) { return $this->gitDriver->getRootIdentifier(); } return $this->rootIdentifier; } public function getUrl(): string { if ($this->gitDriver) { return $this->gitDriver->getUrl(); } return 'https://' . $this->originUrl . '/'.$this->owner.'/'.$this->repository.'.git'; } protected function getApiUrl(): string { if ('github.com' === $this->originUrl) { $apiUrl = 'api.github.com'; } else { $apiUrl = $this->originUrl . '/api/v3'; } return 'https://' . $apiUrl; } public function getSource(string $identifier): array { if ($this->gitDriver) { return $this->gitDriver->getSource($identifier); } if ($this->isPrivate) { $url = $this->generateSshUrl(); } else { $url = $this->getUrl(); } return ['type' => 'git', 'url' => $url, 'reference' => $identifier]; } public function getDist(string $identifier): ?array { $url = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/zipball/'.$identifier; return ['type' => 'zip', 'url' => $url, 'reference' => $identifier, 'shasum' => '']; } public function getComposerInformation(string $identifier): ?array { if ($this->gitDriver) { return $this->gitDriver->getComposerInformation($identifier); } if (!isset($this->infoCache[$identifier])) { if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier)) { $composer = JsonFile::parseJson($res); } else { $composer = $this->getBaseComposerInformation($identifier); if ($this->shouldCache($identifier)) { $this->cache->write($identifier, JsonFile::encode($composer, \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES)); } } if ($composer !== null) { if (isset($composer['support']) && !is_array($composer['support'])) { $composer['support'] = []; } if (!isset($composer['support']['source'])) { $label = array_search($identifier, $this->getTags()) ?: array_search($identifier, $this->getBranches()) ?: $identifier; $composer['support']['source'] = sprintf('https://%s/%s/%s/tree/%s', $this->originUrl, $this->owner, $this->repository, $label); } if (!isset($composer['support']['issues']) && $this->hasIssues) { $composer['support']['issues'] = sprintf('https://%s/%s/%s/issues', $this->originUrl, $this->owner, $this->repository); } if (!isset($composer['abandoned']) && $this->isArchived) { $composer['abandoned'] = true; } if (!isset($composer['funding']) && $funding = $this->getFundingInfo()) { $composer['funding'] = $funding; } } $this->infoCache[$identifier] = $composer; } return $this->infoCache[$identifier]; } private function getFundingInfo() { if (null !== $this->fundingInfo) { return $this->fundingInfo; } if ($this->originUrl !== 'github.com') { return $this->fundingInfo = false; } foreach ([$this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/contents/.github/FUNDING.yml', $this->getApiUrl() . '/repos/'.$this->owner.'/.github/contents/FUNDING.yml'] as $file) { try { $response = $this->httpDownloader->get($file, [ 'retry-auth-failure' => false, ])->decodeJson(); } catch (TransportException $e) { continue; } if (empty($response['content']) || $response['encoding'] !== 'base64' || !($funding = base64_decode($response['content']))) { continue; } break; } if (empty($funding)) { return $this->fundingInfo = false; } $result = []; $key = null; foreach (Preg::split('{\r?\n}', $funding) as $line) { $line = trim($line); if (Preg::isMatchStrictGroups('{^(\w+)\s*:\s*(.+)$}', $line, $match)) { if ($match[2] === '[') { $key = $match[1]; continue; } if (Preg::isMatchStrictGroups('{^\[(.*?)\](?:\s*#.*)?$}', $match[2], $match2)) { foreach (array_map('trim', Preg::split('{[\'"]?\s*,\s*[\'"]?}', $match2[1])) as $item) { $result[] = ['type' => $match[1], 'url' => trim($item, '"\' ')]; } } elseif (Preg::isMatchStrictGroups('{^([^#].*?)(?:\s+#.*)?$}', $match[2], $match2)) { $result[] = ['type' => $match[1], 'url' => trim($match2[1], '"\' ')]; } $key = null; } elseif (Preg::isMatchStrictGroups('{^(\w+)\s*:\s*#\s*$}', $line, $match)) { $key = $match[1]; } elseif ($key !== null && ( Preg::isMatchStrictGroups('{^-\s*(.+)(?:\s+#.*)?$}', $line, $match) || Preg::isMatchStrictGroups('{^(.+),(?:\s*#.*)?$}', $line, $match) )) { $result[] = ['type' => $key, 'url' => trim($match[1], '"\' ')]; } elseif ($key !== null && $line === ']') { $key = null; } } foreach ($result as $key => $item) { switch ($item['type']) { case 'community_bridge': $result[$key]['url'] = 'https://funding.communitybridge.org/projects/' . basename($item['url']); break; case 'github': $result[$key]['url'] = 'https://github.com/' . basename($item['url']); break; case 'issuehunt': $result[$key]['url'] = 'https://issuehunt.io/r/' . $item['url']; break; case 'ko_fi': $result[$key]['url'] = 'https://ko-fi.com/' . basename($item['url']); break; case 'liberapay': $result[$key]['url'] = 'https://liberapay.com/' . basename($item['url']); break; case 'open_collective': $result[$key]['url'] = 'https://opencollective.com/' . basename($item['url']); break; case 'patreon': $result[$key]['url'] = 'https://www.patreon.com/' . basename($item['url']); break; case 'tidelift': $result[$key]['url'] = 'https://tidelift.com/funding/github/' . $item['url']; break; case 'polar': $result[$key]['url'] = 'https://polar.sh/' . basename($item['url']); break; case 'buy_me_a_coffee': $result[$key]['url'] = 'https://www.buymeacoffee.com/' . basename($item['url']); break; case 'thanks_dev': $result[$key]['url'] = 'https://thanks.dev/' . $item['url']; break; case 'otechie': $result[$key]['url'] = 'https://otechie.com/' . basename($item['url']); break; case 'custom': $bits = parse_url($item['url']); if ($bits === false) { unset($result[$key]); break; } if (!array_key_exists('scheme', $bits) && !array_key_exists('host', $bits)) { if (Preg::isMatch('{^[a-z0-9-]++\.[a-z]{2,3}$}', $item['url'])) { $result[$key]['url'] = 'https://'.$item['url']; break; } $this->io->writeError('Funding URL '.$item['url'].' not in a supported format.'); unset($result[$key]); break; } break; } } return $this->fundingInfo = $result; } public function getFileContent(string $file, string $identifier): ?string { if ($this->gitDriver) { return $this->gitDriver->getFileContent($file, $identifier); } $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/contents/' . $file . '?ref='.urlencode($identifier); $resource = $this->getContents($resource)->decodeJson(); if ((!isset($resource['content']) || $resource['content'] === '') && $resource['encoding'] === 'none' && isset($resource['git_url'])) { $resource = $this->getContents($resource['git_url'])->decodeJson(); } if (!isset($resource['content']) || $resource['encoding'] !== 'base64' || false === ($content = base64_decode($resource['content']))) { throw new \RuntimeException('Could not retrieve ' . $file . ' for '.$identifier); } return $content; } public function getChangeDate(string $identifier): ?\DateTimeImmutable { if ($this->gitDriver) { return $this->gitDriver->getChangeDate($identifier); } $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/commits/'.urlencode($identifier); $commit = $this->getContents($resource)->decodeJson(); return new \DateTimeImmutable($commit['commit']['committer']['date']); } public function getTags(): array { if ($this->gitDriver) { return $this->gitDriver->getTags(); } if (null === $this->tags) { $tags = []; $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/tags?per_page=100'; do { $response = $this->getContents($resource); $tagsData = $response->decodeJson(); foreach ($tagsData as $tag) { $tags[$tag['name']] = $tag['commit']['sha']; } $resource = $this->getNextPage($response); } while ($resource); $this->tags = $tags; } return $this->tags; } public function getBranches(): array { if ($this->gitDriver) { return $this->gitDriver->getBranches(); } if (null === $this->branches) { $branches = []; $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/git/refs/heads?per_page=100'; do { $response = $this->getContents($resource); $branchData = $response->decodeJson(); foreach ($branchData as $branch) { $name = substr($branch['ref'], 11); if ($name !== 'gh-pages') { $branches[$name] = $branch['object']['sha']; } } $resource = $this->getNextPage($response); } while ($resource); $this->branches = $branches; } return $this->branches; } public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool { if (!Preg::isMatch('#^((?:https?|git)://([^/]+)/|git@([^:]+):/?)([^/]+)/([^/]+?)(?:\.git|/)?$#', $url, $matches)) { return false; } $originUrl = $matches[2] ?? (string) $matches[3]; if (!in_array(strtolower(Preg::replace('{^www\.}i', '', $originUrl)), $config->get('github-domains'))) { return false; } if (!extension_loaded('openssl')) { $io->writeError('Skipping GitHub driver for '.Url::sanitize($url).' because the OpenSSL PHP extension is missing.', true, IOInterface::VERBOSE); return false; } return true; } public function getRepoData(): ?array { $this->fetchRootIdentifier(); return $this->repoData; } protected function generateSshUrl(): string { if (false !== strpos($this->originUrl, ':')) { return 'ssh://git@' . $this->originUrl . '/'.$this->owner.'/'.$this->repository.'.git'; } return 'git@' . $this->originUrl . ':'.$this->owner.'/'.$this->repository.'.git'; } protected function getContents(string $url, bool $fetchingRepoData = false): Response { try { return parent::getContents($url); } catch (TransportException $e) { $gitHubUtil = new GitHub($this->io, $this->config, $this->process, $this->httpDownloader); switch ($e->getCode()) { case 401: case 404: if (!$fetchingRepoData) { throw $e; } if ($gitHubUtil->authorizeOAuth($this->originUrl)) { return parent::getContents($url); } if (!$this->io->isInteractive()) { $this->attemptCloneFallback($e); return new Response(['url' => 'dummy'], 200, [], 'null'); } $scopesIssued = []; $scopesNeeded = []; if ($headers = $e->getHeaders()) { if ($scopes = Response::findHeaderValue($headers, 'X-OAuth-Scopes')) { $scopesIssued = explode(' ', $scopes); } if ($scopes = Response::findHeaderValue($headers, 'X-Accepted-OAuth-Scopes')) { $scopesNeeded = explode(' ', $scopes); } } $scopesFailed = array_diff($scopesNeeded, $scopesIssued); if (!$headers || !count($scopesNeeded) || count($scopesFailed)) { $gitHubUtil->authorizeOAuthInteractively($this->originUrl, 'Your GitHub credentials are required to fetch private repository metadata ('.Url::sanitize($this->url).')'); } return parent::getContents($url); case 403: if (!$this->io->hasAuthentication($this->originUrl) && $gitHubUtil->authorizeOAuth($this->originUrl)) { return parent::getContents($url); } if (!$this->io->isInteractive() && $fetchingRepoData) { $this->attemptCloneFallback($e); return new Response(['url' => 'dummy'], 200, [], 'null'); } $rateLimited = $gitHubUtil->isRateLimited((array) $e->getHeaders()); if (!$this->io->hasAuthentication($this->originUrl)) { if (!$this->io->isInteractive()) { $this->io->writeError('GitHub API limit exhausted. Failed to get metadata for the '.Url::sanitize($this->url).' repository, try running in interactive mode so that you can enter your GitHub credentials to increase the API limit'); throw $e; } $gitHubUtil->authorizeOAuthInteractively($this->originUrl, 'API limit exhausted. Enter your GitHub credentials to get a larger API limit ('.Url::sanitize($this->url).')'); return parent::getContents($url); } if ($rateLimited) { $rateLimit = $gitHubUtil->getRateLimit($e->getHeaders()); $this->io->writeError(sprintf( 'GitHub API limit (%d calls/hr) is exhausted. You are already authorized so you have to wait until %s before doing more requests', $rateLimit['limit'], $rateLimit['reset'] )); } throw $e; default: throw $e; } } } protected function fetchRootIdentifier(): void { if ($this->repoData) { return; } $repoDataUrl = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository; try { $this->repoData = $this->getContents($repoDataUrl, true)->decodeJson(); } catch (TransportException $e) { if ($e->getCode() === 499) { $this->attemptCloneFallback($e); } else { throw $e; } } if (null === $this->repoData && null !== $this->gitDriver) { return; } $this->owner = $this->repoData['owner']['login']; $this->repository = $this->repoData['name']; $this->isPrivate = !empty($this->repoData['private']); if (isset($this->repoData['default_branch'])) { $this->rootIdentifier = $this->repoData['default_branch']; } elseif (isset($this->repoData['master_branch'])) { $this->rootIdentifier = $this->repoData['master_branch']; } else { $this->rootIdentifier = 'master'; } $this->hasIssues = !empty($this->repoData['has_issues']); $this->isArchived = !empty($this->repoData['archived']); } protected function attemptCloneFallback(?\Throwable $e = null): bool { if (!$this->allowGitFallback) { throw new \RuntimeException('Fallback to git driver disabled', 0, $e); } $this->isPrivate = true; try { $this->setupGitDriver($this->generateSshUrl()); return true; } catch (\RuntimeException $e) { $this->gitDriver = null; $this->io->writeError('Failed to clone the '.$this->generateSshUrl().' repository, try running in interactive mode so that you can enter your GitHub credentials'); throw $e; } } protected function setupGitDriver(string $url): void { if (!$this->allowGitFallback) { throw new \RuntimeException('Fallback to git driver disabled'); } $this->gitDriver = new GitDriver( ['url' => $url], $this->io, $this->config, $this->httpDownloader, $this->process ); $this->gitDriver->initialize(); } protected function getNextPage(Response $response): ?string { $header = $response->getHeader('link'); if (!$header) { return null; } $links = explode(',', $header); foreach ($links as $link) { if (Preg::isMatch('{<(.+?)>; *rel="next"}', $link, $match)) { return $match[1]; } } return null; } } https?)://(?P.+?)(?::(?P[0-9]+))?/|git@(?P[^:]+):)(?P.+)/(?P[^/]+?)(?:\.git|/)?$#'; public function initialize(): void { if (!Preg::isMatch(self::URL_REGEX, $this->url, $match)) { throw new \InvalidArgumentException(sprintf('The GitLab repository URL %s is invalid. It must be the HTTP URL of a GitLab project.', Url::sanitize($this->url))); } $guessedDomain = $match['domain'] ?? (string) $match['domain2']; $configuredDomains = $this->config->get('gitlab-domains'); $urlParts = explode('/', $match['parts']); $this->scheme = in_array($match['scheme'], ['https', 'http'], true) ? $match['scheme'] : (isset($this->repoConfig['secure-http']) && $this->repoConfig['secure-http'] === false ? 'http' : 'https') ; $origin = self::determineOrigin($configuredDomains, $guessedDomain, $urlParts, $match['port']); if (false === $origin) { throw new \LogicException('It should not be possible to create a gitlab driver with an unparsable origin URL ('.Url::sanitize($this->url).')'); } $this->originUrl = $origin; if (is_string($protocol = $this->config->get('gitlab-protocol'))) { if (!in_array($protocol, ['git', 'http', 'https'], true)) { throw new \RuntimeException('gitlab-protocol must be one of git, http.'); } $this->protocol = $protocol === 'git' ? 'ssh' : 'http'; } if (false !== strpos($this->originUrl, ':') || false !== strpos($this->originUrl, '/')) { $this->hasNonstandardOrigin = true; } $this->namespace = implode('/', $urlParts); $this->repository = Preg::replace('#(\.git)$#', '', $match['repo']); $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.$this->originUrl.'/'.$this->namespace.'/'.$this->repository); $this->cache->setReadOnly($this->config->get('cache-read-only')); $this->fetchProject(); } public function setHttpDownloader(HttpDownloader $httpDownloader): void { $this->httpDownloader = $httpDownloader; } public function getComposerInformation(string $identifier): ?array { if ($this->gitDriver) { return $this->gitDriver->getComposerInformation($identifier); } if (!isset($this->infoCache[$identifier])) { if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier)) { $composer = JsonFile::parseJson($res); } else { $composer = $this->getBaseComposerInformation($identifier); if ($this->shouldCache($identifier)) { $this->cache->write($identifier, JsonFile::encode($composer, \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES)); } } if (null !== $composer) { if (isset($composer['support']) && !is_array($composer['support'])) { $composer['support'] = []; } if (!isset($composer['support']['source']) && isset($this->project['web_url'])) { $label = array_search($identifier, $this->getTags(), true) ?: array_search($identifier, $this->getBranches(), true) ?: $identifier; $composer['support']['source'] = sprintf('%s/-/tree/%s', $this->project['web_url'], $label); } if (!isset($composer['support']['issues']) && !empty($this->project['issues_enabled']) && isset($this->project['web_url'])) { $composer['support']['issues'] = sprintf('%s/-/issues', $this->project['web_url']); } if (!isset($composer['abandoned']) && !empty($this->project['archived'])) { $composer['abandoned'] = true; } } $this->infoCache[$identifier] = $composer; } return $this->infoCache[$identifier]; } public function getFileContent(string $file, string $identifier): ?string { if ($this->gitDriver) { return $this->gitDriver->getFileContent($file, $identifier); } if (!Preg::isMatch('{[a-f0-9]{40}}i', $identifier)) { $branches = $this->getBranches(); if (isset($branches[$identifier])) { $identifier = $branches[$identifier]; } } $resource = $this->getApiUrl().'/repository/files/'.$this->urlEncodeAll($file).'/raw?ref='.$identifier; try { $content = $this->getContents($resource)->getBody(); } catch (TransportException $e) { if ($e->getCode() !== 404) { throw $e; } return null; } return $content; } public function getChangeDate(string $identifier): ?\DateTimeImmutable { if ($this->gitDriver) { return $this->gitDriver->getChangeDate($identifier); } if (isset($this->commits[$identifier])) { return new \DateTimeImmutable($this->commits[$identifier]['committed_date']); } return null; } public function getRepositoryUrl(): string { if ($this->protocol) { return $this->project["{$this->protocol}_url_to_repo"]; } return $this->isPrivate ? $this->project['ssh_url_to_repo'] : $this->project['http_url_to_repo']; } public function getUrl(): string { if ($this->gitDriver) { return $this->gitDriver->getUrl(); } return $this->project['web_url']; } public function getDist(string $identifier): ?array { $url = $this->getApiUrl().'/repository/archive.zip?sha='.$identifier; return ['type' => 'zip', 'url' => $url, 'reference' => $identifier, 'shasum' => '']; } public function getSource(string $identifier): array { if ($this->gitDriver) { return $this->gitDriver->getSource($identifier); } return ['type' => 'git', 'url' => $this->getRepositoryUrl(), 'reference' => $identifier]; } public function getRootIdentifier(): string { if ($this->gitDriver) { return $this->gitDriver->getRootIdentifier(); } return $this->project['default_branch']; } public function getBranches(): array { if ($this->gitDriver) { return $this->gitDriver->getBranches(); } if (null === $this->branches) { $this->branches = $this->getReferences('branches'); } return $this->branches; } public function getTags(): array { if ($this->gitDriver) { return $this->gitDriver->getTags(); } if (null === $this->tags) { $this->tags = $this->getReferences('tags'); } return $this->tags; } public function getApiUrl(): string { return $this->scheme.'://'.$this->originUrl.'/api/v4/projects/'.$this->urlEncodeAll($this->namespace).'%2F'.$this->urlEncodeAll($this->repository); } private function urlEncodeAll(string $string): string { $encoded = ''; for ($i = 0; isset($string[$i]); $i++) { $character = $string[$i]; if (!ctype_alnum($character) && !in_array($character, ['-', '_'], true)) { $character = '%' . sprintf('%02X', ord($character)); } $encoded .= $character; } return $encoded; } protected function getReferences(string $type): array { $perPage = 100; $resource = $this->getApiUrl().'/repository/'.$type.'?per_page='.$perPage; $references = []; do { $response = $this->getContents($resource); $data = $response->decodeJson(); foreach ($data as $datum) { $references[$datum['name']] = $datum['commit']['id']; $this->commits[$datum['commit']['id']] = $datum['commit']; } if (count($data) >= $perPage) { $resource = $this->getNextPage($response); } else { $resource = false; } } while ($resource); return $references; } protected function fetchProject(): void { if (!is_null($this->project)) { return; } $resource = $this->getApiUrl(); $this->project = $this->getContents($resource, true)->decodeJson(); if (isset($this->project['visibility'])) { $this->isPrivate = $this->project['visibility'] !== 'public'; } else { $this->isPrivate = false; } } protected function attemptCloneFallback(): bool { if ($this->isPrivate === false) { $url = $this->generatePublicUrl(); } else { $url = $this->generateSshUrl(); } try { $this->setupGitDriver($url); return true; } catch (\RuntimeException $e) { $this->gitDriver = null; $this->io->writeError('Failed to clone the '.Url::sanitize($url).' repository, try running in interactive mode so that you can enter your credentials'); throw $e; } } protected function generateSshUrl(): string { if ($this->hasNonstandardOrigin) { return 'ssh://git@'.$this->originUrl.'/'.$this->namespace.'/'.$this->repository.'.git'; } return 'git@' . $this->originUrl . ':'.$this->namespace.'/'.$this->repository.'.git'; } protected function generatePublicUrl(): string { return $this->scheme . '://' . $this->originUrl . '/'.$this->namespace.'/'.$this->repository.'.git'; } protected function setupGitDriver(string $url): void { $this->gitDriver = new GitDriver( ['url' => $url], $this->io, $this->config, $this->httpDownloader, $this->process ); $this->gitDriver->initialize(); } protected function getContents(string $url, bool $fetchingRepoData = false): Response { try { $response = parent::getContents($url); if ($fetchingRepoData) { $json = $response->decodeJson(); if (!isset($json['default_branch']) && isset($json['permissions'])) { $this->isPrivate = $json['visibility'] !== 'public'; $moreThanGuestAccess = false; foreach ($json['permissions'] as $permission) { if ($permission && $permission['access_level'] >= 20) { $moreThanGuestAccess = true; } } if (!$moreThanGuestAccess) { $this->io->writeError('GitLab token with Guest or Planner only access detected'); $this->attemptCloneFallback(); return new Response(['url' => 'dummy'], 200, [], 'null'); } } if (!isset($json['default_branch'])) { if (isset($json['repository_access_level']) && $json['repository_access_level'] === 'disabled') { throw new TransportException('The GitLab repository is disabled in the project', 400); } if (!empty($json['id'])) { $this->isPrivate = false; } throw new TransportException('GitLab API seems to not be authenticated as it did not return a default_branch', 401); } } return $response; } catch (TransportException $e) { $gitLabUtil = new GitLab($this->io, $this->config, $this->process, $this->httpDownloader); switch ($e->getCode()) { case 401: case 404: if (!$fetchingRepoData) { throw $e; } if ($gitLabUtil->authorizeOAuth($this->originUrl)) { return parent::getContents($url); } if ($gitLabUtil->isOAuthExpired($this->originUrl) && $gitLabUtil->authorizeOAuthRefresh($this->scheme, $this->originUrl)) { return parent::getContents($url); } if (!$this->io->isInteractive()) { $this->attemptCloneFallback(); return new Response(['url' => 'dummy'], 200, [], 'null'); } $this->io->writeError('Failed to download ' . $this->namespace . '/' . $this->repository . ':' . $e->getMessage() . ''); $gitLabUtil->authorizeOAuthInteractively($this->scheme, $this->originUrl, 'Your credentials are required to fetch private repository metadata ('.Url::sanitize($this->url).')'); return parent::getContents($url); case 403: if (!$this->io->hasAuthentication($this->originUrl) && $gitLabUtil->authorizeOAuth($this->originUrl)) { return parent::getContents($url); } if (!$this->io->isInteractive() && $fetchingRepoData) { $this->attemptCloneFallback(); return new Response(['url' => 'dummy'], 200, [], 'null'); } throw $e; default: throw $e; } } } public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool { if (!Preg::isMatch(self::URL_REGEX, $url, $match)) { return false; } $scheme = $match['scheme']; $guessedDomain = $match['domain'] ?? (string) $match['domain2']; $urlParts = explode('/', $match['parts']); if (false === self::determineOrigin($config->get('gitlab-domains'), $guessedDomain, $urlParts, $match['port'])) { return false; } if ('https' === $scheme && !extension_loaded('openssl')) { $io->writeError('Skipping GitLab driver for '.Url::sanitize($url).' because the OpenSSL PHP extension is missing.', true, IOInterface::VERBOSE); return false; } return true; } public function getRepoData(): ?array { $this->fetchProject(); return $this->project; } protected function getNextPage(Response $response): ?string { $header = $response->getHeader('link'); $links = explode(',', $header); foreach ($links as $link) { if (Preg::isMatchStrictGroups('{<(.+?)>; *rel="next"}', $link, $match)) { return $match[1]; } } return null; } private static function determineOrigin(array $configuredDomains, string $guessedDomain, array &$urlParts, ?string $portNumber) { $guessedDomain = strtolower($guessedDomain); if (in_array($guessedDomain, $configuredDomains) || (null !== $portNumber && in_array($guessedDomain.':'.$portNumber, $configuredDomains))) { if (null !== $portNumber) { return $guessedDomain.':'.$portNumber; } return $guessedDomain; } if (null !== $portNumber) { $guessedDomain .= ':'.$portNumber; } while (null !== ($part = array_shift($urlParts))) { $guessedDomain .= '/' . $part; if (in_array($guessedDomain, $configuredDomains) || (null !== $portNumber && in_array(Preg::replace('{:\d+}', '', $guessedDomain), $configuredDomains))) { return $guessedDomain; } } return false; } } url)) { $this->repoDir = $this->url; } else { if (!Cache::isUsable($this->config->get('cache-vcs-dir'))) { throw new \RuntimeException('HgDriver requires a usable cache directory, and it looks like you set it to be disabled'); } $cacheDir = $this->config->get('cache-vcs-dir'); $this->repoDir = $cacheDir . '/' . Preg::replace('{[^a-z0-9]}i', '-', Url::sanitize($this->url)) . '/'; $fs = new Filesystem(); $fs->ensureDirectoryExists($cacheDir); if (!is_writable(dirname($this->repoDir))) { throw new \RuntimeException('Can not clone '.Url::sanitize($this->url).' to access package information. The "'.$cacheDir.'" directory is not writable by the current user.'); } $this->config->prohibitUrlByConfig($this->url, $this->io); $hgUtils = new HgUtils($this->io, $this->config, $this->process); if (is_dir($this->repoDir) && 0 === $this->process->execute(['hg', 'summary'], $output, $this->repoDir)) { if (0 !== $this->process->execute(['hg', 'pull'], $output, $this->repoDir)) { $this->io->writeError('Failed to update '.Url::sanitize($this->url).', package information from this repository may be outdated ('.$this->process->getErrorOutput().')'); } } else { $fs->removeDirectory($this->repoDir); $repoDir = $this->repoDir; $command = static function ($url) use ($repoDir): array { return ['hg', 'clone', '--noupdate', '--', $url, $repoDir]; }; $hgUtils->runCommand($command, $this->url, null); } } $this->getTags(); $this->getBranches(); } public function getRootIdentifier(): string { if (null === $this->rootIdentifier) { $this->process->execute(['hg', 'tip', '--template', '{node}'], $output, $this->repoDir); $output = $this->process->splitLines($output); $this->rootIdentifier = $output[0]; } return $this->rootIdentifier; } public function getUrl(): string { return $this->url; } public function getSource(string $identifier): array { return ['type' => 'hg', 'url' => $this->getUrl(), 'reference' => $identifier]; } public function getDist(string $identifier): ?array { return null; } public function getFileContent(string $file, string $identifier): ?string { if (isset($identifier[0]) && $identifier[0] === '-') { throw new \RuntimeException('Invalid hg identifier detected. Identifier must not start with a -, given: ' . $identifier); } $resource = ['hg', 'cat', '-r', $identifier, '--', $file]; $this->process->execute($resource, $content, $this->repoDir); if (!trim($content)) { return null; } return $content; } public function getChangeDate(string $identifier): ?\DateTimeImmutable { if (isset($identifier[0]) && $identifier[0] === '-') { throw new \RuntimeException('Invalid hg identifier detected. Identifier must not start with a -, given: ' . $identifier); } $this->process->execute( ['hg', 'log', '--template', '{date|rfc3339date}', '-r', $identifier], $output, $this->repoDir ); return new \DateTimeImmutable(trim($output), new \DateTimeZone('UTC')); } public function getTags(): array { if (null === $this->tags) { $tags = []; $this->process->execute(['hg', 'tags'], $output, $this->repoDir); foreach ($this->process->splitLines($output) as $tag) { if ($tag && Preg::isMatchStrictGroups('(^([^\s]+)\s+\d+:(.*)$)', $tag, $match)) { $tags[$match[1]] = $match[2]; } } unset($tags['tip']); $this->tags = $tags; } return $this->tags; } public function getBranches(): array { if (null === $this->branches) { $branches = []; $bookmarks = []; $this->process->execute(['hg', 'branches'], $output, $this->repoDir); foreach ($this->process->splitLines($output) as $branch) { if ($branch && Preg::isMatchStrictGroups('(^([^\s]+)\s+\d+:([a-f0-9]+))', $branch, $match) && $match[1][0] !== '-') { $branches[$match[1]] = $match[2]; } } $this->process->execute(['hg', 'bookmarks'], $output, $this->repoDir); foreach ($this->process->splitLines($output) as $branch) { if ($branch && Preg::isMatchStrictGroups('(^(?:[\s*]*)([^\s]+)\s+\d+:(.*)$)', $branch, $match) && $match[1][0] !== '-') { $bookmarks[$match[1]] = $match[2]; } } $this->branches = array_merge($bookmarks, $branches); } return $this->branches; } public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool { if (Preg::isMatch('#(^(?:https?|ssh)://(?:[^@]+@)?bitbucket.org|https://(?:.*?)\.kilnhg.com)#i', $url)) { return true; } if (Filesystem::isLocalPath($url)) { $url = Filesystem::getPlatformPath($url); if (!is_dir($url)) { return false; } $process = new ProcessExecutor($io); if ($process->execute(['hg', 'summary'], $output, $url) === 0) { return true; } } if (!$deep) { return false; } $process = new ProcessExecutor($io); $exit = $process->execute(['hg', 'identify', '--', $url], $ignored); return $exit === 0; } } depot = $this->repoConfig['depot']; $this->branch = ''; if (!empty($this->repoConfig['branch'])) { $this->branch = $this->repoConfig['branch']; } $this->initPerforce($this->repoConfig); $this->perforce->p4Login(); $this->perforce->checkStream(); $this->perforce->writeP4ClientSpec(); $this->perforce->connectClient(); } private function initPerforce(array $repoConfig): void { if (!empty($this->perforce)) { return; } if (!Cache::isUsable($this->config->get('cache-vcs-dir'))) { throw new \RuntimeException('PerforceDriver requires a usable cache directory, and it looks like you set it to be disabled'); } $repoDir = $this->config->get('cache-vcs-dir') . '/' . $this->depot; $this->perforce = Perforce::create($repoConfig, $this->getUrl(), $repoDir, $this->process, $this->io); } public function getFileContent(string $file, string $identifier): ?string { return $this->perforce->getFileContent($file, $identifier); } public function getChangeDate(string $identifier): ?\DateTimeImmutable { return null; } public function getRootIdentifier(): string { return $this->branch; } public function getBranches(): array { return $this->perforce->getBranches(); } public function getTags(): array { return $this->perforce->getTags(); } public function getDist(string $identifier): ?array { return null; } public function getSource(string $identifier): array { return [ 'type' => 'perforce', 'url' => $this->repoConfig['url'], 'reference' => $identifier, 'p4user' => $this->perforce->getUser(), ]; } public function getUrl(): string { return $this->url; } public function hasComposerFile(string $identifier): bool { $composerInfo = $this->perforce->getComposerInformation('//' . $this->depot . '/' . $identifier); return !empty($composerInfo); } public function getContents(string $url): Response { throw new \BadMethodCallException('Not implemented/used in PerforceDriver'); } public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool { if ($deep || Preg::isMatch('#\b(perforce|p4)\b#i', $url)) { return Perforce::checkServerExists($url, new ProcessExecutor($io)); } return false; } public function cleanup(): void { $this->perforce->cleanupClientSpec(); $this->perforce = null; } public function getDepot(): string { return $this->depot; } public function getBranch(): string { return $this->branch; } } url = $this->baseUrl = rtrim(self::normalizeUrl($this->url), '/'); SvnUtil::cleanEnv(); if (isset($this->repoConfig['trunk-path'])) { $this->trunkPath = $this->repoConfig['trunk-path']; } if (isset($this->repoConfig['branches-path'])) { $this->branchesPath = $this->repoConfig['branches-path']; } if (isset($this->repoConfig['tags-path'])) { $this->tagsPath = $this->repoConfig['tags-path']; } if (array_key_exists('svn-cache-credentials', $this->repoConfig)) { $this->cacheCredentials = (bool) $this->repoConfig['svn-cache-credentials']; } if (isset($this->repoConfig['package-path'])) { $this->packagePath = '/' . trim($this->repoConfig['package-path'], '/'); } if (false !== ($pos = strrpos($this->url, '/' . $this->trunkPath))) { $this->baseUrl = substr($this->url, 0, $pos); } $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($this->baseUrl))); $this->cache->setReadOnly($this->config->get('cache-read-only')); $this->getBranches(); $this->getTags(); } public function getRootIdentifier(): string { return $this->rootIdentifier ?: $this->trunkPath; } public function getUrl(): string { return $this->url; } public function getSource(string $identifier): array { return ['type' => 'svn', 'url' => $this->baseUrl, 'reference' => $identifier]; } public function getDist(string $identifier): ?array { return null; } protected function shouldCache(string $identifier): bool { return $this->cache && Preg::isMatch('{@\d+$}', $identifier); } public function getComposerInformation(string $identifier): ?array { if (!isset($this->infoCache[$identifier])) { if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier.'.json')) { if ($res === '""') { $res = 'null'; $this->cache->write($identifier.'.json', $res); } return $this->infoCache[$identifier] = JsonFile::parseJson($res); } try { $composer = $this->getBaseComposerInformation($identifier); } catch (TransportException $e) { $message = $e->getMessage(); if (stripos($message, 'path not found') === false && stripos($message, 'svn: warning: W160013') === false) { throw $e; } $composer = null; } if ($this->shouldCache($identifier)) { $this->cache->write($identifier.'.json', JsonFile::encode($composer, \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES)); } $this->infoCache[$identifier] = $composer; } if (!is_array($this->infoCache[$identifier])) { return null; } return $this->infoCache[$identifier]; } public function getFileContent(string $file, string $identifier): ?string { $identifier = '/' . trim($identifier, '/') . '/'; if (Preg::isMatch('{^(.+?)(@\d+)?/$}', $identifier, $match) && $match[2] !== null) { $path = $match[1]; $rev = $match[2]; } else { $path = $identifier; $rev = ''; } try { $resource = $path.$file; $output = $this->execute(['svn', 'cat'], $this->baseUrl . $resource . $rev); if ('' === trim($output)) { return null; } } catch (\RuntimeException $e) { throw new TransportException($e->getMessage()); } return $output; } public function getChangeDate(string $identifier): ?\DateTimeImmutable { $identifier = '/' . trim($identifier, '/') . '/'; if (Preg::isMatch('{^(.+?)(@\d+)?/$}', $identifier, $match) && null !== $match[2]) { $path = $match[1]; $rev = $match[2]; } else { $path = $identifier; $rev = ''; } $output = $this->execute(['svn', 'info'], $this->baseUrl . $path . $rev); foreach ($this->process->splitLines($output) as $line) { if ($line !== '' && Preg::isMatchStrictGroups('{^Last Changed Date: ([^(]+)}', $line, $match)) { return new \DateTimeImmutable($match[1], new \DateTimeZone('UTC')); } } return null; } public function getTags(): array { if (null === $this->tags) { $tags = []; if ($this->tagsPath !== false) { $output = $this->execute(['svn', 'ls', '--verbose'], $this->baseUrl . '/' . $this->tagsPath); if ($output !== '') { $lastRev = 0; foreach ($this->process->splitLines($output) as $line) { $line = trim($line); if ($line !== '' && Preg::isMatch('{^\s*(\S+).*?(\S+)\s*$}', $line, $match)) { if ($match[2] === './') { $lastRev = (int) $match[1]; } else { $tags[rtrim($match[2], '/')] = $this->buildIdentifier( '/' . $this->tagsPath . '/' . $match[2], max($lastRev, (int) $match[1]) ); } } } } } $this->tags = $tags; } return $this->tags; } public function getBranches(): array { if (null === $this->branches) { $branches = []; if (false === $this->trunkPath) { $trunkParent = $this->baseUrl . '/'; } else { $trunkParent = $this->baseUrl . '/' . $this->trunkPath; } $output = $this->execute(['svn', 'ls', '--verbose'], $trunkParent); if ($output !== '') { foreach ($this->process->splitLines($output) as $line) { $line = trim($line); if ($line !== '' && Preg::isMatch('{^\s*(\S+).*?(\S+)\s*$}', $line, $match)) { if ($match[2] === './') { $branches['trunk'] = $this->buildIdentifier( '/' . $this->trunkPath, (int) $match[1] ); $this->rootIdentifier = $branches['trunk']; break; } } } } unset($output); if ($this->branchesPath !== false) { $output = $this->execute(['svn', 'ls', '--verbose'], $this->baseUrl . '/' . $this->branchesPath); if ($output !== '') { $lastRev = 0; foreach ($this->process->splitLines(trim($output)) as $line) { $line = trim($line); if ($line !== '' && Preg::isMatch('{^\s*(\S+).*?(\S+)\s*$}', $line, $match)) { if ($match[2] === './') { $lastRev = (int) $match[1]; } else { $branches[rtrim($match[2], '/')] = $this->buildIdentifier( '/' . $this->branchesPath . '/' . $match[2], max($lastRev, (int) $match[1]) ); } } } } } $this->branches = $branches; } return $this->branches; } public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool { $url = self::normalizeUrl($url); if (Preg::isMatch('#(^svn://|^svn\+ssh://|svn\.)#i', $url)) { return true; } if (!$deep && !Filesystem::isLocalPath($url)) { return false; } $process = new ProcessExecutor($io); $exit = $process->execute(['svn', 'info', '--non-interactive', '--', $url], $ignoredOutput); if ($exit === 0) { return true; } if (false !== stripos($process->getErrorOutput(), 'authorization failed:')) { return true; } if (false !== stripos($process->getErrorOutput(), 'Authentication failed')) { return true; } return false; } protected static function normalizeUrl(string $url): string { $fs = new Filesystem(); if ($fs->isAbsolutePath($url)) { return 'file://' . strtr($url, '\\', '/'); } return $url; } protected function execute(array $command, string $url): string { if (null === $this->util) { $this->util = new SvnUtil($this->baseUrl, $this->io, $this->config, $this->process); $this->util->setCacheCredentials($this->cacheCredentials); } try { return $this->util->execute($command, $url); } catch (\RuntimeException $e) { if (null === $this->util->binaryVersion()) { throw new \RuntimeException('Failed to load '.Url::sanitize($this->url).', svn was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput()); } throw new \RuntimeException( 'Repository '.Url::sanitize($this->url).' could not be processed, '.Url::sanitize($e->getMessage()) ); } } protected function buildIdentifier(string $baseDir, int $revision): string { return rtrim($baseDir, '/') . $this->packagePath . '/@' . $revision; } } url = $repoConfig['url']; $this->originUrl = $repoConfig['url']; $this->repoConfig = $repoConfig; $this->io = $io; $this->config = $config; $this->httpDownloader = $httpDownloader; $this->process = $process; } protected function shouldCache(string $identifier): bool { return $this->cache && Preg::isMatch('{^[a-f0-9]{40}$}iD', $identifier); } public function getComposerInformation(string $identifier): ?array { if (!isset($this->infoCache[$identifier])) { if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier)) { return $this->infoCache[$identifier] = JsonFile::parseJson($res); } $composer = $this->getBaseComposerInformation($identifier); if ($this->shouldCache($identifier)) { $this->cache->write($identifier, JsonFile::encode($composer, \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES)); } $this->infoCache[$identifier] = $composer; } return $this->infoCache[$identifier]; } protected function getBaseComposerInformation(string $identifier): ?array { $composerFileContent = $this->getFileContent('composer.json', $identifier); if (!$composerFileContent) { return null; } $composer = JsonFile::parseJson($composerFileContent, $identifier . ':composer.json'); if ([] === $composer || !is_array($composer)) { return null; } if (empty($composer['time']) && null !== ($changeDate = $this->getChangeDate($identifier))) { $composer['time'] = $changeDate->format(DATE_RFC3339); } return $composer; } public function hasComposerFile(string $identifier): bool { try { return null !== $this->getComposerInformation($identifier); } catch (TransportException $e) { } return false; } protected function getScheme(): string { if (extension_loaded('openssl')) { return 'https'; } return 'http'; } protected function getContents(string $url): Response { $options = $this->repoConfig['options'] ?? []; return $this->httpDownloader->get($url, $options); } public function cleanup(): void { } } drivers = $drivers ?: [ 'github' => 'Composer\Repository\Vcs\GitHubDriver', 'gitlab' => 'Composer\Repository\Vcs\GitLabDriver', 'bitbucket' => 'Composer\Repository\Vcs\GitBitbucketDriver', 'git-bitbucket' => 'Composer\Repository\Vcs\GitBitbucketDriver', 'forgejo' => 'Composer\Repository\Vcs\ForgejoDriver', 'git' => 'Composer\Repository\Vcs\GitDriver', 'hg' => 'Composer\Repository\Vcs\HgDriver', 'perforce' => 'Composer\Repository\Vcs\PerforceDriver', 'fossil' => 'Composer\Repository\Vcs\FossilDriver', 'svn' => 'Composer\Repository\Vcs\SvnDriver', ]; $this->url = $repoConfig['url'] = Platform::expandPath($repoConfig['url']); $this->io = $io; $this->type = $repoConfig['type'] ?? 'vcs'; $this->isVerbose = $io->isVerbose(); $this->isVeryVerbose = $io->isVeryVerbose(); $this->config = $config; $this->repoConfig = $repoConfig; $this->versionCache = $versionCache; $this->httpDownloader = $httpDownloader; $this->processExecutor = $process ?? new ProcessExecutor($io); } public function getRepoName() { $driverClass = get_class($this->getDriver()); $driverType = array_search($driverClass, $this->drivers); if (!$driverType) { $driverType = $driverClass; } return 'vcs repo ('.$driverType.' '.Url::sanitize($this->url).')'; } public function getRepoConfig() { return $this->repoConfig; } public function setLoader(LoaderInterface $loader): void { $this->loader = $loader; } public function getDriver(): ?VcsDriverInterface { if ($this->driver) { return $this->driver; } if (isset($this->drivers[$this->type])) { $class = $this->drivers[$this->type]; $this->driver = new $class($this->repoConfig, $this->io, $this->config, $this->httpDownloader, $this->processExecutor); $this->driver->initialize(); return $this->driver; } foreach ($this->drivers as $driver) { if ($driver::supports($this->io, $this->config, $this->url)) { $this->driver = new $driver($this->repoConfig, $this->io, $this->config, $this->httpDownloader, $this->processExecutor); $this->driver->initialize(); return $this->driver; } } foreach ($this->drivers as $driver) { if ($driver::supports($this->io, $this->config, $this->url, true)) { $this->driver = new $driver($this->repoConfig, $this->io, $this->config, $this->httpDownloader, $this->processExecutor); $this->driver->initialize(); return $this->driver; } } return null; } public function hadInvalidBranches(): bool { return $this->branchErrorOccurred; } public function getEmptyReferences(): array { return $this->emptyReferences; } public function getVersionTransportExceptions(): array { return $this->versionTransportExceptions; } protected function initialize() { parent::initialize(); $isVerbose = $this->isVerbose; $isVeryVerbose = $this->isVeryVerbose; $driver = $this->getDriver(); if (!$driver) { throw new \InvalidArgumentException('No driver found to handle VCS repository '.Url::sanitize($this->url)); } $this->versionParser = new VersionParser; if (!$this->loader) { $this->loader = new ArrayLoader($this->versionParser); } $hasRootIdentifierComposerJson = false; try { $hasRootIdentifierComposerJson = $driver->hasComposerFile($driver->getRootIdentifier()); if ($hasRootIdentifierComposerJson) { $data = $driver->getComposerInformation($driver->getRootIdentifier()); $this->packageName = !empty($data['name']) ? $data['name'] : null; } } catch (\Exception $e) { if ($e instanceof TransportException && $this->shouldRethrowTransportException($e)) { throw $e; } if ($isVeryVerbose) { $this->io->writeError('Skipped parsing '.$driver->getRootIdentifier().', '.$e->getMessage().''); } } foreach ($driver->getTags() as $tag => $identifier) { $tag = (string) $tag; $msg = 'Reading composer.json of ' . ($this->packageName ?: Url::sanitize($this->url)) . ' (' . $tag . ')'; $tag = str_replace('release-', '', $tag); $cachedPackage = $this->getCachedPackageVersion($tag, $identifier, $isVerbose, $isVeryVerbose); if ($cachedPackage) { $this->addPackage($cachedPackage); continue; } if ($cachedPackage === false) { $this->emptyReferences[] = $identifier; continue; } if (!$parsedTag = $this->validateTag($tag)) { if ($isVeryVerbose) { $this->io->writeError('Skipped tag '.$tag.', invalid tag name'); } continue; } if ($isVeryVerbose) { $this->io->writeError($msg); } elseif ($isVerbose) { $this->io->overwriteError($msg, false); } try { $data = $driver->getComposerInformation($identifier); if (null === $data) { if ($isVeryVerbose) { $this->io->writeError('Skipped tag '.$tag.', no composer file'); } $this->emptyReferences[] = $identifier; continue; } if (isset($data['version'])) { $data['version_normalized'] = $this->versionParser->normalize($data['version']); } else { $data['version'] = $tag; $data['version_normalized'] = $parsedTag; } $data['version'] = Preg::replace('{[.-]?dev$}i', '', $data['version']); $data['version_normalized'] = Preg::replace('{(^dev-|[.-]?dev$)}i', '', $data['version_normalized']); unset($data['default-branch']); if ($data['version_normalized'] !== $parsedTag) { if ($isVeryVerbose) { if (Preg::isMatch('{(^dev-|[.-]?dev$)}i', $parsedTag)) { $this->io->writeError('Skipped tag '.$tag.', invalid tag name, tags can not use dev prefixes or suffixes'); } else { $this->io->writeError('Skipped tag '.$tag.', tag ('.$parsedTag.') does not match version ('.$data['version_normalized'].') in composer.json'); } } continue; } $tagPackageName = $this->packageName ?: ($data['name'] ?? ''); if ($existingPackage = $this->findPackage($tagPackageName, $data['version_normalized'])) { if ($isVeryVerbose) { $this->io->writeError('Skipped tag '.$tag.', it conflicts with an another tag ('.$existingPackage->getPrettyVersion().') as both resolve to '.$data['version_normalized'].' internally'); } continue; } if ($isVeryVerbose) { $this->io->writeError('Importing tag '.$tag.' ('.$data['version_normalized'].')'); } $this->addPackage($this->loader->load($this->preProcess($driver, $data, $identifier))); } catch (\Exception $e) { if ($e instanceof TransportException) { $this->versionTransportExceptions['tags'][$tag] = $e; if ($e->getCode() === 404) { $this->emptyReferences[] = $identifier; } if ($this->shouldRethrowTransportException($e)) { throw $e; } } if ($isVeryVerbose) { $this->io->writeError('Skipped tag '.$tag.', '.($e instanceof TransportException ? 'no composer file was found (' . $e->getCode() . ' HTTP status code)' : $e->getMessage()).''); } continue; } } if (!$isVeryVerbose) { $this->io->overwriteError('', false); } $branches = $driver->getBranches(); if ($hasRootIdentifierComposerJson && isset($branches[$driver->getRootIdentifier()])) { $branches = [$driver->getRootIdentifier() => $branches[$driver->getRootIdentifier()]] + $branches; } foreach ($branches as $branch => $identifier) { $branch = (string) $branch; $msg = 'Reading composer.json of ' . ($this->packageName ?: Url::sanitize($this->url)) . ' (' . $branch . ')'; if ($isVeryVerbose) { $this->io->writeError($msg); } elseif ($isVerbose) { $this->io->overwriteError($msg, false); } if (!$parsedBranch = $this->validateBranch($branch)) { if ($isVeryVerbose) { $this->io->writeError('Skipped branch '.$branch.', invalid name'); } continue; } if (strpos($parsedBranch, 'dev-') === 0 || VersionParser::DEFAULT_BRANCH_ALIAS === $parsedBranch) { $version = 'dev-' . str_replace('#', '+', $branch); $parsedBranch = str_replace('#', '+', $parsedBranch); } else { $prefix = strpos($branch, 'v') === 0 ? 'v' : ''; $version = $prefix . Preg::replace('{(\.9{7})+}', '.x', $parsedBranch); } $cachedPackage = $this->getCachedPackageVersion($version, $identifier, $isVerbose, $isVeryVerbose, $driver->getRootIdentifier() === $branch); if ($cachedPackage) { $this->addPackage($cachedPackage); continue; } if ($cachedPackage === false) { $this->emptyReferences[] = $identifier; continue; } try { $data = $driver->getComposerInformation($identifier); if (null === $data) { if ($isVeryVerbose) { $this->io->writeError('Skipped branch '.$branch.', no composer file'); } $this->emptyReferences[] = $identifier; continue; } $data['version'] = $version; $data['version_normalized'] = $parsedBranch; unset($data['default-branch']); if ($driver->getRootIdentifier() === $branch) { $data['default-branch'] = true; } if ($isVeryVerbose) { $this->io->writeError('Importing branch '.$branch.' ('.$data['version'].')'); } $packageData = $this->preProcess($driver, $data, $identifier); $package = $this->loader->load($packageData); if ($this->loader instanceof ValidatingArrayLoader && \count($this->loader->getWarnings()) > 0) { throw new InvalidPackageException($this->loader->getErrors(), $this->loader->getWarnings(), $packageData); } $this->addPackage($package); } catch (TransportException $e) { $this->versionTransportExceptions['branches'][$branch] = $e; if ($e->getCode() === 404) { $this->emptyReferences[] = $identifier; } if ($this->shouldRethrowTransportException($e)) { throw $e; } if ($isVeryVerbose) { $this->io->writeError('Skipped branch '.$branch.', no composer file was found (' . $e->getCode() . ' HTTP status code)'); } continue; } catch (\Exception $e) { if (!$isVeryVerbose) { $this->io->writeError(''); } $this->branchErrorOccurred = true; $this->io->writeError('Skipped branch '.$branch.', '.$e->getMessage().''); $this->io->writeError(''); continue; } } $driver->cleanup(); if (!$isVeryVerbose) { $this->io->overwriteError('', false); } if (!$this->getPackages()) { throw new InvalidRepositoryException('No valid composer.json was found in any branch or tag of '.Url::sanitize($this->url).', could not load a package from it.'); } } protected function preProcess(VcsDriverInterface $driver, array $data, string $identifier): array { $dataPackageName = $data['name'] ?? null; $data['name'] = $this->packageName ?: $dataPackageName; if (!isset($data['dist'])) { $data['dist'] = $driver->getDist($identifier); } if (!isset($data['source'])) { $data['source'] = $driver->getSource($identifier); } if (is_array($data['dist']) && !isset($data['dist']['reference']) && isset($data['source']['reference'])) { $data['dist']['reference'] = $data['source']['reference']; } return $data; } private function validateBranch(string $branch) { try { $normalizedBranch = $this->versionParser->normalizeBranch($branch); $this->versionParser->parseConstraints($normalizedBranch); return $normalizedBranch; } catch (\Exception $e) { } return false; } private function validateTag(string $version) { try { return $this->versionParser->normalize($version); } catch (\Exception $e) { } return false; } private function getCachedPackageVersion(string $version, string $identifier, bool $isVerbose, bool $isVeryVerbose, bool $isDefaultBranch = false) { if (!$this->versionCache) { return null; } $cachedPackage = $this->versionCache->getVersionPackage($version, $identifier); if ($cachedPackage === false) { if ($isVeryVerbose) { $this->io->writeError('Skipped '.$version.', no composer file (cached from ref '.$identifier.')'); } return false; } if ($cachedPackage) { $msg = 'Found cached composer.json of ' . ($this->packageName ?: Url::sanitize($this->url)) . ' (' . $version . ')'; if ($isVeryVerbose) { $this->io->writeError($msg); } elseif ($isVerbose) { $this->io->overwriteError($msg, false); } unset($cachedPackage['default-branch']); if ($isDefaultBranch) { $cachedPackage['default-branch'] = true; } if ($existingPackage = $this->findPackage($cachedPackage['name'], new Constraint('=', $cachedPackage['version_normalized']))) { if ($isVeryVerbose) { $this->io->writeError('Skipped cached version '.$version.', it conflicts with an another tag ('.$existingPackage->getPrettyVersion().') as both resolve to '.$cachedPackage['version_normalized'].' internally'); } $cachedPackage = null; } } if ($cachedPackage) { return $this->loader->load($cachedPackage); } return null; } private function shouldRethrowTransportException(TransportException $e): bool { return in_array($e->getCode(), [401, 403, 429], true) || $e->getCode() >= 500; } } devMode; } public function setDevPackageNames(array $devPackageNames) { $this->devPackageNames = $devPackageNames; } public function getDevPackageNames() { return $this->devPackageNames; } public function write(bool $devMode, InstallationManager $installationManager) { $this->devMode = $devMode; } public function reload() { $this->devMode = null; } } composer = $composer; $this->io = $io; $this->devMode = $devMode; } public function getComposer(): Composer { return $this->composer; } public function getIO(): IOInterface { return $this->io; } public function isDevMode(): bool { return $this->devMode; } public function getOriginatingEvent(): ?BaseEvent { return $this->originatingEvent; } public function setOriginatingEvent(BaseEvent $event): self { $this->originatingEvent = $this->calculateOriginatingEvent($event); return $this; } private function calculateOriginatingEvent(BaseEvent $event): BaseEvent { if ($event instanceof Event && $event->getOriginatingEvent()) { return $this->calculateOriginatingEvent($event->getOriginatingEvent()); } return $event; } } httpDownloader = $httpDownloader; $this->config = $config; } public function getChannel(): string { if ($this->channel) { return $this->channel; } $channelFile = $this->config->get('home').'/update-channel'; if (file_exists($channelFile)) { $channel = trim(file_get_contents($channelFile)); if (in_array($channel, ['stable', 'preview', 'snapshot', '2.2'], true)) { return $this->channel = $channel; } } return $this->channel = 'stable'; } public function setChannel(string $channel, ?IOInterface $io = null): void { if (!in_array($channel, self::CHANNELS, true)) { throw new \InvalidArgumentException('Invalid channel '.$channel.', must be one of: ' . implode(', ', self::CHANNELS)); } $channelFile = $this->config->get('home').'/update-channel'; $this->channel = $channel; $storedChannel = Preg::isMatch('{^\d+$}D', $channel) ? 'stable' : $channel; $previouslyStored = file_exists($channelFile) ? trim((string) file_get_contents($channelFile)) : null; file_put_contents($channelFile, $storedChannel.PHP_EOL); if ($io !== null && $previouslyStored !== $storedChannel) { $io->writeError('Storing "'.$storedChannel.'" as default update channel for the next self-update run.'); } } public function getLatest(?string $channel = null): array { $versions = $this->getVersionsData(); foreach ($versions[$channel ?: $this->getChannel()] as $version) { if ($version['min-php'] <= \PHP_VERSION_ID) { return $version; } } throw new \UnexpectedValueException('There is no version of Composer available for your PHP version ('.PHP_VERSION.')'); } private function getVersionsData(): array { if (null === $this->versionsData) { if ($this->config->get('disable-tls') === true) { $protocol = 'http'; } else { $protocol = 'https'; } $this->versionsData = $this->httpDownloader->get($protocol . '://getcomposer.org/versions')->decodeJson(); } return $this->versionsData; } } io = $io; $this->config = $config; } public function storeAuth(string $origin, $storeAuth): void { $store = false; $configSource = $this->config->getAuthConfigSource(); if ($storeAuth === true) { $store = $configSource; } elseif ($storeAuth === 'prompt') { $answer = $this->io->askAndValidate( 'Do you want to store credentials for '.$origin.' in '.$configSource->getName().' ? [Yn] ', static function ($value): string { $input = strtolower(substr(trim($value), 0, 1)); if (in_array($input, ['y','n'])) { return $input; } throw new \RuntimeException('Please answer (y)es or (n)o'); }, null, 'y' ); if ($answer === 'y') { $store = $configSource; } } if ($store) { $store->addConfigSetting( 'http-basic.'.$origin, $this->io->getAuthentication($origin) ); } } public function promptAuthIfNeeded(string $url, string $origin, int $statusCode, ?string $reason = null, array $headers = [], int $retryCount = 0, ?string $responseBody = null): array { $storeAuth = false; if (in_array($origin, $this->config->get('github-domains'), true)) { $gitHubUtil = new GitHub($this->io, $this->config, null); $message = "\n"; $rateLimited = $gitHubUtil->isRateLimited($headers); $requiresSso = $gitHubUtil->requiresSso($headers); if ($requiresSso) { $ssoUrl = $gitHubUtil->getSsoUrl($headers); $message = 'GitHub API token requires SSO authorization. Authorize this token at ' . Url::sanitize($ssoUrl ?? '') . "\n"; $this->io->writeError($message); if (!$this->io->isInteractive()) { throw new TransportException('Could not authenticate against ' . $origin, 403); } $this->io->ask('After authorizing your token, confirm that you would like to retry the request'); return ['retry' => true, 'storeAuth' => $storeAuth]; } if ($rateLimited) { $rateLimit = $gitHubUtil->getRateLimit($headers); if ($this->io->hasAuthentication($origin)) { $message = 'Review your configured GitHub OAuth token or enter a new one to go over the API rate limit.'; } else { $message = 'Create a GitHub OAuth token to go over the API rate limit.'; } $message = sprintf( 'GitHub API limit (%d calls/hr) is exhausted, could not fetch '.Url::sanitize($url).'. '.$message.' You can also wait until %s for the rate limit to reset.', $rateLimit['limit'], $rateLimit['reset'] )."\n"; } else { $gitHubApiMessage = null; if ($responseBody !== null) { $decoded = json_decode($responseBody, true); if (is_array($decoded) && isset($decoded['message']) && is_string($decoded['message'])) { $gitHubApiMessage = $decoded['message']; } } if ($gitHubApiMessage !== null) { $message .= 'Could not fetch '.Url::sanitize($url).': '.$gitHubApiMessage; } else { $message .= 'Could not fetch '.Url::sanitize($url).', please '; if ($this->io->hasAuthentication($origin)) { $message .= 'review your configured GitHub OAuth token or enter a new one to access private repos'; } else { $message .= 'create a GitHub OAuth token to access private repos'; } } } if (!$gitHubUtil->authorizeOAuth($origin) && (!$this->io->isInteractive() || !$gitHubUtil->authorizeOAuthInteractively($origin, $message)) ) { throw new TransportException('Could not authenticate against '.$origin, 401); } } elseif (in_array($origin, $this->config->get('gitlab-domains'), true)) { $message = "\n".'Could not fetch '.Url::sanitize($url).', enter your ' . $origin . ' credentials ' .($statusCode === 401 ? 'to access private repos' : 'to go over the API rate limit'); $gitLabUtil = new GitLab($this->io, $this->config, null); $auth = null; if ($this->io->hasAuthentication($origin)) { $auth = $this->io->getAuthentication($origin); if (in_array($auth['password'], ['gitlab-ci-token', 'private-token', 'oauth2'], true)) { throw new TransportException("Invalid credentials for '" . Url::sanitize($url) . "', aborting.", $statusCode); } } if (!$gitLabUtil->authorizeOAuth($origin) && (!$this->io->isInteractive() || !$gitLabUtil->authorizeOAuthInteractively(parse_url($url, PHP_URL_SCHEME), $origin, $message)) ) { throw new TransportException('Could not authenticate against '.$origin, 401); } if ($auth !== null && $this->io->hasAuthentication($origin)) { if ($auth === $this->io->getAuthentication($origin)) { throw new TransportException("Invalid credentials for '" . Url::sanitize($url) . "', aborting.", $statusCode); } } } elseif ($origin === 'bitbucket.org' || $origin === 'api.bitbucket.org') { $askForOAuthToken = true; $origin = 'bitbucket.org'; if ($this->io->hasAuthentication($origin)) { $auth = $this->io->getAuthentication($origin); if ($auth['username'] !== 'x-token-auth') { $bitbucketUtil = new Bitbucket($this->io, $this->config); $accessToken = $bitbucketUtil->requestToken($origin, $auth['username'], $auth['password']); if (!empty($accessToken)) { $this->io->setAuthentication($origin, 'x-token-auth', $accessToken); $askForOAuthToken = false; } } elseif (!isset($this->bitbucketRetry[$url])) { $askForOAuthToken = false; $this->bitbucketRetry[$url] = true; } else { throw new TransportException('Could not authenticate against ' . $origin, 401); } } if ($askForOAuthToken) { $message = "\n".'Could not fetch ' . Url::sanitize($url) . ', please create a bitbucket OAuth token to ' . (($statusCode === 401 || $statusCode === 403) ? 'access private repos' : 'go over the API rate limit'); $bitBucketUtil = new Bitbucket($this->io, $this->config); if (!$bitBucketUtil->authorizeOAuth($origin) && (!$this->io->isInteractive() || !$bitBucketUtil->authorizeOAuthInteractively($origin, $message)) ) { throw new TransportException('Could not authenticate against ' . $origin, 401); } } } else { if ($statusCode === 404) { return ['retry' => false, 'storeAuth' => false]; } if (!$this->io->isInteractive()) { if ($statusCode === 401) { $message = "The '" . Url::sanitize($url) . "' URL required authentication (HTTP 401).\nYou must be using the interactive console to authenticate"; } elseif ($statusCode === 403) { $message = "The '" . Url::sanitize($url) . "' URL could not be accessed (HTTP 403): " . $reason; } else { $message = "Unknown error code '" . $statusCode . "', reason: " . $reason; } throw new TransportException($message, $statusCode); } if ($this->io->hasAuthentication($origin)) { if ($retryCount === 0) { return ['retry' => true, 'storeAuth' => false]; } throw new TransportException("Invalid credentials (HTTP $statusCode) for '" . Url::sanitize($url) . "', aborting.", $statusCode); } $this->io->writeError(' Authentication required ('.$origin.'):'); $username = $this->io->ask(' Username: '); $password = $this->io->askAndHideAnswer(' Password: '); $this->io->setAuthentication($origin, $username, $password); $storeAuth = $this->config->get('store-auths'); } return ['retry' => true, 'storeAuth' => $storeAuth]; } public function addAuthenticationHeader(array $headers, string $origin, string $url): array { trigger_error('AuthHelper::addAuthenticationHeader is deprecated since Composer 2.9 use addAuthenticationOptions instead.', E_USER_DEPRECATED); $options = ['http' => ['header' => &$headers]]; $options = $this->addAuthenticationOptions($options, $origin, $url); return $options['http']['header']; } public function addAuthenticationOptions(array $options, string $origin, string $url): array { if (!isset($options['http'])) { $options['http'] = []; } if (!isset($options['http']['header'])) { $options['http']['header'] = []; } $headers = &$options['http']['header']; $authOrigin = self::findAuthOrigin($this->io, $origin); if ($authOrigin !== null) { $origin = $authOrigin; $authenticationDisplayMessage = null; $auth = $this->io->getAuthentication($origin); if ($auth['password'] === 'bearer') { $headers[] = 'Authorization: Bearer '.$auth['username']; } elseif ($auth['password'] === 'custom-headers') { $customHeaders = null; if (is_string($auth['username'])) { $customHeaders = json_decode($auth['username'], true); } if (is_array($customHeaders)) { foreach ($customHeaders as $header) { $headers[] = $header; } $authenticationDisplayMessage = 'Using custom HTTP headers for authentication'; } } elseif ('github.com' === $origin && 'x-oauth-basic' === $auth['password']) { if (Preg::isMatch('{^https?://api\.github\.com/}', $url)) { $headers[] = 'Authorization: token '.$auth['username']; $authenticationDisplayMessage = 'Using GitHub token authentication'; } } elseif ( in_array($auth['password'], ['oauth2', 'private-token', 'gitlab-ci-token'], true) && in_array($origin, $this->config->get('gitlab-domains'), true) ) { if ($auth['password'] === 'oauth2') { $headers[] = 'Authorization: Bearer '.$auth['username']; $authenticationDisplayMessage = 'Using GitLab OAuth token authentication'; } else { $headers[] = 'PRIVATE-TOKEN: '.$auth['username']; $authenticationDisplayMessage = 'Using GitLab private token authentication'; } } elseif ( 'bitbucket.org' === $origin && $url !== Bitbucket::OAUTH2_ACCESS_TOKEN_URL && 'x-token-auth' === $auth['username'] ) { if (!$this->isPublicBitBucketDownload($url)) { $headers[] = 'Authorization: Bearer ' . $auth['password']; $authenticationDisplayMessage = 'Using Bitbucket OAuth token authentication'; } } elseif ('client-certificate' === $auth['username']) { $options['ssl'] = array_merge($options['ssl'] ?? [], json_decode((string) $auth['password'], true)); $authenticationDisplayMessage = 'Using SSL client certificate'; } else { $authStr = base64_encode($auth['username'] . ':' . $auth['password']); $headers[] = 'Authorization: Basic '.$authStr; $authenticationDisplayMessage = 'Using HTTP basic authentication with username "' . $auth['username'] . '"'; } if ($authenticationDisplayMessage && (!isset($this->displayedOriginAuthentications[$origin]) || $this->displayedOriginAuthentications[$origin] !== $authenticationDisplayMessage)) { $this->io->writeError($authenticationDisplayMessage, true, IOInterface::DEBUG); $this->displayedOriginAuthentications[$origin] = $authenticationDisplayMessage; } } return $options; } public static function findAuthOrigin(IOInterface $io, string $origin ): ?string { if ($io->hasAuthentication($origin)) { return $origin; } if (in_array($origin, ['api.bitbucket.org', 'api.github.com'], true)) { $canonical = str_replace('api.', '', $origin); if ($io->hasAuthentication($canonical)) { return $canonical; } } return null; } public function isPublicBitBucketDownload(string $urlToBitBucketFile): bool { $domain = parse_url($urlToBitBucketFile, PHP_URL_HOST); if (strpos($domain, 'bitbucket.org') === false) { return true; } $path = parse_url($urlToBitBucketFile, PHP_URL_PATH); $pathParts = explode('/', $path); return count($pathParts) >= 4 && $pathParts[3] === 'downloads'; } } io = $io; $this->config = $config; $this->process = $process ?: new ProcessExecutor($io); $this->httpDownloader = $httpDownloader ?: Factory::createHttpDownloader($this->io, $config); $this->time = $time; } public function getToken(): string { if (!isset($this->token['access_token'])) { return ''; } return $this->token['access_token']; } public function authorizeOAuth(string $originUrl): bool { if ($originUrl !== 'bitbucket.org') { return false; } if (0 === $this->process->execute(['git', 'config', 'bitbucket.accesstoken'], $output)) { $this->io->setAuthentication($originUrl, 'x-token-auth', trim($output)); return true; } return false; } private function requestAccessToken(): bool { try { $response = $this->httpDownloader->get(self::OAUTH2_ACCESS_TOKEN_URL, [ 'retry-auth-failure' => false, 'http' => [ 'method' => 'POST', 'content' => 'grant_type=client_credentials', ], ]); $token = $response->decodeJson(); if (!isset($token['expires_in']) || !isset($token['access_token'])) { throw new \LogicException('Expected a token configured with expires_in and access_token present, got '.json_encode($token)); } $this->token = $token; } catch (TransportException $e) { if ($e->getCode() === 400) { $this->io->writeError('Invalid OAuth consumer provided.'); $this->io->writeError('This can have three reasons:'); $this->io->writeError('1. You are authenticating with a bitbucket username/password combination'); $this->io->writeError('2. You are using an OAuth consumer, but didn\'t configure a (dummy) callback url'); $this->io->writeError('3. You are using an OAuth consumer, but didn\'t configure it as private consumer'); return false; } if (in_array($e->getCode(), [403, 401])) { $this->io->writeError('Invalid OAuth consumer provided.'); $this->io->writeError('You can also add it manually later by using "composer config --global --auth bitbucket-oauth.bitbucket.org "'); return false; } throw $e; } return true; } public function authorizeOAuthInteractively(string $originUrl, ?string $message = null): bool { if ($message) { $this->io->writeError($message); } $localAuthConfig = $this->config->getLocalAuthConfigSource(); $url = 'https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/'; $this->io->writeError('Follow the instructions here:'); $this->io->writeError($url); $this->io->writeError(sprintf('to create a consumer. It will be stored in "%s" for future use by Composer.', ($localAuthConfig !== null ? $localAuthConfig->getName() . ' OR ' : '') . $this->config->getAuthConfigSource()->getName())); $this->io->writeError('Ensure you enter a "Callback URL" (http://example.com is fine) or it will not be possible to create an Access Token (this callback url will not be used by composer)'); $storeInLocalAuthConfig = false; if ($localAuthConfig !== null) { $storeInLocalAuthConfig = $this->io->askConfirmation('A local auth config source was found, do you want to store the token there?', true); } $consumerKey = trim((string) $this->io->askAndHideAnswer('Consumer Key (hidden): ')); if (!$consumerKey) { $this->io->writeError('No consumer key given, aborting.'); $this->io->writeError('You can also add it manually later by using "composer config --global --auth bitbucket-oauth.bitbucket.org "'); return false; } $consumerSecret = trim((string) $this->io->askAndHideAnswer('Consumer Secret (hidden): ')); if (!$consumerSecret) { $this->io->writeError('No consumer secret given, aborting.'); $this->io->writeError('You can also add it manually later by using "composer config --global --auth bitbucket-oauth.bitbucket.org "'); return false; } $this->io->setAuthentication($originUrl, $consumerKey, $consumerSecret); if (!$this->requestAccessToken()) { return false; } $authConfigSource = $storeInLocalAuthConfig && $localAuthConfig !== null ? $localAuthConfig : $this->config->getAuthConfigSource(); $this->storeInAuthConfig($authConfigSource, $originUrl, $consumerKey, $consumerSecret); $this->config->getAuthConfigSource()->removeConfigSetting('http-basic.' . $originUrl); $this->io->writeError('Consumer stored successfully.'); return true; } public function requestToken(string $originUrl, string $consumerKey, string $consumerSecret): string { if ($this->token !== null || $this->getTokenFromConfig($originUrl)) { return $this->token['access_token']; } $this->io->setAuthentication($originUrl, $consumerKey, $consumerSecret); if (!$this->requestAccessToken()) { return ''; } $this->storeInAuthConfig($this->config->getLocalAuthConfigSource() ?? $this->config->getAuthConfigSource(), $originUrl, $consumerKey, $consumerSecret); if (!isset($this->token['access_token'])) { throw new \LogicException('Failed to initialize token above'); } return $this->token['access_token']; } private function storeInAuthConfig(Config\ConfigSourceInterface $authConfigSource, string $originUrl, string $consumerKey, string $consumerSecret): void { $this->config->getConfigSource()->removeConfigSetting('bitbucket-oauth.'.$originUrl); if (null === $this->token || !isset($this->token['expires_in'])) { throw new \LogicException('Expected a token configured with expires_in present, got '.json_encode($this->token)); } $time = null === $this->time ? time() : $this->time; $consumer = [ "consumer-key" => $consumerKey, "consumer-secret" => $consumerSecret, "access-token" => $this->token['access_token'], "access-token-expiration" => $time + $this->token['expires_in'], ]; $this->config->getAuthConfigSource()->addConfigSetting('bitbucket-oauth.'.$originUrl, $consumer); } private function getTokenFromConfig(string $originUrl): bool { $authConfig = $this->config->get('bitbucket-oauth'); if ( !isset($authConfig[$originUrl]['access-token'], $authConfig[$originUrl]['access-token-expiration']) || time() > $authConfig[$originUrl]['access-token-expiration'] ) { return false; } $this->token = [ 'access_token' => $authConfig[$originUrl]['access-token'], ]; return true; } } io = $io; } public function validate(string $file, int $arrayLoaderValidationFlags = ValidatingArrayLoader::CHECK_ALL, int $flags = self::CHECK_VERSION): array { $errors = []; $publishErrors = []; $warnings = []; $laxValid = false; $manifest = null; try { $json = new JsonFile($file, null, $this->io); $manifest = $json->read(); $json->validateSchema(JsonFile::LAX_SCHEMA); $laxValid = true; $json->validateSchema(); } catch (JsonValidationException $e) { foreach ($e->getErrors() as $message) { if ($laxValid) { $publishErrors[] = $message; } else { $errors[] = $message; } } } catch (\Exception $e) { $errors[] = $e->getMessage(); return [$errors, $publishErrors, $warnings]; } if (is_array($manifest)) { $jsonParser = new JsonParser(); try { $jsonParser->parse((string) file_get_contents($file), JsonParser::DETECT_KEY_CONFLICTS); } catch (DuplicateKeyException $e) { $details = $e->getDetails(); $warnings[] = 'Key '.$details['key'].' is a duplicate in '.$file.' at line '.$details['line']; } } if (empty($manifest['license'])) { $warnings[] = 'No license specified, it is recommended to do so. For closed-source software you may use "proprietary" as license.'; } else { $licenses = (array) $manifest['license']; foreach ($licenses as $key => $license) { if ('proprietary' === $license) { unset($licenses[$key]); } } $licenseValidator = new SpdxLicenses(); foreach ($licenses as $license) { $spdxLicense = $licenseValidator->getLicenseByIdentifier($license); if ($spdxLicense && $spdxLicense[3]) { if (Preg::isMatch('{^[AL]?GPL-[123](\.[01])?\+$}i', $license)) { $warnings[] = sprintf( 'License "%s" is a deprecated SPDX license identifier, use "'.str_replace('+', '', $license).'-or-later" instead', $license ); } elseif (Preg::isMatch('{^[AL]?GPL-[123](\.[01])?$}i', $license)) { $warnings[] = sprintf( 'License "%s" is a deprecated SPDX license identifier, use "'.$license.'-only" or "'.$license.'-or-later" instead', $license ); } else { $warnings[] = sprintf( 'License "%s" is a deprecated SPDX license identifier, see https://spdx.org/licenses/', $license ); } } } } if (($flags & self::CHECK_VERSION) && isset($manifest['version'])) { $warnings[] = 'The version field is present, it is recommended to leave it out if the package is published on Packagist.'; } if (!empty($manifest['name']) && Preg::isMatch('{[A-Z]}', $manifest['name'])) { $suggestName = Preg::replace('{(?:([a-z])([A-Z])|([A-Z])([A-Z][a-z]))}', '\\1\\3-\\2\\4', $manifest['name']); $suggestName = strtolower($suggestName); $publishErrors[] = sprintf( 'Name "%s" does not match the best practice (e.g. lower-cased/with-dashes). We suggest using "%s" instead. As such you will not be able to submit it to Packagist.', $manifest['name'], $suggestName ); } if (!empty($manifest['type']) && $manifest['type'] === 'composer-installer') { $warnings[] = "The package type 'composer-installer' is deprecated. Please distribute your custom installers as plugins from now on. See https://getcomposer.org/doc/articles/plugins.md for plugin documentation."; } if (isset($manifest['require'], $manifest['require-dev'])) { $requireOverrides = array_intersect_key($manifest['require'], $manifest['require-dev']); if (!empty($requireOverrides)) { $plural = (count($requireOverrides) > 1) ? 'are' : 'is'; $warnings[] = implode(', ', array_keys($requireOverrides)). " {$plural} required both in require and require-dev, this can lead to unexpected behavior"; } } foreach (['provide', 'replace'] as $linkType) { if (isset($manifest[$linkType])) { foreach (['require', 'require-dev'] as $requireType) { if (isset($manifest[$requireType])) { foreach ($manifest[$linkType] as $provide => $constraint) { if (isset($manifest[$requireType][$provide])) { $warnings[] = 'The package ' . $provide . ' in '.$requireType.' is also listed in '.$linkType.' which satisfies the requirement. Remove it from '.$linkType.' if you wish to install it.'; } } } } } } $require = $manifest['require'] ?? []; $requireDev = $manifest['require-dev'] ?? []; $packages = array_merge($require, $requireDev); foreach ($packages as $package => $version) { if (Preg::isMatch('/#/', $version)) { $warnings[] = sprintf( 'The package "%s" is pointing to a commit-ref, this is bad practice and can cause unforeseen issues.', $package ); } } $scriptsDescriptions = $manifest['scripts-descriptions'] ?? []; $scripts = $manifest['scripts'] ?? []; foreach ($scriptsDescriptions as $scriptName => $scriptDescription) { if (!array_key_exists($scriptName, $scripts)) { $warnings[] = sprintf( 'Description for non-existent script "%s" found in "scripts-descriptions"', $scriptName ); } } $scriptAliases = $manifest['scripts-aliases'] ?? []; foreach ($scriptAliases as $scriptName => $scriptAlias) { if (!array_key_exists($scriptName, $scripts)) { $warnings[] = sprintf( 'Aliases for non-existent script "%s" found in "scripts-aliases"', $scriptName ); } } if (isset($manifest['autoload']['psr-0'][''])) { $warnings[] = "Defining autoload.psr-0 with an empty namespace prefix is a bad idea for performance"; } if (isset($manifest['autoload']['psr-4'][''])) { $warnings[] = "Defining autoload.psr-4 with an empty namespace prefix is a bad idea for performance"; } $loader = new ValidatingArrayLoader(new ArrayLoader(), true, null, $arrayLoaderValidationFlags); try { if (!isset($manifest['version'])) { $manifest['version'] = '1.0.0'; } if (!isset($manifest['name'])) { $manifest['name'] = 'dummy/dummy'; } $loader->load($manifest); } catch (InvalidPackageException $e) { $errors = array_merge($errors, $e->getErrors()); } $warnings = array_merge($warnings, $loader->getWarnings()); return [$errors, $publishErrors, $warnings]; } } 0 && !self::$io->isVerbose()) { if (self::$hasShownDeprecationNotice === 1) { self::$io->writeError('More deprecation notices were hidden, run again with `-v` to show them.'); self::$hasShownDeprecationNotice = 2; } return true; } self::$hasShownDeprecationNotice = 1; self::outputWarning('Deprecation Notice: '.$message.' in '.$file.':'.$line); } return true; } public static function register(?IOInterface $io = null): void { set_error_handler([__CLASS__, 'handle']); error_reporting(E_ALL); self::$io = $io; } private static function outputWarning(string $message, bool $outputEvenWithoutIO = false): void { if (self::$io !== null) { self::$io->writeError(''.$message.''); if (self::$io->isVerbose()) { self::$io->writeError('Stack trace:'); self::$io->writeError(array_filter(array_map(static function ($a): ?string { if (isset($a['line'], $a['file'])) { return ' '.$a['file'].':'.$a['line'].''; } return null; }, array_slice(debug_backtrace(), 2)), static function (?string $line) { return $line !== null; })); } return; } if ($outputEvenWithoutIO) { if (defined('STDERR') && is_resource(STDERR)) { fwrite(STDERR, 'Warning: '.$message.PHP_EOL); } else { echo 'Warning: '.$message.PHP_EOL; } } } } processExecutor = $executor; } public function remove(string $file) { if (is_dir($file)) { return $this->removeDirectory($file); } if (file_exists($file)) { return $this->unlink($file); } return false; } public function isDirEmpty(string $dir) { $finder = Finder::create() ->ignoreVCS(false) ->ignoreDotFiles(false) ->depth(0) ->in($dir); return \count($finder) === 0; } public function emptyDirectory(string $dir, bool $ensureDirectoryExists = true) { if (is_link($dir) && file_exists($dir)) { $this->unlink($dir); } if ($ensureDirectoryExists) { $this->ensureDirectoryExists($dir); } if (is_dir($dir)) { $finder = Finder::create() ->ignoreVCS(false) ->ignoreDotFiles(false) ->depth(0) ->in($dir); foreach ($finder as $path) { $this->remove((string) $path); } } } public function removeDirectory(string $directory) { $edgeCaseResult = $this->removeEdgeCases($directory); if ($edgeCaseResult !== null) { return $edgeCaseResult; } if (Platform::isWindows()) { $cmd = ['rmdir', '/S', '/Q', Platform::realpath($directory)]; } else { $cmd = ['rm', '-rf', $directory]; } $result = $this->getProcess()->execute($cmd, $output) === 0; clearstatcache(); if ($result && !is_dir($directory)) { return true; } return $this->removeDirectoryPhp($directory); } public function removeDirectoryAsync(string $directory) { $edgeCaseResult = $this->removeEdgeCases($directory); if ($edgeCaseResult !== null) { return \React\Promise\resolve($edgeCaseResult); } if (Platform::isWindows()) { $cmd = ['rmdir', '/S', '/Q', Platform::realpath($directory)]; } else { $cmd = ['rm', '-rf', $directory]; } $promise = $this->getProcess()->executeAsync($cmd); return $promise->then(function ($process) use ($directory) { clearstatcache(); if ($process->isSuccessful()) { if (!is_dir($directory)) { return \React\Promise\resolve(true); } } return \React\Promise\resolve($this->removeDirectoryPhp($directory)); }); } private function removeEdgeCases(string $directory, bool $fallbackToPhp = true): ?bool { if ($this->isSymlinkedDirectory($directory)) { return $this->unlinkSymlinkedDirectory($directory); } if ($this->isJunction($directory)) { return $this->removeJunction($directory); } if (is_link($directory)) { return unlink($directory); } if (!is_dir($directory) || !file_exists($directory)) { return true; } if (Preg::isMatch('{^(?:[a-z]:)?[/\\\\]+$}i', $directory)) { throw new \RuntimeException('Aborting an attempted deletion of '.$directory.', this was probably not intended, if it is a real use case please report it.'); } if (!\function_exists('proc_open') && $fallbackToPhp) { return $this->removeDirectoryPhp($directory); } return null; } public function removeDirectoryPhp(string $directory) { $edgeCaseResult = $this->removeEdgeCases($directory, false); if ($edgeCaseResult !== null) { return $edgeCaseResult; } try { $it = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS); } catch (\UnexpectedValueException $e) { clearstatcache(); usleep(100000); if (!is_dir($directory)) { return true; } $it = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS); } $ri = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST); foreach ($ri as $file) { if ($file->isDir()) { $this->rmdir($file->getPathname()); } else { $this->unlink($file->getPathname()); } } unset($ri, $it, $file); return $this->rmdir($directory); } public function ensureDirectoryExists(string $directory) { if (!is_dir($directory)) { if (file_exists($directory)) { throw new \RuntimeException( $directory.' exists and is not a directory.' ); } if (is_link($directory) && !@$this->unlinkImplementation($directory)) { throw new \RuntimeException('Could not delete symbolic link '.$directory.': '.(error_get_last()['message'] ?? '')); } if (!@mkdir($directory, 0777, true)) { $e = new \RuntimeException($directory.' does not exist and could not be created: '.(error_get_last()['message'] ?? '')); $normalized = $this->normalizePath($directory); if ($normalized !== $directory) { try { $this->ensureDirectoryExists($normalized); return; } catch (\Throwable $ignoredEx) { } } throw $e; } } } public function unlink(string $path) { $unlinked = @$this->unlinkImplementation($path); if (!$unlinked) { if (Platform::isWindows()) { usleep(350000); $unlinked = @$this->unlinkImplementation($path); } if (!$unlinked) { $error = error_get_last(); $message = 'Could not delete '.$path.': ' . ($error['message'] ?? ''); if (Platform::isWindows()) { $message .= "\nThis can be due to an antivirus or the Windows Search Indexer locking the file while they are analyzed"; } throw new \RuntimeException($message); } } return true; } public function rmdir(string $path) { $deleted = @rmdir($path); if (!$deleted) { if (Platform::isWindows()) { usleep(350000); $deleted = @rmdir($path); } if (!$deleted) { $error = error_get_last(); $message = 'Could not delete '.$path.': ' . ($error['message'] ?? ''); if (Platform::isWindows()) { $message .= "\nThis can be due to an antivirus or the Windows Search Indexer locking the file while they are analyzed"; } throw new \RuntimeException($message); } } return true; } public function copyThenRemove(string $source, string $target) { $this->copy($source, $target); if (!is_dir($source)) { $this->unlink($source); return; } $this->removeDirectoryPhp($source); } public function copy(string $source, string $target) { $target = $this->normalizePath($target); if (!is_dir($source)) { try { return copy($source, $target); } catch (ErrorException $e) { if (str_contains($e->getMessage(), 'Bad address')) { $sourceHandle = fopen($source, 'r'); $targetHandle = fopen($target, 'w'); if (false === $sourceHandle || false === $targetHandle) { throw $e; } while (!feof($sourceHandle)) { if (false === fwrite($targetHandle, (string) fread($sourceHandle, 1024 * 1024))) { throw $e; } } fclose($sourceHandle); fclose($targetHandle); return true; } throw $e; } } $it = new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS); $ri = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::SELF_FIRST); $this->ensureDirectoryExists($target); $result = true; foreach ($ri as $file) { $targetPath = $target . DIRECTORY_SEPARATOR . $ri->getSubPathname(); if ($file->isDir()) { $this->ensureDirectoryExists($targetPath); } else { $result = $result && copy($file->getPathname(), $targetPath); } } return $result; } public function rename(string $source, string $target) { if (true === @rename($source, $target)) { return; } if (!\function_exists('proc_open')) { $this->copyThenRemove($source, $target); return; } if (Platform::isWindows()) { $result = $this->getProcess()->execute(['xcopy', $source, $target, '/E', '/I', '/Q', '/Y'], $output); clearstatcache(); if (0 === $result) { $this->remove($source); return; } } else { $result = $this->getProcess()->execute(['mv', $source, $target], $output); clearstatcache(); if (0 === $result) { return; } } $this->copyThenRemove($source, $target); } public function findShortestPath(string $from, string $to, bool $directories = false, bool $preferRelative = false) { if (!$this->isAbsolutePath($from) || !$this->isAbsolutePath($to)) { throw new \InvalidArgumentException(sprintf('$from (%s) and $to (%s) must be absolute paths.', $from, $to)); } $from = $this->normalizePath($from); $to = $this->normalizePath($to); if ($directories) { $from = rtrim($from, '/') . '/dummy_file'; } if (\dirname($from) === \dirname($to)) { return './'.basename($to); } $commonPath = $to; while (strpos($from.'/', $commonPath.'/') !== 0 && '/' !== $commonPath && !Preg::isMatch('{^[A-Z]:/?$}i', $commonPath)) { $commonPath = strtr(\dirname($commonPath), '\\', '/'); } if (0 !== strpos($from, $commonPath)) { return $to; } $commonPath = rtrim($commonPath, '/') . '/'; $sourcePathDepth = substr_count((string) substr($from, \strlen($commonPath)), '/'); $commonPathCode = str_repeat('../', $sourcePathDepth); if (!$preferRelative && '/' === $commonPath && $sourcePathDepth > 1) { return $to; } $result = $commonPathCode . substr($to, \strlen($commonPath)); if (\strlen($result) === 0) { return './'; } return $result; } public function findShortestPathCode(string $from, string $to, bool $directories = false, bool $staticCode = false, bool $preferRelative = false) { if (!$this->isAbsolutePath($from) || !$this->isAbsolutePath($to)) { throw new \InvalidArgumentException(sprintf('$from (%s) and $to (%s) must be absolute paths.', $from, $to)); } $from = $this->normalizePath($from); $to = $this->normalizePath($to); if ($from === $to) { return $directories ? '__DIR__' : '__FILE__'; } $commonPath = $to; while (strpos($from.'/', $commonPath.'/') !== 0 && '/' !== $commonPath && !Preg::isMatch('{^[A-Z]:/?$}i', $commonPath) && '.' !== $commonPath) { $commonPath = strtr(\dirname($commonPath), '\\', '/'); } if (0 !== strpos($from, $commonPath) || '.' === $commonPath) { return var_export($to, true); } $commonPath = rtrim($commonPath, '/') . '/'; if (str_starts_with($to, $from.'/')) { return '__DIR__ . '.var_export((string) substr($to, \strlen($from)), true); } $sourcePathDepth = substr_count((string) substr($from, \strlen($commonPath)), '/') + (int) $directories; if (!$preferRelative && '/' === $commonPath && $sourcePathDepth > 1) { return var_export($to, true); } if ($staticCode) { $commonPathCode = "__DIR__ . '".str_repeat('/..', $sourcePathDepth)."'"; } else { $commonPathCode = str_repeat('dirname(', $sourcePathDepth).'__DIR__'.str_repeat(')', $sourcePathDepth); } $relTarget = (string) substr($to, \strlen($commonPath)); return $commonPathCode . (\strlen($relTarget) > 0 ? '.' . var_export('/' . $relTarget, true) : ''); } public function isAbsolutePath(string $path) { return strpos($path, '/') === 0 || substr($path, 1, 1) === ':' || strpos($path, '\\\\') === 0; } public function size(string $path) { if (!file_exists($path)) { throw new \RuntimeException("$path does not exist."); } if (is_dir($path)) { return $this->directorySize($path); } return (int) filesize($path); } public function normalizePath(string $path) { $parts = []; $path = strtr($path, '\\', '/'); $prefix = ''; $absolute = ''; if (strpos($path, '//') === 0 && \strlen($path) > 2) { $absolute = '//'; $path = substr($path, 2); } if (Preg::isMatchStrictGroups('{^( [0-9a-z]{2,}+: (?: // (?: [a-z]: )? )? | [a-z]: )}ix', $path, $match)) { $prefix = $match[1]; $path = substr($path, \strlen($prefix)); } if (strpos($path, '/') === 0) { $absolute = '/'; $path = substr($path, 1); } $up = false; foreach (explode('/', $path) as $chunk) { if ('..' === $chunk && (\strlen($absolute) > 0 || $up)) { array_pop($parts); $up = !(\count($parts) === 0 || '..' === end($parts)); } elseif ('.' !== $chunk && '' !== $chunk) { $parts[] = $chunk; $up = '..' !== $chunk; } } $prefix = Preg::replaceCallback('{(^|://)[a-z]:$}i', static function (array $m) { return strtoupper($m[0]); }, $prefix); return $prefix.$absolute.implode('/', $parts); } public static function trimTrailingSlash(string $path) { if (!Preg::isMatch('{^[/\\\\]+$}', $path)) { $path = rtrim($path, '/\\'); } return $path; } public static function isLocalPath(string $path) { if (Platform::isWindows()) { return Preg::isMatch('{^(file://(?!//)|/(?!/)|/?[a-z]:[\\\\/]|\.\.[\\\\/]|[a-z0-9_.-]+[\\\\/])}i', $path); } return Preg::isMatch('{^(file://|/|/?[a-z]:[\\\\/]|\.\.[\\\\/]|[a-z0-9_.-]+[\\\\/])}i', $path); } public static function getPlatformPath(string $path) { if (Platform::isWindows()) { $path = Preg::replace('{^(?:file:///([a-z]):?/)}i', 'file://$1:/', $path); } return Preg::replace('{^file://}i', '', $path); } public static function isReadable(string $path) { if (is_readable($path)) { return true; } if (is_file($path)) { return false !== Silencer::call('file_get_contents', $path, false, null, 0, 1); } if (is_dir($path)) { return false !== Silencer::call('opendir', $path); } return false; } protected function directorySize(string $directory) { $it = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS); $ri = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST); $size = 0; foreach ($ri as $file) { if ($file->isFile()) { $size += $file->getSize(); } } return $size; } protected function getProcess() { if (null === $this->processExecutor) { $this->processExecutor = new ProcessExecutor(); } return $this->processExecutor; } private function unlinkImplementation(string $path): bool { if (Platform::isWindows() && is_dir($path) && is_link($path)) { return rmdir($path); } return unlink($path); } public function relativeSymlink(string $target, string $link) { if (!function_exists('symlink')) { return false; } $cwd = Platform::getCwd(); $relativePath = $this->findShortestPath($link, $target); chdir(\dirname($link)); $result = @symlink($relativePath, $link); chdir($cwd); return $result; } public function isSymlinkedDirectory(string $directory) { if (!is_dir($directory)) { return false; } $resolved = $this->resolveSymlinkedDirectorySymlink($directory); return is_link($resolved); } private function unlinkSymlinkedDirectory(string $directory): bool { $resolved = $this->resolveSymlinkedDirectorySymlink($directory); return $this->unlink($resolved); } private function resolveSymlinkedDirectorySymlink(string $pathname): string { if (!is_dir($pathname)) { return $pathname; } $resolved = rtrim($pathname, '/'); if (0 === \strlen($resolved)) { return $pathname; } return $resolved; } public function junction(string $target, string $junction) { if (!Platform::isWindows()) { throw new \LogicException(sprintf('Function %s is not available on non-Windows platform', __CLASS__)); } if (!is_dir($target)) { throw new IOException(sprintf('Cannot junction to "%s" as it is not a directory.', $target), 0, null, $target); } if (!is_dir($junction) || $this->isJunction($junction)) { @rmdir($junction); } $cmd = ['mklink', '/J', str_replace('/', DIRECTORY_SEPARATOR, $junction), Platform::realpath($target)]; if ($this->getProcess()->execute($cmd, $output) !== 0) { throw new IOException(sprintf('Failed to create junction to "%s" at "%s".', $target, $junction), 0, null, $target); } clearstatcache(true, $junction); } public function isJunction(string $junction) { if (!Platform::isWindows()) { return false; } clearstatcache(true, $junction); if (!is_dir($junction) || is_link($junction)) { return false; } $stat = lstat($junction); return is_array($stat) ? 0x4000 !== ($stat['mode'] & 0xF000) : false; } public function removeJunction(string $junction) { if (!Platform::isWindows()) { return false; } $junction = rtrim(str_replace('/', DIRECTORY_SEPARATOR, $junction), DIRECTORY_SEPARATOR); if (!$this->isJunction($junction)) { throw new IOException(sprintf('%s is not a junction and thus cannot be removed as one', $junction)); } return $this->rmdir($junction); } public function filePutContentsIfModified(string $path, string $content) { $currentContent = Silencer::call('file_get_contents', $path); if (false === $currentContent || $currentContent !== $content) { return file_put_contents($path, $content); } return 0; } public function safeCopy(string $source, string $target): void { if (!file_exists($target) || !file_exists($source) || !$this->filesAreEqual($source, $target)) { $sourceHandle = fopen($source, 'r'); assert($sourceHandle !== false, 'Could not open "'.$source.'" for reading.'); $targetHandle = fopen($target, 'w+'); assert($targetHandle !== false, 'Could not open "'.$target.'" for writing.'); stream_copy_to_stream($sourceHandle, $targetHandle); fclose($sourceHandle); fclose($targetHandle); touch($target, (int) filemtime($source), (int) fileatime($source)); } } private function filesAreEqual(string $a, string $b): bool { if (filesize($a) !== filesize($b)) { return false; } $aHandle = fopen($a, 'rb'); assert($aHandle !== false, 'Could not open "'.$a.'" for reading.'); $bHandle = fopen($b, 'rb'); assert($bHandle !== false, 'Could not open "'.$b.'" for reading.'); $result = true; while (!feof($aHandle)) { if (fread($aHandle, 8192) !== fread($bHandle, 8192)) { $result = false; break; } } fclose($aHandle); fclose($bHandle); return $result; } } io = $io; $this->config = $config; $this->httpDownloader = $httpDownloader; } public function authorizeOAuthInteractively(string $originUrl, ?string $message = null): bool { if ($message !== null) { $this->io->writeError($message); } $url = 'https://'.$originUrl.'/user/settings/applications'; $this->io->writeError('Setup a personal access token with repository:read permissions on:'); $this->io->writeError($url); $localAuthConfig = $this->config->getLocalAuthConfigSource(); $this->io->writeError(sprintf('Tokens will be stored in plain text in "%s" for future use by Composer.', ($localAuthConfig !== null ? $localAuthConfig->getName() . ' OR ' : '') . $this->config->getAuthConfigSource()->getName())); $this->io->writeError('For additional information, check https://getcomposer.org/doc/articles/authentication-for-private-packages.md#forgejo-token'); $storeInLocalAuthConfig = false; if ($localAuthConfig !== null) { $storeInLocalAuthConfig = $this->io->askConfirmation('A local auth config source was found, do you want to store the token there?', true); } $username = trim((string) $this->io->ask('Username: ')); $token = trim((string) $this->io->askAndHideAnswer('Token (hidden): ')); $addTokenManually = sprintf('You can also add it manually later by using "composer config --global --auth forgejo-token.%s "', $originUrl); if ($token === '' || $username === '') { $this->io->writeError('No username/token given, aborting.'); $this->io->writeError($addTokenManually); return false; } $this->io->setAuthentication($originUrl, $username, $token); try { $this->httpDownloader->get('https://'. $originUrl . '/api/v1/version', [ 'retry-auth-failure' => false, ]); } catch (TransportException $e) { if (in_array($e->getCode(), [403, 401, 404], true)) { $this->io->writeError('Invalid access token provided.'); $this->io->writeError($addTokenManually); return false; } throw $e; } $authConfigSource = $storeInLocalAuthConfig && $localAuthConfig !== null ? $localAuthConfig : $this->config->getAuthConfigSource(); $this->config->getConfigSource()->removeConfigSetting('forgejo-token.'.$originUrl); $authConfigSource->addConfigSetting('forgejo-token.'.$originUrl, ['username' => $username, 'token' => $token]); $this->io->writeError('Token stored successfully.'); return true; } } htmlUrl = $htmlUrl; $this->httpCloneUrl = $httpCloneUrl; $this->sshUrl = $sshUrl; $this->isPrivate = $isPrivate; $this->defaultBranch = $defaultBranch; $this->hasIssues = $hasIssues; $this->isArchived = $isArchived; } public static function fromRemoteData(array $data): self { return new self( $data['html_url'], $data['clone_url'], $data['ssh_url'], $data['private'], $data['default_branch'], $data['has_issues'], $data['archived'] ); } } owner = $owner; $this->repository = $repository; $this->originUrl = $originUrl; $this->apiUrl = $apiUrl; } public static function create(string $repoUrl): self { $url = self::tryFrom($repoUrl); if ($url !== null) { return $url; } throw new \InvalidArgumentException('This is not a valid Forgejo URL: ' . $repoUrl); } public static function tryFrom(?string $repoUrl): ?self { if ($repoUrl === null || !Preg::isMatch(self::URL_REGEX, $repoUrl, $match)) { return null; } $originUrl = strtolower($match[1] ?? (string) $match[2]); $apiBase = $originUrl . '/api/v1'; return new self( $match[3], $match[4], $originUrl, sprintf('https://%s/repos/%s/%s', $apiBase, $match[3], $match[4]) ); } public function generateSshUrl(): string { return 'git@' . $this->originUrl . ':'.$this->owner.'/'.$this->repository.'.git'; } } io = $io; $this->config = $config; $this->process = $process; $this->filesystem = $fs; } public static function checkForRepoOwnershipError(string $output, string $path, ?IOInterface $io = null): void { if (str_contains($output, 'fatal: detected dubious ownership')) { $msg = 'The repository at "' . $path . '" does not have the correct ownership and git refuses to use it:' . PHP_EOL . PHP_EOL . $output; if ($io === null) { throw new \RuntimeException($msg); } $io->writeError(''.$msg.''); } } public function setHttpDownloader(HttpDownloader $httpDownloader): void { $this->httpDownloader = $httpDownloader; } public function runCommands(array $commands, string $url, ?string $cwd, bool $initialClone = false, &$commandOutput = null): void { $callables = []; foreach ($commands as $cmd) { $callables[] = static function (string $url) use ($cmd): array { $map = [ '%url%' => $url, '%sanitizedUrl%' => Preg::replace('{://([^@]+?):(.+?)@}', '://', $url), ]; return array_map(static function ($value) use ($map): string { return $map[$value] ?? $value; }, $cmd); }; } $this->runCommand($callables, $url, $cwd, $initialClone, $commandOutput); } public function runCommand($commandCallable, string $url, ?string $cwd, bool $initialClone = false, &$commandOutput = null): void { $commandCallables = is_callable($commandCallable) ? [$commandCallable] : $commandCallable; $lastCommand = ''; $this->config->prohibitUrlByConfig($url, $this->io); if ($initialClone) { $origCwd = $cwd; } $runCommands = function ($url) use ($commandCallables, $cwd, &$commandOutput, &$lastCommand, $initialClone) { $collectOutputs = !is_callable($commandOutput); $outputs = []; $status = 0; $counter = 0; foreach ($commandCallables as $callable) { $lastCommand = $callable($url); if ($collectOutputs) { $outputs[] = ''; $output = &$outputs[count($outputs) - 1]; } else { $output = &$commandOutput; } $status = $this->process->execute($lastCommand, $output, $initialClone && $counter === 0 ? null : $cwd); if ($status !== 0) { break; } $counter++; } if ($collectOutputs) { $commandOutput = implode('', $outputs); } return $status; }; if (Preg::isMatch('{^ssh://[^@]+@[^:]+:[^0-9]+}', $url)) { throw new \InvalidArgumentException('The source URL ' . Url::sanitize($url) . ' is invalid, ssh URLs should have a port number after ":".' . "\n" . 'Use ssh://git@example.com:22/path or just git@example.com:path if you do not want to provide a password or custom port.'); } if (!$initialClone) { $this->process->execute(['git', 'remote', '-v'], $output, $cwd); if (Preg::isMatchStrictGroups('{^(?:composer|origin)\s+https?://(.+):(.+)@([^/]+)}im', $output, $match) && !$this->io->hasAuthentication($match[3])) { $this->io->setAuthentication($match[3], rawurldecode($match[1]), rawurldecode($match[2])); } } $protocols = $this->config->get('github-protocols'); if (Preg::isMatchStrictGroups('{^(?:https?|git)://' . self::getGitHubDomainsRegex($this->config) . '/(.*)}', $url, $match)) { $messages = []; foreach ($protocols as $protocol) { if ('ssh' === $protocol) { $protoUrl = "git@" . $match[1] . ":" . $match[2]; } else { $protoUrl = $protocol . "://" . $match[1] . "/" . $match[2]; } if (0 === $runCommands($protoUrl)) { return; } $messages[] = '- ' . $protoUrl . "\n" . Preg::replace('#^#m', ' ', $this->process->getErrorOutput()); if ($initialClone && isset($origCwd)) { $this->filesystem->removeDirectory($origCwd); } } if (!$this->io->hasAuthentication($match[1]) && !$this->io->isInteractive()) { $this->throwException('Failed to clone ' . $url . ' via ' . implode(', ', $protocols) . ' protocols, aborting.' . "\n\n" . implode("\n", $messages), $url); } } $bypassSshForGitHub = Preg::isMatch('{^git@' . self::getGitHubDomainsRegex($this->config) . ':(.+?)\.git$}i', $url) && !in_array('ssh', $protocols, true); $auth = null; $credentials = []; if ($bypassSshForGitHub || 0 !== $runCommands($url)) { $errorMsg = $this->process->getErrorOutput(); if (Preg::isMatchStrictGroups('{^git@' . self::getGitHubDomainsRegex($this->config) . ':(.+?)\.git$}i', $url, $match) || Preg::isMatchStrictGroups('{^https?://' . self::getGitHubDomainsRegex($this->config) . '/(.*?)(?:\.git)?$}i', $url, $match) ) { if (!$this->io->hasAuthentication($match[1])) { $gitHubUtil = new GitHub($this->io, $this->config, $this->process); $message = 'Cloning failed using an ssh key for authentication, enter your GitHub credentials to access private repos'; if (!$gitHubUtil->authorizeOAuth($match[1]) && $this->io->isInteractive()) { $gitHubUtil->authorizeOAuthInteractively($match[1], $message); } } if ($this->io->hasAuthentication($match[1])) { $auth = $this->io->getAuthentication($match[1]); $authUrl = 'https://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[1] . '/' . $match[2] . '.git'; if (0 === $runCommands($authUrl)) { return; } $credentials = [rawurlencode($auth['username']), rawurlencode($auth['password'])]; $errorMsg = $this->process->getErrorOutput(); } } elseif ( Preg::isMatchStrictGroups('{^(https?)://(bitbucket\.org)/(.*?)(?:\.git)?$}i', $url, $match) || Preg::isMatchStrictGroups('{^(git)@(bitbucket\.org):(.+?\.git)$}i', $url, $match) ) { $bitbucketUtil = new Bitbucket($this->io, $this->config, $this->process, $this->httpDownloader); $domain = $match[2]; $repo_with_git_part = $match[3]; if (!str_ends_with($repo_with_git_part, '.git')) { $repo_with_git_part .= '.git'; } if (!$this->io->hasAuthentication($domain)) { $message = 'Enter your Bitbucket credentials to access private repos'; if (!$bitbucketUtil->authorizeOAuth($domain) && $this->io->isInteractive()) { $bitbucketUtil->authorizeOAuthInteractively($domain, $message); $accessToken = $bitbucketUtil->getToken(); $this->io->setAuthentication($domain, 'x-token-auth', $accessToken); } } if ($this->io->hasAuthentication($domain)) { $auth = $this->io->getAuthentication($domain); if (strpos((string) $auth['password'], 'ATAT') === 0) { $auth['username'] = 'x-bitbucket-api-token-auth'; } $authUrl = 'https://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $domain . '/' . $repo_with_git_part; if (0 === $runCommands($authUrl)) { return; } if ($auth['username'] !== 'x-token-auth') { $accessToken = $bitbucketUtil->requestToken($domain, $auth['username'], $auth['password']); if (!empty($accessToken)) { $this->io->setAuthentication($domain, 'x-token-auth', $accessToken); } } } if ($this->io->hasAuthentication($domain)) { $auth = $this->io->getAuthentication($domain); $authUrl = 'https://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $domain . '/' . $repo_with_git_part; if (0 === $runCommands($authUrl)) { return; } $credentials = [rawurlencode($auth['username']), rawurlencode($auth['password'])]; } $sshUrl = 'git@bitbucket.org:' . $repo_with_git_part; $this->io->writeError(' No bitbucket authentication configured. Falling back to ssh.'); if (0 === $runCommands($sshUrl)) { return; } $errorMsg = $this->process->getErrorOutput(); } elseif ( Preg::isMatchStrictGroups('{^(git)@' . self::getGitLabDomainsRegex($this->config) . ':(.+?\.git)$}i', $url, $match) || Preg::isMatchStrictGroups('{^(https?)://' . self::getGitLabDomainsRegex($this->config) . '/(.*)}i', $url, $match) ) { if ($match[1] === 'git') { $match[1] = 'https'; } if (!$this->io->hasAuthentication($match[2])) { $gitLabUtil = new GitLab($this->io, $this->config, $this->process); $message = 'Cloning failed, enter your GitLab credentials to access private repos'; if (!$gitLabUtil->authorizeOAuth($match[2]) && $this->io->isInteractive()) { $gitLabUtil->authorizeOAuthInteractively($match[1], $match[2], $message); } } if ($this->io->hasAuthentication($match[2])) { $auth = $this->io->getAuthentication($match[2]); if ($auth['password'] === 'private-token' || $auth['password'] === 'oauth2' || $auth['password'] === 'gitlab-ci-token') { $authUrl = $match[1] . '://' . rawurlencode($auth['password']) . ':' . rawurlencode((string) $auth['username']) . '@' . $match[2] . '/' . $match[3]; } else { $authUrl = $match[1] . '://' . rawurlencode((string) $auth['username']) . ':' . rawurlencode((string) $auth['password']) . '@' . $match[2] . '/' . $match[3]; } if (0 === $runCommands($authUrl)) { return; } $credentials = [rawurlencode((string) $auth['username']), rawurlencode((string) $auth['password'])]; $errorMsg = $this->process->getErrorOutput(); } } elseif (null !== ($match = $this->getAuthenticationFailure($url))) { if (str_contains($match[2], '@')) { [$authParts, $match[2]] = explode('@', $match[2], 2); } $storeAuth = false; if ($this->io->hasAuthentication($match[2])) { $auth = $this->io->getAuthentication($match[2]); } elseif ($this->io->isInteractive()) { $defaultUsername = null; if (isset($authParts) && $authParts !== '') { if (str_contains($authParts, ':')) { [$defaultUsername] = explode(':', $authParts, 2); } else { $defaultUsername = $authParts; } } $this->io->writeError(' Authentication required (' . $match[2] . '):'); $this->io->writeError('' . trim($errorMsg) . '', true, IOInterface::VERBOSE); $auth = [ 'username' => $this->io->ask(' Username: ', $defaultUsername), 'password' => $this->io->askAndHideAnswer(' Password: '), ]; $storeAuth = $this->config->get('store-auths'); } if (null !== $auth) { $authUrl = $match[1] . rawurlencode((string) $auth['username']) . ':' . rawurlencode((string) $auth['password']) . '@' . $match[2] . $match[3]; if (0 === $runCommands($authUrl)) { $this->io->setAuthentication($match[2], $auth['username'], $auth['password']); $authHelper = new AuthHelper($this->io, $this->config); $authHelper->storeAuth($match[2], $storeAuth); return; } $credentials = [rawurlencode((string) $auth['username']), rawurlencode((string) $auth['password'])]; $errorMsg = $this->process->getErrorOutput(); } } if ($initialClone && isset($origCwd)) { $this->filesystem->removeDirectory($origCwd); } if (\is_array($lastCommand)) { $lastCommand = implode(' ', $lastCommand); } if (count($credentials) > 0) { $lastCommand = $this->maskCredentials($lastCommand, $credentials); $errorMsg = $this->maskCredentials($errorMsg, $credentials); } $this->throwException('Failed to execute ' . $lastCommand . "\n\n" . $errorMsg, $url); } } public function syncMirror(string $url, string $dir): bool { if ((bool) Platform::getEnv('COMPOSER_DISABLE_NETWORK') && Platform::getEnv('COMPOSER_DISABLE_NETWORK') !== 'prime') { $this->io->writeError('Aborting git mirror sync of '.Url::sanitize($url).' as network is disabled'); return false; } if (is_dir($dir) && 0 === $this->process->execute(['git', 'rev-parse', '--git-dir'], $output, $dir) && trim($output) === '.') { try { try { $commands = [ ['git', 'remote', 'set-url', 'origin', '--', '%url%'], ['git', 'remote', 'update', '--prune', 'origin'], ['git', 'gc', '--auto'], ]; $this->runCommands($commands, $url, $dir); } finally { $this->runCommands([['git', 'remote', 'set-url', 'origin', '--', '%sanitizedUrl%']], $url, $dir); } } catch (\Exception $e) { $this->io->writeError('Sync mirror failed: ' . $e->getMessage() . '', true, IOInterface::DEBUG); return false; } return true; } self::checkForRepoOwnershipError($this->process->getErrorOutput(), $dir); $this->filesystem->removeDirectory($dir); $this->runCommands([['git', 'clone', '--mirror', '--', '%url%', $dir]], $url, $dir, true); $this->runCommands([['git', 'remote', 'set-url', 'origin', '--', '%sanitizedUrl%']], $url, $dir); return true; } public function fetchRefOrSyncMirror(string $url, string $dir, string $ref, ?string $prettyVersion = null): bool { if ($this->checkRefIsInMirror($dir, $ref)) { if (Preg::isMatch('{^[a-f0-9]{40}$}', $ref) && $prettyVersion !== null) { $branch = Preg::replace('{(?:^dev-|(?:\.x)?-dev$)}i', '', $prettyVersion); $branches = null; $tags = null; if (0 === $this->process->execute(['git', 'branch'], $output, $dir)) { $branches = $output; } if (0 === $this->process->execute(['git', 'tag'], $output, $dir)) { $tags = $output; } if (null !== $branches && !Preg::isMatch('{^[\s*]*v?'.preg_quote($branch).'$}m', $branches) && null !== $tags && !Preg::isMatch('{^[\s*]*'.preg_quote($branch).'$}m', $tags) ) { $this->syncMirror($url, $dir); } } return true; } if ($this->syncMirror($url, $dir)) { return $this->checkRefIsInMirror($dir, $ref); } return false; } public static function getNoShowSignatureFlag(ProcessExecutor $process): string { $gitVersion = self::getVersion($process); if ($gitVersion !== null && version_compare($gitVersion, '2.10.0-rc0', '>=')) { return ' --no-show-signature'; } return ''; } public static function getNoShowSignatureFlags(ProcessExecutor $process): array { $flags = static::getNoShowSignatureFlag($process); if ('' === $flags) { return []; } return explode(' ', substr($flags, 1)); } public static function supportsNoCommitHeaderFlag(ProcessExecutor $process): bool { $gitVersion = self::getVersion($process); return $gitVersion !== null && version_compare($gitVersion, '2.33.0-rc0', '>='); } public static function buildRevListCommand(ProcessExecutor $process, array $arguments): array { $command = ['git', 'rev-list']; if (self::supportsNoCommitHeaderFlag($process)) { $command[] = '--no-commit-header'; } return array_merge($command, $arguments); } public static function parseRevListOutput(string $output, ProcessExecutor $process): string { if (self::supportsNoCommitHeaderFlag($process)) { return $output; } return Preg::replace('{^commit [a-f0-9]{40}\n?}m', '', $output); } private function checkRefIsInMirror(string $dir, string $ref): bool { if (is_dir($dir) && 0 === $this->process->execute(['git', 'rev-parse', '--git-dir'], $output, $dir) && trim($output) === '.') { $exitCode = $this->process->execute(['git', 'rev-parse', '--quiet', '--verify', $ref.'^{commit}'], $ignoredOutput, $dir); if ($exitCode === 0) { return true; } } self::checkForRepoOwnershipError($this->process->getErrorOutput(), $dir); return false; } private function getAuthenticationFailure(string $url): ?array { if (!Preg::isMatchStrictGroups('{^(https?://)([^/]+)(.*)$}i', $url, $match)) { return null; } $authFailures = [ 'fatal: Authentication failed', 'remote error: Invalid username or password.', 'error: 401 Unauthorized', 'fatal: unable to access', 'fatal: could not read Username', ]; $errorOutput = $this->process->getErrorOutput(); foreach ($authFailures as $authFailure) { if (strpos($errorOutput, $authFailure) !== false) { return $match; } } return null; } public function getMirrorDefaultBranch(string $url, string $dir, bool $isLocalPathRepository): ?string { if ((bool) Platform::getEnv('COMPOSER_DISABLE_NETWORK')) { return null; } try { if ($isLocalPathRepository) { $this->process->execute(['git', 'remote', 'show', 'origin'], $output, $dir); } else { $commands = [ ['git', 'remote', 'set-url', 'origin', '--', '%url%'], ['git', 'remote', 'show', 'origin'], ['git', 'remote', 'set-url', 'origin', '--', '%sanitizedUrl%'], ]; $this->runCommands($commands, $url, $dir, false, $output); } $lines = $this->process->splitLines($output); foreach ($lines as $line) { if (Preg::isMatch('{^\s*HEAD branch:\s(.+)\s*$}m', $line, $matches)) { return $matches[1]; } } } catch (\Exception $e) { $this->io->writeError('Failed to fetch root identifier from remote: ' . $e->getMessage() . '', true, IOInterface::DEBUG); } return null; } public static function cleanEnv(?ProcessExecutor $process = null): void { $gitVersion = self::getVersion($process ?? new ProcessExecutor()); if ($gitVersion !== null && version_compare($gitVersion, '2.3.0', '>=')) { if (Platform::getEnv('GIT_TERMINAL_PROMPT') !== '0') { Platform::putEnv('GIT_TERMINAL_PROMPT', '0'); } } else { if (Platform::getEnv('GIT_ASKPASS') !== 'echo') { Platform::putEnv('GIT_ASKPASS', 'echo'); } } if (Platform::getEnv('GIT_DIR')) { Platform::clearEnv('GIT_DIR'); } if (Platform::getEnv('GIT_WORK_TREE')) { Platform::clearEnv('GIT_WORK_TREE'); } if (Platform::getEnv('LANGUAGE') !== 'C') { Platform::putEnv('LANGUAGE', 'C'); } Platform::clearEnv('DYLD_LIBRARY_PATH'); } public static function getGitHubDomainsRegex(Config $config): string { return '(' . implode('|', array_map('preg_quote', $config->get('github-domains'))) . ')'; } public static function getGitLabDomainsRegex(Config $config): string { return '(' . implode('|', array_map('preg_quote', $config->get('gitlab-domains'))) . ')'; } private function throwException($message, string $url): void { clearstatcache(); if (0 !== $this->process->execute(['git', '--version'], $ignoredOutput)) { throw new \RuntimeException(Url::sanitize('Failed to clone ' . $url . ', git was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput())); } throw new \RuntimeException(Url::sanitize($message)); } public static function getVersion(ProcessExecutor $process): ?string { if (false === self::$version) { self::$version = null; if (0 === $process->execute(['git', '--version'], $output) && Preg::isMatch('/^git version (\d+(?:\.\d+)+)/m', $output, $matches)) { self::$version = $matches[1]; } } return self::$version; } private function maskCredentials(string $error, array $credentials): string { $maskedCredentials = []; foreach ($credentials as $credential) { if (in_array($credential, ['private-token', 'x-token-auth', 'oauth2', 'gitlab-ci-token', 'x-oauth-basic'])) { $maskedCredentials[] = $credential; } elseif (strlen($credential) > 6) { $maskedCredentials[] = substr($credential, 0, 3) . '...' . substr($credential, -3); } elseif (strlen($credential) > 3) { $maskedCredentials[] = substr($credential, 0, 3) . '...'; } else { $maskedCredentials[] = 'XXX'; } } return str_replace($credentials, $maskedCredentials, $error); } } io = $io; $this->config = $config; $this->process = $process ?: new ProcessExecutor($io); $this->httpDownloader = $httpDownloader ?: Factory::createHttpDownloader($this->io, $config); } public function authorizeOAuth(string $originUrl): bool { if (!in_array($originUrl, $this->config->get('github-domains'))) { return false; } if (0 === $this->process->execute(['git', 'config', 'github.accesstoken'], $output)) { $this->io->setAuthentication($originUrl, trim($output), 'x-oauth-basic'); return true; } return false; } public function authorizeOAuthInteractively(string $originUrl, ?string $message = null): bool { if ($message) { $this->io->writeError($message); } $note = 'Composer'; if ($this->config->get('github-expose-hostname') === true && 0 === $this->process->execute(['hostname'], $output)) { $note .= ' on ' . trim($output); } $note .= ' ' . date('Y-m-d Hi'); $localAuthConfig = $this->config->getLocalAuthConfigSource(); $this->io->writeError([ 'You need to provide a GitHub access token.', sprintf('Tokens will be stored in plain text in "%s" for future use by Composer.', ($localAuthConfig !== null ? $localAuthConfig->getName() . ' OR ' : '') . $this->config->getAuthConfigSource()->getName()), 'Due to the security risk of tokens being exfiltrated, use tokens with short expiration times and only the minimum permissions necessary.', '', 'Carefully consider the following options in order:', '', ]); $this->io->writeError([ '1. When you don\'t use \'vcs\' type \'repositories\' in composer.json and do not need to clone source or download dist files', 'from private GitHub repositories over HTTPS, use a fine-grained token with read-only access to public information.', 'Use the following URL to create such a token:', 'https://'.$originUrl.'/settings/personal-access-tokens/new?name=' . str_replace('%20', '+', rawurlencode($note)), '', ]); $this->io->writeError([ '2. When all relevant _private_ GitHub repositories belong to a single user or organisation, use a fine-grained token with', 'repository "content" read-only permissions. You can start with the following URL, but you may need to change the resource owner', 'to the right user or organisation. Additionally, you can scope permissions down to apply only to selected repositories.', 'https://'.$originUrl.'/settings/personal-access-tokens/new?contents=read&name=' . str_replace('%20', '+', rawurlencode($note)), '', ]); $this->io->writeError([ '3. A "classic" token grants broad permissions on your behalf to all repositories accessible by you.', 'This may include write permissions, even though not needed by Composer. Use it only when you need to access', 'private repositories across multiple organisations at the same time and using directory-specific authentication sources', 'is not an option. You can generate a classic token here:', 'https://'.$originUrl.'/settings/tokens/new?scopes=repo&description=' . str_replace('%20', '+', rawurlencode($note)), '', ]); $this->io->writeError('For additional information, check https://getcomposer.org/doc/articles/authentication-for-private-packages.md#github-oauth'); $storeInLocalAuthConfig = false; if ($localAuthConfig !== null) { $storeInLocalAuthConfig = $this->io->askConfirmation('A local auth config source was found, do you want to store the token there?', true); } $token = trim((string) $this->io->askAndHideAnswer('Token (hidden): ')); if ($token === '') { $this->io->writeError('No token given, aborting.'); $this->io->writeError('You can also add it manually later by using "composer config --global --auth github-oauth.github.com "'); return false; } $this->io->setAuthentication($originUrl, $token, 'x-oauth-basic'); try { $apiUrl = ('github.com' === $originUrl) ? 'api.github.com/' : $originUrl . '/api/v3/'; $this->httpDownloader->get('https://'. $apiUrl, [ 'retry-auth-failure' => false, ]); } catch (TransportException $e) { if (in_array($e->getCode(), [403, 401])) { $this->io->writeError('Invalid token provided.'); $this->io->writeError('You can also add it manually later by using "composer config --global --auth github-oauth.github.com "'); return false; } throw $e; } $authConfigSource = $storeInLocalAuthConfig && $localAuthConfig !== null ? $localAuthConfig : $this->config->getAuthConfigSource(); $this->config->getConfigSource()->removeConfigSetting('github-oauth.'.$originUrl); $authConfigSource->addConfigSetting('github-oauth.'.$originUrl, $token); $this->io->writeError('Token stored successfully.'); return true; } public function getRateLimit(array $headers): array { $rateLimit = [ 'limit' => '?', 'reset' => '?', ]; foreach ($headers as $header) { $header = trim($header); if (false === stripos($header, 'x-ratelimit-')) { continue; } [$type, $value] = explode(':', $header, 2); switch (strtolower($type)) { case 'x-ratelimit-limit': $rateLimit['limit'] = (int) trim($value); break; case 'x-ratelimit-reset': $rateLimit['reset'] = date('Y-m-d H:i:s', (int) trim($value)); break; } } return $rateLimit; } public function getSsoUrl(array $headers): ?string { foreach ($headers as $header) { $header = trim($header); if (false === stripos($header, 'x-github-sso: required')) { continue; } if (Preg::isMatch('{\burl=(?P[^\s;]+)}', $header, $match)) { return $match['url']; } } return null; } public function isRateLimited(array $headers): bool { foreach ($headers as $header) { if (Preg::isMatch('{^x-ratelimit-remaining: *0$}i', trim($header))) { return true; } } return false; } public function requiresSso(array $headers): bool { foreach ($headers as $header) { if (Preg::isMatch('{^x-github-sso: required}i', trim($header))) { return true; } } return false; } } io = $io; $this->config = $config; $this->process = $process ?: new ProcessExecutor($io); $this->httpDownloader = $httpDownloader ?: Factory::createHttpDownloader($this->io, $config); } public function authorizeOAuth(string $originUrl): bool { $bcOriginUrl = Preg::replace('{:\d+}', '', $originUrl); if (!in_array($originUrl, $this->config->get('gitlab-domains'), true) && !in_array($bcOriginUrl, $this->config->get('gitlab-domains'), true)) { return false; } if (0 === $this->process->execute(['git', 'config', 'gitlab.accesstoken'], $output)) { $this->io->setAuthentication($originUrl, trim($output), 'oauth2'); return true; } if (0 === $this->process->execute(['git', 'config', 'gitlab.deploytoken.user'], $tokenUser) && 0 === $this->process->execute(['git', 'config', 'gitlab.deploytoken.token'], $tokenPassword)) { $this->io->setAuthentication($originUrl, trim($tokenUser), trim($tokenPassword)); return true; } $authTokens = $this->config->get('gitlab-token'); if (isset($authTokens[$originUrl])) { $token = $authTokens[$originUrl]; } if (isset($authTokens[$bcOriginUrl])) { $token = $authTokens[$bcOriginUrl]; } if (isset($token)) { $username = is_array($token) ? $token["username"] : $token; $password = is_array($token) ? $token["token"] : 'private-token'; if (in_array($username, ['private-token', 'gitlab-ci-token', 'oauth2'], true)) { $this->io->setAuthentication($originUrl, $password, $username); } else { $this->io->setAuthentication($originUrl, $username, $password); } return true; } return false; } public function authorizeOAuthInteractively(string $scheme, string $originUrl, ?string $message = null): bool { if ($message) { $this->io->writeError($message); } $localAuthConfig = $this->config->getLocalAuthConfigSource(); $personalAccessTokenLink = $scheme.'://'.$originUrl.'/-/user_settings/personal_access_tokens'; $revokeLink = $scheme.'://'.$originUrl.'/-/user_settings/applications'; $this->io->writeError(sprintf('A token will be created and stored in "%s", your password will never be stored', ($localAuthConfig !== null ? $localAuthConfig->getName() . ' OR ' : '') . $this->config->getAuthConfigSource()->getName())); $this->io->writeError('To revoke access to this token you can visit:'); $this->io->writeError($revokeLink); $this->io->writeError('Alternatively you can setup an personal access token on:'); $this->io->writeError($personalAccessTokenLink); $this->io->writeError('and store it under "gitlab-token" see https://getcomposer.org/doc/articles/authentication-for-private-packages.md#gitlab-token for more details.'); $this->io->writeError('https://getcomposer.org/doc/articles/authentication-for-private-packages.md#gitlab-token'); $this->io->writeError('for more details.'); $storeInLocalAuthConfig = false; if ($localAuthConfig !== null) { $storeInLocalAuthConfig = $this->io->askConfirmation('A local auth config source was found, do you want to store the token there?', true); } $attemptCounter = 0; while ($attemptCounter++ < 5) { try { $response = $this->createToken($scheme, $originUrl); } catch (TransportException $e) { if (in_array($e->getCode(), [403, 401])) { if (401 === $e->getCode()) { $response = json_decode($e->getResponse(), true); if (isset($response['error']) && $response['error'] === 'invalid_grant') { $this->io->writeError('Bad credentials. If you have two factor authentication enabled you will have to manually create a personal access token'); } else { $this->io->writeError('Bad credentials.'); } } else { $this->io->writeError('Maximum number of login attempts exceeded. Please try again later.'); } $this->io->writeError('You can also manually create a personal access token enabling the "read_api" scope at:'); $this->io->writeError($personalAccessTokenLink); $this->io->writeError('Add it using "composer config --global --auth gitlab-token.'.$originUrl.' "'); continue; } throw $e; } $this->io->setAuthentication($originUrl, $response['access_token'], 'oauth2'); $authConfigSource = $storeInLocalAuthConfig && $localAuthConfig !== null ? $localAuthConfig : $this->config->getAuthConfigSource(); if (isset($response['expires_in'])) { $authConfigSource->addConfigSetting( 'gitlab-oauth.'.$originUrl, [ 'expires-at' => intval($response['created_at']) + intval($response['expires_in']), 'refresh-token' => $response['refresh_token'], 'token' => $response['access_token'], ] ); } else { $authConfigSource->addConfigSetting('gitlab-oauth.'.$originUrl, $response['access_token']); } return true; } throw new \RuntimeException('Invalid GitLab credentials 5 times in a row, aborting.'); } public function authorizeOAuthRefresh(string $scheme, string $originUrl): bool { try { $response = $this->refreshToken($scheme, $originUrl); } catch (TransportException $e) { $this->io->writeError("Couldn't refresh access token: ".$e->getMessage()); return false; } $this->io->setAuthentication($originUrl, $response['access_token'], 'oauth2'); $this->config->getAuthConfigSource()->addConfigSetting( 'gitlab-oauth.'.$originUrl, [ 'expires-at' => intval($response['created_at']) + intval($response['expires_in']), 'refresh-token' => $response['refresh_token'], 'token' => $response['access_token'], ] ); return true; } private function createToken(string $scheme, string $originUrl): array { $username = $this->io->ask('Username: '); $password = $this->io->askAndHideAnswer('Password: '); $headers = ['Content-Type: application/x-www-form-urlencoded']; $apiUrl = $originUrl; $data = http_build_query([ 'username' => $username, 'password' => $password, 'grant_type' => 'password', ], '', '&'); $options = [ 'retry-auth-failure' => false, 'http' => [ 'method' => 'POST', 'header' => $headers, 'content' => $data, ], ]; $token = $this->httpDownloader->get($scheme.'://'.$apiUrl.'/oauth/token', $options)->decodeJson(); $this->io->writeError('Token successfully created'); return $token; } public function isOAuthExpired(string $originUrl): bool { $authTokens = $this->config->get('gitlab-oauth'); if (isset($authTokens[$originUrl]['expires-at'])) { if ($authTokens[$originUrl]['expires-at'] < time()) { return true; } } return false; } private function refreshToken(string $scheme, string $originUrl): array { $authTokens = $this->config->get('gitlab-oauth'); if (!isset($authTokens[$originUrl]['refresh-token'])) { throw new \RuntimeException('No GitLab refresh token present for '.$originUrl.'.'); } $refreshToken = $authTokens[$originUrl]['refresh-token']; $headers = ['Content-Type: application/x-www-form-urlencoded']; $data = http_build_query([ 'refresh_token' => $refreshToken, 'grant_type' => 'refresh_token', ], '', '&'); $options = [ 'retry-auth-failure' => false, 'http' => [ 'method' => 'POST', 'header' => $headers, 'content' => $data, ], ]; $token = $this->httpDownloader->get($scheme.'://'.$originUrl.'/oauth/token', $options)->decodeJson(); $this->io->writeError('GitLab token successfully refreshed', true, IOInterface::VERY_VERBOSE); $this->io->writeError('To revoke access to this token you can visit '.$scheme.'://'.$originUrl.'/-/user_settings/applications', true, IOInterface::VERY_VERBOSE); return $token; } } io = $io; $this->config = $config; $this->process = $process; } public function runCommand(callable $commandCallable, string $url, ?string $cwd): void { $this->config->prohibitUrlByConfig($url, $this->io); $command = $commandCallable($url); if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) { return; } if ( Preg::isMatch('{^(?Pssh|https?)://(?:(?P[^:@]+)(?::(?P[^:@]+))?@)?(?P[^/]+)(?P/.*)?}mi', $url, $matches) && $this->io->hasAuthentication($matches['host']) ) { if ($matches['proto'] === 'ssh') { $user = ''; if ($matches['user'] !== null) { $user = rawurlencode($matches['user']) . '@'; } $authenticatedUrl = $matches['proto'] . '://' . $user . $matches['host'] . $matches['path']; } else { $auth = $this->io->getAuthentication($matches['host']); $authenticatedUrl = $matches['proto'] . '://' . rawurlencode((string) $auth['username']) . ':' . rawurlencode((string) $auth['password']) . '@' . $matches['host'] . $matches['path']; } $command = $commandCallable($authenticatedUrl); if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) { return; } $error = $this->process->getErrorOutput(); } else { $error = 'The given URL (' .Url::sanitize($url). ') does not match the required format (ssh|http(s)://(username:password@)example.com/path-to-repository)'; } $this->throwException("Failed to clone $url, \n\n" . $error, $url); } private function throwException($message, string $url): void { if (null === self::getVersion($this->process)) { throw new \RuntimeException(Url::sanitize( 'Failed to clone ' . $url . ', hg was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput() )); } throw new \RuntimeException(Url::sanitize($message)); } public static function getVersion(ProcessExecutor $process): ?string { if (false === self::$version) { self::$version = null; if (0 === $process->execute(['hg', '--version'], $output) && Preg::isMatch('/^.+? (\d+(?:\.\d+)+)(?:\+.*?)?\)?\r?\n/', $output, $matches)) { self::$version = $matches[1]; } } return self::$version; } } ['CURLM_BAD_HANDLE', 'The passed-in handle is not a valid CURLM handle.'], CURLM_BAD_EASY_HANDLE => ['CURLM_BAD_EASY_HANDLE', "An easy handle was not good/valid. It could mean that it isn't an easy handle at all, or possibly that the handle already is in used by this or another multi handle."], CURLM_OUT_OF_MEMORY => ['CURLM_OUT_OF_MEMORY', 'You are doomed.'], CURLM_INTERNAL_ERROR => ['CURLM_INTERNAL_ERROR', 'This can only be returned if libcurl bugs. Please report it to us!'], ]; private static $options = [ 'http' => [ 'method' => CURLOPT_CUSTOMREQUEST, 'content' => CURLOPT_POSTFIELDS, 'header' => CURLOPT_HTTPHEADER, 'timeout' => CURLOPT_TIMEOUT, ], 'ssl' => [ 'cafile' => CURLOPT_CAINFO, 'capath' => CURLOPT_CAPATH, 'verify_peer' => CURLOPT_SSL_VERIFYPEER, 'verify_peer_name' => CURLOPT_SSL_VERIFYHOST, 'local_cert' => CURLOPT_SSLCERT, 'local_pk' => CURLOPT_SSLKEY, 'passphrase' => CURLOPT_SSLKEYPASSWD, ], ]; private static $timeInfo = [ 'total_time' => true, 'namelookup_time' => true, 'connect_time' => true, 'pretransfer_time' => true, 'starttransfer_time' => true, 'redirect_time' => true, ]; public function __construct(IOInterface $io, Config $config, array $options = [], bool $disableTls = false) { $this->io = $io; $this->config = $config; $this->multiHandle = $mh = curl_multi_init(); if (function_exists('curl_multi_setopt')) { if (ProxyManager::getInstance()->hasProxy() && ($version = curl_version()) !== false && in_array($version['version'], self::BAD_MULTIPLEXING_CURL_VERSIONS, true)) { curl_multi_setopt($mh, CURLMOPT_PIPELINING, 0); } else { curl_multi_setopt($mh, CURLMOPT_PIPELINING, \PHP_VERSION_ID >= 70400 ? 2 : 3); } if (defined('CURLMOPT_MAX_HOST_CONNECTIONS') && !defined('HHVM_VERSION')) { curl_multi_setopt($mh, CURLMOPT_MAX_HOST_CONNECTIONS, 8); } } if (function_exists('curl_share_init')) { $this->shareHandle = $sh = curl_share_init(); curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE); curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS); curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION); } $this->authHelper = new AuthHelper($io, $config); } public function download(callable $resolve, callable $reject, string $origin, string $url, array $options, ?string $copyTo = null): int { $attributes = []; if (isset($options['retry-auth-failure'])) { $attributes['retryAuthFailure'] = $options['retry-auth-failure']; unset($options['retry-auth-failure']); } return $this->initDownload($resolve, $reject, $origin, $url, $options, $copyTo, $attributes); } private function initDownload(callable $resolve, callable $reject, string $origin, string $url, array $options, ?string $copyTo = null, array $attributes = []): int { $attributes = array_merge([ 'retryAuthFailure' => true, 'redirects' => 0, 'retries' => 0, 'storeAuth' => false, 'ipResolve' => null, ], $attributes); if ($attributes['ipResolve'] === null && Platform::getEnv('COMPOSER_IPRESOLVE') === '4') { $attributes['ipResolve'] = 4; } elseif ($attributes['ipResolve'] === null && Platform::getEnv('COMPOSER_IPRESOLVE') === '6') { $attributes['ipResolve'] = 6; } $originalOptions = $options; if (!Preg::isMatch('{^http://(repo\.)?packagist\.org/p/}', $url) || (false === strpos($url, '$') && false === strpos($url, '%24'))) { $this->config->prohibitUrlByConfig($url, $this->io, $options); } if ( isset($options['prevent_url_access_callable']) && is_callable($options['prevent_url_access_callable']) && $options['prevent_url_access_callable']($url) ) { throw new TransportException('Access to "'.Url::sanitize($url).'" is blocked.'); } $curlHandle = curl_init(); $headerHandle = fopen('php://temp/maxmemory:32768', 'w+b'); if (false === $headerHandle) { throw new \RuntimeException('Failed to open a temp stream to store curl headers'); } if ($copyTo !== null) { $bodyTarget = $copyTo.'~'; } else { $bodyTarget = 'php://temp/maxmemory:524288'; } $errorMessage = ''; set_error_handler(static function (int $code, string $msg) use (&$errorMessage): bool { if ($errorMessage) { $errorMessage .= "\n"; } $errorMessage .= Preg::replace('{^fopen\(.*?\): }', '', $msg); return true; }); $bodyHandle = fopen($bodyTarget, 'w+b'); restore_error_handler(); if (false === $bodyHandle) { throw new TransportException('The "'.Url::sanitize($url).'" file could not be written to '.($copyTo ?? 'a temporary file').': '.$errorMessage); } curl_setopt($curlHandle, CURLOPT_URL, $url); curl_setopt($curlHandle, CURLOPT_FOLLOWLOCATION, false); curl_setopt($curlHandle, CURLOPT_CONNECTTIMEOUT, 10); curl_setopt($curlHandle, CURLOPT_TIMEOUT, max((int) ini_get("default_socket_timeout"), 300)); curl_setopt($curlHandle, CURLOPT_WRITEHEADER, $headerHandle); curl_setopt($curlHandle, CURLOPT_FILE, $bodyHandle); curl_setopt($curlHandle, CURLOPT_ENCODING, ""); curl_setopt($curlHandle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); if ($attributes['ipResolve'] === 4) { curl_setopt($curlHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); } elseif ($attributes['ipResolve'] === 6) { curl_setopt($curlHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6); } if (function_exists('curl_share_init')) { curl_setopt($curlHandle, CURLOPT_SHARE, $this->shareHandle); } if (!isset($options['http']['header'])) { $options['http']['header'] = []; } $options['http']['header'] = array_diff($options['http']['header'], ['Connection: close']); $options['http']['header'][] = 'Connection: keep-alive'; $version = curl_version(); $features = $version['features']; $proxy = ProxyManager::getInstance()->getProxyForRequest($url); if (0 === strpos($url, 'https://')) { $willUseProxy = $proxy->getStatus() !== '' && !$proxy->isExcludedByNoProxy(); if (!$willUseProxy && \defined('CURL_VERSION_HTTP3') && \defined('CURL_HTTP_VERSION_3') && (CURL_VERSION_HTTP3 & $features) !== 0) { curl_setopt($curlHandle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_3); } elseif (\defined('CURL_VERSION_HTTP2') && \defined('CURL_HTTP_VERSION_2_0') && (CURL_VERSION_HTTP2 & $features) !== 0) { curl_setopt($curlHandle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0); } } if (isset($version['version']) && in_array($version['version'], ['8.7.0', '8.7.1'], true) && \defined('CURL_VERSION_LIBZ') && (CURL_VERSION_LIBZ & $features) !== 0) { curl_setopt($curlHandle, CURLOPT_ENCODING, "gzip"); } $options = $this->authHelper->addAuthenticationOptions($options, $origin, $url); $options = StreamContextFactory::initOptions($url, $options, true); foreach (self::$options as $type => $curlOptions) { foreach ($curlOptions as $name => $curlOption) { if (isset($options[$type][$name])) { if ($type === 'ssl' && $name === 'verify_peer_name') { curl_setopt($curlHandle, $curlOption, $options[$type][$name] === true ? 2 : $options[$type][$name]); } else { curl_setopt($curlHandle, $curlOption, $options[$type][$name]); } } } } curl_setopt_array($curlHandle, $proxy->getCurlOptions($options['ssl'] ?? [])); $progress = array_diff_key(curl_getinfo($curlHandle), self::$timeInfo); $this->jobs[(int) $curlHandle] = [ 'url' => $url, 'origin' => $origin, 'attributes' => $attributes, 'options' => $originalOptions, 'progress' => $progress, 'curlHandle' => $curlHandle, 'filename' => $copyTo, 'headerHandle' => $headerHandle, 'bodyHandle' => $bodyHandle, 'resolve' => $resolve, 'reject' => $reject, 'primaryIp' => '', ]; $usingProxy = $proxy->getStatus(' using proxy (%s)'); $ifModified = false !== stripos(implode(',', $options['http']['header']), 'if-modified-since:') ? ' if modified' : ''; if ($attributes['redirects'] === 0 && $attributes['retries'] === 0) { $this->io->writeError('Downloading ' . Url::sanitize($url) . $usingProxy . $ifModified, true, IOInterface::DEBUG); } $this->checkCurlResult(curl_multi_add_handle($this->multiHandle, $curlHandle)); return (int) $curlHandle; } public function abortRequest(int $id): void { if (isset($this->jobs[$id], $this->jobs[$id]['curlHandle'])) { $job = $this->jobs[$id]; curl_multi_remove_handle($this->multiHandle, $job['curlHandle']); if (\PHP_VERSION_ID < 80000) { curl_close($job['curlHandle']); } if (is_resource($job['headerHandle'])) { fclose($job['headerHandle']); } if (is_resource($job['bodyHandle'])) { fclose($job['bodyHandle']); } if (null !== $job['filename']) { @unlink($job['filename'].'~'); } unset($this->jobs[$id]); } } public function tick(): void { if (count($this->jobs) === 0) { return; } $active = true; $this->checkCurlResult(curl_multi_exec($this->multiHandle, $active)); if (-1 === curl_multi_select($this->multiHandle, $this->selectTimeout)) { usleep(150); } while ($progress = curl_multi_info_read($this->multiHandle)) { $curlHandle = $progress['handle']; $result = $progress['result']; $i = (int) $curlHandle; if (!isset($this->jobs[$i])) { continue; } $progress = curl_getinfo($curlHandle); if (false === $progress) { throw new \RuntimeException('Failed getting info from curl handle '.$i.' ('.Url::sanitize($this->jobs[$i]['url']).')'); } $job = $this->jobs[$i]; unset($this->jobs[$i]); $error = curl_error($curlHandle); $errno = curl_errno($curlHandle); curl_multi_remove_handle($this->multiHandle, $curlHandle); if (\PHP_VERSION_ID < 80000) { curl_close($curlHandle); } $headers = null; $statusCode = null; $response = null; try { if (CURLE_OK !== $errno || $error || $result !== CURLE_OK) { $errno = $errno ?: $result; if (!$error && function_exists('curl_strerror')) { $error = curl_strerror($errno); } $progress['error_code'] = $errno; if ( (!isset($job['options']['http']['method']) || $job['options']['http']['method'] === 'GET') && ( in_array($errno, [7 , 16 , 92 , 6 , 28 ], true) || (in_array($errno, [56 , 35 ], true) && str_contains((string) $error, 'Connection reset by peer')) ) && $job['attributes']['retries'] < $this->maxRetries ) { $attributes = ['retries' => $job['attributes']['retries'] + 1]; if ($errno === 7 && !isset($job['attributes']['ipResolve'])) { $attributes['ipResolve'] = 4; } $this->io->writeError('Retrying ('.($job['attributes']['retries'] + 1).') ' . Url::sanitize($job['url']) . ' due to curl error '. $errno, true, IOInterface::DEBUG); $this->restartJobWithDelay($job, $job['url'], $attributes); continue; } if ($errno === 55 ) { $this->io->writeError('Retrying ('.($job['attributes']['retries'] + 1).') ' . Url::sanitize($job['url']) . ' due to curl error '. $errno, true, IOInterface::DEBUG); $this->restartJobWithDelay($job, $job['url'], ['retries' => $job['attributes']['retries'] + 1]); continue; } throw new TransportException('curl error '.$errno.' while downloading '.Url::sanitize($progress['url']).': '.$error); } $statusCode = $progress['http_code']; rewind($job['headerHandle']); $headers = explode("\r\n", rtrim(stream_get_contents($job['headerHandle']))); fclose($job['headerHandle']); if ($statusCode === 0) { throw new \LogicException('Received unexpected http status code 0 without error for '.Url::sanitize($progress['url']).': headers '.var_export($headers, true).' curl info '.var_export($progress, true)); } if (null !== $job['filename']) { $contents = $job['filename'].'~'; if ($statusCode >= 300) { rewind($job['bodyHandle']); $contents = stream_get_contents($job['bodyHandle']); } $response = new CurlResponse(['url' => $job['url']], $statusCode, $headers, $contents, $progress); $this->io->writeError('['.$statusCode.'] '.Url::sanitize($job['url']), true, IOInterface::DEBUG); } else { $maxFileSize = $job['options']['max_file_size'] ?? null; rewind($job['bodyHandle']); if ($maxFileSize !== null) { $contents = stream_get_contents($job['bodyHandle'], $maxFileSize); if ($contents !== false && Platform::strlen($contents) >= $maxFileSize) { throw new MaxFileSizeExceededException('Maximum allowed download size reached. Downloaded ' . Platform::strlen($contents) . ' of allowed ' . $maxFileSize . ' bytes'); } } else { $contents = stream_get_contents($job['bodyHandle']); } $response = new CurlResponse(['url' => $job['url']], $statusCode, $headers, $contents, $progress); $this->io->writeError('['.$statusCode.'] '.Url::sanitize($job['url']), true, IOInterface::DEBUG); } fclose($job['bodyHandle']); if ($response->getStatusCode() >= 300 && $response->getHeader('content-type') === 'application/json') { HttpDownloader::outputWarnings($this->io, $job['origin'], json_decode($response->getBody(), true)); } $result = $this->isAuthenticatedRetryNeeded($job, $response); if ($result['retry']) { $this->restartJob($job, $job['url'], ['storeAuth' => $result['storeAuth'], 'retries' => $job['attributes']['retries'] + 1]); continue; } if ($statusCode >= 300 && $statusCode <= 399 && $statusCode !== 304 && $job['attributes']['redirects'] < $this->maxRedirects) { $location = $this->handleRedirect($job, $response); if ($location) { $this->restartJob($job, $location, ['redirects' => $job['attributes']['redirects'] + 1]); continue; } } if ($statusCode >= 400 && $statusCode <= 599) { if ( (!isset($job['options']['http']['method']) || $job['options']['http']['method'] === 'GET') && in_array($statusCode, [423, 425, 500, 502, 503, 504, 507, 510], true) && $job['attributes']['retries'] < $this->maxRetries ) { $this->io->writeError('Retrying ('.($job['attributes']['retries'] + 1).') ' . Url::sanitize($job['url']) . ' due to status code '. $statusCode, true, IOInterface::DEBUG); $this->restartJobWithDelay($job, $job['url'], ['retries' => $job['attributes']['retries'] + 1]); continue; } throw $this->failResponse($job, $response, $response->getStatusMessage()); } if ($job['attributes']['storeAuth'] !== false) { $this->authHelper->storeAuth($job['origin'], $job['attributes']['storeAuth']); } if (null !== $job['filename']) { rename($job['filename'].'~', $job['filename']); $job['resolve']($response); } else { $job['resolve']($response); } } catch (\Exception $e) { if ($e instanceof TransportException) { if (null !== $headers) { $e->setHeaders($headers); $e->setStatusCode($statusCode); } if (null !== $response) { $e->setResponse($response->getBody()); } $e->setResponseInfo($progress); } $this->rejectJob($job, $e); } } foreach ($this->jobs as $i => $curlHandle) { $curlHandle = $this->jobs[$i]['curlHandle']; $progress = array_diff_key(curl_getinfo($curlHandle), self::$timeInfo); if ($this->jobs[$i]['progress'] !== $progress) { $this->jobs[$i]['progress'] = $progress; if (isset($this->jobs[$i]['options']['max_file_size'])) { if ($this->jobs[$i]['options']['max_file_size'] < $progress['download_content_length']) { $this->rejectJob($this->jobs[$i], new MaxFileSizeExceededException('Maximum allowed download size reached. Content-length header indicates ' . $progress['download_content_length'] . ' bytes. Allowed ' . $this->jobs[$i]['options']['max_file_size'] . ' bytes')); } if ($this->jobs[$i]['options']['max_file_size'] < $progress['size_download']) { $this->rejectJob($this->jobs[$i], new MaxFileSizeExceededException('Maximum allowed download size reached. Downloaded ' . $progress['size_download'] . ' of allowed ' . $this->jobs[$i]['options']['max_file_size'] . ' bytes')); } } if (isset($progress['primary_ip']) && $progress['primary_ip'] !== $this->jobs[$i]['primaryIp']) { if ( isset($this->jobs[$i]['options']['prevent_ip_access_callable']) && is_callable($this->jobs[$i]['options']['prevent_ip_access_callable']) && $this->jobs[$i]['options']['prevent_ip_access_callable']($progress['primary_ip']) ) { $this->rejectJob($this->jobs[$i], new TransportException(sprintf('IP "%s" is blocked for "%s".', $progress['primary_ip'], Url::sanitize($progress['url'])))); } $this->jobs[$i]['primaryIp'] = (string) $progress['primary_ip']; } } } } private function handleRedirect(array $job, Response $response): string { if ($locationHeader = $response->getHeader('location')) { if (parse_url($locationHeader, PHP_URL_SCHEME)) { $targetUrl = $locationHeader; } elseif (parse_url($locationHeader, PHP_URL_HOST)) { $targetUrl = parse_url($job['url'], PHP_URL_SCHEME).':'.$locationHeader; } elseif ('/' === $locationHeader[0]) { $urlHost = parse_url($job['url'], PHP_URL_HOST); $targetUrl = Preg::replace('{^(.+(?://|@)'.preg_quote($urlHost).'(?::\d+)?)(?:[/\?].*)?$}', '\1'.$locationHeader, $job['url']); } else { $targetUrl = Preg::replace('{^(.+/)[^/?]*(?:\?.*)?$}', '\1'.$locationHeader, $job['url']); } } if (!empty($targetUrl)) { $this->io->writeError(sprintf('Following redirect (%u) %s', $job['attributes']['redirects'] + 1, Url::sanitize($targetUrl)), true, IOInterface::DEBUG); return $targetUrl; } throw new TransportException('The "'.Url::sanitize($job['url']).'" file could not be downloaded, got redirect without Location ('.$response->getStatusMessage().')'); } private function isAuthenticatedRetryNeeded(array $job, Response $response): array { if (in_array($response->getStatusCode(), [401, 403]) && $job['attributes']['retryAuthFailure']) { $result = $this->authHelper->promptAuthIfNeeded($job['url'], $job['origin'], $response->getStatusCode(), $response->getStatusMessage(), $response->getHeaders(), $job['attributes']['retries'], $response->getBody()); if ($result['retry']) { return $result; } } $locationHeader = $response->getHeader('location'); $needsAuthRetry = false; if ( $job['origin'] === 'bitbucket.org' && !$this->authHelper->isPublicBitBucketDownload($job['url']) && substr($job['url'], -4) === '.zip' && (!$locationHeader || substr($locationHeader, -4) !== '.zip') && Preg::isMatch('{^text/html\b}i', $response->getHeader('content-type')) ) { $needsAuthRetry = 'Bitbucket requires authentication and it was not provided'; } if ( $response->getStatusCode() === 404 && in_array($job['origin'], $this->config->get('gitlab-domains'), true) && false !== strpos($job['url'], 'archive.zip') ) { $needsAuthRetry = 'GitLab requires authentication and it was not provided'; } if ($needsAuthRetry) { if ($job['attributes']['retryAuthFailure']) { $result = $this->authHelper->promptAuthIfNeeded($job['url'], $job['origin'], 401, null, [], $job['attributes']['retries']); if ($result['retry']) { return $result; } } throw $this->failResponse($job, $response, $needsAuthRetry); } return ['retry' => false, 'storeAuth' => false]; } private function restartJob(array $job, string $url, array $attributes = []): void { if (null !== $job['filename']) { @unlink($job['filename'].'~'); } $attributes = array_merge($job['attributes'], $attributes); $origin = Url::getOrigin($this->config, $url); $this->initDownload($job['resolve'], $job['reject'], $origin, $url, $job['options'], $job['filename'], $attributes); } private function restartJobWithDelay(array $job, string $url, array $attributes): void { if ($attributes['retries'] >= 3) { usleep(500000); } elseif ($attributes['retries'] >= 2) { usleep(100000); } $this->restartJob($job, $url, $attributes); } private function failResponse(array $job, Response $response, string $errorMessage): TransportException { if (null !== $job['filename']) { @unlink($job['filename'].'~'); } $details = ''; if (in_array(strtolower((string) $response->getHeader('content-type')), ['application/json', 'application/json; charset=utf-8'], true)) { $details = ':'.PHP_EOL.substr($response->getBody(), 0, 200).(strlen($response->getBody()) > 200 ? '...' : ''); } return new TransportException('The "'.Url::sanitize($job['url']).'" file could not be downloaded ('.$errorMessage.')' . $details, $response->getStatusCode()); } private function rejectJob(array $job, \Exception $e): void { if (is_resource($job['headerHandle'])) { fclose($job['headerHandle']); } if (is_resource($job['bodyHandle'])) { fclose($job['bodyHandle']); } if (null !== $job['filename']) { @unlink($job['filename'].'~'); } $job['reject']($e); } private function checkCurlResult(int $code): void { if ($code !== CURLM_OK && $code !== CURLM_CALL_MULTI_PERFORM) { throw new \RuntimeException( isset($this->multiErrors[$code]) ? "cURL error: {$code} ({$this->multiErrors[$code][0]}): cURL message: {$this->multiErrors[$code][1]}" : 'Unexpected cURL error: ' . $code ); } } } curlInfo = $curlInfo; } public function getCurlInfo(): array { return $this->curlInfo; } } 0) { $this->curlAuth = $user; $this->optionsAuth = 'Proxy-Authorization: Basic ' . base64_encode($auth); } } $host = $proxy['host']; $port = null; if (isset($proxy['port'])) { $port = $proxy['port']; } elseif ($scheme === 'http://') { $port = 80; } elseif ($scheme === 'https://') { $port = 443; } if ($port === null) { throw new \RuntimeException('unable to find proxy port in ' . $envName); } if ($port === 0) { throw new \RuntimeException('port 0 is reserved in ' . $envName); } $this->url = sprintf('%s%s:%d', $scheme, $host, $port); $this->safeUrl = sprintf('%s%s%s:%d', $scheme, $safe, $host, $port); $scheme = str_replace(['http://', 'https://'], ['tcp://', 'ssl://'], $scheme); $this->optionsProxy = sprintf('%s%s:%d', $scheme, $host, $port); } public function toRequestProxy(string $scheme): RequestProxy { $options = ['http' => ['proxy' => $this->optionsProxy]]; if ($this->optionsAuth !== null) { $options['http']['header'] = $this->optionsAuth; } if ($scheme === 'http') { $options['http']['request_fulluri'] = true; } return new RequestProxy($this->url, $this->curlAuth, $options, $this->safeUrl); } } getProxyData(); } catch (\RuntimeException $e) { $this->error = $e->getMessage(); } } public static function getInstance(): ProxyManager { if (self::$instance === null) { self::$instance = new self(); } return self::$instance; } public static function reset(): void { self::$instance = null; } public function hasProxy(): bool { return $this->httpProxy !== null || $this->httpsProxy !== null; } public function getProxyForRequest(string $requestUrl): RequestProxy { if ($this->error !== null) { throw new TransportException('Unable to use a proxy: '.$this->error); } $scheme = (string) parse_url($requestUrl, PHP_URL_SCHEME); $proxy = $this->getProxyForScheme($scheme); if ($proxy === null) { return RequestProxy::none(); } if ($this->noProxy($requestUrl)) { return RequestProxy::noProxy(); } return $proxy->toRequestProxy($scheme); } private function getProxyForScheme(string $scheme): ?ProxyItem { $scheme = strtolower($scheme); if ($scheme === 'http') { return $this->httpProxy; } if ($scheme === 'https') { return $this->httpsProxy; } return null; } private function getProxyData(): void { if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { [$env, $name] = $this->getProxyEnv('http_proxy'); if ($env !== null) { $this->httpProxy = new ProxyItem($env, $name); } } if ($this->httpProxy === null) { [$env, $name] = $this->getProxyEnv('cgi_http_proxy'); if ($env !== null) { $this->httpProxy = new ProxyItem($env, $name); } } [$env, $name] = $this->getProxyEnv('https_proxy'); if ($env !== null) { $this->httpsProxy = new ProxyItem($env, $name); } [$env, $name] = $this->getProxyEnv('no_proxy'); if ($env !== null) { $this->noProxyHandler = new NoProxyPattern($env); } } private function getProxyEnv(string $envName): array { $names = [strtolower($envName), strtoupper($envName)]; foreach ($names as $name) { if (is_string($_SERVER[$name] ?? null)) { if ($_SERVER[$name] !== '') { return [$_SERVER[$name], $name]; } } } return [null, '']; } private function noProxy(string $requestUrl): bool { if ($this->noProxyHandler === null) { return false; } return $this->noProxyHandler->test($requestUrl); } } url = $url; $this->auth = $auth; $this->contextOptions = $contextOptions; $this->status = $status; } public static function none(): RequestProxy { return new self(null, null, null, null); } public static function noProxy(): RequestProxy { return new self(null, null, null, 'excluded by no_proxy'); } public function getContextOptions(): ?array { return $this->contextOptions; } public function getCurlOptions(array $sslOptions): array { if ($this->isSecure() && !$this->supportsSecureProxy()) { throw new TransportException('Cannot use an HTTPS proxy. PHP >= 7.3 and cUrl >= 7.52.0 are required.'); } $options = [CURLOPT_PROXY => (string) $this->url]; if ($this->url !== null) { $options[CURLOPT_NOPROXY] = ''; } if ($this->auth !== null) { $options[CURLOPT_PROXYAUTH] = CURLAUTH_BASIC; $options[CURLOPT_PROXYUSERPWD] = $this->auth; } if ($this->isSecure()) { if (isset($sslOptions['cafile'])) { $options[CURLOPT_PROXY_CAINFO] = $sslOptions['cafile']; } if (isset($sslOptions['capath'])) { $options[CURLOPT_PROXY_CAPATH] = $sslOptions['capath']; } } return $options; } public function getStatus(?string $format = null): string { if ($this->status === null) { return ''; } $format = $format ?? '%s'; if (strpos($format, '%s') !== false) { return sprintf($format, $this->status); } throw new \InvalidArgumentException('String format specifier is missing'); } public function isExcludedByNoProxy(): bool { return $this->status !== null && $this->url === null; } public function isSecure(): bool { return 0 === strpos((string) $this->url, 'https://'); } public function supportsSecureProxy(): bool { if (false === ($version = curl_version()) || !defined('CURL_VERSION_HTTPS_PROXY')) { return false; } $features = $version['features']; return (bool) ($features & CURL_VERSION_HTTPS_PROXY); } } request = $request; $this->code = (int) $code; $this->headers = $headers; $this->body = $body; } public function getStatusCode(): int { return $this->code; } public function getStatusMessage(): ?string { $value = null; foreach ($this->headers as $header) { if (Preg::isMatch('{^HTTP/\S+ \d+}i', $header)) { $value = $header; } } return $value; } public function getHeaders(): array { return $this->headers; } public function getHeader(string $name): ?string { return self::findHeaderValue($this->headers, $name); } public function getBody(): ?string { return $this->body; } public function decodeJson() { return JsonFile::parseJson($this->body, $this->request['url']); } public function collect(): void { unset($this->request, $this->code, $this->headers, $this->body); } public static function findHeaderValue(array $headers, string $name): ?string { $value = null; foreach ($headers as $header) { if (Preg::isMatch('{^'.preg_quote($name).':\s*(.+?)\s*$}i', $header, $match)) { $value = $match[1]; } } return $value; } } io = $io; $this->disabled = (bool) Platform::getEnv('COMPOSER_DISABLE_NETWORK'); if ($disableTls === false) { $this->options = StreamContextFactory::getTlsDefaults($options, $io); } $this->options = array_replace_recursive($this->options, $options); $this->config = $config; if (self::isCurlEnabled()) { $this->curl = new CurlDownloader($io, $config, $options, $disableTls); } $this->rfs = new RemoteFilesystem($io, $config, $options, $disableTls); if (is_numeric($maxJobs = Platform::getEnv('COMPOSER_MAX_PARALLEL_HTTP'))) { $this->maxJobs = max(1, min(50, (int) $maxJobs)); } } public function get(string $url, array $options = []) { if ('' === $url) { throw new \InvalidArgumentException('$url must not be an empty string'); } [$job, $promise] = $this->addJob(['url' => $url, 'options' => $options, 'copyTo' => null], true); $promise->then(null, static function (\Throwable $e) { }); $this->wait($job['id']); $response = $this->getResponse($job['id']); return $response; } public function add(string $url, array $options = []) { if ('' === $url) { throw new \InvalidArgumentException('$url must not be an empty string'); } [, $promise] = $this->addJob(['url' => $url, 'options' => $options, 'copyTo' => null]); return $promise; } public function copy(string $url, string $to, array $options = []) { if ('' === $url) { throw new \InvalidArgumentException('$url must not be an empty string'); } [$job, $promise] = $this->addJob(['url' => $url, 'options' => $options, 'copyTo' => $to], true); $promise->then(null, static function (\Throwable $e) { }); $this->wait($job['id']); return $this->getResponse($job['id']); } public function addCopy(string $url, string $to, array $options = []) { if ('' === $url) { throw new \InvalidArgumentException('$url must not be an empty string'); } [, $promise] = $this->addJob(['url' => $url, 'options' => $options, 'copyTo' => $to]); return $promise; } public function getOptions() { return $this->options; } public function setOptions(array $options) { $this->options = array_replace_recursive($this->options, $options); } private function addJob(array $request, bool $sync = false): array { $request['options'] = array_replace_recursive($this->options, $request['options']); $job = [ 'id' => $this->idGen++, 'status' => self::STATUS_QUEUED, 'request' => $request, 'sync' => $sync, 'origin' => Url::getOrigin($this->config, $request['url']), ]; if (!$sync && !$this->allowAsync) { throw new \LogicException('You must use the HttpDownloader instance which is part of a Composer\Loop instance to be able to run async http requests'); } if (Preg::isMatchStrictGroups('{^https?://([^:/]+):([^@/]+)@([^/]+)}i', $request['url'], $match)) { $this->io->setAuthentication($job['origin'], rawurldecode($match[1]), rawurldecode($match[2])); } $rfs = $this->rfs; if ($this->canUseCurl($job)) { $resolver = static function ($resolve, $reject) use (&$job): void { $job['status'] = HttpDownloader::STATUS_QUEUED; $job['resolve'] = $resolve; $job['reject'] = $reject; }; } else { $resolver = static function ($resolve, $reject) use (&$job, $rfs): void { $url = $job['request']['url']; $options = $job['request']['options']; $job['status'] = HttpDownloader::STATUS_STARTED; if ($job['request']['copyTo']) { $rfs->copy($job['origin'], $url, $job['request']['copyTo'], false , $options); $headers = $rfs->getLastHeaders(); $response = new Response($job['request'], $rfs->findStatusCode($headers), $headers, $job['request']['copyTo'].'~'); $resolve($response); } else { $body = $rfs->getContents($job['origin'], $url, false , $options); $headers = $rfs->getLastHeaders(); $response = new Response($job['request'], $rfs->findStatusCode($headers), $headers, $body); $resolve($response); } }; } $curl = $this->curl; $canceler = static function () use (&$job, $curl): void { if ($job['status'] === HttpDownloader::STATUS_QUEUED) { $job['status'] = HttpDownloader::STATUS_ABORTED; } if ($job['status'] !== HttpDownloader::STATUS_STARTED) { return; } $job['status'] = HttpDownloader::STATUS_ABORTED; if (isset($job['curl_id'])) { $curl->abortRequest($job['curl_id']); } throw new IrrecoverableDownloadException('Download of ' . Url::sanitize($job['request']['url']) . ' canceled'); }; $promise = new Promise($resolver, $canceler); $promise = $promise->then(function ($response) use (&$job) { $job['status'] = HttpDownloader::STATUS_COMPLETED; $job['response'] = $response; $this->markJobDone(); return $response; }, function ($e) use (&$job): void { $job['status'] = HttpDownloader::STATUS_FAILED; $job['exception'] = $e; $this->markJobDone(); throw $e; }); $this->jobs[$job['id']] = &$job; if ($this->runningJobs < $this->maxJobs) { $this->startJob($job['id']); } return [$job, $promise]; } private function startJob(int $id): void { $job = &$this->jobs[$id]; if ($job['status'] !== self::STATUS_QUEUED) { return; } $job['status'] = self::STATUS_STARTED; $this->runningJobs++; assert(isset($job['resolve'])); assert(isset($job['reject'])); $resolve = $job['resolve']; $reject = $job['reject']; $url = $job['request']['url']; $options = $job['request']['options']; $origin = $job['origin']; if ($this->disabled) { if (isset($job['request']['options']['http']['header']) && false !== stripos(implode('', $job['request']['options']['http']['header']), 'if-modified-since')) { $resolve(new Response(['url' => $url], 304, [], '')); } else { $e = new TransportException('Network disabled, request canceled: '.Url::sanitize($url), 499); $e->setStatusCode(499); $reject($e); } return; } try { if ($job['request']['copyTo']) { $job['curl_id'] = $this->curl->download($resolve, $reject, $origin, $url, $options, $job['request']['copyTo']); } else { $job['curl_id'] = $this->curl->download($resolve, $reject, $origin, $url, $options); } } catch (\Exception $exception) { $reject($exception); } } private function markJobDone(): void { $this->runningJobs--; } public function wait(?int $index = null) { do { $jobCount = $this->countActiveJobs($index); } while ($jobCount); } public function enableAsync(): void { $this->allowAsync = true; } public function countActiveJobs(?int $index = null): int { if ($this->runningJobs < $this->maxJobs) { foreach ($this->jobs as $job) { if ($job['status'] === self::STATUS_QUEUED && $this->runningJobs < $this->maxJobs) { $this->startJob($job['id']); } } } if ($this->curl) { $this->curl->tick(); } if (null !== $index) { return $this->jobs[$index]['status'] < self::STATUS_COMPLETED ? 1 : 0; } $active = 0; foreach ($this->jobs as $job) { if ($job['status'] < self::STATUS_COMPLETED) { $active++; } elseif (!$job['sync']) { unset($this->jobs[$job['id']]); } } return $active; } private function getResponse(int $index): Response { if (!isset($this->jobs[$index])) { throw new \LogicException('Invalid request id'); } if ($this->jobs[$index]['status'] === self::STATUS_FAILED) { assert(isset($this->jobs[$index]['exception'])); throw $this->jobs[$index]['exception']; } if (!isset($this->jobs[$index]['response'])) { throw new \LogicException('Response not available yet, call wait() first'); } $resp = $this->jobs[$index]['response']; unset($this->jobs[$index]); return $resp; } public static function outputWarnings(IOInterface $io, string $url, $data): void { $cleanMessage = static function ($msg) use ($io) { if (!$io->isDecorated()) { $msg = Preg::replace('{'.chr(27).'\\[[;\d]*m}u', '', $msg); } return $msg; }; foreach (['warning', 'info'] as $type) { if (empty($data[$type])) { continue; } if (!empty($data[$type . '-versions'])) { $versionParser = new VersionParser(); $constraint = $versionParser->parseConstraints($data[$type . '-versions']); $composer = new Constraint('==', $versionParser->normalize(Composer::getVersion())); if (!$constraint->matches($composer)) { continue; } } $io->writeError('<'.$type.'>'.ucfirst($type).' from '.Url::sanitize($url).': '.$cleanMessage($data[$type]).''); } foreach (['warnings', 'infos'] as $key) { if (empty($data[$key])) { continue; } $versionParser = new VersionParser(); foreach ($data[$key] as $spec) { $type = substr($key, 0, -1); $constraint = $versionParser->parseConstraints($spec['versions']); $composer = new Constraint('==', $versionParser->normalize(Composer::getVersion())); if (!$constraint->matches($composer)) { continue; } $io->writeError('<'.$type.'>'.ucfirst($type).' from '.Url::sanitize($url).': '.$cleanMessage($spec['message']).''); } } } public static function getExceptionHints(\Throwable $e): ?array { if (!$e instanceof TransportException) { return null; } if ( false !== strpos($e->getMessage(), 'Resolving timed out') || false !== strpos($e->getMessage(), 'Could not resolve host') ) { Silencer::suppress(); $testConnectivity = file_get_contents('https://8.8.8.8', false, stream_context_create([ 'ssl' => ['verify_peer' => false], 'http' => ['follow_location' => false, 'ignore_errors' => true], ])); Silencer::restore(); if (false !== $testConnectivity) { return [ 'The following exception probably indicates you have misconfigured DNS resolver(s)', ]; } return [ 'The following exception probably indicates you are offline or have misconfigured DNS resolver(s)', ]; } return null; } private function canUseCurl(array $job): bool { if (!$this->curl) { return false; } if (!Preg::isMatch('{^https?://}i', $job['request']['url'])) { return false; } if (!empty($job['request']['options']['ssl']['allow_self_signed'])) { return false; } return true; } public static function isCurlEnabled(): bool { return \extension_loaded('curl') && \function_exists('curl_multi_exec') && \function_exists('curl_multi_init'); } } httpDownloader = $httpDownloader; $this->httpDownloader->enableAsync(); $this->processExecutor = $processExecutor; if ($this->processExecutor) { $this->processExecutor->enableAsync(); } } public function getHttpDownloader(): HttpDownloader { return $this->httpDownloader; } public function getProcessExecutor(): ?ProcessExecutor { return $this->processExecutor; } public function wait(array $promises, ?ProgressBar $progress = null): void { $uncaught = null; \React\Promise\all($promises)->then( static function (): void { }, static function (\Throwable $e) use (&$uncaught): void { $uncaught = $e; } ); $waitIndex = $this->waitIndex++; $this->currentPromises[$waitIndex] = $promises; if ($progress) { $totalJobs = 0; $totalJobs += $this->httpDownloader->countActiveJobs(); if ($this->processExecutor) { $totalJobs += $this->processExecutor->countActiveJobs(); } $progress->start($totalJobs); } $lastUpdate = 0; while (true) { $activeJobs = 0; $activeJobs += $this->httpDownloader->countActiveJobs(); if ($this->processExecutor) { $activeJobs += $this->processExecutor->countActiveJobs(); } if ($progress && microtime(true) - $lastUpdate > 0.1) { $lastUpdate = microtime(true); $progress->setProgress($progress->getMaxSteps() - $activeJobs); } if (!$activeJobs) { break; } } if ($progress) { $progress->finish(); } unset($this->currentPromises[$waitIndex]); if (null !== $uncaught) { throw $uncaught; } } public function abortJobs(): void { foreach ($this->currentPromises as $promiseGroup) { foreach ($promiseGroup as $promise) { \React\Promise\resolve($promise)->cancel(); } } } } hostNames = Preg::split('{[\s,]+}', $pattern, -1, PREG_SPLIT_NO_EMPTY); $this->noproxy = empty($this->hostNames) || '*' === $this->hostNames[0]; } public function test(string $url): bool { if ($this->noproxy) { return true; } if (!$urlData = $this->getUrlData($url)) { return false; } foreach ($this->hostNames as $index => $hostName) { if ($this->match($index, $hostName, $urlData)) { return true; } } return false; } protected function getUrlData(string $url) { if (!$host = parse_url($url, PHP_URL_HOST)) { return false; } $port = parse_url($url, PHP_URL_PORT); if (empty($port)) { switch (strtolower((string) parse_url($url, PHP_URL_SCHEME))) { case 'http': $port = 80; break; case 'https': $port = 443; break; } } $hostName = $host . ($port ? ':' . $port : ''); [$host, $port, $err] = $this->splitHostPort($hostName); if ($err || !$this->ipCheckData($host, $ipdata)) { return false; } return $this->makeData($host, $port, $ipdata); } protected function match(int $index, string $hostName, stdClass $url): bool { if (!$rule = $this->getRule($index, $hostName)) { return false; } if ($rule->ipdata) { if (!$url->ipdata) { return false; } if ($rule->ipdata->netmask) { return $this->matchRange($rule->ipdata, $url->ipdata); } $match = $rule->ipdata->ip === $url->ipdata->ip; } else { $haystack = substr($url->name, -strlen($rule->name)); $match = stripos($haystack, $rule->name) === 0; } if ($match && $rule->port) { $match = $rule->port === $url->port; } return $match; } protected function matchRange(stdClass $network, stdClass $target): bool { $net = unpack('C*', $network->ip); $mask = unpack('C*', $network->netmask); $ip = unpack('C*', $target->ip); if (false === $net) { throw new \RuntimeException('Could not parse network IP '.$network->ip); } if (false === $mask) { throw new \RuntimeException('Could not parse netmask '.$network->netmask); } if (false === $ip) { throw new \RuntimeException('Could not parse target IP '.$target->ip); } for ($i = 1; $i < 17; ++$i) { if (($net[$i] & $mask[$i]) !== ($ip[$i] & $mask[$i])) { return false; } } return true; } private function getRule(int $index, string $hostName): ?stdClass { if (array_key_exists($index, $this->rules)) { return $this->rules[$index]; } $this->rules[$index] = null; [$host, $port, $err] = $this->splitHostPort($hostName); if ($err || !$this->ipCheckData($host, $ipdata, true)) { return null; } $this->rules[$index] = $this->makeData($host, $port, $ipdata); return $this->rules[$index]; } private function ipCheckData(string $host, ?stdClass &$ipdata, bool $allowPrefix = false): bool { $ipdata = null; $netmask = null; $prefix = null; $modified = false; if (strpos($host, '/') !== false) { [$host, $prefix] = explode('/', $host); if (!$allowPrefix || !$this->validateInt($prefix, 0, 128)) { return false; } $prefix = (int) $prefix; $modified = true; } if (!filter_var($host, FILTER_VALIDATE_IP)) { return !$modified; } [$ip, $size] = $this->ipGetAddr($host); if ($prefix !== null) { if ($prefix > $size * 8) { return false; } [$ip, $netmask] = $this->ipGetNetwork($ip, $size, $prefix); } $ipdata = $this->makeIpData($ip, $size, $netmask); return true; } private function ipGetAddr(string $host): array { $ip = inet_pton($host); $size = strlen($ip); $mapped = $this->ipMapTo6($ip, $size); return [$mapped, $size]; } private function ipGetMask(int $prefix, int $size): string { $mask = ''; if ($ones = floor($prefix / 8)) { $mask = str_repeat(chr(255), (int) $ones); } if ($remainder = $prefix % 8) { $mask .= chr(0xff ^ (0xff >> $remainder)); } $mask = str_pad($mask, $size, chr(0)); return $this->ipMapTo6($mask, $size); } private function ipGetNetwork(string $rangeIp, int $size, int $prefix): array { $netmask = $this->ipGetMask($prefix, $size); $mask = unpack('C*', $netmask); $ip = unpack('C*', $rangeIp); $net = ''; if (false === $mask) { throw new \RuntimeException('Could not parse netmask '.$netmask); } if (false === $ip) { throw new \RuntimeException('Could not parse range IP '.$rangeIp); } for ($i = 1; $i < 17; ++$i) { $net .= chr($ip[$i] & $mask[$i]); } return [$net, $netmask]; } private function ipMapTo6(string $binary, int $size): string { if ($size === 4) { $prefix = str_repeat(chr(0), 10) . str_repeat(chr(255), 2); $binary = $prefix . $binary; } return $binary; } private function makeData(string $host, int $port, ?stdClass $ipdata): stdClass { return (object) [ 'host' => $host, 'name' => '.' . ltrim($host, '.'), 'port' => $port, 'ipdata' => $ipdata, ]; } private function makeIpData(string $ip, int $size, ?string $netmask): stdClass { return (object) [ 'ip' => $ip, 'size' => $size, 'netmask' => $netmask, ]; } private function splitHostPort(string $hostName): array { $error = ['', '', true]; $port = 0; $ip6 = ''; if ($hostName[0] === '[') { $index = strpos($hostName, ']'); if (false === $index || $index < 3) { return $error; } $ip6 = substr($hostName, 1, $index - 1); $hostName = substr($hostName, $index + 1); if (strpbrk($hostName, '[]') !== false || substr_count($hostName, ':') > 1) { return $error; } } if (substr_count($hostName, ':') === 1) { $index = strpos($hostName, ':'); $port = substr($hostName, $index + 1); $hostName = substr($hostName, 0, $index); if (!$this->validateInt($port, 1, 65535)) { return $error; } $port = (int) $port; } $host = $ip6 . $hostName; return [$host, $port, false]; } private function validateInt(string $int, int $min, int $max): bool { $options = [ 'options' => [ 'min_range' => $min, 'max_range' => $max, ], ]; return false !== filter_var($int, FILTER_VALIDATE_INT, $options); } } getSupport()['source']) && '' !== $package->getSupport()['source']) { return $package->getSupport()['source']; } return $package->getSourceUrl(); } public static function getViewSourceOrHomepageUrl(PackageInterface $package): ?string { $url = self::getViewSourceUrl($package) ?? ($package instanceof CompletePackageInterface ? $package->getHomepage() : null); if ($url === '') { return null; } return $url; } } isDefaultBranch()) { return $candidate; } if (version_compare($highest->getVersion(), $candidate->getVersion(), '<')) { $highest = $candidate; } } return $highest; } public static function sortPackagesAlphabetically(array $packages): array { usort($packages, static function (PackageInterface $a, PackageInterface $b) { return $a->getName() <=> $b->getName(); }); return $packages; } public static function sortPackages(array $packages, array $weights = []): array { $usageList = []; foreach ($packages as $package) { $links = $package->getRequires(); if ($package instanceof RootPackageInterface) { $links = array_merge($links, $package->getDevRequires()); } foreach ($links as $link) { $target = $link->getTarget(); $usageList[$target][] = $package->getName(); } } $computing = []; $computed = []; $computeImportance = static function ($name) use (&$computeImportance, &$computing, &$computed, $usageList, $weights) { if (isset($computed[$name])) { return $computed[$name]; } if (isset($computing[$name])) { return 0; } $computing[$name] = true; $weight = $weights[$name] ?? 0; if (isset($usageList[$name])) { foreach ($usageList[$name] as $user) { $weight -= 1 - $computeImportance($user); } } unset($computing[$name]); $computed[$name] = $weight; return $weight; }; $weightedPackages = []; foreach ($packages as $index => $package) { $name = $package->getName(); $weight = $computeImportance($name); $weightedPackages[] = ['name' => $name, 'weight' => $weight, 'index' => $index]; } usort($weightedPackages, static function (array $a, array $b): int { if ($a['weight'] !== $b['weight']) { return $a['weight'] - $b['weight']; } return strnatcasecmp($a['name'], $b['name']); }); $sortedPackages = []; foreach ($weightedPackages as $pkg) { $sortedPackages[] = $packages[$pkg['index']]; } return $sortedPackages; } } windowsFlag = $isWindows; $this->p4Port = $port; $this->initializePath($path); $this->process = $process; $this->initialize($repoConfig); $this->io = $io; } public static function create($repoConfig, string $port, string $path, ProcessExecutor $process, IOInterface $io): self { return new Perforce($repoConfig, $port, $path, $process, Platform::isWindows(), $io); } public static function checkServerExists(string $url, ProcessExecutor $processExecutor): bool { return 0 === $processExecutor->execute(['p4', '-p', $url, 'info', '-s'], $ignoredOutput); } public function initialize($repoConfig): void { $this->uniquePerforceClientName = $this->generateUniquePerforceClientName(); if (!$repoConfig) { return; } if (isset($repoConfig['unique_perforce_client_name'])) { $this->uniquePerforceClientName = $repoConfig['unique_perforce_client_name']; } if (isset($repoConfig['depot'])) { $this->p4Depot = $repoConfig['depot']; } if (isset($repoConfig['branch'])) { $this->p4Branch = $repoConfig['branch']; } if (isset($repoConfig['p4user'])) { $this->p4User = $repoConfig['p4user']; } else { $this->p4User = $this->getP4variable('P4USER'); } if (isset($repoConfig['p4password'])) { $this->p4Password = $repoConfig['p4password']; } } public function initializeDepotAndBranch(?string $depot, ?string $branch): void { if (isset($depot)) { $this->p4Depot = $depot; } if (isset($branch)) { $this->p4Branch = $branch; } } public function generateUniquePerforceClientName(): string { return gethostname() . "_" . time(); } public function cleanupClientSpec(): void { $client = $this->getClient(); $task = ['client', '-d', $client]; $useP4Client = false; $command = $this->generateP4Command($task, $useP4Client); $this->executeCommand($command); $clientSpec = $this->getP4ClientSpec(); $fileSystem = $this->getFilesystem(); $fileSystem->remove($clientSpec); } protected function executeCommand($command): int { $this->commandResult = ''; return $this->process->execute($command, $this->commandResult); } public function getClient(): string { if (!isset($this->p4Client)) { $cleanStreamName = str_replace(['//', '/', '@'], ['', '_', ''], $this->getStream()); $this->p4Client = 'composer_perforce_' . $this->uniquePerforceClientName . '_' . $cleanStreamName; } return $this->p4Client; } protected function getPath(): string { return $this->path; } public function initializePath(string $path): void { $this->path = $path; $fs = $this->getFilesystem(); $fs->ensureDirectoryExists($path); } protected function getPort(): string { return $this->p4Port; } public function setStream(string $stream): void { $this->p4Stream = $stream; $index = strrpos($stream, '/'); if ($index > 2) { $this->p4DepotType = 'stream'; } } public function isStream(): bool { return is_string($this->p4DepotType) && (strcmp($this->p4DepotType, 'stream') === 0); } public function getStream(): string { if (!isset($this->p4Stream)) { if ($this->isStream()) { $this->p4Stream = '//' . $this->p4Depot . '/' . $this->p4Branch; } else { $this->p4Stream = '//' . $this->p4Depot; } } return $this->p4Stream; } public function getStreamWithoutLabel(string $stream): string { $index = strpos($stream, '@'); if ($index === false) { return $stream; } return substr($stream, 0, $index); } public function getP4ClientSpec(): string { return $this->path . '/' . $this->getClient() . '.p4.spec'; } public function getUser(): ?string { return $this->p4User; } public function setUser(?string $user): void { $this->p4User = $user; } public function queryP4User(): void { $this->getUser(); if (strlen((string) $this->p4User) > 0) { return; } $this->p4User = $this->getP4variable('P4USER'); if (strlen((string) $this->p4User) > 0) { return; } $this->p4User = $this->io->ask('Enter P4 User:'); if ($this->windowsFlag) { $command = $this->getP4Executable().' set P4USER=' . ProcessExecutor::escape($this->p4User); } else { $command = 'export P4USER=' . ProcessExecutor::escape($this->p4User); } $this->executeCommand($command); } protected function getP4variable(string $name): ?string { if ($this->windowsFlag) { $command = $this->getP4Executable().' set'; $this->executeCommand($command); $result = trim($this->commandResult); $resArray = explode(PHP_EOL, $result); foreach ($resArray as $line) { $fields = explode('=', $line); if (strcmp($name, $fields[0]) === 0) { $index = strpos($fields[1], ' '); if ($index === false) { $value = $fields[1]; } else { $value = substr($fields[1], 0, $index); } $value = trim($value); return $value; } } return null; } $command = 'echo $' . $name; $this->executeCommand($command); $result = trim($this->commandResult); return $result; } public function queryP4Password(): ?string { if (isset($this->p4Password)) { return $this->p4Password; } $password = $this->getP4variable('P4PASSWD'); if (strlen((string) $password) <= 0) { $password = $this->io->askAndHideAnswer('Enter password for Perforce user ' . $this->getUser() . ': '); } $this->p4Password = $password; return $password; } public function generateP4Command(array $arguments, bool $useClient = true): array { $p4Command = [$this->getP4Executable()]; if ($this->getUser() !== null) { $p4Command[] = '-u'; $p4Command[] = $this->getUser(); } if ($useClient) { $p4Command[] = '-c'; $p4Command[] = $this->getClient(); } $p4Command[] = '-p'; $p4Command[] = $this->getPort(); return array_merge($p4Command, $arguments); } public function isLoggedIn(): bool { $command = $this->generateP4Command(['login', '-s'], false); $exitCode = $this->executeCommand($command); if ($exitCode) { $errorOutput = $this->process->getErrorOutput(); $index = strpos($errorOutput, $this->getUser()); if ($index === false) { $index = strpos($errorOutput, 'p4'); if ($index === false) { return false; } throw new \Exception('p4 command not found in path: ' . $errorOutput); } throw new \Exception('Invalid user name: ' . $this->getUser()); } return true; } public function connectClient(): void { $p4CreateClientCommand = $this->generateP4Command(['client', '-i']); $process = new Process($p4CreateClientCommand, null, null, file_get_contents($this->getP4ClientSpec())); $process->run(); } public function syncCodeBase(?string $sourceReference): void { $prevDir = Platform::getCwd(); chdir($this->path); $p4SyncCommand = $this->generateP4Command(['sync', '-f']); if (null !== $sourceReference) { $p4SyncCommand[] = '@' . $sourceReference; } $this->executeCommand($p4SyncCommand); chdir($prevDir); } public function writeClientSpecToFile($spec): void { fwrite($spec, 'Client: ' . $this->getClient() . PHP_EOL . PHP_EOL); fwrite($spec, 'Update: ' . date('Y/m/d H:i:s') . PHP_EOL . PHP_EOL); fwrite($spec, 'Access: ' . date('Y/m/d H:i:s') . PHP_EOL); fwrite($spec, 'Owner: ' . $this->getUser() . PHP_EOL . PHP_EOL); fwrite($spec, 'Description:' . PHP_EOL); fwrite($spec, ' Created by ' . $this->getUser() . ' from composer.' . PHP_EOL . PHP_EOL); fwrite($spec, 'Root: ' . $this->getPath() . PHP_EOL . PHP_EOL); fwrite($spec, 'Options: noallwrite noclobber nocompress unlocked modtime rmdir' . PHP_EOL . PHP_EOL); fwrite($spec, 'SubmitOptions: revertunchanged' . PHP_EOL . PHP_EOL); fwrite($spec, 'LineEnd: local' . PHP_EOL . PHP_EOL); if ($this->isStream()) { fwrite($spec, 'Stream:' . PHP_EOL); fwrite($spec, ' ' . $this->getStreamWithoutLabel($this->p4Stream) . PHP_EOL); } else { fwrite( $spec, 'View: ' . $this->getStream() . '/... //' . $this->getClient() . '/... ' . PHP_EOL ); } } public function writeP4ClientSpec(): void { $clientSpec = $this->getP4ClientSpec(); $spec = fopen($clientSpec, 'w'); try { $this->writeClientSpecToFile($spec); } catch (\Exception $e) { fclose($spec); throw $e; } fclose($spec); } protected function read($pipe, $name): void { if (feof($pipe)) { return; } $line = fgets($pipe); while ($line !== false) { $line = fgets($pipe); } } public function windowsLogin(?string $password): int { $command = $this->generateP4Command(['login', '-a']); $process = new Process($command, null, null, $password); return $process->run(); } public function p4Login(): void { $this->queryP4User(); if (!$this->isLoggedIn()) { $password = $this->queryP4Password(); if ($this->windowsFlag) { $this->windowsLogin($password); } else { $command = $this->generateP4Command(['login', '-a'], false); $process = new Process($command, null, null, $password); $process->run(); if (!$process->isSuccessful()) { throw new \Exception("Error logging in:" . $this->process->getErrorOutput()); } } } } public function getComposerInformation(string $identifier): ?array { $composerFileContent = $this->getFileContent('composer.json', $identifier); if (!$composerFileContent) { return null; } return json_decode($composerFileContent, true); } public function getFileContent(string $file, string $identifier): ?string { $path = $this->getFilePath($file, $identifier); if ($path === null) { return null; } $command = $this->generateP4Command(['print', $path]); $this->executeCommand($command); $result = $this->commandResult; if (!trim($result)) { return null; } return $result; } public function getFilePath(string $file, string $identifier): ?string { $index = strpos($identifier, '@'); if ($index === false) { return $identifier. '/' . $file; } $path = substr($identifier, 0, $index) . '/' . $file . substr($identifier, $index); $command = $this->generateP4Command(['files', $path], false); $this->executeCommand($command); $result = $this->commandResult; $index2 = strpos($result, 'no such file(s).'); if ($index2 === false) { $index3 = strpos($result, 'change'); if ($index3 !== false) { $phrase = trim(substr($result, $index3)); $fields = explode(' ', $phrase); return substr($identifier, 0, $index) . '/' . $file . '@' . $fields[1]; } } return null; } public function getBranches(): array { $possibleBranches = []; if (!$this->isStream()) { $possibleBranches[$this->p4Branch] = $this->getStream(); } else { $command = $this->generateP4Command(['streams', '//' . $this->p4Depot . '/...']); $this->executeCommand($command); $result = $this->commandResult; $resArray = explode(PHP_EOL, $result); foreach ($resArray as $line) { $resBits = explode(' ', $line); if (count($resBits) > 4) { $branch = Preg::replace('/[^A-Za-z0-9 ]/', '', $resBits[4]); $possibleBranches[$branch] = $resBits[1]; } } } $command = $this->generateP4Command(['changes', $this->getStream() . '/...'], false); $this->executeCommand($command); $result = $this->commandResult; $resArray = explode(PHP_EOL, $result); $lastCommit = $resArray[0]; $lastCommitArr = explode(' ', $lastCommit); $lastCommitNum = $lastCommitArr[1]; return ['master' => $possibleBranches[$this->p4Branch] . '@'. $lastCommitNum]; } public function getTags(): array { $command = $this->generateP4Command(['labels']); $this->executeCommand($command); $result = $this->commandResult; $resArray = explode(PHP_EOL, $result); $tags = []; foreach ($resArray as $line) { if (strpos($line, 'Label') !== false) { $fields = explode(' ', $line); $tags[$fields[1]] = $this->getStream() . '@' . $fields[1]; } } return $tags; } public function checkStream(): bool { $command = $this->generateP4Command(['depots'], false); $this->executeCommand($command); $result = $this->commandResult; $resArray = explode(PHP_EOL, $result); foreach ($resArray as $line) { if (strpos($line, 'Depot') !== false) { $fields = explode(' ', $line); if (strcmp($this->p4Depot, $fields[1]) === 0) { $this->p4DepotType = $fields[3]; return $this->isStream(); } } } return false; } protected function getChangeList(string $reference): mixed { $index = strpos($reference, '@'); if ($index === false) { return null; } $label = substr($reference, $index); $command = $this->generateP4Command(['changes', '-m1', $label]); $this->executeCommand($command); $changes = $this->commandResult; if (strpos($changes, 'Change') !== 0) { return null; } $fields = explode(' ', $changes); return $fields[1]; } public function getCommitLogs(string $fromReference, string $toReference): mixed { $fromChangeList = $this->getChangeList($fromReference); if ($fromChangeList === null) { return null; } $toChangeList = $this->getChangeList($toReference); if ($toChangeList === null) { return null; } $index = strpos($fromReference, '@'); $main = substr($fromReference, 0, $index) . '/...'; $command = $this->generateP4Command(['filelog', $main . '@' . $fromChangeList . ',' . $toChangeList]); $this->executeCommand($command); return $this->commandResult; } public function getFilesystem(): Filesystem { if (null === $this->filesystem) { $this->filesystem = new Filesystem($this->process); } return $this->filesystem; } public function setFilesystem(Filesystem $fs): void { $this->filesystem = $fs; } private function getP4Executable(): string { static $p4Executable; if ($p4Executable) { return $p4Executable; } $finder = new ExecutableFinder(); return $p4Executable = $finder->find('p4') ?? 'p4'; } } %))(?P\w++)(?(percent)%)(?P.*)#', static function ($matches): string { if (Platform::isWindows() && $matches['var'] === 'HOME') { if ((bool) Platform::getEnv('HOME')) { return Platform::getEnv('HOME') . $matches['path']; } return Platform::getEnv('USERPROFILE') . $matches['path']; } return Platform::getEnv($matches['var']) . $matches['path']; }, $path); } public static function getUserDirectory(): string { if (false !== ($home = self::getEnv('HOME'))) { return $home; } if (self::isWindows() && false !== ($home = self::getEnv('USERPROFILE'))) { return $home; } if (\function_exists('posix_getuid') && \function_exists('posix_getpwuid')) { $info = posix_getpwuid(posix_getuid()); if (is_array($info)) { return $info['dir']; } } throw new \RuntimeException('Could not determine user directory'); } public static function isWindowsSubsystemForLinux(): bool { if (null === self::$isWindowsSubsystemForLinux) { self::$isWindowsSubsystemForLinux = false; if (self::isWindows()) { return self::$isWindowsSubsystemForLinux = false; } if ( !(bool) ini_get('open_basedir') && is_readable('/proc/version') && false !== stripos((string) Silencer::call('file_get_contents', '/proc/version'), 'microsoft') && !self::isDocker() ) { return self::$isWindowsSubsystemForLinux = true; } } return self::$isWindowsSubsystemForLinux; } public static function isWindows(): bool { return \defined('PHP_WINDOWS_VERSION_BUILD'); } public static function isDocker(): bool { if (null !== self::$isDocker) { return self::$isDocker; } if ((bool) ini_get('open_basedir')) { return self::$isDocker = false; } if (file_exists('/.dockerenv') || file_exists('/run/.containerenv') || file_exists('/var/run/.containerenv')) { return self::$isDocker = true; } $cgroups = [ '/proc/self/mountinfo', '/proc/1/cgroup', ]; foreach ($cgroups as $cgroup) { if (!is_readable($cgroup)) { continue; } try { $data = @file_get_contents($cgroup); } catch (\Throwable $e) { break; } if (!is_string($data)) { continue; } if (str_contains($data, '/var/lib/docker/') || str_contains($data, '/io.containerd.snapshotter')) { return self::$isDocker = true; } } return self::$isDocker = false; } public static function strlen(string $str): int { static $useMbString = null; if (null === $useMbString) { $useMbString = \function_exists('mb_strlen') && (bool) ini_get('mbstring.func_overload'); } if ($useMbString) { return mb_strlen($str, '8bit'); } return \strlen($str); } public static function isTty($fd = null): bool { if ($fd === null) { $fd = defined('STDOUT') ? STDOUT : fopen('php://stdout', 'w'); if ($fd === false) { return false; } } if (in_array(strtoupper((string) self::getEnv('MSYSTEM')), ['MINGW32', 'MINGW64'], true)) { return true; } if (function_exists('stream_isatty')) { return stream_isatty($fd); } if (function_exists('posix_isatty') && posix_isatty($fd)) { return true; } $stat = @fstat($fd); if ($stat === false) { return false; } return 0020000 === ($stat['mode'] & 0170000); } public static function isInputCompletionProcess(): bool { return '_complete' === ($_SERVER['argv'][1] ?? null); } public static function workaroundFilesystemIssues(): void { if (self::isVirtualBoxGuest()) { usleep(200000); } } private static function isVirtualBoxGuest(): bool { if (null === self::$isVirtualBoxGuest) { self::$isVirtualBoxGuest = false; if (self::isWindows()) { return self::$isVirtualBoxGuest; } if (function_exists('posix_getpwuid') && function_exists('posix_geteuid')) { $processUser = posix_getpwuid(posix_geteuid()); if (is_array($processUser) && $processUser['name'] === 'vagrant') { return self::$isVirtualBoxGuest = true; } } if (self::getEnv('COMPOSER_RUNTIME_ENV') === 'virtualbox') { return self::$isVirtualBoxGuest = true; } if (defined('PHP_OS_FAMILY') && PHP_OS_FAMILY === 'Linux') { $process = new ProcessExecutor(); try { if (0 === $process->execute(['lsmod'], $output) && str_contains($output, 'vboxguest')) { return self::$isVirtualBoxGuest = true; } } catch (\Exception $e) { } } } return self::$isVirtualBoxGuest; } public static function getDevNull(): string { if (self::isWindows()) { return 'NUL'; } return '/dev/null'; } } io = $io; $this->resetMaxJobs(); } public function execute($command, &$output = null, ?string $cwd = null): int { if (func_num_args() > 1) { return $this->doExecute($command, $cwd, false, $output); } return $this->doExecute($command, $cwd, false); } public function executeTty($command, ?string $cwd = null): int { if (Platform::isTty()) { return $this->doExecute($command, $cwd, true); } return $this->doExecute($command, $cwd, false); } private function runProcess($command, ?string $cwd, ?array $env, bool $tty, &$output = null): ?int { if (is_string($command)) { if (Platform::isWindows() && Preg::isMatch('{^([^:/\\\\]++) }', $command, $match)) { $command = substr_replace($command, self::escape(self::getExecutable($match[1])), 0, strlen($match[1])); } $process = Process::fromShellCommandline($command, $cwd, $env, null, static::getTimeout()); } else { if (Platform::isWindows() && \strlen($command[0]) === strcspn($command[0], ':/\\')) { $command[0] = self::getExecutable($command[0]); } $process = new Process($command, $cwd, $env, null, static::getTimeout()); } if (!Platform::isWindows() && $tty) { try { $process->setTty(true); } catch (RuntimeException $e) { } } $callback = is_callable($output) ? $output : function (string $type, string $buffer): void { $this->outputHandler($type, $buffer); }; $signalHandler = SignalHandler::create( [SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP], function (string $signal) { if ($this->io !== null) { $this->io->writeError( 'Received '.$signal.', aborting when child process is done', true, IOInterface::DEBUG ); } } ); try { $process->run($callback); if ($this->captureOutput && !is_callable($output)) { $output = $process->getOutput(); } $this->errorOutput = $process->getErrorOutput(); } catch (ProcessSignaledException $e) { if ($signalHandler->isTriggered()) { $signalHandler->exitWithLastSignal(); } } finally { $signalHandler->unregister(); } return $process->getExitCode(); } private function doExecute($command, ?string $cwd, bool $tty, &$output = null): int { $this->outputCommandRun($command, $cwd, false); $this->captureOutput = func_num_args() > 3; $this->errorOutput = ''; $env = null; $requiresGitDirEnv = $this->requiresGitDirEnv($command); if ($cwd !== null && $requiresGitDirEnv) { $isBareRepository = !is_dir(sprintf('%s/.git', rtrim($cwd, '/'))); if ($isBareRepository) { $configValue = ''; $this->runProcess(['git', 'config', 'safe.bareRepository'], $cwd, ['GIT_DIR' => $cwd], $tty, $configValue); $configValue = trim($configValue); if ($configValue === 'explicit') { $env = ['GIT_DIR' => $cwd]; } } } return $this->runProcess($command, $cwd, $env, $tty, $output); } public function executeAsync($command, ?string $cwd = null): PromiseInterface { if (!$this->allowAsync) { throw new \LogicException('You must use the ProcessExecutor instance which is part of a Composer\Loop instance to be able to run async processes'); } $job = [ 'id' => $this->idGen++, 'status' => self::STATUS_QUEUED, 'command' => $command, 'cwd' => $cwd, ]; $resolver = static function ($resolve, $reject) use (&$job): void { $job['status'] = ProcessExecutor::STATUS_QUEUED; $job['resolve'] = $resolve; $job['reject'] = $reject; }; $canceler = static function () use (&$job): void { if ($job['status'] === ProcessExecutor::STATUS_QUEUED) { $job['status'] = ProcessExecutor::STATUS_ABORTED; } if ($job['status'] !== ProcessExecutor::STATUS_STARTED) { return; } $job['status'] = ProcessExecutor::STATUS_ABORTED; try { if (defined('SIGINT')) { $job['process']->signal(SIGINT); } } catch (\Exception $e) { } $job['process']->stop(1); throw new \RuntimeException('Aborted process'); }; $promise = new Promise($resolver, $canceler); $promise = $promise->then(function () use (&$job) { if ($job['process']->isSuccessful()) { $job['status'] = ProcessExecutor::STATUS_COMPLETED; } else { $job['status'] = ProcessExecutor::STATUS_FAILED; } $this->markJobDone(); return $job['process']; }, function ($e) use (&$job): void { $job['status'] = ProcessExecutor::STATUS_FAILED; $this->markJobDone(); throw $e; }); $this->jobs[$job['id']] = &$job; if ($this->runningJobs < $this->maxJobs) { $this->startJob($job['id']); } return $promise; } protected function outputHandler(string $type, string $buffer): void { if ($this->captureOutput) { return; } if (null === $this->io) { echo $buffer; return; } if (Process::ERR === $type) { $this->io->writeErrorRaw($buffer, false); } else { $this->io->writeRaw($buffer, false); } } private function startJob(int $id): void { $job = &$this->jobs[$id]; if ($job['status'] !== self::STATUS_QUEUED) { return; } $job['status'] = self::STATUS_STARTED; $this->runningJobs++; $command = $job['command']; $cwd = $job['cwd']; $this->outputCommandRun($command, $cwd, true); try { if (is_string($command)) { $process = Process::fromShellCommandline($command, $cwd, null, null, static::getTimeout()); } else { $process = new Process($command, $cwd, null, null, static::getTimeout()); } } catch (\Throwable $e) { $job['reject']($e); return; } $job['process'] = $process; try { $process->start(); } catch (\Throwable $e) { $job['reject']($e); return; } } public function setMaxJobs(int $maxJobs): void { $this->maxJobs = $maxJobs; } public function resetMaxJobs(): void { if (is_numeric($maxJobs = Platform::getEnv('COMPOSER_MAX_PARALLEL_PROCESSES'))) { $this->maxJobs = max(1, min(50, (int) $maxJobs)); } else { $this->maxJobs = 10; } } public function wait($index = null): void { while (true) { if (0 === $this->countActiveJobs($index)) { return; } usleep(1000); } } public function enableAsync(): void { $this->allowAsync = true; } public function countActiveJobs($index = null): int { foreach ($this->jobs as $job) { if ($job['status'] === self::STATUS_STARTED) { if (!$job['process']->isRunning()) { call_user_func($job['resolve'], $job['process']); } $job['process']->checkTimeout(); } if ($this->runningJobs < $this->maxJobs) { if ($job['status'] === self::STATUS_QUEUED) { $this->startJob($job['id']); } } } if (null !== $index) { return $this->jobs[$index]['status'] < self::STATUS_COMPLETED ? 1 : 0; } $active = 0; foreach ($this->jobs as $job) { if ($job['status'] < self::STATUS_COMPLETED) { $active++; } else { unset($this->jobs[$job['id']]); } } return $active; } private function markJobDone(): void { $this->runningJobs--; } public function splitLines(?string $output): array { $output = trim((string) $output); return $output === '' ? [] : Preg::split('{\r?\n}', $output); } public function getErrorOutput(): string { return $this->errorOutput; } public static function getTimeout(): int { return static::$timeout; } public static function setTimeout(int $timeout): void { static::$timeout = $timeout; } public static function escape($argument): string { return self::escapeArgument($argument); } private function outputCommandRun($command, ?string $cwd, bool $async): void { if (null === $this->io || !$this->io->isDebug()) { return; } $commandString = is_string($command) ? $command : implode(' ', array_map(self::class.'::escape', $command)); $safeCommand = Preg::replaceCallback('{://(?P[^:/\s]+):(?P[^@\s/]+)@}i', static function ($m): string { if (Preg::isMatch(GitHub::GITHUB_TOKEN_REGEX, $m['user'])) { return '://***:***@'; } if (Preg::isMatch('{^[a-f0-9]{12,}$}', $m['user'])) { return '://***:***@'; } if (strlen($m['user']) >= 12) { return '://'.substr($m['user'], 0, 8).'***:***@'; } return '://'.$m['user'].':***@'; }, $commandString); $safeCommand = Preg::replace("{--password (.*[^\\\\]\') }", '--password \'***\' ', $safeCommand); $this->io->writeError('Executing'.($async ? ' async' : '').' command ('.($cwd ?: 'CWD').'): '.$safeCommand); } private static function escapeArgument($argument): string { if ('' === ($argument = (string) $argument)) { return escapeshellarg($argument); } if (!Platform::isWindows()) { return "'".str_replace("'", "'\\''", $argument)."'"; } $argument = strtr($argument, [ "\n" => ' ', "\u{ff02}" => '"', "\u{02ba}" => '"', "\u{301d}" => '"', "\u{301e}" => '"', "\u{030e}" => '"', "\u{ff1a}" => ':', "\u{0589}" => ':', "\u{2236}" => ':', "\u{ff0f}" => '/', "\u{2044}" => '/', "\u{2215}" => '/', "\u{00b4}" => '/', ]); $quote = strpbrk($argument, " \t,") !== false; $argument = Preg::replace('/(\\\\*)"/', '$1$1\\"', $argument, -1, $dquotes); $meta = $dquotes > 0 || Preg::isMatch('/%[^%]+%|![^!]+!/', $argument); if (!$meta && !$quote) { $quote = strpbrk($argument, '^&|<>()') !== false; } if ($quote) { $argument = '"'.Preg::replace('/(\\\\*)$/', '$1$1', $argument).'"'; } if ($meta) { $argument = Preg::replace('/(["^&|<>()%])/', '^$1', $argument); $argument = Preg::replace('/(!)/', '^^$1', $argument); } return $argument; } public function requiresGitDirEnv($command): bool { $cmd = !is_array($command) ? explode(' ', $command) : $command; if ($cmd[0] !== 'git') { return false; } foreach (self::GIT_CMDS_NEED_GIT_DIR as $gitCmd) { if (array_intersect($cmd, $gitCmd) === $gitCmd) { return true; } } return false; } private static function getExecutable(string $name): string { if (\in_array(strtolower($name), self::BUILTIN_CMD_COMMANDS, true)) { return $name; } if (!isset(self::$executables[$name])) { $path = (new ExecutableFinder())->find($name, $name); if ($path !== null) { self::$executables[$name] = $path; } } return self::$executables[$name] ?? $name; } } io = $io; if ($disableTls === false) { $this->options = StreamContextFactory::getTlsDefaults($options, $io); } else { $this->disableTls = true; } $this->options = array_replace_recursive($this->options, $options); $this->config = $config; $this->authHelper = $authHelper ?? new AuthHelper($io, $config); } public function copy(string $originUrl, string $fileUrl, string $fileName, bool $progress = true, array $options = []) { return $this->get($originUrl, $fileUrl, $options, $fileName, $progress); } public function getContents(string $originUrl, string $fileUrl, bool $progress = true, array $options = []) { return $this->get($originUrl, $fileUrl, $options, null, $progress); } public function getOptions() { return $this->options; } public function setOptions(array $options) { $this->options = array_replace_recursive($this->options, $options); } public function isTlsDisabled() { return $this->disableTls === true; } public function getLastHeaders() { return $this->lastHeaders; } public static function findStatusCode(array $headers) { $value = null; foreach ($headers as $header) { if (Preg::isMatch('{^HTTP/\S+ (\d+)}i', $header, $match)) { $value = (int) $match[1]; } } return $value; } public function findStatusMessage(array $headers) { $value = null; foreach ($headers as $header) { if (Preg::isMatch('{^HTTP/\S+ \d+}i', $header)) { $value = $header; } } return $value; } protected function get(string $originUrl, string $fileUrl, array $additionalOptions = [], ?string $fileName = null, bool $progress = true) { $this->scheme = parse_url(strtr($fileUrl, '\\', '/'), PHP_URL_SCHEME); $this->bytesMax = 0; $this->originUrl = $originUrl; $this->fileUrl = $fileUrl; $this->fileName = $fileName; $this->progress = $progress; $this->lastProgress = null; $retryAuthFailure = true; $this->lastHeaders = []; $this->redirects = 1; $tempAdditionalOptions = $additionalOptions; if (isset($tempAdditionalOptions['retry-auth-failure'])) { $retryAuthFailure = (bool) $tempAdditionalOptions['retry-auth-failure']; unset($tempAdditionalOptions['retry-auth-failure']); } $isRedirect = false; if (isset($tempAdditionalOptions['redirects'])) { $this->redirects = $tempAdditionalOptions['redirects']; $isRedirect = true; unset($tempAdditionalOptions['redirects']); } $options = $this->getOptionsForUrl($originUrl, $tempAdditionalOptions); unset($tempAdditionalOptions); $origFileUrl = $fileUrl; if (isset($options['prevent_ip_access_callable'])) { throw new \RuntimeException("RemoteFilesystem doesn't support the 'prevent_ip_access_callable' config."); } if (isset($options['prevent_url_access_callable'])) { throw new \RuntimeException("RemoteFilesystem doesn't support the 'prevent_url_access_callable' config."); } if (isset($options['gitlab-token'])) { $fileUrl .= (false === strpos($fileUrl, '?') ? '?' : '&') . 'access_token='.$options['gitlab-token']; unset($options['gitlab-token']); } if (isset($options['http'])) { $options['http']['ignore_errors'] = true; } if ($this->degradedMode && strpos($fileUrl, 'http://repo.packagist.org/') === 0) { $fileUrl = 'http://' . gethostbyname('repo.packagist.org') . substr($fileUrl, 20); $degradedPackagist = true; } $maxFileSize = null; if (isset($options['max_file_size'])) { $maxFileSize = $options['max_file_size']; unset($options['max_file_size']); } $ctx = StreamContextFactory::getContext($fileUrl, $options, ['notification' => Closure::fromCallable([$this, 'callbackGet'])]); $proxy = ProxyManager::getInstance()->getProxyForRequest($fileUrl); $usingProxy = $proxy->getStatus(' using proxy (%s)'); $this->io->writeError((strpos($origFileUrl, 'http') === 0 ? 'Downloading ' : 'Reading ') . Url::sanitize($origFileUrl) . $usingProxy, true, IOInterface::DEBUG); unset($origFileUrl, $proxy, $usingProxy); if ((!Preg::isMatch('{^http://(repo\.)?packagist\.org/p/}', $fileUrl) || (false === strpos($fileUrl, '$') && false === strpos($fileUrl, '%24'))) && empty($degradedPackagist)) { $this->config->prohibitUrlByConfig($fileUrl, $this->io); } if ($this->progress && !$isRedirect) { $this->io->writeError("Downloading (connecting...)", false); } $errorMessage = ''; $errorCode = 0; $result = false; set_error_handler(static function ($code, $msg) use (&$errorMessage): bool { if ($errorMessage) { $errorMessage .= "\n"; } $errorMessage .= Preg::replace('{^file_get_contents\(.*?\): }', '', $msg); return true; }); $http_response_header = []; try { $result = $this->getRemoteContents($originUrl, $fileUrl, $ctx, $http_response_header, $maxFileSize); if (!empty($http_response_header[0])) { $statusCode = self::findStatusCode($http_response_header); if ($statusCode >= 300 && Response::findHeaderValue($http_response_header, 'content-type') === 'application/json') { HttpDownloader::outputWarnings($this->io, $originUrl, json_decode($result, true)); } if (in_array($statusCode, [401, 403]) && $retryAuthFailure) { $this->promptAuthAndRetry($statusCode, $this->findStatusMessage($http_response_header), $http_response_header); } } $contentLength = !empty($http_response_header[0]) ? Response::findHeaderValue($http_response_header, 'content-length') : null; if ($contentLength && Platform::strlen($result) < $contentLength) { $e = new TransportException('Content-Length mismatch, received '.Platform::strlen($result).' bytes out of the expected '.$contentLength); $e->setHeaders($http_response_header); $e->setStatusCode(self::findStatusCode($http_response_header)); try { $e->setResponse($this->decodeResult($result, $http_response_header)); } catch (\Exception $discarded) { $e->setResponse($this->normalizeResult($result)); } $this->io->writeError('Content-Length mismatch, received '.Platform::strlen($result).' out of '.$contentLength.' bytes: (' . base64_encode($result).')', true, IOInterface::DEBUG); throw $e; } } catch (\Exception $e) { if ($e instanceof TransportException && !empty($http_response_header[0])) { $e->setHeaders($http_response_header); $e->setStatusCode(self::findStatusCode($http_response_header)); } if ($e instanceof TransportException && $result !== false) { $e->setResponse($this->decodeResult($result, $http_response_header)); } $result = false; } if ($errorMessage && !filter_var(ini_get('allow_url_fopen'), FILTER_VALIDATE_BOOLEAN)) { $errorMessage = 'allow_url_fopen must be enabled in php.ini ('.$errorMessage.')'; } restore_error_handler(); if (isset($e) && !$this->retry) { if (!$this->degradedMode && false !== strpos($e->getMessage(), 'Operation timed out')) { $this->degradedMode = true; $this->io->writeError(''); $this->io->writeError([ ''.$e->getMessage().'', 'Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info', ]); return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress); } throw $e; } $statusCode = null; $contentType = null; $locationHeader = null; if (!empty($http_response_header[0])) { $statusCode = self::findStatusCode($http_response_header); $contentType = Response::findHeaderValue($http_response_header, 'content-type'); $locationHeader = Response::findHeaderValue($http_response_header, 'location'); } if ($originUrl === 'bitbucket.org' && !$this->authHelper->isPublicBitBucketDownload($fileUrl) && substr($fileUrl, -4) === '.zip' && (!$locationHeader || substr(parse_url($locationHeader, PHP_URL_PATH), -4) !== '.zip') && $contentType && Preg::isMatch('{^text/html\b}i', $contentType) ) { $result = false; if ($retryAuthFailure) { $this->promptAuthAndRetry(401); } } if ($statusCode === 404 && in_array($originUrl, $this->config->get('gitlab-domains'), true) && false !== strpos($fileUrl, 'archive.zip') ) { $result = false; if ($retryAuthFailure) { $this->promptAuthAndRetry(401); } } $hasFollowedRedirect = false; if ($statusCode >= 300 && $statusCode <= 399 && $statusCode !== 304 && $this->redirects < $this->maxRedirects) { $hasFollowedRedirect = true; $result = $this->handleRedirect($http_response_header, $additionalOptions, $result); } if ($statusCode && $statusCode >= 400 && $statusCode <= 599) { if (!$this->retry) { if ($this->progress && !$isRedirect) { $this->io->overwriteError("Downloading (failed)", false); } $e = new TransportException('The "'.$this->fileUrl.'" file could not be downloaded ('.$http_response_header[0].')', $statusCode); $e->setHeaders($http_response_header); $e->setResponse($this->decodeResult($result, $http_response_header)); $e->setStatusCode($statusCode); throw $e; } $result = false; } if ($this->progress && !$this->retry && !$isRedirect) { $this->io->overwriteError("Downloading (".($result === false ? 'failed' : '100%').")", false); } if ($result && extension_loaded('zlib') && strpos($fileUrl, 'http') === 0 && !$hasFollowedRedirect) { try { $result = $this->decodeResult($result, $http_response_header); } catch (\Exception $e) { if ($this->degradedMode) { throw $e; } $this->degradedMode = true; $this->io->writeError([ '', 'Failed to decode response: '.$e->getMessage().'', 'Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info', ]); return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress); } } if (false !== $result && null !== $fileName && !$isRedirect) { if ('' === $result) { throw new TransportException('"'.$this->fileUrl.'" appears broken, and returned an empty 200 response'); } $errorMessage = ''; set_error_handler(static function ($code, $msg) use (&$errorMessage): bool { if ($errorMessage) { $errorMessage .= "\n"; } $errorMessage .= Preg::replace('{^file_put_contents\(.*?\): }', '', $msg); return true; }); $result = (bool) file_put_contents($fileName, $result); restore_error_handler(); if (false === $result) { throw new TransportException('The "'.$this->fileUrl.'" file could not be written to '.$fileName.': '.$errorMessage); } } if ($this->retry) { $this->retry = false; $result = $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress); if ($this->storeAuth) { $this->authHelper->storeAuth($this->originUrl, $this->storeAuth); $this->storeAuth = false; } return $result; } if (false === $result) { $e = new TransportException('The "'.$this->fileUrl.'" file could not be downloaded: '.$errorMessage, $errorCode); if (!empty($http_response_header[0])) { $e->setHeaders($http_response_header); } if (!$this->degradedMode && false !== strpos($e->getMessage(), 'Operation timed out')) { $this->degradedMode = true; $this->io->writeError(''); $this->io->writeError([ ''.$e->getMessage().'', 'Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info', ]); return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress); } throw $e; } if (!empty($http_response_header[0])) { $this->lastHeaders = $http_response_header; } return $result; } protected function getRemoteContents(string $originUrl, string $fileUrl, $context, ?array &$responseHeaders = null, ?int $maxFileSize = null) { $result = false; if (\PHP_VERSION_ID >= 80400) { http_clear_last_response_headers(); } try { $e = null; if ($maxFileSize !== null) { $result = file_get_contents($fileUrl, false, $context, 0, $maxFileSize); } else { $result = file_get_contents($fileUrl, false, $context); } } catch (\Throwable $e) { } if ($result !== false && $maxFileSize !== null && Platform::strlen($result) >= $maxFileSize) { throw new MaxFileSizeExceededException('Maximum allowed download size reached. Downloaded ' . Platform::strlen($result) . ' of allowed ' . $maxFileSize . ' bytes'); } if (\PHP_VERSION_ID >= 80400) { $responseHeaders = http_get_last_response_headers() ?? []; http_clear_last_response_headers(); } else { $responseHeaders = $http_response_header ?? []; } if (null !== $e) { throw $e; } return $result; } protected function callbackGet(int $notificationCode, int $severity, ?string $message, int $messageCode, int $bytesTransferred, int $bytesMax) { switch ($notificationCode) { case STREAM_NOTIFY_FAILURE: if (400 === $messageCode) { throw new TransportException("The '" . $this->fileUrl . "' URL could not be accessed: " . $message, $messageCode); } break; case STREAM_NOTIFY_FILE_SIZE_IS: $this->bytesMax = $bytesMax; break; case STREAM_NOTIFY_PROGRESS: if ($this->bytesMax > 0 && $this->progress) { $progression = min(100, (int) round($bytesTransferred / $this->bytesMax * 100)); if ((0 === $progression % 5) && 100 !== $progression && $progression !== $this->lastProgress) { $this->lastProgress = $progression; $this->io->overwriteError("Downloading ($progression%)", false); } } break; default: break; } } protected function promptAuthAndRetry($httpStatus, ?string $reason = null, array $headers = []) { $result = $this->authHelper->promptAuthIfNeeded($this->fileUrl, $this->originUrl, $httpStatus, $reason, $headers, 1 ); $this->storeAuth = $result['storeAuth']; $this->retry = $result['retry']; if ($this->retry) { throw new TransportException('RETRY'); } } protected function getOptionsForUrl(string $originUrl, array $additionalOptions) { $tlsOptions = []; $headers = []; if (extension_loaded('zlib')) { $headers[] = 'Accept-Encoding: gzip'; } $options = array_replace_recursive($this->options, $tlsOptions, $additionalOptions); if (!$this->degradedMode) { $options['http']['protocol_version'] = 1.1; $headers[] = 'Connection: close'; } if (isset($options['http']['header']) && !is_array($options['http']['header'])) { $options['http']['header'] = explode("\r\n", trim($options['http']['header'], "\r\n")); } $options = $this->authHelper->addAuthenticationOptions($options, $originUrl, $this->fileUrl); $options['http']['follow_location'] = 0; foreach ($headers as $header) { $options['http']['header'][] = $header; } return $options; } private function handleRedirect(array $responseHeaders, array $additionalOptions, $result) { if ($locationHeader = Response::findHeaderValue($responseHeaders, 'location')) { if (parse_url($locationHeader, PHP_URL_SCHEME)) { $targetUrl = $locationHeader; } elseif (parse_url($locationHeader, PHP_URL_HOST)) { $targetUrl = $this->scheme.':'.$locationHeader; } elseif ('/' === $locationHeader[0]) { $urlHost = parse_url($this->fileUrl, PHP_URL_HOST); $targetUrl = Preg::replace('{^(.+(?://|@)'.preg_quote($urlHost).'(?::\d+)?)(?:[/\?].*)?$}', '\1'.$locationHeader, $this->fileUrl); } else { $targetUrl = Preg::replace('{^(.+/)[^/?]*(?:\?.*)?$}', '\1'.$locationHeader, $this->fileUrl); } } if (!empty($targetUrl)) { $this->redirects++; $this->io->writeError('', true, IOInterface::DEBUG); $this->io->writeError(sprintf('Following redirect (%u) %s', $this->redirects, Url::sanitize($targetUrl)), true, IOInterface::DEBUG); $additionalOptions['redirects'] = $this->redirects; return $this->get(parse_url($targetUrl, PHP_URL_HOST), $targetUrl, $additionalOptions, $this->fileName, $this->progress); } if (!$this->retry) { $e = new TransportException('The "'.$this->fileUrl.'" file could not be downloaded, got redirect without Location ('.$responseHeaders[0].')'); $e->setHeaders($responseHeaders); $e->setResponse($this->decodeResult($result, $responseHeaders)); throw $e; } return false; } private function decodeResult($result, array $responseHeaders): ?string { if ($result && extension_loaded('zlib')) { $contentEncoding = Response::findHeaderValue($responseHeaders, 'content-encoding'); $decode = $contentEncoding && 'gzip' === strtolower($contentEncoding); if ($decode) { $result = zlib_decode($result); if ($result === false) { throw new TransportException('Failed to decode zlib stream'); } } } return $this->normalizeResult($result); } private function normalizeResult($result): ?string { if ($result === false) { return null; } return $result; } } [ 'follow_location' => 1, 'max_redirects' => 20, ]]; $options = array_replace_recursive($options, self::initOptions($url, $defaultOptions)); unset($defaultOptions['http']['header']); $options = array_replace_recursive($options, $defaultOptions); if (isset($options['http']['header'])) { $options['http']['header'] = self::fixHttpHeaderField($options['http']['header']); } return stream_context_create($options, $defaultParams); } public static function initOptions(string $url, array $options, bool $forCurl = false): array { if (!isset($options['http']['header'])) { $options['http']['header'] = []; } if (is_string($options['http']['header'])) { $options['http']['header'] = explode("\r\n", $options['http']['header']); } if (!$forCurl) { $proxy = ProxyManager::getInstance()->getProxyForRequest($url); $proxyOptions = $proxy->getContextOptions(); if ($proxyOptions !== null) { $isHttpsRequest = 0 === strpos($url, 'https://'); if ($proxy->isSecure()) { if (!extension_loaded('openssl')) { throw new TransportException('You must enable the openssl extension to use a secure proxy.'); } if ($isHttpsRequest) { throw new TransportException('You must enable the curl extension to make https requests through a secure proxy.'); } } elseif ($isHttpsRequest && !extension_loaded('openssl')) { throw new TransportException('You must enable the openssl extension to make https requests through a proxy.'); } if (isset($proxyOptions['http']['header'])) { $options['http']['header'][] = $proxyOptions['http']['header']; unset($proxyOptions['http']['header']); } $options = array_replace_recursive($options, $proxyOptions); } } if (defined('HHVM_VERSION')) { $phpVersion = 'HHVM ' . HHVM_VERSION; } else { $phpVersion = 'PHP ' . PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION; } if ($forCurl) { $curl = curl_version(); $httpVersion = 'cURL '.$curl['version']; } else { $httpVersion = 'streams'; } if (!isset($options['http']['header']) || false === stripos(implode('', $options['http']['header']), 'user-agent')) { $platformPhpVersion = PlatformRepository::getPlatformPhpVersion(); $options['http']['header'][] = sprintf( 'User-Agent: Composer/%s (%s; %s; %s; %s%s%s)', Composer::getVersion(), function_exists('php_uname') ? php_uname('s') : 'Unknown', function_exists('php_uname') ? php_uname('r') : 'Unknown', $phpVersion, $httpVersion, $platformPhpVersion ? '; Platform-PHP '.$platformPhpVersion : '', Platform::getEnv('CI') ? '; CI' : '' ); } return $options; } public static function getTlsDefaults(array $options, ?LoggerInterface $logger = null): array { $ciphers = implode(':', [ 'ECDHE-RSA-AES128-GCM-SHA256', 'ECDHE-ECDSA-AES128-GCM-SHA256', 'ECDHE-RSA-AES256-GCM-SHA384', 'ECDHE-ECDSA-AES256-GCM-SHA384', 'DHE-RSA-AES128-GCM-SHA256', 'DHE-DSS-AES128-GCM-SHA256', 'kEDH+AESGCM', 'ECDHE-RSA-AES128-SHA256', 'ECDHE-ECDSA-AES128-SHA256', 'ECDHE-RSA-AES128-SHA', 'ECDHE-ECDSA-AES128-SHA', 'ECDHE-RSA-AES256-SHA384', 'ECDHE-ECDSA-AES256-SHA384', 'ECDHE-RSA-AES256-SHA', 'ECDHE-ECDSA-AES256-SHA', 'DHE-RSA-AES128-SHA256', 'DHE-RSA-AES128-SHA', 'DHE-DSS-AES128-SHA256', 'DHE-RSA-AES256-SHA256', 'DHE-DSS-AES256-SHA', 'DHE-RSA-AES256-SHA', 'AES128-GCM-SHA256', 'AES256-GCM-SHA384', 'AES128-SHA256', 'AES256-SHA256', 'AES128-SHA', 'AES256-SHA', 'AES', 'CAMELLIA', '!aNULL', '!eNULL', '!EXPORT', '!DES', '!3DES', '!RC4', '!MD5', '!PSK', '!aECDH', '!EDH-DSS-DES-CBC3-SHA', '!EDH-RSA-DES-CBC3-SHA', '!KRB5-DES-CBC3-SHA', ]); $defaults = [ 'ssl' => [ 'ciphers' => $ciphers, 'verify_peer' => true, 'verify_depth' => 7, 'SNI_enabled' => true, 'capture_peer_cert' => true, ], ]; if (isset($options['ssl'])) { $defaults['ssl'] = array_replace_recursive($defaults['ssl'], $options['ssl']); } if (!isset($defaults['ssl']['cafile']) && !isset($defaults['ssl']['capath'])) { $result = CaBundle::getSystemCaRootBundlePath($logger); if (is_dir($result)) { $defaults['ssl']['capath'] = $result; } else { $defaults['ssl']['cafile'] = $result; } } if (isset($defaults['ssl']['cafile']) && (!Filesystem::isReadable($defaults['ssl']['cafile']) || !CaBundle::validateCaFile($defaults['ssl']['cafile'], $logger))) { throw new TransportException('The configured cafile was not valid or could not be read.'); } if (isset($defaults['ssl']['capath']) && (!is_dir($defaults['ssl']['capath']) || !Filesystem::isReadable($defaults['ssl']['capath']))) { throw new TransportException('The configured capath was not valid or could not be read.'); } $defaults['ssl']['disable_compression'] = true; return $defaults; } private static function fixHttpHeaderField($header): array { if (!is_array($header)) { $header = explode("\r\n", $header); } uasort($header, static function ($el): int { return stripos($el, 'content-type') === 0 ? 1 : -1; }); return $header; } } url = $url; $this->io = $io; $this->config = $config; $this->process = $process ?: new ProcessExecutor($io); } public static function cleanEnv(): void { Platform::clearEnv('DYLD_LIBRARY_PATH'); } public function execute(array $command, string $url, ?string $cwd = null, ?string $path = null, bool $verbose = false): string { $this->config->prohibitUrlByConfig($url, $this->io); return $this->executeWithAuthRetry($command, $cwd, $url, $path, $verbose); } public function executeLocal(array $command, string $path, ?string $cwd = null, bool $verbose = false): string { return $this->executeWithAuthRetry($command, $cwd, '', $path, $verbose); } private function executeWithAuthRetry(array $svnCommand, ?string $cwd, string $url, ?string $path, bool $verbose): ?string { $command = $this->getCommand($svnCommand, $url, $path); $output = null; $io = $this->io; $handler = static function ($type, $buffer) use (&$output, $io, $verbose) { if ($type !== 'out') { return null; } if (strpos($buffer, 'Redirecting to URL ') === 0) { return null; } $output .= $buffer; if ($verbose) { $io->writeError($buffer, false); } }; $status = $this->process->execute($command, $handler, $cwd); if (0 === $status) { return $output; } $errorOutput = $this->process->getErrorOutput(); $fullOutput = trim(implode("\n", [$output, $errorOutput])); if (false === stripos($fullOutput, 'Could not authenticate to server:') && false === stripos($fullOutput, 'authorization failed') && false === stripos($fullOutput, 'svn: E170001:') && false === stripos($fullOutput, 'svn: E215004:')) { throw new \RuntimeException($fullOutput); } if (!$this->hasAuth()) { $this->doAuthDance(); } if ($this->qtyAuthTries++ < self::MAX_QTY_AUTH_TRIES) { return $this->executeWithAuthRetry($svnCommand, $cwd, $url, $path, $verbose); } throw new \RuntimeException( 'wrong credentials provided ('.$fullOutput.')' ); } public function setCacheCredentials(bool $cacheCredentials): void { $this->cacheCredentials = $cacheCredentials; } protected function doAuthDance(): Svn { if (!$this->io->isInteractive()) { throw new \RuntimeException( 'can not ask for authentication in non interactive mode' ); } $this->io->writeError("The Subversion server (" . Url::sanitize($this->url) . ") requested credentials:"); $this->hasAuth = true; $this->credentials = [ 'username' => (string) $this->io->ask("Username: ", ''), 'password' => (string) $this->io->askAndHideAnswer("Password: "), ]; $this->cacheCredentials = $this->io->askConfirmation("Should Subversion cache these credentials? (yes/no) "); return $this; } protected function getCommand(array $cmd, string $url, ?string $path = null): array { $cmd = array_merge( $cmd, ['--non-interactive'], $this->getCredentialArgs(), ['--', $url] ); if ($path !== null) { $cmd[] = $path; } return $cmd; } protected function getCredentialArgs(): array { if (!$this->hasAuth()) { return []; } return array_merge( $this->getAuthCacheArgs(), ['--username', $this->getUsername(), '--password', $this->getPassword()] ); } protected function getPassword(): string { if ($this->credentials === null) { throw new \LogicException("No svn auth detected."); } return $this->credentials['password']; } protected function getUsername(): string { if ($this->credentials === null) { throw new \LogicException("No svn auth detected."); } return $this->credentials['username']; } protected function hasAuth(): bool { if (null !== $this->hasAuth) { return $this->hasAuth; } if (false === $this->createAuthFromConfig()) { $this->createAuthFromUrl(); } return (bool) $this->hasAuth; } protected function getAuthCacheArgs(): array { return $this->cacheCredentials ? [] : ['--no-auth-cache']; } private function createAuthFromConfig(): bool { if (!$this->config->has('http-basic')) { return $this->hasAuth = false; } $authConfig = $this->config->get('http-basic'); $host = parse_url($this->url, PHP_URL_HOST); if (isset($authConfig[$host])) { $this->credentials = [ 'username' => $authConfig[$host]['username'], 'password' => $authConfig[$host]['password'], ]; return $this->hasAuth = true; } return $this->hasAuth = false; } private function createAuthFromUrl(): bool { $uri = parse_url($this->url); if (empty($uri['user'])) { return $this->hasAuth = false; } $this->credentials = [ 'username' => $uri['user'], 'password' => !empty($uri['pass']) ? $uri['pass'] : '', ]; return $this->hasAuth = true; } public function binaryVersion(): ?string { if (!self::$version) { if (0 === $this->process->execute(['svn', '--version'], $output)) { if (Preg::isMatch('{(\d+(?:\.\d+)+)}', $output, $match)) { self::$version = $match[1]; } } } return self::$version; } } download($package, $path, $prevPackage)); self::await($loop, $downloader->prepare($type, $package, $path, $prevPackage)); if ($type === 'update' && $prevPackage !== null) { self::await($loop, $downloader->update($package, $prevPackage, $path)); } else { self::await($loop, $downloader->install($package, $path)); } } catch (\Exception $e) { self::await($loop, $downloader->cleanup($type, $package, $path, $prevPackage)); throw $e; } self::await($loop, $downloader->cleanup($type, $package, $path, $prevPackage)); } public static function await(Loop $loop, ?PromiseInterface $promise = null): void { if ($promise !== null) { $loop->wait([$promise]); } } } valid()) { return null; } return self::extractComposerJsonFromFolder($phar); } private static function extractComposerJsonFromFolder(\PharData $phar): string { if (isset($phar['composer.json'])) { return $phar['composer.json']->getContent(); } $topLevelPaths = []; foreach ($phar as $folderFile) { $name = $folderFile->getBasename(); if ($folderFile->isDir()) { $topLevelPaths[$name] = true; if (\count($topLevelPaths) > 1) { throw new \RuntimeException('Archive has more than one top level directories, and no composer.json was found on the top level, so it\'s an invalid archive. Top level paths found were: '.implode(',', array_keys($topLevelPaths))); } } } $composerJsonPath = key($topLevelPaths).'/composer.json'; if (\count($topLevelPaths) > 0 && isset($phar[$composerJsonPath])) { return $phar[$composerJsonPath]->getContent(); } throw new \RuntimeException('No composer.json found either at the top level or within the topmost directory'); } } $commonName, 'san' => $subjectAltNames, ]; } public static function getCertificateFingerprint(string $certificate): string { $pubkey = openssl_get_publickey($certificate); if ($pubkey === false) { throw new \RuntimeException('Failed to retrieve the public key from certificate'); } $pubkeydetails = openssl_pkey_get_details($pubkey); $pubkeypem = $pubkeydetails['key']; $start = '-----BEGIN PUBLIC KEY-----'; $end = '-----END PUBLIC KEY-----'; $pemtrim = substr($pubkeypem, strpos($pubkeypem, $start) + strlen($start), (strlen($pubkeypem) - strpos($pubkeypem, $end)) * (-1)); $der = base64_decode($pemtrim); return hash('sha1', $der); } public static function isOpensslParseSafe(): bool { return CaBundle::isOpensslParseSafe(); } private static function certNameMatcher(string $certName): ?callable { $wildcards = substr_count($certName, '*'); if (0 === $wildcards) { return static function ($hostname) use ($certName): bool { return $hostname === $certName; }; } if (1 === $wildcards) { $components = explode('.', $certName); if (3 > count($components)) { return null; } $firstComponent = $components[0]; if ('*' !== $firstComponent[strlen($firstComponent) - 1]) { return null; } $wildcardRegex = preg_quote($certName); $wildcardRegex = str_replace('\\*', '[a-z0-9-]+', $wildcardRegex); $wildcardRegex = "{^{$wildcardRegex}$}"; return static function ($hostname) use ($wildcardRegex): bool { return Preg::isMatch($wildcardRegex, $hostname); }; } return null; } } get('github-domains'), true)) { $url = Preg::replace('{(/repos/[^/]+/[^/]+/(zip|tar)ball)(?:/.+)?$}i', '$1/'.$ref, $url); } elseif (in_array($host, $config->get('gitlab-domains'), true)) { $url = Preg::replace('{(/api/v[34]/projects/[^/]+/repository/archive\.(?:zip|tar\.gz|tar\.bz2|tar)\?sha=).+$}i', '${1}'.$ref, $url); } assert($url !== ''); return $url; } public static function getOrigin(Config $config, string $url): string { if (0 === strpos($url, 'file://')) { return $url; } $origin = (string) parse_url($url, PHP_URL_HOST); if ($port = parse_url($url, PHP_URL_PORT)) { $origin .= ':'.$port; } if (str_ends_with($origin, '.github.com') && $origin !== 'codeload.github.com') { return 'github.com'; } if ($origin === 'repo.packagist.org') { return 'packagist.org'; } if ($origin === '') { $origin = $url; } if ( false === strpos($origin, '/') && !in_array($origin, $config->get('gitlab-domains'), true) ) { foreach ($config->get('gitlab-domains') as $gitlabDomain) { if ($gitlabDomain !== '' && str_starts_with($gitlabDomain, $origin)) { return $gitlabDomain; } } } return $origin; } public static function sanitize(string $url): string { $url = Preg::replace('{([&?]access_token=)[^&]+}', '$1***', $url); $url = Preg::replaceCallback('{^(?P[a-z0-9]+://)?(?P[^:/\s@]+):(?P[^@\s/]+)@}i', static function ($m): string { if (Preg::isMatch(GitHub::GITHUB_TOKEN_REGEX, $m['user'])) { return $m['prefix'].'***:***@'; } if (strlen($m['user']) >= 12) { return $m['prefix'].substr($m['user'], 0, 8).'***:***@'; } return $m['prefix'].$m['user'].':***@'; }, $url); return $url; } } open($pathToZip) !== true) { return null; } if (0 === $zip->numFiles) { $zip->close(); return null; } $foundFileIndex = self::locateFile($zip, 'composer.json'); $content = null; $configurationFileName = $zip->getNameIndex($foundFileIndex); $stream = $zip->getStream($configurationFileName); if (false !== $stream) { $content = stream_get_contents($stream); } $zip->close(); return $content; } private static function locateFile(\ZipArchive $zip, string $filename): int { if (false !== ($index = $zip->locateName($filename)) && $zip->getFromIndex($index) !== false) { return $index; } $topLevelPaths = []; for ($i = 0; $i < $zip->numFiles; $i++) { $name = $zip->getNameIndex($i); $dirname = dirname($name); if (strpos($name, '__MACOSX') !== false) { continue; } if ($dirname === '.') { $topLevelPaths[$name] = true; if (\count($topLevelPaths) > 1) { throw new \RuntimeException('Archive has more than one top level directories, and no composer.json was found on the top level, so it\'s an invalid archive. Top level paths found were: '.implode(',', array_keys($topLevelPaths))); } continue; } if (false === strpos($dirname, '\\') && false === strpos($dirname, '/')) { $topLevelPaths[$dirname.'/'] = true; if (\count($topLevelPaths) > 1) { throw new \RuntimeException('Archive has more than one top level directories, and no composer.json was found on the top level, so it\'s an invalid archive. Top level paths found were: '.implode(',', array_keys($topLevelPaths))); } } } if ($topLevelPaths && false !== ($index = $zip->locateName(key($topLevelPaths).$filename)) && $zip->getFromIndex($index) !== false) { return $index; } throw new \RuntimeException('No composer.json found either at the top level or within the topmost directory'); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Autoload; /** * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. * * $loader = new \Composer\Autoload\ClassLoader(); * * // register classes with namespaces * $loader->add('Symfony\Component', __DIR__.'/component'); * $loader->add('Symfony', __DIR__.'/framework'); * * // activate the autoloader * $loader->register(); * * // to enable searching the include path (eg. for PEAR packages) * $loader->setUseIncludePath(true); * * In this example, if you try to use a class in the Symfony\Component * namespace or one of its children (Symfony\Component\Console for instance), * the autoloader will first look for the class under the component/ * directory, and it will then fallback to the framework/ directory if not * found before giving up. * * This class is loosely based on the Symfony UniversalClassLoader. * * @author Fabien Potencier * @author Jordi Boggiano * @see https://www.php-fig.org/psr/psr-0/ * @see https://www.php-fig.org/psr/psr-4/ */ class ClassLoader { /** @var \Closure(string):void */ private static $includeFile; /** @var string|null */ private $vendorDir; // PSR-4 /** * @var array> */ private $prefixLengthsPsr4 = array(); /** * @var array> */ private $prefixDirsPsr4 = array(); /** * @var list */ private $fallbackDirsPsr4 = array(); // PSR-0 /** * List of PSR-0 prefixes * * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2'))) * * @var array>> */ private $prefixesPsr0 = array(); /** * @var list */ private $fallbackDirsPsr0 = array(); /** @var bool */ private $useIncludePath = false; /** * @var array */ private $classMap = array(); /** @var bool */ private $classMapAuthoritative = false; /** * @var array */ private $missingClasses = array(); /** @var string|null */ private $apcuPrefix; /** * @var array */ private static $registeredLoaders = array(); /** * @param string|null $vendorDir */ public function __construct($vendorDir = null) { $this->vendorDir = $vendorDir; self::initializeIncludeClosure(); } /** * @return array> */ public function getPrefixes() { if (!empty($this->prefixesPsr0)) { return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); } return array(); } /** * @return array> */ public function getPrefixesPsr4() { return $this->prefixDirsPsr4; } /** * @return list */ public function getFallbackDirs() { return $this->fallbackDirsPsr0; } /** * @return list */ public function getFallbackDirsPsr4() { return $this->fallbackDirsPsr4; } /** * @return array Array of classname => path */ public function getClassMap() { return $this->classMap; } /** * @param array $classMap Class to filename map * * @return void */ public function addClassMap(array $classMap) { if ($this->classMap) { $this->classMap = array_merge($this->classMap, $classMap); } else { $this->classMap = $classMap; } } /** * Registers a set of PSR-0 directories for a given prefix, either * appending or prepending to the ones previously set for this prefix. * * @param string $prefix The prefix * @param list|string $paths The PSR-0 root directories * @param bool $prepend Whether to prepend the directories * * @return void */ public function add($prefix, $paths, $prepend = false) { $paths = (array) $paths; if (!$prefix) { if ($prepend) { $this->fallbackDirsPsr0 = array_merge( $paths, $this->fallbackDirsPsr0 ); } else { $this->fallbackDirsPsr0 = array_merge( $this->fallbackDirsPsr0, $paths ); } return; } $first = $prefix[0]; if (!isset($this->prefixesPsr0[$first][$prefix])) { $this->prefixesPsr0[$first][$prefix] = $paths; return; } if ($prepend) { $this->prefixesPsr0[$first][$prefix] = array_merge( $paths, $this->prefixesPsr0[$first][$prefix] ); } else { $this->prefixesPsr0[$first][$prefix] = array_merge( $this->prefixesPsr0[$first][$prefix], $paths ); } } /** * Registers a set of PSR-4 directories for a given namespace, either * appending or prepending to the ones previously set for this namespace. * * @param string $prefix The prefix/namespace, with trailing '\\' * @param list|string $paths The PSR-4 base directories * @param bool $prepend Whether to prepend the directories * * @throws \InvalidArgumentException * * @return void */ public function addPsr4($prefix, $paths, $prepend = false) { $paths = (array) $paths; if (!$prefix) { // Register directories for the root namespace. if ($prepend) { $this->fallbackDirsPsr4 = array_merge( $paths, $this->fallbackDirsPsr4 ); } else { $this->fallbackDirsPsr4 = array_merge( $this->fallbackDirsPsr4, $paths ); } } elseif (!isset($this->prefixDirsPsr4[$prefix])) { // Register directories for a new namespace. $length = strlen($prefix); if ('\\' !== $prefix[$length - 1]) { throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); } $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; $this->prefixDirsPsr4[$prefix] = $paths; } elseif ($prepend) { // Prepend directories for an already registered namespace. $this->prefixDirsPsr4[$prefix] = array_merge( $paths, $this->prefixDirsPsr4[$prefix] ); } else { // Append directories for an already registered namespace. $this->prefixDirsPsr4[$prefix] = array_merge( $this->prefixDirsPsr4[$prefix], $paths ); } } /** * Registers a set of PSR-0 directories for a given prefix, * replacing any others previously set for this prefix. * * @param string $prefix The prefix * @param list|string $paths The PSR-0 base directories * * @return void */ public function set($prefix, $paths) { if (!$prefix) { $this->fallbackDirsPsr0 = (array) $paths; } else { $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; } } /** * Registers a set of PSR-4 directories for a given namespace, * replacing any others previously set for this namespace. * * @param string $prefix The prefix/namespace, with trailing '\\' * @param list|string $paths The PSR-4 base directories * * @throws \InvalidArgumentException * * @return void */ public function setPsr4($prefix, $paths) { if (!$prefix) { $this->fallbackDirsPsr4 = (array) $paths; } else { $length = strlen($prefix); if ('\\' !== $prefix[$length - 1]) { throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); } $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; $this->prefixDirsPsr4[$prefix] = (array) $paths; } } /** * Turns on searching the include path for class files. * * @param bool $useIncludePath * * @return void */ public function setUseIncludePath($useIncludePath) { $this->useIncludePath = $useIncludePath; } /** * Can be used to check if the autoloader uses the include path to check * for classes. * * @return bool */ public function getUseIncludePath() { return $this->useIncludePath; } /** * Turns off searching the prefix and fallback directories for classes * that have not been registered with the class map. * * @param bool $classMapAuthoritative * * @return void */ public function setClassMapAuthoritative($classMapAuthoritative) { $this->classMapAuthoritative = $classMapAuthoritative; } /** * Should class lookup fail if not found in the current class map? * * @return bool */ public function isClassMapAuthoritative() { return $this->classMapAuthoritative; } /** * APCu prefix to use to cache found/not-found classes, if the extension is enabled. * * @param string|null $apcuPrefix * * @return void */ public function setApcuPrefix($apcuPrefix) { $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; } /** * The APCu prefix in use, or null if APCu caching is not enabled. * * @return string|null */ public function getApcuPrefix() { return $this->apcuPrefix; } /** * Registers this instance as an autoloader. * * @param bool $prepend Whether to prepend the autoloader or not * * @return void */ public function register($prepend = false) { spl_autoload_register(array($this, 'loadClass'), true, $prepend); if (null === $this->vendorDir) { return; } if ($prepend) { self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; } else { unset(self::$registeredLoaders[$this->vendorDir]); self::$registeredLoaders[$this->vendorDir] = $this; } } /** * Unregisters this instance as an autoloader. * * @return void */ public function unregister() { spl_autoload_unregister(array($this, 'loadClass')); if (null !== $this->vendorDir) { unset(self::$registeredLoaders[$this->vendorDir]); } } /** * Loads the given class or interface. * * @param string $class The name of the class * @return true|null True if loaded, null otherwise */ public function loadClass($class) { if ($file = $this->findFile($class)) { $includeFile = self::$includeFile; $includeFile($file); return true; } return null; } /** * Finds the path to the file where the class is defined. * * @param string $class The name of the class * * @return string|false The path if found, false otherwise */ public function findFile($class) { // class map lookup if (isset($this->classMap[$class])) { return $this->classMap[$class]; } if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { return false; } if (null !== $this->apcuPrefix) { $file = apcu_fetch($this->apcuPrefix.$class, $hit); if ($hit) { return $file; } } $file = $this->findFileWithExtension($class, '.php'); // Search for Hack files if we are running on HHVM if (false === $file && defined('HHVM_VERSION')) { $file = $this->findFileWithExtension($class, '.hh'); } if (null !== $this->apcuPrefix) { apcu_add($this->apcuPrefix.$class, $file); } if (false === $file) { // Remember that this class does not exist. $this->missingClasses[$class] = true; } return $file; } /** * Returns the currently registered loaders keyed by their corresponding vendor directories. * * @return array */ public static function getRegisteredLoaders() { return self::$registeredLoaders; } /** * @param string $class * @param string $ext * @return string|false */ private function findFileWithExtension($class, $ext) { // PSR-4 lookup $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; $first = $class[0]; if (isset($this->prefixLengthsPsr4[$first])) { $subPath = $class; while (false !== $lastPos = strrpos($subPath, '\\')) { $subPath = substr($subPath, 0, $lastPos); $search = $subPath . '\\'; if (isset($this->prefixDirsPsr4[$search])) { $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); foreach ($this->prefixDirsPsr4[$search] as $dir) { if (file_exists($file = $dir . $pathEnd)) { return $file; } } } } } // PSR-4 fallback dirs foreach ($this->fallbackDirsPsr4 as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { return $file; } } // PSR-0 lookup if (false !== $pos = strrpos($class, '\\')) { // namespaced class name $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); } else { // PEAR-like class name $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; } if (isset($this->prefixesPsr0[$first])) { foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { if (0 === strpos($class, $prefix)) { foreach ($dirs as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { return $file; } } } } } // PSR-0 fallback dirs foreach ($this->fallbackDirsPsr0 as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { return $file; } } // PSR-0 include paths. if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { return $file; } return false; } /** * @return void */ private static function initializeIncludeClosure() { if (self::$includeFile !== null) { return; } /** * Scope isolated include. * * Prevents access to $this/self from included files. * * @param string $file * @return void */ self::$includeFile = \Closure::bind(static function($file) { include $file; }, null, null); } } * Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer; use Composer\Autoload\ClassLoader; use Composer\Semver\VersionParser; /** * This class is copied in every Composer installed project and available to all * * See also https://getcomposer.org/doc/07-runtime.md#installed-versions * * To require its presence, you can require `composer-runtime-api ^2.0` * * @final */ class InstalledVersions { /** * @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to * @internal */ private static $selfDir = null; /** * @var mixed[]|null * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null */ private static $installed; /** * @var bool */ private static $installedIsLocalDir; /** * @var bool|null */ private static $canGetVendors; /** * @var array[] * @psalm-var array}> */ private static $installedByVendor = array(); /** * Returns a list of all package names which are present, either by being installed, replaced or provided * * @return string[] * @psalm-return list */ public static function getInstalledPackages() { $packages = array(); foreach (self::getInstalled() as $installed) { $packages[] = array_keys($installed['versions']); } if (1 === \count($packages)) { return $packages[0]; } return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); } /** * Returns a list of all package names with a specific type e.g. 'library' * * @param string $type * @return string[] * @psalm-return list */ public static function getInstalledPackagesByType($type) { $packagesByType = array(); foreach (self::getInstalled() as $installed) { foreach ($installed['versions'] as $name => $package) { if (isset($package['type']) && $package['type'] === $type) { $packagesByType[] = $name; } } } return $packagesByType; } /** * Checks whether the given package is installed * * This also returns true if the package name is provided or replaced by another package * * @param string $packageName * @param bool $includeDevRequirements * @return bool */ public static function isInstalled($packageName, $includeDevRequirements = true) { foreach (self::getInstalled() as $installed) { if (isset($installed['versions'][$packageName])) { return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; } } return false; } /** * Checks whether the given package satisfies a version constraint * * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: * * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') * * @param VersionParser $parser Install composer/semver to have access to this class and functionality * @param string $packageName * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package * @return bool */ public static function satisfies(VersionParser $parser, $packageName, $constraint) { $constraint = $parser->parseConstraints((string) $constraint); $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); return $provided->matches($constraint); } /** * Returns a version constraint representing all the range(s) which are installed for a given package * * It is easier to use this via isInstalled() with the $constraint argument if you need to check * whether a given version of a package is installed, and not just whether it exists * * @param string $packageName * @return string Version constraint usable with composer/semver */ public static function getVersionRanges($packageName) { foreach (self::getInstalled() as $installed) { if (!isset($installed['versions'][$packageName])) { continue; } $ranges = array(); if (isset($installed['versions'][$packageName]['pretty_version'])) { $ranges[] = $installed['versions'][$packageName]['pretty_version']; } if (array_key_exists('aliases', $installed['versions'][$packageName])) { $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); } if (array_key_exists('replaced', $installed['versions'][$packageName])) { $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); } if (array_key_exists('provided', $installed['versions'][$packageName])) { $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); } return implode(' || ', $ranges); } throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); } /** * @param string $packageName * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present */ public static function getVersion($packageName) { foreach (self::getInstalled() as $installed) { if (!isset($installed['versions'][$packageName])) { continue; } if (!isset($installed['versions'][$packageName]['version'])) { return null; } return $installed['versions'][$packageName]['version']; } throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); } /** * @param string $packageName * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present */ public static function getPrettyVersion($packageName) { foreach (self::getInstalled() as $installed) { if (!isset($installed['versions'][$packageName])) { continue; } if (!isset($installed['versions'][$packageName]['pretty_version'])) { return null; } return $installed['versions'][$packageName]['pretty_version']; } throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); } /** * @param string $packageName * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference */ public static function getReference($packageName) { foreach (self::getInstalled() as $installed) { if (!isset($installed['versions'][$packageName])) { continue; } if (!isset($installed['versions'][$packageName]['reference'])) { return null; } return $installed['versions'][$packageName]['reference']; } throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); } /** * @param string $packageName * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. */ public static function getInstallPath($packageName) { foreach (self::getInstalled() as $installed) { if (!isset($installed['versions'][$packageName])) { continue; } return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; } throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); } /** * @return array * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} */ public static function getRootPackage() { $installed = self::getInstalled(); return $installed[0]['root']; } /** * Returns the raw installed.php data for custom implementations * * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. * @return array[] * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} */ public static function getRawData() { @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); if (null === self::$installed) { // only require the installed.php file if this file is loaded from its dumped location, // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 if (substr(__DIR__, -8, 1) !== 'C') { self::$installed = include __DIR__ . '/installed.php'; } else { self::$installed = array(); } } return self::$installed; } /** * Returns the raw data of all installed.php which are currently loaded for custom implementations * * @return array[] * @psalm-return list}> */ public static function getAllRawData() { return self::getInstalled(); } /** * Lets you reload the static array from another file * * This is only useful for complex integrations in which a project needs to use * this class but then also needs to execute another project's autoloader in process, * and wants to ensure both projects have access to their version of installed.php. * * A typical case would be PHPUnit, where it would need to make sure it reads all * the data it needs from this class, then call reload() with * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure * the project in which it runs can then also use this class safely, without * interference between PHPUnit's dependencies and the project's dependencies. * * @param array[] $data A vendor/composer/installed.php data set * @return void * * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data */ public static function reload($data) { self::$installed = $data; self::$installedByVendor = array(); // when using reload, we disable the duplicate protection to ensure that self::$installed data is // always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not, // so we have to assume it does not, and that may result in duplicate data being returned when listing // all installed packages for example self::$installedIsLocalDir = false; } /** * @return string */ private static function getSelfDir() { if (self::$selfDir === null) { self::$selfDir = strtr(__DIR__, '\\', '/'); } return self::$selfDir; } /** * @return array[] * @psalm-return list}> */ private static function getInstalled() { if (null === self::$canGetVendors) { self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); } $installed = array(); $copiedLocalDir = false; if (self::$canGetVendors) { $selfDir = self::getSelfDir(); foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { $vendorDir = strtr($vendorDir, '\\', '/'); if (isset(self::$installedByVendor[$vendorDir])) { $installed[] = self::$installedByVendor[$vendorDir]; } elseif (is_file($vendorDir.'/composer/installed.php')) { /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ $required = require $vendorDir.'/composer/installed.php'; self::$installedByVendor[$vendorDir] = $required; $installed[] = $required; if (self::$installed === null && $vendorDir.'/composer' === $selfDir) { self::$installed = $required; self::$installedIsLocalDir = true; } } if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) { $copiedLocalDir = true; } } } if (null === self::$installed) { // only require the installed.php file if this file is loaded from its dumped location, // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 if (substr(__DIR__, -8, 1) !== 'C') { /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ $required = require __DIR__ . '/installed.php'; self::$installed = $required; } else { self::$installed = array(); } } if (self::$installed !== array() && !$copiedLocalDir) { $installed[] = self::$installed; } return $installed; } } { "$schema": "https://json-schema.org/draft-04/schema#", "title": "Composer Lock File", "type": "object", "required": [ "content-hash", "packages", "packages-dev" ], "additionalProperties": true, "properties": { "_readme": { "type": "array", "items": { "type": "string" }, "description": "Informational text for humans reading the file" }, "content-hash": { "type": "string", "description": "Hash of all relevant properties of the composer.json that was used to create this lock file." }, "packages": { "type": "array", "description": "An array of packages that are required.", "items": { "$ref": "./composer-schema.json", "required": ["name", "version"] } }, "packages-dev": { "type": "array", "description": "An array of packages that are required in require-dev.", "items": { "$ref": "./composer-schema.json" } }, "aliases": { "type": "array", "description": "Inline aliases defined in the root package.", "items": { "type": "object", "required": [ "package", "version", "alias", "alias_normalized" ], "properties": { "package": { "type": "string" }, "version": { "type": "string" }, "alias": { "type": "string" }, "alias_normalized": { "type": "string" } } } }, "minimum-stability": { "type": "string", "description": "The minimum-stability used to generate this lock file." }, "stability-flags": { "type": "object", "description": "Root package stability flags changing the minimum-stability for specific packages.", "additionalProperties": { "type": "integer" } }, "prefer-stable": { "type": "boolean", "description": "Whether the --prefer-stable flag was used when building this lock file." }, "prefer-lowest": { "type": "boolean", "description": "Whether the --prefer-lowest flag was used when building this lock file." }, "platform": { "type": "object", "description": "Platform requirements of the root package.", "additionalProperties": { "type": "string" } }, "platform-dev": { "type": "object", "description": "Platform dev-requirements of the root package.", "additionalProperties": { "type": "string" } }, "platform-overrides": { "type": "object", "description": "Platform config overrides of the root package.", "additionalProperties": { "type": "string" } }, "plugin-api-version": { "type": "string", "description": "The composer-plugin-api version that was used to generate this lock file." } } } { "$schema": "https://json-schema.org/draft-04/schema#", "title": "Composer Package Repository", "type": "object", "oneOf": [ { "required": [ "packages" ] }, { "required": [ "providers" ] }, { "required": [ "provider-includes", "providers-url" ] }, { "required": [ "metadata-url" ] } ], "properties": { "packages": { "type": ["object", "array"], "description": "A hashmap of package names in the form of /.", "additionalProperties": { "$ref": "#/definitions/versions" } }, "metadata-url": { "type": "string", "description": "Endpoint to retrieve package metadata data from, in Composer v2 format, e.g. '/p2/%package%.json'." }, "available-packages": { "type": "array", "items": { "type": "string" }, "description": "If your repository only has a small number of packages, and you want to avoid serving many 404s, specify all the package names that your repository contains here." }, "available-package-patterns": { "type": "array", "items": { "type": "string" }, "description": "If your repository only has a small number of packages, and you want to avoid serving many 404s, specify package name patterns containing wildcards (*) that your repository contains here." }, "security-advisories": { "type": "array", "items": { "type": "object", "required": ["metadata", "api-url"], "properties": { "metadata": { "type": "boolean", "description": "Whether metadata files contain security advisory data or whether it should always be queried using the API URL." }, "api-url": { "type": "string", "description": "Endpoint to call to retrieve security advisories data." } } } }, "filter": { "type": "object", "description": "Filter list configuration advertised by the repository.", "required": ["metadata", "lists"], "properties": { "metadata": { "type": "boolean", "description": "Whether metadata files contain filter list data." }, "lists": { "type": "object", "description": "Names of all filter lists provided by this repository, mapped to an object describing the list. The object shape allows future per-list metadata to be added without another wire-format break.", "additionalProperties": { "type": "object", "required": ["enabled"], "properties": { "enabled": { "type": "boolean", "description": "Indicates if the filter list is available." } } } }, "summary-url": { "type": "string", "description": "Endpoint returning a compact mapping of list name to package name to version constraint, used to skip per-package metadata fetches for packages that cannot match any active list." }, "api-url": { "type": "string", "description": "Endpoint that accepts a POST body with the relevant package PURLs and configured list names and returns the matching filter entries directly. Takes precedence over summary-url when both are advertised." } } }, "metadata-changes-url": { "type": "string", "description": "Endpoint to retrieve package metadata updates from. This should receive a timestamp since last call to be able to return new changes. e.g. '/metadata/changes.json'." }, "providers-api": { "type": "string", "description": "Endpoint to retrieve package names providing a given name from, e.g. '/providers/%package%.json'." }, "notify-batch": { "type": "string", "description": "Endpoint to call after multiple packages have been installed, e.g. '/downloads/'." }, "search": { "type": "string", "description": "Endpoint that provides search capabilities, e.g. '/search.json?q=%query%&type=%type%'." }, "list": { "type": "string", "description": "Endpoint that provides a full list of packages present in the repository. It should accept an optional `?filter=xx` query param, which can contain `*` as wildcards matching any substring. e.g. '/list.json'." }, "warnings": { "type": "array", "items": { "type": "object", "required": ["message", "versions"], "properties": { "message": { "type": "string", "description": "A message that will be output by Composer as a warning when this source is consulted." }, "versions": { "type": "string", "description": "A version constraint to limit to which Composer versions the warning should be shown." } } } }, "infos": { "type": "array", "items": { "type": "object", "required": ["message", "versions"], "properties": { "message": { "type": "string", "description": "A message that will be output by Composer as info when this source is consulted." }, "versions": { "type": "string", "description": "A version constraint to limit to which Composer versions the info should be shown." } } } }, "providers-url": { "type": "string", "description": "DEPRECATED: Endpoint to retrieve provider data from, e.g. '/p/%package%$%hash%.json'." }, "provider-includes": { "type": "object", "description": "DEPRECATED: A hashmap of provider listings.", "additionalProperties": { "$ref": "#/definitions/provider" } }, "providers": { "type": "object", "description": "DEPRECATED: A hashmap of package names in the form of /.", "additionalProperties": { "$ref": "#/definitions/provider" } }, "warning": { "type": "string", "description": "DEPRECATED: A message that will be output by Composer as a warning when this source is consulted." }, "warning-versions": { "type": "string", "description": "DEPRECATED: A version constraint to limit to which Composer versions the warning should be shown." }, "info": { "type": "string", "description": "DEPRECATED: A message that will be output by Composer as a info when this source is consulted." }, "info-versions": { "type": "string", "description": "DEPRECATED: A version constraint to limit to which Composer versions the info should be shown." } }, "definitions": { "versions": { "type": "object", "description": "A hashmap of versions and their metadata.", "additionalProperties": { "$ref": "#/definitions/version" } }, "version": { "type": "object", "oneOf": [ { "$ref": "#/definitions/package" }, { "$ref": "#/definitions/metapackage" } ] }, "package-base": { "properties": { "name": { "type": "string" }, "type": { "type": "string" }, "version": { "type": "string" }, "version_normalized": { "type": "string", "description": "Normalized version, optional but can save computational time on client side." }, "autoload": { "type": "object" }, "require": { "type": "object" }, "replace": { "type": "object" }, "conflict": { "type": "object" }, "provide": { "type": "object" }, "time": { "type": "string" } }, "additionalProperties": true }, "package": { "allOf": [ { "$ref": "#/definitions/package-base" }, { "properties": { "dist": { "type": "object" }, "source": { "type": "object" } } }, { "oneOf": [ { "required": [ "name", "version", "source" ] }, { "required": [ "name", "version", "dist" ] } ] } ] }, "metapackage": { "allOf": [ { "$ref": "#/definitions/package-base" }, { "properties": { "type": { "type": "string", "enum": [ "metapackage" ] } }, "required": [ "name", "version", "type" ] } ] }, "provider": { "type": "object", "properties": { "sha256": { "type": "string", "description": "Hash value that can be used to validate the resource." } } } } } { "$schema": "https://json-schema.org/draft-04/schema#", "title": "Composer Package", "type": "object", "properties": { "name": { "type": "string", "description": "Package name, including 'vendor-name/' prefix.", "pattern": "^[a-z0-9]([_.-]?[a-z0-9]+)*\/[a-z0-9](([_.]|-{1,2})?[a-z0-9]+)*$" }, "description": { "type": "string", "description": "Short package description." }, "license": { "type": ["string", "array"], "description": "License name. Or an array of license names." }, "type": { "description": "Package type, either 'library' for common packages, 'composer-plugin' for plugins, 'metapackage' for empty packages, or a custom type ([a-z0-9-]+) defined by whatever project this package applies to.", "type": "string", "pattern": "^[a-z0-9-]+$" }, "abandoned": { "type": ["boolean", "string"], "description": "Indicates whether this package has been abandoned, it can be boolean or a package name/URL pointing to a recommended alternative. Defaults to false." }, "version": { "type": "string", "description": "Package version, see https://getcomposer.org/doc/04-schema.md#version for more info on valid schemes.", "pattern": "^[vV]?\\d+(?:[.-]\\d+){0,3}[._-]?(?:(?:[sS][tT][aA][bB][lL][eE]|[bB][eE][tT][aA]|[bB]|[rR][cC]|[aA][lL][pP][hH][aA]|[aA]|[pP][aA][tT][cC][hH]|[pP][lL]|[pP])(?:(?:[.-]?\\d+)*)?)?(?:[.-]?[dD][eE][vV]|\\.x-dev)?(?:\\+.*)?$|^dev-.*$" }, "default-branch": { "type": ["boolean"], "description": "Internal use only, do not specify this in composer.json. Indicates whether this version is the default branch of the linked VCS repository. Defaults to false." }, "non-feature-branches": { "type": ["array"], "description": "A set of string or regex patterns for non-numeric branch names that will not be handled as feature branches.", "items": { "type": "string" } }, "keywords": { "type": "array", "items": { "type": "string", "description": "A tag/keyword that this package relates to." } }, "readme": { "type": "string", "description": "Relative path to the readme document." }, "time": { "type": "string", "description": "Package release date, in 'YYYY-MM-DD', 'YYYY-MM-DD HH:MM:SS' or 'YYYY-MM-DDTHH:MM:SSZ' format." }, "authors": { "$ref": "#/definitions/authors" }, "homepage": { "type": "string", "description": "Homepage URL for the project.", "format": "uri" }, "support": { "type": "object", "properties": { "email": { "type": "string", "description": "Email address for support.", "format": "email" }, "issues": { "type": "string", "description": "URL to the issue tracker.", "format": "uri" }, "forum": { "type": "string", "description": "URL to the forum.", "format": "uri" }, "wiki": { "type": "string", "description": "URL to the wiki.", "format": "uri" }, "irc": { "type": "string", "description": "IRC channel for support, as irc://server/channel.", "format": "uri" }, "chat": { "type": "string", "description": "URL to the support chat.", "format": "uri" }, "source": { "type": "string", "description": "URL to browse or download the sources.", "format": "uri" }, "docs": { "type": "string", "description": "URL to the documentation.", "format": "uri" }, "rss": { "type": "string", "description": "URL to the RSS feed.", "format": "uri" }, "security": { "type": "string", "description": "URL to the vulnerability disclosure policy (VDP).", "format": "uri" } } }, "funding": { "type": "array", "description": "A list of options to fund the development and maintenance of the package.", "items": { "type": "object", "properties": { "type": { "type": "string", "description": "Type of funding or platform through which funding is possible." }, "url": { "type": "string", "description": "URL to a website with details on funding and a way to fund the package.", "format": "uri" } } } }, "source": { "$ref": "#/definitions/source" }, "dist": { "$ref": "#/definitions/dist" }, "_comment": { "type": ["array", "string"], "description": "A key to store comments in" }, "require": { "type": "object", "description": "This is an object of package name (keys) and version constraints (values) that are required to run this package.", "additionalProperties": { "type": "string" } }, "require-dev": { "type": "object", "description": "This is an object of package name (keys) and version constraints (values) that this package requires for developing it (testing tools and such).", "additionalProperties": { "type": "string" } }, "replace": { "type": "object", "description": "This is an object of package name (keys) and version constraints (values) that can be replaced by this package.", "additionalProperties": { "type": "string" } }, "conflict": { "type": "object", "description": "This is an object of package name (keys) and version constraints (values) that conflict with this package.", "additionalProperties": { "type": "string" } }, "provide": { "type": "object", "description": "This is an object of package name (keys) and version constraints (values) that this package provides in addition to this package's name.", "additionalProperties": { "type": "string" } }, "suggest": { "type": "object", "description": "This is an object of package name (keys) and descriptions (values) that this package suggests work well with it (this will be suggested to the user during installation).", "additionalProperties": { "type": "string" } }, "repositories": { "type": ["object", "array"], "description": "A set of additional repositories where packages can be found.", "additionalProperties": { "anyOf": [ { "$ref": "#/definitions/anonymous-repository" }, { "type": "boolean", "enum": [false] } ] }, "items": { "anyOf": [ { "$ref": "#/definitions/repository" }, { "type": "object", "additionalProperties": { "type": "boolean", "enum": [false] }, "minProperties": 1, "maxProperties": 1 } ] } }, "minimum-stability": { "type": ["string"], "description": "The minimum stability the packages must have to be install-able. Possible values are: dev, alpha, beta, RC, stable.", "enum": ["dev", "alpha", "beta", "rc", "RC", "stable"] }, "prefer-stable": { "type": ["boolean"], "description": "If set to true, stable packages will be preferred to dev packages when possible, even if the minimum-stability allows unstable packages." }, "autoload": { "$ref": "#/definitions/autoload" }, "autoload-dev": { "type": "object", "description": "Description of additional autoload rules for development purpose (eg. a test suite).", "properties": { "psr-0": { "type": "object", "description": "This is an object of namespaces (keys) and the directories they can be found into (values, can be arrays of paths) by the autoloader.", "additionalProperties": { "type": ["string", "array"], "items": { "type": "string" } } }, "psr-4": { "type": "object", "description": "This is an object of namespaces (keys) and the PSR-4 directories they can map to (values, can be arrays of paths) by the autoloader.", "additionalProperties": { "type": ["string", "array"], "items": { "type": "string" } } }, "classmap": { "type": "array", "description": "This is an array of paths that contain classes to be included in the class-map generation process." }, "files": { "type": "array", "description": "This is an array of files that are always required on every request." } } }, "target-dir": { "deprecated": true, "description": "DEPRECATED: Forces the package to be installed into the given subdirectory path. This is used for autoloading PSR-0 packages that do not contain their full path. Use forward slashes for cross-platform compatibility.", "type": "string" }, "include-path": { "deprecated": true, "type": ["array"], "description": "DEPRECATED: A list of directories which should get added to PHP's include path. This is only present to support legacy projects, and all new code should preferably use autoloading.", "items": { "type": "string" } }, "bin": { "type": ["string", "array"], "description": "A set of files, or a single file, that should be treated as binaries and symlinked into bin-dir (from config).", "items": { "type": "string" } }, "archive": { "type": ["object"], "description": "Options for creating package archives for distribution.", "properties": { "name": { "type": "string", "description": "A base name for archive." }, "exclude": { "type": "array", "description": "A list of patterns for paths to exclude or include if prefixed with an exclamation mark." } } }, "php-ext": { "type": "object", "description": "Settings for PHP extension packages.", "properties": { "extension-name": { "type": "string", "description": "If specified, this will be used as the name of the extension, where needed by tooling. If this is not specified, the extension name will be derived from the Composer package name (e.g. `vendor/name` would become `ext-name`). The extension name may be specified with or without the `ext-` prefix, and tools that use this must normalise this appropriately.", "example": "ext-xdebug" }, "priority": { "type": "integer", "description": "This is used to add a prefix to the INI file, e.g. `90-xdebug.ini` which affects the loading order. The priority is a number in the range 10-99 inclusive, with 10 being the highest priority (i.e. will be processed first), and 99 being the lowest priority (i.e. will be processed last). There are two digits so that the files sort correctly on any platform, whether the sorting is natural or not.", "minimum": 10, "maximum": 99, "example": 80, "default": 80 }, "support-zts": { "type": "boolean", "description": "Does this package support Zend Thread Safety", "example": false, "default": true }, "support-nts": { "type": "boolean", "description": "Does this package support non-Thread Safe mode", "example": false, "default": true }, "build-path": { "type": ["string", "null"], "description": "If specified, this is the subdirectory that will be used to build the extension instead of the root of the project.", "example": "my-extension-source", "default": null }, "download-url-method": { "oneOf": [ { "type": "string", "description": "DEPRECATED: use the array form instead. If specified, this technique will be used to override the URL that PIE uses to download the asset. The default, if not specified, is composer-default.", "deprecated": true, "enum": ["composer-default", "pre-packaged-source", "pre-packaged-binary"], "example": "composer-default", "default": "composer-default" }, { "type": "array", "description": "Multiple techniques can be specified, in which case PIE will try each in turn until one succeeds. The first technique that succeeds will be used.", "items": { "type": "string", "description": "If specified, this technique will be used to override the URL that PIE uses to download the asset. The default, if not specified, is composer-default.", "enum": ["composer-default", "pre-packaged-source", "pre-packaged-binary"], "example": ["pre-packaged-binary", "composer-default"] }, "minItems": 1, "default": ["composer-default"] } ] }, "os-families": { "type": "array", "minItems": 1, "description": "An array of OS families to mark as compatible with the extension. Specifying this property will mean this package is not installable with PIE on any OS family not listed here. Must not be specified alongside os-families-exclude.", "items": { "type": "string", "enum": ["windows", "bsd", "darwin", "solaris", "linux", "unknown"], "description": "The name of the OS family to mark as compatible." } }, "os-families-exclude": { "type": "array", "minItems": 1, "description": "An array of OS families to mark as incompatible with the extension. Specifying this property will mean this package is installable on any OS family except those listed here. Must not be specified alongside os-families.", "items": { "type": "string", "enum": ["windows", "bsd", "darwin", "solaris", "linux", "unknown"], "description": "The name of the OS family to exclude." } }, "configure-options": { "type": "array", "description": "These configure options make up the flags that can be passed to ./configure when installing the extension.", "items": { "type": "object", "required": ["name"], "properties": { "name": { "type": "string", "description": "The name of the flag, this would typically be prefixed with `--`, for example, the value 'the-flag' would be passed as `./configure --the-flag`.", "example": "without-xdebug-compression", "pattern": "^[a-zA-Z0-9][a-zA-Z0-9-_]*$" }, "needs-value": { "type": "boolean", "description": "If this is set to true, the flag needs a value (e.g. --with-somelib=), otherwise it is a flag without a value (e.g. --enable-some-feature).", "example": false, "default": false }, "description": { "type": "string", "description": "The description of what the flag does or means.", "example": "Disable compression through zlib" } } } } }, "allOf": [ { "not": { "required": ["os-families", "os-families-exclude"] } } ] }, "config": { "type": "object", "description": "Composer options.", "properties": { "platform": { "type": "object", "description": "This is an object of package name (keys) and version (values) that will be used to mock the platform packages on this machine, the version can be set to false to make it appear like the package is not present.", "additionalProperties": { "type": ["string", "boolean"] } }, "allow-plugins": { "type": ["object", "boolean"], "description": "This is an object of {\"pattern\": true|false} with packages which are allowed to be loaded as plugins, or true to allow all, false to allow none. Defaults to {} which prompts when an unknown plugin is added.", "additionalProperties": { "type": ["boolean"] } }, "process-timeout": { "type": "integer", "description": "The timeout in seconds for process executions, defaults to 300 (5mins)." }, "use-include-path": { "type": "boolean", "description": "If true, the Composer autoloader will also look for classes in the PHP include path." }, "use-parent-dir": { "type": ["string", "boolean"], "description": "When running Composer in a directory where there is no composer.json, if there is one present in a directory above Composer will by default ask you whether you want to use that directory's composer.json instead. One of: true (always use parent if needed), false (never ask or use it) or \"prompt\" (ask every time), defaults to prompt." }, "preferred-install": { "type": ["string", "object"], "description": "The install method Composer will prefer to use, defaults to auto and can be any of source, dist, auto, or an object of {\"pattern\": \"preference\"}.", "additionalProperties": { "type": ["string"] } }, "audit": { "type": "object", "deprecated": true, "description": "DEPRECATED: use 'config.policy' instead. All 'audit.*' keys still work as a fallback for now. See https://getcomposer.org/doc/06-config.md#policy", "properties": { "ignore": { "deprecated": true, "description": "DEPRECATED: use 'config.policy.advisories.ignore-id' for advisory IDs (CVE/GHSA/PKSA) and 'config.policy.advisories.ignore' for package names instead. The new format uses 'on-block' / 'on-audit' booleans instead of 'apply: audit|block|all'.", "anyOf": [ { "type": "object", "description": "A list of advisory ids, remote ids, CVE ids or package names (keys) with either explanations (string values) or detailed configuration (object values with 'apply' and 'reason' fields) for why they're ignored.", "additionalProperties": { "anyOf": [ { "type": "string", "description": "Explanation for ignoring this advisory (applies to audit reports and version blocking)" }, { "type": "object", "description": "Detailed configuration for ignoring this advisory", "properties": { "apply": { "type": "string", "enum": ["audit", "block", "all"], "description": "Where to apply this ignore: 'audit' (only audit reports), 'block' (only version blocking), or 'all' (both)" }, "reason": { "type": "string", "description": "Explanation for ignoring this advisory, e.g. vulnerable code path not reachable in application" } } }, { "type": "null", "description": "Ignore without explanation (applies to audit reports and version blocking)" } ] } }, { "type": "array", "description": "A set of advisory ids, remote ids, CVE ids or package names that are reported but let the audit command pass. Applies to audit reports and version blocking.", "items": { "type": "string" } } ] }, "abandoned": { "enum": ["ignore", "report", "fail"], "deprecated": true, "description": "DEPRECATED: use 'config.policy.abandoned.audit' instead. Whether abandoned packages should be ignored, reported as problems or cause an audit failure. Applies only to audit reports, not to version blocking." }, "ignore-severity": { "deprecated": true, "description": "DEPRECATED: use 'config.policy.advisories.ignore-severity' instead. The new format uses 'on-block' / 'on-audit' booleans instead of 'apply: audit|block|all'.", "anyOf": [ { "type": "object", "description": "A list of severities (keys) with either explanations (string values) or detailed configuration (object values with 'apply' fields) for why they're ignored.", "additionalProperties": { "anyOf": [ { "type": "string", "description": "Explanation for ignoring this severity (applies to audit reports and version blocking)" }, { "type": "object", "description": "Detailed configuration for ignoring this severity", "properties": { "apply": { "type": "string", "enum": ["audit", "block", "all"], "description": "Where to apply this ignore: 'audit' (only audit reports), 'block' (only version blocking), or 'all' (both)" } } }, { "type": "null", "description": "Ignore without explanation (applies to audit reports and version blocking)" } ] } }, { "type": "array", "description": "A list of severities for which advisories with matching severity will be ignored, e.g. critical, high, medium, low, or none. Applies to audit reports and version blocking.", "items": { "type": "string" } } ] }, "ignore-unreachable": { "type": "boolean", "deprecated": true, "description": "DEPRECATED: use 'config.policy.ignore-unreachable' instead. The new key accepts a boolean or an array of scopes ('audit', 'install', 'update') and is shared across all dependency policies." }, "block-insecure": { "type": "boolean", "deprecated": true, "description": "DEPRECATED: use 'config.policy.advisories.block' instead. Whether insecure versions should be blocked during a composer update/require command or not.", "default": true }, "block-abandoned": { "type": "boolean", "deprecated": true, "description": "DEPRECATED: use 'config.policy.abandoned.block' instead. Whether abandoned packages should be blocked during a composer update/require command or not.", "default": false }, "ignore-abandoned": { "deprecated": true, "description": "DEPRECATED: use 'config.policy.abandoned.ignore' instead. The new format uses 'on-block' / 'on-audit' booleans instead of 'apply: audit|block|all'.", "anyOf": [ { "type": "object", "description": "A list of abandoned package names (keys) with either explanations (string values) or detailed configuration (object values with 'apply' and 'reason' fields) for why they're ignored.", "additionalProperties": { "anyOf": [ { "type": "string", "description": "Explanation for ignoring this abandoned package (applies to audit reports and version blocking)" }, { "type": "object", "description": "Detailed configuration for ignoring this abandoned package", "properties": { "apply": { "type": "string", "enum": ["audit", "block", "all"], "description": "Where to apply this ignore: 'audit' (only audit reports), 'block' (only version blocking), or 'all' (both)" }, "reason": { "type": "string", "description": "Explanation for ignoring this abandoned package" } } }, { "type": "null", "description": "Ignore without explanation (applies to audit reports and version blocking)" } ] } }, { "type": "array", "description": "A set of abandoned package names that are reported but let the audit command pass and that are not blocked during updates/requires.", "items": { "type": "string" } } ] } } }, "policy": { "type": ["boolean", "object"], "description": "Unified security and package policy configuration. Set to false to disable all policy enforcement.", "properties": { "advisories": { "oneOf": [ { "type": "boolean" }, { "type": "object", "description": "Configuration for packages affected by security advisories.", "properties": { "block": { "type": "boolean", "description": "When true (default), package versions with active security advisories are blocked during update/require/remove.", "default": true }, "audit": { "enum": ["ignore", "report", "fail"], "description": "How composer audit treats packages with security advisories. Defaults to fail.", "default": "fail" }, "ignore-id": { "$ref": "#/definitions/policy-ignore-id" }, "ignore": { "$ref": "#/definitions/policy-ignore" }, "ignore-severity": { "$ref": "#/definitions/policy-ignore-severity" } }, "additionalProperties": false } ] }, "malware": { "oneOf": [ { "type": "boolean" }, { "type": "object", "description": "Configuration for packages flagged as containing malware.", "properties": { "block": { "type": "boolean", "description": "When true (default), packages flagged as malware are blocked.", "default": true }, "block-scope": { "enum": ["all", "update", "install"], "description": "Controls which commands trigger blocking: all (default), update (only update/require/remove), or install (only install).", "default": "all" }, "audit": { "enum": ["ignore", "report", "fail"], "description": "How composer audit treats packages flagged as malware. Defaults to fail.", "default": "fail" }, "ignore": { "$ref": "#/definitions/policy-ignore" }, "ignore-source": { "type": "array", "description": "A list of source names to exclude from malware checks.", "items": { "type": "string" } } }, "additionalProperties": false } ] }, "abandoned": { "oneOf": [ { "type": "boolean" }, { "type": "object", "description": "Configuration for abandoned packages.", "properties": { "block": { "type": "boolean", "description": "When true, abandoned packages cannot be installed during update/require/remove. Defaults to false.", "default": false }, "audit": { "enum": ["ignore", "report", "fail"], "description": "How composer audit treats abandoned packages. Defaults to fail.", "default": "fail" }, "ignore": { "$ref": "#/definitions/policy-ignore" } }, "additionalProperties": false } ] }, "ignore-unreachable": { "description": "Controls for which operations unreachable repositories and policy sources are silently ignored. Defaults to [\"update\", \"install\"]. Set to true to ignore for all operations, false for none.", "oneOf": [ { "type": "boolean" }, { "type": "array", "items": { "type": "string", "enum": ["audit", "install", "update"] } } ] } }, "patternProperties": { "^ignore(?!-unreachable$)": { "not": {}, "description": "Custom dependency policy names must not start with \"ignore\" — that prefix is reserved for future use." }, "^(package|packages|license|licence|licenses|licences|support|maintenance|security|minimum-release-age)$": { "not": {}, "description": "This name is reserved for future use and cannot be used as a custom dependency policy name." } }, "additionalProperties": { "description": "Custom dependency policy configuration.", "oneOf": [ { "type": "boolean" }, { "type": "object", "properties": { "block": { "type": "boolean", "description": "When true (default), matched packages are blocked during update/require/remove.", "default": true }, "audit": { "enum": ["ignore", "report", "fail"], "description": "How composer audit treats packages matched by this dependency policy. Defaults to fail.", "default": "fail" }, "sources": { "type": "array", "description": "Sources supplying the package versions this dependency policy applies to.", "items": { "type": "object", "required": ["type", "url"], "properties": { "type": { "type": "string", "enum": ["url"], "description": "Source type, currently only 'url' is supported." }, "url": { "type": "string", "pattern": "^https://", "description": "URL to fetch the list of package versions from. Must use https://." } }, "additionalProperties": false } }, "ignore": { "$ref": "#/definitions/policy-ignore" } } } ] } }, "notify-on-install": { "type": "boolean", "description": "Composer allows repositories to define a notification URL, so that they get notified whenever a package from that repository is installed. This option allows you to disable that behaviour, defaults to true." }, "source-fallback": { "type": "boolean", "description": "DEPRECATED, will be removed in Composer 2.11. Defaults to false. If true, Composer will fall back to a different installation source (e.g., from dist to source or vice versa) when a download fails. Automatic source fallback has security implications, please open an issue at https://github.com/composer/composer/issues if you need this kept around.", "deprecated": true }, "github-protocols": { "type": "array", "description": "A list of protocols to use for github.com clones, in priority order, defaults to [\"https\", \"ssh\", \"git\"].", "items": { "type": "string" } }, "github-oauth": { "type": "object", "description": "An object of domain name => github API oauth tokens, typically {\"github.com\":\"\"}.", "additionalProperties": { "type": "string" } }, "gitlab-oauth": { "type": "object", "description": "An object of domain name => gitlab API oauth tokens, typically {\"gitlab.com\":{\"expires-at\":\"\", \"refresh-token\":\"\", \"token\":\"\"}}.", "additionalProperties": { "type": ["string", "object"], "required": [ "token"], "properties": { "expires-at": { "type": "integer", "description": "The expiration date for this GitLab token" }, "refresh-token": { "type": "string", "description": "The refresh token used for GitLab authentication" }, "token": { "type": "string", "description": "The token used for GitLab authentication" } } } }, "gitlab-token": { "type": "object", "description": "An object of domain name => gitlab private tokens, typically {\"gitlab.com\":\"\"}, or an object with username and token keys.", "additionalProperties": { "type": ["string", "object"], "required": ["username", "token"], "properties": { "username": { "type": "string", "description": "The username used for GitLab authentication" }, "token": { "type": "string", "description": "The token used for GitLab authentication" } } } }, "gitlab-protocol": { "enum": ["git", "http", "https"], "description": "A protocol to force use of when creating a repository URL for the `source` value of the package metadata. One of `git` or `http`. By default, Composer will generate a git URL for private repositories and http one for public repos." }, "bearer": { "type": "object", "description": "An object of domain name => bearer authentication token, for example {\"example.com\":\"\"}.", "additionalProperties": { "type": "string" } }, "custom-headers": { "type": "object", "description": "Custom HTTP headers for specific domains.", "additionalProperties": { "type": "array", "items": { "type": "string", "description": "A header in format 'Header-Name: Header-Value'" } } }, "forgejo-token": { "type": "object", "description": "An object of domain name => forgejo username/access token, typically {\"codeberg.org\":{\"username\": \"\", \"token\": \"\"}}.", "additionalProperties": { "type": ["object"], "required": ["username", "token"], "properties": { "username": { "type": "string", "description": "The username used for Forgejo authentication" }, "token": { "type": "string", "description": "The access token used for Forgejo authentication" } } } }, "disable-tls": { "type": "boolean", "description": "Defaults to `false`. If set to true all HTTPS URLs will be tried with HTTP instead and no network level encryption is performed. Enabling this is a security risk and is NOT recommended. The better way is to enable the php_openssl extension in php.ini." }, "secure-http": { "type": "boolean", "description": "Defaults to `true`. If set to true only HTTPS URLs are allowed to be downloaded via Composer. If you really absolutely need HTTP access to something then you can disable it, but using \"Let's Encrypt\" to get a free SSL certificate is generally a better alternative." }, "secure-svn-domains": { "type": "array", "description": "A list of domains which should be trusted/marked as using a secure Subversion/SVN transport. By default svn:// protocol is seen as insecure and will throw. This is a better/safer alternative to disabling `secure-http` altogether.", "items": { "type": "string" } }, "cafile": { "type": "string", "description": "A way to set the path to the openssl CA file. In PHP 5.6+ you should rather set this via openssl.cafile in php.ini, although PHP 5.6+ should be able to detect your system CA file automatically." }, "capath": { "type": "string", "description": "If cafile is not specified or if the certificate is not found there, the directory pointed to by capath is searched for a suitable certificate. capath must be a correctly hashed certificate directory." }, "http-basic": { "type": "object", "description": "An object of domain name => {\"username\": \"...\", \"password\": \"...\"}.", "additionalProperties": { "type": "object", "required": ["username", "password"], "properties": { "username": { "type": "string", "description": "The username used for HTTP Basic authentication" }, "password": { "type": "string", "description": "The password used for HTTP Basic authentication" } } } }, "client-certificate": { "type": "object", "description": "An object of domain name => {\"local_cert\": \"...\", \"local_pk\"?: \"...\", \"passphrase\"?: \"...\"} to provide client certificate.", "additionalProperties": { "type": "object", "required": ["local_cert"], "properties": { "local_cert": { "type": "string", "description": "Path to a certificate (pem) or pair certificate+key (pem)" }, "local_pk": { "type": "string", "description": "Path to a private key file (pem)" }, "passphrase": { "type": "string", "description": "Passphrase for private key" } } } }, "store-auths": { "type": ["string", "boolean"], "description": "What to do after prompting for authentication, one of: true (store), false (do not store) or \"prompt\" (ask every time), defaults to prompt." }, "vendor-dir": { "type": "string", "description": "The location where all packages are installed, defaults to \"vendor\"." }, "bin-dir": { "type": "string", "description": "The location where all binaries are linked, defaults to \"vendor/bin\"." }, "data-dir": { "type": "string", "description": "The location where old phar files are stored, defaults to \"$home\" except on XDG Base Directory compliant unixes." }, "cache-dir": { "type": "string", "description": "The location where all caches are located, defaults to \"~/.composer/cache\" on *nix and \"%LOCALAPPDATA%\\Composer\" on windows." }, "cache-files-dir": { "type": "string", "description": "The location where files (zip downloads) are cached, defaults to \"{$cache-dir}/files\"." }, "cache-repo-dir": { "type": "string", "description": "The location where repo (git/hg repo clones) are cached, defaults to \"{$cache-dir}/repo\"." }, "cache-vcs-dir": { "type": "string", "description": "The location where vcs infos (git clones, github api calls, etc. when reading vcs repos) are cached, defaults to \"{$cache-dir}/vcs\"." }, "cache-ttl": { "type": "integer", "description": "The default cache time-to-live, defaults to 15552000 (6 months)." }, "cache-files-ttl": { "type": "integer", "description": "The cache time-to-live for files, defaults to the value of cache-ttl." }, "cache-files-maxsize": { "type": ["string", "integer"], "description": "The cache max size for the files cache, defaults to \"300MiB\"." }, "cache-read-only": { "type": ["boolean"], "description": "Whether to use the Composer cache in read-only mode." }, "bin-compat": { "enum": ["auto", "full", "proxy", "symlink"], "description": "The compatibility of the binaries, defaults to \"auto\" (automatically guessed), can be \"full\" (compatible with both Windows and Unix-based systems) and \"proxy\" (only bash-style proxy)." }, "discard-changes": { "type": ["string", "boolean"], "description": "The default style of handling dirty updates, defaults to false and can be any of true, false or \"stash\"." }, "autoloader-suffix": { "type": "string", "description": "Optional string to be used as a suffix for the generated Composer autoloader. When null a random one will be generated." }, "optimize-autoloader": { "type": "boolean", "description": "Always optimize when dumping the autoloader." }, "prepend-autoloader": { "type": "boolean", "description": "If false, the composer autoloader will not be prepended to existing autoloaders, defaults to true." }, "classmap-authoritative": { "type": "boolean", "description": "If true, the composer autoloader will not scan the filesystem for classes that are not found in the class map, defaults to false." }, "apcu-autoloader": { "type": "boolean", "description": "If true, the Composer autoloader will check for APCu and use it to cache found/not-found classes when the extension is enabled, defaults to false." }, "github-domains": { "type": "array", "description": "A list of domains to use in github mode. This is used for GitHub Enterprise setups, defaults to [\"github.com\"].", "items": { "type": "string" } }, "github-expose-hostname": { "type": "boolean", "description": "Defaults to true. If set to false, the OAuth tokens created to access the github API will have a date instead of the machine hostname." }, "gitlab-domains": { "type": "array", "description": "A list of domains to use in gitlab mode. This is used for custom GitLab setups, defaults to [\"gitlab.com\"].", "items": { "type": "string" } }, "forgejo-domains": { "type": "array", "description": "A list of domains to use in forgejo mode. This is used for custom Forgejo setups, defaults to [\"codeberg.org\"].", "items": { "type": "string" } }, "bitbucket-oauth": { "type": "object", "description": "An object of domain name => {\"consumer-key\": \"...\", \"consumer-secret\": \"...\"}.", "additionalProperties": { "type": "object", "required": ["consumer-key", "consumer-secret"], "properties": { "consumer-key": { "type": "string", "description": "The consumer-key used for OAuth authentication" }, "consumer-secret": { "type": "string", "description": "The consumer-secret used for OAuth authentication" }, "access-token": { "type": "string", "description": "The OAuth token retrieved from Bitbucket's API, this is written by Composer and you should not set it nor modify it." }, "access-token-expiration": { "type": "integer", "description": "The generated token's expiration timestamp, this is written by Composer and you should not set it nor modify it." } } } }, "use-github-api": { "type": "boolean", "description": "Defaults to true. If set to false, globally disables the use of the GitHub API for all GitHub repositories and clones the repository as it would for any other repository." }, "archive-format": { "type": "string", "description": "The default archiving format when not provided on cli, defaults to \"tar\"." }, "archive-dir": { "type": "string", "description": "The default archive path when not provided on cli, defaults to \".\"." }, "htaccess-protect": { "type": "boolean", "description": "Defaults to true. If set to false, Composer will not create .htaccess files in the composer home, cache, and data directories." }, "sort-packages": { "type": "boolean", "description": "Defaults to false. If set to true, Composer will sort packages when adding/updating a new dependency." }, "lock": { "type": "boolean", "description": "Defaults to true. If set to false, Composer will not create a composer.lock file." }, "platform-check": { "type": ["boolean", "string"], "enum": ["php-only", true, false], "description": "Defaults to \"php-only\" which checks only the PHP version. Setting to true will also check the presence of required PHP extensions. If set to false, Composer will not create and require a platform_check.php file as part of the autoloader bootstrap." }, "bump-after-update": { "type": ["string", "boolean"], "enum": ["dev", "no-dev", true, false], "description": "Defaults to false and can be any of true, false, \"dev\"` or \"no-dev\"`. If set to true, Composer will run the bump command after running the update command. If set to \"dev\" or \"no-dev\" then only the corresponding dependencies will be bumped." }, "allow-missing-requirements": { "type": ["boolean"], "description": "Defaults to false. If set to true, Composer will allow install when lock file is not up to date with the latest changes in composer.json." }, "update-with-minimal-changes": { "type": ["boolean"], "description": "Defaults to false. If set to true, Composer will only perform absolutely necessary changes to transitive dependencies during update." } } }, "extra": { "type": ["object", "array"], "description": "Arbitrary extra data that can be used by plugins, for example, package of type composer-plugin may have a 'class' key defining an installer class name.", "additionalProperties": true }, "scripts": { "type": ["object"], "description": "Script listeners that will be executed before/after some events.", "properties": { "pre-install-cmd": { "type": ["array", "string"], "description": "Occurs before the install command is executed, contains one or more Class::method callables or shell commands." }, "post-install-cmd": { "type": ["array", "string"], "description": "Occurs after the install command is executed, contains one or more Class::method callables or shell commands." }, "pre-update-cmd": { "type": ["array", "string"], "description": "Occurs before the update command is executed, contains one or more Class::method callables or shell commands." }, "post-update-cmd": { "type": ["array", "string"], "description": "Occurs after the update command is executed, contains one or more Class::method callables or shell commands." }, "pre-status-cmd": { "type": ["array", "string"], "description": "Occurs before the status command is executed, contains one or more Class::method callables or shell commands." }, "post-status-cmd": { "type": ["array", "string"], "description": "Occurs after the status command is executed, contains one or more Class::method callables or shell commands." }, "pre-package-install": { "type": ["array", "string"], "description": "Occurs before a package is installed, contains one or more Class::method callables or shell commands." }, "post-package-install": { "type": ["array", "string"], "description": "Occurs after a package is installed, contains one or more Class::method callables or shell commands." }, "pre-package-update": { "type": ["array", "string"], "description": "Occurs before a package is updated, contains one or more Class::method callables or shell commands." }, "post-package-update": { "type": ["array", "string"], "description": "Occurs after a package is updated, contains one or more Class::method callables or shell commands." }, "pre-package-uninstall": { "type": ["array", "string"], "description": "Occurs before a package has been uninstalled, contains one or more Class::method callables or shell commands." }, "post-package-uninstall": { "type": ["array", "string"], "description": "Occurs after a package has been uninstalled, contains one or more Class::method callables or shell commands." }, "pre-autoload-dump": { "type": ["array", "string"], "description": "Occurs before the autoloader is dumped, contains one or more Class::method callables or shell commands." }, "post-autoload-dump": { "type": ["array", "string"], "description": "Occurs after the autoloader is dumped, contains one or more Class::method callables or shell commands." }, "post-root-package-install": { "type": ["array", "string"], "description": "Occurs after the root-package is installed, contains one or more Class::method callables or shell commands." }, "post-create-project-cmd": { "type": ["array", "string"], "description": "Occurs after the create-project command is executed, contains one or more Class::method callables or shell commands." } } }, "scripts-descriptions": { "type": ["object"], "description": "Descriptions for custom commands, shown in console help.", "additionalProperties": { "type": "string" } }, "scripts-aliases": { "type": ["object"], "description": "Aliases for custom commands.", "additionalProperties": { "type": "array" } } }, "definitions": { "authors": { "type": "array", "description": "List of authors that contributed to the package. This is typically the main maintainers, not the full list.", "items": { "type": "object", "additionalProperties": false, "required": [ "name"], "properties": { "name": { "type": "string", "description": "Full name of the author." }, "email": { "type": "string", "description": "Email address of the author.", "format": "email" }, "homepage": { "type": "string", "description": "Homepage URL for the author.", "format": "uri" }, "role": { "type": "string", "description": "Author's role in the project." } } } }, "autoload": { "type": "object", "description": "Description of how the package can be autoloaded.", "properties": { "psr-0": { "type": "object", "description": "This is an object of namespaces (keys) and the directories they can be found in (values, can be arrays of paths) by the autoloader.", "additionalProperties": { "type": ["string", "array"], "items": { "type": "string" } } }, "psr-4": { "type": "object", "description": "This is an object of namespaces (keys) and the PSR-4 directories they can map to (values, can be arrays of paths) by the autoloader.", "additionalProperties": { "type": ["string", "array"], "items": { "type": "string" } } }, "classmap": { "type": "array", "description": "This is an array of paths that contain classes to be included in the class-map generation process." }, "files": { "type": "array", "description": "This is an array of files that are always required on every request." }, "exclude-from-classmap": { "type": "array", "description": "This is an array of patterns to exclude from autoload classmap generation. (e.g. \"exclude-from-classmap\": [\"/test/\", \"/tests/\", \"/Tests/\"]" } } }, "repository": { "type": "object", "anyOf": [ { "$ref": "#/definitions/composer-repository" }, { "$ref": "#/definitions/vcs-repository" }, { "$ref": "#/definitions/path-repository" }, { "$ref": "#/definitions/artifact-repository" }, { "$ref": "#/definitions/pear-repository" }, { "$ref": "#/definitions/package-repository" } ] }, "anonymous-repository": { "allOf": [ { "$ref": "#/definitions/repository" }, { "not": { "required": ["name"] } } ] }, "composer-repository": { "type": "object", "required": ["type", "url"], "properties": { "name": { "type": "string" }, "type": { "type": "string", "enum": ["composer"] }, "url": { "type": "string" }, "canonical": { "type": "boolean" }, "only": { "type": "array", "items": { "type": "string" } }, "exclude": { "type": "array", "items": { "type": "string" } }, "options": { "type": "object", "additionalProperties": true }, "allow_ssl_downgrade": { "type": "boolean" }, "force-lazy-providers": { "type": "boolean" }, "filter": { "type": ["boolean", "object"], "description": "Filter list configuration for this repository. Set to false to disable all filter lists advertised by this repository, or to an object listing advertised list names that should be skipped (set to false). Lists not mentioned remain enabled.", "additionalProperties": { "type": "boolean", "description": "Set to false to skip this list from the repository." } } } }, "vcs-repository": { "type": "object", "required": ["type", "url"], "properties": { "name": { "type": "string" }, "type": { "type": "string", "enum": ["vcs", "github", "git", "gitlab", "bitbucket", "git-bitbucket", "hg", "fossil", "perforce", "svn", "forgejo"] }, "url": { "type": "string" }, "canonical": { "type": "boolean" }, "only": { "type": "array", "items": { "type": "string" } }, "exclude": { "type": "array", "items": { "type": "string" } }, "no-api": { "type": "boolean" }, "secure-http": { "type": "boolean" }, "svn-cache-credentials": { "type": "boolean" }, "trunk-path": { "type": ["string", "boolean"] }, "branches-path": { "type": ["string", "boolean"] }, "tags-path": { "type": ["string", "boolean"] }, "package-path": { "type": "string" }, "depot": { "type": "string" }, "branch": { "type": "string" }, "unique_perforce_client_name": { "type": "string" }, "p4user": { "type": "string" }, "p4password": { "type": "string" } } }, "path-repository": { "type": "object", "required": ["type", "url"], "properties": { "name": { "type": "string" }, "type": { "type": "string", "enum": ["path"] }, "url": { "type": "string" }, "canonical": { "type": "boolean" }, "only": { "type": "array", "items": { "type": "string" } }, "exclude": { "type": "array", "items": { "type": "string" } }, "options": { "type": "object", "properties": { "reference": { "type": ["string"], "enum": ["none", "config", "auto"] }, "symlink": { "type": ["boolean", "null"] }, "relative": { "type": ["boolean"] }, "versions": { "type": "object", "additionalProperties": { "type": "string" } } }, "additionalProperties": true } } }, "artifact-repository": { "type": "object", "required": ["type", "url"], "properties": { "name": { "type": "string" }, "type": { "type": "string", "enum": ["artifact"] }, "url": { "type": "string" }, "canonical": { "type": "boolean" }, "only": { "type": "array", "items": { "type": "string" } }, "exclude": { "type": "array", "items": { "type": "string" } } } }, "pear-repository": { "type": "object", "required": ["type", "url"], "properties": { "name": { "type": "string" }, "type": { "type": "string", "enum": ["pear"] }, "url": { "type": "string" }, "canonical": { "type": "boolean" }, "only": { "type": "array", "items": { "type": "string" } }, "exclude": { "type": "array", "items": { "type": "string" } }, "vendor-alias": { "type": "string" } } }, "package-repository": { "type": "object", "required": ["type", "package"], "properties": { "name": { "type": "string" }, "type": { "type": "string", "enum": ["package"] }, "canonical": { "type": "boolean" }, "only": { "type": "array", "items": { "type": "string" } }, "exclude": { "type": "array", "items": { "type": "string" } }, "package": { "oneOf": [ { "$ref": "#/definitions/inline-package" }, { "type": "array", "items": { "$ref": "#/definitions/inline-package" } } ] } } }, "inline-package": { "type": "object", "required": ["name", "version"], "properties": { "name": { "type": "string", "description": "Package name, including 'vendor-name/' prefix." }, "type": { "type": "string" }, "target-dir": { "deprecated": true, "description": "DEPRECATED: Forces the package to be installed into the given subdirectory path. This is used for autoloading PSR-0 packages that do not contain their full path. Use forward slashes for cross-platform compatibility.", "type": "string" }, "description": { "type": "string" }, "keywords": { "type": "array", "items": { "type": "string" } }, "homepage": { "type": "string", "format": "uri" }, "version": { "type": "string" }, "time": { "type": "string" }, "license": { "type": [ "string", "array" ] }, "authors": { "$ref": "#/definitions/authors" }, "require": { "type": "object", "additionalProperties": { "type": "string" } }, "replace": { "type": "object", "additionalProperties": { "type": "string" } }, "conflict": { "type": "object", "additionalProperties": { "type": "string" } }, "provide": { "type": "object", "additionalProperties": { "type": "string" } }, "require-dev": { "type": "object", "additionalProperties": { "type": "string" } }, "suggest": { "type": "object", "additionalProperties": { "type": "string" } }, "extra": { "type": ["object", "array"], "additionalProperties": true }, "autoload": { "$ref": "#/definitions/autoload" }, "archive": { "type": ["object"], "properties": { "exclude": { "type": "array" } } }, "bin": { "type": ["string", "array"], "description": "A set of files, or a single file, that should be treated as binaries and symlinked into bin-dir (from config).", "items": { "type": "string" } }, "include-path": { "deprecated": true, "type": ["array"], "description": "DEPRECATED: A list of directories which should get added to PHP's include path. This is only present to support legacy projects, and all new code should preferably use autoloading.", "items": { "type": "string" } }, "source": { "$ref": "#/definitions/source" }, "dist": { "$ref": "#/definitions/dist" } }, "additionalProperties": true }, "source": { "type": "object", "required": ["type", "url", "reference"], "properties": { "type": { "type": "string" }, "url": { "type": "string" }, "reference": { "type": "string" }, "mirrors": { "type": "array" } } }, "dist": { "type": "object", "required": ["type", "url"], "properties": { "type": { "type": "string" }, "url": { "type": "string" }, "reference": { "type": "string" }, "shasum": { "type": "string" }, "mirrors": { "type": "array" } } }, "policy-ignore": { "description": "Package name patterns with optional version constraints and per-rule scoping (on-block/on-audit).", "anyOf": [ { "type": "array", "description": "Simple list of package names or patterns to ignore.", "items": { "type": "string" } }, { "type": "object", "description": "Map of package names/patterns to reason strings or detailed rule objects.", "additionalProperties": { "anyOf": [ { "type": "string", "description": "Reason for ignoring this package." }, { "type": "null", "description": "Ignore without explanation." }, { "type": "object", "description": "Detailed ignore rule with optional constraint and scoping.", "properties": { "constraint": { "type": "string", "description": "Version constraint to limit the ignore to specific versions." }, "reason": { "type": "string", "description": "Explanation for ignoring this package." }, "on-block": { "type": "boolean", "description": "If false, does not ignore during version blocking (only during audit)." }, "on-audit": { "type": "boolean", "description": "If false, does not ignore during audit reporting (only during blocking)." } } }, { "type": "array", "description": "Multiple ignore rules for the same package.", "items": { "type": "object", "properties": { "constraint": { "type": "string" }, "reason": { "type": "string" }, "on-block": { "type": "boolean" }, "on-audit": { "type": "boolean" } } } } ] } } ] }, "policy-ignore-id": { "description": "Advisory IDs (CVE, GHSA, PKSA, ...) with optional reason and per-rule scoping (on-block/on-audit).", "anyOf": [ { "type": "array", "description": "Simple list of advisory IDs to ignore.", "items": { "type": "string" } }, { "type": "object", "description": "Map of advisory IDs to reason strings or detailed rule objects.", "additionalProperties": { "anyOf": [ { "type": "string", "description": "Reason for ignoring this advisory ID." }, { "type": "null", "description": "Ignore without explanation." }, { "type": "object", "description": "Detailed ignore rule with optional scoping.", "properties": { "reason": { "type": "string", "description": "Explanation for ignoring this advisory ID." }, "on-block": { "type": "boolean", "description": "If false, does not ignore during version blocking (only during audit)." }, "on-audit": { "type": "boolean", "description": "If false, does not ignore during audit reporting (only during blocking)." } } } ] } } ] }, "policy-ignore-severity": { "description": "Advisory severity levels (low, medium, high, critical) with optional reason and per-rule scoping (on-block/on-audit).", "$comment": "anyOf (not oneOf) because the array and object branches both contain string values that can validate against multiple sub-schemas; oneOf would reject otherwise-valid input.", "anyOf": [ { "anyOf": [ { "type": "array", "description": "Simple list of severity levels to ignore.", "items": { "type": "string", "enum": ["low", "medium", "high", "critical"] } }, { "type": "object", "description": "Map of severity levels to reason strings or detailed rule objects.", "additionalProperties": { "anyOf": [ { "type": "string", "description": "Reason for ignoring this severity level." }, { "type": "null", "description": "Ignore without explanation." }, { "type": "object", "description": "Detailed ignore rule with optional scoping.", "properties": { "reason": { "type": "string", "description": "Explanation for ignoring this severity level." }, "on-block": { "type": "boolean", "description": "If false, does not ignore during version blocking (only during audit)." }, "on-audit": { "type": "boolean", "description": "If false, does not ignore during audit reporting (only during blocking)." } } } ] } } ] }, { "type": "array", "description": "A list of severities for which advisories with matching severity will be ignored, e.g. critical, high, medium, low, or none. Applies to audit reports and version blocking.", "items": { "type": "string" } } ] } } } vendorDir = $vendorDir; self::initializeIncludeClosure(); } public function getPrefixes() { if (!empty($this->prefixesPsr0)) { return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); } return array(); } public function getPrefixesPsr4() { return $this->prefixDirsPsr4; } public function getFallbackDirs() { return $this->fallbackDirsPsr0; } public function getFallbackDirsPsr4() { return $this->fallbackDirsPsr4; } public function getClassMap() { return $this->classMap; } public function addClassMap(array $classMap) { if ($this->classMap) { $this->classMap = array_merge($this->classMap, $classMap); } else { $this->classMap = $classMap; } } public function add($prefix, $paths, $prepend = false) { $paths = (array) $paths; if (!$prefix) { if ($prepend) { $this->fallbackDirsPsr0 = array_merge( $paths, $this->fallbackDirsPsr0 ); } else { $this->fallbackDirsPsr0 = array_merge( $this->fallbackDirsPsr0, $paths ); } return; } $first = $prefix[0]; if (!isset($this->prefixesPsr0[$first][$prefix])) { $this->prefixesPsr0[$first][$prefix] = $paths; return; } if ($prepend) { $this->prefixesPsr0[$first][$prefix] = array_merge( $paths, $this->prefixesPsr0[$first][$prefix] ); } else { $this->prefixesPsr0[$first][$prefix] = array_merge( $this->prefixesPsr0[$first][$prefix], $paths ); } } public function addPsr4($prefix, $paths, $prepend = false) { $paths = (array) $paths; if (!$prefix) { if ($prepend) { $this->fallbackDirsPsr4 = array_merge( $paths, $this->fallbackDirsPsr4 ); } else { $this->fallbackDirsPsr4 = array_merge( $this->fallbackDirsPsr4, $paths ); } } elseif (!isset($this->prefixDirsPsr4[$prefix])) { $length = strlen($prefix); if ('\\' !== $prefix[$length - 1]) { throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); } $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; $this->prefixDirsPsr4[$prefix] = $paths; } elseif ($prepend) { $this->prefixDirsPsr4[$prefix] = array_merge( $paths, $this->prefixDirsPsr4[$prefix] ); } else { $this->prefixDirsPsr4[$prefix] = array_merge( $this->prefixDirsPsr4[$prefix], $paths ); } } public function set($prefix, $paths) { if (!$prefix) { $this->fallbackDirsPsr0 = (array) $paths; } else { $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; } } public function setPsr4($prefix, $paths) { if (!$prefix) { $this->fallbackDirsPsr4 = (array) $paths; } else { $length = strlen($prefix); if ('\\' !== $prefix[$length - 1]) { throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); } $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; $this->prefixDirsPsr4[$prefix] = (array) $paths; } } public function setUseIncludePath($useIncludePath) { $this->useIncludePath = $useIncludePath; } public function getUseIncludePath() { return $this->useIncludePath; } public function setClassMapAuthoritative($classMapAuthoritative) { $this->classMapAuthoritative = $classMapAuthoritative; } public function isClassMapAuthoritative() { return $this->classMapAuthoritative; } public function setApcuPrefix($apcuPrefix) { $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; } public function getApcuPrefix() { return $this->apcuPrefix; } public function register($prepend = false) { spl_autoload_register(array($this, 'loadClass'), true, $prepend); if (null === $this->vendorDir) { return; } if ($prepend) { self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; } else { unset(self::$registeredLoaders[$this->vendorDir]); self::$registeredLoaders[$this->vendorDir] = $this; } } public function unregister() { spl_autoload_unregister(array($this, 'loadClass')); if (null !== $this->vendorDir) { unset(self::$registeredLoaders[$this->vendorDir]); } } public function loadClass($class) { if ($file = $this->findFile($class)) { $includeFile = self::$includeFile; $includeFile($file); return true; } return null; } public function findFile($class) { if (isset($this->classMap[$class])) { return $this->classMap[$class]; } if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { return false; } if (null !== $this->apcuPrefix) { $file = apcu_fetch($this->apcuPrefix.$class, $hit); if ($hit) { return $file; } } $file = $this->findFileWithExtension($class, '.php'); if (false === $file && defined('HHVM_VERSION')) { $file = $this->findFileWithExtension($class, '.hh'); } if (null !== $this->apcuPrefix) { apcu_add($this->apcuPrefix.$class, $file); } if (false === $file) { $this->missingClasses[$class] = true; } return $file; } public static function getRegisteredLoaders() { return self::$registeredLoaders; } private function findFileWithExtension($class, $ext) { $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; $first = $class[0]; if (isset($this->prefixLengthsPsr4[$first])) { $subPath = $class; while (false !== $lastPos = strrpos($subPath, '\\')) { $subPath = substr($subPath, 0, $lastPos); $search = $subPath . '\\'; if (isset($this->prefixDirsPsr4[$search])) { $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); foreach ($this->prefixDirsPsr4[$search] as $dir) { if (file_exists($file = $dir . $pathEnd)) { return $file; } } } } } foreach ($this->fallbackDirsPsr4 as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { return $file; } } if (false !== $pos = strrpos($class, '\\')) { $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); } else { $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; } if (isset($this->prefixesPsr0[$first])) { foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { if (0 === strpos($class, $prefix)) { foreach ($dirs as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { return $file; } } } } } foreach ($this->fallbackDirsPsr0 as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { return $file; } } if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { return $file; } return false; } private static function initializeIncludeClosure() { if (self::$includeFile !== null) { return; } self::$includeFile = \Closure::bind(static function($file) { include $file; }, null, null); } } $package) { if (isset($package['type']) && $package['type'] === $type) { $packagesByType[] = $name; } } } return $packagesByType; } public static function isInstalled($packageName, $includeDevRequirements = true) { foreach (self::getInstalled() as $installed) { if (isset($installed['versions'][$packageName])) { return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; } } return false; } public static function satisfies(VersionParser $parser, $packageName, $constraint) { $constraint = $parser->parseConstraints((string) $constraint); $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); return $provided->matches($constraint); } public static function getVersionRanges($packageName) { foreach (self::getInstalled() as $installed) { if (!isset($installed['versions'][$packageName])) { continue; } $ranges = array(); if (isset($installed['versions'][$packageName]['pretty_version'])) { $ranges[] = $installed['versions'][$packageName]['pretty_version']; } if (array_key_exists('aliases', $installed['versions'][$packageName])) { $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); } if (array_key_exists('replaced', $installed['versions'][$packageName])) { $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); } if (array_key_exists('provided', $installed['versions'][$packageName])) { $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); } return implode(' || ', $ranges); } throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); } public static function getVersion($packageName) { foreach (self::getInstalled() as $installed) { if (!isset($installed['versions'][$packageName])) { continue; } if (!isset($installed['versions'][$packageName]['version'])) { return null; } return $installed['versions'][$packageName]['version']; } throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); } public static function getPrettyVersion($packageName) { foreach (self::getInstalled() as $installed) { if (!isset($installed['versions'][$packageName])) { continue; } if (!isset($installed['versions'][$packageName]['pretty_version'])) { return null; } return $installed['versions'][$packageName]['pretty_version']; } throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); } public static function getReference($packageName) { foreach (self::getInstalled() as $installed) { if (!isset($installed['versions'][$packageName])) { continue; } if (!isset($installed['versions'][$packageName]['reference'])) { return null; } return $installed['versions'][$packageName]['reference']; } throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); } public static function getInstallPath($packageName) { foreach (self::getInstalled() as $installed) { if (!isset($installed['versions'][$packageName])) { continue; } return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; } throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); } public static function getRootPackage() { $installed = self::getInstalled(); return $installed[0]['root']; } public static function getRawData() { @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); if (null === self::$installed) { if (substr(__DIR__, -8, 1) !== 'C') { self::$installed = include __DIR__ . '/installed.php'; } else { self::$installed = array(); } } return self::$installed; } public static function getAllRawData() { return self::getInstalled(); } public static function reload($data) { self::$installed = $data; self::$installedByVendor = array(); self::$installedIsLocalDir = false; } private static function getSelfDir() { if (self::$selfDir === null) { self::$selfDir = strtr(__DIR__, '\\', '/'); } return self::$selfDir; } private static function getInstalled() { if (null === self::$canGetVendors) { self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); } $installed = array(); $copiedLocalDir = false; if (self::$canGetVendors) { $selfDir = self::getSelfDir(); foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { $vendorDir = strtr($vendorDir, '\\', '/'); if (isset(self::$installedByVendor[$vendorDir])) { $installed[] = self::$installedByVendor[$vendorDir]; } elseif (is_file($vendorDir.'/composer/installed.php')) { $required = require $vendorDir.'/composer/installed.php'; self::$installedByVendor[$vendorDir] = $required; $installed[] = $required; if (self::$installed === null && $vendorDir.'/composer' === $selfDir) { self::$installed = $required; self::$installedIsLocalDir = true; } } if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) { $copiedLocalDir = true; } } } if (null === self::$installed) { if (substr(__DIR__, -8, 1) !== 'C') { $required = require __DIR__ . '/installed.php'; self::$installed = $required; } else { self::$installed = array(); } } if (self::$installed !== array() && !$copiedLocalDir) { $installed[] = self::$installed; } return $installed; } } $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', 'CURLStringFile' => $vendorDir . '/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php', 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', 'Deprecated' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/Deprecated.php', 'JsonException' => $vendorDir . '/symfony/polyfill-php73/Resources/stubs/JsonException.php', 'Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php', 'Pdo\\Dblib' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/Pdo/Dblib.php', 'Pdo\\Firebird' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/Pdo/Firebird.php', 'Pdo\\Mysql' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/Pdo/Mysql.php', 'Pdo\\Odbc' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/Pdo/Odbc.php', 'Pdo\\Pgsql' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/Pdo/Pgsql.php', 'Pdo\\Sqlite' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/Pdo/Sqlite.php', 'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', 'ReflectionConstant' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/ReflectionConstant.php', 'ReturnTypeWillChange' => $vendorDir . '/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php', 'RoundingMode' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/RoundingMode.php', 'Stringable' => $vendorDir . '/marc-mabe/php-enum/stubs/Stringable.php', 'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', 'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', ); $vendorDir . '/symfony/polyfill-php80/bootstrap.php', '6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php', '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', '8825ede83f2f289127722d4e842cf7e8' => $vendorDir . '/symfony/polyfill-intl-grapheme/bootstrap.php', 'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php', '0d59ee240a4cd96ddbb4ff164fccea4d' => $vendorDir . '/symfony/polyfill-php73/bootstrap.php', 'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php', 'ad155f8f1cf0d418fe49e248db8c661b' => $vendorDir . '/react/promise/src/functions_include.php', '23c18046f52bef3eea034657bafda50f' => $vendorDir . '/symfony/polyfill-php81/bootstrap.php', '9d2b9fc6db0f153a0a149fefb182415e' => $vendorDir . '/symfony/polyfill-php84/bootstrap.php', ); array($vendorDir . '/symfony/polyfill-php84'), 'Symfony\\Polyfill\\Php81\\' => array($vendorDir . '/symfony/polyfill-php81'), 'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'), 'Symfony\\Polyfill\\Php73\\' => array($vendorDir . '/symfony/polyfill-php73'), 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'), 'Symfony\\Polyfill\\Intl\\Normalizer\\' => array($vendorDir . '/symfony/polyfill-intl-normalizer'), 'Symfony\\Polyfill\\Intl\\Grapheme\\' => array($vendorDir . '/symfony/polyfill-intl-grapheme'), 'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'), 'Symfony\\Contracts\\Service\\' => array($vendorDir . '/symfony/service-contracts'), 'Symfony\\Component\\String\\' => array($vendorDir . '/symfony/string'), 'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'), 'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'), 'Symfony\\Component\\Filesystem\\' => array($vendorDir . '/symfony/filesystem'), 'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'), 'Seld\\Signal\\' => array($vendorDir . '/seld/signal-handler/src'), 'Seld\\PharUtils\\' => array($vendorDir . '/seld/phar-utils/src'), 'Seld\\JsonLint\\' => array($vendorDir . '/seld/jsonlint/src/Seld/JsonLint'), 'React\\Promise\\' => array($vendorDir . '/react/promise/src'), 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'), 'Psr\\Container\\' => array($vendorDir . '/psr/container/src'), 'MabeEnum\\' => array($vendorDir . '/marc-mabe/php-enum/src'), 'JsonSchema\\' => array($vendorDir . '/justinrainbow/json-schema/src/JsonSchema'), 'Composer\\XdebugHandler\\' => array($vendorDir . '/composer/xdebug-handler/src'), 'Composer\\Spdx\\' => array($vendorDir . '/composer/spdx-licenses/src'), 'Composer\\Semver\\' => array($vendorDir . '/composer/semver/src'), 'Composer\\Pcre\\' => array($vendorDir . '/composer/pcre/src'), 'Composer\\MetadataMinifier\\' => array($vendorDir . '/composer/metadata-minifier/src'), 'Composer\\ClassMapGenerator\\' => array($vendorDir . '/composer/class-map-generator/src'), 'Composer\\CaBundle\\' => array($vendorDir . '/composer/ca-bundle/src'), 'Composer\\' => array($baseDir . '/src/Composer'), ); register(true); $filesToLoad = \Composer\Autoload\ComposerStaticInitComposerPhar1779960128::$files; $requireFile = \Closure::bind(static function ($fileIdentifier, $file) { if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; require $file; } }, null, null); foreach ($filesToLoad as $fileIdentifier => $file) { $requireFile($fileIdentifier, $file); } return $loader; } } __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php', '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', '8825ede83f2f289127722d4e842cf7e8' => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme/bootstrap.php', 'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php', '0d59ee240a4cd96ddbb4ff164fccea4d' => __DIR__ . '/..' . '/symfony/polyfill-php73/bootstrap.php', 'b6b991a57620e2fb6b2f66f03fe9ddc2' => __DIR__ . '/..' . '/symfony/string/Resources/functions.php', 'ad155f8f1cf0d418fe49e248db8c661b' => __DIR__ . '/..' . '/react/promise/src/functions_include.php', '23c18046f52bef3eea034657bafda50f' => __DIR__ . '/..' . '/symfony/polyfill-php81/bootstrap.php', '9d2b9fc6db0f153a0a149fefb182415e' => __DIR__ . '/..' . '/symfony/polyfill-php84/bootstrap.php', ); public static $prefixLengthsPsr4 = array ( 'S' => array ( 'Symfony\\Polyfill\\Php84\\' => 23, 'Symfony\\Polyfill\\Php81\\' => 23, 'Symfony\\Polyfill\\Php80\\' => 23, 'Symfony\\Polyfill\\Php73\\' => 23, 'Symfony\\Polyfill\\Mbstring\\' => 26, 'Symfony\\Polyfill\\Intl\\Normalizer\\' => 33, 'Symfony\\Polyfill\\Intl\\Grapheme\\' => 31, 'Symfony\\Polyfill\\Ctype\\' => 23, 'Symfony\\Contracts\\Service\\' => 26, 'Symfony\\Component\\String\\' => 25, 'Symfony\\Component\\Process\\' => 26, 'Symfony\\Component\\Finder\\' => 25, 'Symfony\\Component\\Filesystem\\' => 29, 'Symfony\\Component\\Console\\' => 26, 'Seld\\Signal\\' => 12, 'Seld\\PharUtils\\' => 15, 'Seld\\JsonLint\\' => 14, ), 'R' => array ( 'React\\Promise\\' => 14, ), 'P' => array ( 'Psr\\Log\\' => 8, 'Psr\\Container\\' => 14, ), 'M' => array ( 'MabeEnum\\' => 9, ), 'J' => array ( 'JsonSchema\\' => 11, ), 'C' => array ( 'Composer\\XdebugHandler\\' => 23, 'Composer\\Spdx\\' => 14, 'Composer\\Semver\\' => 16, 'Composer\\Pcre\\' => 14, 'Composer\\MetadataMinifier\\' => 26, 'Composer\\ClassMapGenerator\\' => 27, 'Composer\\CaBundle\\' => 18, 'Composer\\' => 9, ), ); public static $prefixDirsPsr4 = array ( 'Symfony\\Polyfill\\Php84\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-php84', ), 'Symfony\\Polyfill\\Php81\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-php81', ), 'Symfony\\Polyfill\\Php80\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-php80', ), 'Symfony\\Polyfill\\Php73\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-php73', ), 'Symfony\\Polyfill\\Mbstring\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', ), 'Symfony\\Polyfill\\Intl\\Normalizer\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer', ), 'Symfony\\Polyfill\\Intl\\Grapheme\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme', ), 'Symfony\\Polyfill\\Ctype\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-ctype', ), 'Symfony\\Contracts\\Service\\' => array ( 0 => __DIR__ . '/..' . '/symfony/service-contracts', ), 'Symfony\\Component\\String\\' => array ( 0 => __DIR__ . '/..' . '/symfony/string', ), 'Symfony\\Component\\Process\\' => array ( 0 => __DIR__ . '/..' . '/symfony/process', ), 'Symfony\\Component\\Finder\\' => array ( 0 => __DIR__ . '/..' . '/symfony/finder', ), 'Symfony\\Component\\Filesystem\\' => array ( 0 => __DIR__ . '/..' . '/symfony/filesystem', ), 'Symfony\\Component\\Console\\' => array ( 0 => __DIR__ . '/..' . '/symfony/console', ), 'Seld\\Signal\\' => array ( 0 => __DIR__ . '/..' . '/seld/signal-handler/src', ), 'Seld\\PharUtils\\' => array ( 0 => __DIR__ . '/..' . '/seld/phar-utils/src', ), 'Seld\\JsonLint\\' => array ( 0 => __DIR__ . '/..' . '/seld/jsonlint/src/Seld/JsonLint', ), 'React\\Promise\\' => array ( 0 => __DIR__ . '/..' . '/react/promise/src', ), 'Psr\\Log\\' => array ( 0 => __DIR__ . '/..' . '/psr/log/Psr/Log', ), 'Psr\\Container\\' => array ( 0 => __DIR__ . '/..' . '/psr/container/src', ), 'MabeEnum\\' => array ( 0 => __DIR__ . '/..' . '/marc-mabe/php-enum/src', ), 'JsonSchema\\' => array ( 0 => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema', ), 'Composer\\XdebugHandler\\' => array ( 0 => __DIR__ . '/..' . '/composer/xdebug-handler/src', ), 'Composer\\Spdx\\' => array ( 0 => __DIR__ . '/..' . '/composer/spdx-licenses/src', ), 'Composer\\Semver\\' => array ( 0 => __DIR__ . '/..' . '/composer/semver/src', ), 'Composer\\Pcre\\' => array ( 0 => __DIR__ . '/..' . '/composer/pcre/src', ), 'Composer\\MetadataMinifier\\' => array ( 0 => __DIR__ . '/..' . '/composer/metadata-minifier/src', ), 'Composer\\ClassMapGenerator\\' => array ( 0 => __DIR__ . '/..' . '/composer/class-map-generator/src', ), 'Composer\\CaBundle\\' => array ( 0 => __DIR__ . '/..' . '/composer/ca-bundle/src', ), 'Composer\\' => array ( 0 => __DIR__ . '/../..' . '/src/Composer', ), ); public static $classMap = array ( 'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', 'CURLStringFile' => __DIR__ . '/..' . '/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php', 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', 'Deprecated' => __DIR__ . '/..' . '/symfony/polyfill-php84/Resources/stubs/Deprecated.php', 'JsonException' => __DIR__ . '/..' . '/symfony/polyfill-php73/Resources/stubs/JsonException.php', 'Normalizer' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php', 'Pdo\\Dblib' => __DIR__ . '/..' . '/symfony/polyfill-php84/Resources/stubs/Pdo/Dblib.php', 'Pdo\\Firebird' => __DIR__ . '/..' . '/symfony/polyfill-php84/Resources/stubs/Pdo/Firebird.php', 'Pdo\\Mysql' => __DIR__ . '/..' . '/symfony/polyfill-php84/Resources/stubs/Pdo/Mysql.php', 'Pdo\\Odbc' => __DIR__ . '/..' . '/symfony/polyfill-php84/Resources/stubs/Pdo/Odbc.php', 'Pdo\\Pgsql' => __DIR__ . '/..' . '/symfony/polyfill-php84/Resources/stubs/Pdo/Pgsql.php', 'Pdo\\Sqlite' => __DIR__ . '/..' . '/symfony/polyfill-php84/Resources/stubs/Pdo/Sqlite.php', 'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', 'ReflectionConstant' => __DIR__ . '/..' . '/symfony/polyfill-php84/Resources/stubs/ReflectionConstant.php', 'ReturnTypeWillChange' => __DIR__ . '/..' . '/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php', 'RoundingMode' => __DIR__ . '/..' . '/symfony/polyfill-php84/Resources/stubs/RoundingMode.php', 'Stringable' => __DIR__ . '/..' . '/marc-mabe/php-enum/stubs/Stringable.php', 'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', 'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', ); public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { $loader->prefixLengthsPsr4 = ComposerStaticInitComposerPhar1779960128::$prefixLengthsPsr4; $loader->prefixDirsPsr4 = ComposerStaticInitComposerPhar1779960128::$prefixDirsPsr4; $loader->classMap = ComposerStaticInitComposerPhar1779960128::$classMap; }, null, ClassLoader::class); } } Copyright (C) 2016 Composer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## ## Bundle of CA Root Certificates ## ## Certificate data from Mozilla as of: Thu May 14 03:12:02 2026 GMT ## ## Find updated versions here: https://curl.se/docs/caextract.html ## ## This is a bundle of X.509 certificates of public Certificate Authorities ## (CA). These were automatically extracted from Mozilla's root certificates ## file (certdata.txt). This file can be found in the mozilla source tree: ## https://raw.githubusercontent.com/mozilla-firefox/firefox/refs/heads/release/security/nss/lib/ckfw/builtins/certdata.txt ## ## It contains the certificates in PEM format and therefore ## can be directly used with curl / libcurl / php_curl, or with ## an Apache+mod_ssl webserver for SSL client authentication. ## Just configure this file as the SSLCACertificateFile. ## ## Conversion done with mk-ca-bundle.pl version 1.33. ## SHA256: 77130ef91213772844561fbd3aa31d413b25c2ac7f576fea3bc3bbff7ef93489 ## Entrust Root Certification Authority ==================================== -----BEGIN CERTIFICATE----- MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMCVVMxFjAUBgNV BAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0Lm5ldC9DUFMgaXMgaW5jb3Jw b3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMWKGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsG A1UEAxMkRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0 MloXDTI2MTEyNzIwNTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMu MTkwNwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSByZWZlcmVu Y2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNVBAMTJEVudHJ1c3QgUm9v dCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB ALaVtkNC+sZtKm9I35RMOVcF7sN5EUFoNu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYsz A9u3g3s+IIRe7bJWKKf44LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOww Cj0Yzfv9KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGIrb68 j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi94DkZfs0Nw4pgHBN rziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOBsDCBrTAOBgNVHQ8BAf8EBAMCAQYw DwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAigA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1 MzQyWjAfBgNVHSMEGDAWgBRokORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DH hmak8fdLQ/uEvW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9tO1KzKtvn1ISM Y/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6ZuaAGAT/3B+XxFNSRuzFVJ7yVTa v52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTS W3iDVuycNsMm4hH2Z0kdkquM++v/eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0 tHuu2guQOHXvgR1m0vdXcDazv/wor3ElhVsT/h5/WrQ8 -----END CERTIFICATE----- COMODO ECC Certification Authority ================================== -----BEGIN CERTIFICATE----- MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTELMAkGA1UEBhMC R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBB dXRob3JpdHkwHhcNMDgwMzA2MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0Ix GzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRo b3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSRFtSrYpn1PlILBs5BAH+X 4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0JcfRK9ChQtP6IHG4/bC8vCVlbpVsLM5ni wz2J+Wos77LTBumjQjBAMB0GA1UdDgQWBBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8E BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VG FAkK+qDmfQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdvGDeA U/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= -----END CERTIFICATE----- ePKI Root Certification Authority ================================= -----BEGIN CERTIFICATE----- MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBeMQswCQYDVQQG EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xKjAoBgNVBAsMIWVQS0kg Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMx MjdaMF4xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEq MCgGA1UECwwhZVBLSSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0B AQEFAAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAHSyZbCUNs IZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAhijHyl3SJCRImHJ7K2RKi lTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3XDZoTM1PRYfl61dd4s5oz9wCGzh1NlDiv qOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX 12ruOzjjK9SXDrkb5wdJfzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0O WQqraffAsgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uUWH1+ ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLSnT0IFaUQAS2zMnao lQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pHdmX2Os+PYhcZewoozRrSgx4hxyy/ vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJipNiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXi Zo1jDiVN1Rmy5nk3pyKdVDECAwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/Qkqi MAwGA1UdEwQFMAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGBuvl2ICO1J2B0 1GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6YlPwZpVnPDimZI+ymBV3QGypzq KOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkPJXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdV xrsStZf0X4OFunHB2WyBEXYKCrC/gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEP NXubrjlpC2JgQCA2j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+r GNm65ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUBo2M3IUxE xJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS/jQ6fbjpKdx2qcgw+BRx gMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2zGp1iro2C6pSe3VkQw63d4k3jMdXH7Ojy sP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTEW9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmOD BCEIZ43ygknQW/2xzQ+DhNQ+IIX3Sj0rnP0qCglN6oH4EZw= -----END CERTIFICATE----- NetLock Arany (Class Gold) Főtanúsítvány ======================================== -----BEGIN CERTIFICATE----- MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQGEwJIVTERMA8G A1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610 dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBB cmFueSAoQ2xhc3MgR29sZCkgRsWRdGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgx MjA2MTUwODIxWjCBpzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxO ZXRMb2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlmaWNhdGlv biBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNzIEdvbGQpIEbFkXRhbsO6 c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCRec75LbRTDofTjl5Bu 0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrTlF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw /HpYzY6b7cNGbIRwXdrzAZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAk H3B5r9s5VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRGILdw fzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2BJtr+UBdADTHLpl1 neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAGAQH/AgEEMA4GA1UdDwEB/wQEAwIB BjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2MU9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwW qZw8UQCgwBEIBaeZ5m8BiFRhbvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTta YtOUZcTh5m2C+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2FuLjbvrW5Kfna NwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2XjG4Kvte9nHfRCaexOYNkbQu dZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= -----END CERTIFICATE----- Microsec e-Szigno Root CA 2009 ============================== -----BEGIN CERTIFICATE----- MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYDVQQGEwJIVTER MA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jv c2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o dTAeFw0wOTA2MTYxMTMwMThaFw0yOTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UE BwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUt U3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTCCASIw DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvPkd6mJviZpWNwrZuuyjNA fW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tccbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG 0IMZfcChEhyVbUr02MelTTMuhTlAdX4UfIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKA pxn1ntxVUwOXewdI/5n7N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm 1HxdrtbCxkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1+rUC AwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTLD8bf QkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAbBgNVHREE FDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqGSIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0o lZMEyL/azXm4Q5DwpL7v8u8hmLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfX I/OMn74dseGkddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775 tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c2Pm2G2JwCz02 yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5tHMN1Rq41Bab2XD0h7lbwyYIi LXpUq3DDfSJlgnCW -----END CERTIFICATE----- GlobalSign Root CA - R3 ======================= -----BEGIN CERTIFICATE----- MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4GA1UECxMXR2xv YmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh bFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT aWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWt iHL8RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsTgHeMCOFJ 0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmmKPZpO/bLyCiR5Z2KYVc3 rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zdQQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjl OCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZXriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2 xmmFghcCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE FI/wS3+oLkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZURUm7 lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMpjjM5RcOO5LlXbKr8 EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK6fBdRoyV3XpYKBovHd7NADdBj+1E bddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQXmcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18 YIvDQVETI53O9zJrlAGomecsMx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7r kpeDMdmztcpHWD9f -----END CERTIFICATE----- Izenpe.com ========== -----BEGIN CERTIFICATE----- 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 -----END CERTIFICATE----- Go Daddy Root Certificate Authority - G2 ======================================== -----BEGIN CERTIFICATE----- MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoTEUdvRGFkZHkuY29tLCBJbmMu MTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5 MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8G A1UEAxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI hvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKDE6bFIEMBO4Tx5oVJnyfq 9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD +qK+ihVqf94Lw7YZFAXK6sOoBJQ7RnwyDfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutd fMh8+7ArU6SSYmlRJQVhGkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMl NAJWJwGRtDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEAAaNC MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFDqahQcQZyi27/a9 BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmXWWcDYfF+OwYxdS2hII5PZYe096ac vNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r 5N9ss4UXnT3ZJE95kTXWXwTrgIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYV N8Gb5DKj7Tjo2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI4uJEvlz36hz1 -----END CERTIFICATE----- Starfield Root Certificate Authority - G2 ========================================= -----BEGIN CERTIFICATE----- MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s b2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVsZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0 eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAw DgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQg VGVjaG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZpY2F0ZSBB dXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL3twQP89o/8ArFv W59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMgnLRJdzIpVv257IzdIvpy3Cdhl+72WoTs bhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNk N3mSwOxGXn/hbVNMYq/NHwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7Nf ZTD4p7dNdloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0HZbU JtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC AQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0GCSqGSIb3DQEBCwUAA4IBAQARWfol TwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjUsHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx 4mcujJUDJi5DnUox9g61DLu34jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUw F5okxBDgBPfg8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1mMpYjn0q7pBZ c2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 -----END CERTIFICATE----- Starfield Services Root Certificate Authority - G2 ================================================== -----BEGIN CERTIFICATE----- MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMxEDAOBgNVBAgT B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s b2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVsZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRl IEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNV BAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxT dGFyZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2VydmljZXMg Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC AQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20pOsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2 h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm28xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4Pa hHQUw2eeBGg6345AWh1KTs9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLP LJGmpufehRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk6mFB rMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAwDwYDVR0TAQH/BAUw AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+qAdcwKziIorhtSpzyEZGDMA0GCSqG SIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMIbw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPP E95Dz+I0swSdHynVv/heyNXBve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTy xQGjhdByPq1zqwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn0q23KXB56jza YyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCNsSi6 -----END CERTIFICATE----- Certum Trusted Network CA ========================= -----BEGIN CERTIFICATE----- MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQK ExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlv biBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIy MTIwNzM3WhcNMjkxMjMxMTIwNzM3WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBU ZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 MSIwIAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC AQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rHUV+rpDKmYYe2bg+G0jAC l/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LMTXPb865Px1bVWqeWifrzq2jUI4ZZJ88J J7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVUBBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4 fOQtf/WsX+sWn7Et0brMkUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0 cvW0QM8xAcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNVHRMB Af8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNVHQ8BAf8EBAMCAQYw DQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15ysHhE49wcrwn9I0j6vSrEuVUEtRCj jSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfLI9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1 mS1FhIrlQgnXdAIv94nYmem8J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5aj Zt3hrvJBW8qYVoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI 03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= -----END CERTIFICATE----- TWCA Root Certification Authority ================================= -----BEGIN CERTIFICATE----- MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJ VEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlmaWNh dGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMzWhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQG EwJUVzESMBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NB IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK AoIBAQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFEAcK0HMMx QhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HHK3XLfJ+utdGdIzdjp9xC oi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeXRfwZVzsrb+RH9JlF/h3x+JejiB03HFyP 4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/zrX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1r y+UPizgN7gr8/g+YnzAx3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIB BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkqhkiG 9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeCMErJk/9q56YAf4lC mtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdlsXebQ79NqZp4VKIV66IIArB6nCWlW QtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62Dlhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVY T0bf+215WfKEIlKuD8z7fDvnaspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocny Yh0igzyXxfkZYiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw== -----END CERTIFICATE----- Security Communication RootCA2 ============================== -----BEGIN CERTIFICATE----- MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDElMCMGA1UEChMc U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMeU2VjdXJpdHkgQ29tbXVuaWNh dGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoXDTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMC SlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3Vy aXR5IENvbW11bmljYXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB ANAVOVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGrzbl+dp++ +T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVMVAX3NuRFg3sUZdbcDE3R 3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQhNBqyjoGADdH5H5XTz+L62e4iKrFvlNV spHEfbmwhRkGeC7bYRr6hfVKkaHnFtWOojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1K EOtOghY6rCcMU/Gt1SSwawNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8 QIH4D5csOPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB CwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpFcoJxDjrSzG+ntKEj u/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXcokgfGT+Ok+vx+hfuzU7jBBJV1uXk 3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6q tnRGEmyR7jTV7JqR50S+kDFy1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29 mvVXIwAHIRc/SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 -----END CERTIFICATE----- Actalis Authentication Root CA ============================== -----BEGIN CERTIFICATE----- MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCSVQxDjAM BgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UE AwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDky MjExMjIwMlowazELMAkGA1UEBhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlz IFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNvUTufClrJ wkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX4ay8IMKx4INRimlNAJZa by/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9KK3giq0itFZljoZUj5NDKd45RnijMCO6 zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1f YVEiVRvjRuPjPdA1YprbrxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2 oxgkg4YQ51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2Fbe8l EfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxeKF+w6D9Fz8+vm2/7 hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4Fv6MGn8i1zeQf1xcGDXqVdFUNaBr8 EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbnfpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5 jF66CyCU3nuDuP/jVo23Eek7jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLY iDrIn3hm7YnzezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQALe3KHwGCmSUyI WOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70jsNjLiNmsGe+b7bAEzlgqqI0 JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDzWochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKx K3JCaKygvU5a2hi/a5iB0P2avl4VSM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+ Xlff1ANATIGk0k9jpwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC 4yyXX04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+OkfcvHlXHo 2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7RK4X9p2jIugErsWx0Hbhz lefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btUZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXem OR/qnuOf0GZvBeyqdn6/axag67XH/JJULysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9 vwGYT7JZVEc+NHt4bVaTLnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== -----END CERTIFICATE----- Buypass Class 2 Root CA ======================= -----BEGIN CERTIFICATE----- MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMiBSb290IENBMB4X DTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1owTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1 eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIw DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1 g1Lr6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPVL4O2fuPn 9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC911K2GScuVr1QGbNgGE41b /+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHxMlAQTn/0hpPshNOOvEu/XAFOBz3cFIqU CqTqc/sLUegTBxj6DvEr0VQVfTzh97QZQmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeff awrbD02TTqigzXsu8lkBarcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgI zRFo1clrUs3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLiFRhn Bkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRSP/TizPJhk9H9Z2vX Uq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN9SG9dKpN6nIDSdvHXx1iY8f93ZHs M+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxPAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD VR0OBBYEFMmAd+BikoL1RpzzuvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF AAOCAgEAU18h9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3tOluwlN5E40EI osHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo+fsicdl9sz1Gv7SEr5AcD48S aq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYd DnkM/crqJIByw5c/8nerQyIKx+u2DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWD LfJ6v9r9jv6ly0UsH8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0 oyLQI+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK75t98biGC wWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h3PFaTWwyI0PurKju7koS CTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPzY11aWOIv4x3kqdbQCtCev9eBCfHJxyYN rJgWVqA= -----END CERTIFICATE----- Buypass Class 3 Root CA ======================= -----BEGIN CERTIFICATE----- MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMyBSb290IENBMB4X DTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFowTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1 eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIw DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRH sJ8YZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3EN3coTRiR 5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9tznDDgFHmV0ST9tD+leh 7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX0DJq1l1sDPGzbjniazEuOQAnFN44wOwZ ZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH 2xc519woe2v1n/MuwU8XKhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV /afmiSTYzIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvSO1UQ RwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D34xFMFbG02SrZvPA Xpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgPK9Dx2hzLabjKSWJtyNBjYt1gD1iq j6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD VR0OBBYEFEe4zf/lb+74suwvTg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF AAOCAgEAACAjQTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXSIGrs/CIBKM+G uIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2HJLw5QY33KbmkJs4j1xrG0aG Q0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsaO5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8 ZORK15FTAaggiG6cX0S5y2CBNOxv033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2 KSb12tjE8nVhz36udmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz 6MkEkbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg413OEMXbug UZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvDu79leNKGef9JOxqDDPDe eOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq4/g7u9xN12TyUb7mqqta6THuBrxzvxNi Cp/HuZc= -----END CERTIFICATE----- T-TeleSec GlobalRoot Class 3 ============================ -----BEGIN CERTIFICATE----- MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgx MDAxMTAyOTU2WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0GCSqGSIb3 DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN8ELg63iIVl6bmlQdTQyK 9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/RLyTPWGrTs0NvvAgJ1gORH8EGoel15YU NpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZF iP0Zf3WHHx+xGwpzJFu5ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W 0eDrXltMEnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGjQjBA MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1A/d2O2GCahKqGFPr AyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOyWL6ukK2YJ5f+AbGwUgC4TeQbIXQb fsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzT ucpH9sry9uetuUg/vBa3wW306gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7h P0HHRwA11fXT91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4pTpPDpFQUWw== -----END CERTIFICATE----- D-TRUST Root Class 3 CA 2 2009 ============================== -----BEGIN CERTIFICATE----- MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQK DAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTAe Fw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NThaME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxE LVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIw DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOAD ER03UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42tSHKXzlA BF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9RySPocq60vFYJfxLLHLGv KZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsMlFqVlNpQmvH/pStmMaTJOKDfHR+4CS7z p+hnUquVH+BGPtikw8paxTGA6Eian5Rp/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUC AwEAAaOCARowggEWMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ 4PGEMA4GA1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVjdG9y eS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUyMENBJTIwMiUyMDIw MDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRlcmV2b2NhdGlvbmxpc3QwQ6BBoD+G PWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAw OS5jcmwwDQYJKoZIhvcNAQELBQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm 2H6NMLVwMeniacfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0 o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4KzCUqNQT4YJEV dT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8PIWmawomDeCTmGCufsYkl4ph X5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3YJohw1+qRzT65ysCQblrGXnRl11z+o+I= -----END CERTIFICATE----- D-TRUST Root Class 3 CA 2 EV 2009 ================================= -----BEGIN CERTIFICATE----- MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw OTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUwNDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw OTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfS egpnljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM03TP1YtHh zRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6ZqQTMFexgaDbtCHu39b+T 7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lRp75mpoo6Kr3HGrHhFPC+Oh25z1uxav60 sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure35 11H3a6UCAwEAAaOCASQwggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyv cop9NteaHNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFwOi8v ZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xhc3MlMjAzJTIwQ0El MjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1ERT9jZXJ0aWZpY2F0ZXJldm9jYXRp b25saXN0MEagRKBChkBodHRwOi8vd3d3LmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xh c3NfM19jYV8yX2V2XzIwMDkuY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+ PPoeUSbrh/Yp3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05 nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNFCSuGdXzfX2lX ANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7naxpeG0ILD5EJt/rDiZE4OJudA NCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqXKVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVv w9y4AyHqnxbxLFS1 -----END CERTIFICATE----- CA Disig Root R2 ================ -----BEGIN CERTIFICATE----- MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNVBAYTAlNLMRMw EQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMuMRkwFwYDVQQDExBDQSBEaXNp ZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQyMDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sx EzARBgNVBAcTCkJyYXRpc2xhdmExEzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERp c2lnIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbC w3OeNcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNHPWSb6Wia xswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3Ix2ymrdMxp7zo5eFm1tL7 A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbeQTg06ov80egEFGEtQX6sx3dOy1FU+16S GBsEWmjGycT6txOgmLcRK7fWV8x8nhfRyyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqV g8NTEQxzHQuyRpDRQjrOQG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa 5Beny912H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJQfYE koopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUDi/ZnWejBBhG93c+A Ak9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORsnLMOPReisjQS1n6yqEm70XooQL6i Fh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNV HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5u Qu0wDQYJKoZIhvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqfGopTpti72TVV sRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkblvdhuDvEK7Z4bLQjb/D907Je dR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka+elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W8 1k/BfDxujRNt+3vrMNDcTa/F1balTFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjx mHHEt38OFdAlab0inSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01 utI3gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18DrG5gPcFw0 sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3OszMOl6W8KjptlwlCFtaOg UxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8xL4ysEr3vQCj8KWefshNPZiTEUxnpHikV 7+ZtsH8tZ/3zbBt1RqPlShfppNcL -----END CERTIFICATE----- ACCVRAIZ1 ========= -----BEGIN CERTIFICATE----- MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UEAwwJQUNDVlJB SVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQswCQYDVQQGEwJFUzAeFw0xMTA1 MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQBgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwH UEtJQUNDVjENMAsGA1UECgwEQUNDVjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4IC DwAwggIKAoICAQCbqau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gM jmoYHtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWoG2ioPej0 RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpAlHPrzg5XPAOBOp0KoVdD aaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhrIA8wKFSVf+DuzgpmndFALW4ir50awQUZ 0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDG WuzndN9wrqODJerWx5eHk6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs7 8yM2x/474KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMOm3WR 5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpacXpkatcnYGMN285J 9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPluUsXQA+xtrn13k/c4LOsOxFwYIRK Q26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYIKwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRw Oi8vd3d3LmFjY3YuZXMvZmlsZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEu Y3J0MB8GCCsGAQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2 VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeTVfZW6oHlNsyM Hj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIGCCsGAQUFBwICMIIBFB6CARAA QQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUAcgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBh AO0AegAgAGQAZQAgAGwAYQAgAEEAQwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUA YwBuAG8AbABvAGcA7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBj AHQAcgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAAQwBQAFMA IABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUAczAwBggrBgEFBQcCARYk aHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2MuaHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0 dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRtaW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2 MV9kZXIuY3JsMA4GA1UdDwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZI hvcNAQEFBQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdpD70E R9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gUJyCpZET/LtZ1qmxN YEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+mAM/EKXMRNt6GGT6d7hmKG9Ww7Y49 nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepDvV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJ TS+xJlsndQAJxGJ3KQhfnlmstn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3 sCPdK6jT2iWH7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szAh1xA2syVP1Xg Nce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xFd3+YJ5oyXSrjhO7FmGYvliAd 3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2HpPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3p EfbRD0tVNEYqi4Y7 -----END CERTIFICATE----- TWCA Global Root CA =================== -----BEGIN CERTIFICATE----- MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcxEjAQBgNVBAoT CVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMTVFdDQSBHbG9iYWwgUm9vdCBD QTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQK EwlUQUlXQU4tQ0ExEDAOBgNVBAsTB1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3Qg Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2C nJfF10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz0ALfUPZV r2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfChMBwqoJimFb3u/Rk28OKR Q4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbHzIh1HrtsBv+baz4X7GGqcXzGHaL3SekV tTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1W KKD+u4ZqyPpcC1jcxkt2yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99 sy2sbZCilaLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYPoA/p yJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQABDzfuBSO6N+pjWxn kjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcEqYSjMq+u7msXi7Kx/mzhkIyIqJdI zshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMC AQYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6g cFGn90xHNcgL1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WFH6vPNOw/KP4M 8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNoRI2T9GRwoD2dKAXDOXC4Ynsg /eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlg lPx4mI88k1HtQJAH32RjJMtOcQWh15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryP A9gK8kxkRr05YuWW6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3m i4TWnsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5jwa19hAM8 EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWzaGHQRiapIVJpLesux+t3 zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmyKwbQBM0= -----END CERTIFICATE----- T-TeleSec GlobalRoot Class 2 ============================ -----BEGIN CERTIFICATE----- MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgx MDAxMTA0MDE0WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0GCSqGSIb3 DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUdAqSzm1nzHoqvNK38DcLZ SBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiCFoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/F vudocP05l03Sx5iRUKrERLMjfTlH6VJi1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx970 2cu+fjOlbpSD8DT6IavqjnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGV WOHAD3bZwI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGjQjBA MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/WSA2AHmgoCJrjNXy YdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhyNsZt+U2e+iKo4YFWz827n+qrkRk4 r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPACuvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNf vNoBYimipidx5joifsFvHZVwIEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR 3p1m0IvVVGb6g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN 9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlPBSeOE6Fuwg== -----END CERTIFICATE----- Atos TrustedRoot 2011 ===================== -----BEGIN CERTIFICATE----- MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UEAwwVQXRvcyBU cnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0xMTA3MDcxNDU4 MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMMFUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsG A1UECgwEQXRvczELMAkGA1UEBhMCREUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCV hTuXbyo7LjvPpvMpNb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr 54rMVD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+SZFhyBH+ DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ4J7sVaE3IqKHBAUsR320 HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0Lcp2AMBYHlT8oDv3FdU9T1nSatCQujgKR z3bFmx5VdJx4IbHwLfELn8LVlhgf8FQieowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7R l+lwrrw7GWzbITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZ bNshMBgGA1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB CwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8jvZfza1zv7v1Apt+h k6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kPDpFrdRbhIfzYJsdHt6bPWHJxfrrh TZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pcmaHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a9 61qn8FYiqTxlVMYVqL2Gns2Dlmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G 3mB/ufNPRJLvKrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed -----END CERTIFICATE----- QuoVadis Root CA 1 G3 ===================== -----BEGIN CERTIFICATE----- MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQELBQAwSDELMAkG A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv b3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJN MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEg RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakE PBtVwedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWerNrwU8lm PNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF34168Xfuw6cwI2H44g4hWf6 Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh4Pw5qlPafX7PGglTvF0FBM+hSo+LdoIN ofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXpUhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/l g6AnhF4EwfWQvTA9xO+oabw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV 7qJZjqlc3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/GKubX 9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSthfbZxbGL0eUQMk1f iyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KOTk0k+17kBL5yG6YnLUlamXrXXAkg t3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOtzCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZI hvcNAQELBQADggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2cDMT/uFPpiN3 GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUNqXsCHKnQO18LwIE6PWThv6ct Tr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP +V04ikkwj+3x6xn0dxoxGE1nVGwvb2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh 3jRJjehZrJ3ydlo28hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fa wx/kNSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNjZgKAvQU6 O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhpq1467HxpvMc7hU6eFbm0 FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFtnh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOV hMJKzRwuJIczYOXD -----END CERTIFICATE----- QuoVadis Root CA 2 G3 ===================== -----BEGIN CERTIFICATE----- MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQELBQAwSDELMAkG A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv b3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJN MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIg RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFh ZiFfqq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMWn4rjyduY NM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ymc5GQYaYDFCDy54ejiK2t oIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+O7q414AB+6XrW7PFXmAqMaCvN+ggOp+o MiwMzAkd056OXbxMmO7FGmh77FOm6RQ1o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+l V0POKa2Mq1W/xPtbAd0jIaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZo L1NesNKqIcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz8eQQ sSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43ehvNURG3YBZwjgQQvD 6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l7ZizlWNof/k19N+IxWA1ksB8aRxh lRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALGcC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTAD AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZI hvcNAQELBQADggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RCroijQ1h5fq7K pVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0GaW/ZZGYjeVYg3UQt4XAoeo0L9 x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4nlv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgz dWqTHBLmYF5vHX/JHyPLhGGfHoJE+V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6X U/IyAgkwo1jwDQHVcsaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+Nw mNtddbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNgKCLjsZWD zYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeMHVOyToV7BjjHLPj4sHKN JeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4WSr2Rz0ZiC3oheGe7IUIarFsNMkd7Egr O3jtZsSOeWmD3n+M -----END CERTIFICATE----- QuoVadis Root CA 3 G3 ===================== -----BEGIN CERTIFICATE----- MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQELBQAwSDELMAkG A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv b3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJN MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMg RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286 IxSR/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNuFoM7pmRL Mon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXRU7Ox7sWTaYI+FrUoRqHe 6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+cra1AdHkrAj80//ogaX3T7mH1urPnMNA3 I4ZyYUUpSFlob3emLoG+B01vr87ERRORFHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3U VDmrJqMz6nWB2i3ND0/kA9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f7 5li59wzweyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634RylsSqi Md5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBpVzgeAVuNVejH38DM dyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0QA4XN8f+MFrXBsj6IbGB/kE+V9/Yt rQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZI hvcNAQELBQADggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnIFUBhynLWcKzS t/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5WvvoxXqA/4Ti2Tk08HS6IT7SdEQ TXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFgu/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9Du DcpmvJRPpq3t/O5jrFc/ZSXPsoaP0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGib Ih6BJpsQBJFxwAYf3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmD hPbl8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+DhcI00iX 0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HNPlopNLk9hM6xZdRZkZFW dSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ywaZWWDYWGWVjUTR939+J399roD1B0y2 PpxxVJkES/1Y+Zj0 -----END CERTIFICATE----- DigiCert Assured ID Root G2 =========================== -----BEGIN CERTIFICATE----- MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBlMQswCQYDVQQG EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgw MTE1MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIw ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSAn61UQbVH 35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4HteccbiJVMWWXvdMX0h5i89vq bFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9HpEgjAALAcKxHad3A2m67OeYfcgnDmCXRw VWmvo2ifv922ebPynXApVfSr/5Vh88lAbx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OP YLfykqGxvYmJHzDNw6YuYjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+Rn lTGNAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTO w0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPIQW5pJ6d1Ee88hjZv 0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I0jJmwYrA8y8678Dj1JGG0VDjA9tz d29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4GnilmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAW hsI6yLETcDbYz+70CjTVW0z9B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0M jomZmWzwPDCvON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo IhNzbM8m9Yop5w== -----END CERTIFICATE----- DigiCert Assured ID Root G3 =========================== -----BEGIN CERTIFICATE----- MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYD VQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1 MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQ BgcqhkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJfZn4f5dwb RXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17QRSAPWXYQ1qAk8C3eNvJs KTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgF UaFNN6KDec6NHSrkhDAKBggqhkjOPQQDAwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5Fy YZ5eEJJZVrmDxxDnOOlYJjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy 1vUhZscv6pZjamVFkpUBtA== -----END CERTIFICATE----- DigiCert Global Root G2 ======================= -----BEGIN CERTIFICATE----- MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQG EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUx MjAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkq hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI2/Ou8jqJ kTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx1x7e/dfgy5SDN67sH0NO 3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQq2EGnI/yuum06ZIya7XzV+hdG82MHauV BJVJ8zUtluNJbd134/tJS7SsVQepj5WztCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyM UNGPHgm+F6HmIcr9g+UQvIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQAB o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV5uNu 5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY1Yl9PMWLSn/pvtsr F9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4NeF22d+mQrvHRAiGfzZ0JFrabA0U WTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NGFdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBH QRFXGU7Aj64GxJUTFy8bJZ918rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/ iyK5S9kJRaTepLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl MrY= -----END CERTIFICATE----- DigiCert Global Root G3 ======================= -----BEGIN CERTIFICATE----- MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQswCQYDVQQGEwJV UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAwHgYD VQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAw MDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5k aWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0C AQYFK4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FGfp4tn+6O YwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPOZ9wj/wMco+I+o0IwQDAP BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNp Yim8S8YwCgYIKoZIzj0EAwMDaAAwZQIxAK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y 3maTD/HMsQmP3Wyr+mt/oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34 VOKa5Vt8sycX -----END CERTIFICATE----- DigiCert Trusted Root G4 ======================== -----BEGIN CERTIFICATE----- MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQG EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEw HwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1 MTIwMDAwWjBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0G CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3yithZwuEp pz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1Ifxp4VpX6+n6lXFllVcq9o k3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDVySAdYyktzuxeTsiT+CFhmzTrBcZe7Fsa vOvJz82sNEBfsXpm7nfISKhmV1efVFiODCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGY QJB5w3jHtrHEtWoYOAMQjdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6 MUSaM0C/CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCiEhtm mnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADMfRyVw4/3IbKyEbe7 f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QYuKZ3AeEPlAwhHbJUKSWJbOUOUlFH dL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXKchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8 oR7FwI+isX4KJpn15GkvmB0t9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud DwEB/wQEAwIBhjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2SV1EY+CtnJYY ZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd+SeuMIW59mdNOj6PWTkiU0Tr yF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWcfFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy 7zBZLq7gcfJW5GqXb5JQbZaNaHqasjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iah ixTXTBmyUEFxPT9NcCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN 5r5N0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie4u1Ki7wb /UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mIr/OSmbaz5mEP0oUA51Aa 5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tK G48BtieVU+i2iW1bvGjUI+iLUaJW+fCmgKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP 82Z+ -----END CERTIFICATE----- COMODO RSA Certification Authority ================================== -----BEGIN CERTIFICATE----- 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 LaZRfyHBNVOFBkpdn627G190 -----END CERTIFICATE----- USERTrust RSA Certification Authority ===================================== -----BEGIN CERTIFICATE----- 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 Q+6IHdfGjjxDah2nGN59PRbxYvnKkKj9 -----END CERTIFICATE----- USERTrust ECC Certification Authority ===================================== -----BEGIN CERTIFICATE----- MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDELMAkGA1UEBhMC VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv biBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMC VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqfloI+d61SRvU8Za2EurxtW2 0eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinngo4N+LZfQYcTxmdwlkWOrfzCjtHDix6Ez nPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0GA1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNV HQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBB HU6+4WMBzzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbWRNZu 9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= -----END CERTIFICATE----- GlobalSign ECC Root CA - R5 =========================== -----BEGIN CERTIFICATE----- MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEkMCIGA1UECxMb R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD EwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoXDTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMb R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD EwpHbG9iYWxTaWduMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6 SFkc8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8kehOvRnkmS h5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd BgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYIKoZIzj0EAwMDaAAwZQIxAOVpEslu28Yx uglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7 yFz9SO8NdCKoCOJuxUnOxwy8p2Fp8fc74SrL+SvzZpA3 -----END CERTIFICATE----- IdenTrust Commercial Root CA 1 ============================== -----BEGIN CERTIFICATE----- MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBKMQswCQYDVQQG EwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBS b290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQwMTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzES MBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENB IDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ld hNlT3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU+ehcCuz/ mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gpS0l4PJNgiCL8mdo2yMKi 1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1bVoE/c40yiTcdCMbXTMTEl3EASX2MN0C XZ/g1Ue9tOsbobtJSdifWwLziuQkkORiT0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl 3ZBWzvurpWCdxJ35UrCLvYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzy NeVJSQjKVsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZKdHzV WYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHTc+XvvqDtMwt0viAg xGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hvl7yTmvmcEpB4eoCHFddydJxVdHix uuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5NiGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMC AQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZI hvcNAQELBQADggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH 6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwtLRvM7Kqas6pg ghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93nAbowacYXVKV7cndJZ5t+qnt ozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3+wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmV YjzlVYA211QC//G5Xc7UI2/YRYRKW2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUX feu+h1sXIFRRk0pTAwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/ro kTLql1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG4iZZRHUe 2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZmUlO+KWA2yUPHGNiiskz Z2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7R cGzM7vRX+Bi6hG6H -----END CERTIFICATE----- IdenTrust Public Sector Root CA 1 ================================= -----BEGIN CERTIFICATE----- MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBNMQswCQYDVQQG EwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3Rv ciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcNMzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJV UzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBS b290IENBIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTy P4o7ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGyRBb06tD6 Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlSbdsHyo+1W/CD80/HLaXI rcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF/YTLNiCBWS2ab21ISGHKTN9T0a9SvESf qy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoS mJxZZoY+rfGwyj4GD3vwEUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFn ol57plzy9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9VGxyh LrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ2fjXctscvG29ZV/v iDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsVWaFHVCkugyhfHMKiq3IXAAaOReyL 4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gDW/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8B Af8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMw DQYJKoZIhvcNAQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHVDRDtfULAj+7A mgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9TaDKQGXSc3z1i9kKlT/YPyNt GtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8GlwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFt m6/n6J91eEyrRjuazr8FGF1NFTwWmhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMx NRF4eKLg6TCMf4DfWN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4 Mhn5+bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJtshquDDI ajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhAGaQdp/lLQzfcaFpPz+vC ZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ 3Wl9af0AVqW3rLatt8o+Ae+c -----END CERTIFICATE----- CFCA EV ROOT ============ -----BEGIN CERTIFICATE----- MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJDTjEwMC4GA1UE CgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNB IEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkxMjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEw MC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQD DAxDRkNBIEVWIFJPT1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnV BU03sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpLTIpTUnrD 7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5/ZOkVIBMUtRSqy5J35DN uF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp7hZZLDRJGqgG16iI0gNyejLi6mhNbiyW ZXvKWfry4t3uMCz7zEasxGPrb382KzRzEpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7 xzbh72fROdOXW3NiGUgthxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9f py25IGvPa931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqotaK8K gWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNgTnYGmE69g60dWIol hdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfVPKPtl8MeNPo4+QgO48BdK4PRVmrJ tqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hvcWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAf BgNVHSMEGDAWgBTj/i39KNALtbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB /wQEAwIBBjAdBgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObTej/tUxPQ4i9q ecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdLjOztUmCypAbqTuv0axn96/Ua 4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBSESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sG E5uPhnEFtC+NiWYzKXZUmhH4J/qyP5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfX BDrDMlI1Dlb4pd19xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjn aH9dCi77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN5mydLIhy PDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe/v5WOaHIz16eGWRGENoX kbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+ZAAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3C ekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su -----END CERTIFICATE----- OISTE WISeKey Global Root GB CA =============================== -----BEGIN CERTIFICATE----- MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBtMQswCQYDVQQG EwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAw MzJaFw0zOTEyMDExNTEwMzFaMG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYD VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEds b2JhbCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3HEokKtaX scriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGxWuR51jIjK+FTzJlFXHtP rby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk 9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNku7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4o Qnc/nSMbsrY9gBQHTC5P99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvg GUpuuy9rM2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB /zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZI hvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrghcViXfa43FK8+5/ea4n32cZiZBKpD dHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0 VQreUGdNZtGn//3ZwLWoo4rOZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEui HZeeevJuQHHfaPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM= -----END CERTIFICATE----- SZAFIR ROOT CA2 =============== -----BEGIN CERTIFICATE----- MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQELBQAwUTELMAkG A1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6ZW5pb3dhIFMuQS4xGDAWBgNV BAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkwNzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJ BgNVBAYTAlBMMSgwJgYDVQQKDB9LcmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYD VQQDDA9TWkFGSVIgUk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5Q qEvNQLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT3PSQ1hNK DJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw3gAeqDRHu5rr/gsUvTaE 2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr63fE9biCloBK0TXC5ztdyO4mTp4CEHCdJ ckm1/zuVnsHMyAHs6A6KCpbns6aH5db5BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwi ieDhZNRnvDF5YTy7ykHNXGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P AQH/BAQDAgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsFAAOC AQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw8PRBEew/R40/cof5 O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOGnXkZ7/e7DDWQw4rtTw/1zBLZpD67 oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCPoky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul 4+vJhaAlIDf7js4MNIThPIGyd05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6 +/NNIxuZMzSgLvWpCz/UXeHPhJ/iGcJfitYgHuNztw== -----END CERTIFICATE----- Certum Trusted Network CA 2 =========================== -----BEGIN CERTIFICATE----- MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCBgDELMAkGA1UE BhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMuQS4xJzAlBgNVBAsTHkNlcnR1 bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29y ayBDQSAyMCIYDzIwMTExMDA2MDgzOTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQ TDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENl cnRpZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENB IDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWADGSdhhuWZGc/IjoedQF9 7/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+o CgCXhVqqndwpyeI1B+twTUrWwbNWuKFBOJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40b Rr5HMNUuctHFY9rnY3lEfktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2p uTRZCr+ESv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1mo130 GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02isx7QBlrd9pPPV3WZ 9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOWOZV7bIBaTxNyxtd9KXpEulKkKtVB Rgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgezTv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pye hizKV/Ma5ciSixqClnrDvFASadgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vM BhBgu4M1t15n3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZI hvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQF/xlhMcQSZDe28cmk4gmb3DW Al45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTfCVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuA L55MYIR4PSFk1vtBHxgP58l1cb29XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMo clm2q8KMZiYcdywmdjWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tM pkT/WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jbAoJnwTnb w3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksqP/ujmv5zMnHCnsZy4Ypo J/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Kob7a6bINDd82Kkhehnlt4Fj1F4jNy3eFm ypnTycUm/Q1oBEauttmbjL4ZvrHG8hnjXALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLX is7VmFxWlgPF7ncGNf/P5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7 zAYspsbiDrW5viSP -----END CERTIFICATE----- Hellenic Academic and Research Institutions RootCA 2015 ======================================================= -----BEGIN CERTIFICATE----- MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1IxDzANBgNVBAcT BkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0 aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl YXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAx MTIxWjCBpjELMAkGA1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMg QWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNV BAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIw MTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDC+Kk/G4n8PDwEXT2QNrCROnk8Zlrv bTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+eh iGsxr/CL0BgzuNtFajT0AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+ 6PAQZe104S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06CojXd FPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV9Cz82XBST3i4vTwr i5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrDgfgXy5I2XdGj2HUb4Ysn6npIQf1F GQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2 fu/Z8VFRfS0myGlZYeCsargqNhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9mu iNX6hME6wGkoLfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVdctA4GGqd83EkVAswDQYJKoZI hvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0IXtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+ D1hYc2Ryx+hFjtyp8iY/xnmMsVMIM4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrM d/K4kPFox/la/vot9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+y d+2VZ5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/eaj8GsGsVn 82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnhX9izjFk0WaSrT2y7Hxjb davYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQl033DlZdwJVqwjbDG2jJ9SrcR5q+ss7F Jej6A7na+RZukYT1HCjI/CbM1xyQVqdfbzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVt J94Cj8rDtSvK6evIIVM4pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGa JI7ZjnHKe7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0vm9q p/UsQu0yrbYhnr68 -----END CERTIFICATE----- Hellenic Academic and Research Institutions ECC RootCA 2015 =========================================================== -----BEGIN CERTIFICATE----- MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzANBgNVBAcTBkF0 aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9u cyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJj aCBJbnN0aXR1dGlvbnMgRUNDIFJvb3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEw MzcxMlowgaoxCzAJBgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmlj IEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUQwQgYD VQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIEVDQyBSb290 Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKgQehLgoRc4vgxEZmGZE4JJS+dQS8KrjVP dJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJajq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoK Vlp8aQuqgAkkbH7BRqNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O BBYEFLQiC4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaeplSTA GiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7SofTUwJCA3sS61kFyjn dc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR -----END CERTIFICATE----- ISRG Root X1 ============ -----BEGIN CERTIFICATE----- MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAwTzELMAkGA1UE BhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2VhcmNoIEdyb3VwMRUwEwYDVQQD EwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQG EwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMT DElTUkcgUm9vdCBYMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54r Vygch77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+0TM8ukj1 3Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6UA5/TR5d8mUgjU+g4rk8K b4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sWT8KOEUt+zwvo/7V3LvSye0rgTBIlDHCN Aymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyHB5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ 4Q7e2RCOFvu396j3x+UCB5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf 1b0SHzUvKBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWnOlFu hjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTnjh8BCNAw1FtxNrQH usEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbwqHyGO0aoSCqI3Haadr8faqU9GY/r OPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CIrU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4G A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY 9umbbjANBgkqhkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ3BebYhtF8GaV 0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KKNFtY2PwByVS5uCbMiogziUwt hDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJw TdwJx4nLCgdNbOhdjsnvzqvHu7UrTkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nx e5AW0wdeRlN8NwdCjNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZA JzVcoyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq4RgqsahD YVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPAmRGunUHBcnWEvgJBQl9n JEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57demyPxgcYxn/eR44/KJ4EBs+lVDR3veyJ m+kXQ99b21/+jh5Xos1AnX5iItreGCc= -----END CERTIFICATE----- AC RAIZ FNMT-RCM ================ -----BEGIN CERTIFICATE----- MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsxCzAJBgNVBAYT AkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTAeFw0wODEw MjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJD TTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC ggIBALpxgHpMhm5/yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcf qQgfBBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAzWHFctPVr btQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxFtBDXaEAUwED653cXeuYL j2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z374jNUUeAlz+taibmSXaXvMiwzn15Cou 08YfxGyqxRxqAQVKL9LFwag0Jl1mpdICIfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mw WsXmo8RZZUc1g16p6DULmbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnT tOmlcYF7wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peSMKGJ 47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2ZSysV4999AeU14EC ll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMetUqIJ5G+GR4of6ygnXYMgrwTJbFaa i0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE FPd9xf3E6Jobd2Sn9R2gzL+HYJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1o dHRwOi8vd3d3LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1RXxlDPiyN8+s D8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYMLVN0V2Ue1bLdI4E7pWYjJ2cJ j+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrT Qfv6MooqtyuGC2mDOL7Nii4LcK2NJpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW +YJF1DngoABd15jmfZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7 Ixjp6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp1txyM/1d 8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B9kiABdcPUXmsEKvU7ANm 5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wokRqEIr9baRRmW1FMdW4R58MD3R++Lj8UG rp1MYp3/RgT408m2ECVAdf4WqslKYIYvuu8wd+RU4riEmViAqhOLUTpPSPaLtrM= -----END CERTIFICATE----- Amazon Root CA 1 ================ -----BEGIN CERTIFICATE----- MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsFADA5MQswCQYD VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAxMB4XDTE1 MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC ggEBALJ4gHHKeNXjca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgH FzZM9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qwIFAGbHrQ gLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6VOujw5H5SNz/0egwLX0t dHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L93FcXmn/6pUCyziKrlA4b9v7LWIbxcce VOF34GfID5yHI9Y/QCB/IIDEgEw+OyQmjgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB /zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3 DQEBCwUAA4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDIU5PM CCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUsN+gDS63pYaACbvXy 8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vvo/ufQJVtMVT8QtPHRh8jrdkPSHCa 2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2 xJNDd2ZhwLnoQdeXeGADbkpyrqXRfboQnoZsG4q5WTP468SQvvG5 -----END CERTIFICATE----- Amazon Root CA 2 ================ -----BEGIN CERTIFICATE----- MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwFADA5MQswCQYD VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAyMB4XDTE1 MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC ggIBAK2Wny2cSkxKgXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4 kHbZW0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg1dKmSYXp N+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K8nu+NQWpEjTj82R0Yiw9 AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvd fLC6HM783k81ds8P+HgfajZRRidhW+mez/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAEx kv8LV/SasrlX6avvDXbR8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSS btqDT6ZjmUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz7Mt0 Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6+XUyo05f7O0oYtlN c/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI0u1ufm8/0i2BWSlmy5A5lREedCf+ 3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSw DPBMMPQFWAJI/TPlUq9LhONmUjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oA A7CXDpO8Wqj2LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY +gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kSk5Nrp+gvU5LE YFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl7uxMMne0nxrpS10gxdr9HIcW xkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygmbtmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQ gj9sAq+uEjonljYE1x2igGOpm/HlurR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbW aQbLU8uz/mtBzUF+fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoV Yh63n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE76KlXIx3 KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H9jVlpNMKVv/1F2Rs76gi JUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT4PsJYGw= -----END CERTIFICATE----- Amazon Root CA 3 ================ -----BEGIN CERTIFICATE----- MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5MQswCQYDVQQG EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAzMB4XDTE1MDUy NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZB f8ANm+gBG1bG8lKlui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjr Zt6jQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSrttvXBp43 rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkrBqWTrBqYaGFy+uGh0Psc eGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteMYyRIHN8wfdVoOw== -----END CERTIFICATE----- Amazon Root CA 4 ================ -----BEGIN CERTIFICATE----- MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5MQswCQYDVQQG EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSA0MB4XDTE1MDUy NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN /sGKe0uoe0ZLY7Bi9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri 83BkM6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV HQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WBMAoGCCqGSM49BAMDA2gA MGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlwCkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1 AE47xDqUEpHJWEadIRNyp4iciuRMStuW1KyLa2tJElMzrdfkviT8tQp21KW8EA== -----END CERTIFICATE----- TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 ============================================= -----BEGIN CERTIFICATE----- MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIxGDAWBgNVBAcT D0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxpbXNlbCB2ZSBUZWtub2xvamlr IEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0wKwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24g TWVya2V6aSAtIEthbXUgU00xNjA0BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRp ZmlrYXNpIC0gU3VydW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYD VQQGEwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXllIEJpbGlt c2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklUQUsxLTArBgNVBAsTJEth bXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBTTTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11 IFNNIFNTTCBLb2sgU2VydGlmaWthc2kgLSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A MIIBCgKCAQEAr3UwM6q7a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y8 6Ij5iySrLqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INrN3wc wv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2XYacQuFWQfw4tJzh0 3+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/iSIzL+aFCr2lqBs23tPcLG07xxO9 WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4fAJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQU ZT/HiobGPN08VFw1+DrtUgxHV8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJ KoZIhvcNAQELBQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPfIPP54+M638yc lNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4lzwDGrpDxpa5RXI4s6ehlj2R e37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0j q5Rm+K37DwhuJi1/FwcJsoz7UMCflo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM= -----END CERTIFICATE----- GDCA TrustAUTH R5 ROOT ====================== -----BEGIN CERTIFICATE----- MIIFiDCCA3CgAwIBAgIIfQmX/vBH6nowDQYJKoZIhvcNAQELBQAwYjELMAkGA1UEBhMCQ04xMjAw BgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZIENPLixMVEQuMR8wHQYDVQQD DBZHRENBIFRydXN0QVVUSCBSNSBST09UMB4XDTE0MTEyNjA1MTMxNVoXDTQwMTIzMTE1NTk1OVow YjELMAkGA1UEBhMCQ04xMjAwBgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZ IENPLixMVEQuMR8wHQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMIICIjANBgkqhkiG9w0B AQEFAAOCAg8AMIICCgKCAgEA2aMW8Mh0dHeb7zMNOwZ+Vfy1YI92hhJCfVZmPoiC7XJjDp6L3TQs AlFRwxn9WVSEyfFrs0yw6ehGXTjGoqcuEVe6ghWinI9tsJlKCvLriXBjTnnEt1u9ol2x8kECK62p OqPseQrsXzrj/e+APK00mxqriCZ7VqKChh/rNYmDf1+uKU49tm7srsHwJ5uu4/Ts765/94Y9cnrr pftZTqfrlYwiOXnhLQiPzLyRuEH3FMEjqcOtmkVEs7LXLM3GKeJQEK5cy4KOFxg2fZfmiJqwTTQJ 9Cy5WmYqsBebnh52nUpmMUHfP/vFBu8btn4aRjb3ZGM74zkYI+dndRTVdVeSN72+ahsmUPI2JgaQ xXABZG12ZuGR224HwGGALrIuL4xwp9E7PLOR5G62xDtw8mySlwnNR30YwPO7ng/Wi64HtloPzgsM R6flPri9fcebNaBhlzpBdRfMK5Z3KpIhHtmVdiBnaM8Nvd/WHwlqmuLMc3GkL30SgLdTMEZeS1SZ D2fJpcjyIMGC7J0R38IC+xo70e0gmu9lZJIQDSri3nDxGGeCjGHeuLzRL5z7D9Ar7Rt2ueQ5Vfj4 oR24qoAATILnsn8JuLwwoC8N9VKejveSswoAHQBUlwbgsQfZxw9cZX08bVlX5O2ljelAU58VS6Bx 9hoh49pwBiFYFIeFd3mqgnkCAwEAAaNCMEAwHQYDVR0OBBYEFOLJQJ9NzuiaoXzPDj9lxSmIahlR MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQDRSVfg p8xoWLoBDysZzY2wYUWsEe1jUGn4H3++Fo/9nesLqjJHdtJnJO29fDMylyrHBYZmDRd9FBUb1Ov9 H5r2XpdptxolpAqzkT9fNqyL7FeoPueBihhXOYV0GkLH6VsTX4/5COmSdI31R9KrO9b7eGZONn35 6ZLpBN79SWP8bfsUcZNnL0dKt7n/HipzcEYwv1ryL3ml4Y0M2fmyYzeMN2WFcGpcWwlyua1jPLHd +PwyvzeG5LuOmCd+uh8W4XAR8gPfJWIyJyYYMoSf/wA6E7qaTfRPuBRwIrHKK5DOKcFw9C+df/KQ HtZa37dG/OaG+svgIHZ6uqbL9XzeYqWxi+7egmaKTjowHz+Ay60nugxe19CxVsp3cbK1daFQqUBD F8Io2c9Si1vIY9RCPqAzekYu9wogRlR+ak8x8YF+QnQ4ZXMn7sZ8uI7XpTrXmKGcjBBV09tL7ECQ 8s1uV9JiDnxXk7Gnbc2dg7sq5+W2O3FYrf3RRbxake5TFW/TRQl1brqQXR4EzzffHqhmsYzmIGrv /EhOdJhCrylvLmrH+33RZjEizIYAfmaDDEL0vTSSwxrqT8p+ck0LcIymSLumoRT2+1hEmRSuqguT aaApJUqlyyvdimYHFngVV3Eb7PVHhPOeMTd61X8kreS8/f3MboPoDKi3QWwH3b08hpcv0g== -----END CERTIFICATE----- SSL.com Root Certification Authority RSA ======================================== -----BEGIN CERTIFICATE----- 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 WuuA8ShYIc2wBlX7Jz9TkHCpBB5XJ7k= -----END CERTIFICATE----- SSL.com Root Certification Authority ECC ======================================== -----BEGIN CERTIFICATE----- MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMCVVMxDjAMBgNV BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xMTAv BgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEy MTgxNDAzWhcNNDEwMjEyMTgxNDAzWjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAO BgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuBBAAiA2IA BEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI7Z4INcgn64mMU1jrYor+ 8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPgCemB+vNH06NjMGEwHQYDVR0OBBYEFILR hXMw5zUE044CkvvlpNHEIejNMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTT jgKS++Wk0cQh6M0wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCW e+0F+S8Tkdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+gA0z 5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl -----END CERTIFICATE----- SSL.com EV Root Certification Authority RSA R2 ============================================== -----BEGIN CERTIFICATE----- MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNVBAYTAlVTMQ4w DAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9u MTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy MB4XDTE3MDUzMTE4MTQzN1oXDTQyMDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQI DAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYD VQQDDC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMIICIjAN BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvqM0fNTPl9fb69LT3w23jh hqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssufOePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7w cXHswxzpY6IXFJ3vG2fThVUCAtZJycxa4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTO Zw+oz12WGQvE43LrrdF9HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+ B6KjBSYRaZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcAb9Zh CBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQGp8hLH94t2S42Oim 9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQVPWKchjgGAGYS5Fl2WlPAApiiECto RHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMOpgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+Slm JuwgUHfbSguPvuUCYHBBXtSuUDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48 +qvWBkofZ6aYMBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa49QaAJadz20Zp qJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBWs47LCp1Jjr+kxJG7ZhcFUZh1 ++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nx Y/hoLVUE0fKNsKTPvDxeH3jnpaAgcLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2G guDKBAdRUNf/ktUM79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDz OFSz/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXtll9ldDz7 CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEmKf7GUmG6sXP/wwyc5Wxq lD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKKQbNmC1r7fSOl8hqw/96bg5Qu0T/fkreR rwU7ZcegbLHNYhLDkBvjJc40vG93drEQw/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1 hlMYegouCRw2n5H9gooiS9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX 9hwJ1C07mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w== -----END CERTIFICATE----- SSL.com EV Root Certification Authority ECC =========================================== -----BEGIN CERTIFICATE----- MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMCVVMxDjAMBgNV BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xNDAy BgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYw MjEyMTgxNTIzWhcNNDEwMjEyMTgxNTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NM LmNvbSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB BAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMAVIbc/R/fALhBYlzccBYy 3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1KthkuWnBaBu2+8KGwytAJKaNjMGEwHQYDVR0O BBYEFFvKXuXe0oGqzagtZFG22XKbl+ZPMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe 5d7SgarNqC1kUbbZcpuX5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJ N+vp1RPZytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZgh5Mm m7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg== -----END CERTIFICATE----- GlobalSign Root CA - R6 ======================= -----BEGIN CERTIFICATE----- MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEgMB4GA1UECxMX R2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds b2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQxMjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9i YWxTaWduIFJvb3QgQ0EgLSBSNjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFs U2lnbjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQss grRIxutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1kZguSgMpE 3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxDaNc9PIrFsmbVkJq3MQbF vuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJwLnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqM PKq0pPbzlUoSB239jLKJz9CgYXfIWHSw1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+ azayOeSsJDa38O+2HBNXk7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05O WgtH8wY2SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/hbguy CLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4nWUx2OVvq+aWh2IMP 0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpYrZxCRXluDocZXFSxZba/jJvcE+kN b7gu3GduyYsRtYQUigAZcIN5kZeR1BonvzceMgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQE AwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNV HSMEGDAWgBSubAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGtIxg93eFyRJa0 lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr6155wsTLxDKZmOMNOsIeDjHfrY BzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLjvUYAGm0CuiVdjaExUd1URhxN25mW7xocBFym Fe944Hn+Xds+qkxV/ZoVqW/hpvvfcDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr 3TsTjxKM4kEaSHpzoHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB1 0jZpnOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfspA9MRf/T uTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+vJJUEeKgDu+6B5dpffItK oZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+t JDfLRVpOoERIyNiwmcUVhAn21klJwGW45hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA= -----END CERTIFICATE----- OISTE WISeKey Global Root GC CA =============================== -----BEGIN CERTIFICATE----- MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQswCQYDVQQGEwJD SDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNlZDEo MCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRa Fw00MjA1MDkwOTU4MzNaMG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQL ExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh bCBSb290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4nieUqjFqdr VCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4Wp2OQ0jnUsYd4XxiWD1Ab NTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd BgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7TrYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0E AwMDaAAwZQIwJsdpW9zV57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtk AjEA2zQgMgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9 -----END CERTIFICATE----- UCA Global G2 Root ================== -----BEGIN CERTIFICATE----- MIIFRjCCAy6gAwIBAgIQXd+x2lqj7V2+WmUgZQOQ7zANBgkqhkiG9w0BAQsFADA9MQswCQYDVQQG EwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxGzAZBgNVBAMMElVDQSBHbG9iYWwgRzIgUm9vdDAeFw0x NjAzMTEwMDAwMDBaFw00MDEyMzEwMDAwMDBaMD0xCzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlU cnVzdDEbMBkGA1UEAwwSVUNBIEdsb2JhbCBHMiBSb290MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A MIICCgKCAgEAxeYrb3zvJgUno4Ek2m/LAfmZmqkywiKHYUGRO8vDaBsGxUypK8FnFyIdK+35KYmT oni9kmugow2ifsqTs6bRjDXVdfkX9s9FxeV67HeToI8jrg4aA3++1NDtLnurRiNb/yzmVHqUwCoV 8MmNsHo7JOHXaOIxPAYzRrZUEaalLyJUKlgNAQLx+hVRZ2zA+te2G3/RVogvGjqNO7uCEeBHANBS h6v7hn4PJGtAnTRnvI3HLYZveT6OqTwXS3+wmeOwcWDcC/Vkw85DvG1xudLeJ1uK6NjGruFZfc8o LTW4lVYa8bJYS7cSN8h8s+1LgOGN+jIjtm+3SJUIsUROhYw6AlQgL9+/V087OpAh18EmNVQg7Mc/ R+zvWr9LesGtOxdQXGLYD0tK3Cv6brxzks3sx1DoQZbXqX5t2Okdj4q1uViSukqSKwxW/YDrCPBe KW4bHAyvj5OJrdu9o54hyokZ7N+1wxrrFv54NkzWbtA+FxyQF2smuvt6L78RHBgOLXMDj6DlNaBa 4kx1HXHhOThTeEDMg5PXCp6dW4+K5OXgSORIskfNTip1KnvyIvbJvgmRlld6iIis7nCs+dwp4wwc OxJORNanTrAmyPPZGpeRaOrvjUYG0lZFWJo8DA+DuAUlwznPO6Q0ibd5Ei9Hxeepl2n8pndntd97 8XplFeRhVmUCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O BBYEFIHEjMz15DD/pQwIX4wVZyF0Ad/fMA0GCSqGSIb3DQEBCwUAA4ICAQATZSL1jiutROTL/7lo 5sOASD0Ee/ojL3rtNtqyzm325p7lX1iPyzcyochltq44PTUbPrw7tgTQvPlJ9Zv3hcU2tsu8+Mg5 1eRfB70VVJd0ysrtT7q6ZHafgbiERUlMjW+i67HM0cOU2kTC5uLqGOiiHycFutfl1qnN3e92mI0A Ds0b+gO3joBYDic/UvuUospeZcnWhNq5NXHzJsBPd+aBJ9J3O5oUb3n09tDh05S60FdRvScFDcH9 yBIw7m+NESsIndTUv4BFFJqIRNow6rSn4+7vW4LVPtateJLbXDzz2K36uGt/xDYotgIVilQsnLAX c47QN6MUPJiVAAwpBVueSUmxX8fjy88nZY41F7dXyDDZQVu5FLbowg+UMaeUmMxq67XhJ/UQqAHo jhJi6IjMtX9Gl8CbEGY4GjZGXyJoPd/JxhMnq1MGrKI8hgZlb7F+sSlEmqO6SWkoaY/X5V+tBIZk bxqgDMUIYs6Ao9Dz7GjevjPHF1t/gMRMTLGmhIrDO7gJzRSBuhjjVFc2/tsvfEehOjPI+Vg7RE+x ygKJBJYoaMVLuCaJu9YzL1DV/pqJuhgyklTGW+Cd+V7lDSKb9triyCGyYiGqhkCyLmTTX8jjfhFn RR8F/uOi77Oos/N9j/gMHyIfLXC0uAE0djAA5SN4p1bXUB+K+wb1whnw0A== -----END CERTIFICATE----- UCA Extended Validation Root ============================ -----BEGIN CERTIFICATE----- MIIFWjCCA0KgAwIBAgIQT9Irj/VkyDOeTzRYZiNwYDANBgkqhkiG9w0BAQsFADBHMQswCQYDVQQG EwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9u IFJvb3QwHhcNMTUwMzEzMDAwMDAwWhcNMzgxMjMxMDAwMDAwWjBHMQswCQYDVQQGEwJDTjERMA8G A1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwggIi MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCpCQcoEwKwmeBkqh5DFnpzsZGgdT6o+uM4AHrs iWogD4vFsJszA1qGxliG1cGFu0/GnEBNyr7uaZa4rYEwmnySBesFK5pI0Lh2PpbIILvSsPGP2KxF Rv+qZ2C0d35qHzwaUnoEPQc8hQ2E0B92CvdqFN9y4zR8V05WAT558aopO2z6+I9tTcg1367r3CTu eUWnhbYFiN6IXSV8l2RnCdm/WhUFhvMJHuxYMjMR83dksHYf5BA1FxvyDrFspCqjc/wJHx4yGVMR 59mzLC52LqGj3n5qiAno8geK+LLNEOfic0CTuwjRP+H8C5SzJe98ptfRr5//lpr1kXuYC3fUfugH 0mK1lTnj8/FtDw5lhIpjVMWAtuCeS31HJqcBCF3RiJ7XwzJE+oJKCmhUfzhTA8ykADNkUVkLo4KR el7sFsLzKuZi2irbWWIQJUoqgQtHB0MGcIfS+pMRKXpITeuUx3BNr2fVUbGAIAEBtHoIppB/TuDv B0GHr2qlXov7z1CymlSvw4m6WC31MJixNnI5fkkE/SmnTHnkBVfblLkWU41Gsx2VYVdWf6/wFlth WG82UBEL2KwrlRYaDh8IzTY0ZRBiZtWAXxQgXy0MoHgKaNYs1+lvK9JKBZP8nm9rZ/+I8U6laUpS NwXqxhaN0sSZ0YIrO7o1dfdRUVjzyAfd5LQDfwIDAQABo0IwQDAdBgNVHQ4EFgQU2XQ65DA9DfcS 3H5aBZ8eNJr34RQwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQEL BQADggIBADaNl8xCFWQpN5smLNb7rhVpLGsaGvdftvkHTFnq88nIua7Mui563MD1sC3AO6+fcAUR ap8lTwEpcOPlDOHqWnzcSbvBHiqB9RZLcpHIojG5qtr8nR/zXUACE/xOHAbKsxSQVBcZEhrxH9cM aVr2cXj0lH2RC47skFSOvG+hTKv8dGT9cZr4QQehzZHkPJrgmzI5c6sq1WnIeJEmMX3ixzDx/BR4 dxIOE/TdFpS/S2d7cFOFyrC78zhNLJA5wA3CXWvp4uXViI3WLL+rG761KIcSF3Ru/H38j9CHJrAb +7lsq+KePRXBOy5nAliRn+/4Qh8st2j1da3Ptfb/EX3C8CSlrdP6oDyp+l3cpaDvRKS+1ujl5BOW F3sGPjLtx7dCvHaj2GU4Kzg1USEODm8uNBNA4StnDG1KQTAYI1oyVZnJF+A83vbsea0rWBmirSwi GpWOvpaQXUJXxPkUAzUrHC1RVwinOt4/5Mi0A3PCwSaAuwtCH60NryZy2sy+s6ODWA2CxR9GUeOc GMyNm43sSet1UNWMKFnKdDTajAshqx7qG+XH/RU+wBeq+yNuJkbL+vmxcmtpzyKEC2IPrNkZAJSi djzULZrtBJ4tBmIQN1IchXIbJ+XMxjHsN+xjWZsLHXbMfjKaiJUINlK73nZfdklJrX+9ZSCyycEr dhh2n1ax -----END CERTIFICATE----- Certigna Root CA ================ -----BEGIN CERTIFICATE----- MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAwWjELMAkGA1UE BhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAwMiA0ODE0NjMwODEwMDAzNjEZ MBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0xMzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjda MFoxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYz MDgxMDAwMzYxGTAXBgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4IC DwAwggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sOty3tRQgX stmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9MCiBtnyN6tMbaLOQdLNyz KNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPuI9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8 JXrJhFwLrN1CTivngqIkicuQstDuI7pmTLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16 XdG+RCYyKfHx9WzMfgIhC59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq 4NYKpkDfePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3YzIoej wpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWTCo/1VTp2lc5ZmIoJ lXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1kJWumIWmbat10TWuXekG9qxf5kBdI jzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp/ /TBt2dzhauH8XwIDAQABo4IBGjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw HQYDVR0OBBYEFBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of 1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczovL3d3d3cuY2Vy dGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilodHRwOi8vY3JsLmNlcnRpZ25h LmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYraHR0cDovL2NybC5kaGlteW90aXMuY29tL2Nl cnRpZ25hcm9vdGNhLmNybDANBgkqhkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOIt OoldaDgvUSILSo3L6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxP TGRGHVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH60BGM+RFq 7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncBlA2c5uk5jR+mUYyZDDl3 4bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdio2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd 8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS 6Cvu5zHbugRqh5jnxV/vfaci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaY tlu3zM63Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayhjWZS aX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw3kAP+HwV96LOPNde E4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0= -----END CERTIFICATE----- emSign Root CA - G1 =================== -----BEGIN CERTIFICATE----- MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYDVQQGEwJJTjET MBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRl ZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBHMTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgx ODMwMDBaMGcxCzAJBgNVBAYTAklOMRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVk aHJhIFRlY2hub2xvZ2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIB IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQzf2N4aLTN LnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO8oG0x5ZOrRkVUkr+PHB1 cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aqd7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHW DV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhMtTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ 6DqS0hdW5TUaQBw+jSztOd9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrH hQIDAQABo0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQDAgEG MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31xPaOfG1vR2vjTnGs2 vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjMwiI/aTvFthUvozXGaCocV685743Q NcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6dGNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q +Mri/Tm3R7nrft8EI6/6nAYH6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeih U80Bv2noWgbyRQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx iN66zB+Afko= -----END CERTIFICATE----- emSign ECC Root CA - G3 ======================= -----BEGIN CERTIFICATE----- MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQGEwJJTjETMBEG A1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEg MB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4 MTgzMDAwWjBrMQswCQYDVQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11 ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g RzMwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0WXTsuwYc 58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xySfvalY8L1X44uT6EYGQIr MgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuBzhccLikenEhjQjAOBgNVHQ8BAf8EBAMC AQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+D CBeQyh+KTOgNG3qxrdWBCUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7 jHvrZQnD+JbNR6iC8hZVdyR+EhCVBCyj -----END CERTIFICATE----- emSign Root CA - C1 =================== -----BEGIN CERTIFICATE----- MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkGA1UEBhMCVVMx EzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNp Z24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAwMFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UE BhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQD ExNlbVNpZ24gUm9vdCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+up ufGZBczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZHdPIWoU/ Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH3DspVpNqs8FqOp099cGX OFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvHGPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4V I5b2P/AgNBbeCsbEBEV5f6f9vtKppa+cxSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleooms lMuoaJuvimUnzYnu3Yy1aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+ XJGFehiqTbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQAD ggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87/kOXSTKZEhVb3xEp /6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4kqNPEjE2NuLe/gDEo2APJ62gsIq1 NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrGYQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9 wC68AivTxEDkigcxHpvOJpkT+xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQ BmIMMMAVSKeoWXzhriKi4gp6D/piq1JM4fHfyr6DDUI= -----END CERTIFICATE----- emSign ECC Root CA - C3 ======================= -----BEGIN CERTIFICATE----- MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQGEwJVUzETMBEG A1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMxIDAeBgNVBAMTF2VtU2lnbiBF Q0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAwMFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UE BhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQD ExdlbVNpZ24gRUNDIFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd 6bciMK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4OjavtisIGJAnB9 SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0OBBYEFPtaSNCAIEDyqOkA B2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2gA MGUCMQC02C8Cif22TGK6Q04ThHK1rt0c3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwU ZOR8loMRnLDRWmFLpg9J0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ== -----END CERTIFICATE----- Hongkong Post Root CA 3 ======================= -----BEGIN CERTIFICATE----- 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 dBb9HxEGmpv0 -----END CERTIFICATE----- Microsoft ECC Root Certificate Authority 2017 ============================================= -----BEGIN CERTIFICATE----- MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQgRUND IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4 MjMxNjA0WjBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw NAYDVQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQ BgcqhkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZRogPZnZH6 thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYbhGBKia/teQ87zvH2RPUB eMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTIy5lycFIM +Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlf Xu5gKcs68tvWMoQZP3zVL8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaR eNtUjGUBiudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M= -----END CERTIFICATE----- Microsoft RSA Root Certificate Authority 2017 ============================================= -----BEGIN CERTIFICATE----- MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBlMQswCQYDVQQG EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQg UlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIw NzE4MjMwMDIzWjBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u MTYwNAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcw ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZNt9GkMml 7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0ZdDMbRnMlfl7rEqUrQ7e S0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw7 1VdyvD/IybLeS2v4I2wDwAW9lcfNcztmgGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+ dkC0zVJhUXAoP8XFWvLJjEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49F yGcohJUcaDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaGYaRS MLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6W6IYZVcSn2i51BVr lMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4KUGsTuqwPN1q3ErWQgR5WrlcihtnJ 0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH+FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJ ClTUFLkqqNfs+avNJVgyeY+QW5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYw DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZCLgLNFgVZJ8og 6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OCgMNPOsduET/m4xaRhPtthH80 dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk +ONVFT24bcMKpBLBaYVu32TxU5nhSnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex /2kskZGT4d9Mozd2TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDy AmH3pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGRxpl/j8nW ZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiAppGWSZI1b7rCoucL5mxAyE 7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKT c0QWbej09+CVgI+WXTik9KveCjCHk9hNAHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D 5KbvtwEwXlGjefVwaaZBRA+GsCyRxj3qrg+E -----END CERTIFICATE----- e-Szigno Root CA 2017 ===================== -----BEGIN CERTIFICATE----- MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNVBAYTAkhVMREw DwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUt MjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJvb3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZa Fw00MjA4MjIxMjA3MDZaMHExCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UE CgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3pp Z25vIFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtvxie+RJCx s1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+HWyx7xf58etqjYzBhMA8G A1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSHERUI0arBeAyxr87GyZDv vzAEwDAfBgNVHSMEGDAWgBSHERUI0arBeAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEA tVfd14pVCzbhhkT61NlojbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxO svxyqltZ+efcMQ== -----END CERTIFICATE----- certSIGN Root CA G2 =================== -----BEGIN CERTIFICATE----- MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNVBAYTAlJPMRQw EgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04gUk9PVCBDQSBHMjAeFw0xNzAy MDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJBgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lH TiBTQTEcMBoGA1UECxMTY2VydFNJR04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIP ADCCAgoCggIBAMDFdRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05 N0IwvlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZuIt4Imfk abBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhpn+Sc8CnTXPnGFiWeI8Mg wT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKscpc/I1mbySKEwQdPzH/iV8oScLumZfNp dWO9lfsbl83kqK/20U6o2YpxJM02PbyWxPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91Qqh ngLjYl/rNUssuHLoPj1PrCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732 jcZZroiFDsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fxDTvf 95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgyLcsUDFDYg2WD7rlc z8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6CeWRgKRM+o/1Pcmqr4tTluCRVLERL iohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1Ud DgQWBBSCIS1mxteg4BXrzkwJd8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOB ywaK8SJJ6ejqkX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQlqiCA2ClV9+BB /AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0OJD7uNGzcgbJceaBxXntC6Z5 8hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+cNywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5 BiKDUyUM/FHE5r7iOZULJK2v0ZXkltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklW atKcsWMy5WHgUyIOpwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tU Sxfj03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZkPuXaTH4M NMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE1LlSVHJ7liXMvGnjSG4N 0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MXQRBdJ3NghVdJIgc= -----END CERTIFICATE----- NAVER Global Root Certification Authority ========================================= -----BEGIN CERTIFICATE----- MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEMBQAwaTELMAkG A1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRGT1JNIENvcnAuMTIwMAYDVQQD DClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MTgwODU4 NDJaFw0zNzA4MTgyMzU5NTlaMGkxCzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVT UyBQTEFURk9STSBDb3JwLjEyMDAGA1UEAwwpTkFWRVIgR2xvYmFsIFJvb3QgQ2VydGlmaWNhdGlv biBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC21PGTXLVAiQqrDZBb UGOukJR0F0Vy1ntlWilLp1agS7gvQnXp2XskWjFlqxcX0TM62RHcQDaH38dq6SZeWYp34+hInDEW +j6RscrJo+KfziFTowI2MMtSAuXaMl3Dxeb57hHHi8lEHoSTGEq0n+USZGnQJoViAbbJAh2+g1G7 XNr4rRVqmfeSVPc0W+m/6imBEtRTkZazkVrd/pBzKPswRrXKCAfHcXLJZtM0l/aM9BhK4dA9WkW2 aacp+yPOiNgSnABIqKYPszuSjXEOdMWLyEz59JuOuDxp7W87UC9Y7cSw0BwbagzivESq2M0UXZR4 Yb8ObtoqvC8MC3GmsxY/nOb5zJ9TNeIDoKAYv7vxvvTWjIcNQvcGufFt7QSUqP620wbGQGHfnZ3z VHbOUzoBppJB7ASjjw2i1QnK1sua8e9DXcCrpUHPXFNwcMmIpi3Ua2FzUCaGYQ5fG8Ir4ozVu53B A0K6lNpfqbDKzE0K70dpAy8i+/Eozr9dUGWokG2zdLAIx6yo0es+nPxdGoMuK8u180SdOqcXYZai cdNwlhVNt0xz7hlcxVs+Qf6sdWA7G2POAN3aCJBitOUt7kinaxeZVL6HSuOpXgRM6xBtVNbv8ejy YhbLgGvtPe31HzClrkvJE+2KAQHJuFFYwGY6sWZLxNUxAmLpdIQM201GLQIDAQABo0IwQDAdBgNV HQ4EFgQU0p+I36HNLL3s9TsBAZMzJ7LrYEswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB Af8wDQYJKoZIhvcNAQEMBQADggIBADLKgLOdPVQG3dLSLvCkASELZ0jKbY7gyKoNqo0hV4/GPnrK 21HUUrPUloSlWGB/5QuOH/XcChWB5Tu2tyIvCZwTFrFsDDUIbatjcu3cvuzHV+YwIHHW1xDBE1UB jCpD5EHxzzp6U5LOogMFDTjfArsQLtk70pt6wKGm+LUx5vR1yblTmXVHIloUFcd4G7ad6Qz4G3bx hYTeodoS76TiEJd6eN4MUZeoIUCLhr0N8F5OSza7OyAfikJW4Qsav3vQIkMsRIz75Sq0bBwcupTg E34h5prCy8VCZLQelHsIJchxzIdFV4XTnyliIoNRlwAYl3dqmJLJfGBs32x9SuRwTMKeuB330DTH D8z7p/8Dvq1wkNoL3chtl1+afwkyQf3NosxabUzyqkn+Zvjp2DXrDige7kgvOtB5CTh8piKCk5XQ A76+AqAF3SAi428diDRgxuYKuQl1C/AH6GmWNcf7I4GOODm4RStDeKLRLBT/DShycpWbXgnbiUSY qqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oG I/hGoiLtk/bdmuYqh7GYVPEi92tF4+KOdh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmg kpzNNIaRkPpkUZ3+/uul9XXeifdy -----END CERTIFICATE----- AC RAIZ FNMT-RCM SERVIDORES SEGUROS =================================== -----BEGIN CERTIFICATE----- MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQswCQYDVQQGEwJF UzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgwFgYDVQRhDA9WQVRFUy1RMjgy NjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1SQ00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4 MTIyMDA5MzczM1oXDTQzMTIyMDA5MzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQt UkNNMQ4wDAYDVQQLDAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNB QyBSQUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuBBAAiA2IA BPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LHsbI6GA60XYyzZl2hNPk2 LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oKUm8BA06Oi6NCMEAwDwYDVR0TAQH/BAUw AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqG SM49BAMDA2kAMGYCMQCuSuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoD zBOQn5ICMQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJyv+c= -----END CERTIFICATE----- GlobalSign Root R46 =================== -----BEGIN CERTIFICATE----- MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUAMEYxCzAJBgNV BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJv b3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAX BgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIi MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08Es CVeJOaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQGvGIFAha/ r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud316HCkD7rRlr+/fKYIje 2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo0q3v84RLHIf8E6M6cqJaESvWJ3En7YEt bWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSEy132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvj K8Cd+RTyG/FWaha/LIWFzXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD4 12lPFzYE+cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCNI/on ccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzsx2sZy/N78CsHpdls eVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqaByFrgY/bxFn63iLABJzjqls2k+g9 vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYD VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEM BQADggIBAHx47PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg JuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti2kM3S+LGteWy gxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIkpnnpHs6i58FZFZ8d4kuaPp92 CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRFFRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZm OUdkLG5NrmJ7v2B0GbhWrJKsFjLtrWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qq JZ4d16GLuc1CLgSkZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwye qiv5u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP4vkYxboz nxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6N3ec592kD3ZDZopD8p/7 DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3vouXsXgxT7PntgMTzlSdriVZzH81Xwj3 QEUxeCp6 -----END CERTIFICATE----- GlobalSign Root E46 =================== -----BEGIN CERTIFICATE----- MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYxCzAJBgNVBAYT AkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3Qg RTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNV BAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcq hkjOPQIBBgUrgQQAIgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkB jtjqR+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGddyXqBPCCj QjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQxCpCPtsad0kRL gLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZk vLtoURMMA/cVi4RguYv/Uo7njLwcAjA8+RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+ CAezNIm8BZ/3Hobui3A= -----END CERTIFICATE----- ANF Secure Server Root CA ========================= -----BEGIN CERTIFICATE----- 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 -----END CERTIFICATE----- Certum EC-384 CA ================ -----BEGIN CERTIFICATE----- MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQswCQYDVQQGEwJQ TDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2Vy dGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwHhcNMTgwMzI2 MDcyNDU0WhcNNDMwMzI2MDcyNDU0WjB0MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERh dGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkx GTAXBgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATEKI6rGFtq vm5kN2PkzeyrOvfMobgOgknXhimfoZTy42B4mIF4Bk3y7JoOV2CDn7TmFy8as10CW4kjPMIRBSqn iBMY81CE1700LCeJVf/OTOffph8oxPBUw7l8t1Ot68KjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD VR0OBBYEFI0GZnQkdjrzife81r1HfS+8EF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNo ADBlAjADVS2m5hjEfO/JUG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0 QoSZ/6vnnvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k= -----END CERTIFICATE----- Certum Trusted Root CA ====================== -----BEGIN CERTIFICATE----- MIIFwDCCA6igAwIBAgIQHr9ZULjJgDdMBvfrVU+17TANBgkqhkiG9w0BAQ0FADB6MQswCQYDVQQG EwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0g Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0Ew HhcNMTgwMzE2MTIxMDEzWhcNNDMwMzE2MTIxMDEzWjB6MQswCQYDVQQGEwJQTDEhMB8GA1UEChMY QXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBB dXRob3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEB AQUAA4ICDwAwggIKAoICAQDRLY67tzbqbTeRn06TpwXkKQMlzhyC93yZn0EGze2jusDbCSzBfN8p fktlL5On1AFrAygYo9idBcEq2EXxkd7fO9CAAozPOA/qp1x4EaTByIVcJdPTsuclzxFUl6s1wB52 HO8AU5853BSlLCIls3Jy/I2z5T4IHhQqNwuIPMqw9MjCoa68wb4pZ1Xi/K1ZXP69VyywkI3C7Te2 fJmItdUDmj0VDT06qKhF8JVOJVkdzZhpu9PMMsmN74H+rX2Ju7pgE8pllWeg8xn2A1bUatMn4qGt g/BKEiJ3HAVz4hlxQsDsdUaakFjgao4rpUYwBI4Zshfjvqm6f1bxJAPXsiEodg42MEx51UGamqi4 NboMOvJEGyCI98Ul1z3G4z5D3Yf+xOr1Uz5MZf87Sst4WmsXXw3Hw09Omiqi7VdNIuJGmj8PkTQk fVXjjJU30xrwCSss0smNtA0Aq2cpKNgB9RkEth2+dv5yXMSFytKAQd8FqKPVhJBPC/PgP5sZ0jeJ P/J7UhyM9uH3PAeXjA6iWYEMspA90+NZRu0PqafegGtaqge2Gcu8V/OXIXoMsSt0Puvap2ctTMSY njYJdmZm/Bo/6khUHL4wvYBQv3y1zgD2DGHZ5yQD4OMBgQ692IU0iL2yNqh7XAjlRICMb/gv1SHK HRzQ+8S1h9E6Tsd2tTVItQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSM+xx1 vALTn04uSNn5YFSqxLNP+jAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQENBQADggIBAEii1QAL LtA/vBzVtVRJHlpr9OTy4EA34MwUe7nJ+jW1dReTagVphZzNTxl4WxmB82M+w85bj/UvXgF2Ez8s ALnNllI5SW0ETsXpD4YN4fqzX4IS8TrOZgYkNCvozMrnadyHncI013nR03e4qllY/p0m+jiGPp2K h2RX5Rc64vmNueMzeMGQ2Ljdt4NR5MTMI9UGfOZR0800McD2RrsLrfw9EAUqO0qRJe6M1ISHgCq8 CYyqOhNf6DR5UMEQGfnTKB7U0VEwKbOukGfWHwpjscWpxkIxYxeU72nLL/qMFH3EQxiJ2fAyQOaA 4kZf5ePBAFmo+eggvIksDkc0C+pXwlM2/KfUrzHN/gLldfq5Jwn58/U7yn2fqSLLiMmq0Uc9Nneo WWRrJ8/vJ8HjJLWG965+Mk2weWjROeiQWMODvA8s1pfrzgzhIMfatz7DP78v3DSk+yshzWePS/Tj 6tQ/50+6uaWTRRxmHyH6ZF5v4HaUMst19W7l9o/HuKTMqJZ9ZPskWkoDbGs4xugDQ5r3V7mzKWmT OPQD8rv7gmsHINFSH5pkAnuYZttcTVoP0ISVoDwUQwbKytu4QTbaakRnh6+v40URFWkIsr4WOZck bxJF0WddCajJFdr60qZfE2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb -----END CERTIFICATE----- TunTrust Root CA ================ -----BEGIN CERTIFICATE----- MIIFszCCA5ugAwIBAgIUEwLV4kBMkkaGFmddtLu7sms+/BMwDQYJKoZIhvcNAQELBQAwYTELMAkG A1UEBhMCVE4xNzA1BgNVBAoMLkFnZW5jZSBOYXRpb25hbGUgZGUgQ2VydGlmaWNhdGlvbiBFbGVj dHJvbmlxdWUxGTAXBgNVBAMMEFR1blRydXN0IFJvb3QgQ0EwHhcNMTkwNDI2MDg1NzU2WhcNNDQw NDI2MDg1NzU2WjBhMQswCQYDVQQGEwJUTjE3MDUGA1UECgwuQWdlbmNlIE5hdGlvbmFsZSBkZSBD ZXJ0aWZpY2F0aW9uIEVsZWN0cm9uaXF1ZTEZMBcGA1UEAwwQVHVuVHJ1c3QgUm9vdCBDQTCCAiIw DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMPN0/y9BFPdDCA61YguBUtB9YOCfvdZn56eY+hz 2vYGqU8ftPkLHzmMmiDQfgbU7DTZhrx1W4eI8NLZ1KMKsmwb60ksPqxd2JQDoOw05TDENX37Jk0b bjBU2PWARZw5rZzJJQRNmpA+TkBuimvNKWfGzC3gdOgFVwpIUPp6Q9p+7FuaDmJ2/uqdHYVy7BG7 NegfJ7/Boce7SBbdVtfMTqDhuazb1YMZGoXRlJfXyqNlC/M4+QKu3fZnz8k/9YosRxqZbwUN/dAd gjH8KcwAWJeRTIAAHDOFli/LQcKLEITDCSSJH7UP2dl3RxiSlGBcx5kDPP73lad9UKGAwqmDrViW VSHbhlnUr8a83YFuB9tgYv7sEG7aaAH0gxupPqJbI9dkxt/con3YS7qC0lH4Zr8GRuR5KiY2eY8f Tpkdso8MDhz/yV3A/ZAQprE38806JG60hZC/gLkMjNWb1sjxVj8agIl6qeIbMlEsPvLfe/ZdeikZ juXIvTZxi11Mwh0/rViizz1wTaZQmCXcI/m4WEEIcb9PuISgjwBUFfyRbVinljvrS5YnzWuioYas DXxU5mZMZl+QviGaAkYt5IPCgLnPSz7ofzwB7I9ezX/SKEIBlYrilz0QIX32nRzFNKHsLA4KUiwS VXAkPcvCFDVDXSdOvsC9qnyW5/yeYa1E0wCXAgMBAAGjYzBhMB0GA1UdDgQWBBQGmpsfU33x9aTI 04Y+oXNZtPdEITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFAaamx9TffH1pMjThj6hc1m0 90QhMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAqgVutt0Vyb+zxiD2BkewhpMl 0425yAA/l/VSJ4hxyXT968pk21vvHl26v9Hr7lxpuhbI87mP0zYuQEkHDVneixCwSQXi/5E/S7fd Ao74gShczNxtr18UnH1YeA32gAm56Q6XKRm4t+v4FstVEuTGfbvE7Pi1HE4+Z7/FXxttbUcoqgRY YdZ2vyJ/0Adqp2RT8JeNnYA/u8EH22Wv5psymsNUk8QcCMNE+3tjEUPRahphanltkE8pjkcFwRJp adbGNjHh/PqAulxPxOu3Mqz4dWEX1xAZufHSCe96Qp1bWgvUxpVOKs7/B9dPfhgGiPEZtdmYu65x xBzndFlY7wyJz4sfdZMaBBSSSFCp61cpABbjNhzI+L/wM9VBD8TMPN3pM0MBkRArHtG5Xc0yGYuP jCB31yLEQtyEFpslbei0VXF/sHyz03FJuc9SpAQ/3D2gu68zngowYI7bnV2UqL1g52KAdoGDDIzM MEZJ4gzSqK/rYXHv5yJiqfdcZGyfFoxnNidF9Ql7v/YQCvGwjVRDjAS6oz/v4jXH+XTgbzRB0L9z ZVcg+ZtnemZoJE6AZb0QmQZZ8mWvuMZHu/2QeItBcy6vVR/cO5JyboTT0GFMDcx2V+IthSIVNg3r AZ3r2OvEhJn7wAzMMujjd9qDRIueVSjAi1jTkD5OGwDxFa2DK5o= -----END CERTIFICATE----- HARICA TLS RSA Root CA 2021 =========================== -----BEGIN CERTIFICATE----- MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQG EwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9u cyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0EgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTUz OFoXDTQ1MDIxMzEwNTUzN1owbDELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRl bWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgUlNB IFJvb3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIvC569lmwVnlskN JLnQDmT8zuIkGCyEf3dRywQRNrhe7Wlxp57kJQmXZ8FHws+RFjZiPTgE4VGC/6zStGndLuwRo0Xu a2s7TL+MjaQenRG56Tj5eg4MmOIjHdFOY9TnuEFE+2uva9of08WRiFukiZLRgeaMOVig1mlDqa2Y Ulhu2wr7a89o+uOkXjpFc5gH6l8Cct4MpbOfrqkdtx2z/IpZ525yZa31MJQjB/OCFks1mJxTuy/K 5FrZx40d/JiZ+yykgmvwKh+OC19xXFyuQnspiYHLA6OZyoieC0AJQTPb5lh6/a6ZcMBaD9YThnEv dmn8kN3bLW7R8pv1GmuebxWMevBLKKAiOIAkbDakO/IwkfN4E8/BPzWr8R0RI7VDIp4BkrcYAuUR 0YLbFQDMYTfBKnya4dC6s1BG7oKsnTH4+yPiAwBIcKMJJnkVU2DzOFytOOqBAGMUuTNe3QvboEUH GjMJ+E20pwKmafTCWQWIZYVWrkvL4N48fS0ayOn7H6NhStYqE613TBoYm5EPWNgGVMWX+Ko/IIqm haZ39qb8HOLubpQzKoNQhArlT4b4UEV4AIHrW2jjJo3Me1xR9BQsQL4aYB16cmEdH2MtiKrOokWQ CPxrvrNQKlr9qEgYRtaQQJKQCoReaDH46+0N0x3GfZkYVVYnZS6NRcUk7M7jAgMBAAGjQjBAMA8G A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFApII6ZgpJIKM+qTW8VX6iVNvRLuMA4GA1UdDwEB/wQE AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAPpBIqm5iFSVmewzVjIuJndftTgfvnNAUX15QvWiWkKQU EapobQk1OUAJ2vQJLDSle1mESSmXdMgHHkdt8s4cUCbjnj1AUz/3f5Z2EMVGpdAgS1D0NTsY9FVq QRtHBmg8uwkIYtlfVUKqrFOFrJVWNlar5AWMxajaH6NpvVMPxP/cyuN+8kyIhkdGGvMA9YCRotxD QpSbIPDRzbLrLFPCU3hKTwSUQZqPJzLB5UkZv/HywouoCjkxKLR9YjYsTewfM7Z+d21+UPCfDtcR j88YxeMn/ibvBZ3PzzfF0HvaO7AWhAw6k9a+F9sPPg4ZeAnHqQJyIkv3N3a6dcSFA1pj1bF1BcK5 vZStjBWZp5N99sXzqnTPBIWUmAD04vnKJGW/4GKvyMX6ssmeVkjaef2WdhW+o45WxLM0/L5H9MG0 qPzVMIho7suuyWPEdr6sOBjhXlzPrjoiUevRi7PzKzMHVIf6tLITe7pTBGIBnfHAT+7hOtSLIBD6 Alfm78ELt5BGnBkpjNxvoEppaZS3JGWg/6w/zgH7IS79aPib8qXPMThcFarmlwDB31qlpzmq6YR/ PFGoOtmUW4y/Twhx5duoXNTSpv4Ao8YWxw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnn kf3/W9b3raYvAwtt41dU63ZTGI0RmLo= -----END CERTIFICATE----- HARICA TLS ECC Root CA 2021 =========================== -----BEGIN CERTIFICATE----- MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQswCQYDVQQGEwJH UjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBD QTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTExMDExMFoX DTQ1MDIxMzExMDEwOVowbDELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWlj IGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgRUNDIFJv b3QgQ0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDgI/rGgltJ6rK9JOtDA4MM7KKrxcm1l AEeIhPyaJmuqS7psBAqIXhfyVYf8MLA04jRYVxqEU+kw2anylnTDUR9YSTHMmE5gEYd103KUkE+b ECUqqHgtvpBBWJAVcqeht6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUyRtTgRL+BNUW 0aq8mm+3oJUZbsowDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2cAMGQCMBHervjcToiwqfAi rcJRQO9gcS3ujwLEXQNwSaSS6sUUiHCm0w2wqsosQJz76YJumgIwK0eaB8bRwoF8yguWGEEbo/Qw CZ61IygNnxS2PFOiTAZpffpskcYqSUXm7LcT4Tps -----END CERTIFICATE----- Autoridad de Certificacion Firmaprofesional CIF A62634068 ========================================================= -----BEGIN CERTIFICATE----- MIIGFDCCA/ygAwIBAgIIG3Dp0v+ubHEwDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCRVMxQjBA BgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2 MjYzNDA2ODAeFw0xNDA5MjMxNTIyMDdaFw0zNjA1MDUxNTIyMDdaMFExCzAJBgNVBAYTAkVTMUIw QAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBB NjI2MzQwNjgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDD Utd9thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQMcas9UX4P B99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefGL9ItWY16Ck6WaVICqjaY 7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15iNA9wBj4gGFrO93IbJWyTdBSTo3OxDqqH ECNZXyAFGUftaI6SEspd/NYrspI8IM/hX68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyI plD9amML9ZMWGxmPsu2bm8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctX MbScyJCyZ/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirjaEbsX LZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/TKI8xWVvTyQKmtFLK bpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF6NkBiDkal4ZkQdU7hwxu+g/GvUgU vzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVhOSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMB0GA1Ud DgQWBBRlzeurNR4APn7VdMActHNHDhpkLzASBgNVHRMBAf8ECDAGAQH/AgEBMIGmBgNVHSAEgZ4w gZswgZgGBFUdIAAwgY8wLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuZmlybWFwcm9mZXNpb25hbC5j b20vY3BzMFwGCCsGAQUFBwICMFAeTgBQAGEAcwBlAG8AIABkAGUAIABsAGEAIABCAG8AbgBhAG4A bwB2AGEAIAA0ADcAIABCAGEAcgBjAGUAbABvAG4AYQAgADAAOAAwADEANzAOBgNVHQ8BAf8EBAMC AQYwDQYJKoZIhvcNAQELBQADggIBAHSHKAIrdx9miWTtj3QuRhy7qPj4Cx2Dtjqn6EWKB7fgPiDL 4QjbEwj4KKE1soCzC1HA01aajTNFSa9J8OA9B3pFE1r/yJfY0xgsfZb43aJlQ3CTkBW6kN/oGbDb LIpgD7dvlAceHabJhfa9NPhAeGIQcDq+fUs5gakQ1JZBu/hfHAsdCPKxsIl68veg4MSPi3i1O1il I45PVf42O+AMt8oqMEEgtIDNrvx2ZnOorm7hfNoD6JQg5iKj0B+QXSBTFCZX2lSX3xZEEAEeiGaP cjiT3SC3NL7X8e5jjkd5KAb881lFJWAiMxujX6i6KtoaPc1A6ozuBRWV1aUsIC+nmCjuRfzxuIgA LI9C2lHVnOUTaHFFQ4ueCyE8S1wF3BqfmI7avSKecs2tCsvMo2ebKHTEm9caPARYpoKdrcd7b/+A lun4jWq9GJAd/0kakFI3ky88Al2CdgtR5xbHV/g4+afNmyJU72OwFW1TZQNKXkqgsqeOSQBZONXH 9IBk9W6VULgRfhVwOEqwf9DEMnDAGf/JOC0ULGb0QkTmVXYbgBVX/8Cnp6o5qtjTcNAuuuuUavpf NIbnYrX9ivAwhZTJryQCL2/W3Wf+47BVTwSYT6RBVuKT0Gro1vP7ZeDOdcQxWQzugsgMYDNKGbqE ZycPvEJdvSRUDewdcAZfpLz6IHxV -----END CERTIFICATE----- vTrus ECC Root CA ================= -----BEGIN CERTIFICATE----- MIICDzCCAZWgAwIBAgIUbmq8WapTvpg5Z6LSa6Q75m0c1towCgYIKoZIzj0EAwMwRzELMAkGA1UE BhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBS b290IENBMB4XDTE4MDczMTA3MjY0NFoXDTQzMDczMTA3MjY0NFowRzELMAkGA1UEBhMCQ04xHDAa BgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBSb290IENBMHYw EAYHKoZIzj0CAQYFK4EEACIDYgAEZVBKrox5lkqqHAjDo6LN/llWQXf9JpRCux3NCNtzslt188+c ToL0v/hhJoVs1oVbcnDS/dtitN9Ti72xRFhiQgnH+n9bEOf+QP3A2MMrMudwpremIFUde4BdS49n TPEQo0IwQDAdBgNVHQ4EFgQUmDnNvtiyjPeyq+GtJK97fKHbH88wDwYDVR0TAQH/BAUwAwEB/zAO BgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwMDaAAwZQIwV53dVvHH4+m4SVBrm2nDb+zDfSXkV5UT QJtS0zvzQBm8JsctBp61ezaf9SXUY2sAAjEA6dPGnlaaKsyh2j/IZivTWJwghfqrkYpwcBE4YGQL YgmRWAD5Tfs0aNoJrSEGGJTO -----END CERTIFICATE----- vTrus Root CA ============= -----BEGIN CERTIFICATE----- MIIFVjCCAz6gAwIBAgIUQ+NxE9izWRRdt86M/TX9b7wFjUUwDQYJKoZIhvcNAQELBQAwQzELMAkG A1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xFjAUBgNVBAMTDXZUcnVzIFJv b3QgQ0EwHhcNMTgwNzMxMDcyNDA1WhcNNDMwNzMxMDcyNDA1WjBDMQswCQYDVQQGEwJDTjEcMBoG A1UEChMTaVRydXNDaGluYSBDby4sTHRkLjEWMBQGA1UEAxMNdlRydXMgUm9vdCBDQTCCAiIwDQYJ KoZIhvcNAQEBBQADggIPADCCAgoCggIBAL1VfGHTuB0EYgWgrmy3cLRB6ksDXhA/kFocizuwZots SKYcIrrVQJLuM7IjWcmOvFjai57QGfIvWcaMY1q6n6MLsLOaXLoRuBLpDLvPbmyAhykUAyyNJJrI ZIO1aqwTLDPxn9wsYTwaP3BVm60AUn/PBLn+NvqcwBauYv6WTEN+VRS+GrPSbcKvdmaVayqwlHeF XgQPYh1jdfdr58tbmnDsPmcF8P4HCIDPKNsFxhQnL4Z98Cfe/+Z+M0jnCx5Y0ScrUw5XSmXX+6KA YPxMvDVTAWqXcoKv8R1w6Jz1717CbMdHflqUhSZNO7rrTOiwCcJlwp2dCZtOtZcFrPUGoPc2BX70 kLJrxLT5ZOrpGgrIDajtJ8nU57O5q4IikCc9Kuh8kO+8T/3iCiSn3mUkpF3qwHYw03dQ+A0Em5Q2 AXPKBlim0zvc+gRGE1WKyURHuFE5Gi7oNOJ5y1lKCn+8pu8fA2dqWSslYpPZUxlmPCdiKYZNpGvu /9ROutW04o5IWgAZCfEF2c6Rsffr6TlP9m8EQ5pV9T4FFL2/s1m02I4zhKOQUqqzApVg+QxMaPnu 1RcN+HFXtSXkKe5lXa/R7jwXC1pDxaWG6iSe4gUH3DRCEpHWOXSuTEGC2/KmSNGzm/MzqvOmwMVO 9fSddmPmAsYiS8GVP1BkLFTltvA8Kc9XAgMBAAGjQjBAMB0GA1UdDgQWBBRUYnBj8XWEQ1iO0RYg scasGrz2iTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOC AgEAKbqSSaet8PFww+SX8J+pJdVrnjT+5hpk9jprUrIQeBqfTNqK2uwcN1LgQkv7bHbKJAs5EhWd nxEt/Hlk3ODg9d3gV8mlsnZwUKT+twpw1aA08XXXTUm6EdGz2OyC/+sOxL9kLX1jbhd47F18iMjr jld22VkE+rxSH0Ws8HqA7Oxvdq6R2xCOBNyS36D25q5J08FsEhvMKar5CKXiNxTKsbhm7xqC5PD4 8acWabfbqWE8n/Uxy+QARsIvdLGx14HuqCaVvIivTDUHKgLKeBRtRytAVunLKmChZwOgzoy8sHJn xDHO2zTlJQNgJXtxmOTAGytfdELSS8VZCAeHvsXDf+eW2eHcKJfWjwXj9ZtOyh1QRwVTsMo554Wg icEFOwE30z9J4nfrI8iIZjs9OXYhRvHsXyO466JmdXTBQPfYaJqT4i2pLr0cox7IdMakLXogqzu4 sEb9b91fUlV1YvCXoHzXOP0l382gmxDPi7g4Xl7FtKYCNqEeXxzP4padKar9mK5S4fNBUvupLnKW nyfjqnN9+BojZns7q2WwMgFLFT49ok8MKzWixtlnEjUwzXYuFrOZnk1PTi07NEPhmg4NpGaXutIc SkwsKouLgU9xGqndXHt7CMUADTdA43x7VF8vhV929vensBxXVsFy6K2ir40zSbofitzmdHxghm+H l3s= -----END CERTIFICATE----- ISRG Root X2 ============ -----BEGIN CERTIFICATE----- MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQswCQYDVQQGEwJV UzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElT UkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVT MSkwJwYDVQQKEyBJbnRlcm5ldCBTZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNS RyBSb290IFgyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0H ttwW+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9ItgKbppb d9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV HQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZIzj0EAwMDaAAwZQIwe3lORlCEwkSHRhtF cP9Ymd70/aTSVaYgLXTWNLxBo1BfASdWtL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5 U6VR5CmD1/iQMVtCnwr1/q4AaOeMSQ+2b1tbFfLn -----END CERTIFICATE----- HiPKI Root CA - G1 ================== -----BEGIN CERTIFICATE----- MIIFajCCA1KgAwIBAgIQLd2szmKXlKFD6LDNdmpeYDANBgkqhkiG9w0BAQsFADBPMQswCQYDVQQG EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xGzAZBgNVBAMMEkhpUEtJ IFJvb3QgQ0EgLSBHMTAeFw0xOTAyMjIwOTQ2MDRaFw0zNzEyMzExNTU5NTlaME8xCzAJBgNVBAYT AlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEbMBkGA1UEAwwSSGlQS0kg Um9vdCBDQSAtIEcxMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA9B5/UnMyDHPkvRN0 o9QwqNCuS9i233VHZvR85zkEHmpwINJaR3JnVfSl6J3VHiGh8Ge6zCFovkRTv4354twvVcg3Px+k wJyz5HdcoEb+d/oaoDjq7Zpy3iu9lFc6uux55199QmQ5eiY29yTw1S+6lZgRZq2XNdZ1AYDgr/SE YYwNHl98h5ZeQa/rh+r4XfEuiAU+TCK72h8q3VJGZDnzQs7ZngyzsHeXZJzA9KMuH5UHsBffMNsA GJZMoYFL3QRtU6M9/Aes1MU3guvklQgZKILSQjqj2FPseYlgSGDIcpJQ3AOPgz+yQlda22rpEZfd hSi8MEyr48KxRURHH+CKFgeW0iEPU8DtqX7UTuybCeyvQqww1r/REEXgphaypcXTT3OUM3ECoWqj 1jOXTyFjHluP2cFeRXF3D4FdXyGarYPM+l7WjSNfGz1BryB1ZlpK9p/7qxj3ccC2HTHsOyDry+K4 9a6SsvfhhEvyovKTmiKe0xRvNlS9H15ZFblzqMF8b3ti6RZsR1pl8w4Rm0bZ/W3c1pzAtH2lsN0/ Vm+h+fbkEkj9Bn8SV7apI09bA8PgcSojt/ewsTu8mL3WmKgMa/aOEmem8rJY5AIJEzypuxC00jBF 8ez3ABHfZfjcK0NVvxaXxA/VLGGEqnKG/uY6fsI/fe78LxQ+5oXdUG+3Se0CAwEAAaNCMEAwDwYD VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ncX+l6o/vY9cdVouslGDDjYr7AwDgYDVR0PAQH/BAQD AgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBQUfB13HAE4/+qddRxosuej6ip0691x1TPOhwEmSKsxBHi 7zNKpiMdDg1H2DfHb680f0+BazVP6XKlMeJ45/dOlBhbQH3PayFUhuaVevvGyuqcSE5XCV0vrPSl tJczWNWseanMX/mF+lLFjfiRFOs6DRfQUsJ748JzjkZ4Bjgs6FzaZsT0pPBWGTMpWmWSBUdGSquE wx4noR8RkpkndZMPvDY7l1ePJlsMu5wP1G4wB9TcXzZoZjmDlicmisjEOf6aIW/Vcobpf2Lll07Q JNBAsNB1CI69aO4I1258EHBGG3zgiLKecoaZAeO/n0kZtCW+VmWuF2PlHt/o/0elv+EmBYTksMCv 5wiZqAxeJoBF1PhoL5aPruJKHJwWDBNvOIf2u8g0X5IDUXlwpt/L9ZlNec1OvFefQ05rLisY+Gpz jLrFNe85akEez3GoorKGB1s6yeHvP2UEgEcyRHCVTjFnanRbEEV16rCf0OY1/k6fi8wrkkVbbiVg hUbN0aqwdmaTd5a+g744tiROJgvM7XpWGuDpWsZkrUx6AEhEL7lAuxM+vhV4nYWBSipX3tUZQ9rb yltHhoMLP7YNdnhzeSJesYAfz77RP1YQmCuVh6EfnWQUYDksswBVLuT1sw5XxJFBAJw/6KXf6vb/ yPCtbVKoF6ubYfwSUTXkJf2vqmqGOQ== -----END CERTIFICATE----- GlobalSign ECC Root CA - R4 =========================== -----BEGIN CERTIFICATE----- MIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYDVQQLExtHbG9i YWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds b2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgwMTE5MDMxNDA3WjBQMSQwIgYDVQQLExtHbG9i YWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds b2JhbFNpZ24wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAS4xnnTj2wlDp8uORkcA6SumuU5BwkW ymOxuYb4ilfBV85C+nOh92VC/x7BALJucw7/xyHlGKSq2XE/qNS5zowdo0IwQDAOBgNVHQ8BAf8E BAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/+wpu+74zyTyjhNUwCgYI KoZIzj0EAwIDRwAwRAIgIk90crlgr/HmnKAWBVBfw147bmF0774BxL4YSFlhgjICICadVGNA3jdg UM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm -----END CERTIFICATE----- GTS Root R1 =========== -----BEGIN CERTIFICATE----- MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQGEwJV UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg UjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0G CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaM f/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7wCl7raKb0 xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjwTcLCeoiKu7rPWRnWr4+w B7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0PfyblqAj+lug8aJRT7oM6iCsVlgmy4HqMLnXW nOunVmSPlk9orj2XwoSPwLxAwAtcvfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk 9+aCEI3oncKKiPo4Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zq kUspzBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92wO1A K/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70paDPvOmbsB4om3xPX V2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrNVjzRlwW5y0vtOUucxD/SVRNuJLDW cfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0T AQH/BAUwAwEB/zAdBgNVHQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQAD ggIBAJ+qQibbC5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuyh6f88/qBVRRi ClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM47HLwEXWdyzRSjeZ2axfG34ar J45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8JZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYci NuaCp+0KueIHoI17eko8cdLiA6EfMgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5me LMFrUKTX5hgUvYU/Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJF fbdT6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ0E6yove+ 7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm2tIMPNuzjsmhDYAPexZ3 FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bbbP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3 gm3c -----END CERTIFICATE----- GTS Root R3 =========== -----BEGIN CERTIFICATE----- MIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJVUzEi MCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMw HhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZ R29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjO PQIBBgUrgQQAIgNiAAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout 736GjOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL24CejQjBA MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTB8Sa6oC2uhYHP0/Eq Er24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEA9uEglRR7VKOQFhG/hMjqb2sXnh5GmCCbn9MN2azT L818+FsuVbu/3ZL3pAzcMeGiAjEA/JdmZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV 11RZt+cRLInUue4X -----END CERTIFICATE----- GTS Root R4 =========== -----BEGIN CERTIFICATE----- MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJVUzEi MCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQw HhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZ R29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjO PQIBBgUrgQQAIgNiAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzu hXyiQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvRHYqjQjBA MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSATNbrdP9JNqPV2Py1 PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/C r8deVl5c1RxYIigL9zC2L7F8AjEA8GE8p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh 4rsUecrNIdSUtUlD -----END CERTIFICATE----- Telia Root CA v2 ================ -----BEGIN CERTIFICATE----- MIIFdDCCA1ygAwIBAgIPAWdfJ9b+euPkrL4JWwWeMA0GCSqGSIb3DQEBCwUAMEQxCzAJBgNVBAYT AkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2 MjAeFw0xODExMjkxMTU1NTRaFw00MzExMjkxMTU1NTRaMEQxCzAJBgNVBAYTAkZJMRowGAYDVQQK DBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2MjCCAiIwDQYJKoZI hvcNAQEBBQADggIPADCCAgoCggIBALLQPwe84nvQa5n44ndp586dpAO8gm2h/oFlH0wnrI4AuhZ7 6zBqAMCzdGh+sq/H1WKzej9Qyow2RCRj0jbpDIX2Q3bVTKFgcmfiKDOlyzG4OiIjNLh9vVYiQJ3q 9HsDrWj8soFPmNB06o3lfc1jw6P23pLCWBnglrvFxKk9pXSW/q/5iaq9lRdU2HhE8Qx3FZLgmEKn pNaqIJLNwaCzlrI6hEKNfdWV5Nbb6WLEWLN5xYzTNTODn3WhUidhOPFZPY5Q4L15POdslv5e2QJl tI5c0BE0312/UqeBAMN/mUWZFdUXyApT7GPzmX3MaRKGwhfwAZ6/hLzRUssbkmbOpFPlob/E2wnW 5olWK8jjfN7j/4nlNW4o6GwLI1GpJQXrSPjdscr6bAhR77cYbETKJuFzxokGgeWKrLDiKca5JLNr RBH0pUPCTEPlcDaMtjNXepUugqD0XBCzYYP2AgWGLnwtbNwDRm41k9V6lS/eINhbfpSQBGq6WT0E BXWdN6IOLj3rwaRSg/7Qa9RmjtzG6RJOHSpXqhC8fF6CfaamyfItufUXJ63RDolUK5X6wK0dmBR4 M0KGCqlztft0DbcbMBnEWg4cJ7faGND/isgFuvGqHKI3t+ZIpEYslOqodmJHixBTB0hXbOKSTbau BcvcwUpej6w9GU7C7WB1K9vBykLVAgMBAAGjYzBhMB8GA1UdIwQYMBaAFHKs5DN5qkWH9v2sHZ7W xy+G2CQ5MB0GA1UdDgQWBBRyrOQzeapFh/b9rB2e1scvhtgkOTAOBgNVHQ8BAf8EBAMCAQYwDwYD VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAoDtZpwmUPjaE0n4vOaWWl/oRrfxn83EJ 8rKJhGdEr7nv7ZbsnGTbMjBvZ5qsfl+yqwE2foH65IRe0qw24GtixX1LDoJt0nZi0f6X+J8wfBj5 tFJ3gh1229MdqfDBmgC9bXXYfef6xzijnHDoRnkDry5023X4blMMA8iZGok1GTzTyVR8qPAs5m4H eW9q4ebqkYJpCh3DflminmtGFZhb069GHWLIzoBSSRE/yQQSwxN8PzuKlts8oB4KtItUsiRnDe+C y748fdHif64W1lZYudogsYMVoe+KTTJvQS8TUoKU1xrBeKJR3Stwbbca+few4GeXVtt8YVMJAygC QMez2P2ccGrGKMOF6eLtGpOg3kuYooQ+BXcBlj37tCAPnHICehIv1aO6UXivKitEZU61/Qrowc15 h2Er3oBXRb9n8ZuRXqWk7FlIEA04x7D6w0RtBPV4UBySllva9bguulvP5fBqnUsvWHMtTy3EHD70 sz+rFQ47GUGKpMFXEmZxTPpT41frYpUJnlTd0cI8Vzy9OK2YZLe4A5pTVmBds9hCG1xLEooc6+t9 xnppxyd/pPiL8uSUZodL6ZQHCRJ5irLrdATczvREWeAWysUsWNc8e89ihmpQfTU2Zqf7N+cox9jQ raVplI/owd8k+BsHMYeB2F326CjYSlKArBPuUBQemMc= -----END CERTIFICATE----- D-TRUST BR Root CA 1 2020 ========================= -----BEGIN CERTIFICATE----- MIIC2zCCAmCgAwIBAgIQfMmPK4TX3+oPyWWa00tNljAKBggqhkjOPQQDAzBIMQswCQYDVQQGEwJE RTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEJSIFJvb3QgQ0EgMSAy MDIwMB4XDTIwMDIxMTA5NDUwMFoXDTM1MDIxMTA5NDQ1OVowSDELMAkGA1UEBhMCREUxFTATBgNV BAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDEgMjAyMDB2MBAG ByqGSM49AgEGBSuBBAAiA2IABMbLxyjR+4T1mu9CFCDhQ2tuda38KwOE1HaTJddZO0Flax7mNCq7 dPYSzuht56vkPE4/RAiLzRZxy7+SmfSk1zxQVFKQhYN4lGdnoxwJGT11NIXe7WB9xwy0QVK5buXu QqOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHOREKv/VbNafAkl1bK6CKBrqx9t MA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6gPKA6hjhodHRwOi8vY3JsLmQtdHJ1c3Qu bmV0L2NybC9kLXRydXN0X2JyX3Jvb3RfY2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVj dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwQlIlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxP PUQtVHJ1c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjOPQQD AwNpADBmAjEAlJAtE/rhY/hhY+ithXhUkZy4kzg+GkHaQBZTQgjKL47xPoFWwKrY7RjEsK70Pvom AjEA8yjixtsrmfu3Ubgko6SUeho/5jbiA1czijDLgsfWFBHVdWNbFJWcHwHP2NVypw87 -----END CERTIFICATE----- D-TRUST EV Root CA 1 2020 ========================= -----BEGIN CERTIFICATE----- MIIC2zCCAmCgAwIBAgIQXwJB13qHfEwDo6yWjfv/0DAKBggqhkjOPQQDAzBIMQswCQYDVQQGEwJE RTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEVWIFJvb3QgQ0EgMSAy MDIwMB4XDTIwMDIxMTEwMDAwMFoXDTM1MDIxMTA5NTk1OVowSDELMAkGA1UEBhMCREUxFTATBgNV BAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBFViBSb290IENBIDEgMjAyMDB2MBAG ByqGSM49AgEGBSuBBAAiA2IABPEL3YZDIBnfl4XoIkqbz52Yv7QFJsnL46bSj8WeeHsxiamJrSc8 ZRCC/N/DnU7wMyPE0jL1HLDfMxddxfCxivnvubcUyilKwg+pf3VlSSowZ/Rk99Yad9rDwpdhQntJ raOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFH8QARY3OqQo5FD4pPfsazK2/umL MA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6gPKA6hjhodHRwOi8vY3JsLmQtdHJ1c3Qu bmV0L2NybC9kLXRydXN0X2V2X3Jvb3RfY2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVj dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwRVYlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxP PUQtVHJ1c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjOPQQD AwNpADBmAjEAyjzGKnXCXnViOTYAYFqLwZOZzNnbQTs7h5kXO9XMT8oi96CAy/m0sRtW9XLS/BnR AjEAkfcwkz8QRitxpNA7RJvAKQIFskF3UfN5Wp6OFKBOQtJbgfM0agPnIjhQW+0ZT0MW -----END CERTIFICATE----- DigiCert TLS ECC P384 Root G5 ============================= -----BEGIN CERTIFICATE----- MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQswCQYDVQQGEwJV UzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURpZ2lDZXJ0IFRMUyBFQ0MgUDM4 NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2MDExNDIzNTk1OVowTjELMAkGA1UEBhMCVVMx FzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMSYwJAYDVQQDEx1EaWdpQ2VydCBUTFMgRUNDIFAzODQg Um9vdCBHNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABMFEoc8Rl1Ca3iOCNQfN0MsYndLxf3c1Tzvd lHJS7cI7+Oz6e2tYIOyZrsn8aLN1udsJ7MgT9U7GCh1mMEy7H0cKPGEQQil8pQgO4CLp0zVozptj n4S1mU1YoI71VOeVyaNCMEAwHQYDVR0OBBYEFMFRRVBZqz7nLFr6ICISB4CIfBFqMA4GA1UdDwEB /wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQCJao1H5+z8blUD2Wds Jk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQLgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIx AJSdYsiJvRmEFOml+wG4DXZDjC5Ty3zfDBeWUA== -----END CERTIFICATE----- DigiCert TLS RSA4096 Root G5 ============================ -----BEGIN CERTIFICATE----- MIIFZjCCA06gAwIBAgIQCPm0eKj6ftpqMzeJ3nzPijANBgkqhkiG9w0BAQwFADBNMQswCQYDVQQG EwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0 MDk2IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcNNDYwMTE0MjM1OTU5WjBNMQswCQYDVQQGEwJV UzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0MDk2 IFJvb3QgRzUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz0PTJeRGd/fxmgefM1eS8 7IE+ajWOLrfn3q/5B03PMJ3qCQuZvWxX2hhKuHisOjmopkisLnLlvevxGs3npAOpPxG02C+JFvuU AT27L/gTBaF4HI4o4EXgg/RZG5Wzrn4DReW+wkL+7vI8toUTmDKdFqgpwgscONyfMXdcvyej/Ces tyu9dJsXLfKB2l2w4SMXPohKEiPQ6s+d3gMXsUJKoBZMpG2T6T867jp8nVid9E6P/DsjyG244gXa zOvswzH016cpVIDPRFtMbzCe88zdH5RDnU1/cHAN1DrRN/BsnZvAFJNY781BOHW8EwOVfH/jXOnV DdXifBBiqmvwPXbzP6PosMH976pXTayGpxi0KcEsDr9kvimM2AItzVwv8n/vFfQMFawKsPHTDU9q TXeXAaDxZre3zu/O7Oyldcqs4+Fj97ihBMi8ez9dLRYiVu1ISf6nL3kwJZu6ay0/nTvEF+cdLvvy z6b84xQslpghjLSR6Rlgg/IwKwZzUNWYOwbpx4oMYIwo+FKbbuH2TbsGJJvXKyY//SovcfXWJL5/ MZ4PbeiPT02jP/816t9JXkGPhvnxd3lLG7SjXi/7RgLQZhNeXoVPzthwiHvOAbWWl9fNff2C+MIk wcoBOU+NosEUQB+cZtUMCUbW8tDRSHZWOkPLtgoRObqME2wGtZ7P6wIDAQABo0IwQDAdBgNVHQ4E FgQUUTMc7TZArxfTJc1paPKvTiM+s0EwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8w DQYJKoZIhvcNAQEMBQADggIBAGCmr1tfV9qJ20tQqcQjNSH/0GEwhJG3PxDPJY7Jv0Y02cEhJhxw GXIeo8mH/qlDZJY6yFMECrZBu8RHANmfGBg7sg7zNOok992vIGCukihfNudd5N7HPNtQOa27PShN lnx2xlv0wdsUpasZYgcYQF+Xkdycx6u1UQ3maVNVzDl92sURVXLFO4uJ+DQtpBflF+aZfTCIITfN MBc9uPK8qHWgQ9w+iUuQrm0D4ByjoJYJu32jtyoQREtGBzRj7TG5BO6jm5qu5jF49OokYTurWGT/ u4cnYiWB39yhL/btp/96j1EuMPikAdKFOV8BmZZvWltwGUb+hmA+rYAQCd05JS9Yf7vSdPD3Rh9G OUrYU9DzLjtxpdRv/PNn5AeP3SYZ4Y1b+qOTEZvpyDrDVWiakuFSdjjo4bq9+0/V77PnSIMx8IIh 47a+p6tv75/fTM8BuGJqIz3nCU2AG3swpMPdB380vqQmsvZB6Akd4yCYqjdP//fx4ilwMUc/dNAU FvohigLVigmUdy7yWSiLfFCSCmZ4OIN1xLVaqBHG5cGdZlXPU8Sv13WFqUITVuwhd4GTWgzqltlJ yqEI8pc7bZsEGCREjnwB8twl2F6GmrE52/WRMmrRpnCKovfepEWFJqgejF0pW8hL2JpqA15w8oVP bEtoL8pU9ozaMv7Da4M/OMZ+ -----END CERTIFICATE----- Certainly Root R1 ================= -----BEGIN CERTIFICATE----- MIIFRzCCAy+gAwIBAgIRAI4P+UuQcWhlM1T01EQ5t+AwDQYJKoZIhvcNAQELBQAwPTELMAkGA1UE BhMCVVMxEjAQBgNVBAoTCUNlcnRhaW5seTEaMBgGA1UEAxMRQ2VydGFpbmx5IFJvb3QgUjEwHhcN MjEwNDAxMDAwMDAwWhcNNDYwNDAxMDAwMDAwWjA9MQswCQYDVQQGEwJVUzESMBAGA1UEChMJQ2Vy dGFpbmx5MRowGAYDVQQDExFDZXJ0YWlubHkgUm9vdCBSMTCCAiIwDQYJKoZIhvcNAQEBBQADggIP ADCCAgoCggIBANA21B/q3avk0bbm+yLA3RMNansiExyXPGhjZjKcA7WNpIGD2ngwEc/csiu+kr+O 5MQTvqRoTNoCaBZ0vrLdBORrKt03H2As2/X3oXyVtwxwhi7xOu9S98zTm/mLvg7fMbedaFySpvXl 8wo0tf97ouSHocavFwDvA5HtqRxOcT3Si2yJ9HiG5mpJoM610rCrm/b01C7jcvk2xusVtyWMOvwl DbMicyF0yEqWYZL1LwsYpfSt4u5BvQF5+paMjRcCMLT5r3gajLQ2EBAHBXDQ9DGQilHFhiZ5shGI XsXwClTNSaa/ApzSRKft43jvRl5tcdF5cBxGX1HpyTfcX35pe0HfNEXgO4T0oYoKNp43zGJS4YkN KPl6I7ENPT2a/Z2B7yyQwHtETrtJ4A5KVpK8y7XdeReJkd5hiXSSqOMyhb5OhaRLWcsrxXiOcVTQ AjeZjOVJ6uBUcqQRBi8LjMFbvrWhsFNunLhgkR9Za/kt9JQKl7XsxXYDVBtlUrpMklZRNaBA2Cnb rlJ2Oy0wQJuK0EJWtLeIAaSHO1OWzaMWj/Nmqhexx2DgwUMFDO6bW2BvBlyHWyf5QBGenDPBt+U1 VwV/J84XIIwc/PH72jEpSe31C4SnT8H2TsIonPru4K8H+zMReiFPCyEQtkA6qyI6BJyLm4SGcprS p6XEtHWRqSsjAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud DgQWBBTgqj8ljZ9EXME66C6ud0yEPmcM9DANBgkqhkiG9w0BAQsFAAOCAgEAuVevuBLaV4OPaAsz HQNTVfSVcOQrPbA56/qJYv331hgELyE03fFo8NWWWt7CgKPBjcZq91l3rhVkz1t5BXdm6ozTaw3d 8VkswTOlMIAVRQdFGjEitpIAq5lNOo93r6kiyi9jyhXWx8bwPWz8HA2YEGGeEaIi1wrykXprOQ4v MMM2SZ/g6Q8CRFA3lFV96p/2O7qUpUzpvD5RtOjKkjZUbVwlKNrdrRT90+7iIgXr0PK3aBLXWopB GsaSpVo7Y0VPv+E6dyIvXL9G+VoDhRNCX8reU9ditaY1BMJH/5n9hN9czulegChB8n3nHpDYT3Y+ gjwN/KUD+nsa2UUeYNrEjvn8K8l7lcUq/6qJ34IxD3L/DCfXCh5WAFAeDJDBlrXYFIW7pw0WwfgH JBu6haEaBQmAupVjyTrsJZ9/nbqkRxWbRHDxakvWOF5D8xh+UG7pWijmZeZ3Gzr9Hb4DJqPb1OG7 fpYnKx3upPvaJVQTA945xsMfTZDsjxtK0hzthZU4UHlG1sGQUDGpXJpuHfUzVounmdLyyCwzk5Iw x06MZTMQZBf9JBeW0Y3COmor6xOLRPIh80oat3df1+2IpHLlOR+Vnb5nwXARPbv0+Em34yaXOp/S X3z7wJl8OSngex2/DaeP0ik0biQVy96QXr8axGbqwua6OV+KmalBWQewLK8= -----END CERTIFICATE----- Certainly Root E1 ================= -----BEGIN CERTIFICATE----- MIIB9zCCAX2gAwIBAgIQBiUzsUcDMydc+Y2aub/M+DAKBggqhkjOPQQDAzA9MQswCQYDVQQGEwJV UzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0YWlubHkgUm9vdCBFMTAeFw0yMTA0 MDEwMDAwMDBaFw00NjA0MDEwMDAwMDBaMD0xCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlDZXJ0YWlu bHkxGjAYBgNVBAMTEUNlcnRhaW5seSBSb290IEUxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3m/4 fxzf7flHh4axpMCK+IKXgOqPyEpeKn2IaKcBYhSRJHpcnqMXfYqGITQYUBsQ3tA3SybHGWCA6TS9 YBk2QNYphwk8kXr2vBMj3VlOBF7PyAIcGFPBMdjaIOlEjeR2o0IwQDAOBgNVHQ8BAf8EBAMCAQYw DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ygYy2R17ikq6+2uI1g4hevIIgcwCgYIKoZIzj0E AwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozmut6Dacpps6kFtZaSF4fC0urQe87YQVt8 rgIwRt7qy12a7DLCZRawTDBcMPPaTnOGBtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR -----END CERTIFICATE----- Security Communication ECC RootCA1 ================================== -----BEGIN CERTIFICATE----- MIICODCCAb6gAwIBAgIJANZdm7N4gS7rMAoGCCqGSM49BAMDMGExCzAJBgNVBAYTAkpQMSUwIwYD VQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSswKQYDVQQDEyJTZWN1cml0eSBDb21t dW5pY2F0aW9uIEVDQyBSb290Q0ExMB4XDTE2MDYxNjA1MTUyOFoXDTM4MDExODA1MTUyOFowYTEL MAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKzApBgNV BAMTIlNlY3VyaXR5IENvbW11bmljYXRpb24gRUNDIFJvb3RDQTEwdjAQBgcqhkjOPQIBBgUrgQQA IgNiAASkpW9gAwPDvTH00xecK4R1rOX9PVdu12O/5gSJko6BnOPpR27KkBLIE+CnnfdldB9sELLo 5OnvbYUymUSxXv3MdhDYW72ixvnWQuRXdtyQwjWpS4g8EkdtXP9JTxpKULGjQjBAMB0GA1UdDgQW BBSGHOf+LaVKiwj+KBH6vqNm+GBZLzAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAK BggqhkjOPQQDAwNoADBlAjAVXUI9/Lbu9zuxNuie9sRGKEkz0FhDKmMpzE2xtHqiuQ04pV1IKv3L snNdo4gIxwwCMQDAqy0Obe0YottT6SXbVQjgUMzfRGEWgqtJsLKB7HOHeLRMsmIbEvoWTSVLY70e N9k= -----END CERTIFICATE----- BJCA Global Root CA1 ==================== -----BEGIN CERTIFICATE----- MIIFdDCCA1ygAwIBAgIQVW9l47TZkGobCdFsPsBsIDANBgkqhkiG9w0BAQsFADBUMQswCQYDVQQG EwJDTjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRIT1JJVFkxHTAbBgNVBAMMFEJK Q0EgR2xvYmFsIFJvb3QgQ0ExMB4XDTE5MTIxOTAzMTYxN1oXDTQ0MTIxMjAzMTYxN1owVDELMAkG A1UEBhMCQ04xJjAkBgNVBAoMHUJFSUpJTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQD DBRCSkNBIEdsb2JhbCBSb290IENBMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAPFm CL3ZxRVhy4QEQaVpN3cdwbB7+sN3SJATcmTRuHyQNZ0YeYjjlwE8R4HyDqKYDZ4/N+AZspDyRhyS sTphzvq3Rp4Dhtczbu33RYx2N95ulpH3134rhxfVizXuhJFyV9xgw8O558dnJCNPYwpj9mZ9S1Wn P3hkSWkSl+BMDdMJoDIwOvqfwPKcxRIqLhy1BDPapDgRat7GGPZHOiJBhyL8xIkoVNiMpTAK+BcW yqw3/XmnkRd4OJmtWO2y3syJfQOcs4ll5+M7sSKGjwZteAf9kRJ/sGsciQ35uMt0WwfCyPQ10WRj eulumijWML3mG90Vr4TqnMfK9Q7q8l0ph49pczm+LiRvRSGsxdRpJQaDrXpIhRMsDQa4bHlW/KNn MoH1V6XKV0Jp6VwkYe/iMBhORJhVb3rCk9gZtt58R4oRTklH2yiUAguUSiz5EtBP6DF+bHq/pj+b OT0CFqMYs2esWz8sgytnOYFcuX6U1WTdno9uruh8W7TXakdI136z1C2OVnZOz2nxbkRs1CTqjSSh GL+9V/6pmTW12xB3uD1IutbB5/EjPtffhZ0nPNRAvQoMvfXnjSXWgXSHRtQpdaJCbPdzied9v3pK H9MiyRVVz99vfFXQpIsHETdfg6YmV6YBW37+WGgHqel62bno/1Afq8K0wM7o6v0PvY1NuLxxAgMB AAGjQjBAMB0GA1UdDgQWBBTF7+3M2I0hxkjk49cULqcWk+WYATAPBgNVHRMBAf8EBTADAQH/MA4G A1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAUoKsITQfI/Ki2Pm4rzc2IInRNwPWaZ+4 YRC6ojGYWUfo0Q0lHhVBDOAqVdVXUsv45Mdpox1NcQJeXyFFYEhcCY5JEMEE3KliawLwQ8hOnThJ dMkycFRtwUf8jrQ2ntScvd0g1lPJGKm1Vrl2i5VnZu69mP6u775u+2D2/VnGKhs/I0qUJDAnyIm8 60Qkmss9vk/Ves6OF8tiwdneHg56/0OGNFK8YT88X7vZdrRTvJez/opMEi4r89fO4aL/3Xtw+zuh TaRjAv04l5U/BXCga99igUOLtFkNSoxUnMW7gZ/NfaXvCyUeOiDbHPwfmGcCCtRzRBPbUYQaVQNW 4AB+dAb/OMRyHdOoP2gxXdMJxy6MW2Pg6Nwe0uxhHvLe5e/2mXZgLR6UcnHGCyoyx5JO1UbXHfmp GQrI+pXObSOYqgs4rZpWDW+N8TEAiMEXnM0ZNjX+VVOg4DwzX5Ze4jLp3zO7Bkqp2IRzznfSxqxx 4VyjHQy7Ct9f4qNx2No3WqB4K/TUfet27fJhcKVlmtOJNBir+3I+17Q9eVzYH6Eze9mCUAyTF6ps 3MKCuwJXNq+YJyo5UOGwifUll35HaBC07HPKs5fRJNz2YqAo07WjuGS3iGJCz51TzZm+ZGiPTx4S SPfSKcOYKMryMguTjClPPGAyzQWWYezyr/6zcCwupvI= -----END CERTIFICATE----- BJCA Global Root CA2 ==================== -----BEGIN CERTIFICATE----- MIICJTCCAaugAwIBAgIQLBcIfWQqwP6FGFkGz7RK6zAKBggqhkjOPQQDAzBUMQswCQYDVQQGEwJD TjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRIT1JJVFkxHTAbBgNVBAMMFEJKQ0Eg R2xvYmFsIFJvb3QgQ0EyMB4XDTE5MTIxOTAzMTgyMVoXDTQ0MTIxMjAzMTgyMVowVDELMAkGA1UE BhMCQ04xJjAkBgNVBAoMHUJFSUpJTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQDDBRC SkNBIEdsb2JhbCBSb290IENBMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABJ3LgJGNU2e1uVCxA/jl SR9BIgmwUVJY1is0j8USRhTFiy8shP8sbqjV8QnjAyEUxEM9fMEsxEtqSs3ph+B99iK++kpRuDCK /eHeGBIK9ke35xe/J4rUQUyWPGCWwf0VHKNCMEAwHQYDVR0OBBYEFNJKsVF/BvDRgh9Obl+rg/xI 1LCRMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gAMGUCMBq8 W9f+qdJUDkpd0m2xQNz0Q9XSSpkZElaA94M04TVOSG0ED1cxMDAtsaqdAzjbBgIxAMvMh1PLet8g UXOQwKhbYdDFUDn9hf7B43j4ptZLvZuHjw/l1lOWqzzIQNph91Oj9w== -----END CERTIFICATE----- Sectigo Public Server Authentication Root E46 ============================================= -----BEGIN CERTIFICATE----- MIICOjCCAcGgAwIBAgIQQvLM2htpN0RfFf51KBC49DAKBggqhkjOPQQDAzBfMQswCQYDVQQGEwJH QjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBTZXJ2 ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwHhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5 WjBfMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0 aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUr gQQAIgNiAAR2+pmpbiDt+dd34wc7qNs9Xzjoq1WmVk/WSOrsfy2qw7LFeeyZYX8QeccCWvkEN/U0 NSt3zn8gj1KjAIns1aeibVvjS5KToID1AZTc8GgHHs3u/iVStSBDHBv+6xnOQ6OjQjBAMB0GA1Ud DgQWBBTRItpMWfFLXyY4qp3W7usNw/upYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB /zAKBggqhkjOPQQDAwNnADBkAjAn7qRaqCG76UeXlImldCBteU/IvZNeWBj7LRoAasm4PdCkT0RH lAFWovgzJQxC36oCMB3q4S6ILuH5px0CMk7yn2xVdOOurvulGu7t0vzCAxHrRVxgED1cf5kDW21U SAGKcw== -----END CERTIFICATE----- Sectigo Public Server Authentication Root R46 ============================================= -----BEGIN CERTIFICATE----- MIIFijCCA3KgAwIBAgIQdY39i658BwD6qSWn4cetFDANBgkqhkiG9w0BAQwFADBfMQswCQYDVQQG EwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBT ZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwHhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1 OTU5WjBfMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1T ZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwggIiMA0GCSqGSIb3 DQEBAQUAA4ICDwAwggIKAoICAQCTvtU2UnXYASOgHEdCSe5jtrch/cSV1UgrJnwUUxDaef0rty2k 1Cz66jLdScK5vQ9IPXtamFSvnl0xdE8H/FAh3aTPaE8bEmNtJZlMKpnzSDBh+oF8HqcIStw+Kxwf GExxqjWMrfhu6DtK2eWUAtaJhBOqbchPM8xQljeSM9xfiOefVNlI8JhD1mb9nxc4Q8UBUQvX4yMP FF1bFOdLvt30yNoDN9HWOaEhUTCDsG3XME6WW5HwcCSrv0WBZEMNvSE6Lzzpng3LILVCJ8zab5vu ZDCQOc2TZYEhMbUjUDM3IuM47fgxMMxF/mL50V0yeUKH32rMVhlATc6qu/m1dkmU8Sf4kaWD5Qaz Yw6A3OASVYCmO2a0OYctyPDQ0RTp5A1NDvZdV3LFOxxHVp3i1fuBYYzMTYCQNFu31xR13NgESJ/A wSiItOkcyqex8Va3e0lMWeUgFaiEAin6OJRpmkkGj80feRQXEgyDet4fsZfu+Zd4KKTIRJLpfSYF plhym3kT2BFfrsU4YjRosoYwjviQYZ4ybPUHNs2iTG7sijbt8uaZFURww3y8nDnAtOFr94MlI1fZ EoDlSfB1D++N6xybVCi0ITz8fAr/73trdf+LHaAZBav6+CuBQug4urv7qv094PPK306Xlynt8xhW 6aWWrL3DkJiy4Pmi1KZHQ3xtzwIDAQABo0IwQDAdBgNVHQ4EFgQUVnNYZJX5khqwEioEYnmhQBWI IUkwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAC9c mTz8Bl6MlC5w6tIyMY208FHVvArzZJ8HXtXBc2hkeqK5Duj5XYUtqDdFqij0lgVQYKlJfp/imTYp E0RHap1VIDzYm/EDMrraQKFz6oOht0SmDpkBm+S8f74TlH7Kph52gDY9hAaLMyZlbcp+nv4fjFg4 exqDsQ+8FxG75gbMY/qB8oFM2gsQa6H61SilzwZAFv97fRheORKkU55+MkIQpiGRqRxOF3yEvJ+M 0ejf5lG5Nkc/kLnHvALcWxxPDkjBJYOcCj+esQMzEhonrPcibCTRAUH4WAP+JWgiH5paPHxsnnVI 84HxZmduTILA7rpXDhjvLpr3Etiga+kFpaHpaPi8TD8SHkXoUsCjvxInebnMMTzD9joiFgOgyY9m pFuiTdaBJQbpdqQACj7LzTWb4OE4y2BThihCQRxEV+ioratF4yUQvNs+ZUH7G6aXD+u5dHn5Hrwd Vw1Hr8Mvn4dGp+smWg9WY7ViYG4A++MnESLn/pmPNPW56MORcr3Ywx65LvKRRFHQV80MNNVIIb/b E/FmJUNS0nAiNs2fxBx1IK1jcmMGDw4nztJqDby1ORrp0XZ60Vzk50lJLVU3aPAaOpg+VBeHVOmm J1CJeyAvP/+/oYtKR5j/K3tJPsMpRmAYQqszKbrAKbkTidOIijlBO8n9pu0f9GBj39ItVQGL -----END CERTIFICATE----- SSL.com TLS RSA Root CA 2022 ============================ -----BEGIN CERTIFICATE----- MIIFiTCCA3GgAwIBAgIQb77arXO9CEDii02+1PdbkTANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQG EwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxTU0wuY29tIFRMUyBSU0Eg Um9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzQyMloXDTQ2MDgxOTE2MzQyMVowTjELMAkGA1UEBhMC VVMxGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgUlNBIFJv b3QgQ0EgMjAyMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANCkCXJPQIgSYT41I57u 9nTPL3tYPc48DRAokC+X94xI2KDYJbFMsBFMF3NQ0CJKY7uB0ylu1bUJPiYYf7ISf5OYt6/wNr/y 7hienDtSxUcZXXTzZGbVXcdotL8bHAajvI9AI7YexoS9UcQbOcGV0insS657Lb85/bRi3pZ7Qcac oOAGcvvwB5cJOYF0r/c0WRFXCsJbwST0MXMwgsadugL3PnxEX4MN8/HdIGkWCVDi1FW24IBydm5M R7d1VVm0U3TZlMZBrViKMWYPHqIbKUBOL9975hYsLfy/7PO0+r4Y9ptJ1O4Fbtk085zx7AGL0SDG D6C1vBdOSHtRwvzpXGk3R2azaPgVKPC506QVzFpPulJwoxJF3ca6TvvC0PeoUidtbnm1jPx7jMEW TO6Af77wdr5BUxIzrlo4QqvXDz5BjXYHMtWrifZOZ9mxQnUjbvPNQrL8VfVThxc7wDNY8VLS+YCk 8OjwO4s4zKTGkH8PnP2L0aPP2oOnaclQNtVcBdIKQXTbYxE3waWglksejBYSd66UNHsef8JmAOSq g+qKkK3ONkRN0VHpvB/zagX9wHQfJRlAUW7qglFA35u5CCoGAtUjHBPW6dvbxrB6y3snm/vg1UYk 7RBLY0ulBY+6uB0rpvqR4pJSvezrZ5dtmi2fgTIFZzL7SAg/2SW4BCUvAgMBAAGjYzBhMA8GA1Ud EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU+y437uOEeicuzRk1sTN8/9REQrkwHQYDVR0OBBYEFPsu N+7jhHonLs0ZNbEzfP/UREK5MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAjYlt hEUY8U+zoO9opMAdrDC8Z2awms22qyIZZtM7QbUQnRC6cm4pJCAcAZli05bg4vsMQtfhWsSWTVTN j8pDU/0quOr4ZcoBwq1gaAafORpR2eCNJvkLTqVTJXojpBzOCBvfR4iyrT7gJ4eLSYwfqUdYe5by iB0YrrPRpgqU+tvT5TgKa3kSM/tKWTcWQA673vWJDPFs0/dRa1419dvAJuoSc06pkZCmF8NsLzjU o3KUQyxi4U5cMj29TH0ZR6LDSeeWP4+a0zvkEdiLA9z2tmBVGKaBUfPhqBVq6+AL8BQx1rmMRTqo ENjwuSfr98t67wVylrXEj5ZzxOhWc5y8aVFjvO9nHEMaX3cZHxj4HCUp+UmZKbaSPaKDN7Egkaib MOlqbLQjk2UEqxHzDh1TJElTHaE/nUiSEeJ9DU/1172iWD54nR4fK/4huxoTtrEoZP2wAgDHbICi vRZQIA9ygV/MlP+7mea6kMvq+cYMwq7FGc4zoWtcu358NFcXrfA/rs3qr5nsLFR+jM4uElZI7xc7 P0peYNLcdDa8pUNjyw9bowJWCZ4kLOGGgYz+qxcs+sjiMho6/4UIyYOf8kpIEFR3N+2ivEC+5BB0 9+Rbu7nzifmPQdjH5FCQNYA+HLhNkNPU98OwoX6EyneSMSy4kLGCenROmxMmtNVQZlR4rmA= -----END CERTIFICATE----- SSL.com TLS ECC Root CA 2022 ============================ -----BEGIN CERTIFICATE----- MIICOjCCAcCgAwIBAgIQFAP1q/s3ixdAW+JDsqXRxDAKBggqhkjOPQQDAzBOMQswCQYDVQQGEwJV UzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxTU0wuY29tIFRMUyBFQ0MgUm9v dCBDQSAyMDIyMB4XDTIyMDgyNTE2MzM0OFoXDTQ2MDgxOTE2MzM0N1owTjELMAkGA1UEBhMCVVMx GDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgRUNDIFJvb3Qg Q0EgMjAyMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABEUpNXP6wrgjzhR9qLFNoFs27iosU8NgCTWy JGYmacCzldZdkkAZDsalE3D07xJRKF3nzL35PIXBz5SQySvOkkJYWWf9lCcQZIxPBLFNSeR7T5v1 5wj4A4j3p8OSSxlUgaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSJjy+j6CugFFR7 81a4Jl9nOAuc0DAdBgNVHQ4EFgQUiY8vo+groBRUe/NWuCZfZzgLnNAwDgYDVR0PAQH/BAQDAgGG MAoGCCqGSM49BAMDA2gAMGUCMFXjIlbp15IkWE8elDIPDAI2wv2sdDJO4fscgIijzPvX6yv/N33w 7deedWo1dlJF4AIxAMeNb0Igj762TVntd00pxCAgRWSGOlDGxK0tk/UYfXLtqc/ErFc2KAhl3zx5 Zn6g6g== -----END CERTIFICATE----- Atos TrustedRoot Root CA ECC TLS 2021 ===================================== -----BEGIN CERTIFICATE----- MIICFTCCAZugAwIBAgIQPZg7pmY9kGP3fiZXOATvADAKBggqhkjOPQQDAzBMMS4wLAYDVQQDDCVB dG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgRUNDIFRMUyAyMDIxMQ0wCwYDVQQKDARBdG9zMQswCQYD VQQGEwJERTAeFw0yMTA0MjIwOTI2MjNaFw00MTA0MTcwOTI2MjJaMEwxLjAsBgNVBAMMJUF0b3Mg VHJ1c3RlZFJvb3QgUm9vdCBDQSBFQ0MgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYT AkRFMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEloZYKDcKZ9Cg3iQZGeHkBQcfl+3oZIK59sRxUM6K DP/XtXa7oWyTbIOiaG6l2b4siJVBzV3dscqDY4PMwL502eCdpO5KTlbgmClBk1IQ1SQ4AjJn8ZQS b+/Xxd4u/RmAo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR2KCXWfeBmmnoJsmo7jjPX NtNPojAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMDaAAwZQIwW5kp85wxtolrbNa9d+F851F+ uDrNozZffPc8dz7kUK2o59JZDCaOMDtuCCrCp1rIAjEAmeMM56PDr9NJLkaCI2ZdyQAUEv049OGY a3cpetskz2VAv9LcjBHo9H1/IISpQuQo -----END CERTIFICATE----- Atos TrustedRoot Root CA RSA TLS 2021 ===================================== -----BEGIN CERTIFICATE----- MIIFZDCCA0ygAwIBAgIQU9XP5hmTC/srBRLYwiqipDANBgkqhkiG9w0BAQwFADBMMS4wLAYDVQQD DCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgUlNBIFRMUyAyMDIxMQ0wCwYDVQQKDARBdG9zMQsw CQYDVQQGEwJERTAeFw0yMTA0MjIwOTIxMTBaFw00MTA0MTcwOTIxMDlaMEwxLjAsBgNVBAMMJUF0 b3MgVHJ1c3RlZFJvb3QgUm9vdCBDQSBSU0EgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNV BAYTAkRFMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtoAOxHm9BYx9sKOdTSJNy/BB l01Z4NH+VoyX8te9j2y3I49f1cTYQcvyAh5x5en2XssIKl4w8i1mx4QbZFc4nXUtVsYvYe+W/CBG vevUez8/fEc4BKkbqlLfEzfTFRVOvV98r61jx3ncCHvVoOX3W3WsgFWZkmGbzSoXfduP9LVq6hdK ZChmFSlsAvFr1bqjM9xaZ6cF4r9lthawEO3NUDPJcFDsGY6wx/J0W2tExn2WuZgIWWbeKQGb9Cpt 0xU6kGpn8bRrZtkh68rZYnxGEFzedUlnnkL5/nWpo63/dgpnQOPF943HhZpZnmKaau1Fh5hnstVK PNe0OwANwI8f4UDErmwh3El+fsqyjW22v5MvoVw+j8rtgI5Y4dtXz4U2OLJxpAmMkokIiEjxQGMY sluMWuPD0xeqqxmjLBvk1cbiZnrXghmmOxYsL3GHX0WelXOTwkKBIROW1527k2gV+p2kHYzygeBY Br3JtuP2iV2J+axEoctr+hbxx1A9JNr3w+SH1VbxT5Aw+kUJWdo0zuATHAR8ANSbhqRAvNncTFd+ rrcztl524WWLZt+NyteYr842mIycg5kDcPOvdO3GDjbnvezBc6eUWsuSZIKmAMFwoW4sKeFYV+xa fJlrJaSQOoD0IJ2azsct+bJLKZWD6TWNp0lIpw9MGZHQ9b8Q4HECAwEAAaNCMEAwDwYDVR0TAQH/ BAUwAwEB/zAdBgNVHQ4EFgQUdEmZ0f+0emhFdcN+tNzMzjkz2ggwDgYDVR0PAQH/BAQDAgGGMA0G CSqGSIb3DQEBDAUAA4ICAQAjQ1MkYlxt/T7Cz1UAbMVWiLkO3TriJQ2VSpfKgInuKs1l+NsW4AmS 4BjHeJi78+xCUvuppILXTdiK/ORO/auQxDh1MoSf/7OwKwIzNsAQkG8dnK/haZPso0UvFJ/1TCpl Q3IM98P4lYsU84UgYt1UU90s3BiVaU+DR3BAM1h3Egyi61IxHkzJqM7F78PRreBrAwA0JrRUITWX AdxfG/F851X6LWh3e9NpzNMOa7pNdkTWwhWaJuywxfW70Xp0wmzNxbVe9kzmWy2B27O3Opee7c9G slA9hGCZcbUztVdF5kJHdWoOsAgMrr3e97sPWD2PAzHoPYJQyi9eDF20l74gNAf0xBLh7tew2Vkt afcxBPTy+av5EzH4AXcOPUIjJsyacmdRIXrMPIWo6iFqO9taPKU0nprALN+AnCng33eU0aKAQv9q TFsR0PXNor6uzFFcw9VUewyu1rkGd4Di7wcaaMxZUa1+XGdrudviB0JbuAEFWDlN5LuYo7Ey7Nmj 1m+UI/87tyll5gfp77YZ6ufCOB0yiJA8EytuzO+rdwY0d4RPcuSBhPm5dDTedk+SKlOxJTnbPP/l PqYO5Wue/9vsL3SD3460s6neFE3/MaNFcyT6lSnMEpcEoji2jbDwN/zIIX8/syQbPYtuzE2wFg2W HYMfRsCbvUOZ58SWLs5fyQ== -----END CERTIFICATE----- TrustAsia Global Root CA G3 =========================== -----BEGIN CERTIFICATE----- MIIFpTCCA42gAwIBAgIUZPYOZXdhaqs7tOqFhLuxibhxkw8wDQYJKoZIhvcNAQEMBQAwWjELMAkG A1UEBhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dpZXMsIEluYy4xJDAiBgNVBAMM G1RydXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHMzAeFw0yMTA1MjAwMjEwMTlaFw00NjA1MTkwMjEw MTlaMFoxCzAJBgNVBAYTAkNOMSUwIwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMu MSQwIgYDVQQDDBtUcnVzdEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzMwggIiMA0GCSqGSIb3DQEBAQUA A4ICDwAwggIKAoICAQDAMYJhkuSUGwoqZdC+BqmHO1ES6nBBruL7dOoKjbmzTNyPtxNST1QY4Sxz lZHFZjtqz6xjbYdT8PfxObegQ2OwxANdV6nnRM7EoYNl9lA+sX4WuDqKAtCWHwDNBSHvBm3dIZwZ Q0WhxeiAysKtQGIXBsaqvPPW5vxQfmZCHzyLpnl5hkA1nyDvP+uLRx+PjsXUjrYsyUQE49RDdT/V P68czH5GX6zfZBCK70bwkPAPLfSIC7Epqq+FqklYqL9joDiR5rPmd2jE+SoZhLsO4fWvieylL1Ag dB4SQXMeJNnKziyhWTXAyB1GJ2Faj/lN03J5Zh6fFZAhLf3ti1ZwA0pJPn9pMRJpxx5cynoTi+jm 9WAPzJMshH/x/Gr8m0ed262IPfN2dTPXS6TIi/n1Q1hPy8gDVI+lhXgEGvNz8teHHUGf59gXzhqc D0r83ERoVGjiQTz+LISGNzzNPy+i2+f3VANfWdP3kXjHi3dqFuVJhZBFcnAvkV34PmVACxmZySYg WmjBNb9Pp1Hx2BErW+Canig7CjoKH8GB5S7wprlppYiU5msTf9FkPz2ccEblooV7WIQn3MSAPmea mseaMQ4w7OYXQJXZRe0Blqq/DPNL0WP3E1jAuPP6Z92bfW1K/zJMtSU7/xxnD4UiWQWRkUF3gdCF TIcQcf+eQxuulXUtgQIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFEDk5PIj 7zjKsK5Xf/IhMBY027ySMB0GA1UdDgQWBBRA5OTyI+84yrCuV3/yITAWNNu8kjAOBgNVHQ8BAf8E BAMCAQYwDQYJKoZIhvcNAQEMBQADggIBACY7UeFNOPMyGLS0XuFlXsSUT9SnYaP4wM8zAQLpw6o1 D/GUE3d3NZ4tVlFEbuHGLige/9rsR82XRBf34EzC4Xx8MnpmyFq2XFNFV1pF1AWZLy4jVe5jaN/T G3inEpQGAHUNcoTpLrxaatXeL1nHo+zSh2bbt1S1JKv0Q3jbSwTEb93mPmY+KfJLaHEih6D4sTNj duMNhXJEIlU/HHzp/LgV6FL6qj6jITk1dImmasI5+njPtqzn59ZW/yOSLlALqbUHM/Q4X6RJpstl cHboCoWASzY9M/eVVHUl2qzEc4Jl6VL1XP04lQJqaTDFHApXB64ipCz5xUG3uOyfT0gA+QEEVcys +TIxxHWVBqB/0Y0n3bOppHKH/lmLmnp0Ft0WpWIp6zqW3IunaFnT63eROfjXy9mPX1onAX1daBli 2MjN9LdyR75bl87yraKZk62Uy5P2EgmVtqvXO9A/EcswFi55gORngS1d7XB4tmBZrOFdRWOPyN9y aFvqHbgB8X7754qz41SgOAngPN5C8sLtLpvzHzW2NtjjgKGLzZlkD8Kqq7HK9W+eQ42EVJmzbsAS ZthwEPEGNTNDqJwuuhQxzhB/HIbjj9LV+Hfsm6vxL2PZQl/gZ4FkkfGXL/xuJvYz+NO1+MRiqzFR JQJ6+N1rZdVtTTDIZbpoFGWsJwt0ivKH -----END CERTIFICATE----- TrustAsia Global Root CA G4 =========================== -----BEGIN CERTIFICATE----- MIICVTCCAdygAwIBAgIUTyNkuI6XY57GU4HBdk7LKnQV1tcwCgYIKoZIzj0EAwMwWjELMAkGA1UE BhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dpZXMsIEluYy4xJDAiBgNVBAMMG1Ry dXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHNDAeFw0yMTA1MjAwMjEwMjJaFw00NjA1MTkwMjEwMjJa MFoxCzAJBgNVBAYTAkNOMSUwIwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQw IgYDVQQDDBtUcnVzdEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNi AATxs8045CVD5d4ZCbuBeaIVXxVjAd7Cq92zphtnS4CDr5nLrBfbK5bKfFJV4hrhPVbwLxYI+hW8 m7tH5j/uqOFMjPXTNvk4XatwmkcN4oFBButJ+bAp3TPsUKV/eSm4IJijYzBhMA8GA1UdEwEB/wQF MAMBAf8wHwYDVR0jBBgwFoAUpbtKl86zK3+kMd6Xg1mDpm9xy94wHQYDVR0OBBYEFKW7SpfOsyt/ pDHel4NZg6ZvccveMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjBe8usGzEkxn0AA bbd+NvBNEU/zy4k6LHiRUKNbwMp1JvK/kF0LgoxgKJ/GcJpo5PECMFxYDlZ2z1jD1xCMuo6u47xk dUfFVZDj/bpV6wfEU6s3qe4hsiFbYI89MvHVI5TWWA== -----END CERTIFICATE----- Telekom Security TLS ECC Root 2020 ================================== -----BEGIN CERTIFICATE----- MIICQjCCAcmgAwIBAgIQNjqWjMlcsljN0AFdxeVXADAKBggqhkjOPQQDAzBjMQswCQYDVQQGEwJE RTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBHbWJIMSswKQYDVQQDDCJUZWxl a29tIFNlY3VyaXR5IFRMUyBFQ0MgUm9vdCAyMDIwMB4XDTIwMDgyNTA3NDgyMFoXDTQ1MDgyNTIz NTk1OVowYzELMAkGA1UEBhMCREUxJzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkg R21iSDErMCkGA1UEAwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgRUNDIFJvb3QgMjAyMDB2MBAGByqG SM49AgEGBSuBBAAiA2IABM6//leov9Wq9xCazbzREaK9Z0LMkOsVGJDZos0MKiXrPk/OtdKPD/M1 2kOLAoC+b1EkHQ9rK8qfwm9QMuU3ILYg/4gND21Ju9sGpIeQkpT0CdDPf8iAC8GXs7s1J8nCG6NC MEAwHQYDVR0OBBYEFONyzG6VmUex5rNhTNHLq+O6zd6fMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P AQH/BAQDAgEGMAoGCCqGSM49BAMDA2cAMGQCMHVSi7ekEE+uShCLsoRbQuHmKjYC2qBuGT8lv9pZ Mo7k+5Dck2TOrbRBR2Diz6fLHgIwN0GMZt9Ba9aDAEH9L1r3ULRn0SyocddDypwnJJGDSA3PzfdU ga/sf+Rn27iQ7t0l -----END CERTIFICATE----- Telekom Security TLS RSA Root 2023 ================================== -----BEGIN CERTIFICATE----- MIIFszCCA5ugAwIBAgIQIZxULej27HF3+k7ow3BXlzANBgkqhkiG9w0BAQwFADBjMQswCQYDVQQG EwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBHbWJIMSswKQYDVQQDDCJU ZWxla29tIFNlY3VyaXR5IFRMUyBSU0EgUm9vdCAyMDIzMB4XDTIzMDMyODEyMTY0NVoXDTQ4MDMy NzIzNTk1OVowYzELMAkGA1UEBhMCREUxJzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJp dHkgR21iSDErMCkGA1UEAwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgUlNBIFJvb3QgMjAyMzCCAiIw DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAO01oYGA88tKaVvC+1GDrib94W7zgRJ9cUD/h3VC KSHtgVIs3xLBGYSJwb3FKNXVS2xE1kzbB5ZKVXrKNoIENqil/Cf2SfHVcp6R+SPWcHu79ZvB7JPP GeplfohwoHP89v+1VmLhc2o0mD6CuKyVU/QBoCcHcqMAU6DksquDOFczJZSfvkgdmOGjup5czQRx UX11eKvzWarE4GC+j4NSuHUaQTXtvPM6Y+mpFEXX5lLRbtLevOP1Czvm4MS9Q2QTps70mDdsipWo l8hHD/BeEIvnHRz+sTugBTNoBUGCwQMrAcjnj02r6LX2zWtEtefdi+zqJbQAIldNsLGyMcEWzv/9 FIS3R/qy8XDe24tsNlikfLMR0cN3f1+2JeANxdKz+bi4d9s3cXFH42AYTyS2dTd4uaNir73Jco4v zLuu2+QVUhkHM/tqty1LkCiCc/4YizWN26cEar7qwU02OxY2kTLvtkCJkUPg8qKrBC7m8kwOFjQg rIfBLX7JZkcXFBGk8/ehJImr2BrIoVyxo/eMbcgByU/J7MT8rFEz0ciD0cmfHdRHNCk+y7AO+oML KFjlKdw/fKifybYKu6boRhYPluV75Gp6SG12mAWl3G0eQh5C2hrgUve1g8Aae3g1LDj1H/1Joy7S WWO/gLCMk3PLNaaZlSJhZQNg+y+TS/qanIA7AgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAdBgNV HQ4EFgQUtqeXgj10hZv3PJ+TmpV5dVKMbUcwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBS2 p5eCPXSFm/c8n5OalXl1UoxtRzANBgkqhkiG9w0BAQwFAAOCAgEAqMxhpr51nhVQpGv7qHBFfLp+ sVr8WyP6Cnf4mHGCDG3gXkaqk/QeoMPhk9tLrbKmXauw1GLLXrtm9S3ul0A8Yute1hTWjOKWi0Fp kzXmuZlrYrShF2Y0pmtjxrlO8iLpWA1WQdH6DErwM807u20hOq6OcrXDSvvpfeWxm4bu4uB9tPcy /SKE8YXJN3nptT+/XOR0so8RYgDdGGah2XsjX/GO1WfoVNpbOms2b/mBsTNHM3dA+VKq3dSDz4V4 mZqTuXNnQkYRIer+CqkbGmVps4+uFrb2S1ayLfmlyOw7YqPta9BO1UAJpB+Y1zqlklkg5LB9zVtz aL1txKITDmcZuI1CfmwMmm6gJC3VRRvcxAIU/oVbZZfKTpBQCHpCNfnqwmbU+AGuHrS+w6jv/naa oqYfRvaE7fzbzsQCzndILIyy7MMAo+wsVRjBfhnu4S/yrYObnqsZ38aKL4x35bcF7DvB7L6Gs4a8 wPfc5+pbrrLMtTWGS9DiP7bY+A4A7l3j941Y/8+LN+ljX273CXE2whJdV/LItM3z7gLfEdxquVeE HVlNjM7IDiPCtyaaEBRx/pOyiriA8A4QntOoUAw3gi/q4Iqd4Sw5/7W0cwDk90imc6y/st53BIe0 o82bNSQ3+pCTE4FCxpgmdTdmQRCsu/WU48IxK63nI1bMNSWSs1A= -----END CERTIFICATE----- TWCA CYBER Root CA ================== -----BEGIN CERTIFICATE----- MIIFjTCCA3WgAwIBAgIQQAE0jMIAAAAAAAAAATzyxjANBgkqhkiG9w0BAQwFADBQMQswCQYDVQQG EwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJUV0NB IENZQkVSIFJvb3QgQ0EwHhcNMjIxMTIyMDY1NDI5WhcNNDcxMTIyMTU1OTU5WjBQMQswCQYDVQQG EwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJUV0NB IENZQkVSIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDG+Moe2Qkgfh1s Ts6P40czRJzHyWmqOlt47nDSkvgEs1JSHWdyKKHfi12VCv7qze33Kc7wb3+szT3vsxxFavcokPFh V8UMxKNQXd7UtcsZyoC5dc4pztKFIuwCY8xEMCDa6pFbVuYdHNWdZsc/34bKS1PE2Y2yHer43CdT o0fhYcx9tbD47nORxc5zb87uEB8aBs/pJ2DFTxnk684iJkXXYJndzk834H/nY62wuFm40AZoNWDT Nq5xQwTxaWV4fPMf88oon1oglWa0zbfuj3ikRRjpJi+NmykosaS3Om251Bw4ckVYsV7r8Cibt4LK /c/WMw+f+5eesRycnupfXtuq3VTpMCEobY5583WSjCb+3MX2w7DfRFlDo7YDKPYIMKoNM+HvnKkH IuNZW0CP2oi3aQiotyMuRAlZN1vH4xfyIutuOVLF3lSnmMlLIJXcRolftBL5hSmO68gnFSDAS9TM fAxsNAwmmyYxpjyn9tnQS6Jk/zuZQXLB4HCX8SS7K8R0IrGsayIyJNN4KsDAoS/xUgXJP+92ZuJF 2A09rZXIx4kmyA+upwMu+8Ff+iDhcK2wZSA3M2Cw1a/XDBzCkHDXShi8fgGwsOsVHkQGzaRP6AzR wyAQ4VRlnrZR0Bp2a0JaWHY06rc3Ga4udfmW5cFZ95RXKSWNOkyrTZpB0F8mAwIDAQABo2MwYTAO BgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSdhWEUfMFib5do5E83 QOGt4A1WNzAdBgNVHQ4EFgQUnYVhFHzBYm+XaORPN0DhreANVjcwDQYJKoZIhvcNAQEMBQADggIB AGSPesRiDrWIzLjHhg6hShbNcAu3p4ULs3a2D6f/CIsLJc+o1IN1KriWiLb73y0ttGlTITVX1olN c79pj3CjYcya2x6a4CD4bLubIp1dhDGaLIrdaqHXKGnK/nZVekZn68xDiBaiA9a5F/gZbG0jAn/x X9AKKSM70aoK7akXJlQKTcKlTfjF/biBzysseKNnTKkHmvPfXvt89YnNdJdhEGoHK4Fa0o635yDR IG4kqIQnoVesqlVYL9zZyvpoBJ7tRCT5dEA7IzOrg1oYJkK2bVS1FmAwbLGg+LhBoF1JSdJlBTrq /p1hvIbZv97Tujqxf36SNI7JAG7cmL3c7IAFrQI932XtCwP39xaEBDG6k5TY8hL4iuO/Qq+n1M0R FxbIQh0UqEL20kCGoE8jypZFVmAGzbdVAaYBlGX+bgUJurSkquLvWL69J1bY73NxW0Qz8ppy6rBe Pm6pUlvscG21h483XjyMnM7k8M4MZ0HMzvaAq07MTFb1wWFZk7Q+ptq4NxKfKjLji7gh7MMrZQzv It6IKTtM1/r+t+FHvpw+PoP7UV31aPcuIYXcv/Fa4nzXxeSDwWrruoBa3lwtcHb4yOWHh8qgnaHl IhInD0Q9HWzq1MKLL295q39QpsQZp6F6t5b5wR9iWqJDB0BeJsas7a5wFsWqynKKTbDPAYsDP27X -----END CERTIFICATE----- SecureSign Root CA12 ==================== -----BEGIN CERTIFICATE----- MIIDcjCCAlqgAwIBAgIUZvnHwa/swlG07VOX5uaCwysckBYwDQYJKoZIhvcNAQELBQAwUTELMAkG A1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBMdGQuMR0wGwYDVQQDExRT ZWN1cmVTaWduIFJvb3QgQ0ExMjAeFw0yMDA0MDgwNTM2NDZaFw00MDA0MDgwNTM2NDZaMFExCzAJ BgNVBAYTAkpQMSMwIQYDVQQKExpDeWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMU U2VjdXJlU2lnbiBSb290IENBMTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6OcE3 emhFKxS06+QT61d1I02PJC0W6K6OyX2kVzsqdiUzg2zqMoqUm048luT9Ub+ZyZN+v/mtp7JIKwcc J/VMvHASd6SFVLX9kHrko+RRWAPNEHl57muTH2SOa2SroxPjcf59q5zdJ1M3s6oYwlkm7Fsf0uZl fO+TvdhYXAvA42VvPMfKWeP+bl+sg779XSVOKik71gurFzJ4pOE+lEa+Ym6b3kaosRbnhW70CEBF EaCeVESE99g2zvVQR9wsMJvuwPWW0v4JhscGWa5Pro4RmHvzC1KqYiaqId+OJTN5lxZJjfU+1Uef NzFJM3IFTQy2VYzxV4+Kh9GtxRESOaCtAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P AQH/BAQDAgEGMB0GA1UdDgQWBBRXNPN0zwRL1SXm8UC2LEzZLemgrTANBgkqhkiG9w0BAQsFAAOC AQEAPrvbFxbS8hQBICw4g0utvsqFepq2m2um4fylOqyttCg6r9cBg0krY6LdmmQOmFxv3Y67ilQi LUoT865AQ9tPkbeGGuwAtEGBpE/6aouIs3YIcipJQMPTw4WJmBClnW8Zt7vPemVV2zfrPIpyMpce mik+rY3moxtt9XUa5rBouVui7mlHJzWhhpmA8zNL4WukJsPvdFlseqJkth5Ew1DgDzk9qTPxpfPS vWKErI4cqc1avTc7bgoitPQV55FYxTpE05Uo2cBl6XLK0A+9H7MV2anjpEcJnuDLN/v9vZfVvhga aaI5gdka9at/yOPiZwud9AzqVN/Ssq+xIvEg37xEHA== -----END CERTIFICATE----- SecureSign Root CA14 ==================== -----BEGIN CERTIFICATE----- MIIFcjCCA1qgAwIBAgIUZNtaDCBO6Ncpd8hQJ6JaJ90t8sswDQYJKoZIhvcNAQEMBQAwUTELMAkG A1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBMdGQuMR0wGwYDVQQDExRT ZWN1cmVTaWduIFJvb3QgQ0ExNDAeFw0yMDA0MDgwNzA2MTlaFw00NTA0MDgwNzA2MTlaMFExCzAJ BgNVBAYTAkpQMSMwIQYDVQQKExpDeWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMU U2VjdXJlU2lnbiBSb290IENBMTQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDF0nqh 1oq/FjHQmNE6lPxauG4iwWL3pwon71D2LrGeaBLwbCRjOfHw3xDG3rdSINVSW0KZnvOgvlIfX8xn bacuUKLBl422+JX1sLrcneC+y9/3OPJH9aaakpUqYllQC6KxNedlsmGy6pJxaeQp8E+BgQQ8sqVb 1MWoWWd7VRxJq3qdwudzTe/NCcLEVxLbAQ4jeQkHO6Lo/IrPj8BGJJw4J+CDnRugv3gVEOuGTgpa /d/aLIJ+7sr2KeH6caH3iGicnPCNvg9JkdjqOvn90Ghx2+m1K06Ckm9mH+Dw3EzsytHqunQG+bOE kJTRX45zGRBdAuVwpcAQ0BB8b8VYSbSwbprafZX1zNoCr7gsfXmPvkPx+SgojQlD+Ajda8iLLCSx jVIHvXiby8posqTdDEx5YMaZ0ZPxMBoH064iwurO8YQJzOAUbn8/ftKChazcqRZOhaBgy/ac18iz ju3Gm5h1DVXoX+WViwKkrkMpKBGk5hIwAUt1ax5mnXkvpXYvHUC0bcl9eQjs0Wq2XSqypWa9a4X0 dFbD9ed1Uigspf9mR6XU/v6eVL9lfgHWMI+lNpyiUBzuOIABSMbHdPTGrMNASRZhdCyvjG817XsY AFs2PJxQDcqSMxDxJklt33UkN4Ii1+iW/RVLApY+B3KVfqs9TC7XyvDf4Fg/LS8EmjijAQIDAQAB o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUBpOjCl4oaTeq YR3r6/wtbyPk86AwDQYJKoZIhvcNAQEMBQADggIBAJaAcgkGfpzMkwQWu6A6jZJOtxEaCnFxEM0E rX+lRVAQZk5KQaID2RFPeje5S+LGjzJmdSX7684/AykmjbgWHfYfM25I5uj4V7Ibed87hwriZLoA ymzvftAj63iP/2SbNDefNWWipAA9EiOWWF3KY4fGoweITedpdopTzfFP7ELyk+OZpDc8h7hi2/Ds Hzc/N19DzFGdtfCXwreFamgLRB7lUe6TzktuhsHSDCRZNhqfLJGP4xjblJUK7ZGqDpncllPjYYPG FrojutzdfhrGe0K22VoF3Jpf1d+42kd92jjbrDnVHmtsKheMYc2xbXIBw8MgAGJoFjHVdqqGuw6q nsb58Nn4DSEC5MUoFlkRudlpcyqSeLiSV5sI8jrlL5WwWLdrIBRtFO8KvH7YVdiI2i/6GaX7i+B/ OfVyK4XELKzvGUWSTLNhB9xNH27SgRNcmvMSZ4PPmz+Ln52kuaiWA3rF7iDeM9ovnhp6dB7h7sxa OgTdsxoEqBRjrLdHEoOabPXm6RUVkRqEGQ6UROcSjiVbgGcZ3GOTEAtlLor6CZpO2oYofaphNdgO pygau1LgePhsumywbrmHXumZNTfxPWQrqaA0k89jL9WB365jJ6UeTo3cKXhZ+PmhIIynJkBugnLN eLLIjzwec+fBH7/PzqUqm9tEZDKgu39cJRNItX+S -----END CERTIFICATE----- SecureSign Root CA15 ==================== -----BEGIN CERTIFICATE----- MIICIzCCAamgAwIBAgIUFhXHw9hJp75pDIqI7fBw+d23PocwCgYIKoZIzj0EAwMwUTELMAkGA1UE BhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBMdGQuMR0wGwYDVQQDExRTZWN1 cmVTaWduIFJvb3QgQ0ExNTAeFw0yMDA0MDgwODMyNTZaFw00NTA0MDgwODMyNTZaMFExCzAJBgNV BAYTAkpQMSMwIQYDVQQKExpDeWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2Vj dXJlU2lnbiBSb290IENBMTUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQLUHSNZDKZmbPSYAi4Io5G dCx4wCtELW1fHcmuS1Iggz24FG1Th2CeX2yF2wYUleDHKP+dX+Sq8bOLbe1PL0vJSpSRZHX+AezB 2Ot6lHhWGENfa4HL9rzatAy2KZMIaY+jQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD AgEGMB0GA1UdDgQWBBTrQciu/NWeUUj1vYv0hyCTQSvT9DAKBggqhkjOPQQDAwNoADBlAjEA2S6J fl5OpBEHvVnCB96rMjhTKkZEBhd6zlHp4P9mLQlO4E/0BdGF9jVg3PVys0Z9AjBEmEYagoUeYWmJ SwdLZrWeqrqgHkHZAXQ6bkU6iYAZezKYVWOr62Nuk22rGwlgMU4= -----END CERTIFICATE----- D-TRUST BR Root CA 2 2023 ========================= -----BEGIN CERTIFICATE----- MIIFqTCCA5GgAwIBAgIQczswBEhb2U14LnNLyaHcZjANBgkqhkiG9w0BAQ0FADBIMQswCQYDVQQG EwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEJSIFJvb3QgQ0Eg MiAyMDIzMB4XDTIzMDUwOTA4NTYzMVoXDTM4MDUwOTA4NTYzMFowSDELMAkGA1UEBhMCREUxFTAT BgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDIgMjAyMzCC AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK7/CVmRgApKaOYkP7in5Mg6CjoWzckjYaCT cfKri3OPoGdlYNJUa2NRb0kz4HIHE304zQaSBylSa053bATTlfrdTIzZXcFhfUvnKLNEgXtRr90z sWh81k5M/itoucpmacTsXld/9w3HnDY25QdgrMBM6ghs7wZ8T1soegj8k12b9py0i4a6Ibn08OhZ WiihNIQaJZG2tY/vsvmA+vk9PBFy2OMvhnbFeSzBqZCTRphny4NqoFAjpzv2gTng7fC5v2Xx2Mt6 ++9zA84A9H3X4F07ZrjcjrqDy4d2A/wl2ecjbwb9Z/Pg/4S8R7+1FhhGaRTMBffb00msa8yr5LUL QyReS2tNZ9/WtT5PeB+UcSTq3nD88ZP+npNa5JRal1QMNXtfbO4AHyTsA7oC9Xb0n9Sa7YUsOCIv x9gvdhFP/Wxc6PWOJ4d/GUohR5AdeY0cW/jPSoXk7bNbjb7EZChdQcRurDhaTyN0dKkSw/bSuREV MweR2Ds3OmMwBtHFIjYoYiMQ4EbMl6zWK11kJNXuHA7e+whadSr2Y23OC0K+0bpwHJwh5Q8xaRfX /Aq03u2AnMuStIv13lmiWAmlY0cL4UEyNEHZmrHZqLAbWt4NDfTisl01gLmB1IRpkQLLddCNxbU9 CZEJjxShFHR5PtbJFR2kWVki3PaKRT08EtY+XTIvAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB /zAdBgNVHQ4EFgQUZ5Dw1t61GNVGKX5cq/ieCLxklRAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRC MEAwPqA8oDqGOGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfYnJfcm9vdF9jYV8y XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQA097N3U9swFrktpSHxQCF16+tIFoE9c+CeJyrr d6kTpGoKWloUMz1oH4Guaf2Mn2VsNELZLdB/eBaxOqwjMa1ef67nriv6uvw8l5VAk1/DLQOj7aRv U9f6QA4w9QAgLABMjDu0ox+2v5Eyq6+SmNMW5tTRVFxDWy6u71cqqLRvpO8NVhTaIasgdp4D/Ca4 nj8+AybmTNudX0KEPUUDAxxZiMrcLmEkWqTqJwtzEr5SswrPMhfiHocaFpVIbVrg0M8JkiZmkdij YQ6qgYF/6FKC0ULn4B0Y+qSFNueG4A3rvNTJ1jxD8V1Jbn6Bm2m1iWKPiFLY1/4nwSPFyysCu7Ff /vtDhQNGvl3GyiEm/9cCnnRK3PgTFbGBVzbLZVzRHTF36SXDw7IyN9XxmAnkbWOACKsGkoHU6XCP pz+y7YaMgmo1yEJagtFSGkUPFaUA8JR7ZSdXOUPPfH/mvTWze/EZTN46ls/pdu4D58JDUjxqgejB WoC9EV2Ta/vH5mQ/u2kc6d0li690yVRAysuTEwrt+2aSEcr1wPrYg1UDfNPFIkZ1cGt5SAYqgpq/ 5usWDiJFAbzdNpQ0qTUmiteXue4Icr80knCDgKs4qllo3UCkGJCy89UDyibK79XH4I9TjvAA46jt n/mtd+ArY0+ew+43u3gJhJ65bvspmZDogNOfJA== -----END CERTIFICATE----- TrustAsia TLS ECC Root CA ========================= -----BEGIN CERTIFICATE----- MIICMTCCAbegAwIBAgIUNnThTXxlE8msg1UloD5Sfi9QaMcwCgYIKoZIzj0EAwMwWDELMAkGA1UE BhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dpZXMsIEluYy4xIjAgBgNVBAMTGVRy dXN0QXNpYSBUTFMgRUNDIFJvb3QgQ0EwHhcNMjQwNTE1MDU0MTU2WhcNNDQwNTE1MDU0MTU1WjBY MQswCQYDVQQGEwJDTjElMCMGA1UEChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywgSW5jLjEiMCAG A1UEAxMZVHJ1c3RBc2lhIFRMUyBFQ0MgUm9vdCBDQTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLh/ pVs/AT598IhtrimY4ZtcU5nb9wj/1WrgjstEpvDBjL1P1M7UiFPoXlfXTr4sP/MSpwDpguMqWzJ8 S5sUKZ74LYO1644xST0mYekdcouJtgq7nDM1D9rs3qlKH8kzsaNCMEAwDwYDVR0TAQH/BAUwAwEB /zAdBgNVHQ4EFgQULIVTu7FDzTLqnqOH/qKYqKaT6RAwDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49 BAMDA2gAMGUCMFRH18MtYYZI9HlaVQ01L18N9mdsd0AaRuf4aFtOJx24mH1/k78ITcTaRTChD15K eAIxAKORh/IRM4PDwYqROkwrULG9IpRdNYlzg8WbGf60oenUoWa2AaU2+dhoYSi3dOGiMQ== -----END CERTIFICATE----- TrustAsia TLS RSA Root CA ========================= -----BEGIN CERTIFICATE----- MIIFgDCCA2igAwIBAgIUHBjYz+VTPyI1RlNUJDxsR9FcSpwwDQYJKoZIhvcNAQEMBQAwWDELMAkG A1UEBhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dpZXMsIEluYy4xIjAgBgNVBAMT GVRydXN0QXNpYSBUTFMgUlNBIFJvb3QgQ0EwHhcNMjQwNTE1MDU0MTU3WhcNNDQwNTE1MDU0MTU2 WjBYMQswCQYDVQQGEwJDTjElMCMGA1UEChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywgSW5jLjEi MCAGA1UEAxMZVHJ1c3RBc2lhIFRMUyBSU0EgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIP ADCCAgoCggIBAMMWuBtqpERz5dZO9LnPWwvB0ZqB9WOwj0PBuwhaGnrhB3YmH49pVr7+NmDQDIPN lOrnxS1cLwUWAp4KqC/lYCZUlviYQB2srp10Zy9U+5RjmOMmSoPGlbYJQ1DNDX3eRA5gEk9bNb2/ mThtfWza4mhzH/kxpRkQcwUqwzIZheo0qt1CHjCNP561HmHVb70AcnKtEj+qpklz8oYVlQwQX1Fk zv93uMltrOXVmPGZLmzjyUT5tUMnCE32ft5EebuyjBza00tsLtbDeLdM1aTk2tyKjg7/D8OmYCYo zza/+lcK7Fs/6TAWe8TbxNRkoDD75f0dcZLdKY9BWN4ArTr9PXwaqLEX8E40eFgl1oUh63kd0Nyr z2I8sMeXi9bQn9P+PN7F4/w6g3CEIR0JwqH8uyghZVNgepBtljhb//HXeltt08lwSUq6HTrQUNoy IBnkiz/r1RYmNzz7dZ6wB3C4FGB33PYPXFIKvF1tjVEK2sUYyJtt3LCDs3+jTnhMmCWr8n4uIF6C FabW2I+s5c0yhsj55NqJ4js+k8UTav/H9xj8Z7XvGCxUq0DTbE3txci3OE9kxJRMT6DNrqXGJyV1 J23G2pyOsAWZ1SgRxSHUuPzHlqtKZFlhaxP8S8ySpg+kUb8OWJDZgoM5pl+z+m6Ss80zDoWo8SnT q1mt1tve1CuBAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFLgHkXlcBvRG/XtZ ylomkadFK/hTMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQwFAAOCAgEAIZtqBSBdGBanEqT3 Rz/NyjuujsCCztxIJXgXbODgcMTWltnZ9r96nBO7U5WS/8+S4PPFJzVXqDuiGev4iqME3mmL5Dw8 veWv0BIb5Ylrc5tvJQJLkIKvQMKtuppgJFqBTQUYo+IzeXoLH5Pt7DlK9RME7I10nYEKqG/odv6L TytpEoYKNDbdgptvT+Bz3Ul/KD7JO6NXBNiT2Twp2xIQaOHEibgGIOcberyxk2GaGUARtWqFVwHx tlotJnMnlvm5P1vQiJ3koP26TpUJg3933FEFlJ0gcXax7PqJtZwuhfG5WyRasQmr2soaB82G39tp 27RIGAAtvKLEiUUjpQ7hRGU+isFqMB3iYPg6qocJQrmBktwliJiJ8Xw18WLK7nn4GS/+X/jbh87q qA8MpugLoDzga5SYnH+tBuYc6kIQX+ImFTw3OffXvO645e8D7r0i+yiGNFjEWn9hongPXvPKnbwb PKfILfanIhHKA9jnZwqKDss1jjQ52MjqjZ9k4DewbNfFj8GQYSbbJIweSsCI3zWQzj8C9GRh3sfI B5XeMhg6j6JCQCTl1jNdfK7vsU1P1FeQNWrcrgSXSYk0ly4wBOeY99sLAZDBHwo/+ML+TvrbmnNz FrwFuHnYWa8G5z9nODmxfKuU4CkUpijy323imttUQ/hHWKNddBWcwauwxzQ= -----END CERTIFICATE----- D-TRUST EV Root CA 2 2023 ========================= -----BEGIN CERTIFICATE----- MIIFqTCCA5GgAwIBAgIQaSYJfoBLTKCnjHhiU19abzANBgkqhkiG9w0BAQ0FADBIMQswCQYDVQQG EwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEVWIFJvb3QgQ0Eg MiAyMDIzMB4XDTIzMDUwOTA5MTAzM1oXDTM4MDUwOTA5MTAzMlowSDELMAkGA1UEBhMCREUxFTAT BgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBFViBSb290IENBIDIgMjAyMzCC AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANiOo4mAC7JXUtypU0w3uX9jFxPvp1sjW2l1 sJkKF8GLxNuo4MwxusLyzV3pt/gdr2rElYfXR8mV2IIEUD2BCP/kPbOx1sWy/YgJ25yE7CUXFId/ MHibaljJtnMoPDT3mfd/06b4HEV8rSyMlD/YZxBTfiLNTiVR8CUkNRFeEMbsh2aJgWi6zCudR3Mf vc2RpHJqnKIbGKBv7FD0fUDCqDDPvXPIEysQEx6Lmqg6lHPTGGkKSv/BAQP/eX+1SH977ugpbzZM lWGG2Pmic4ruri+W7mjNPU0oQvlFKzIbRlUWaqZLKfm7lVa/Rh3sHZMdwGWyH6FDrlaeoLGPaxK3 YG14C8qKXO0elg6DpkiVjTujIcSuWMYAsoS0I6SWhjW42J7YrDRJmGOVxcttSEfi8i4YHtAxq910 7PncjLgcjmgjutDzUNzPZY9zOjLHfP7KgiJPvo5iR2blzYfi6NUPGJ/lBHJLRjwQ8kTCZFZxTnXo nMkmdMV9WdEKWw9t/p51HBjGGjp82A0EzM23RWV6sY+4roRIPrN6TagD4uJ+ARZZaBhDM7DS3LAa QzXupdqpRlyuhoFBAUp0JuyfBr/CBTdkdXgpaP3F9ev+R/nkhbDhezGdpn9yo7nELC7MmVcOIQxF AZRl62UJxmMiCzNJkkg8/M3OsD6Onov4/knFNXJHAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB /zAdBgNVHQ4EFgQUqvyREBuHkV8Wub9PS5FeAByxMoAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRC MEAwPqA8oDqGOGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfZXZfcm9vdF9jYV8y XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQCTy6UfmRHsmg1fLBWTxj++EI14QvBukEdHjqOS Mo1wj/Zbjb6JzkcBahsgIIlbyIIQbODnmaprxiqgYzWRaoUlrRc4pZt+UPJ26oUFKidBK7GB0aL2 QHWpDsvxVUjY7NHss+jOFKE17MJeNRqrphYBBo7q3C+jisosketSjl8MmxfPy3MHGcRqwnNU73xD UmPBEcrCRbH0O1P1aa4846XerOhUt7KR/aypH/KH5BfGSah82ApB9PI+53c0BFLd6IHyTS9URZ0V 4U/M5d40VxDJI3IXcI1QcB9WbMy5/zpaT2N6w25lBx2Eof+pDGOJbbJAiDnXH3dotfyc1dZnaVuo dNv8ifYbMvekJKZ2t0dT741Jj6m2g1qllpBFYfXeA08mD6iL8AOWsKwV0HFaanuU5nCT2vFp4LJi TZ6P/4mdm13NRemUAiKN4DV/6PEEeXFsVIP4M7kFMhtYVRFP0OUnR3Hs7dpn1mKmS00PaaLJvOwi S5THaJQXfuKOKD62xur1NGyfN4gHONuGcfrNlUhDbqNPgofXNJhuS5N5YHVpD/Aa1VP6IQzCP+k/ HxiMkl14p3ZnGbuy6n/pcAlWVqOwDAstNl7F6cTVg8uGF5csbBNvh1qvSaYd2804BC5f4ko1Di1L +KIkBI3Y4WNeApI02phhXBxvWHZks/wCuPWdCg== -----END CERTIFICATE----- SwissSign RSA TLS Root CA 2022 - 1 ================================== -----BEGIN CERTIFICATE----- MIIFkzCCA3ugAwIBAgIUQ/oMX04bgBhE79G0TzUfRPSA7cswDQYJKoZIhvcNAQELBQAwUTELMAkG A1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzErMCkGA1UEAxMiU3dpc3NTaWduIFJTQSBU TFMgUm9vdCBDQSAyMDIyIC0gMTAeFw0yMjA2MDgxMTA4MjJaFw00NzA2MDgxMTA4MjJaMFExCzAJ BgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxKzApBgNVBAMTIlN3aXNzU2lnbiBSU0Eg VExTIFJvb3QgQ0EgMjAyMiAtIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDLKmji C8NXvDVjvHClO/OMPE5Xlm7DTjak9gLKHqquuN6orx122ro10JFwB9+zBvKK8i5VUXu7LCTLf5Im gKO0lPaCoaTo+nUdWfMHamFk4saMla+ju45vVs9xzF6BYQ1t8qsCLqSX5XH8irCRIFucdFJtrhUn WXjyCcplDn/L9Ovn3KlMd/YrFgSVrpxxpT8q2kFC5zyEEPThPYxr4iuRR1VPuFa+Rd4iUU1OKNlf GUEGjw5NBuBwQCMBauTLE5tzrE0USJIt/m2n+IdreXXhvhCxqohAWVTXz8TQm0SzOGlkjIHRI36q OTw7D59Ke4LKa2/KIj4x0LDQKhySio/YGZxH5D4MucLNvkEM+KRHBdvBFzA4OmnczcNpI/2aDwLO EGrOyvi5KaM2iYauC8BPY7kGWUleDsFpswrzd34unYyzJ5jSmY0lpx+Gs6ZUcDj8fV3oT4MM0ZPl EuRU2j7yrTrePjxF8CgPBrnh25d7mUWe3f6VWQQvdT/TromZhqwUtKiE+shdOxtYk8EXlFXIC+OC eYSf8wCENO7cMdWP8vpPlkwGqnj73mSiI80fPsWMvDdUDrtaclXvyFu1cvh43zcgTFeRc5JzrBh3 Q4IgaezprClG5QtO+DdziZaKHG29777YtvTKwP1H8K4LWCDFyB02rpeNUIMmJCn3nTsPBQIDAQAB o2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBRvjmKLk0Ow 4UD2p8P98Q+4DxU4pTAdBgNVHQ4EFgQUb45ii5NDsOFA9qfD/fEPuA8VOKUwDQYJKoZIhvcNAQEL BQADggIBAKwsKUF9+lz1GpUYvyypiqkkVHX1uECry6gkUSsYP2OprphWKwVDIqO310aewCoSPY6W lkDfDDOLazeROpW7OSltwAJsipQLBwJNGD77+3v1dj2b9l4wBlgzHqp41eZUBDqyggmNzhYzWUUo 8aWjlw5DI/0LIICQ/+Mmz7hkkeUFjxOgdg3XNwwQiJb0Pr6VvfHDffCjw3lHC1ySFWPtUnWK50Zp y1FVCypM9fJkT6lc/2cyjlUtMoIcgC9qkfjLvH4YoiaoLqNTKIftV+Vlek4ASltOU8liNr3Cjlvr zG4ngRhZi0Rjn9UMZfQpZX+RLOV/fuiJz48gy20HQhFRJjKKLjpHE7iNvUcNCfAWpO2Whi4Z2L6M OuhFLhG6rlrnub+xzI/goP+4s9GFe3lmozm1O2bYQL7Pt2eLSMkZJVX8vY3PXtpOpvJpzv1/THfQ wUY1mFwjmwJFQ5Ra3bxHrSL+ul4vkSkphnsh3m5kt8sNjzdbowhq6/TdAo9QAwKxuDdollDruF/U KIqlIgyKhPBZLtU30WHlQnNYKoH3dtvi4k0NX/a3vgW0rk4N3hY9A4GzJl5LuEsAz/+MF7psYC0n hzck5npgL7XTgwSqT0N1osGDsieYK7EOgLrAhV5Cud+xYJHT6xh+cHiudoO+cVrQkOPKwRYlZ0rw tnu64ZzZ -----END CERTIFICATE----- OISTE Server Root ECC G1 ======================== -----BEGIN CERTIFICATE----- MIICNTCCAbqgAwIBAgIQI/nD1jWvjyhLH/BU6n6XnTAKBggqhkjOPQQDAzBLMQswCQYDVQQGEwJD SDEZMBcGA1UECgwQT0lTVEUgRm91bmRhdGlvbjEhMB8GA1UEAwwYT0lTVEUgU2VydmVyIFJvb3Qg RUNDIEcxMB4XDTIzMDUzMTE0NDIyOFoXDTQ4MDUyNDE0NDIyN1owSzELMAkGA1UEBhMCQ0gxGTAX BgNVBAoMEE9JU1RFIEZvdW5kYXRpb24xITAfBgNVBAMMGE9JU1RFIFNlcnZlciBSb290IEVDQyBH MTB2MBAGByqGSM49AgEGBSuBBAAiA2IABBcv+hK8rBjzCvRE1nZCnrPoH7d5qVi2+GXROiFPqOuj vqQycvO2Ackr/XeFblPdreqqLiWStukhEaivtUwL85Zgmjvn6hp4LrQ95SjeHIC6XG4N2xml4z+c KrhAS93mT6NjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBQ3TYhlz/w9itWj8UnATgwQ b0K0nDAdBgNVHQ4EFgQUN02IZc/8PYrVo/FJwE4MEG9CtJwwDgYDVR0PAQH/BAQDAgGGMAoGCCqG SM49BAMDA2kAMGYCMQCpKjAd0MKfkFFRQD6VVCHNFmb3U2wIFjnQEnx/Yxvf4zgAOdktUyBFCxxg ZzFDJe0CMQCSia7pXGKDYmH5LVerVrkR3SW+ak5KGoJr3M/TvEqzPNcum9v4KGm8ay3sMaE641c= -----END CERTIFICATE----- OISTE Server Root RSA G1 ======================== -----BEGIN CERTIFICATE----- MIIFgzCCA2ugAwIBAgIQVaXZZ5Qoxu0M+ifdWwFNGDANBgkqhkiG9w0BAQwFADBLMQswCQYDVQQG EwJDSDEZMBcGA1UECgwQT0lTVEUgRm91bmRhdGlvbjEhMB8GA1UEAwwYT0lTVEUgU2VydmVyIFJv b3QgUlNBIEcxMB4XDTIzMDUzMTE0MzcxNloXDTQ4MDUyNDE0MzcxNVowSzELMAkGA1UEBhMCQ0gx GTAXBgNVBAoMEE9JU1RFIEZvdW5kYXRpb24xITAfBgNVBAMMGE9JU1RFIFNlcnZlciBSb290IFJT QSBHMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKqu9KuCz/vlNwvn1ZatkOhLKdxV YOPMvLO8LZK55KN68YG0nnJyQ98/qwsmtO57Gmn7KNByXEptaZnwYx4M0rH/1ow00O7brEi56rAU jtgHqSSY3ekJvqgiG1k50SeH3BzN+Puz6+mTeO0Pzjd8JnduodgsIUzkik/HEzxux9UTl7Ko2yRp g1bTacuCErudG/L4NPKYKyqOBGf244ehHa1uzjZ0Dl4zO8vbUZeUapU8zhhabkvG/AePLhq5Svdk NCncpo1Q4Y2LS+VIG24ugBA/5J8bZT8RtOpXaZ+0AOuFJJkk9SGdl6r7NH8CaxWQrbueWhl/pIzY +m0o/DjH40ytas7ZTpOSjswMZ78LS5bOZmdTaMsXEY5Z96ycG7mOaES3GK/m5Q9l3JUJsJMStR8+ lKXHiHUhsd4JJCpM4rzsTGdHwimIuQq6+cF0zowYJmXa92/GjHtoXAvuY8BeS/FOzJ8vD+HomnqT 8eDI278n5mUpezbgMxVz8p1rhAhoKzYHKyfMeNhqhw5HdPSqoBNdZH702xSu+zrkL8Fl47l6QGzw Brd7KJvX4V84c5Ss2XCTLdyEr0YconosP4EmQufU2MVshGYRi3drVByjtdgQ8K4p92cIiBdcuJd5 z+orKu5YM+Vt6SmqZQENghPsJQtdLEByFSnTkCz3GkPVavBpAgMBAAGjYzBhMA8GA1UdEwEB/wQF MAMBAf8wHwYDVR0jBBgwFoAU8snBDw1jALvsRQ5KH7WxszbNDo0wHQYDVR0OBBYEFPLJwQ8NYwC7 7EUOSh+1sbM2zQ6NMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQwFAAOCAgEANGd5sjrG5T33 I3K5Ce+SrScfoE4KsvXaFwyihdJ+klH9FWXXXGtkFu6KRcoMQzZENdl//nk6HOjG5D1rd9QhEOP2 8yBOqb6J8xycqd+8MDoX0TJD0KqKchxRKEzdNsjkLWd9kYccnbz8qyiWXmFcuCIzGEgWUOrKL+ml Sdx/PKQZvDatkuK59EvV6wit53j+F8Bdh3foZ3dPAGav9LEDOr4SfEE15fSmG0eLy3n31r8Xbk5l 8PjaV8GUgeV6Vg27Rn9vkf195hfkgSe7BYhW3SCl95gtkRlpMV+bMPKZrXJAlszYd2abtNUOshD+ FKrDgHGdPY3ofRRsYWSGRqbXVMW215AWRqWFyp464+YTFrYVI8ypKVL9AMb2kI5Wj4kI3Zaq5tNq qYY19tVFeEJKRvwDyF7YZvZFZSS0vod7VSCd9521Kvy5YhnLbDuv0204bKt7ph6N/Ome/msVuduC msuY33OhkKCgxeDoAaijFJzIwZqsFVAzje18KotzlUBDJvyBpCpfOZC3J8tRd/iWkx7P8nd9H0aT olkelUTFLXVksNb54Dxp6gS1HAviRkRNQzuXSXERvSS2wq1yVAb+axj5d9spLFKebXd7Yv0PTY6Y MjAwcRLWJTXjn/hvnLXrahut6hDTlhZyBiElxky8j3C7DOReIoMt0r7+hVu05L0= -----END CERTIFICATE----- e-Szigno TLS Root CA 2023 ========================= -----BEGIN CERTIFICATE----- MIICzzCCAjGgAwIBAgINAOhvGHvWOWuYSkmYCjAKBggqhkjOPQQDBDB1MQswCQYDVQQGEwJIVTER MA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xFzAVBgNVBGEMDlZBVEhV LTIzNTg0NDk3MSIwIAYDVQQDDBllLVN6aWdubyBUTFMgUm9vdCBDQSAyMDIzMB4XDTIzMDcxNzE0 MDAwMFoXDTM4MDcxNzE0MDAwMFowdTELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRYw FAYDVQQKDA1NaWNyb3NlYyBMdGQuMRcwFQYDVQRhDA5WQVRIVS0yMzU4NDQ5NzEiMCAGA1UEAwwZ ZS1Temlnbm8gVExTIFJvb3QgQ0EgMjAyMzCBmzAQBgcqhkjOPQIBBgUrgQQAIwOBhgAEAGgP36J8 PKp0iGEKjcJMpQEiFNT3YHdCnAo4YKGMZz6zY+n6kbCLS+Y53wLCMAFSAL/fjO1ZrTJlqwlZULUZ wmgcAOAFX9pQJhzDrAQixTpN7+lXWDajwRlTEArRzT/vSzUaQ49CE0y5LBqcvjC2xN7cS53kpDzL Ltmt3999Cd8ukv+ho2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4E FgQUWYQCYlpGePVd3I8KECgj3NXW+0UwHwYDVR0jBBgwFoAUWYQCYlpGePVd3I8KECgj3NXW+0Uw CgYIKoZIzj0EAwQDgYsAMIGHAkIBLdqu9S54tma4n7Zwf2Z0z+yOfP7AAXmazlIC58PRDHpty7Ve 7hekm9sEdu4pKeiv+62sUvTXK9Z3hBC9xdIoaDQCQTV2WnXzkoYI9bIeCvZlC9p2x1L/Cx6AcCIw wzPbGO2E14vs7dOoY4G1VnxHx1YwlGhza9IuqbnZLBwpvQy6uWWL -----END CERTIFICATE----- 0) { $contents = preg_replace("/^(\\-+(?:BEGIN|END))\\s+TRUSTED\\s+(CERTIFICATE\\-+)\$/m", '$1 $2', $contents); if (null === $contents) { $isValid = false; } else { $isValid = (bool) openssl_x509_parse($contents); } } else { $isValid = false; } if ($logger) { $logger->debug('Checked CA file '.realpath($filename).': '.($isValid ? 'valid' : 'invalid')); } return self::$caFileValidity[$filename] = $isValid; } public static function isOpensslParseSafe() { return true; } public static function reset() { self::$caFileValidity = array(); self::$caPath = null; } private static function getEnvVariable($name) { if (isset($_SERVER[$name])) { return (string) $_SERVER[$name]; } if (PHP_SAPI === 'cli' && ($value = getenv($name)) !== false && $value !== null) { return (string) $value; } return false; } private static function caFileUsable($certFile, ?LoggerInterface $logger = null) { return $certFile && self::isFile($certFile, $logger) && self::isReadable($certFile, $logger) && self::validateCaFile($certFile, $logger); } private static function caDirUsable($certDir, ?LoggerInterface $logger = null) { return $certDir && self::isDir($certDir, $logger) && self::isReadable($certDir, $logger) && self::glob($certDir . '/*', $logger); } private static function isFile($certFile, ?LoggerInterface $logger = null) { $isFile = @is_file($certFile); if (!$isFile && $logger) { $logger->debug(sprintf('Checked CA file %s does not exist or it is not a file.', $certFile)); } return $isFile; } private static function isDir($certDir, ?LoggerInterface $logger = null) { $isDir = @is_dir($certDir); if (!$isDir && $logger) { $logger->debug(sprintf('Checked directory %s does not exist or it is not a directory.', $certDir)); } return $isDir; } private static function isReadable($certFileOrDir, ?LoggerInterface $logger = null) { $isReadable = @is_readable($certFileOrDir); if (!$isReadable && $logger) { $logger->debug(sprintf('Checked file or directory %s is not readable.', $certFileOrDir)); } return $isReadable; } private static function glob($pattern, ?LoggerInterface $logger = null) { $certs = glob($pattern); if ($certs === false) { if ($logger) { $logger->debug(sprintf("An error occurred while trying to find certificates for pattern: %s", $pattern)); } return false; } if (count($certs) === 0) { if ($logger) { $logger->debug(sprintf("No CA files found for pattern: %s", $pattern)); } return false; } return true; } } Copyright (C) 2022 Composer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. map; } public function getPsrViolations(): array { if (\count($this->psrViolations) === 0) { return []; } return array_map(static function (array $violation): string { return $violation['warning']; }, array_merge(...array_values($this->psrViolations))); } public function getAmbiguousClasses($duplicatesFilter = '{/(test|fixture|example|stub)s?/}i'): array { if (false === $duplicatesFilter) { return $this->ambiguousClasses; } if (true === $duplicatesFilter) { throw new \InvalidArgumentException('$duplicatesFilter should be false or a string with a valid regex, got true.'); } $ambiguousClasses = []; foreach ($this->ambiguousClasses as $class => $paths) { $paths = array_filter($paths, static function ($path) use ($duplicatesFilter): bool { return !Preg::isMatch($duplicatesFilter, strtr($path, '\\', '/')); }); if (\count($paths) > 0) { $ambiguousClasses[$class] = array_values($paths); } } return $ambiguousClasses; } public function sort(): void { ksort($this->map); } public function addClass(string $className, string $path): void { unset($this->psrViolations[strtr($path, '\\', '/')]); $this->map[$className] = $path; } public function getClassPath(string $className): string { if (!isset($this->map[$className])) { throw new \OutOfBoundsException('Class '.$className.' is not present in the map'); } return $this->map[$className]; } public function hasClass(string $className): bool { return isset($this->map[$className]); } public function addPsrViolation(string $warning, string $className, string $path): void { $path = rtrim(strtr($path, '\\', '/'), '/'); $this->psrViolations[$path][] = ['warning' => $warning, 'className' => $className]; } public function clearPsrViolationsByPath(string $pathPrefix): void { $pathPrefix = rtrim(strtr($pathPrefix, '\\', '/'), '/'); foreach ($this->psrViolations as $path => $violations) { if ($path === $pathPrefix || 0 === strpos($path, $pathPrefix.'/')) { unset($this->psrViolations[$path]); } } } public function addAmbiguousClass(string $className, string $path): void { $this->ambiguousClasses[$className][] = $path; } public function count(): int { return \count($this->map); } public function getRawPsrViolations(): array { return $this->psrViolations; } } extensions = $extensions; $this->classMap = new ClassMap; $this->streamWrappersRegex = \sprintf('{^(?:%s)://}', implode('|', array_map('preg_quote', stream_get_wrappers()))); } public function avoidDuplicateScans(?FileList $scannedFiles = null): self { $this->scannedFiles = $scannedFiles ?? new FileList; return $this; } public static function createMap($path): array { $generator = new self(); $generator->scanPaths($path); return $generator->getClassMap()->getMap(); } public function getClassMap(): ClassMap { return $this->classMap; } public function scanPaths($path, ?string $excluded = null, string $autoloadType = 'classmap', ?string $namespace = null, array $excludedDirs = []): void { if (!\in_array($autoloadType, ['psr-0', 'psr-4', 'classmap'], true)) { throw new \InvalidArgumentException('$autoloadType must be one of: "psr-0", "psr-4" or "classmap"'); } if ('classmap' !== $autoloadType) { if (!\is_string($path)) { throw new \InvalidArgumentException('$path must be a string when specifying a psr-0 or psr-4 autoload type'); } if (!\is_string($namespace)) { throw new \InvalidArgumentException('$namespace must be given (even if it is an empty string if you do not want to filter) when specifying a psr-0 or psr-4 autoload type'); } $basePath = $path; } if (\is_string($path)) { if (is_file($path)) { $path = [new \SplFileInfo($path)]; } elseif (is_dir($path) || strpos($path, '*') !== false) { $path = Finder::create() ->files() ->followLinks() ->name('/\.(?:'.implode('|', array_map('preg_quote', $this->extensions)).')$/') ->in($path) ->exclude($excludedDirs); } else { throw new \RuntimeException( 'Could not scan for classes inside "'.$path.'" which does not appear to be a file nor a folder' ); } } $cwd = realpath(self::getCwd()); foreach ($path as $file) { $filePath = $file->getPathname(); if (!\in_array(pathinfo($filePath, PATHINFO_EXTENSION), $this->extensions, true)) { continue; } $isStreamWrapperPath = Preg::isMatch($this->streamWrappersRegex, $filePath); if (!self::isAbsolutePath($filePath) && !$isStreamWrapperPath) { $filePath = $cwd . '/' . $filePath; $filePath = self::normalizePath($filePath); } else { $filePath = Preg::replace('{(?getPathname()); } $realPath = $isStreamWrapperPath ? $filePath : realpath($filePath); if (false === $realPath) { throw new \RuntimeException('realpath of '.$filePath.' failed to resolve, got false'); } if ($this->scannedFiles !== null && $this->scannedFiles->contains($realPath)) { continue; } if (null !== $excluded && Preg::isMatch($excluded, strtr($realPath, '\\', '/'))) { continue; } if (null !== $excluded && Preg::isMatch($excluded, strtr($filePath, '\\', '/'))) { continue; } $classes = PhpFileParser::findClasses($filePath); if ('classmap' !== $autoloadType && isset($namespace)) { $classes = $this->filterByNamespace($classes, $filePath, $namespace, $autoloadType, $basePath); if (\count($classes) > 0 && $this->scannedFiles !== null) { $this->scannedFiles->add($realPath); } } elseif ($this->scannedFiles !== null) { $this->scannedFiles->add($realPath); } foreach ($classes as $class) { if (!$this->classMap->hasClass($class)) { $this->classMap->addClass($class, $filePath); } elseif ($filePath !== $this->classMap->getClassPath($class)) { $this->classMap->addAmbiguousClass($class, $filePath); } } } } private function filterByNamespace(array $classes, string $filePath, string $baseNamespace, string $namespaceType, string $basePath): array { $validClasses = []; $rejectedClasses = []; $realSubPath = substr($filePath, \strlen($basePath) + 1); $dotPosition = strrpos($realSubPath, '.'); $realSubPath = substr($realSubPath, 0, $dotPosition === false ? PHP_INT_MAX : $dotPosition); foreach ($classes as $class) { if ('psr-0' === $namespaceType) { if ('' !== $baseNamespace && !str_starts_with($class, $baseNamespace)) { $rejectedClasses[] = $class; continue; } $namespaceLength = strrpos($class, '\\'); if (false !== $namespaceLength) { $namespace = substr($class, 0, $namespaceLength + 1); $className = substr($class, $namespaceLength + 1); $subPath = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . str_replace('_', DIRECTORY_SEPARATOR, $className); } else { $subPath = str_replace('_', DIRECTORY_SEPARATOR, $class); } } elseif ('psr-4' === $namespaceType) { $subNamespace = ('' !== $baseNamespace) ? substr($class, \strlen($baseNamespace)) : $class; $subPath = str_replace('\\', DIRECTORY_SEPARATOR, $subNamespace); } else { throw new \InvalidArgumentException('$namespaceType must be "psr-0" or "psr-4"'); } if ($subPath === $realSubPath) { $validClasses[] = $class; } else { $rejectedClasses[] = $class; } } if (\count($validClasses) === 0) { $cwd = realpath(self::getCwd()); if ($cwd === false) { $cwd = self::getCwd(); } $cwd = self::normalizePath($cwd); $shortPath = Preg::replace('{^'.preg_quote($cwd).'}', '.', self::normalizePath($filePath), 1); $shortBasePath = Preg::replace('{^'.preg_quote($cwd).'}', '.', self::normalizePath($basePath), 1); foreach ($rejectedClasses as $class) { $this->classMap->addPsrViolation("Class $class located in $shortPath does not comply with $namespaceType autoloading standard (rule: $baseNamespace => $shortBasePath). Skipping.", $class, $filePath); } return []; } return $validClasses; } private static function isAbsolutePath(string $path): bool { return strpos($path, '/') === 0 || substr($path, 1, 1) === ':' || strpos($path, '\\\\') === 0; } private static function normalizePath(string $path): string { $parts = []; $path = strtr($path, '\\', '/'); $prefix = ''; $absolute = ''; if (strpos($path, '//') === 0 && \strlen($path) > 2) { $absolute = '//'; $path = substr($path, 2); } if (Preg::isMatchStrictGroups('{^( [0-9a-z]{2,}+: (?: // (?: [a-z]: )? )? | [a-z]: )}ix', $path, $match)) { $prefix = $match[1]; $path = substr($path, \strlen($prefix)); } if (strpos($path, '/') === 0) { $absolute = '/'; $path = substr($path, 1); } $up = false; foreach (explode('/', $path) as $chunk) { if ('..' === $chunk && (\strlen($absolute) > 0 || $up)) { array_pop($parts); $up = !(\count($parts) === 0 || '..' === end($parts)); } elseif ('.' !== $chunk && '' !== $chunk) { $parts[] = $chunk; $up = '..' !== $chunk; } } $prefix = Preg::replaceCallback('{(?:^|://)[a-z]:$}i', static function (array $m) { return strtoupper((string) $m[0]); }, $prefix); return $prefix.$absolute.implode('/', $parts); } private static function getCwd(): string { $cwd = getcwd(); if (false === $cwd) { throw new \RuntimeException('Could not determine the current working directory'); } return $cwd; } } files[$path] = true; } public function contains(string $path): bool { return isset($this->files[$path]); } } $type, 'length' => \strlen($type), 'pattern' => '{.\b(?])'.$type.'\s++[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+}Ais', ]; } self::$rejectChars = '?"\'contents = $contents; $this->len = \strlen($this->contents); $this->maxMatches = $maxMatches; } public function clean(): string { $clean = ''; while ($this->index < $this->len) { $this->skipToPhp(); $clean .= 'index < $this->len) { $char = $this->contents[$this->index]; if ($char === '?' && $this->peek('>')) { $clean .= '?>'; $this->index += 2; continue 2; } if ($char === '"') { $this->skipString('"'); $clean .= 'null'; continue; } if ($char === "'") { $this->skipString("'"); $clean .= 'null'; continue; } if ($char === "<" && $this->peek('<') && $this->match('{<<<[ \t]*+([\'"]?)([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*+)\\1(?:\r\n|\n|\r)}A', $match)) { $this->index += \strlen($match[0]); $this->skipHeredoc($match[2]); $clean .= 'null'; continue; } if ($char === '/') { if ($this->peek('/')) { $this->skipToNewline(); continue; } if ($this->peek('*')) { $this->skipComment(); continue; } } if ($this->maxMatches === 1 && isset(self::$typeConfig[$char])) { $type = self::$typeConfig[$char]; if ( substr($this->contents, $this->index, $type['length']) === $type['name'] && Preg::isMatch($type['pattern'], $this->contents, $match, 0, $this->index - 1) ) { return $clean . $match[0]; } } $this->index += 1; $skip = strcspn($this->contents, self::$rejectChars, $this->index); if ($skip > 0) { $clean .= $char . substr($this->contents, $this->index, $skip); $this->index += $skip; } else { $clean .= $char; } } } return $clean; } private function skipToPhp(): void { while ($this->index < $this->len) { if ($this->contents[$this->index] === '<' && $this->peek('?')) { $this->index += 2; break; } $this->index += 1; } } private function skipString(string $delimiter): void { $rejectChars = '\\' . $delimiter; $this->index += 1; while ($this->index < $this->len) { $this->index += strcspn($this->contents, $rejectChars, $this->index); if ($this->index >= $this->len) { break; } if ($this->contents[$this->index] === '\\' && ($this->peek('\\') || $this->peek($delimiter))) { $this->index += 2; continue; } if ($this->contents[$this->index] === $delimiter) { $this->index += 1; break; } $this->index += 1; } } private function skipComment(): void { $this->index += 2; while ($this->index < $this->len) { $this->index += strcspn($this->contents, '*', $this->index); if ($this->peek('/')) { $this->index += 2; break; } $this->index += 1; } } private function skipToNewline(): void { $this->index += strcspn($this->contents, "\r\n", $this->index); } private function skipHeredoc(string $delimiter): void { $firstDelimiterChar = $delimiter[0]; $delimiterLength = \strlen($delimiter); $delimiterPattern = '{'.preg_quote($delimiter).'(?![a-zA-Z0-9_\x80-\xff])}A'; while ($this->index < $this->len) { switch ($this->contents[$this->index]) { case "\t": case " ": $this->index += 1; continue 2; case $firstDelimiterChar: if ( substr($this->contents, $this->index, $delimiterLength) === $delimiter && $this->match($delimiterPattern) ) { $this->index += $delimiterLength; return; } break; } $this->skipToNewline(); $this->index += strspn($this->contents, "\r\n", $this->index); } } private function peek(string $char): bool { return $this->index + 1 < $this->len && $this->contents[$this->index + 1] === $char; } private function match(string $regex, ?array &$match = null): bool { return Preg::isMatchStrictGroups($regex, $this->contents, $match, 0, $this->index); } } clean(); unset($p); Preg::matchAll('{ (?: \b(?])(?Pclass|interface|trait'.$extraTypes.') \s++ (?P[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+) | \b(?])(?Pnamespace) (?P\s++[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\s*+\\\\\s*+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+)? \s*+ [\{;] ) }ix', $contents, $matches); $classes = []; $namespace = ''; for ($i = 0, $len = \count($matches['type']); $i < $len; ++$i) { if (isset($matches['ns'][$i]) && $matches['ns'][$i] !== '') { $namespace = str_replace([' ', "\t", "\r", "\n"], '', (string) $matches['nsname'][$i]) . '\\'; } else { $name = $matches['name'][$i]; \assert(\is_string($name)); if ($name === 'extends') { continue; } if ($name === 'implements') { continue; } if ($name[0] === ':') { $name = 'xhp'.substr(str_replace(['-', ':'], ['_', '__'], $name), 1); } elseif (strtolower((string) $matches['type'][$i]) === 'enum') { $colonPos = strrpos($name, ':'); if (false !== $colonPos) { $name = substr($name, 0, $colonPos); } } $className = ltrim($namespace . $name, '\\'); $classes[] = $className; } } return $classes; } private static function getExtraTypes(): string { static $extraTypes = null; if (null === $extraTypes) { $extraTypes = ''; $extraTypesArray = []; if (PHP_VERSION_ID >= 80100 || (\defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '3.3', '>='))) { $extraTypes .= '|enum'; $extraTypesArray = ['enum']; } PhpFileCleaner::setTypeConfig(array_merge(['class', 'interface', 'trait'], $extraTypesArray)); } return $extraTypes; } private static function isReadable(string $path) { if (is_readable($path)) { return true; } if (is_file($path)) { return false !== @file_get_contents($path, false, null, 0, 1); } return false; } } { "packages": [ { "name": "composer/ca-bundle", "version": "1.5.12", "version_normalized": "1.5.12.0", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", "reference": "00a2f4201641d5c53f7fc0195e6c8d9fcc321a78" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/composer/ca-bundle/zipball/00a2f4201641d5c53f7fc0195e6c8d9fcc321a78", "reference": "00a2f4201641d5c53f7fc0195e6c8d9fcc321a78", "shasum": "" }, "require": { "ext-openssl": "*", "ext-pcre": "*", "php": "^7.2 || ^8.0" }, "require-dev": { "phpstan/phpstan": "^1.10", "phpunit/phpunit": "^8 || ^9", "psr/log": "^1.0 || ^2.0 || ^3.0", "symfony/process": "^4.0 || ^5.0 || ^6.0 || ^7.0" }, "time": "2026-05-19T11:26:22+00:00", "type": "library", "extra": { "branch-alias": { "dev-main": "1.x-dev" } }, "installation-source": "dist", "autoload": { "psr-4": { "Composer\\CaBundle\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", "homepage": "http://seld.be" } ], "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", "keywords": [ "cabundle", "cacert", "certificate", "ssl", "tls" ], "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", "source": "https://github.com/composer/ca-bundle/tree/1.5.12" }, "funding": [ { "url": "https://packagist.com", "type": "custom" }, { "url": "https://github.com/composer", "type": "github" } ], "install-path": "./ca-bundle" }, { "name": "composer/class-map-generator", "version": "1.7.3", "version_normalized": "1.7.3.0", "source": { "type": "git", "url": "https://github.com/composer/class-map-generator.git", "reference": "86d8208fc3c649a3a999daf1a63c25201be2990f" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/composer/class-map-generator/zipball/86d8208fc3c649a3a999daf1a63c25201be2990f", "reference": "86d8208fc3c649a3a999daf1a63c25201be2990f", "shasum": "" }, "require": { "composer/pcre": "^2.1 || ^3.1", "php": "^7.2 || ^8.0", "symfony/finder": "^4.4 || ^5.3 || ^6 || ^7 || ^8" }, "require-dev": { "phpstan/phpstan": "^1.12 || ^2", "phpstan/phpstan-deprecation-rules": "^1 || ^2", "phpstan/phpstan-phpunit": "^1 || ^2", "phpstan/phpstan-strict-rules": "^1.1 || ^2", "phpunit/phpunit": "^8", "symfony/filesystem": "^5.4 || ^6 || ^7 || ^8" }, "time": "2026-05-05T09:17:07+00:00", "type": "library", "extra": { "branch-alias": { "dev-main": "1.x-dev" } }, "installation-source": "dist", "autoload": { "psr-4": { "Composer\\ClassMapGenerator\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", "homepage": "https://seld.be" } ], "description": "Utilities to scan PHP code and generate class maps.", "keywords": [ "classmap" ], "support": { "issues": "https://github.com/composer/class-map-generator/issues", "source": "https://github.com/composer/class-map-generator/tree/1.7.3" }, "funding": [ { "url": "https://packagist.com", "type": "custom" }, { "url": "https://github.com/composer", "type": "github" } ], "install-path": "./class-map-generator" }, { "name": "composer/metadata-minifier", "version": "1.0.0", "version_normalized": "1.0.0.0", "source": { "type": "git", "url": "https://github.com/composer/metadata-minifier.git", "reference": "c549d23829536f0d0e984aaabbf02af91f443207" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/composer/metadata-minifier/zipball/c549d23829536f0d0e984aaabbf02af91f443207", "reference": "c549d23829536f0d0e984aaabbf02af91f443207", "shasum": "" }, "require": { "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { "composer/composer": "^2", "phpstan/phpstan": "^0.12.55", "symfony/phpunit-bridge": "^4.2 || ^5" }, "time": "2021-04-07T13:37:33+00:00", "type": "library", "extra": { "branch-alias": { "dev-main": "1.x-dev" } }, "installation-source": "dist", "autoload": { "psr-4": { "Composer\\MetadataMinifier\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", "homepage": "http://seld.be" } ], "description": "Small utility library that handles metadata minification and expansion.", "keywords": [ "composer", "compression" ], "support": { "issues": "https://github.com/composer/metadata-minifier/issues", "source": "https://github.com/composer/metadata-minifier/tree/1.0.0" }, "funding": [ { "url": "https://packagist.com", "type": "custom" }, { "url": "https://github.com/composer", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/composer/composer", "type": "tidelift" } ], "install-path": "./metadata-minifier" }, { "name": "composer/pcre", "version": "2.3.2", "version_normalized": "2.3.2.0", "source": { "type": "git", "url": "https://github.com/composer/pcre.git", "reference": "ebb81df8f52b40172d14062ae96a06939d80a069" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/composer/pcre/zipball/ebb81df8f52b40172d14062ae96a06939d80a069", "reference": "ebb81df8f52b40172d14062ae96a06939d80a069", "shasum": "" }, "require": { "php": "^7.2 || ^8.0" }, "conflict": { "phpstan/phpstan": "<1.11.10" }, "require-dev": { "phpstan/phpstan": "^1.12 || ^2", "phpstan/phpstan-strict-rules": "^1 || ^2", "phpunit/phpunit": "^8 || ^9" }, "time": "2024-11-12T16:24:47+00:00", "type": "library", "extra": { "phpstan": { "includes": [ "extension.neon" ] }, "branch-alias": { "dev-main": "2.x-dev" } }, "installation-source": "dist", "autoload": { "psr-4": { "Composer\\Pcre\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", "homepage": "http://seld.be" } ], "description": "PCRE wrapping library that offers type-safe preg_* replacements.", "keywords": [ "PCRE", "preg", "regex", "regular expression" ], "support": { "issues": "https://github.com/composer/pcre/issues", "source": "https://github.com/composer/pcre/tree/2.3.2" }, "funding": [ { "url": "https://packagist.com", "type": "custom" }, { "url": "https://github.com/composer", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/composer/composer", "type": "tidelift" } ], "install-path": "./pcre" }, { "name": "composer/semver", "version": "3.4.4", "version_normalized": "3.4.4.0", "source": { "type": "git", "url": "https://github.com/composer/semver.git", "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", "shasum": "" }, "require": { "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { "phpstan/phpstan": "^1.11", "symfony/phpunit-bridge": "^3 || ^7" }, "time": "2025-08-20T19:15:30+00:00", "type": "library", "extra": { "branch-alias": { "dev-main": "3.x-dev" } }, "installation-source": "dist", "autoload": { "psr-4": { "Composer\\Semver\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Nils Adermann", "email": "naderman@naderman.de", "homepage": "http://www.naderman.de" }, { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", "homepage": "http://seld.be" }, { "name": "Rob Bast", "email": "rob.bast@gmail.com", "homepage": "http://robbast.nl" } ], "description": "Semver library that offers utilities, version constraint parsing and validation.", "keywords": [ "semantic", "semver", "validation", "versioning" ], "support": { "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", "source": "https://github.com/composer/semver/tree/3.4.4" }, "funding": [ { "url": "https://packagist.com", "type": "custom" }, { "url": "https://github.com/composer", "type": "github" } ], "install-path": "./semver" }, { "name": "composer/spdx-licenses", "version": "1.6.0", "version_normalized": "1.6.0.0", "source": { "type": "git", "url": "https://github.com/composer/spdx-licenses.git", "reference": "5ecd0cb4177696f9fd48f1605dda81db3dee7889" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/5ecd0cb4177696f9fd48f1605dda81db3dee7889", "reference": "5ecd0cb4177696f9fd48f1605dda81db3dee7889", "shasum": "" }, "require": { "php": "^7.2 || ^8.0" }, "require-dev": { "phpstan/phpstan": "^1.11", "symfony/phpunit-bridge": "^6.4.25 || ^7.3.3 || ^8.0" }, "time": "2026-04-08T20:18:39+00:00", "type": "library", "extra": { "branch-alias": { "dev-main": "1.x-dev" } }, "installation-source": "dist", "autoload": { "psr-4": { "Composer\\Spdx\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Nils Adermann", "email": "naderman@naderman.de", "homepage": "http://www.naderman.de" }, { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", "homepage": "http://seld.be" }, { "name": "Rob Bast", "email": "rob.bast@gmail.com", "homepage": "http://robbast.nl" } ], "description": "SPDX licenses list and validation library.", "keywords": [ "license", "spdx", "validator" ], "support": { "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/spdx-licenses/issues", "source": "https://github.com/composer/spdx-licenses/tree/1.6.0" }, "funding": [ { "url": "https://packagist.com", "type": "custom" }, { "url": "https://github.com/composer", "type": "github" } ], "install-path": "./spdx-licenses" }, { "name": "composer/xdebug-handler", "version": "3.0.5", "version_normalized": "3.0.5.0", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", "shasum": "" }, "require": { "composer/pcre": "^1 || ^2 || ^3", "php": "^7.2.5 || ^8.0", "psr/log": "^1 || ^2 || ^3" }, "require-dev": { "phpstan/phpstan": "^1.0", "phpstan/phpstan-strict-rules": "^1.1", "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" }, "time": "2024-05-06T16:37:16+00:00", "type": "library", "installation-source": "dist", "autoload": { "psr-4": { "Composer\\XdebugHandler\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "John Stevenson", "email": "john-stevenson@blueyonder.co.uk" } ], "description": "Restarts a process without Xdebug.", "keywords": [ "Xdebug", "performance" ], "support": { "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/xdebug-handler/issues", "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" }, "funding": [ { "url": "https://packagist.com", "type": "custom" }, { "url": "https://github.com/composer", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/composer/composer", "type": "tidelift" } ], "install-path": "./xdebug-handler" }, { "name": "justinrainbow/json-schema", "version": "6.8.2", "version_normalized": "6.8.2.0", "source": { "type": "git", "url": "https://github.com/jsonrainbow/json-schema.git", "reference": "2c89ebb95ca9cedc9347f780333f7b25792dcb76" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/2c89ebb95ca9cedc9347f780333f7b25792dcb76", "reference": "2c89ebb95ca9cedc9347f780333f7b25792dcb76", "shasum": "" }, "require": { "ext-json": "*", "marc-mabe/php-enum": "^4.4", "php": "^7.2 || ^8.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "3.3.0", "json-schema/json-schema-test-suite": "dev-main", "marc-mabe/php-enum-phpstan": "^2.0", "phpspec/prophecy": "^1.19", "phpstan/phpstan": "^1.12", "phpunit/phpunit": "^8.5" }, "time": "2026-05-05T05:39:01+00:00", "bin": [ "bin/validate-json" ], "type": "library", "extra": { "branch-alias": { "dev-master": "6.x-dev" } }, "installation-source": "dist", "autoload": { "psr-4": { "JsonSchema\\": "src/JsonSchema/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Bruno Prieto Reis", "email": "bruno.p.reis@gmail.com" }, { "name": "Justin Rainbow", "email": "justin.rainbow@gmail.com" }, { "name": "Igor Wiedler", "email": "igor@wiedler.ch" }, { "name": "Robert Schönthal", "email": "seroscho@googlemail.com" } ], "description": "A library to validate a json schema.", "homepage": "https://github.com/jsonrainbow/json-schema", "keywords": [ "json", "schema" ], "support": { "issues": "https://github.com/jsonrainbow/json-schema/issues", "source": "https://github.com/jsonrainbow/json-schema/tree/6.8.2" }, "install-path": "../justinrainbow/json-schema" }, { "name": "marc-mabe/php-enum", "version": "v4.7.2", "version_normalized": "4.7.2.0", "source": { "type": "git", "url": "https://github.com/marc-mabe/php-enum.git", "reference": "bb426fcdd65c60fb3638ef741e8782508fda7eef" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/marc-mabe/php-enum/zipball/bb426fcdd65c60fb3638ef741e8782508fda7eef", "reference": "bb426fcdd65c60fb3638ef741e8782508fda7eef", "shasum": "" }, "require": { "ext-reflection": "*", "php": "^7.1 | ^8.0" }, "require-dev": { "phpbench/phpbench": "^0.16.10 || ^1.0.4", "phpstan/phpstan": "^1.3.1", "phpunit/phpunit": "^7.5.20 | ^8.5.22 | ^9.5.11", "vimeo/psalm": "^4.17.0 | ^5.26.1" }, "time": "2025-09-14T11:18:39+00:00", "type": "library", "extra": { "branch-alias": { "dev-3.x": "3.2-dev", "dev-master": "4.7-dev" } }, "installation-source": "dist", "autoload": { "psr-4": { "MabeEnum\\": "src/" }, "classmap": [ "stubs/Stringable.php" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], "authors": [ { "name": "Marc Bennewitz", "email": "dev@mabe.berlin", "homepage": "https://mabe.berlin/", "role": "Lead" } ], "description": "Simple and fast implementation of enumerations with native PHP", "homepage": "https://github.com/marc-mabe/php-enum", "keywords": [ "enum", "enum-map", "enum-set", "enumeration", "enumerator", "enummap", "enumset", "map", "set", "type", "type-hint", "typehint" ], "support": { "issues": "https://github.com/marc-mabe/php-enum/issues", "source": "https://github.com/marc-mabe/php-enum/tree/v4.7.2" }, "install-path": "../marc-mabe/php-enum" }, { "name": "psr/container", "version": "1.1.1", "version_normalized": "1.1.1.0", "source": { "type": "git", "url": "https://github.com/php-fig/container.git", "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf", "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf", "shasum": "" }, "require": { "php": ">=7.2.0" }, "time": "2021-03-05T17:36:06+00:00", "type": "library", "installation-source": "dist", "autoload": { "psr-4": { "Psr\\Container\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "PHP-FIG", "homepage": "https://www.php-fig.org/" } ], "description": "Common Container Interface (PHP FIG PSR-11)", "homepage": "https://github.com/php-fig/container", "keywords": [ "PSR-11", "container", "container-interface", "container-interop", "psr" ], "support": { "issues": "https://github.com/php-fig/container/issues", "source": "https://github.com/php-fig/container/tree/1.1.1" }, "install-path": "../psr/container" }, { "name": "psr/log", "version": "1.1.4", "version_normalized": "1.1.4.0", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", "reference": "d49695b909c3b7628b6289db5479a1c204601f11" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", "reference": "d49695b909c3b7628b6289db5479a1c204601f11", "shasum": "" }, "require": { "php": ">=5.3.0" }, "time": "2021-05-03T11:20:27+00:00", "type": "library", "extra": { "branch-alias": { "dev-master": "1.1.x-dev" } }, "installation-source": "dist", "autoload": { "psr-4": { "Psr\\Log\\": "Psr/Log/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "PHP-FIG", "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for logging libraries", "homepage": "https://github.com/php-fig/log", "keywords": [ "log", "psr", "psr-3" ], "support": { "source": "https://github.com/php-fig/log/tree/1.1.4" }, "install-path": "../psr/log" }, { "name": "react/promise", "version": "v3.3.0", "version_normalized": "3.3.0.0", "source": { "type": "git", "url": "https://github.com/reactphp/promise.git", "reference": "23444f53a813a3296c1368bb104793ce8d88f04a" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a", "reference": "23444f53a813a3296c1368bb104793ce8d88f04a", "shasum": "" }, "require": { "php": ">=7.1.0" }, "require-dev": { "phpstan/phpstan": "1.12.28 || 1.4.10", "phpunit/phpunit": "^9.6 || ^7.5" }, "time": "2025-08-19T18:57:03+00:00", "type": "library", "installation-source": "dist", "autoload": { "files": [ "src/functions_include.php" ], "psr-4": { "React\\Promise\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Jan Sorgalla", "email": "jsorgalla@gmail.com", "homepage": "https://sorgalla.com/" }, { "name": "Christian Lück", "email": "christian@clue.engineering", "homepage": "https://clue.engineering/" }, { "name": "Cees-Jan Kiewiet", "email": "reactphp@ceesjankiewiet.nl", "homepage": "https://wyrihaximus.net/" }, { "name": "Chris Boden", "email": "cboden@gmail.com", "homepage": "https://cboden.dev/" } ], "description": "A lightweight implementation of CommonJS Promises/A for PHP", "keywords": [ "promise", "promises" ], "support": { "issues": "https://github.com/reactphp/promise/issues", "source": "https://github.com/reactphp/promise/tree/v3.3.0" }, "funding": [ { "url": "https://opencollective.com/reactphp", "type": "open_collective" } ], "install-path": "../react/promise" }, { "name": "seld/jsonlint", "version": "1.11.0", "version_normalized": "1.11.0.0", "source": { "type": "git", "url": "https://github.com/Seldaek/jsonlint.git", "reference": "1748aaf847fc731cfad7725aec413ee46f0cc3a2" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/1748aaf847fc731cfad7725aec413ee46f0cc3a2", "reference": "1748aaf847fc731cfad7725aec413ee46f0cc3a2", "shasum": "" }, "require": { "php": "^5.3 || ^7.0 || ^8.0" }, "require-dev": { "phpstan/phpstan": "^1.11", "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^8.5.13" }, "time": "2024-07-11T14:55:45+00:00", "bin": [ "bin/jsonlint" ], "type": "library", "installation-source": "dist", "autoload": { "psr-4": { "Seld\\JsonLint\\": "src/Seld/JsonLint/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", "homepage": "https://seld.be" } ], "description": "JSON Linter", "keywords": [ "json", "linter", "parser", "validator" ], "support": { "issues": "https://github.com/Seldaek/jsonlint/issues", "source": "https://github.com/Seldaek/jsonlint/tree/1.11.0" }, "funding": [ { "url": "https://github.com/Seldaek", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint", "type": "tidelift" } ], "install-path": "../seld/jsonlint" }, { "name": "seld/phar-utils", "version": "1.2.1", "version_normalized": "1.2.1.0", "source": { "type": "git", "url": "https://github.com/Seldaek/phar-utils.git", "reference": "ea2f4014f163c1be4c601b9b7bd6af81ba8d701c" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/ea2f4014f163c1be4c601b9b7bd6af81ba8d701c", "reference": "ea2f4014f163c1be4c601b9b7bd6af81ba8d701c", "shasum": "" }, "require": { "php": ">=5.3" }, "time": "2022-08-31T10:31:18+00:00", "type": "library", "extra": { "branch-alias": { "dev-master": "1.x-dev" } }, "installation-source": "dist", "autoload": { "psr-4": { "Seld\\PharUtils\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be" } ], "description": "PHAR file format utilities, for when PHP phars you up", "keywords": [ "phar" ], "support": { "issues": "https://github.com/Seldaek/phar-utils/issues", "source": "https://github.com/Seldaek/phar-utils/tree/1.2.1" }, "install-path": "../seld/phar-utils" }, { "name": "seld/signal-handler", "version": "2.0.2", "version_normalized": "2.0.2.0", "source": { "type": "git", "url": "https://github.com/Seldaek/signal-handler.git", "reference": "04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/Seldaek/signal-handler/zipball/04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98", "reference": "04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98", "shasum": "" }, "require": { "php": ">=7.2.0" }, "require-dev": { "phpstan/phpstan": "^1", "phpstan/phpstan-deprecation-rules": "^1.0", "phpstan/phpstan-phpunit": "^1", "phpstan/phpstan-strict-rules": "^1.3", "phpunit/phpunit": "^7.5.20 || ^8.5.23", "psr/log": "^1 || ^2 || ^3" }, "time": "2023-09-03T09:24:00+00:00", "type": "library", "extra": { "branch-alias": { "dev-main": "2.x-dev" } }, "installation-source": "dist", "autoload": { "psr-4": { "Seld\\Signal\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", "homepage": "http://seld.be" } ], "description": "Simple unix signal handler that silently fails where signals are not supported for easy cross-platform development", "keywords": [ "posix", "sigint", "signal", "sigterm", "unix" ], "support": { "issues": "https://github.com/Seldaek/signal-handler/issues", "source": "https://github.com/Seldaek/signal-handler/tree/2.0.2" }, "install-path": "../seld/signal-handler" }, { "name": "symfony/console", "version": "v5.4.47", "version_normalized": "5.4.47.0", "source": { "type": "git", "url": "https://github.com/symfony/console.git", "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/symfony/console/zipball/c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed", "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed", "shasum": "" }, "require": { "php": ">=7.2.5", "symfony/deprecation-contracts": "^2.1|^3", "symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-php73": "^1.9", "symfony/polyfill-php80": "^1.16", "symfony/service-contracts": "^1.1|^2|^3", "symfony/string": "^5.1|^6.0" }, "conflict": { "psr/log": ">=3", "symfony/dependency-injection": "<4.4", "symfony/dotenv": "<5.1", "symfony/event-dispatcher": "<4.4", "symfony/lock": "<4.4", "symfony/process": "<4.4" }, "provide": { "psr/log-implementation": "1.0|2.0" }, "require-dev": { "psr/log": "^1|^2", "symfony/config": "^4.4|^5.0|^6.0", "symfony/dependency-injection": "^4.4|^5.0|^6.0", "symfony/event-dispatcher": "^4.4|^5.0|^6.0", "symfony/lock": "^4.4|^5.0|^6.0", "symfony/process": "^4.4|^5.0|^6.0", "symfony/var-dumper": "^4.4|^5.0|^6.0" }, "suggest": { "psr/log": "For using the console logger", "symfony/event-dispatcher": "", "symfony/lock": "", "symfony/process": "" }, "time": "2024-11-06T11:30:55+00:00", "type": "library", "installation-source": "dist", "autoload": { "psr-4": { "Symfony\\Component\\Console\\": "" }, "exclude-from-classmap": [ "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Fabien Potencier", "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], "description": "Eases the creation of beautiful and testable command line interfaces", "homepage": "https://symfony.com", "keywords": [ "cli", "command-line", "console", "terminal" ], "support": { "source": "https://github.com/symfony/console/tree/v5.4.47" }, "funding": [ { "url": "https://symfony.com/sponsor", "type": "custom" }, { "url": "https://github.com/fabpot", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], "install-path": "../symfony/console" }, { "name": "symfony/deprecation-contracts", "version": "v2.5.4", "version_normalized": "2.5.4.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/605389f2a7e5625f273b53960dc46aeaf9c62918", "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918", "shasum": "" }, "require": { "php": ">=7.1" }, "time": "2024-09-25T14:11:13+00:00", "type": "library", "extra": { "thanks": { "url": "https://github.com/symfony/contracts", "name": "symfony/contracts" }, "branch-alias": { "dev-main": "2.5-dev" } }, "installation-source": "dist", "autoload": { "files": [ "function.php" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Nicolas Grekas", "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.4" }, "funding": [ { "url": "https://symfony.com/sponsor", "type": "custom" }, { "url": "https://github.com/fabpot", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], "install-path": "../symfony/deprecation-contracts" }, { "name": "symfony/filesystem", "version": "v5.4.45", "version_normalized": "5.4.45.0", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", "reference": "57c8294ed37d4a055b77057827c67f9558c95c54" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/symfony/filesystem/zipball/57c8294ed37d4a055b77057827c67f9558c95c54", "reference": "57c8294ed37d4a055b77057827c67f9558c95c54", "shasum": "" }, "require": { "php": ">=7.2.5", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.8", "symfony/polyfill-php80": "^1.16" }, "require-dev": { "symfony/process": "^5.4|^6.4" }, "time": "2024-10-22T13:05:35+00:00", "type": "library", "installation-source": "dist", "autoload": { "psr-4": { "Symfony\\Component\\Filesystem\\": "" }, "exclude-from-classmap": [ "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Fabien Potencier", "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { "source": "https://github.com/symfony/filesystem/tree/v5.4.45" }, "funding": [ { "url": "https://symfony.com/sponsor", "type": "custom" }, { "url": "https://github.com/fabpot", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], "install-path": "../symfony/filesystem" }, { "name": "symfony/finder", "version": "v5.4.45", "version_normalized": "5.4.45.0", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", "reference": "63741784cd7b9967975eec610b256eed3ede022b" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/symfony/finder/zipball/63741784cd7b9967975eec610b256eed3ede022b", "reference": "63741784cd7b9967975eec610b256eed3ede022b", "shasum": "" }, "require": { "php": ">=7.2.5", "symfony/deprecation-contracts": "^2.1|^3", "symfony/polyfill-php80": "^1.16" }, "time": "2024-09-28T13:32:08+00:00", "type": "library", "installation-source": "dist", "autoload": { "psr-4": { "Symfony\\Component\\Finder\\": "" }, "exclude-from-classmap": [ "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Fabien Potencier", "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { "source": "https://github.com/symfony/finder/tree/v5.4.45" }, "funding": [ { "url": "https://symfony.com/sponsor", "type": "custom" }, { "url": "https://github.com/fabpot", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], "install-path": "../symfony/finder" }, { "name": "symfony/polyfill-ctype", "version": "v1.37.0", "version_normalized": "1.37.0.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", "reference": "141046a8f9477948ff284fa65be2095baafb94f2" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", "reference": "141046a8f9477948ff284fa65be2095baafb94f2", "shasum": "" }, "require": { "php": ">=7.2" }, "provide": { "ext-ctype": "*" }, "suggest": { "ext-ctype": "For best performance" }, "time": "2026-04-10T16:19:22+00:00", "type": "library", "extra": { "thanks": { "url": "https://github.com/symfony/polyfill", "name": "symfony/polyfill" } }, "installation-source": "dist", "autoload": { "files": [ "bootstrap.php" ], "psr-4": { "Symfony\\Polyfill\\Ctype\\": "" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Gert de Pagter", "email": "BackEndTea@gmail.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], "description": "Symfony polyfill for ctype functions", "homepage": "https://symfony.com", "keywords": [ "compatibility", "ctype", "polyfill", "portable" ], "support": { "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0" }, "funding": [ { "url": "https://symfony.com/sponsor", "type": "custom" }, { "url": "https://github.com/fabpot", "type": "github" }, { "url": "https://github.com/nicolas-grekas", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], "install-path": "../symfony/polyfill-ctype" }, { "name": "symfony/polyfill-intl-grapheme", "version": "v1.38.1", "version_normalized": "1.38.1.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", "reference": "e9247d281d694a5120554d9afaf54e070e88a603" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/e9247d281d694a5120554d9afaf54e070e88a603", "reference": "e9247d281d694a5120554d9afaf54e070e88a603", "shasum": "" }, "require": { "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" }, "time": "2026-05-26T05:58:03+00:00", "type": "library", "extra": { "thanks": { "url": "https://github.com/symfony/polyfill", "name": "symfony/polyfill" } }, "installation-source": "dist", "autoload": { "files": [ "bootstrap.php" ], "psr-4": { "Symfony\\Polyfill\\Intl\\Grapheme\\": "" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Nicolas Grekas", "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], "description": "Symfony polyfill for intl's grapheme_* functions", "homepage": "https://symfony.com", "keywords": [ "compatibility", "grapheme", "intl", "polyfill", "portable", "shim" ], "support": { "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.38.1" }, "funding": [ { "url": "https://symfony.com/sponsor", "type": "custom" }, { "url": "https://github.com/fabpot", "type": "github" }, { "url": "https://github.com/nicolas-grekas", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], "install-path": "../symfony/polyfill-intl-grapheme" }, { "name": "symfony/polyfill-intl-normalizer", "version": "v1.38.0", "version_normalized": "1.38.0.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/2d446c214bdbe5b71bde5011b060a05fece3ae6b", "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b", "shasum": "" }, "require": { "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" }, "time": "2026-05-25T13:48:31+00:00", "type": "library", "extra": { "thanks": { "url": "https://github.com/symfony/polyfill", "name": "symfony/polyfill" } }, "installation-source": "dist", "autoload": { "files": [ "bootstrap.php" ], "psr-4": { "Symfony\\Polyfill\\Intl\\Normalizer\\": "" }, "classmap": [ "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Nicolas Grekas", "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], "description": "Symfony polyfill for intl's Normalizer class and related functions", "homepage": "https://symfony.com", "keywords": [ "compatibility", "intl", "normalizer", "polyfill", "portable", "shim" ], "support": { "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.38.0" }, "funding": [ { "url": "https://symfony.com/sponsor", "type": "custom" }, { "url": "https://github.com/fabpot", "type": "github" }, { "url": "https://github.com/nicolas-grekas", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], "install-path": "../symfony/polyfill-intl-normalizer" }, { "name": "symfony/polyfill-mbstring", "version": "v1.38.1", "version_normalized": "1.38.1.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", "reference": "14c5439eec4ccff081ac14eca2dc57feb2a66d92" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/14c5439eec4ccff081ac14eca2dc57feb2a66d92", "reference": "14c5439eec4ccff081ac14eca2dc57feb2a66d92", "shasum": "" }, "require": { "ext-iconv": "*", "php": ">=7.2" }, "provide": { "ext-mbstring": "*" }, "suggest": { "ext-mbstring": "For best performance" }, "time": "2026-05-26T12:51:13+00:00", "type": "library", "extra": { "thanks": { "url": "https://github.com/symfony/polyfill", "name": "symfony/polyfill" } }, "installation-source": "dist", "autoload": { "files": [ "bootstrap.php" ], "psr-4": { "Symfony\\Polyfill\\Mbstring\\": "" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Nicolas Grekas", "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], "description": "Symfony polyfill for the Mbstring extension", "homepage": "https://symfony.com", "keywords": [ "compatibility", "mbstring", "polyfill", "portable", "shim" ], "support": { "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.1" }, "funding": [ { "url": "https://symfony.com/sponsor", "type": "custom" }, { "url": "https://github.com/fabpot", "type": "github" }, { "url": "https://github.com/nicolas-grekas", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], "install-path": "../symfony/polyfill-mbstring" }, { "name": "symfony/polyfill-php73", "version": "v1.37.0", "version_normalized": "1.37.0.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", "shasum": "" }, "require": { "php": ">=7.2" }, "time": "2024-09-09T11:45:10+00:00", "type": "library", "extra": { "thanks": { "url": "https://github.com/symfony/polyfill", "name": "symfony/polyfill" } }, "installation-source": "dist", "autoload": { "files": [ "bootstrap.php" ], "psr-4": { "Symfony\\Polyfill\\Php73\\": "" }, "classmap": [ "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Nicolas Grekas", "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", "polyfill", "portable", "shim" ], "support": { "source": "https://github.com/symfony/polyfill-php73/tree/v1.37.0" }, "funding": [ { "url": "https://symfony.com/sponsor", "type": "custom" }, { "url": "https://github.com/fabpot", "type": "github" }, { "url": "https://github.com/nicolas-grekas", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], "install-path": "../symfony/polyfill-php73" }, { "name": "symfony/polyfill-php80", "version": "v1.37.0", "version_normalized": "1.37.0.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411", "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411", "shasum": "" }, "require": { "php": ">=7.2" }, "time": "2026-04-10T16:19:22+00:00", "type": "library", "extra": { "thanks": { "url": "https://github.com/symfony/polyfill", "name": "symfony/polyfill" } }, "installation-source": "dist", "autoload": { "files": [ "bootstrap.php" ], "psr-4": { "Symfony\\Polyfill\\Php80\\": "" }, "classmap": [ "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Ion Bazan", "email": "ion.bazan@gmail.com" }, { "name": "Nicolas Grekas", "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", "polyfill", "portable", "shim" ], "support": { "source": "https://github.com/symfony/polyfill-php80/tree/v1.37.0" }, "funding": [ { "url": "https://symfony.com/sponsor", "type": "custom" }, { "url": "https://github.com/fabpot", "type": "github" }, { "url": "https://github.com/nicolas-grekas", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], "install-path": "../symfony/polyfill-php80" }, { "name": "symfony/polyfill-php81", "version": "v1.38.1", "version_normalized": "1.38.1.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", "reference": "6bfb9c766cacffbc8e118cb87217d08ed84e5cd7" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/6bfb9c766cacffbc8e118cb87217d08ed84e5cd7", "reference": "6bfb9c766cacffbc8e118cb87217d08ed84e5cd7", "shasum": "" }, "require": { "php": ">=7.2" }, "time": "2026-05-26T12:45:58+00:00", "type": "library", "extra": { "thanks": { "url": "https://github.com/symfony/polyfill", "name": "symfony/polyfill" } }, "installation-source": "dist", "autoload": { "files": [ "bootstrap.php" ], "psr-4": { "Symfony\\Polyfill\\Php81\\": "" }, "classmap": [ "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Nicolas Grekas", "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", "polyfill", "portable", "shim" ], "support": { "source": "https://github.com/symfony/polyfill-php81/tree/v1.38.1" }, "funding": [ { "url": "https://symfony.com/sponsor", "type": "custom" }, { "url": "https://github.com/fabpot", "type": "github" }, { "url": "https://github.com/nicolas-grekas", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], "install-path": "../symfony/polyfill-php81" }, { "name": "symfony/polyfill-php84", "version": "v1.38.1", "version_normalized": "1.38.1.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php84.git", "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", "shasum": "" }, "require": { "php": ">=7.2" }, "time": "2026-05-26T12:51:13+00:00", "type": "library", "extra": { "thanks": { "url": "https://github.com/symfony/polyfill", "name": "symfony/polyfill" } }, "installation-source": "dist", "autoload": { "files": [ "bootstrap.php" ], "psr-4": { "Symfony\\Polyfill\\Php84\\": "" }, "classmap": [ "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Nicolas Grekas", "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", "polyfill", "portable", "shim" ], "support": { "source": "https://github.com/symfony/polyfill-php84/tree/v1.38.1" }, "funding": [ { "url": "https://symfony.com/sponsor", "type": "custom" }, { "url": "https://github.com/fabpot", "type": "github" }, { "url": "https://github.com/nicolas-grekas", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], "install-path": "../symfony/polyfill-php84" }, { "name": "symfony/process", "version": "v5.4.51", "version_normalized": "5.4.51.0", "source": { "type": "git", "url": "https://github.com/symfony/process.git", "reference": "467bfc56f18f5ef6d5ccb09324d7e988c1c0a98f" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/symfony/process/zipball/467bfc56f18f5ef6d5ccb09324d7e988c1c0a98f", "reference": "467bfc56f18f5ef6d5ccb09324d7e988c1c0a98f", "shasum": "" }, "require": { "php": ">=7.2.5", "symfony/polyfill-php80": "^1.16" }, "time": "2026-01-26T15:53:37+00:00", "type": "library", "installation-source": "dist", "autoload": { "psr-4": { "Symfony\\Component\\Process\\": "" }, "exclude-from-classmap": [ "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Fabien Potencier", "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { "source": "https://github.com/symfony/process/tree/v5.4.51" }, "funding": [ { "url": "https://symfony.com/sponsor", "type": "custom" }, { "url": "https://github.com/fabpot", "type": "github" }, { "url": "https://github.com/nicolas-grekas", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], "install-path": "../symfony/process" }, { "name": "symfony/service-contracts", "version": "v2.5.4", "version_normalized": "2.5.4.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", "reference": "f37b419f7aea2e9abf10abd261832cace12e3300" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f37b419f7aea2e9abf10abd261832cace12e3300", "reference": "f37b419f7aea2e9abf10abd261832cace12e3300", "shasum": "" }, "require": { "php": ">=7.2.5", "psr/container": "^1.1", "symfony/deprecation-contracts": "^2.1|^3" }, "conflict": { "ext-psr": "<1.1|>=2" }, "suggest": { "symfony/service-implementation": "" }, "time": "2024-09-25T14:11:13+00:00", "type": "library", "extra": { "thanks": { "url": "https://github.com/symfony/contracts", "name": "symfony/contracts" }, "branch-alias": { "dev-main": "2.5-dev" } }, "installation-source": "dist", "autoload": { "psr-4": { "Symfony\\Contracts\\Service\\": "" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Nicolas Grekas", "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], "description": "Generic abstractions related to writing services", "homepage": "https://symfony.com", "keywords": [ "abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards" ], "support": { "source": "https://github.com/symfony/service-contracts/tree/v2.5.4" }, "funding": [ { "url": "https://symfony.com/sponsor", "type": "custom" }, { "url": "https://github.com/fabpot", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], "install-path": "../symfony/service-contracts" }, { "name": "symfony/string", "version": "v5.4.47", "version_normalized": "5.4.47.0", "source": { "type": "git", "url": "https://github.com/symfony/string.git", "reference": "136ca7d72f72b599f2631aca474a4f8e26719799" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/symfony/string/zipball/136ca7d72f72b599f2631aca474a4f8e26719799", "reference": "136ca7d72f72b599f2631aca474a4f8e26719799", "shasum": "" }, "require": { "php": ">=7.2.5", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-intl-grapheme": "~1.0", "symfony/polyfill-intl-normalizer": "~1.0", "symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-php80": "~1.15" }, "conflict": { "symfony/translation-contracts": ">=3.0" }, "require-dev": { "symfony/error-handler": "^4.4|^5.0|^6.0", "symfony/http-client": "^4.4|^5.0|^6.0", "symfony/translation-contracts": "^1.1|^2", "symfony/var-exporter": "^4.4|^5.0|^6.0" }, "time": "2024-11-10T20:33:58+00:00", "type": "library", "installation-source": "dist", "autoload": { "files": [ "Resources/functions.php" ], "psr-4": { "Symfony\\Component\\String\\": "" }, "exclude-from-classmap": [ "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Nicolas Grekas", "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", "homepage": "https://symfony.com", "keywords": [ "grapheme", "i18n", "string", "unicode", "utf-8", "utf8" ], "support": { "source": "https://github.com/symfony/string/tree/v5.4.47" }, "funding": [ { "url": "https://symfony.com/sponsor", "type": "custom" }, { "url": "https://github.com/fabpot", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], "install-path": "../symfony/string" } ], "dev": false, "dev-package-names": [] } array( 'name' => 'composer/composer', 'pretty_version' => '2.10.0', 'version' => '2.10.0.0', 'reference' => 'c13824d95608b15913a7c0def0a3dea4474b71fc', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'dev' => false, ), 'versions' => array( 'composer/ca-bundle' => array( 'pretty_version' => '1.5.12', 'version' => '1.5.12.0', 'reference' => '00a2f4201641d5c53f7fc0195e6c8d9fcc321a78', 'type' => 'library', 'install_path' => __DIR__ . '/./ca-bundle', 'aliases' => array(), 'dev_requirement' => false, ), 'composer/class-map-generator' => array( 'pretty_version' => '1.7.3', 'version' => '1.7.3.0', 'reference' => '86d8208fc3c649a3a999daf1a63c25201be2990f', 'type' => 'library', 'install_path' => __DIR__ . '/./class-map-generator', 'aliases' => array(), 'dev_requirement' => false, ), 'composer/composer' => array( 'pretty_version' => '2.10.0', 'version' => '2.10.0.0', 'reference' => 'c13824d95608b15913a7c0def0a3dea4474b71fc', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'dev_requirement' => false, ), 'composer/metadata-minifier' => array( 'pretty_version' => '1.0.0', 'version' => '1.0.0.0', 'reference' => 'c549d23829536f0d0e984aaabbf02af91f443207', 'type' => 'library', 'install_path' => __DIR__ . '/./metadata-minifier', 'aliases' => array(), 'dev_requirement' => false, ), 'composer/pcre' => array( 'pretty_version' => '2.3.2', 'version' => '2.3.2.0', 'reference' => 'ebb81df8f52b40172d14062ae96a06939d80a069', 'type' => 'library', 'install_path' => __DIR__ . '/./pcre', 'aliases' => array(), 'dev_requirement' => false, ), 'composer/semver' => array( 'pretty_version' => '3.4.4', 'version' => '3.4.4.0', 'reference' => '198166618906cb2de69b95d7d47e5fa8aa1b2b95', 'type' => 'library', 'install_path' => __DIR__ . '/./semver', 'aliases' => array(), 'dev_requirement' => false, ), 'composer/spdx-licenses' => array( 'pretty_version' => '1.6.0', 'version' => '1.6.0.0', 'reference' => '5ecd0cb4177696f9fd48f1605dda81db3dee7889', 'type' => 'library', 'install_path' => __DIR__ . '/./spdx-licenses', 'aliases' => array(), 'dev_requirement' => false, ), 'composer/xdebug-handler' => array( 'pretty_version' => '3.0.5', 'version' => '3.0.5.0', 'reference' => '6c1925561632e83d60a44492e0b344cf48ab85ef', 'type' => 'library', 'install_path' => __DIR__ . '/./xdebug-handler', 'aliases' => array(), 'dev_requirement' => false, ), 'justinrainbow/json-schema' => array( 'pretty_version' => '6.8.2', 'version' => '6.8.2.0', 'reference' => '2c89ebb95ca9cedc9347f780333f7b25792dcb76', 'type' => 'library', 'install_path' => __DIR__ . '/../justinrainbow/json-schema', 'aliases' => array(), 'dev_requirement' => false, ), 'marc-mabe/php-enum' => array( 'pretty_version' => 'v4.7.2', 'version' => '4.7.2.0', 'reference' => 'bb426fcdd65c60fb3638ef741e8782508fda7eef', 'type' => 'library', 'install_path' => __DIR__ . '/../marc-mabe/php-enum', 'aliases' => array(), 'dev_requirement' => false, ), 'psr/container' => array( 'pretty_version' => '1.1.1', 'version' => '1.1.1.0', 'reference' => '8622567409010282b7aeebe4bb841fe98b58dcaf', 'type' => 'library', 'install_path' => __DIR__ . '/../psr/container', 'aliases' => array(), 'dev_requirement' => false, ), 'psr/log' => array( 'pretty_version' => '1.1.4', 'version' => '1.1.4.0', 'reference' => 'd49695b909c3b7628b6289db5479a1c204601f11', 'type' => 'library', 'install_path' => __DIR__ . '/../psr/log', 'aliases' => array(), 'dev_requirement' => false, ), 'psr/log-implementation' => array( 'dev_requirement' => false, 'provided' => array( 0 => '1.0|2.0', ), ), 'react/promise' => array( 'pretty_version' => 'v3.3.0', 'version' => '3.3.0.0', 'reference' => '23444f53a813a3296c1368bb104793ce8d88f04a', 'type' => 'library', 'install_path' => __DIR__ . '/../react/promise', 'aliases' => array(), 'dev_requirement' => false, ), 'seld/jsonlint' => array( 'pretty_version' => '1.11.0', 'version' => '1.11.0.0', 'reference' => '1748aaf847fc731cfad7725aec413ee46f0cc3a2', 'type' => 'library', 'install_path' => __DIR__ . '/../seld/jsonlint', 'aliases' => array(), 'dev_requirement' => false, ), 'seld/phar-utils' => array( 'pretty_version' => '1.2.1', 'version' => '1.2.1.0', 'reference' => 'ea2f4014f163c1be4c601b9b7bd6af81ba8d701c', 'type' => 'library', 'install_path' => __DIR__ . '/../seld/phar-utils', 'aliases' => array(), 'dev_requirement' => false, ), 'seld/signal-handler' => array( 'pretty_version' => '2.0.2', 'version' => '2.0.2.0', 'reference' => '04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98', 'type' => 'library', 'install_path' => __DIR__ . '/../seld/signal-handler', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/console' => array( 'pretty_version' => 'v5.4.47', 'version' => '5.4.47.0', 'reference' => 'c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/console', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/deprecation-contracts' => array( 'pretty_version' => 'v2.5.4', 'version' => '2.5.4.0', 'reference' => '605389f2a7e5625f273b53960dc46aeaf9c62918', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/deprecation-contracts', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/filesystem' => array( 'pretty_version' => 'v5.4.45', 'version' => '5.4.45.0', 'reference' => '57c8294ed37d4a055b77057827c67f9558c95c54', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/filesystem', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/finder' => array( 'pretty_version' => 'v5.4.45', 'version' => '5.4.45.0', 'reference' => '63741784cd7b9967975eec610b256eed3ede022b', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/finder', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/polyfill-ctype' => array( 'pretty_version' => 'v1.37.0', 'version' => '1.37.0.0', 'reference' => '141046a8f9477948ff284fa65be2095baafb94f2', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-ctype', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/polyfill-intl-grapheme' => array( 'pretty_version' => 'v1.38.1', 'version' => '1.38.1.0', 'reference' => 'e9247d281d694a5120554d9afaf54e070e88a603', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-intl-grapheme', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/polyfill-intl-normalizer' => array( 'pretty_version' => 'v1.38.0', 'version' => '1.38.0.0', 'reference' => '2d446c214bdbe5b71bde5011b060a05fece3ae6b', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-intl-normalizer', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/polyfill-mbstring' => array( 'pretty_version' => 'v1.38.1', 'version' => '1.38.1.0', 'reference' => '14c5439eec4ccff081ac14eca2dc57feb2a66d92', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-mbstring', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/polyfill-php73' => array( 'pretty_version' => 'v1.37.0', 'version' => '1.37.0.0', 'reference' => '0f68c03565dcaaf25a890667542e8bd75fe7e5bb', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-php73', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/polyfill-php80' => array( 'pretty_version' => 'v1.37.0', 'version' => '1.37.0.0', 'reference' => 'dfb55726c3a76ea3b6459fcfda1ec2d80a682411', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-php80', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/polyfill-php81' => array( 'pretty_version' => 'v1.38.1', 'version' => '1.38.1.0', 'reference' => '6bfb9c766cacffbc8e118cb87217d08ed84e5cd7', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-php81', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/polyfill-php84' => array( 'pretty_version' => 'v1.38.1', 'version' => '1.38.1.0', 'reference' => 'f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-php84', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/process' => array( 'pretty_version' => 'v5.4.51', 'version' => '5.4.51.0', 'reference' => '467bfc56f18f5ef6d5ccb09324d7e988c1c0a98f', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/process', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/service-contracts' => array( 'pretty_version' => 'v2.5.4', 'version' => '2.5.4.0', 'reference' => 'f37b419f7aea2e9abf10abd261832cace12e3300', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/service-contracts', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/string' => array( 'pretty_version' => 'v5.4.47', 'version' => '5.4.47.0', 'reference' => '136ca7d72f72b599f2631aca474a4f8e26719799', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/string', 'aliases' => array(), 'dev_requirement' => false, ), ), ); Copyright (C) 2021 Composer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. $val) { if ($val === '__unset') { unset($expandedVersion[$key]); } else { $expandedVersion[$key] = $val; } } $expanded[] = $expandedVersion; } return $expanded; } public static function minify(array $versions) { $minifiedVersions = array(); $lastKnownVersionData = null; foreach ($versions as $version) { if (!$lastKnownVersionData) { $lastKnownVersionData = $version; $minifiedVersions[] = $version; continue; } $minifiedVersion = array(); foreach ($version as $key => $val) { if (!isset($lastKnownVersionData[$key]) || $lastKnownVersionData[$key] !== $val) { $minifiedVersion[$key] = $val; $lastKnownVersionData[$key] = $val; } } foreach ($lastKnownVersionData as $key => $val) { if (!isset($version[$key])) { $minifiedVersion[$key] = "__unset"; unset($lastKnownVersionData[$key]); } } $minifiedVersions[] = $minifiedVersion; } return $minifiedVersions; } } Copyright (C) 2021 Composer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. matches = $matches; $this->matched = (bool) $count; $this->count = $count; } } matches = $matches; $this->matched = (bool) $count; $this->count = $count; } } matches = $matches; $this->matched = (bool) $count; $this->count = $count; } } matches = $matches; $this->matched = (bool) $count; } } matches = $matches; $this->matched = (bool) $count; } } matches = $matches; $this->matched = (bool) $count; } } extractPatterns($node, $scope); $errors = []; foreach ($patterns as $pattern) { $errorMessage = $this->validatePattern($pattern); if ($errorMessage === null) { continue; } $errors[] = RuleErrorBuilder::message(sprintf('Regex pattern is invalid: %s', $errorMessage))->identifier('regexp.pattern')->build(); } return $errors; } private function extractPatterns(StaticCall $node, Scope $scope): array { if (!$node->class instanceof FullyQualified) { return []; } $isRegex = $node->class->toString() === Regex::class; $isPreg = $node->class->toString() === Preg::class; if (!$isRegex && !$isPreg) { return []; } if (!$node->name instanceof Node\Identifier || !Preg::isMatch('{^(match|isMatch|grep|replace|split)}', $node->name->name)) { return []; } $functionName = $node->name->name; if (!isset($node->getArgs()[0])) { return []; } $patternNode = $node->getArgs()[0]->value; $patternType = $scope->getType($patternNode); $patternStrings = []; foreach ($patternType->getConstantStrings() as $constantStringType) { if ($functionName === 'replaceCallbackArray') { continue; } $patternStrings[] = $constantStringType->getValue(); } foreach ($patternType->getConstantArrays() as $constantArrayType) { if ( in_array($functionName, [ 'replace', 'replaceCallback', ], true) ) { foreach ($constantArrayType->getValueTypes() as $arrayKeyType) { foreach ($arrayKeyType->getConstantStrings() as $constantString) { $patternStrings[] = $constantString->getValue(); } } } if ($functionName !== 'replaceCallbackArray') { continue; } foreach ($constantArrayType->getKeyTypes() as $arrayKeyType) { foreach ($arrayKeyType->getConstantStrings() as $constantString) { $patternStrings[] = $constantString->getValue(); } } } return $patternStrings; } private function validatePattern(string $pattern): ?string { try { $msg = null; $prev = set_error_handler(function (int $severity, string $message, string $file) use (&$msg): bool { $msg = preg_replace("#^preg_match(_all)?\\(.*?\\): #", '', $message); return true; }); if ($pattern === '') { return 'Empty string is not a valid regular expression'; } Preg::match($pattern, ''); if ($msg !== null) { return $msg; } } catch (PcreException $e) { if ($e->getCode() === PREG_INTERNAL_ERROR && $msg !== null) { return $msg; } return preg_replace('{.*? failed executing ".*": }', '', $e->getMessage()); } finally { restore_error_handler(); } return null; } } getType($flagsArg->value); $constantScalars = $flagsType->getConstantScalarValues(); if ($constantScalars === []) { return null; } $internalFlagsTypes = []; foreach ($flagsType->getConstantScalarValues() as $constantScalarValue) { if (!is_int($constantScalarValue)) { return null; } $internalFlagsTypes[] = new ConstantIntegerType($constantScalarValue | PREG_UNMATCHED_AS_NULL | RegexArrayShapeMatcher::PREG_UNMATCHED_AS_NULL_ON_72_73); } return TypeCombinator::union(...$internalFlagsTypes); } static public function removeNullFromMatches(Type $matchesType): Type { return TypeTraverser::map($matchesType, static function (Type $type, callable $traverse): Type { if ($type instanceof UnionType || $type instanceof IntersectionType) { return $traverse($type); } if ($type instanceof ConstantArrayType) { return new ConstantArrayType( $type->getKeyTypes(), array_map(static function (Type $valueType) use ($traverse): Type { return $traverse($valueType); }, $type->getValueTypes()), $type->getNextAutoIndexes(), [], $type->isList() ); } if ($type instanceof ArrayType) { return new ArrayType($type->getKeyType(), $traverse($type->getItemType())); } return TypeCombinator::removeNull($type); }); } } regexShapeMatcher = $regexShapeMatcher; } public function isStaticMethodSupported(MethodReflection $methodReflection, ParameterReflection $parameter): bool { return $methodReflection->getDeclaringClass()->getName() === Preg::class && in_array($methodReflection->getName(), [ 'match', 'isMatch', 'matchStrictGroups', 'isMatchStrictGroups', 'matchAll', 'isMatchAll', 'matchAllStrictGroups', 'isMatchAllStrictGroups' ], true) && $parameter->getName() === 'matches'; } public function getParameterOutTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, ParameterReflection $parameter, Scope $scope): ?Type { $args = $methodCall->getArgs(); $patternArg = $args[0] ?? null; $matchesArg = $args[2] ?? null; $flagsArg = $args[3] ?? null; if ( $patternArg === null || $matchesArg === null ) { return null; } $flagsType = PregMatchFlags::getType($flagsArg, $scope); if ($flagsType === null) { return null; } if (stripos($methodReflection->getName(), 'matchAll') !== false) { return $this->regexShapeMatcher->matchAllExpr($patternArg->value, $flagsType, TrinaryLogic::createMaybe(), $scope); } return $this->regexShapeMatcher->matchExpr($patternArg->value, $flagsType, TrinaryLogic::createMaybe(), $scope); } } regexShapeMatcher = $regexShapeMatcher; } public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void { $this->typeSpecifier = $typeSpecifier; } public function getClass(): string { return Preg::class; } public function isStaticMethodSupported(MethodReflection $methodReflection, StaticCall $node, TypeSpecifierContext $context): bool { return in_array($methodReflection->getName(), [ 'match', 'isMatch', 'matchStrictGroups', 'isMatchStrictGroups', 'matchAll', 'isMatchAll', 'matchAllStrictGroups', 'isMatchAllStrictGroups' ], true) && !$context->null(); } public function specifyTypes(MethodReflection $methodReflection, StaticCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes { $args = $node->getArgs(); $patternArg = $args[0] ?? null; $matchesArg = $args[2] ?? null; $flagsArg = $args[3] ?? null; if ( $patternArg === null || $matchesArg === null ) { return new SpecifiedTypes(); } $flagsType = PregMatchFlags::getType($flagsArg, $scope); if ($flagsType === null) { return new SpecifiedTypes(); } if (stripos($methodReflection->getName(), 'matchAll') !== false) { $matchedType = $this->regexShapeMatcher->matchAllExpr($patternArg->value, $flagsType, TrinaryLogic::createFromBoolean($context->true()), $scope); } else { $matchedType = $this->regexShapeMatcher->matchExpr($patternArg->value, $flagsType, TrinaryLogic::createFromBoolean($context->true()), $scope); } if ($matchedType === null) { return new SpecifiedTypes(); } if ( in_array($methodReflection->getName(), ['matchStrictGroups', 'isMatchStrictGroups', 'matchAllStrictGroups', 'isMatchAllStrictGroups'], true) ) { $matchedType = PregMatchFlags::removeNullFromMatches($matchedType); } $overwrite = false; if ($context->false()) { $overwrite = true; $context = $context->negate(); } if (method_exists('PHPStan\Analyser\SpecifiedTypes', 'setRootExpr')) { $typeSpecifier = $this->typeSpecifier->create( $matchesArg->value, $matchedType, $context, $scope )->setRootExpr($node); return $overwrite ? $typeSpecifier->setAlwaysOverwriteTypes() : $typeSpecifier; } return $this->typeSpecifier->create( $matchesArg->value, $matchedType, $context, $overwrite, $scope, $node ); } } regexShapeMatcher = $regexShapeMatcher; } public function isStaticMethodSupported(MethodReflection $methodReflection, ParameterReflection $parameter): bool { return in_array($methodReflection->getDeclaringClass()->getName(), [Preg::class, Regex::class], true) && in_array($methodReflection->getName(), ['replaceCallback'], true) && $parameter->getName() === 'replacement'; } public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, ParameterReflection $parameter, Scope $scope): ?Type { $args = $methodCall->getArgs(); $patternArg = $args[0] ?? null; $flagsArg = $args[5] ?? null; if ( $patternArg === null ) { return null; } $flagsType = null; if ($flagsArg !== null) { $flagsType = $scope->getType($flagsArg->value); } $matchesType = $this->regexShapeMatcher->matchExpr($patternArg->value, $flagsType, TrinaryLogic::createYes(), $scope); if ($matchesType === null) { return null; } return new ClosureType( [ new NativeParameterReflection($parameter->getName(), $parameter->isOptional(), $matchesType, $parameter->passedByReference(), $parameter->isVariadic(), $parameter->getDefaultValue()), ], new StringType() ); } } regexShapeMatcher = $regexShapeMatcher; } public function getNodeType(): string { return StaticCall::class; } public function processNode(Node $node, Scope $scope): array { if (!$node->class instanceof FullyQualified) { return []; } $isRegex = $node->class->toString() === Regex::class; $isPreg = $node->class->toString() === Preg::class; if (!$isRegex && !$isPreg) { return []; } if (!$node->name instanceof Node\Identifier || !in_array($node->name->name, ['matchStrictGroups', 'isMatchStrictGroups', 'matchAllStrictGroups', 'isMatchAllStrictGroups'], true)) { return []; } $args = $node->getArgs(); if (!isset($args[0])) { return []; } $patternArg = $args[0] ?? null; if ($isPreg) { if (!isset($args[2])) { return []; } $flagsArg = $args[3] ?? null; } else { $flagsArg = $args[2] ?? null; } if ($patternArg === null) { return []; } $flagsType = PregMatchFlags::getType($flagsArg, $scope); if ($flagsType === null) { return []; } $matchedType = $this->regexShapeMatcher->matchExpr($patternArg->value, $flagsType, TrinaryLogic::createYes(), $scope); if ($matchedType === null) { return [ RuleErrorBuilder::message(sprintf('The %s call is potentially unsafe as $matches\' type could not be inferred.', $node->name->name)) ->identifier('composerPcre.maybeUnsafeStrictGroups') ->build(), ]; } if (count($matchedType->getConstantArrays()) === 1) { $matchedType = $matchedType->getConstantArrays()[0]; $nullableGroups = []; foreach ($matchedType->getValueTypes() as $index => $type) { if (TypeCombinator::containsNull($type)) { $nullableGroups[] = $matchedType->getKeyTypes()[$index]->getValue(); } } if (\count($nullableGroups) > 0) { return [ RuleErrorBuilder::message(sprintf( 'The %s call is unsafe as match group%s "%s" %s optional and may be null.', $node->name->name, \count($nullableGroups) > 1 ? 's' : '', implode('", "', $nullableGroups), \count($nullableGroups) > 1 ? 'are' : 'is' ))->identifier('composerPcre.unsafeStrictGroups')->build(), ]; } } return []; } } $val) { if ($val === $code && substr($const, -6) === '_ERROR') { return $const; } } return 'UNDEFINED_ERROR'; } } = 70400) { $result = preg_replace_callback($pattern, $replacement, $subject, $limit, $count, $flags); } else { $result = preg_replace_callback($pattern, $replacement, $subject, $limit, $count); } if ($result === null) { throw PcreException::fromFunction('preg_replace_callback', $pattern); } return $result; } public static function replaceCallbackArray(array $pattern, $subject, int $limit = -1, ?int &$count = null, int $flags = 0): string { if (!is_scalar($subject)) { if (is_array($subject)) { throw new \InvalidArgumentException(static::ARRAY_MSG); } throw new \TypeError(sprintf(static::INVALID_TYPE_MSG, gettype($subject))); } if (PHP_VERSION_ID >= 70400) { $result = preg_replace_callback_array($pattern, $subject, $limit, $count, $flags); } else { $result = preg_replace_callback_array($pattern, $subject, $limit, $count); } if ($result === null) { $pattern = array_keys($pattern); throw PcreException::fromFunction('preg_replace_callback_array', $pattern); } return $result; } public static function split(string $pattern, string $subject, int $limit = -1, int $flags = 0): array { if (($flags & PREG_SPLIT_OFFSET_CAPTURE) !== 0) { throw new \InvalidArgumentException('PREG_SPLIT_OFFSET_CAPTURE is not supported as it changes the type of $matches, use splitWithOffsets() instead'); } $result = preg_split($pattern, $subject, $limit, $flags); if ($result === false) { throw PcreException::fromFunction('preg_split', $pattern); } return $result; } public static function splitWithOffsets(string $pattern, string $subject, int $limit = -1, int $flags = 0): array { $result = preg_split($pattern, $subject, $limit, $flags | PREG_SPLIT_OFFSET_CAPTURE); if ($result === false) { throw PcreException::fromFunction('preg_split', $pattern); } return $result; } public static function grep(string $pattern, array $array, int $flags = 0): array { $result = preg_grep($pattern, $array, $flags); if ($result === false) { throw PcreException::fromFunction('preg_grep', $pattern); } return $result; } public static function isMatch(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): bool { return (bool) static::match($pattern, $subject, $matches, $flags, $offset); } public static function isMatchStrictGroups(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): bool { return (bool) self::matchStrictGroups($pattern, $subject, $matches, $flags, $offset); } public static function isMatchAll(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): bool { return (bool) static::matchAll($pattern, $subject, $matches, $flags, $offset); } public static function isMatchAllStrictGroups(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): bool { return (bool) self::matchAllStrictGroups($pattern, $subject, $matches, $flags, $offset); } public static function isMatchWithOffsets(string $pattern, string $subject, ?array &$matches, int $flags = 0, int $offset = 0): bool { return (bool) static::matchWithOffsets($pattern, $subject, $matches, $flags, $offset); } public static function isMatchAllWithOffsets(string $pattern, string $subject, ?array &$matches, int $flags = 0, int $offset = 0): bool { return (bool) static::matchAllWithOffsets($pattern, $subject, $matches, $flags, $offset); } private static function checkOffsetCapture(int $flags, string $useFunctionName): void { if (($flags & PREG_OFFSET_CAPTURE) !== 0) { throw new \InvalidArgumentException('PREG_OFFSET_CAPTURE is not supported as it changes the type of $matches, use ' . $useFunctionName . '() instead'); } } private static function checkSetOrder(int $flags): void { if (($flags & PREG_SET_ORDER) !== 0) { throw new \InvalidArgumentException('PREG_SET_ORDER is not supported as it changes the type of $matches'); } } private static function enforceNonNullMatches(string $pattern, array $matches, string $variantMethod) { foreach ($matches as $group => $match) { if (null === $match) { throw new UnexpectedNullMatchException('Pattern "'.$pattern.'" had an unexpected unmatched group "'.$group.'", make sure the pattern always matches or use '.$variantMethod.'() instead.'); } } return $matches; } private static function enforceNonNullMatchAll(string $pattern, array $matches, string $variantMethod) { foreach ($matches as $group => $groupMatches) { foreach ($groupMatches as $match) { if (null === $match) { throw new UnexpectedNullMatchException('Pattern "'.$pattern.'" had an unexpected unmatched group "'.$group.'", make sure the pattern always matches or use '.$variantMethod.'() instead.'); } } } return $matches; } private static function pregMatch(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0) { if (PHP_VERSION_ID >= 70400) { return preg_match($pattern, $subject, $matches, $flags, $offset); } $result = preg_match_all($pattern, $subject, $matchesInternal, $flags, $offset); if (!is_int($result)) { throw PcreException::fromFunction('preg_match', $pattern); } if ($result === 0) { $matches = []; } else { $matches = array_map(function (array $m) { $first = reset($m); assert($first !== false); return $first; }, $matchesInternal); $result = min($result, 1); } return $result; } } count = $count; $this->matched = (bool) $count; $this->result = $result; } } ', $version2); } public static function greaterThanOrEqualTo($version1, $version2) { return self::compare($version1, '>=', $version2); } public static function lessThan($version1, $version2) { return self::compare($version1, '<', $version2); } public static function lessThanOrEqualTo($version1, $version2) { return self::compare($version1, '<=', $version2); } public static function equalTo($version1, $version2) { return self::compare($version1, '==', $version2); } public static function notEqualTo($version1, $version2) { return self::compare($version1, '!=', $version2); } public static function compare($version1, $operator, $version2) { $constraint = new Constraint($operator, $version2); return $constraint->matchSpecific(new Constraint('==', $version1), true); } } Constraint::STR_OP_EQ, Constraint::OP_LT => Constraint::STR_OP_LT, Constraint::OP_LE => Constraint::STR_OP_LE, Constraint::OP_GT => Constraint::STR_OP_GT, Constraint::OP_GE => Constraint::STR_OP_GE, Constraint::OP_NE => Constraint::STR_OP_NE, ); public static function clear() { self::$resultCache = array(); self::$compiledCheckerCache = array(); } public static function match(ConstraintInterface $constraint, $operator, $version) { $resultCacheKey = $operator.$constraint.';'.$version; if (isset(self::$resultCache[$resultCacheKey])) { return self::$resultCache[$resultCacheKey]; } if (self::$enabled === null) { self::$enabled = !\in_array('eval', explode(',', (string) ini_get('disable_functions')), true); } if (!self::$enabled) { return self::$resultCache[$resultCacheKey] = $constraint->matches(new Constraint(self::$transOpInt[$operator], $version)); } $cacheKey = $operator.$constraint; if (!isset(self::$compiledCheckerCache[$cacheKey])) { $code = $constraint->compile($operator); self::$compiledCheckerCache[$cacheKey] = $function = eval('return function($v, $b){return '.$code.';};'); } else { $function = self::$compiledCheckerCache[$cacheKey]; } return self::$resultCache[$resultCacheKey] = $function($version, strpos($version, 'dev-') === 0); } } version = $version; $this->isInclusive = $isInclusive; } public function getVersion() { return $this->version; } public function isInclusive() { return $this->isInclusive; } public function isZero() { return $this->getVersion() === '0.0.0.0-dev' && $this->isInclusive(); } public function isPositiveInfinity() { return $this->getVersion() === PHP_INT_MAX.'.0.0.0' && !$this->isInclusive(); } public function compareTo(Bound $other, $operator) { if (!\in_array($operator, array('<', '>'), true)) { throw new \InvalidArgumentException('Does not support any other operator other than > or <.'); } if ($this == $other) { return false; } $compareResult = version_compare($this->getVersion(), $other->getVersion()); if (0 !== $compareResult) { return (('>' === $operator) ? 1 : -1) === $compareResult; } return '>' === $operator ? $other->isInclusive() : !$other->isInclusive(); } public function __toString() { return sprintf( '%s [%s]', $this->getVersion(), $this->isInclusive() ? 'inclusive' : 'exclusive' ); } public static function zero() { return new Bound('0.0.0.0-dev', true); } public static function positiveInfinity() { return new Bound(PHP_INT_MAX.'.0.0.0', false); } } '; const STR_OP_GE = '>='; const STR_OP_NE = '!='; const STR_OP_NE_ALT = '<>'; private static $transOpStr = array( '=' => self::OP_EQ, '==' => self::OP_EQ, '<' => self::OP_LT, '<=' => self::OP_LE, '>' => self::OP_GT, '>=' => self::OP_GE, '<>' => self::OP_NE, '!=' => self::OP_NE, ); private static $transOpInt = array( self::OP_EQ => '==', self::OP_LT => '<', self::OP_LE => '<=', self::OP_GT => '>', self::OP_GE => '>=', self::OP_NE => '!=', ); protected $operator; protected $version; protected $prettyString; protected $lowerBound; protected $upperBound; public function __construct($operator, $version) { if (!isset(self::$transOpStr[$operator])) { throw new \InvalidArgumentException(sprintf( 'Invalid operator "%s" given, expected one of: %s', $operator, implode(', ', self::getSupportedOperators()) )); } $this->operator = self::$transOpStr[$operator]; $this->version = $version; } public function getVersion() { return $this->version; } public function getOperator() { return self::$transOpInt[$this->operator]; } public function matches(ConstraintInterface $provider) { if ($provider instanceof self) { return $this->matchSpecific($provider); } return $provider->matches($this); } public function setPrettyString($prettyString) { $this->prettyString = $prettyString; } public function getPrettyString() { if ($this->prettyString) { return $this->prettyString; } return $this->__toString(); } public static function getSupportedOperators() { return array_keys(self::$transOpStr); } public static function getOperatorConstant($operator) { return self::$transOpStr[$operator]; } public function versionCompare($a, $b, $operator, $compareBranches = false) { if (!isset(self::$transOpStr[$operator])) { throw new \InvalidArgumentException(sprintf( 'Invalid operator "%s" given, expected one of: %s', $operator, implode(', ', self::getSupportedOperators()) )); } $aIsBranch = strpos($a, 'dev-') === 0; $bIsBranch = strpos($b, 'dev-') === 0; if ($operator === '!=' && ($aIsBranch || $bIsBranch)) { return $a !== $b; } if ($aIsBranch && $bIsBranch) { return $operator === '==' && $a === $b; } if (!$compareBranches && ($aIsBranch || $bIsBranch)) { return false; } return \version_compare($a, $b, $operator); } public function compile($otherOperator) { if (strpos($this->version, 'dev-') === 0) { if (self::OP_EQ === $this->operator) { if (self::OP_EQ === $otherOperator) { return sprintf('$b && $v === %s', \var_export($this->version, true)); } if (self::OP_NE === $otherOperator) { return sprintf('!$b || $v !== %s', \var_export($this->version, true)); } return 'false'; } if (self::OP_NE === $this->operator) { if (self::OP_EQ === $otherOperator) { return sprintf('!$b || $v !== %s', \var_export($this->version, true)); } if (self::OP_NE === $otherOperator) { return 'true'; } return '!$b'; } return 'false'; } if (self::OP_EQ === $this->operator) { if (self::OP_EQ === $otherOperator) { return sprintf('\version_compare($v, %s, \'==\')', \var_export($this->version, true)); } if (self::OP_NE === $otherOperator) { return sprintf('$b || \version_compare($v, %s, \'!=\')', \var_export($this->version, true)); } return sprintf('!$b && \version_compare(%s, $v, \'%s\')', \var_export($this->version, true), self::$transOpInt[$otherOperator]); } if (self::OP_NE === $this->operator) { if (self::OP_EQ === $otherOperator) { return sprintf('$b || (!$b && \version_compare($v, %s, \'!=\'))', \var_export($this->version, true)); } if (self::OP_NE === $otherOperator) { return 'true'; } return '!$b'; } if (self::OP_LT === $this->operator || self::OP_LE === $this->operator) { if (self::OP_LT === $otherOperator || self::OP_LE === $otherOperator) { return '!$b'; } } else { if (self::OP_GT === $otherOperator || self::OP_GE === $otherOperator) { return '!$b'; } } if (self::OP_NE === $otherOperator) { return 'true'; } $codeComparison = sprintf('\version_compare($v, %s, \'%s\')', \var_export($this->version, true), self::$transOpInt[$this->operator]); if ($this->operator === self::OP_LE) { if ($otherOperator === self::OP_GT) { return sprintf('!$b && \version_compare($v, %s, \'!=\') && ', \var_export($this->version, true)) . $codeComparison; } } elseif ($this->operator === self::OP_GE) { if ($otherOperator === self::OP_LT) { return sprintf('!$b && \version_compare($v, %s, \'!=\') && ', \var_export($this->version, true)) . $codeComparison; } } return sprintf('!$b && %s', $codeComparison); } public function matchSpecific(Constraint $provider, $compareBranches = false) { $noEqualOp = str_replace('=', '', self::$transOpInt[$this->operator]); $providerNoEqualOp = str_replace('=', '', self::$transOpInt[$provider->operator]); $isEqualOp = self::OP_EQ === $this->operator; $isNonEqualOp = self::OP_NE === $this->operator; $isProviderEqualOp = self::OP_EQ === $provider->operator; $isProviderNonEqualOp = self::OP_NE === $provider->operator; if ($isNonEqualOp || $isProviderNonEqualOp) { if ($isNonEqualOp && !$isProviderNonEqualOp && !$isProviderEqualOp && strpos($provider->version, 'dev-') === 0) { return false; } if ($isProviderNonEqualOp && !$isNonEqualOp && !$isEqualOp && strpos($this->version, 'dev-') === 0) { return false; } if (!$isEqualOp && !$isProviderEqualOp) { return true; } return $this->versionCompare($provider->version, $this->version, '!=', $compareBranches); } if ($this->operator !== self::OP_EQ && $noEqualOp === $providerNoEqualOp) { return !(strpos($this->version, 'dev-') === 0 || strpos($provider->version, 'dev-') === 0); } $version1 = $isEqualOp ? $this->version : $provider->version; $version2 = $isEqualOp ? $provider->version : $this->version; $operator = $isEqualOp ? $provider->operator : $this->operator; if ($this->versionCompare($version1, $version2, self::$transOpInt[$operator], $compareBranches)) { return !(self::$transOpInt[$provider->operator] === $providerNoEqualOp && self::$transOpInt[$this->operator] !== $noEqualOp && \version_compare($provider->version, $this->version, '==')); } return false; } public function __toString() { return self::$transOpInt[$this->operator] . ' ' . $this->version; } public function getLowerBound() { $this->extractBounds(); return $this->lowerBound; } public function getUpperBound() { $this->extractBounds(); return $this->upperBound; } private function extractBounds() { if (null !== $this->lowerBound) { return; } if (strpos($this->version, 'dev-') === 0) { $this->lowerBound = Bound::zero(); $this->upperBound = Bound::positiveInfinity(); return; } switch ($this->operator) { case self::OP_EQ: $this->lowerBound = new Bound($this->version, true); $this->upperBound = new Bound($this->version, true); break; case self::OP_LT: $this->lowerBound = Bound::zero(); $this->upperBound = new Bound($this->version, false); break; case self::OP_LE: $this->lowerBound = Bound::zero(); $this->upperBound = new Bound($this->version, true); break; case self::OP_GT: $this->lowerBound = new Bound($this->version, false); $this->upperBound = Bound::positiveInfinity(); break; case self::OP_GE: $this->lowerBound = new Bound($this->version, true); $this->upperBound = Bound::positiveInfinity(); break; case self::OP_NE: $this->lowerBound = Bound::zero(); $this->upperBound = Bound::positiveInfinity(); break; } } } prettyString = $prettyString; } public function getPrettyString() { if ($this->prettyString) { return $this->prettyString; } return (string) $this; } public function __toString() { return '*'; } public function getUpperBound() { return Bound::positiveInfinity(); } public function getLowerBound() { return Bound::zero(); } } prettyString = $prettyString; } public function getPrettyString() { if ($this->prettyString) { return $this->prettyString; } return (string) $this; } public function __toString() { return '[]'; } public function getUpperBound() { return new Bound('0.0.0.0-dev', false); } public function getLowerBound() { return new Bound('0.0.0.0-dev', false); } } constraints = $constraints; $this->conjunctive = $conjunctive; } public function getConstraints() { return $this->constraints; } public function isConjunctive() { return $this->conjunctive; } public function isDisjunctive() { return !$this->conjunctive; } public function compile($otherOperator) { $parts = array(); foreach ($this->constraints as $constraint) { $code = $constraint->compile($otherOperator); if ($code === 'true') { if (!$this->conjunctive) { return 'true'; } } elseif ($code === 'false') { if ($this->conjunctive) { return 'false'; } } else { $parts[] = '('.$code.')'; } } if (!$parts) { return $this->conjunctive ? 'true' : 'false'; } return $this->conjunctive ? implode('&&', $parts) : implode('||', $parts); } public function matches(ConstraintInterface $provider) { if (false === $this->conjunctive) { foreach ($this->constraints as $constraint) { if ($provider->matches($constraint)) { return true; } } return false; } if ($provider instanceof MultiConstraint && $provider->isDisjunctive()) { return $provider->matches($this); } foreach ($this->constraints as $constraint) { if (!$provider->matches($constraint)) { return false; } } return true; } public function setPrettyString($prettyString) { $this->prettyString = $prettyString; } public function getPrettyString() { if ($this->prettyString) { return $this->prettyString; } return (string) $this; } public function __toString() { if ($this->string !== null) { return $this->string; } $constraints = array(); foreach ($this->constraints as $constraint) { $constraints[] = (string) $constraint; } return $this->string = '[' . implode($this->conjunctive ? ' ' : ' || ', $constraints) . ']'; } public function getLowerBound() { $this->extractBounds(); if (null === $this->lowerBound) { throw new \LogicException('extractBounds should have populated the lowerBound property'); } return $this->lowerBound; } public function getUpperBound() { $this->extractBounds(); if (null === $this->upperBound) { throw new \LogicException('extractBounds should have populated the upperBound property'); } return $this->upperBound; } public static function create(array $constraints, $conjunctive = true) { if (0 === \count($constraints)) { return new MatchAllConstraint(); } if (1 === \count($constraints)) { return $constraints[0]; } $optimized = self::optimizeConstraints($constraints, $conjunctive); if ($optimized !== null) { list($constraints, $conjunctive) = $optimized; if (\count($constraints) === 1) { return $constraints[0]; } } return new self($constraints, $conjunctive); } private static function optimizeConstraints(array $constraints, $conjunctive) { if (!$conjunctive) { $left = $constraints[0]; $mergedConstraints = array(); $optimized = false; for ($i = 1, $l = \count($constraints); $i < $l; $i++) { $right = $constraints[$i]; if ( $left instanceof self && $left->conjunctive && $right instanceof self && $right->conjunctive && \count($left->constraints) === 2 && \count($right->constraints) === 2 && ($left0 = (string) $left->constraints[0]) && $left0[0] === '>' && $left0[1] === '=' && ($left1 = (string) $left->constraints[1]) && $left1[0] === '<' && ($right0 = (string) $right->constraints[0]) && $right0[0] === '>' && $right0[1] === '=' && ($right1 = (string) $right->constraints[1]) && $right1[0] === '<' && substr($left1, 2) === substr($right0, 3) ) { $optimized = true; $left = new MultiConstraint( array( $left->constraints[0], $right->constraints[1], ), true); } else { $mergedConstraints[] = $left; $left = $right; } } if ($optimized) { $mergedConstraints[] = $left; return array($mergedConstraints, false); } } return null; } private function extractBounds() { if (null !== $this->lowerBound) { return; } foreach ($this->constraints as $constraint) { if (null === $this->lowerBound || null === $this->upperBound) { $this->lowerBound = $constraint->getLowerBound(); $this->upperBound = $constraint->getUpperBound(); continue; } if ($constraint->getLowerBound()->compareTo($this->lowerBound, $this->isConjunctive() ? '>' : '<')) { $this->lowerBound = $constraint->getLowerBound(); } if ($constraint->getUpperBound()->compareTo($this->upperBound, $this->isConjunctive() ? '<' : '>')) { $this->upperBound = $constraint->getUpperBound(); } } } } start = $start; $this->end = $end; } public function getStart() { return $this->start; } public function getEnd() { return $this->end; } public static function fromZero() { static $zero; if (null === $zero) { $zero = new Constraint('>=', '0.0.0.0-dev'); } return $zero; } public static function untilPositiveInfinity() { static $positiveInfinity; if (null === $positiveInfinity) { $positiveInfinity = new Constraint('<', PHP_INT_MAX.'.0.0.0'); } return $positiveInfinity; } public static function any() { return new self(self::fromZero(), self::untilPositiveInfinity()); } public static function anyDev() { return array('names' => array(), 'exclude' => true); } public static function noDev() { return array('names' => array(), 'exclude' => false); } } =' => -3, '<' => -2, '>' => 2, '<=' => 3, ); public static function clear() { self::$intervalsCache = array(); } public static function isSubsetOf(ConstraintInterface $candidate, ConstraintInterface $constraint) { if ($constraint instanceof MatchAllConstraint) { return true; } if ($candidate instanceof MatchNoneConstraint || $constraint instanceof MatchNoneConstraint) { return false; } $intersectionIntervals = self::get(new MultiConstraint(array($candidate, $constraint), true)); $candidateIntervals = self::get($candidate); if (\count($intersectionIntervals['numeric']) !== \count($candidateIntervals['numeric'])) { return false; } foreach ($intersectionIntervals['numeric'] as $index => $interval) { if (!isset($candidateIntervals['numeric'][$index])) { return false; } if ((string) $candidateIntervals['numeric'][$index]->getStart() !== (string) $interval->getStart()) { return false; } if ((string) $candidateIntervals['numeric'][$index]->getEnd() !== (string) $interval->getEnd()) { return false; } } if ($intersectionIntervals['branches']['exclude'] !== $candidateIntervals['branches']['exclude']) { return false; } if (\count($intersectionIntervals['branches']['names']) !== \count($candidateIntervals['branches']['names'])) { return false; } foreach ($intersectionIntervals['branches']['names'] as $index => $name) { if ($name !== $candidateIntervals['branches']['names'][$index]) { return false; } } return true; } public static function haveIntersections(ConstraintInterface $a, ConstraintInterface $b) { if ($a instanceof MatchAllConstraint || $b instanceof MatchAllConstraint) { return true; } if ($a instanceof MatchNoneConstraint || $b instanceof MatchNoneConstraint) { return false; } $intersectionIntervals = self::generateIntervals(new MultiConstraint(array($a, $b), true), true); return \count($intersectionIntervals['numeric']) > 0 || $intersectionIntervals['branches']['exclude'] || \count($intersectionIntervals['branches']['names']) > 0; } public static function compactConstraint(ConstraintInterface $constraint) { if (!$constraint instanceof MultiConstraint) { return $constraint; } $intervals = self::generateIntervals($constraint); $constraints = array(); $hasNumericMatchAll = false; if (\count($intervals['numeric']) === 1 && (string) $intervals['numeric'][0]->getStart() === (string) Interval::fromZero() && (string) $intervals['numeric'][0]->getEnd() === (string) Interval::untilPositiveInfinity()) { $constraints[] = $intervals['numeric'][0]->getStart(); $hasNumericMatchAll = true; } else { $unEqualConstraints = array(); for ($i = 0, $count = \count($intervals['numeric']); $i < $count; $i++) { $interval = $intervals['numeric'][$i]; if ($interval->getEnd()->getOperator() === '<' && $i+1 < $count) { $nextInterval = $intervals['numeric'][$i+1]; if ($interval->getEnd()->getVersion() === $nextInterval->getStart()->getVersion() && $nextInterval->getStart()->getOperator() === '>') { if (\count($unEqualConstraints) === 0 && (string) $interval->getStart() !== (string) Interval::fromZero()) { $unEqualConstraints[] = $interval->getStart(); } $unEqualConstraints[] = new Constraint('!=', $interval->getEnd()->getVersion()); continue; } } if (\count($unEqualConstraints) > 0) { if ((string) $interval->getEnd() !== (string) Interval::untilPositiveInfinity()) { $unEqualConstraints[] = $interval->getEnd(); } if (\count($unEqualConstraints) > 1) { $constraints[] = new MultiConstraint($unEqualConstraints, true); } else { $constraints[] = $unEqualConstraints[0]; } $unEqualConstraints = array(); continue; } if ($interval->getStart()->getVersion() === $interval->getEnd()->getVersion() && $interval->getStart()->getOperator() === '>=' && $interval->getEnd()->getOperator() === '<=') { $constraints[] = new Constraint('==', $interval->getStart()->getVersion()); continue; } if ((string) $interval->getStart() === (string) Interval::fromZero()) { $constraints[] = $interval->getEnd(); } elseif ((string) $interval->getEnd() === (string) Interval::untilPositiveInfinity()) { $constraints[] = $interval->getStart(); } else { $constraints[] = new MultiConstraint(array($interval->getStart(), $interval->getEnd()), true); } } } $devConstraints = array(); if (0 === \count($intervals['branches']['names'])) { if ($intervals['branches']['exclude']) { if ($hasNumericMatchAll) { return new MatchAllConstraint; } } } else { foreach ($intervals['branches']['names'] as $branchName) { if ($intervals['branches']['exclude']) { $devConstraints[] = new Constraint('!=', $branchName); } else { $devConstraints[] = new Constraint('==', $branchName); } } if ($intervals['branches']['exclude']) { if (\count($constraints) > 1) { return new MultiConstraint(array_merge( array(new MultiConstraint($constraints, false)), $devConstraints ), true); } if (\count($constraints) === 1 && (string)$constraints[0] === (string)Interval::fromZero()) { if (\count($devConstraints) > 1) { return new MultiConstraint($devConstraints, true); } return $devConstraints[0]; } return new MultiConstraint(array_merge($constraints, $devConstraints), true); } $constraints = array_merge($constraints, $devConstraints); } if (\count($constraints) > 1) { return new MultiConstraint($constraints, false); } if (\count($constraints) === 1) { return $constraints[0]; } return new MatchNoneConstraint; } public static function get(ConstraintInterface $constraint) { $key = (string) $constraint; if (!isset(self::$intervalsCache[$key])) { self::$intervalsCache[$key] = self::generateIntervals($constraint); } return self::$intervalsCache[$key]; } private static function generateIntervals(ConstraintInterface $constraint, $stopOnFirstValidInterval = false) { if ($constraint instanceof MatchAllConstraint) { return array('numeric' => array(new Interval(Interval::fromZero(), Interval::untilPositiveInfinity())), 'branches' => Interval::anyDev()); } if ($constraint instanceof MatchNoneConstraint) { return array('numeric' => array(), 'branches' => array('names' => array(), 'exclude' => false)); } if ($constraint instanceof Constraint) { return self::generateSingleConstraintIntervals($constraint); } if (!$constraint instanceof MultiConstraint) { throw new \UnexpectedValueException('The constraint passed in should be an MatchAllConstraint, Constraint or MultiConstraint instance, got '.\get_class($constraint).'.'); } $constraints = $constraint->getConstraints(); $numericGroups = array(); $constraintBranches = array(); foreach ($constraints as $c) { $res = self::get($c); $numericGroups[] = $res['numeric']; $constraintBranches[] = $res['branches']; } if ($constraint->isDisjunctive()) { $branches = Interval::noDev(); foreach ($constraintBranches as $b) { if ($b['exclude']) { if ($branches['exclude']) { $branches['names'] = array_intersect($branches['names'], $b['names']); } else { $branches['exclude'] = true; $branches['names'] = array_diff($b['names'], $branches['names']); } } else { if ($branches['exclude']) { $branches['names'] = array_diff($branches['names'], $b['names']); } else { $branches['names'] = array_merge($branches['names'], $b['names']); } } } } else { $branches = Interval::anyDev(); foreach ($constraintBranches as $b) { if ($b['exclude']) { if ($branches['exclude']) { $branches['names'] = array_merge($branches['names'], $b['names']); } else { $branches['names'] = array_diff($branches['names'], $b['names']); } } else { if ($branches['exclude']) { $branches['names'] = array_diff($b['names'], $branches['names']); $branches['exclude'] = false; } else { $branches['names'] = array_intersect($branches['names'], $b['names']); } } } } $branches['names'] = array_unique($branches['names']); if (\count($numericGroups) === 1) { return array('numeric' => $numericGroups[0], 'branches' => $branches); } $borders = array(); foreach ($numericGroups as $group) { foreach ($group as $interval) { $borders[] = array('version' => $interval->getStart()->getVersion(), 'operator' => $interval->getStart()->getOperator(), 'side' => 'start'); $borders[] = array('version' => $interval->getEnd()->getVersion(), 'operator' => $interval->getEnd()->getOperator(), 'side' => 'end'); } } $opSortOrder = self::$opSortOrder; usort($borders, function ($a, $b) use ($opSortOrder) { $order = version_compare($a['version'], $b['version']); if ($order === 0) { return $opSortOrder[$a['operator']] - $opSortOrder[$b['operator']]; } return $order; }); $activeIntervals = 0; $intervals = array(); $index = 0; $activationThreshold = $constraint->isConjunctive() ? \count($numericGroups) : 1; $start = null; foreach ($borders as $border) { if ($border['side'] === 'start') { $activeIntervals++; } else { $activeIntervals--; } if (!$start && $activeIntervals >= $activationThreshold) { $start = new Constraint($border['operator'], $border['version']); } elseif ($start && $activeIntervals < $activationThreshold) { if ( version_compare($start->getVersion(), $border['version'], '=') && ( ($start->getOperator() === '>' && $border['operator'] === '<=') || ($start->getOperator() === '>=' && $border['operator'] === '<') ) ) { unset($intervals[$index]); } else { $intervals[$index] = new Interval($start, new Constraint($border['operator'], $border['version'])); $index++; if ($stopOnFirstValidInterval) { break; } } $start = null; } } return array('numeric' => $intervals, 'branches' => $branches); } private static function generateSingleConstraintIntervals(Constraint $constraint) { $op = $constraint->getOperator(); if (strpos($constraint->getVersion(), 'dev-') === 0) { $intervals = array(); $branches = array('names' => array(), 'exclude' => false); if ($op === '!=') { $intervals[] = new Interval(Interval::fromZero(), Interval::untilPositiveInfinity()); $branches = array('names' => array($constraint->getVersion()), 'exclude' => true); } elseif ($op === '==') { $branches['names'][] = $constraint->getVersion(); } return array( 'numeric' => $intervals, 'branches' => $branches, ); } if ($op[0] === '>') { return array('numeric' => array(new Interval($constraint, Interval::untilPositiveInfinity())), 'branches' => Interval::noDev()); } if ($op[0] === '<') { return array('numeric' => array(new Interval(Interval::fromZero(), $constraint)), 'branches' => Interval::noDev()); } if ($op === '!=') { return array('numeric' => array( new Interval(Interval::fromZero(), new Constraint('<', $constraint->getVersion())), new Interval(new Constraint('>', $constraint->getVersion()), Interval::untilPositiveInfinity()), ), 'branches' => Interval::anyDev()); } return array('numeric' => array( new Interval(new Constraint('>=', $constraint->getVersion()), new Constraint('<=', $constraint->getVersion())), ), 'branches' => Interval::noDev()); } } normalize($version)); $parsedConstraints = $versionParser->parseConstraints($constraints); return $parsedConstraints->matches($provider); } public static function satisfiedBy(array $versions, $constraints) { $versions = array_filter($versions, function ($version) use ($constraints) { return Semver::satisfies($version, $constraints); }); return array_values($versions); } public static function sort(array $versions) { return self::usort($versions, self::SORT_ASC); } public static function rsort(array $versions) { return self::usort($versions, self::SORT_DESC); } private static function usort(array $versions, $direction) { if (null === self::$versionParser) { self::$versionParser = new VersionParser(); } $versionParser = self::$versionParser; $normalized = array(); foreach ($versions as $key => $version) { $normalizedVersion = $versionParser->normalize($version); $normalizedVersion = $versionParser->normalizeDefaultBranch($normalizedVersion); $normalized[] = array($normalizedVersion, $key); } usort($normalized, function (array $left, array $right) use ($direction) { if ($left[0] === $right[0]) { return 0; } if (Comparator::lessThan($left[0], $right[0])) { return -$direction; } return $direction; }); $sorted = array(); foreach ($normalized as $item) { $sorted[] = $versions[$item[1]]; } return $sorted; } } expandStability($matches[$index]) . (isset($matches[$index + 1]) && '' !== $matches[$index + 1] ? ltrim($matches[$index + 1], '.-') : ''); } if (!empty($matches[$index + 2])) { $version .= '-dev'; } return $version; } if (preg_match('{(.*?)[.-]?dev$}i', $version, $match)) { try { $normalized = $this->normalizeBranch($match[1]); if (strpos($normalized, 'dev-') === false) { return $normalized; } } catch (\Exception $e) { } } $extraMessage = ''; if (preg_match('{ +as +' . preg_quote($version) . '(?:@(?:'.self::$stabilitiesRegex.'))?$}', $fullVersion)) { $extraMessage = ' in "' . $fullVersion . '", the alias must be an exact version'; } elseif (preg_match('{^' . preg_quote($version) . '(?:@(?:'.self::$stabilitiesRegex.'))? +as +}', $fullVersion)) { $extraMessage = ' in "' . $fullVersion . '", the alias source must be an exact version, if it is a branch name you should prefix it with dev-'; } throw new \UnexpectedValueException('Invalid version string "' . $origVersion . '"' . $extraMessage); } public function parseNumericAliasPrefix($branch) { if (preg_match('{^(?P(\d++\\.)*\d++)(?:\.x)?-dev$}i', (string) $branch, $matches)) { return $matches['version'] . '.'; } return false; } public function normalizeBranch($name) { $name = trim((string) $name); if (preg_match('{^v?(\d++)(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?$}i', $name, $matches)) { $version = ''; for ($i = 1; $i < 5; ++$i) { $version .= isset($matches[$i]) ? str_replace(array('*', 'X'), 'x', $matches[$i]) : '.x'; } return str_replace('x', '9999999', $version) . '-dev'; } return 'dev-' . $name; } public function normalizeDefaultBranch($name) { if ($name === 'dev-master' || $name === 'dev-default' || $name === 'dev-trunk') { return '9999999-dev'; } return (string) $name; } public function parseConstraints($constraints) { $prettyConstraint = (string) $constraints; $orConstraints = preg_split('{\s*\|\|?\s*}', trim((string) $constraints)); if (false === $orConstraints) { throw new \RuntimeException('Failed to preg_split string: '.$constraints); } $orGroups = array(); foreach ($orConstraints as $orConstraint) { $andConstraints = preg_split('{(?< ,]) *(? 1) { $constraintObjects = array(); foreach ($andConstraints as $andConstraint) { foreach ($this->parseConstraint($andConstraint) as $parsedAndConstraint) { $constraintObjects[] = $parsedAndConstraint; } } } else { $constraintObjects = $this->parseConstraint($andConstraints[0]); } if (1 === \count($constraintObjects)) { $constraint = $constraintObjects[0]; } else { $constraint = new MultiConstraint($constraintObjects); } $orGroups[] = $constraint; } $parsedConstraint = MultiConstraint::create($orGroups, false); $parsedConstraint->setPrettyString($prettyConstraint); return $parsedConstraint; } private function parseConstraint($constraint) { if (preg_match('{^([^,\s]++) ++as ++([^,\s]++)$}', $constraint, $match)) { $constraint = $match[1]; } if (preg_match('{^([^,\s]*?)@(' . self::$stabilitiesRegex . ')$}i', $constraint, $match)) { $constraint = '' !== $match[1] ? $match[1] : '*'; if ($match[2] !== 'stable') { $stabilityModifier = $match[2]; } } if (preg_match('{^(dev-[^,\s@]+?|[^,\s@]+?\.x-dev)#.+$}i', $constraint, $match)) { $constraint = $match[1]; } if (preg_match('{^(v)?[xX*](\.[xX*])*$}i', $constraint, $match)) { if (!empty($match[1]) || !empty($match[2])) { return array(new Constraint('>=', '0.0.0.0-dev')); } return array(new MatchAllConstraint()); } $versionRegex = 'v?(\d++)(?:\.(\d++))?(?:\.(\d++))?(?:\.(\d++))?(?:' . self::$modifierRegex . '|\.([xX*][.-]?dev))(?:\+[^\s]+)?'; if (preg_match('{^~>?' . $versionRegex . '$}i', $constraint, $matches)) { if (strpos($constraint, '~>') === 0) { throw new \UnexpectedValueException( 'Could not parse version constraint ' . $constraint . ': ' . 'Invalid operator "~>", you probably meant to use the "~" operator' ); } if (isset($matches[4]) && '' !== $matches[4] && null !== $matches[4]) { $position = 4; } elseif (isset($matches[3]) && '' !== $matches[3] && null !== $matches[3]) { $position = 3; } elseif (isset($matches[2]) && '' !== $matches[2] && null !== $matches[2]) { $position = 2; } else { $position = 1; } if (!empty($matches[8])) { $position++; } $stabilitySuffix = ''; if (empty($matches[5]) && empty($matches[7]) && empty($matches[8])) { $stabilitySuffix .= '-dev'; } $lowVersion = $this->normalize(substr($constraint . $stabilitySuffix, 1)); $lowerBound = new Constraint('>=', $lowVersion); $highPosition = max(1, $position - 1); $highVersion = $this->manipulateVersionString($matches, $highPosition, 1) . '-dev'; $upperBound = new Constraint('<', $highVersion); return array( $lowerBound, $upperBound, ); } if (preg_match('{^\^' . $versionRegex . '($)}i', $constraint, $matches)) { if ('0' !== $matches[1] || '' === $matches[2] || null === $matches[2]) { $position = 1; } elseif ('0' !== $matches[2] || '' === $matches[3] || null === $matches[3]) { $position = 2; } else { $position = 3; } $stabilitySuffix = ''; if (empty($matches[5]) && empty($matches[7]) && empty($matches[8])) { $stabilitySuffix .= '-dev'; } $lowVersion = $this->normalize(substr($constraint . $stabilitySuffix, 1)); $lowerBound = new Constraint('>=', $lowVersion); $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev'; $upperBound = new Constraint('<', $highVersion); return array( $lowerBound, $upperBound, ); } if (preg_match('{^v?(\d++)(?:\.(\d++))?(?:\.(\d++))?(?:\.[xX*])++$}', $constraint, $matches)) { if (isset($matches[3]) && '' !== $matches[3] && null !== $matches[3]) { $position = 3; } elseif (isset($matches[2]) && '' !== $matches[2] && null !== $matches[2]) { $position = 2; } else { $position = 1; } $lowVersion = $this->manipulateVersionString($matches, $position) . '-dev'; $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev'; if ($lowVersion === '0.0.0.0-dev') { return array(new Constraint('<', $highVersion)); } return array( new Constraint('>=', $lowVersion), new Constraint('<', $highVersion), ); } if (preg_match('{^(?P' . $versionRegex . ') +- +(?P' . $versionRegex . ')($)}i', $constraint, $matches)) { $lowStabilitySuffix = ''; if (empty($matches[6]) && empty($matches[8]) && empty($matches[9])) { $lowStabilitySuffix = '-dev'; } $lowVersion = $this->normalize($matches['from']); $lowerBound = new Constraint('>=', $lowVersion . $lowStabilitySuffix); $empty = function ($x) { return ($x === 0 || $x === '0') ? false : empty($x); }; if ((!$empty($matches[12]) && !$empty($matches[13])) || !empty($matches[15]) || !empty($matches[17]) || !empty($matches[18])) { $highVersion = $this->normalize($matches['to']); $upperBound = new Constraint('<=', $highVersion); } else { $highMatch = array('', $matches[11], $matches[12], $matches[13], $matches[14]); $this->normalize($matches['to']); $highVersion = $this->manipulateVersionString($highMatch, $empty($matches[12]) ? 1 : 2, 1) . '-dev'; $upperBound = new Constraint('<', $highVersion); } return array( $lowerBound, $upperBound, ); } if (preg_match('{^(<>|!=|>=?|<=?|==?)?\s*(.*)}', $constraint, $matches)) { try { try { $version = $this->normalize($matches[2]); } catch (\UnexpectedValueException $e) { if (substr($matches[2], -4) === '-dev' && preg_match('{^[0-9a-zA-Z-./]+$}', $matches[2])) { $version = $this->normalize('dev-'.substr($matches[2], 0, -4)); } else { throw $e; } } $op = $matches[1] ?: '='; if ($op !== '==' && $op !== '=' && !empty($stabilityModifier) && self::parseStability($version) === 'stable') { $version .= '-' . $stabilityModifier; } elseif ('<' === $op || '>=' === $op) { if (!preg_match('/-' . self::$modifierRegex . '$/', strtolower($matches[2]))) { if (strpos($matches[2], 'dev-') !== 0) { $version .= '-dev'; } } } return array(new Constraint($matches[1] ?: '=', $version)); } catch (\Exception $e) { } } $message = 'Could not parse version constraint ' . $constraint; if (isset($e)) { $message .= ': ' . $e->getMessage(); } throw new \UnexpectedValueException($message); } private function manipulateVersionString(array $matches, $position, $increment = 0, $pad = '0') { for ($i = 4; $i > 0; --$i) { if ($i > $position) { $matches[$i] = $pad; } elseif ($i === $position && $increment) { $matches[$i] += $increment; if ($matches[$i] < 0) { $matches[$i] = $pad; --$position; if ($i === 1) { return null; } } } } return $matches[1] . '.' . $matches[2] . '.' . $matches[3] . '.' . $matches[4]; } private function expandStability($stability) { $stability = strtolower($stability); switch ($stability) { case 'a': return 'alpha'; case 'b': return 'beta'; case 'p': case 'pl': return 'patch'; case 'rc': return 'RC'; default: return $stability; } } } Copyright (C) 2015 Composer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. { "389-exception": [ "389 Directory Server Exception" ], "Asterisk-exception": [ "Asterisk exception" ], "Asterisk-linking-protocols-exception": [ "Asterisk linking protocols exception" ], "Autoconf-exception-2.0": [ "Autoconf exception 2.0" ], "Autoconf-exception-3.0": [ "Autoconf exception 3.0" ], "Autoconf-exception-generic": [ "Autoconf generic exception" ], "Autoconf-exception-generic-3.0": [ "Autoconf generic exception for GPL-3.0" ], "Autoconf-exception-macro": [ "Autoconf macro exception" ], "Bison-exception-1.24": [ "Bison exception 1.24" ], "Bison-exception-2.2": [ "Bison exception 2.2" ], "Bootloader-exception": [ "Bootloader Distribution Exception" ], "CGAL-linking-exception": [ "CGAL Linking Exception" ], "Classpath-exception-2.0": [ "Classpath exception 2.0" ], "Classpath-exception-2.0-short": [ "Classpath exception 2.0 - short" ], "CLISP-exception-2.0": [ "CLISP exception 2.0" ], "cryptsetup-OpenSSL-exception": [ "cryptsetup OpenSSL exception" ], "Digia-Qt-LGPL-exception-1.1": [ "Digia Qt LGPL Exception version 1.1" ], "DigiRule-FOSS-exception": [ "DigiRule FOSS License Exception" ], "eCos-exception-2.0": [ "eCos exception 2.0" ], "erlang-otp-linking-exception": [ "Erlang/OTP Linking Exception" ], "Fawkes-Runtime-exception": [ "Fawkes Runtime Exception" ], "FLTK-exception": [ "FLTK exception" ], "fmt-exception": [ "fmt exception" ], "Font-exception-2.0": [ "Font exception 2.0" ], "freertos-exception-2.0": [ "FreeRTOS Exception 2.0" ], "GCC-exception-2.0": [ "GCC Runtime Library exception 2.0" ], "GCC-exception-2.0-note": [ "GCC Runtime Library exception 2.0 - note variant" ], "GCC-exception-3.1": [ "GCC Runtime Library exception 3.1" ], "Gmsh-exception": [ "Gmsh exception" ], "GNAT-exception": [ "GNAT exception" ], "GNOME-examples-exception": [ "GNOME examples exception" ], "GNU-compiler-exception": [ "GNU Compiler Exception" ], "gnu-javamail-exception": [ "GNU JavaMail exception" ], "GPL-3.0-389-ds-base-exception": [ "GPL-3.0 389 DS Base Exception" ], "GPL-3.0-interface-exception": [ "GPL-3.0 Interface Exception" ], "GPL-3.0-linking-exception": [ "GPL-3.0 Linking Exception" ], "GPL-3.0-linking-source-exception": [ "GPL-3.0 Linking Exception (with Corresponding Source)" ], "GPL-CC-1.0": [ "GPL Cooperation Commitment 1.0" ], "GStreamer-exception-2005": [ "GStreamer Exception (2005)" ], "GStreamer-exception-2008": [ "GStreamer Exception (2008)" ], "harbour-exception": [ "harbour exception" ], "i2p-gpl-java-exception": [ "i2p GPL+Java Exception" ], "Independent-modules-exception": [ "Independent Module Linking exception" ], "KiCad-libraries-exception": [ "KiCad Libraries Exception" ], "kvirc-openssl-exception": [ "kvirc OpenSSL Exception" ], "LGPL-3.0-linking-exception": [ "LGPL-3.0 Linking Exception" ], "libpri-OpenH323-exception": [ "libpri OpenH323 exception" ], "Libtool-exception": [ "Libtool Exception" ], "Linux-syscall-note": [ "Linux Syscall Note" ], "LLGPL": [ "LLGPL Preamble" ], "LLVM-exception": [ "LLVM Exception" ], "LZMA-exception": [ "LZMA exception" ], "mif-exception": [ "Macros and Inline Functions Exception" ], "mxml-exception": [ "mxml Exception" ], "Nokia-Qt-exception-1.1": [ "Nokia Qt LGPL exception 1.1" ], "OCaml-LGPL-linking-exception": [ "OCaml LGPL Linking Exception" ], "OCCT-exception-1.0": [ "Open CASCADE Exception 1.0" ], "OpenJDK-assembly-exception-1.0": [ "OpenJDK Assembly exception 1.0" ], "openvpn-openssl-exception": [ "OpenVPN OpenSSL Exception" ], "PCRE2-exception": [ "PCRE2 exception" ], "polyparse-exception": [ "Polyparse Exception" ], "PS-or-PDF-font-exception-20170817": [ "PS/PDF font exception (2017-08-17)" ], "QPL-1.0-INRIA-2004-exception": [ "INRIA QPL 1.0 2004 variant exception" ], "Qt-GPL-exception-1.0": [ "Qt GPL exception 1.0" ], "Qt-LGPL-exception-1.1": [ "Qt LGPL exception 1.1" ], "Qwt-exception-1.0": [ "Qwt exception 1.0" ], "romic-exception": [ "Romic Exception" ], "RRDtool-FLOSS-exception-2.0": [ "RRDtool FLOSS exception 2.0" ], "rsync-linking-exception": [ "rsync Linking Exception" ], "SANE-exception": [ "SANE Exception" ], "SHL-2.0": [ "Solderpad Hardware License v2.0" ], "SHL-2.1": [ "Solderpad Hardware License v2.1" ], "Simple-Library-Usage-exception": [ "Simple Library Usage Exception" ], "sqlitestudio-OpenSSL-exception": [ "sqlitestudio OpenSSL exception" ], "stunnel-exception": [ "stunnel Exception" ], "SWI-exception": [ "SWI exception" ], "Swift-exception": [ "Swift Exception" ], "Texinfo-exception": [ "Texinfo exception" ], "u-boot-exception-2.0": [ "U-Boot exception 2.0" ], "UBDL-exception": [ "Unmodified Binary Distribution exception" ], "Universal-FOSS-exception-1.0": [ "Universal FOSS Exception, Version 1.0" ], "vsftpd-openssl-exception": [ "vsftpd OpenSSL exception" ], "WxWindows-exception-3.1": [ "WxWindows Library Exception 3.1" ], "x11vnc-openssl-exception": [ "x11vnc OpenSSL Exception" ] }{ "0BSD": [ "BSD Zero Clause License", true, false ], "3D-Slicer-1.0": [ "3D Slicer License v1.0", false, false ], "AAL": [ "Attribution Assurance License", true, false ], "Abstyles": [ "Abstyles License", false, false ], "AdaCore-doc": [ "AdaCore Doc License", false, false ], "Adobe-2006": [ "Adobe Systems Incorporated Source Code License Agreement", false, false ], "Adobe-Display-PostScript": [ "Adobe Display PostScript License", false, false ], "Adobe-Glyph": [ "Adobe Glyph List License", false, false ], "Adobe-Utopia": [ "Adobe Utopia Font License", false, false ], "ADSL": [ "Amazon Digital Services License", false, false ], "Advanced-Cryptics-Dictionary": [ "Advanced Cryptics Dictionary License", false, false ], "AFL-1.1": [ "Academic Free License v1.1", true, false ], "AFL-1.2": [ "Academic Free License v1.2", true, false ], "AFL-2.0": [ "Academic Free License v2.0", true, false ], "AFL-2.1": [ "Academic Free License v2.1", true, false ], "AFL-3.0": [ "Academic Free License v3.0", true, false ], "Afmparse": [ "Afmparse License", false, false ], "AGPL-1.0": [ "Affero General Public License v1.0", false, true ], "AGPL-1.0-only": [ "Affero General Public License v1.0 only", false, false ], "AGPL-1.0-or-later": [ "Affero General Public License v1.0 or later", false, false ], "AGPL-3.0": [ "GNU Affero General Public License v3.0", true, true ], "AGPL-3.0-only": [ "GNU Affero General Public License v3.0 only", true, false ], "AGPL-3.0-or-later": [ "GNU Affero General Public License v3.0 or later", true, false ], "Aladdin": [ "Aladdin Free Public License", false, false ], "ALGLIB-Documentation": [ "ALGLIB Documentation License", true, false ], "AMD-newlib": [ "AMD newlib License", false, false ], "AMDPLPA": [ "AMD's plpa_map.c License", false, false ], "AML": [ "Apple MIT License", false, false ], "AML-glslang": [ "AML glslang variant License", false, false ], "AMPAS": [ "Academy of Motion Picture Arts and Sciences BSD", false, false ], "ANTLR-PD": [ "ANTLR Software Rights Notice", false, false ], "ANTLR-PD-fallback": [ "ANTLR Software Rights Notice with license fallback", false, false ], "any-OSI": [ "Any OSI License", false, false ], "any-OSI-perl-modules": [ "Any OSI License - Perl Modules", false, false ], "Apache-1.0": [ "Apache License 1.0", false, false ], "Apache-1.1": [ "Apache License 1.1", true, false ], "Apache-2.0": [ "Apache License 2.0", true, false ], "APAFML": [ "Adobe Postscript AFM License", false, false ], "APL-1.0": [ "Adaptive Public License 1.0", true, false ], "App-s2p": [ "App::s2p License", false, false ], "APSL-1.0": [ "Apple Public Source License 1.0", true, false ], "APSL-1.1": [ "Apple Public Source License 1.1", true, false ], "APSL-1.2": [ "Apple Public Source License 1.2", true, false ], "APSL-2.0": [ "Apple Public Source License 2.0", true, false ], "Arphic-1999": [ "Arphic Public License", false, false ], "Artistic-1.0": [ "Artistic License 1.0", true, false ], "Artistic-1.0-cl8": [ "Artistic License 1.0 w/clause 8", true, false ], "Artistic-1.0-Perl": [ "Artistic License 1.0 (Perl)", true, false ], "Artistic-2.0": [ "Artistic License 2.0", true, false ], "Artistic-dist": [ "Artistic License 1.0 (dist)", false, false ], "Aspell-RU": [ "Aspell Russian License", false, false ], "ASWF-Digital-Assets-1.0": [ "ASWF Digital Assets License version 1.0", false, false ], "ASWF-Digital-Assets-1.1": [ "ASWF Digital Assets License 1.1", false, false ], "Baekmuk": [ "Baekmuk License", false, false ], "Bahyph": [ "Bahyph License", false, false ], "Barr": [ "Barr License", false, false ], "bcrypt-Solar-Designer": [ "bcrypt Solar Designer License", false, false ], "Beerware": [ "Beerware License", false, false ], "Bitstream-Charter": [ "Bitstream Charter Font License", false, false ], "Bitstream-Vera": [ "Bitstream Vera Font License", false, false ], "BitTorrent-1.0": [ "BitTorrent Open Source License v1.0", false, false ], "BitTorrent-1.1": [ "BitTorrent Open Source License v1.1", false, false ], "blessing": [ "SQLite Blessing", false, false ], "BlueOak-1.0.0": [ "Blue Oak Model License 1.0.0", true, false ], "Boehm-GC": [ "Boehm-Demers-Weiser GC License", false, false ], "Boehm-GC-without-fee": [ "Boehm-Demers-Weiser GC License (without fee)", false, false ], "BOLA-1.1": [ "Buena Onda License Agreement v1.1", false, false ], "Borceux": [ "Borceux license", false, false ], "Brian-Gladman-2-Clause": [ "Brian Gladman 2-Clause License", false, false ], "Brian-Gladman-3-Clause": [ "Brian Gladman 3-Clause License", false, false ], "BSD-1-Clause": [ "BSD 1-Clause License", true, false ], "BSD-2-Clause": [ "BSD 2-Clause \"Simplified\" License", true, false ], "BSD-2-Clause-Darwin": [ "BSD 2-Clause - Ian Darwin variant", false, false ], "BSD-2-Clause-first-lines": [ "BSD 2-Clause - first lines requirement", false, false ], "BSD-2-Clause-FreeBSD": [ "BSD 2-Clause FreeBSD License", false, true ], "BSD-2-Clause-NetBSD": [ "BSD 2-Clause NetBSD License", false, true ], "BSD-2-Clause-Patent": [ "BSD-2-Clause Plus Patent License", true, false ], "BSD-2-Clause-pkgconf-disclaimer": [ "BSD 2-Clause pkgconf disclaimer variant", false, false ], "BSD-2-Clause-Views": [ "BSD 2-Clause with views sentence", false, false ], "BSD-3-Clause": [ "BSD 3-Clause \"New\" or \"Revised\" License", true, false ], "BSD-3-Clause-acpica": [ "BSD 3-Clause acpica variant", false, false ], "BSD-3-Clause-Attribution": [ "BSD with attribution", false, false ], "BSD-3-Clause-Clear": [ "BSD 3-Clause Clear License", false, false ], "BSD-3-Clause-flex": [ "BSD 3-Clause Flex variant", false, false ], "BSD-3-Clause-HP": [ "Hewlett-Packard BSD variant license", false, false ], "BSD-3-Clause-LBNL": [ "Lawrence Berkeley National Labs BSD variant license", true, false ], "BSD-3-Clause-Modification": [ "BSD 3-Clause Modification", false, false ], "BSD-3-Clause-No-Military-License": [ "BSD 3-Clause No Military License", false, false ], "BSD-3-Clause-No-Nuclear-License": [ "BSD 3-Clause No Nuclear License", false, false ], "BSD-3-Clause-No-Nuclear-License-2014": [ "BSD 3-Clause No Nuclear License 2014", false, false ], "BSD-3-Clause-No-Nuclear-Warranty": [ "BSD 3-Clause No Nuclear Warranty", false, false ], "BSD-3-Clause-Open-MPI": [ "BSD 3-Clause Open MPI variant", true, false ], "BSD-3-Clause-Sun": [ "BSD 3-Clause Sun Microsystems", false, false ], "BSD-3-Clause-Tso": [ "BSD 3-Clause Tso variant", false, false ], "BSD-4-Clause": [ "BSD 4-Clause \"Original\" or \"Old\" License", false, false ], "BSD-4-Clause-Shortened": [ "BSD 4 Clause Shortened", false, false ], "BSD-4-Clause-UC": [ "BSD-4-Clause (University of California-Specific)", false, false ], "BSD-4.3RENO": [ "BSD 4.3 RENO License", false, false ], "BSD-4.3TAHOE": [ "BSD 4.3 TAHOE License", false, false ], "BSD-Advertising-Acknowledgement": [ "BSD Advertising Acknowledgement License", false, false ], "BSD-Attribution-HPND-disclaimer": [ "BSD with Attribution and HPND disclaimer", false, false ], "BSD-Inferno-Nettverk": [ "BSD-Inferno-Nettverk", false, false ], "BSD-Mark-Modifications": [ "BSD Mark Modifications License", false, false ], "BSD-Protection": [ "BSD Protection License", false, false ], "BSD-Source-beginning-file": [ "BSD Source Code Attribution - beginning of file variant", false, false ], "BSD-Source-Code": [ "BSD Source Code Attribution", false, false ], "BSD-Systemics": [ "Systemics BSD variant license", false, false ], "BSD-Systemics-W3Works": [ "Systemics W3Works BSD variant license", false, false ], "BSL-1.0": [ "Boost Software License 1.0", true, false ], "Buddy": [ "Buddy License", false, false ], "BUSL-1.1": [ "Business Source License 1.1", false, false ], "bzip2-1.0.5": [ "bzip2 and libbzip2 License v1.0.5", false, true ], "bzip2-1.0.6": [ "bzip2 and libbzip2 License v1.0.6", false, false ], "C-UDA-1.0": [ "Computational Use of Data Agreement v1.0", false, false ], "CAL-1.0": [ "Cryptographic Autonomy License 1.0", true, false ], "CAL-1.0-Combined-Work-Exception": [ "Cryptographic Autonomy License 1.0 (Combined Work Exception)", true, false ], "Caldera": [ "Caldera License", false, false ], "Caldera-no-preamble": [ "Caldera License (without preamble)", false, false ], "CAPEC-tou": [ "Common Attack Pattern Enumeration and Classification License", false, false ], "Catharon": [ "Catharon License", false, false ], "CATOSL-1.1": [ "Computer Associates Trusted Open Source License 1.1", true, false ], "CC-BY-1.0": [ "Creative Commons Attribution 1.0 Generic", false, false ], "CC-BY-2.0": [ "Creative Commons Attribution 2.0 Generic", false, false ], "CC-BY-2.5": [ "Creative Commons Attribution 2.5 Generic", false, false ], "CC-BY-2.5-AU": [ "Creative Commons Attribution 2.5 Australia", false, false ], "CC-BY-3.0": [ "Creative Commons Attribution 3.0 Unported", false, false ], "CC-BY-3.0-AT": [ "Creative Commons Attribution 3.0 Austria", false, false ], "CC-BY-3.0-AU": [ "Creative Commons Attribution 3.0 Australia", false, false ], "CC-BY-3.0-DE": [ "Creative Commons Attribution 3.0 Germany", false, false ], "CC-BY-3.0-IGO": [ "Creative Commons Attribution 3.0 IGO", false, false ], "CC-BY-3.0-NL": [ "Creative Commons Attribution 3.0 Netherlands", false, false ], "CC-BY-3.0-US": [ "Creative Commons Attribution 3.0 United States", false, false ], "CC-BY-4.0": [ "Creative Commons Attribution 4.0 International", false, false ], "CC-BY-NC-1.0": [ "Creative Commons Attribution Non Commercial 1.0 Generic", false, false ], "CC-BY-NC-2.0": [ "Creative Commons Attribution Non Commercial 2.0 Generic", false, false ], "CC-BY-NC-2.5": [ "Creative Commons Attribution Non Commercial 2.5 Generic", false, false ], "CC-BY-NC-3.0": [ "Creative Commons Attribution Non Commercial 3.0 Unported", false, false ], "CC-BY-NC-3.0-DE": [ "Creative Commons Attribution Non Commercial 3.0 Germany", false, false ], "CC-BY-NC-4.0": [ "Creative Commons Attribution Non Commercial 4.0 International", false, false ], "CC-BY-NC-ND-1.0": [ "Creative Commons Attribution Non Commercial No Derivatives 1.0 Generic", false, false ], "CC-BY-NC-ND-2.0": [ "Creative Commons Attribution Non Commercial No Derivatives 2.0 Generic", false, false ], "CC-BY-NC-ND-2.5": [ "Creative Commons Attribution Non Commercial No Derivatives 2.5 Generic", false, false ], "CC-BY-NC-ND-3.0": [ "Creative Commons Attribution Non Commercial No Derivatives 3.0 Unported", false, false ], "CC-BY-NC-ND-3.0-DE": [ "Creative Commons Attribution Non Commercial No Derivatives 3.0 Germany", false, false ], "CC-BY-NC-ND-3.0-IGO": [ "Creative Commons Attribution Non Commercial No Derivatives 3.0 IGO", false, false ], "CC-BY-NC-ND-4.0": [ "Creative Commons Attribution Non Commercial No Derivatives 4.0 International", false, false ], "CC-BY-NC-SA-1.0": [ "Creative Commons Attribution Non Commercial Share Alike 1.0 Generic", false, false ], "CC-BY-NC-SA-2.0": [ "Creative Commons Attribution Non Commercial Share Alike 2.0 Generic", false, false ], "CC-BY-NC-SA-2.0-DE": [ "Creative Commons Attribution Non Commercial Share Alike 2.0 Germany", false, false ], "CC-BY-NC-SA-2.0-FR": [ "Creative Commons Attribution-NonCommercial-ShareAlike 2.0 France", false, false ], "CC-BY-NC-SA-2.0-UK": [ "Creative Commons Attribution Non Commercial Share Alike 2.0 England and Wales", false, false ], "CC-BY-NC-SA-2.5": [ "Creative Commons Attribution Non Commercial Share Alike 2.5 Generic", false, false ], "CC-BY-NC-SA-3.0": [ "Creative Commons Attribution Non Commercial Share Alike 3.0 Unported", false, false ], "CC-BY-NC-SA-3.0-DE": [ "Creative Commons Attribution Non Commercial Share Alike 3.0 Germany", false, false ], "CC-BY-NC-SA-3.0-IGO": [ "Creative Commons Attribution Non Commercial Share Alike 3.0 IGO", false, false ], "CC-BY-NC-SA-4.0": [ "Creative Commons Attribution Non Commercial Share Alike 4.0 International", false, false ], "CC-BY-ND-1.0": [ "Creative Commons Attribution No Derivatives 1.0 Generic", false, false ], "CC-BY-ND-2.0": [ "Creative Commons Attribution No Derivatives 2.0 Generic", false, false ], "CC-BY-ND-2.5": [ "Creative Commons Attribution No Derivatives 2.5 Generic", false, false ], "CC-BY-ND-3.0": [ "Creative Commons Attribution No Derivatives 3.0 Unported", false, false ], "CC-BY-ND-3.0-DE": [ "Creative Commons Attribution No Derivatives 3.0 Germany", false, false ], "CC-BY-ND-4.0": [ "Creative Commons Attribution No Derivatives 4.0 International", false, false ], "CC-BY-SA-1.0": [ "Creative Commons Attribution Share Alike 1.0 Generic", false, false ], "CC-BY-SA-2.0": [ "Creative Commons Attribution Share Alike 2.0 Generic", false, false ], "CC-BY-SA-2.0-UK": [ "Creative Commons Attribution Share Alike 2.0 England and Wales", false, false ], "CC-BY-SA-2.1-JP": [ "Creative Commons Attribution Share Alike 2.1 Japan", false, false ], "CC-BY-SA-2.5": [ "Creative Commons Attribution Share Alike 2.5 Generic", false, false ], "CC-BY-SA-3.0": [ "Creative Commons Attribution Share Alike 3.0 Unported", false, false ], "CC-BY-SA-3.0-AT": [ "Creative Commons Attribution Share Alike 3.0 Austria", false, false ], "CC-BY-SA-3.0-DE": [ "Creative Commons Attribution Share Alike 3.0 Germany", false, false ], "CC-BY-SA-3.0-IGO": [ "Creative Commons Attribution-ShareAlike 3.0 IGO", false, false ], "CC-BY-SA-4.0": [ "Creative Commons Attribution Share Alike 4.0 International", false, false ], "CC-PDDC": [ "Creative Commons Public Domain Dedication and Certification", false, false ], "CC-PDM-1.0": [ "Creative Commons Public Domain Mark 1.0 Universal", false, false ], "CC-SA-1.0": [ "Creative Commons Share Alike 1.0 Generic", false, false ], "CC0-1.0": [ "Creative Commons Zero v1.0 Universal", false, false ], "CDDL-1.0": [ "Common Development and Distribution License 1.0", true, false ], "CDDL-1.1": [ "Common Development and Distribution License 1.1", false, false ], "CDL-1.0": [ "Common Documentation License 1.0", false, false ], "CDLA-Permissive-1.0": [ "Community Data License Agreement Permissive 1.0", false, false ], "CDLA-Permissive-2.0": [ "Community Data License Agreement Permissive 2.0", false, false ], "CDLA-Sharing-1.0": [ "Community Data License Agreement Sharing 1.0", false, false ], "CECILL-1.0": [ "CeCILL Free Software License Agreement v1.0", false, false ], "CECILL-1.1": [ "CeCILL Free Software License Agreement v1.1", false, false ], "CECILL-2.0": [ "CeCILL Free Software License Agreement v2.0", false, false ], "CECILL-2.1": [ "CeCILL Free Software License Agreement v2.1", true, false ], "CECILL-B": [ "CeCILL-B Free Software License Agreement", false, false ], "CECILL-C": [ "CeCILL-C Free Software License Agreement", false, false ], "CERN-OHL-1.1": [ "CERN Open Hardware Licence v1.1", false, false ], "CERN-OHL-1.2": [ "CERN Open Hardware Licence v1.2", false, false ], "CERN-OHL-P-2.0": [ "CERN Open Hardware Licence Version 2 - Permissive", true, false ], "CERN-OHL-S-2.0": [ "CERN Open Hardware Licence Version 2 - Strongly Reciprocal", true, false ], "CERN-OHL-W-2.0": [ "CERN Open Hardware Licence Version 2 - Weakly Reciprocal", true, false ], "CFITSIO": [ "CFITSIO License", false, false ], "check-cvs": [ "check-cvs License", false, false ], "checkmk": [ "Checkmk License", false, false ], "ClArtistic": [ "Clarified Artistic License", false, false ], "Clips": [ "Clips License", false, false ], "CMU-Mach": [ "CMU Mach License", false, false ], "CMU-Mach-nodoc": [ "CMU Mach - no notices-in-documentation variant", false, false ], "CNRI-Jython": [ "CNRI Jython License", false, false ], "CNRI-Python": [ "CNRI Python License", true, false ], "CNRI-Python-GPL-Compatible": [ "CNRI Python Open Source GPL Compatible License Agreement", false, false ], "COIL-1.0": [ "Copyfree Open Innovation License", false, false ], "Community-Spec-1.0": [ "Community Specification License 1.0", false, false ], "Condor-1.1": [ "Condor Public License v1.1", false, false ], "copyleft-next-0.3.0": [ "copyleft-next 0.3.0", false, false ], "copyleft-next-0.3.1": [ "copyleft-next 0.3.1", false, false ], "Cornell-Lossless-JPEG": [ "Cornell Lossless JPEG License", false, false ], "CPAL-1.0": [ "Common Public Attribution License 1.0", true, false ], "CPL-1.0": [ "Common Public License 1.0", true, false ], "CPOL-1.02": [ "Code Project Open License 1.02", false, false ], "Cronyx": [ "Cronyx License", false, false ], "Crossword": [ "Crossword License", false, false ], "CryptoSwift": [ "CryptoSwift License", false, false ], "CrystalStacker": [ "CrystalStacker License", false, false ], "CUA-OPL-1.0": [ "CUA Office Public License v1.0", true, false ], "Cube": [ "Cube License", false, false ], "curl": [ "curl License", false, false ], "cve-tou": [ "Common Vulnerability Enumeration ToU License", false, false ], "D-FSL-1.0": [ "Deutsche Freie Software Lizenz", false, false ], "DEC-3-Clause": [ "DEC 3-Clause License", false, false ], "diffmark": [ "diffmark license", false, false ], "DL-DE-BY-2.0": [ "Data licence Germany \u2013 attribution \u2013 version 2.0", false, false ], "DL-DE-ZERO-2.0": [ "Data licence Germany \u2013 zero \u2013 version 2.0", false, false ], "DOC": [ "DOC License", false, false ], "DocBook-DTD": [ "DocBook DTD License", false, false ], "DocBook-Schema": [ "DocBook Schema License", false, false ], "DocBook-Stylesheet": [ "DocBook Stylesheet License", false, false ], "DocBook-XML": [ "DocBook XML License", false, false ], "Dotseqn": [ "Dotseqn License", false, false ], "DRL-1.0": [ "Detection Rule License 1.0", false, false ], "DRL-1.1": [ "Detection Rule License 1.1", false, false ], "DSDP": [ "DSDP License", false, false ], "dtoa": [ "David M. Gay dtoa License", false, false ], "dvipdfm": [ "dvipdfm License", false, false ], "ECL-1.0": [ "Educational Community License v1.0", true, false ], "ECL-2.0": [ "Educational Community License v2.0", true, false ], "eCos-2.0": [ "eCos license version 2.0", false, true ], "EFL-1.0": [ "Eiffel Forum License v1.0", true, false ], "EFL-2.0": [ "Eiffel Forum License v2.0", true, false ], "eGenix": [ "eGenix.com Public License 1.1.0", false, false ], "Elastic-2.0": [ "Elastic License 2.0", false, false ], "Entessa": [ "Entessa Public License v1.0", true, false ], "EPICS": [ "EPICS Open License", false, false ], "EPL-1.0": [ "Eclipse Public License 1.0", true, false ], "EPL-2.0": [ "Eclipse Public License 2.0", true, false ], "ErlPL-1.1": [ "Erlang Public License v1.1", false, false ], "ESA-PL-permissive-2.4": [ "European Space Agency Public License \u2013 v2.4 \u2013 Permissive (Type 3)", false, false ], "ESA-PL-strong-copyleft-2.4": [ "European Space Agency Public License (ESA-PL) - V2.4 - Strong Copyleft (Type 1)", false, false ], "ESA-PL-weak-copyleft-2.4": [ "European Space Agency Public License \u2013 v2.4 \u2013 Weak Copyleft (Type 2)", false, false ], "etalab-2.0": [ "Etalab Open License 2.0", false, false ], "EUDatagrid": [ "EU DataGrid Software License", true, false ], "EUPL-1.0": [ "European Union Public License 1.0", false, false ], "EUPL-1.1": [ "European Union Public License 1.1", true, false ], "EUPL-1.2": [ "European Union Public License 1.2", true, false ], "Eurosym": [ "Eurosym License", false, false ], "Fair": [ "Fair License", true, false ], "FBM": [ "Fuzzy Bitmap License", false, false ], "FDK-AAC": [ "Fraunhofer FDK AAC Codec Library", false, false ], "Ferguson-Twofish": [ "Ferguson Twofish License", false, false ], "Frameworx-1.0": [ "Frameworx Open License 1.0", true, false ], "FreeBSD-DOC": [ "FreeBSD Documentation License", false, false ], "FreeImage": [ "FreeImage Public License v1.0", false, false ], "FSFAP": [ "FSF All Permissive License", false, false ], "FSFAP-no-warranty-disclaimer": [ "FSF All Permissive License (without Warranty)", false, false ], "FSFUL": [ "FSF Unlimited License", false, false ], "FSFULLR": [ "FSF Unlimited License (with License Retention)", false, false ], "FSFULLRSD": [ "FSF Unlimited License (with License Retention and Short Disclaimer)", false, false ], "FSFULLRWD": [ "FSF Unlimited License (With License Retention and Warranty Disclaimer)", false, false ], "FSL-1.1-ALv2": [ "Functional Source License, Version 1.1, ALv2 Future License", false, false ], "FSL-1.1-MIT": [ "Functional Source License, Version 1.1, MIT Future License", false, false ], "FTL": [ "Freetype Project License", false, false ], "Furuseth": [ "Furuseth License", false, false ], "fwlw": [ "fwlw License", false, false ], "Game-Programming-Gems": [ "Game Programming Gems License", false, false ], "GCR-docs": [ "Gnome GCR Documentation License", false, false ], "GD": [ "GD License", false, false ], "generic-xts": [ "Generic XTS License", false, false ], "GFDL-1.1": [ "GNU Free Documentation License v1.1", false, true ], "GFDL-1.1-invariants-only": [ "GNU Free Documentation License v1.1 only - invariants", false, false ], "GFDL-1.1-invariants-or-later": [ "GNU Free Documentation License v1.1 or later - invariants", false, false ], "GFDL-1.1-no-invariants-only": [ "GNU Free Documentation License v1.1 only - no invariants", false, false ], "GFDL-1.1-no-invariants-or-later": [ "GNU Free Documentation License v1.1 or later - no invariants", false, false ], "GFDL-1.1-only": [ "GNU Free Documentation License v1.1 only", false, false ], "GFDL-1.1-or-later": [ "GNU Free Documentation License v1.1 or later", false, false ], "GFDL-1.2": [ "GNU Free Documentation License v1.2", false, true ], "GFDL-1.2-invariants-only": [ "GNU Free Documentation License v1.2 only - invariants", false, false ], "GFDL-1.2-invariants-or-later": [ "GNU Free Documentation License v1.2 or later - invariants", false, false ], "GFDL-1.2-no-invariants-only": [ "GNU Free Documentation License v1.2 only - no invariants", false, false ], "GFDL-1.2-no-invariants-or-later": [ "GNU Free Documentation License v1.2 or later - no invariants", false, false ], "GFDL-1.2-only": [ "GNU Free Documentation License v1.2 only", false, false ], "GFDL-1.2-or-later": [ "GNU Free Documentation License v1.2 or later", false, false ], "GFDL-1.3": [ "GNU Free Documentation License v1.3", false, true ], "GFDL-1.3-invariants-only": [ "GNU Free Documentation License v1.3 only - invariants", false, false ], "GFDL-1.3-invariants-or-later": [ "GNU Free Documentation License v1.3 or later - invariants", false, false ], "GFDL-1.3-no-invariants-only": [ "GNU Free Documentation License v1.3 only - no invariants", false, false ], "GFDL-1.3-no-invariants-or-later": [ "GNU Free Documentation License v1.3 or later - no invariants", false, false ], "GFDL-1.3-only": [ "GNU Free Documentation License v1.3 only", false, false ], "GFDL-1.3-or-later": [ "GNU Free Documentation License v1.3 or later", false, false ], "Giftware": [ "Giftware License", false, false ], "GL2PS": [ "GL2PS License", false, false ], "Glide": [ "3dfx Glide License", false, false ], "Glulxe": [ "Glulxe License", false, false ], "GLWTPL": [ "Good Luck With That Public License", false, false ], "gnuplot": [ "gnuplot License", false, false ], "GPL-1.0": [ "GNU General Public License v1.0 only", false, true ], "GPL-1.0+": [ "GNU General Public License v1.0 or later", false, true ], "GPL-1.0-only": [ "GNU General Public License v1.0 only", false, false ], "GPL-1.0-or-later": [ "GNU General Public License v1.0 or later", false, false ], "GPL-2.0": [ "GNU General Public License v2.0 only", true, true ], "GPL-2.0+": [ "GNU General Public License v2.0 or later", true, true ], "GPL-2.0-only": [ "GNU General Public License v2.0 only", true, false ], "GPL-2.0-or-later": [ "GNU General Public License v2.0 or later", true, false ], "GPL-2.0-with-autoconf-exception": [ "GNU General Public License v2.0 w/Autoconf exception", false, true ], "GPL-2.0-with-bison-exception": [ "GNU General Public License v2.0 w/Bison exception", false, true ], "GPL-2.0-with-classpath-exception": [ "GNU General Public License v2.0 w/Classpath exception", false, true ], "GPL-2.0-with-font-exception": [ "GNU General Public License v2.0 w/Font exception", false, true ], "GPL-2.0-with-GCC-exception": [ "GNU General Public License v2.0 w/GCC Runtime Library exception", false, true ], "GPL-3.0": [ "GNU General Public License v3.0 only", true, true ], "GPL-3.0+": [ "GNU General Public License v3.0 or later", true, true ], "GPL-3.0-only": [ "GNU General Public License v3.0 only", true, false ], "GPL-3.0-or-later": [ "GNU General Public License v3.0 or later", true, false ], "GPL-3.0-with-autoconf-exception": [ "GNU General Public License v3.0 w/Autoconf exception", false, true ], "GPL-3.0-with-GCC-exception": [ "GNU General Public License v3.0 w/GCC Runtime Library exception", true, true ], "Graphics-Gems": [ "Graphics Gems License", false, false ], "gSOAP-1.3b": [ "gSOAP Public License v1.3b", false, false ], "gtkbook": [ "gtkbook License", false, false ], "Gutmann": [ "Gutmann License", false, false ], "HaskellReport": [ "Haskell Language Report License", false, false ], "HDF5": [ "HDF5 License", false, false ], "hdparm": [ "hdparm License", false, false ], "HIDAPI": [ "HIDAPI License", false, false ], "Hippocratic-2.1": [ "Hippocratic License 2.1", false, false ], "HP-1986": [ "Hewlett-Packard 1986 License", false, false ], "HP-1989": [ "Hewlett-Packard 1989 License", false, false ], "HPND": [ "Historical Permission Notice and Disclaimer", true, false ], "HPND-DEC": [ "Historical Permission Notice and Disclaimer - DEC variant", false, false ], "HPND-doc": [ "Historical Permission Notice and Disclaimer - documentation variant", false, false ], "HPND-doc-sell": [ "Historical Permission Notice and Disclaimer - documentation sell variant", false, false ], "HPND-export-US": [ "HPND with US Government export control warning", false, false ], "HPND-export-US-acknowledgement": [ "HPND with US Government export control warning and acknowledgment", false, false ], "HPND-export-US-modify": [ "HPND with US Government export control warning and modification rqmt", false, false ], "HPND-export2-US": [ "HPND with US Government export control and 2 disclaimers", false, false ], "HPND-Fenneberg-Livingston": [ "Historical Permission Notice and Disclaimer - Fenneberg-Livingston variant", false, false ], "HPND-INRIA-IMAG": [ "Historical Permission Notice and Disclaimer - INRIA-IMAG variant", false, false ], "HPND-Intel": [ "Historical Permission Notice and Disclaimer - Intel variant", false, false ], "HPND-Kevlin-Henney": [ "Historical Permission Notice and Disclaimer - Kevlin Henney variant", false, false ], "HPND-Markus-Kuhn": [ "Historical Permission Notice and Disclaimer - Markus Kuhn variant", false, false ], "HPND-merchantability-variant": [ "Historical Permission Notice and Disclaimer - merchantability variant", false, false ], "HPND-MIT-disclaimer": [ "Historical Permission Notice and Disclaimer with MIT disclaimer", false, false ], "HPND-Netrek": [ "Historical Permission Notice and Disclaimer - Netrek variant", false, false ], "HPND-Pbmplus": [ "Historical Permission Notice and Disclaimer - Pbmplus variant", false, false ], "HPND-sell-MIT-disclaimer-xserver": [ "Historical Permission Notice and Disclaimer - sell xserver variant with MIT disclaimer", false, false ], "HPND-sell-regexpr": [ "Historical Permission Notice and Disclaimer - sell regexpr variant", false, false ], "HPND-sell-variant": [ "Historical Permission Notice and Disclaimer - sell variant", false, false ], "HPND-sell-variant-critical-systems": [ "HPND - sell variant with safety critical systems clause", false, false ], "HPND-sell-variant-MIT-disclaimer": [ "HPND sell variant with MIT disclaimer", false, false ], "HPND-sell-variant-MIT-disclaimer-rev": [ "HPND sell variant with MIT disclaimer - reverse", false, false ], "HPND-SMC": [ "Historical Permission Notice and Disclaimer - SMC variant", false, false ], "HPND-UC": [ "Historical Permission Notice and Disclaimer - University of California variant", false, false ], "HPND-UC-export-US": [ "Historical Permission Notice and Disclaimer - University of California, US export warning", false, false ], "HTMLTIDY": [ "HTML Tidy License", false, false ], "hyphen-bulgarian": [ "hyphen-bulgarian License", false, false ], "IBM-pibs": [ "IBM PowerPC Initialization and Boot Software", false, false ], "ICU": [ "ICU License", true, false ], "IEC-Code-Components-EULA": [ "IEC Code Components End-user licence agreement", false, false ], "IJG": [ "Independent JPEG Group License", false, false ], "IJG-short": [ "Independent JPEG Group License - short", false, false ], "ImageMagick": [ "ImageMagick License", false, false ], "iMatix": [ "iMatix Standard Function Library Agreement", false, false ], "Imlib2": [ "Imlib2 License", false, false ], "Info-ZIP": [ "Info-ZIP License", false, false ], "Inner-Net-2.0": [ "Inner Net License v2.0", false, false ], "InnoSetup": [ "Inno Setup License", false, false ], "Intel": [ "Intel Open Source License", true, false ], "Intel-ACPI": [ "Intel ACPI Software License Agreement", false, false ], "Interbase-1.0": [ "Interbase Public License v1.0", false, false ], "IPA": [ "IPA Font License", true, false ], "IPL-1.0": [ "IBM Public License v1.0", true, false ], "ISC": [ "ISC License", true, false ], "ISC-Veillard": [ "ISC Veillard variant", false, false ], "ISO-permission": [ "ISO permission notice", false, false ], "Jam": [ "Jam License", true, false ], "JasPer-2.0": [ "JasPer License", false, false ], "jove": [ "Jove License", false, false ], "JPL-image": [ "JPL Image Use Policy", false, false ], "JPNIC": [ "Japan Network Information Center License", false, false ], "JSON": [ "JSON License", false, false ], "Kastrup": [ "Kastrup License", false, false ], "Kazlib": [ "Kazlib License", false, false ], "Knuth-CTAN": [ "Knuth CTAN License", false, false ], "LAL-1.2": [ "Licence Art Libre 1.2", false, false ], "LAL-1.3": [ "Licence Art Libre 1.3", false, false ], "Latex2e": [ "Latex2e License", false, false ], "Latex2e-translated-notice": [ "Latex2e with translated notice permission", false, false ], "Leptonica": [ "Leptonica License", false, false ], "LGPL-2.0": [ "GNU Library General Public License v2 only", true, true ], "LGPL-2.0+": [ "GNU Library General Public License v2 or later", true, true ], "LGPL-2.0-only": [ "GNU Library General Public License v2 only", true, false ], "LGPL-2.0-or-later": [ "GNU Library General Public License v2 or later", true, false ], "LGPL-2.1": [ "GNU Lesser General Public License v2.1 only", true, true ], "LGPL-2.1+": [ "GNU Lesser General Public License v2.1 or later", true, true ], "LGPL-2.1-only": [ "GNU Lesser General Public License v2.1 only", true, false ], "LGPL-2.1-or-later": [ "GNU Lesser General Public License v2.1 or later", true, false ], "LGPL-3.0": [ "GNU Lesser General Public License v3.0 only", true, true ], "LGPL-3.0+": [ "GNU Lesser General Public License v3.0 or later", true, true ], "LGPL-3.0-only": [ "GNU Lesser General Public License v3.0 only", true, false ], "LGPL-3.0-or-later": [ "GNU Lesser General Public License v3.0 or later", true, false ], "LGPLLR": [ "Lesser General Public License For Linguistic Resources", false, false ], "Libpng": [ "libpng License", false, false ], "libpng-1.6.35": [ "PNG Reference Library License v1 (for libpng 0.5 through 1.6.35)", false, false ], "libpng-2.0": [ "PNG Reference Library version 2", false, false ], "libselinux-1.0": [ "libselinux public domain notice", false, false ], "libtiff": [ "libtiff License", false, false ], "libutil-David-Nugent": [ "libutil David Nugent License", false, false ], "LiLiQ-P-1.1": [ "Licence Libre du Qu\u00e9bec \u2013 Permissive version 1.1", true, false ], "LiLiQ-R-1.1": [ "Licence Libre du Qu\u00e9bec \u2013 R\u00e9ciprocit\u00e9 version 1.1", true, false ], "LiLiQ-Rplus-1.1": [ "Licence Libre du Qu\u00e9bec \u2013 R\u00e9ciprocit\u00e9 forte version 1.1", true, false ], "Linux-man-pages-1-para": [ "Linux man-pages - 1 paragraph", false, false ], "Linux-man-pages-copyleft": [ "Linux man-pages Copyleft", false, false ], "Linux-man-pages-copyleft-2-para": [ "Linux man-pages Copyleft - 2 paragraphs", false, false ], "Linux-man-pages-copyleft-var": [ "Linux man-pages Copyleft Variant", false, false ], "Linux-OpenIB": [ "Linux Kernel Variant of OpenIB.org license", false, false ], "LOOP": [ "Common Lisp LOOP License", false, false ], "LPD-document": [ "LPD Documentation License", false, false ], "LPL-1.0": [ "Lucent Public License Version 1.0", true, false ], "LPL-1.02": [ "Lucent Public License v1.02", true, false ], "LPPL-1.0": [ "LaTeX Project Public License v1.0", false, false ], "LPPL-1.1": [ "LaTeX Project Public License v1.1", false, false ], "LPPL-1.2": [ "LaTeX Project Public License v1.2", false, false ], "LPPL-1.3a": [ "LaTeX Project Public License v1.3a", false, false ], "LPPL-1.3c": [ "LaTeX Project Public License v1.3c", true, false ], "lsof": [ "lsof License", false, false ], "Lucida-Bitmap-Fonts": [ "Lucida Bitmap Fonts License", false, false ], "LZMA-SDK-9.11-to-9.20": [ "LZMA SDK License (versions 9.11 to 9.20)", false, false ], "LZMA-SDK-9.22": [ "LZMA SDK License (versions 9.22 and beyond)", false, false ], "Mackerras-3-Clause": [ "Mackerras 3-Clause License", false, false ], "Mackerras-3-Clause-acknowledgment": [ "Mackerras 3-Clause - acknowledgment variant", false, false ], "magaz": [ "magaz License", false, false ], "mailprio": [ "mailprio License", false, false ], "MakeIndex": [ "MakeIndex License", false, false ], "man2html": [ "man2html License", false, false ], "Martin-Birgmeier": [ "Martin Birgmeier License", false, false ], "McPhee-slideshow": [ "McPhee Slideshow License", false, false ], "metamail": [ "metamail License", false, false ], "Minpack": [ "Minpack License", false, false ], "MIPS": [ "MIPS License", false, false ], "MirOS": [ "The MirOS Licence", true, false ], "MIT": [ "MIT License", true, false ], "MIT-0": [ "MIT No Attribution", true, false ], "MIT-advertising": [ "Enlightenment License (e16)", false, false ], "MIT-Click": [ "MIT Click License", false, false ], "MIT-CMU": [ "CMU License", false, false ], "MIT-enna": [ "enna License", false, false ], "MIT-feh": [ "feh License", false, false ], "MIT-Festival": [ "MIT Festival Variant", false, false ], "MIT-Khronos-old": [ "MIT Khronos - old variant", false, false ], "MIT-Modern-Variant": [ "MIT License Modern Variant", true, false ], "MIT-open-group": [ "MIT Open Group variant", false, false ], "MIT-STK": [ "MIT-STK License", false, false ], "MIT-testregex": [ "MIT testregex Variant", false, false ], "MIT-Wu": [ "MIT Tom Wu Variant", false, false ], "MITNFA": [ "MIT +no-false-attribs license", false, false ], "MMIXware": [ "MMIXware License", false, false ], "MMPL-1.0.1": [ "Minecraft Mod Public License v1.0.1", false, false ], "Motosoto": [ "Motosoto License", true, false ], "MPEG-SSG": [ "MPEG Software Simulation", false, false ], "mpi-permissive": [ "mpi Permissive License", false, false ], "mpich2": [ "mpich2 License", false, false ], "MPL-1.0": [ "Mozilla Public License 1.0", true, false ], "MPL-1.1": [ "Mozilla Public License 1.1", true, false ], "MPL-2.0": [ "Mozilla Public License 2.0", true, false ], "MPL-2.0-no-copyleft-exception": [ "Mozilla Public License 2.0 (no copyleft exception)", true, false ], "mplus": [ "mplus Font License", false, false ], "MS-LPL": [ "Microsoft Limited Public License", false, false ], "MS-PL": [ "Microsoft Public License", true, false ], "MS-RL": [ "Microsoft Reciprocal License", true, false ], "MTLL": [ "Matrix Template Library License", false, false ], "MulanPSL-1.0": [ "Mulan Permissive Software License, Version 1", false, false ], "MulanPSL-2.0": [ "Mulan Permissive Software License, Version 2", true, false ], "Multics": [ "Multics License", true, false ], "Mup": [ "Mup License", false, false ], "NAIST-2003": [ "Nara Institute of Science and Technology License (2003)", false, false ], "NASA-1.3": [ "NASA Open Source Agreement 1.3", true, false ], "Naumen": [ "Naumen Public License", true, false ], "NBPL-1.0": [ "Net Boolean Public License v1", false, false ], "NCBI-PD": [ "NCBI Public Domain Notice", false, false ], "NCGL-UK-2.0": [ "Non-Commercial Government Licence", false, false ], "NCL": [ "NCL Source Code License", false, false ], "NCSA": [ "University of Illinois/NCSA Open Source License", true, false ], "Net-SNMP": [ "Net-SNMP License", false, true ], "NetCDF": [ "NetCDF license", false, false ], "Newsletr": [ "Newsletr License", false, false ], "NGPL": [ "Nethack General Public License", true, false ], "ngrep": [ "ngrep License", false, false ], "NICTA-1.0": [ "NICTA Public Software License, Version 1.0", false, false ], "NIST-PD": [ "NIST Public Domain Notice", false, false ], "NIST-PD-fallback": [ "NIST Public Domain Notice with license fallback", false, false ], "NIST-PD-TNT": [ "NIST Public Domain Notice TNT variant", false, false ], "NIST-Software": [ "NIST Software License", false, false ], "NLOD-1.0": [ "Norwegian Licence for Open Government Data (NLOD) 1.0", false, false ], "NLOD-2.0": [ "Norwegian Licence for Open Government Data (NLOD) 2.0", false, false ], "NLPL": [ "No Limit Public License", false, false ], "Nokia": [ "Nokia Open Source License", true, false ], "NOSL": [ "Netizen Open Source License", false, false ], "Noweb": [ "Noweb License", false, false ], "NPL-1.0": [ "Netscape Public License v1.0", false, false ], "NPL-1.1": [ "Netscape Public License v1.1", false, false ], "NPOSL-3.0": [ "Non-Profit Open Software License 3.0", true, false ], "NRL": [ "NRL License", false, false ], "NTIA-PD": [ "NTIA Public Domain Notice", false, false ], "NTP": [ "NTP License", true, false ], "NTP-0": [ "NTP No Attribution", false, false ], "Nunit": [ "Nunit License", false, true ], "O-UDA-1.0": [ "Open Use of Data Agreement v1.0", false, false ], "OAR": [ "OAR License", false, false ], "OCCT-PL": [ "Open CASCADE Technology Public License", false, false ], "OCLC-2.0": [ "OCLC Research Public License 2.0", true, false ], "ODbL-1.0": [ "Open Data Commons Open Database License v1.0", false, false ], "ODC-By-1.0": [ "Open Data Commons Attribution License v1.0", false, false ], "OFFIS": [ "OFFIS License", false, false ], "OFL-1.0": [ "SIL Open Font License 1.0", false, false ], "OFL-1.0-no-RFN": [ "SIL Open Font License 1.0 with no Reserved Font Name", false, false ], "OFL-1.0-RFN": [ "SIL Open Font License 1.0 with Reserved Font Name", false, false ], "OFL-1.1": [ "SIL Open Font License 1.1", true, false ], "OFL-1.1-no-RFN": [ "SIL Open Font License 1.1 with no Reserved Font Name", true, false ], "OFL-1.1-RFN": [ "SIL Open Font License 1.1 with Reserved Font Name", true, false ], "OGC-1.0": [ "OGC Software License, Version 1.0", false, false ], "OGDL-Taiwan-1.0": [ "Taiwan Open Government Data License, version 1.0", false, false ], "OGL-Canada-2.0": [ "Open Government Licence - Canada", false, false ], "OGL-UK-1.0": [ "Open Government Licence v1.0", false, false ], "OGL-UK-2.0": [ "Open Government Licence v2.0", false, false ], "OGL-UK-3.0": [ "Open Government Licence v3.0", false, false ], "OGTSL": [ "Open Group Test Suite License", true, false ], "OLDAP-1.1": [ "Open LDAP Public License v1.1", false, false ], "OLDAP-1.2": [ "Open LDAP Public License v1.2", false, false ], "OLDAP-1.3": [ "Open LDAP Public License v1.3", false, false ], "OLDAP-1.4": [ "Open LDAP Public License v1.4", false, false ], "OLDAP-2.0": [ "Open LDAP Public License v2.0 (or possibly 2.0A and 2.0B)", false, false ], "OLDAP-2.0.1": [ "Open LDAP Public License v2.0.1", false, false ], "OLDAP-2.1": [ "Open LDAP Public License v2.1", false, false ], "OLDAP-2.2": [ "Open LDAP Public License v2.2", false, false ], "OLDAP-2.2.1": [ "Open LDAP Public License v2.2.1", false, false ], "OLDAP-2.2.2": [ "Open LDAP Public License 2.2.2", false, false ], "OLDAP-2.3": [ "Open LDAP Public License v2.3", false, false ], "OLDAP-2.4": [ "Open LDAP Public License v2.4", false, false ], "OLDAP-2.5": [ "Open LDAP Public License v2.5", false, false ], "OLDAP-2.6": [ "Open LDAP Public License v2.6", false, false ], "OLDAP-2.7": [ "Open LDAP Public License v2.7", false, false ], "OLDAP-2.8": [ "Open LDAP Public License v2.8", true, false ], "OLFL-1.3": [ "Open Logistics Foundation License Version 1.3", true, false ], "OML": [ "Open Market License", false, false ], "OpenMDW-1.0": [ "OpenMDW License Agreement v1.0", false, false ], "OpenPBS-2.3": [ "OpenPBS v2.3 Software License", false, false ], "OpenSSL": [ "OpenSSL License", false, false ], "OpenSSL-standalone": [ "OpenSSL License - standalone", false, false ], "OpenVision": [ "OpenVision License", false, false ], "OPL-1.0": [ "Open Public License v1.0", false, false ], "OPL-UK-3.0": [ "United Kingdom Open Parliament Licence v3.0", false, false ], "OPUBL-1.0": [ "Open Publication License v1.0", false, false ], "OSC-1.0": [ "OSC License 1.0", true, false ], "OSET-PL-2.1": [ "OSET Public License version 2.1", true, false ], "OSL-1.0": [ "Open Software License 1.0", true, false ], "OSL-1.1": [ "Open Software License 1.1", false, false ], "OSL-2.0": [ "Open Software License 2.0", true, false ], "OSL-2.1": [ "Open Software License 2.1", true, false ], "OSL-3.0": [ "Open Software License 3.0", true, false ], "OSSP": [ "OSSP License", false, false ], "PADL": [ "PADL License", false, false ], "ParaType-Free-Font-1.3": [ "ParaType Free Font Licensing Agreement v1.3", false, false ], "Parity-6.0.0": [ "The Parity Public License 6.0.0", false, false ], "Parity-7.0.0": [ "The Parity Public License 7.0.0", false, false ], "PDDL-1.0": [ "Open Data Commons Public Domain Dedication & License 1.0", false, false ], "PHP-3.0": [ "PHP License v3.0", true, false ], "PHP-3.01": [ "PHP License v3.01", true, false ], "Pixar": [ "Pixar License", false, false ], "pkgconf": [ "pkgconf License", false, false ], "Plexus": [ "Plexus Classworlds License", false, false ], "pnmstitch": [ "pnmstitch License", false, false ], "PolyForm-Noncommercial-1.0.0": [ "PolyForm Noncommercial License 1.0.0", false, false ], "PolyForm-Small-Business-1.0.0": [ "PolyForm Small Business License 1.0.0", false, false ], "PostgreSQL": [ "PostgreSQL License", true, false ], "PPL": [ "Peer Production License", false, false ], "PSF-2.0": [ "Python Software Foundation License 2.0", false, false ], "psfrag": [ "psfrag License", false, false ], "psutils": [ "psutils License", false, false ], "Python-2.0": [ "Python License 2.0", true, false ], "Python-2.0.1": [ "Python License 2.0.1", false, false ], "python-ldap": [ "Python ldap License", false, false ], "Qhull": [ "Qhull License", false, false ], "QPL-1.0": [ "Q Public License 1.0", true, false ], "QPL-1.0-INRIA-2004": [ "Q Public License 1.0 - INRIA 2004 variant", false, false ], "radvd": [ "radvd License", false, false ], "Rdisc": [ "Rdisc License", false, false ], "RHeCos-1.1": [ "Red Hat eCos Public License v1.1", false, false ], "RPL-1.1": [ "Reciprocal Public License 1.1", true, false ], "RPL-1.5": [ "Reciprocal Public License 1.5", true, false ], "RPSL-1.0": [ "RealNetworks Public Source License v1.0", true, false ], "RSA-MD": [ "RSA Message-Digest License", false, false ], "RSCPL": [ "Ricoh Source Code Public License", true, false ], "Ruby": [ "Ruby License", false, false ], "Ruby-pty": [ "Ruby pty extension license", false, false ], "SAX-PD": [ "Sax Public Domain Notice", false, false ], "SAX-PD-2.0": [ "Sax Public Domain Notice 2.0", false, false ], "Saxpath": [ "Saxpath License", false, false ], "SCEA": [ "SCEA Shared Source License", false, false ], "SchemeReport": [ "Scheme Language Report License", false, false ], "Sendmail": [ "Sendmail License", false, false ], "Sendmail-8.23": [ "Sendmail License 8.23", false, false ], "Sendmail-Open-Source-1.1": [ "Sendmail Open Source License v1.1", false, false ], "SGI-B-1.0": [ "SGI Free Software License B v1.0", false, false ], "SGI-B-1.1": [ "SGI Free Software License B v1.1", false, false ], "SGI-B-2.0": [ "SGI Free Software License B v2.0", false, false ], "SGI-OpenGL": [ "SGI OpenGL License", false, false ], "SGMLUG-PM": [ "SGMLUG Parser Materials License", false, false ], "SGP4": [ "SGP4 Permission Notice", false, false ], "SHL-0.5": [ "Solderpad Hardware License v0.5", false, false ], "SHL-0.51": [ "Solderpad Hardware License, Version 0.51", false, false ], "SimPL-2.0": [ "Simple Public License 2.0", true, false ], "SISSL": [ "Sun Industry Standards Source License v1.1", true, false ], "SISSL-1.2": [ "Sun Industry Standards Source License v1.2", false, false ], "SL": [ "SL License", false, false ], "Sleepycat": [ "Sleepycat License", true, false ], "SMAIL-GPL": [ "SMAIL General Public License", false, false ], "SMLNJ": [ "Standard ML of New Jersey License", false, false ], "SMPPL": [ "Secure Messaging Protocol Public License", false, false ], "SNIA": [ "SNIA Public License 1.1", false, false ], "snprintf": [ "snprintf License", false, false ], "SOFA": [ "SOFA Software License", false, false ], "softSurfer": [ "softSurfer License", false, false ], "Soundex": [ "Soundex License", false, false ], "Spencer-86": [ "Spencer License 86", false, false ], "Spencer-94": [ "Spencer License 94", false, false ], "Spencer-99": [ "Spencer License 99", false, false ], "SPL-1.0": [ "Sun Public License v1.0", true, false ], "ssh-keyscan": [ "ssh-keyscan License", false, false ], "SSH-OpenSSH": [ "SSH OpenSSH license", false, false ], "SSH-short": [ "SSH short notice", false, false ], "SSLeay-standalone": [ "SSLeay License - standalone", false, false ], "SSPL-1.0": [ "Server Side Public License, v 1", false, false ], "StandardML-NJ": [ "Standard ML of New Jersey License", false, true ], "SugarCRM-1.1.3": [ "SugarCRM Public License v1.1.3", false, false ], "SUL-1.0": [ "Sustainable Use License v1.0", false, false ], "Sun-PPP": [ "Sun PPP License", false, false ], "Sun-PPP-2000": [ "Sun PPP License (2000)", false, false ], "SunPro": [ "SunPro License", false, false ], "SWL": [ "Scheme Widget Library (SWL) Software License Agreement", false, false ], "swrule": [ "swrule License", false, false ], "Symlinks": [ "Symlinks License", false, false ], "TAPR-OHL-1.0": [ "TAPR Open Hardware License v1.0", false, false ], "TCL": [ "TCL/TK License", false, false ], "TCP-wrappers": [ "TCP Wrappers License", false, false ], "TekHVC": [ "TekHVC License", false, false ], "TermReadKey": [ "TermReadKey License", false, false ], "TGPPL-1.0": [ "Transitive Grace Period Public Licence 1.0", false, false ], "ThirdEye": [ "ThirdEye License", false, false ], "threeparttable": [ "threeparttable License", false, false ], "TMate": [ "TMate Open Source License", false, false ], "TORQUE-1.1": [ "TORQUE v2.5+ Software License v1.1", false, false ], "TOSL": [ "Trusster Open Source License", false, false ], "TPDL": [ "Time::ParseDate License", false, false ], "TPL-1.0": [ "THOR Public License 1.0", false, false ], "TrustedQSL": [ "TrustedQSL License", false, false ], "TTWL": [ "Text-Tabs+Wrap License", false, false ], "TTYP0": [ "TTYP0 License", false, false ], "TU-Berlin-1.0": [ "Technische Universitaet Berlin License 1.0", false, false ], "TU-Berlin-2.0": [ "Technische Universitaet Berlin License 2.0", false, false ], "Ubuntu-font-1.0": [ "Ubuntu Font Licence v1.0", false, false ], "UCAR": [ "UCAR License", false, false ], "UCL-1.0": [ "Upstream Compatibility License v1.0", true, false ], "ulem": [ "ulem License", false, false ], "UMich-Merit": [ "Michigan/Merit Networks License", false, false ], "Unicode-3.0": [ "Unicode License v3", true, false ], "Unicode-DFS-2015": [ "Unicode License Agreement - Data Files and Software (2015)", false, false ], "Unicode-DFS-2016": [ "Unicode License Agreement - Data Files and Software (2016)", true, false ], "Unicode-TOU": [ "Unicode Terms of Use", false, false ], "UnixCrypt": [ "UnixCrypt License", false, false ], "Unlicense": [ "The Unlicense", true, false ], "Unlicense-libtelnet": [ "Unlicense - libtelnet variant", false, false ], "Unlicense-libwhirlpool": [ "Unlicense - libwhirlpool variant", false, false ], "UnRAR": [ "UnRAR License", false, false ], "UPL-1.0": [ "Universal Permissive License v1.0", true, false ], "URT-RLE": [ "Utah Raster Toolkit Run Length Encoded License", false, false ], "Vim": [ "Vim License", false, false ], "Vixie-Cron": [ "Vixie Cron License", false, false ], "VOSTROM": [ "VOSTROM Public License for Open Source", false, false ], "VSL-1.0": [ "Vovida Software License v1.0", true, false ], "W3C": [ "W3C Software Notice and License (2002-12-31)", true, false ], "W3C-19980720": [ "W3C Software Notice and License (1998-07-20)", false, false ], "W3C-20150513": [ "W3C Software Notice and Document License (2015-05-13)", true, false ], "w3m": [ "w3m License", false, false ], "Watcom-1.0": [ "Sybase Open Watcom Public License 1.0", true, false ], "Widget-Workshop": [ "Widget Workshop License", false, false ], "WordNet": [ "WordNet License", true, false ], "Wsuipa": [ "Wsuipa License", false, false ], "WTFNMFPL": [ "Do What The F*ck You Want To But It's Not My Fault Public License", false, false ], "WTFPL": [ "Do What The F*ck You Want To Public License", false, false ], "wwl": [ "WWL License", false, false ], "wxWindows": [ "wxWindows Library License", true, true ], "X11": [ "X11 License", false, false ], "X11-distribute-modifications-variant": [ "X11 License Distribution Modification Variant", false, false ], "X11-no-permit-persons": [ "X11 no permit persons clause", false, false ], "X11-swapped": [ "X11 swapped final paragraphs", false, false ], "Xdebug-1.03": [ "Xdebug License v 1.03", false, false ], "Xerox": [ "Xerox License", false, false ], "Xfig": [ "Xfig License", false, false ], "XFree86-1.1": [ "XFree86 License 1.1", false, false ], "xinetd": [ "xinetd License", false, false ], "xkeyboard-config-Zinoviev": [ "xkeyboard-config Zinoviev License", false, false ], "xlock": [ "xlock License", false, false ], "Xnet": [ "X.Net License", true, false ], "xpp": [ "XPP License", false, false ], "XSkat": [ "XSkat License", false, false ], "xzoom": [ "xzoom License", false, false ], "YPL-1.0": [ "Yahoo! Public License v1.0", false, false ], "YPL-1.1": [ "Yahoo! Public License v1.1", false, false ], "Zed": [ "Zed License", false, false ], "Zeeff": [ "Zeeff License", false, false ], "Zend-2.0": [ "Zend License v2.0", false, false ], "Zimbra-1.3": [ "Zimbra Public License v1.3", false, false ], "Zimbra-1.4": [ "Zimbra Public License v1.4", false, false ], "Zlib": [ "zlib License", true, false ], "zlib-acknowledgement": [ "zlib/libpng License with Acknowledgement", false, false ], "ZPL-1.1": [ "Zope Public License 1.1", false, false ], "ZPL-2.0": [ "Zope Public License 2.0", true, false ], "ZPL-2.1": [ "Zope Public License 2.1", true, false ] }loadLicenses(); $this->loadExceptions(); } public function getLicenseByIdentifier($identifier) { $key = strtolower($identifier); if (!isset($this->licenses[$key])) { return null; } [$identifier, $name, $isOsiApproved, $isDeprecatedLicenseId] = $this->licenses[$key]; return [ $name, $isOsiApproved, 'https://spdx.org/licenses/' . $identifier . '.html#licenseText', $isDeprecatedLicenseId, ]; } public function getLicenses() { return $this->licenses; } public function getExceptionByIdentifier($identifier) { $key = strtolower($identifier); if (!isset($this->exceptions[$key])) { return null; } [$identifier, $name] = $this->exceptions[$key]; return [ $name, 'https://spdx.org/licenses/' . $identifier . '.html#licenseExceptionText', ]; } public function getIdentifierByName($name) { foreach ($this->licenses as $licenseData) { if ($licenseData[1] === $name) { return $licenseData[0]; } } foreach ($this->exceptions as $licenseData) { if ($licenseData[1] === $name) { return $licenseData[0]; } } return null; } public function isOsiApprovedByIdentifier($identifier) { return $this->licenses[strtolower($identifier)][2]; } public function isDeprecatedByIdentifier($identifier) { return $this->licenses[strtolower($identifier)][3]; } public function validate($license) { if (is_array($license)) { $count = count($license); if ($count !== count(array_filter($license, 'is_string'))) { throw new \InvalidArgumentException('Array of strings expected.'); } $license = $count > 1 ? '(' . implode(' OR ', $license) . ')' : (string) reset($license); } if (!is_string($license)) { throw new \InvalidArgumentException(sprintf( 'Array or String expected, %s given.', gettype($license) )); } return $this->isValidLicenseString($license); } public static function getResourcesDir() { return dirname(__DIR__) . '/res'; } private function loadLicenses() { if (null !== $this->licenses) { return; } $json = file_get_contents(self::getResourcesDir() . '/' . self::LICENSES_FILE); if (false === $json) { throw new \RuntimeException('Missing license file in ' . self::getResourcesDir() . '/' . self::LICENSES_FILE); } $this->licenses = []; foreach (json_decode($json, true) as $identifier => $license) { $this->licenses[strtolower($identifier)] = [$identifier, $license[0], $license[1], $license[2]]; } } private function loadExceptions() { if (null !== $this->exceptions) { return; } $json = file_get_contents(self::getResourcesDir() . '/' . self::EXCEPTIONS_FILE); if (false === $json) { throw new \RuntimeException('Missing exceptions file in ' . self::getResourcesDir() . '/' . self::EXCEPTIONS_FILE); } $this->exceptions = []; foreach (json_decode($json, true) as $identifier => $exception) { $this->exceptions[strtolower($identifier)] = [$identifier, $exception[0]]; } } private function getLicensesExpression() { if (null === $this->licensesExpression) { $licenses = array_map('preg_quote', array_keys($this->licenses)); rsort($licenses); $licenses = implode('|', $licenses); $this->licensesExpression = $licenses; } return $this->licensesExpression; } private function getExceptionsExpression() { if (null === $this->exceptionsExpression) { $exceptions = array_map('preg_quote', array_keys($this->exceptions)); rsort($exceptions); $exceptions = implode('|', $exceptions); $this->exceptionsExpression = $exceptions; } return $this->exceptionsExpression; } private function isValidLicenseString($license) { if (isset($this->licenses[strtolower($license)])) { return true; } $licenses = $this->getLicensesExpression(); $exceptions = $this->getExceptionsExpression(); $regex = <<[\pL\pN.-]{1,}) # license-id: taken from list (?{$licenses}) # license-exception-id: taken from list (?{$exceptions}) # license-ref: [DocumentRef-1*(idstring):]LicenseRef-1*(idstring) (?(?:DocumentRef-(?&idstring):)?LicenseRef-(?&idstring)) # simple-expresssion: license-id / license-id+ / license-ref (?(?&licenseid)\+? | (?&licenseid) | (?&licenseref)) # compound-expression: 1*( # simple-expression / # simple-expression WITH license-exception-id / # compound-expression AND compound-expression / # compound-expression OR compound-expression # ) / ( compound-expression ) ) (? (?&simple_expression) ( \s+ WITH \s+ (?&licenseexceptionid))? | \( \s* (?&compound_expression) \s* \) ) (? (?&compound_head) (?: \s+ (?:AND|OR) \s+ (?&compound_expression))? ) # license-expression: 1*1(simple-expression / compound-expression) (?(?&compound_expression) | (?&simple_expression)) ) # end of define ^(NONE | NOASSERTION | (?&license_expression))$ }xi REGEX; $match = preg_match($regex, $license); if (0 === $match) { return false; } if (false === $match) { throw new \RuntimeException('Regex failed to compile/run.'); } return true; } } MIT License Copyright (c) 2017 Composer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. getDataAndReset(); return []; } public function useStandard(): array { $data = $this->getDataAndReset(); if ($data !== null) { return ['-n', '-c', $data['tmpIni']]; } return []; } public function usePersistent(): array { $data = $this->getDataAndReset(); if ($data !== null) { $this->updateEnv('PHPRC', $data['tmpIni']); $this->updateEnv('PHP_INI_SCAN_DIR', ''); } return []; } private function getDataAndReset(): ?array { $data = XdebugHandler::getRestartSettings(); if ($data !== null) { $this->updateEnv('PHPRC', $data['phprc']); $this->updateEnv('PHP_INI_SCAN_DIR', $data['scanDir']); } return $data; } private function updateEnv(string $name, $value): void { Process::setEnv($name, false !== $value ? $value : null); } } ()') !== false; } elseif ($module && !$dquotes && $quote) { $meta = false; } } if ($quote) { $arg = '"'.(Preg::replace('/(\\\\*)$/', '$1$1', $arg)).'"'; } if ($meta) { $arg = Preg::replace('/(["^&|<>()%])/', '^$1', $arg); } return $arg; } public static function escapeShellCommand(array $args): string { $command = ''; $module = array_shift($args); if ($module !== null) { $command = self::escape($module, true, true); foreach ($args as $arg) { $command .= ' '.self::escape($arg); } } return $command; } public static function setEnv(string $name, ?string $value = null): bool { $unset = null === $value; if (!putenv($unset ? $name : $name.'='.$value)) { return false; } if ($unset) { unset($_SERVER[$name]); } else { $_SERVER[$name] = $value; } if (false !== stripos((string) ini_get('variables_order'), 'E')) { if ($unset) { unset($_ENV[$name]); } else { $_ENV[$name] = $value; } } return true; } } time = is_numeric($start) ? round((microtime(true) - $start) * 1000) : 0; $this->envAllowXdebug = $envAllowXdebug; $this->debug = $debug && defined('STDERR'); $this->modeOff = false; } public function setLogger(LoggerInterface $logger): void { $this->logger = $logger; } public function report(string $op, ?string $data): void { if ($this->logger !== null || $this->debug) { $param = (string) $data; switch($op) { case self::CHECK: $this->reportCheck($param); break; case self::ERROR: $this->reportError($param); break; case self::INFO: $this->reportInfo($param); break; case self::NORESTART: $this->reportNoRestart(); break; case self::RESTART: $this->reportRestart(); break; case self::RESTARTED: $this->reportRestarted(); break; case self::RESTARTING: $this->reportRestarting($param); break; default: throw new \InvalidArgumentException('Unknown op handler: '.$op); } } } private function output(string $text, ?string $level = null): void { if ($this->logger !== null) { $this->logger->log($level !== null ? $level: LogLevel::DEBUG, $text); } if ($this->debug) { fwrite(STDERR, sprintf('xdebug-handler[%d] %s', getmypid(), $text.PHP_EOL)); } } private function reportCheck(string $loaded): void { list($version, $mode) = explode('|', $loaded); if ($version !== '') { $this->loaded = '('.$version.')'.($mode !== '' ? ' xdebug.mode='.$mode : ''); } $this->modeOff = $mode === 'off'; $this->output('Checking '.$this->envAllowXdebug); } private function reportError(string $error): void { $this->output(sprintf('No restart (%s)', $error), LogLevel::WARNING); } private function reportInfo(string $info): void { $this->output($info); } private function reportNoRestart(): void { $this->output($this->getLoadedMessage()); if ($this->loaded !== null) { $text = sprintf('No restart (%s)', $this->getEnvAllow()); if (!((bool) getenv($this->envAllowXdebug))) { $text .= ' Allowed by '.($this->modeOff ? 'xdebug.mode' : 'application'); } $this->output($text); } } private function reportRestart(): void { $this->output($this->getLoadedMessage()); Process::setEnv(self::ENV_RESTART, (string) microtime(true)); } private function reportRestarted(): void { $loaded = $this->getLoadedMessage(); $text = sprintf('Restarted (%d ms). %s', $this->time, $loaded); $level = $this->loaded !== null ? LogLevel::WARNING : null; $this->output($text, $level); } private function reportRestarting(string $command): void { $text = sprintf('Process restarting (%s)', $this->getEnvAllow()); $this->output($text); $text = 'Running: '.$command; $this->output($text); } private function getEnvAllow(): string { return $this->envAllowXdebug.'='.getenv($this->envAllowXdebug); } private function getLoadedMessage(): string { $loaded = $this->loaded !== null ? sprintf('loaded %s', $this->loaded) : 'not loaded'; return 'The Xdebug extension is '.$loaded; } } envAllowXdebug = self::$name.self::SUFFIX_ALLOW; $this->envOriginalInis = self::$name.self::SUFFIX_INIS; self::setXdebugDetails(); self::$inRestart = false; if ($this->cli = PHP_SAPI === 'cli') { $this->debug = (string) getenv(self::DEBUG); } $this->statusWriter = new Status($this->envAllowXdebug, (bool) $this->debug); } public function setLogger(LoggerInterface $logger): self { $this->statusWriter->setLogger($logger); return $this; } public function setMainScript(string $script): self { $this->script = $script; return $this; } public function setPersistent(): self { $this->persistent = true; return $this; } public function check(): void { $this->notify(Status::CHECK, self::$xdebugVersion.'|'.self::$xdebugMode); $envArgs = explode('|', (string) getenv($this->envAllowXdebug)); if (!((bool) $envArgs[0]) && $this->requiresRestart(self::$xdebugActive)) { $this->notify(Status::RESTART); $command = $this->prepareRestart(); if ($command !== null) { $this->restart($command); } return; } if (self::RESTART_ID === $envArgs[0] && count($envArgs) === 5) { $this->notify(Status::RESTARTED); Process::setEnv($this->envAllowXdebug); self::$inRestart = true; if (self::$xdebugVersion === null) { self::$skipped = $envArgs[1]; } $this->tryEnableSignals(); $this->setEnvRestartSettings($envArgs); return; } $this->notify(Status::NORESTART); $settings = self::getRestartSettings(); if ($settings !== null) { $this->syncSettings($settings); } } public static function getAllIniFiles(): array { if (self::$name !== null) { $env = getenv(self::$name.self::SUFFIX_INIS); if (false !== $env) { return explode(PATH_SEPARATOR, $env); } } $paths = [(string) php_ini_loaded_file()]; $scanned = php_ini_scanned_files(); if ($scanned !== false) { $paths = array_merge($paths, array_map('trim', explode(',', $scanned))); } return $paths; } public static function getRestartSettings(): ?array { $envArgs = explode('|', (string) getenv(self::RESTART_SETTINGS)); if (count($envArgs) !== 6 || (!self::$inRestart && php_ini_loaded_file() !== $envArgs[0])) { return null; } return [ 'tmpIni' => $envArgs[0], 'scannedInis' => (bool) $envArgs[1], 'scanDir' => '*' === $envArgs[2] ? false : $envArgs[2], 'phprc' => '*' === $envArgs[3] ? false : $envArgs[3], 'inis' => explode(PATH_SEPARATOR, $envArgs[4]), 'skipped' => $envArgs[5], ]; } public static function getSkippedVersion(): string { return (string) self::$skipped; } public static function isXdebugActive(): bool { self::setXdebugDetails(); return self::$xdebugActive; } protected function requiresRestart(bool $default): bool { return $default; } protected function restart(array $command): void { $this->doRestart($command); } private function doRestart(array $command): void { if (PHP_VERSION_ID >= 70400) { $cmd = $command; $displayCmd = sprintf('[%s]', implode(', ', $cmd)); } else { $cmd = Process::escapeShellCommand($command); if (defined('PHP_WINDOWS_VERSION_BUILD')) { $cmd = '"'.$cmd.'"'; } $displayCmd = $cmd; } $this->tryEnableSignals(); $this->notify(Status::RESTARTING, $displayCmd); $process = proc_open($cmd, [], $pipes); if (is_resource($process)) { $exitCode = proc_close($process); } if (!isset($exitCode)) { $this->notify(Status::ERROR, 'Unable to restart process'); $exitCode = -1; } else { $this->notify(Status::INFO, 'Restarted process exited '.$exitCode); } if ($this->debug === '2') { $this->notify(Status::INFO, 'Temp ini saved: '.$this->tmpIni); } else { @unlink((string) $this->tmpIni); } exit($exitCode); } private function prepareRestart(): ?array { if (!$this->cli) { $this->notify(Status::ERROR, 'Unsupported SAPI: '.PHP_SAPI); return null; } if (($argv = $this->checkServerArgv()) === null) { $this->notify(Status::ERROR, '$_SERVER[argv] is not as expected'); return null; } if (!$this->checkConfiguration($info)) { $this->notify(Status::ERROR, $info); return null; } $mainScript = (string) $this->script; if (!$this->checkMainScript($mainScript, $argv)) { $this->notify(Status::ERROR, 'Unable to access main script: '.$mainScript); return null; } $tmpDir = sys_get_temp_dir(); $iniError = 'Unable to create temp ini file at: '.$tmpDir; if (($tmpfile = @tempnam($tmpDir, '')) === false) { $this->notify(Status::ERROR, $iniError); return null; } $error = null; $iniFiles = self::getAllIniFiles(); $scannedInis = count($iniFiles) > 1; if (!$this->writeTmpIni($tmpfile, $iniFiles, $error)) { $this->notify(Status::ERROR, $error ?? $iniError); @unlink($tmpfile); return null; } if (!$this->setEnvironment($scannedInis, $iniFiles, $tmpfile)) { $this->notify(Status::ERROR, 'Unable to set environment variables'); @unlink($tmpfile); return null; } $this->tmpIni = $tmpfile; return $this->getCommand($argv, $tmpfile, $mainScript); } private function writeTmpIni(string $tmpFile, array $iniFiles, ?string &$error): bool { if ($iniFiles[0] === '') { array_shift($iniFiles); } $content = ''; $sectionRegex = '/^\s*\[(?:PATH|HOST)\s*=/mi'; $xdebugRegex = '/^\s*(zend_extension\s*=.*xdebug.*)$/mi'; foreach ($iniFiles as $file) { if (($data = @file_get_contents($file)) === false) { $error = 'Unable to read ini: '.$file; return false; } if (Preg::isMatchWithOffsets($sectionRegex, $data, $matches)) { $data = substr($data, 0, $matches[0][1]); } $content .= Preg::replace($xdebugRegex, ';$1', $data).PHP_EOL; } $config = parse_ini_string($content); $loaded = ini_get_all(null, false); if (false === $config || false === $loaded) { $error = 'Unable to parse ini data'; return false; } $content .= $this->mergeLoadedConfig($loaded, $config); $content .= 'opcache.enable_cli=0'.PHP_EOL; return (bool) @file_put_contents($tmpFile, $content); } private function getCommand(array $argv, string $tmpIni, string $mainScript): array { $php = [PHP_BINARY]; $args = array_slice($argv, 1); if (!$this->persistent) { array_push($php, '-n', '-c', $tmpIni); } return array_merge($php, [$mainScript], $args); } private function setEnvironment(bool $scannedInis, array $iniFiles, string $tmpIni): bool { $scanDir = getenv('PHP_INI_SCAN_DIR'); $phprc = getenv('PHPRC'); if (!putenv($this->envOriginalInis.'='.implode(PATH_SEPARATOR, $iniFiles))) { return false; } if ($this->persistent) { if (!putenv('PHP_INI_SCAN_DIR=') || !putenv('PHPRC='.$tmpIni)) { return false; } } $envArgs = [ self::RESTART_ID, self::$xdebugVersion, (int) $scannedInis, false === $scanDir ? '*' : $scanDir, false === $phprc ? '*' : $phprc, ]; return putenv($this->envAllowXdebug.'='.implode('|', $envArgs)); } private function notify(string $op, ?string $data = null): void { $this->statusWriter->report($op, $data); } private function mergeLoadedConfig(array $loadedConfig, array $iniConfig): string { $content = ''; foreach ($loadedConfig as $name => $value) { if (!is_string($value) || strpos($name, 'xdebug') === 0 || $name === 'apc.mmap_file_mask') { continue; } if (!isset($iniConfig[$name]) || $iniConfig[$name] !== $value) { $content .= $name.'="'.addcslashes($value, '\\"').'"'.PHP_EOL; } } return $content; } private function checkMainScript(string &$mainScript, array $argv): bool { if ($mainScript !== '') { return file_exists($mainScript) || '--' === $mainScript; } if (file_exists($mainScript = $argv[0])) { return true; } $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); $main = end($trace); if ($main !== false && isset($main['file'])) { return file_exists($mainScript = $main['file']); } return false; } private function setEnvRestartSettings(array $envArgs): void { $settings = [ php_ini_loaded_file(), $envArgs[2], $envArgs[3], $envArgs[4], getenv($this->envOriginalInis), self::$skipped, ]; Process::setEnv(self::RESTART_SETTINGS, implode('|', $settings)); } private function syncSettings(array $settings): void { if (false === getenv($this->envOriginalInis)) { Process::setEnv($this->envOriginalInis, implode(PATH_SEPARATOR, $settings['inis'])); } self::$skipped = $settings['skipped']; $this->notify(Status::INFO, 'Process called with existing restart settings'); } private function checkConfiguration(?string &$info): bool { if (!function_exists('proc_open')) { $info = 'proc_open function is disabled'; return false; } if (!file_exists(PHP_BINARY)) { $info = 'PHP_BINARY is not available'; return false; } if (extension_loaded('uopz') && !((bool) ini_get('uopz.disable'))) { if (function_exists('uopz_allow_exit')) { @uopz_allow_exit(true); } else { $info = 'uopz extension is not compatible'; return false; } } if (defined('PHP_WINDOWS_VERSION_BUILD') && PHP_VERSION_ID < 70400) { $workingDir = getcwd(); if ($workingDir === false) { $info = 'unable to determine working directory'; return false; } if (0 === strpos($workingDir, '\\\\')) { $info = 'cmd.exe does not support UNC paths: '.$workingDir; return false; } } return true; } private function tryEnableSignals(): void { if (function_exists('pcntl_async_signals') && function_exists('pcntl_signal')) { pcntl_async_signals(true); $message = 'Async signals enabled'; if (!self::$inRestart) { pcntl_signal(SIGINT, SIG_IGN); } elseif (is_int(pcntl_signal_get_handler(SIGINT))) { pcntl_signal(SIGINT, SIG_DFL); } } if (!self::$inRestart && function_exists('sapi_windows_set_ctrl_handler')) { sapi_windows_set_ctrl_handler(function ($evt) {}); } } private function checkServerArgv(): ?array { $result = []; if (isset($_SERVER['argv']) && is_array($_SERVER['argv'])) { foreach ($_SERVER['argv'] as $value) { if (!is_string($value)) { return null; } $result[] = $value; } } return count($result) > 0 ? $result : null; } private static function setXdebugDetails(): void { if (self::$xdebugActive !== null) { return; } self::$xdebugActive = false; if (!extension_loaded('xdebug')) { return; } $version = phpversion('xdebug'); self::$xdebugVersion = $version !== false ? $version : 'unknown'; if (version_compare(self::$xdebugVersion, '3.1', '>=')) { $modes = xdebug_info('mode'); self::$xdebugMode = count($modes) === 0 ? 'off' : implode(',', $modes); self::$xdebugActive = self::$xdebugMode !== 'off'; return; } $iniMode = ini_get('xdebug.mode'); if ($iniMode === false) { self::$xdebugActive = true; return; } $envMode = (string) getenv('XDEBUG_MODE'); if ($envMode !== '') { self::$xdebugMode = $envMode; } else { self::$xdebugMode = $iniMode !== '' ? $iniMode : 'off'; } if (Preg::isMatch('/^,+$/', str_replace(' ', '', self::$xdebugMode))) { self::$xdebugMode = 'off'; } self::$xdebugActive = self::$xdebugMode !== 'off'; } } MIT License Copyright (c) 2016 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. getValue(); static $messages = [ self::ADDITIONAL_ITEMS => 'The item %s[%s] is not defined and the definition does not allow additional items', self::ADDITIONAL_PROPERTIES => 'The property %s is not defined and the definition does not allow additional properties', self::ALL_OF => 'Failed to match all schemas', self::ANY_OF => 'Failed to match at least one schema', self::DEPENDENCIES => '%s depends on %s, which is missing', self::DISALLOW => 'Disallowed value was matched', self::DIVISIBLE_BY => 'Is not divisible by %d', self::ENUM => 'Does not have a value in the enumeration %s', self::CONSTANT => 'Does not have a value equal to %s', self::CONTAINS => 'Does not have a value valid to contains schema', self::EXCLUSIVE_MINIMUM => 'Must have a minimum value greater than %d', self::EXCLUSIVE_MAXIMUM => 'Must have a maximum value less than %d', self::FALSE => 'Boolean schema false', self::FORMAT_COLOR => 'Invalid color', self::FORMAT_DATE => 'Invalid date %s, expected format YYYY-MM-DD', self::FORMAT_DATE_TIME => 'Invalid date-time %s, expected format YYYY-MM-DDThh:mm:ssZ or YYYY-MM-DDThh:mm:ss+hh:mm', self::FORMAT_DATE_UTC => 'Invalid time %s, expected integer of milliseconds since Epoch', self::FORMAT_EMAIL => 'Invalid email', self::FORMAT_HOSTNAME => 'Invalid hostname', self::FORMAT_IP => 'Invalid IP address', self::FORMAT_JSON_POINTER => 'Invalid JSON pointer', self::FORMAT_PHONE => 'Invalid phone number', self::FORMAT_REGEX=> 'Invalid regex format %s', self::FORMAT_STYLE => 'Invalid style', self::FORMAT_TIME => 'Invalid time %s, expected format hh:mm:ss', self::FORMAT_URI_TEMPLATE => 'Invalid URI template format', self::FORMAT_URL => 'Invalid URL format', self::FORMAT_URL_REF => 'Invalid URL reference format', self::LENGTH_MAX => 'Must be at most %d characters long', self::INVALID_SCHEMA => 'Schema is not valid', self::LENGTH_MIN => 'Must be at least %d characters long', self::MAX_ITEMS => 'There must be a maximum of %d items in the array, %d found', self::MAXIMUM => 'Must have a maximum value less than or equal to %d', self::MIN_ITEMS => 'There must be a minimum of %d items in the array, %d found', self::MINIMUM => 'Must have a minimum value greater than or equal to %d', self::MISSING_MAXIMUM => 'Use of exclusiveMaximum requires presence of maximum', self::MISSING_MINIMUM => 'Use of exclusiveMinimum requires presence of minimum', self::MULTIPLE_OF => 'Must be a multiple of %s', self::NOT => 'Matched a schema which it should not', self::ONE_OF => 'Failed to match exactly one schema', self::REQUIRED => 'The property %s is required', self::REQUIRES => 'The presence of the property %s requires that %s also be present', self::PATTERN => 'Does not match the regex pattern %s', self::PREGEX_INVALID => 'The pattern %s is invalid', self::PROPERTIES_MIN => 'Must contain a minimum of %d properties', self::PROPERTIES_MAX => 'Must contain no more than %d properties', self::PROPERTY_NAMES => 'Property name %s is invalid', self::TYPE => '%s value found, but %s is required', self::UNIQUE_ITEMS => 'There are no duplicates allowed in the array', self::CONTENT_MEDIA_TYPE => 'Value is not valid with content media type', self::CONTENT_ENCODING => 'Value is not valid with content encoding', ]; if (!isset($messages[$name])) { throw new InvalidArgumentException('Missing error message for ' . $name); } return $messages[$name]; } } factory = $factory ?: new Factory(); } public function addError(ConstraintError $constraint, ?JsonPointer $path = null, array $more = []): void { $message = $constraint->getMessage(); $name = $constraint->getValue(); $error = [ 'property' => $this->convertJsonPointerIntoPropertyPath($path ?: new JsonPointer('')), 'pointer' => ltrim((string) ($path ?: new JsonPointer('')), '#'), 'message' => ucfirst(vsprintf($message, array_map(static function ($val) { if (is_scalar($val)) { return is_bool($val) ? var_export($val, true) : $val; } return json_encode($val); }, array_values($more)))), 'constraint' => [ 'name' => $name, 'params' => $more ], 'context' => $this->factory->getErrorContext(), ]; if ($this->factory->getConfig(Constraint::CHECK_MODE_EXCEPTIONS)) { throw new ValidationException(sprintf('Error validating %s: %s', $error['pointer'], $error['message'])); } $this->errors[] = $error; $this->errorMask |= $error['context']; } public function addErrors(array $errors): void { if ($errors) { $this->errors = array_merge($this->errors, $errors); $errorMask = &$this->errorMask; array_walk($errors, static function ($error) use (&$errorMask) { if (isset($error['context'])) { $errorMask |= $error['context']; } }); } } public function getErrors(int $errorContext = Validator::ERROR_ALL): array { if ($errorContext === Validator::ERROR_ALL) { return $this->errors; } return array_filter($this->errors, static function ($error) use ($errorContext) { return (bool) ($errorContext & $error['context']); }); } public function numErrors(int $errorContext = Validator::ERROR_ALL): int { if ($errorContext === Validator::ERROR_ALL) { return count($this->errors); } return count($this->getErrors($errorContext)); } public function isValid(): bool { return !$this->getErrors(); } public function reset(): void { $this->errors = []; $this->errorMask = Validator::ERROR_NONE; } public function getErrorMask(): int { return $this->errorMask; } public static function arrayToObjectRecursive(array $array): object { $json = json_encode($array); if (json_last_error() !== JSON_ERROR_NONE) { $message = 'Unable to encode schema array as JSON'; if (function_exists('json_last_error_msg')) { $message .= ': ' . json_last_error_msg(); } throw new InvalidArgumentException($message); } return (object) json_decode($json, false); } public static function jsonPatternToPhpRegex(string $pattern): string { return '~' . str_replace('~', '\\~', $pattern) . '~u'; } protected function convertJsonPointerIntoPropertyPath(JsonPointer $pointer): string { $result = array_map( static function ($path) { return sprintf(is_numeric($path) ? '[%d]' : '.%s', $path); }, $pointer->getPropertyPaths() ); return trim(implode('', $result), '.'); } } minItems) && count($value) < $schema->minItems) { $this->addError(ConstraintError::MIN_ITEMS(), $path, ['minItems' => $schema->minItems, 'found' => count($value)]); } if (isset($schema->maxItems) && count($value) > $schema->maxItems) { $this->addError(ConstraintError::MAX_ITEMS(), $path, ['maxItems' => $schema->maxItems, 'found' => count($value)]); } if (isset($schema->uniqueItems) && $schema->uniqueItems) { $count = count($value); for ($x = 0; $x < $count - 1; $x++) { for ($y = $x + 1; $y < $count; $y++) { if (DeepComparer::isEqual($value[$x], $value[$y])) { $this->addError(ConstraintError::UNIQUE_ITEMS(), $path); break 2; } } } } $this->validateItems($value, $schema, $path, $i); } protected function validateItems(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (\is_null($schema) || !isset($schema->items)) { return; } if ($schema->items === true) { return; } if (is_object($schema->items)) { foreach ($value as $k => &$v) { $initErrors = $this->getErrors(); $this->checkUndefined($v, $schema->items, $path, $k); if (count($initErrors) < count($this->getErrors()) && (isset($schema->additionalItems) && $schema->additionalItems !== false)) { $secondErrors = $this->getErrors(); $this->checkUndefined($v, $schema->additionalItems, $path, $k); } if (isset($secondErrors) && count($secondErrors) < count($this->getErrors())) { $this->errors = $secondErrors; } elseif (isset($secondErrors) && count($secondErrors) === count($this->getErrors())) { $this->errors = $initErrors; } } unset($v); } else { foreach ($value as $k => &$v) { if (array_key_exists($k, $schema->items)) { $this->checkUndefined($v, $schema->items[$k], $path, $k); } else { if (property_exists($schema, 'additionalItems')) { if ($schema->additionalItems !== false) { $this->checkUndefined($v, $schema->additionalItems, $path, $k); } else { $this->addError( ConstraintError::ADDITIONAL_ITEMS(), $path, [ 'item' => $i, 'property' => $k, 'additionalItems' => $schema->additionalItems ] ); } } else { $this->checkUndefined($v, new \stdClass(), $path, $k); } } } unset($v); if (count($value) > 0) { for ($k = count($value); $k < count($schema->items); $k++) { $undefinedInstance = $this->factory->createInstanceFor('undefined'); $this->checkUndefined($undefinedInstance, $schema->items[$k], $path, $k); } } } } } required) || !$schema->required)) { return; } $const = $schema->const; $type = gettype($element); $constType = gettype($const); if ($this->factory->getConfig(self::CHECK_MODE_TYPE_CAST) && $type === 'array' && $constType === 'object') { if (DeepComparer::isEqual((object) $element, $const)) { return; } } if (DeepComparer::isEqual($element, $const)) { return; } $this->addError(ConstraintError::CONSTANT(), $path, ['const' => $schema->const]); } } withPropertyPaths( array_merge( $path->getPropertyPaths(), [$i] ) ); } protected function checkArray(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { $validator = $this->factory->createInstanceFor('collection'); $validator->check($value, $schema, $path, $i); $this->addErrors($validator->getErrors()); } protected function checkObject( &$value, $schema = null, ?JsonPointer $path = null, $properties = null, $additionalProperties = null, $patternProperties = null, array $appliedDefaults = [] ): void { $validator = $this->factory->createInstanceFor('object'); $validator->check($value, $schema, $path, $properties, $additionalProperties, $patternProperties, $appliedDefaults); $this->addErrors($validator->getErrors()); } protected function checkType(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { $validator = $this->factory->createInstanceFor('type'); $validator->check($value, $schema, $path, $i); $this->addErrors($validator->getErrors()); } protected function checkUndefined(&$value, $schema = null, ?JsonPointer $path = null, $i = null, bool $fromDefault = false): void { $validator = $this->factory->createInstanceFor('undefined'); $validator->check($value, $this->factory->getSchemaStorage()->resolveRefSchema($schema), $path, $i, $fromDefault); $this->addErrors($validator->getErrors()); } protected function checkString($value, $schema = null, ?JsonPointer $path = null, $i = null): void { $validator = $this->factory->createInstanceFor('string'); $validator->check($value, $schema, $path, $i); $this->addErrors($validator->getErrors()); } protected function checkNumber($value, $schema = null, ?JsonPointer $path = null, $i = null): void { $validator = $this->factory->createInstanceFor('number'); $validator->check($value, $schema, $path, $i); $this->addErrors($validator->getErrors()); } protected function checkEnum($value, $schema = null, ?JsonPointer $path = null, $i = null): void { $validator = $this->factory->createInstanceFor('enum'); $validator->check($value, $schema, $path, $i); $this->addErrors($validator->getErrors()); } protected function checkConst($value, $schema = null, ?JsonPointer $path = null, $i = null): void { $validator = $this->factory->createInstanceFor('const'); $validator->check($value, $schema, $path, $i); $this->addErrors($validator->getErrors()); } protected function checkFormat($value, $schema = null, ?JsonPointer $path = null, $i = null): void { $validator = $this->factory->createInstanceFor('format'); $validator->check($value, $schema, $path, $i); $this->addErrors($validator->getErrors()); } protected function getTypeCheck(): TypeCheck\TypeCheckInterface { return $this->factory->getTypeCheck(); } } factory = $factory ?: new Factory(); $this->initialiseErrorBag($this->factory); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'additionalItems')) { return; } if ($schema->additionalItems === true) { return; } if ($schema->additionalItems === false && !property_exists($schema, 'items')) { return; } if (!is_array($value)) { return; } if (!property_exists($schema, 'items')) { return; } if (property_exists($schema, 'items') && is_object($schema->items)) { return; } $additionalItems = array_diff_key($value, property_exists($schema, 'items') ? $schema->items : []); foreach ($additionalItems as $propertyName => $propertyValue) { $schemaConstraint = $this->factory->createInstanceFor('schema'); $schemaConstraint->check($propertyValue, $schema->additionalItems, $path, $i); if ($schemaConstraint->isValid()) { continue; } $this->addError(ConstraintError::ADDITIONAL_ITEMS(), $path, ['item' => $i, 'property' => $propertyName, 'additionalItems' => $schema->additionalItems]); } } } factory = $factory ?: new Factory(); $this->initialiseErrorBag($this->factory); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'additionalProperties')) { return; } if ($schema->additionalProperties === true) { return; } if (!is_object($value)) { return; } $additionalProperties = get_object_vars($value); if (isset($schema->properties)) { $additionalProperties = array_diff_key($additionalProperties, (array) $schema->properties); } if (isset($schema->patternProperties)) { $patterns = array_keys(get_object_vars($schema->patternProperties)); foreach ($additionalProperties as $key => $_) { foreach ($patterns as $pattern) { if (preg_match($this->createPregMatchPattern($pattern), (string) $key)) { unset($additionalProperties[$key]); break; } } } } if (is_object($schema->additionalProperties)) { foreach ($additionalProperties as $key => $additionalPropertiesValue) { $schemaConstraint = $this->factory->createInstanceFor('schema'); $schemaConstraint->check($additionalPropertiesValue, $schema->additionalProperties, $path, $i); if ($schemaConstraint->isValid()) { unset($additionalProperties[$key]); } } } foreach ($additionalProperties as $key => $additionalPropertiesValue) { $this->addError(ConstraintError::ADDITIONAL_PROPERTIES(), $path, ['found' => $additionalPropertiesValue]); } } private function createPregMatchPattern(string $pattern): string { $replacements = [ '\p{digit}' => '\p{Nd}', '\p{Letter}' => '\p{L}', ]; $pattern = str_replace( array_keys($replacements), array_values($replacements), $pattern ); return '/' . str_replace('/', '\/', $pattern) . '/u'; } } factory = $factory ?: new Factory(); $this->initialiseErrorBag($this->factory); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'allOf')) { return; } foreach ($schema->allOf as $allOfSchema) { $schemaConstraint = $this->factory->createInstanceFor('schema'); $schemaConstraint->check($value, $allOfSchema, $path, $i); if ($schemaConstraint->isValid()) { continue; } $this->addError(ConstraintError::ALL_OF(), $path); $this->addErrors($schemaConstraint->getErrors()); } } } factory = $factory ?: new Factory(); $this->initialiseErrorBag($this->factory); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'anyOf')) { return; } foreach ($schema->anyOf as $anyOfSchema) { $schemaConstraint = $this->factory->createInstanceFor('schema'); try { $schemaConstraint->check($value, $anyOfSchema, $path, $i); if ($schemaConstraint->isValid()) { $this->errorBag()->reset(); return; } $this->addErrors($schemaConstraint->getErrors()); } catch (ValidationException $e) { } } $this->addError(ConstraintError::ANY_OF(), $path); } } initialiseErrorBag($factory ?: new Factory()); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'const')) { return; } if (DeepComparer::isEqual($value, $schema->const)) { return; } $this->addError(ConstraintError::CONSTANT(), $path, ['const' => $schema->const]); } } factory = $factory ?: new Factory(); $this->initialiseErrorBag($this->factory); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'contains')) { return; } $properties = []; if (!is_array($value)) { return; } foreach ($value as $propertyName => $propertyValue) { $schemaConstraint = $this->factory->createInstanceFor('schema'); $schemaConstraint->check($propertyValue, $schema->contains, $path, $i); if ($schemaConstraint->isValid()) { return; } } $this->addError(ConstraintError::CONTAINS(), $path, ['contains' => $schema->contains]); } } factory = $factory ?: new Factory(); $this->initialiseErrorBag($this->factory); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'dependencies')) { return; } if (!is_object($value)) { return; } foreach ($schema->dependencies as $dependant => $dependencies) { if (!property_exists($value, $dependant)) { continue; } if ($dependencies === true) { continue; } if ($dependencies === false) { $this->addError(ConstraintError::FALSE(), $path, ['dependant' => $dependant]); continue; } if (is_array($dependencies)) { foreach ($dependencies as $dependency) { if (property_exists($value, $dependant) && !property_exists($value, $dependency)) { $this->addError(ConstraintError::DEPENDENCIES(), $path, ['dependant' => $dependant, 'dependency' => $dependency]); } } } if (is_object($dependencies)) { $schemaConstraint = $this->factory->createInstanceFor('schema'); $schemaConstraint->check($value, $dependencies, $path, $i); if (!$schemaConstraint->isValid()) { $this->addErrors($schemaConstraint->getErrors()); } } } } } getSchemaStorage() : new SchemaStorage(), $factory ? $factory->getUriRetriever() : new UriRetriever(), $factory ? $factory->getConfig() : Constraint::CHECK_MODE_NORMAL )); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (is_bool($schema)) { if ($schema === false) { $this->addError(ConstraintError::FALSE(), $path, []); } return; } $this->checkForKeyword('ref', $value, $schema, $path, $i); $this->checkForKeyword('required', $value, $schema, $path, $i); $this->checkForKeyword('contains', $value, $schema, $path, $i); $this->checkForKeyword('properties', $value, $schema, $path, $i); $this->checkForKeyword('propertyNames', $value, $schema, $path, $i); $this->checkForKeyword('patternProperties', $value, $schema, $path, $i); $this->checkForKeyword('type', $value, $schema, $path, $i); $this->checkForKeyword('not', $value, $schema, $path, $i); $this->checkForKeyword('dependencies', $value, $schema, $path, $i); $this->checkForKeyword('allOf', $value, $schema, $path, $i); $this->checkForKeyword('anyOf', $value, $schema, $path, $i); $this->checkForKeyword('oneOf', $value, $schema, $path, $i); $this->checkForKeyword('additionalProperties', $value, $schema, $path, $i); $this->checkForKeyword('items', $value, $schema, $path, $i); $this->checkForKeyword('additionalItems', $value, $schema, $path, $i); $this->checkForKeyword('uniqueItems', $value, $schema, $path, $i); $this->checkForKeyword('minItems', $value, $schema, $path, $i); $this->checkForKeyword('minProperties', $value, $schema, $path, $i); $this->checkForKeyword('maxProperties', $value, $schema, $path, $i); $this->checkForKeyword('minimum', $value, $schema, $path, $i); $this->checkForKeyword('maximum', $value, $schema, $path, $i); $this->checkForKeyword('minLength', $value, $schema, $path, $i); $this->checkForKeyword('exclusiveMinimum', $value, $schema, $path, $i); $this->checkForKeyword('maxItems', $value, $schema, $path, $i); $this->checkForKeyword('maxLength', $value, $schema, $path, $i); $this->checkForKeyword('exclusiveMaximum', $value, $schema, $path, $i); $this->checkForKeyword('enum', $value, $schema, $path, $i); $this->checkForKeyword('const', $value, $schema, $path, $i); $this->checkForKeyword('multipleOf', $value, $schema, $path, $i); $this->checkForKeyword('format', $value, $schema, $path, $i); $this->checkForKeyword('pattern', $value, $schema, $path, $i); } protected function checkForKeyword(string $keyword, $value, $schema = null, ?JsonPointer $path = null, $i = null): void { $validator = $this->factory->createInstanceFor($keyword); $validator->check($value, $schema, $path, $i); $this->addErrors($validator->getErrors()); } } initialiseErrorBag($factory ?: new Factory()); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'enum')) { return; } foreach ($schema->enum as $enumCase) { if (DeepComparer::isEqual($value, $enumCase)) { return; } if (is_numeric($value) && is_numeric($enumCase) && DeepComparer::isEqual((float) $value, (float) $enumCase)) { return; } } $this->addError(ConstraintError::ENUM(), $path, ['enum' => $schema->enum]); } } initialiseErrorBag($factory ?: new Factory()); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'exclusiveMaximum')) { return; } if (!is_numeric($value)) { return; } if ($value < $schema->exclusiveMaximum) { return; } $this->addError(ConstraintError::EXCLUSIVE_MAXIMUM(), $path, ['exclusiveMaximum' => $schema->exclusiveMaximum, 'found' => $value]); } } initialiseErrorBag($factory ?: new Factory()); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'exclusiveMinimum')) { return; } if (!is_numeric($value)) { return; } if ($value > $schema->exclusiveMinimum) { return; } $this->addError(ConstraintError::EXCLUSIVE_MINIMUM(), $path, ['exclusiveMinimum' => $schema->exclusiveMinimum, 'found' => $value]); } } Draft06Constraint::class, 'additionalProperties' => AdditionalPropertiesConstraint::class, 'additionalItems' => AdditionalItemsConstraint::class, 'dependencies' => DependenciesConstraint::class, 'type' => TypeConstraint::class, 'const' => ConstConstraint::class, 'enum' => EnumConstraint::class, 'uniqueItems' => UniqueItemsConstraint::class, 'minItems' => MinItemsConstraint::class, 'minProperties' => MinPropertiesConstraint::class, 'maxProperties' => MaxPropertiesConstraint::class, 'minimum' => MinimumConstraint::class, 'maximum' => MaximumConstraint::class, 'exclusiveMinimum' => ExclusiveMinimumConstraint::class, 'minLength' => MinLengthConstraint::class, 'maxLength' => MaxLengthConstraint::class, 'maxItems' => MaxItemsConstraint::class, 'exclusiveMaximum' => ExclusiveMaximumConstraint::class, 'multipleOf' => MultipleOfConstraint::class, 'required' => RequiredConstraint::class, 'format' => FormatConstraint::class, 'anyOf' => AnyOfConstraint::class, 'allOf' => AllOfConstraint::class, 'oneOf' => OneOfConstraint::class, 'not' => NotConstraint::class, 'contains' => ContainsConstraint::class, 'propertyNames' => PropertiesNamesConstraint::class, 'patternProperties' => PatternPropertiesConstraint::class, 'pattern' => PatternConstraint::class, 'properties' => PropertiesConstraint::class, 'items' => ItemsConstraint::class, 'ref' => RefConstraint::class, ]; } initialiseErrorBag($factory ?: new Factory()); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'format')) { return; } if (!is_string($value)) { return; } switch ($schema->format) { case 'date': if (!$this->validateDateTime($value, 'Y-m-d')) { $this->addError(ConstraintError::FORMAT_DATE(), $path, ['date' => $value, 'format' => $schema->format]); } break; case 'time': if (!$this->validateDateTime($value, 'H:i:s')) { $this->addError(ConstraintError::FORMAT_TIME(), $path, ['time' => $value, 'format' => $schema->format]); } break; case 'date-time': if (!$this->validateRfc3339DateTime($value)) { $this->addError(ConstraintError::FORMAT_DATE_TIME(), $path, ['dateTime' => $value, 'format' => $schema->format]); } break; case 'utc-millisec': if (!$this->validateDateTime($value, 'U')) { $this->addError(ConstraintError::FORMAT_DATE_UTC(), $path, ['value' => $value, 'format' => $schema->format]); } break; case 'regex': if (!$this->validateRegex($value)) { $this->addError(ConstraintError::FORMAT_REGEX(), $path, ['value' => $value, 'format' => $schema->format]); } break; case 'ip-address': case 'ipv4': if (filter_var($value, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV4) === null) { $this->addError(ConstraintError::FORMAT_IP(), $path, ['format' => $schema->format]); } break; case 'ipv6': if (filter_var($value, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV6) === null) { $this->addError(ConstraintError::FORMAT_IP(), $path, ['format' => $schema->format]); } break; case 'color': if (!$this->validateColor($value)) { $this->addError(ConstraintError::FORMAT_COLOR(), $path, ['format' => $schema->format]); } break; case 'style': if (!$this->validateStyle($value)) { $this->addError(ConstraintError::FORMAT_STYLE(), $path, ['format' => $schema->format]); } break; case 'phone': if (!$this->validatePhone($value)) { $this->addError(ConstraintError::FORMAT_PHONE(), $path, ['format' => $schema->format]); } break; case 'uri': if (!UriValidator::isValid($value)) { $this->addError(ConstraintError::FORMAT_URL(), $path, ['format' => $schema->format]); } break; case 'uriref': case 'uri-reference': if (!(UriValidator::isValid($value) || RelativeReferenceValidator::isValid($value))) { $this->addError(ConstraintError::FORMAT_URL(), $path, ['format' => $schema->format]); } break; case 'uri-template': if (!$this->validateUriTemplate($value)) { $this->addError(ConstraintError::FORMAT_URI_TEMPLATE(), $path, ['format' => $schema->format]); } break; case 'email': if (filter_var($value, FILTER_VALIDATE_EMAIL, FILTER_NULL_ON_FAILURE | FILTER_FLAG_EMAIL_UNICODE) === null) { $this->addError(ConstraintError::FORMAT_EMAIL(), $path, ['format' => $schema->format]); } break; case 'host-name': case 'hostname': if (!$this->validateHostname($value)) { $this->addError(ConstraintError::FORMAT_HOSTNAME(), $path, ['format' => $schema->format]); } break; case 'json-pointer': if (!$this->validateJsonPointer($value)) { $this->addError(ConstraintError::FORMAT_JSON_POINTER(), $path, ['format' => $schema->format]); } break; default: break; } } private function validateDateTime(string $datetime, string $format): bool { $dt = \DateTime::createFromFormat($format, $datetime); if (!$dt) { return false; } return $datetime === $dt->format($format); } private function validateRegex(string $regex): bool { return preg_match(self::jsonPatternToPhpRegex($regex), '') !== false; } private static function jsonPatternToPhpRegex(string $pattern): string { return '~' . str_replace('~', '\\~', $pattern) . '~u'; } private function validateColor(string $color): bool { if (in_array(strtolower($color), ['aqua', 'black', 'blue', 'fuchsia', 'gray', 'green', 'lime', 'maroon', 'navy', 'olive', 'orange', 'purple', 'red', 'silver', 'teal', 'white', 'yellow'])) { return true; } return preg_match('/^#([a-f0-9]{3}|[a-f0-9]{6})$/i', $color) !== false; } private function validateStyle(string $style): bool { $properties = explode(';', rtrim($style, ';')); $invalidEntries = preg_grep('/^\s*[-a-z]+\s*:\s*.+$/i', $properties, PREG_GREP_INVERT); return empty($invalidEntries); } private function validatePhone(string $phone): bool { return preg_match('/^\+?(\(\d{3}\)|\d{3}) \d{3} \d{4}$/', $phone) !== false; } private function validateHostname(string $host): bool { $hostnameRegex = '/^(?!-)(?!.*?[^A-Za-z0-9\-\.])(?:(?!-)[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?\.)*(?!-)[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?$/'; return preg_match($hostnameRegex, $host) === 1; } private function validateJsonPointer(string $value): bool { if ($value !== '' && $value[0] !== '/') { return false; } $tokens = explode('/', $value); array_shift($tokens); foreach ($tokens as $token) { if (preg_match('/~(?![01])/', $token)) { return false; } } return true; } private function validateRfc3339DateTime(string $value): bool { $dateTime = Rfc3339::createFromString($value); if (is_null($dateTime)) { return false; } return true; } private function validateUriTemplate(string $value): bool { return preg_match( '/^(?:[^\{\}]*|\{[a-zA-Z0-9_:%\/\.~\-\+\*]+\})*$/', $value ) === 1; } } factory = $factory ?: new Factory(); $this->initialiseErrorBag($this->factory); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'items')) { return; } if (!is_array($value)) { return; } foreach ($value as $propertyName => $propertyValue) { $itemSchema = $schema->items; if (is_array($itemSchema)) { if (!array_key_exists($propertyName, $itemSchema)) { continue; } $itemSchema = $itemSchema[$propertyName]; } $schemaConstraint = $this->factory->createInstanceFor('schema'); $schemaConstraint->check($propertyValue, $itemSchema, $path, $i); if ($schemaConstraint->isValid()) { continue; } $this->addErrors($schemaConstraint->getErrors()); } } } initialiseErrorBag($factory ?: new Factory()); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'maxItems')) { return; } if (!is_array($value)) { return; } $count = count($value); if ($count <= $schema->maxItems) { return; } $this->addError(ConstraintError::MAX_ITEMS(), $path, ['maxItems' => $schema->maxItems, 'found' => $count]); } } initialiseErrorBag($factory ?: new Factory()); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'maxLength')) { return; } if (!is_string($value)) { return; } $length = mb_strlen($value); if ($length <= $schema->maxLength) { return; } $this->addError(ConstraintError::LENGTH_MAX(), $path, ['maxLength' => $schema->maxLength, 'found' => $length]); } } initialiseErrorBag($factory ?: new Factory()); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'maxProperties')) { return; } if (!is_object($value)) { return; } $count = count(get_object_vars($value)); if ($count <= $schema->maxProperties) { return; } $this->addError(ConstraintError::PROPERTIES_MAX(), $path, ['maxProperties' => $schema->maxProperties, 'found' => $count]); } } initialiseErrorBag($factory ?: new Factory()); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'maximum')) { return; } if (!is_numeric($value)) { return; } if ($value <= $schema->maximum) { return; } $this->addError(ConstraintError::MAXIMUM(), $path, ['maximum' => $schema->maximum, 'found' => $value]); } } initialiseErrorBag($factory ?: new Factory()); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'minItems')) { return; } if (!is_array($value)) { return; } $count = count($value); if ($count >= $schema->minItems) { return; } $this->addError(ConstraintError::MIN_ITEMS(), $path, ['minItems' => $schema->minItems, 'found' => $count]); } } initialiseErrorBag($factory ?: new Factory()); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'minLength')) { return; } if (!is_string($value)) { return; } $length = mb_strlen($value); if ($length >= $schema->minLength) { return; } $this->addError(ConstraintError::LENGTH_MIN(), $path, ['minLength' => $schema->minLength, 'found' => $length]); } } initialiseErrorBag($factory ?: new Factory()); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'minProperties')) { return; } if (!is_object($value)) { return; } $count = count(get_object_vars($value)); if ($count >= $schema->minProperties) { return; } $this->addError(ConstraintError::PROPERTIES_MIN(), $path, ['minProperties' => $schema->minProperties, 'found' => $count]); } } initialiseErrorBag($factory ?: new Factory()); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'minimum')) { return; } if (!is_numeric($value)) { return; } if ($value >= $schema->minimum) { return; } $this->addError(ConstraintError::MINIMUM(), $path, ['minimum' => $schema->minimum, 'found' => $value]); } } initialiseErrorBag($factory ?: new Factory()); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'multipleOf')) { return; } if (!is_int($schema->multipleOf) && !is_float($schema->multipleOf) && $schema->multipleOf <= 0.0) { return; } if (!is_int($value) && !is_float($value)) { return; } if ($this->isMultipleOf($value, $schema->multipleOf)) { return; } $this->addError(ConstraintError::MULTIPLE_OF(), $path, ['multipleOf' => $schema->multipleOf, 'found' => $value]); } private function isMultipleOf($number1, $number2): bool { $modulus = ($number1 - round($number1 / $number2) * $number2); $precision = 0.0000000001; return -$precision < $modulus && $modulus < $precision; } } factory = $factory ?: new Factory(); $this->initialiseErrorBag($this->factory); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'not')) { return; } $schemaConstraint = $this->factory->createInstanceFor('schema'); $schemaConstraint->check($value, $schema->not, $path, $i); if (!$schemaConstraint->isValid()) { return; } $this->addError(ConstraintError::NOT(), $path); } } factory = $factory ?: new Factory(); $this->initialiseErrorBag($this->factory); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'oneOf')) { return; } $matchedSchema = 0; foreach ($schema->oneOf as $oneOfSchema) { $schemaConstraint = $this->factory->createInstanceFor('schema'); $schemaConstraint->check($value, $oneOfSchema, $path, $i); if ($schemaConstraint->isValid()) { $matchedSchema++; continue; } $this->addErrors($schemaConstraint->getErrors()); } if ($matchedSchema !== 1) { $this->addError(ConstraintError::ONE_OF(), $path); } else { $this->errorBag()->reset(); } } } factory = $factory ?: new Factory(); $this->initialiseErrorBag($this->factory); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'pattern')) { return; } if (!is_string($value)) { return; } $matchPattern = $this->createPregMatchPattern($schema->pattern); if (preg_match($matchPattern, $value) === 1) { return; } $this->addError(ConstraintError::PATTERN(), $path, ['found' => $value, 'pattern' => $schema->pattern]); } private function createPregMatchPattern(string $pattern): string { $replacements = [ '\D' => '[^0-9]', '\d' => '[0-9]', '\p{digit}' => '[0-9]', '\w' => '[A-Za-z0-9_]', '\W' => '[^A-Za-z0-9_]', '\s' => '[\s\x{200B}]', '\p{Letter}' => '\p{L}', ]; $pattern = str_replace( array_keys($replacements), array_values($replacements), $pattern ); return '/' . str_replace('/', '\/', $pattern) . '/u'; } } factory = $factory ?: new Factory(); $this->initialiseErrorBag($this->factory); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'patternProperties')) { return; } if (!is_object($value)) { return; } $properties = get_object_vars($value); foreach ($properties as $propertyName => $propertyValue) { foreach ($schema->patternProperties as $patternPropertyRegex => $patternPropertySchema) { $matchPattern = $this->createPregMatchPattern($patternPropertyRegex); if (preg_match($matchPattern, (string) $propertyName)) { $schemaConstraint = $this->factory->createInstanceFor('schema'); $schemaConstraint->check($propertyValue, $patternPropertySchema, $path, $i); if ($schemaConstraint->isValid()) { continue; } $this->addErrors($schemaConstraint->getErrors()); } } } } private function createPregMatchPattern(string $pattern): string { $replacements = [ '\d' => '[0-9]', '\p{digit}' => '[0-9]', '\p{Letter}' => '\p{L}', ]; $pattern = str_replace( array_keys($replacements), array_values($replacements), $pattern ); return '/' . str_replace('/', '\/', $pattern) . '/u'; } } factory = $factory ?: new Factory(); $this->initialiseErrorBag($this->factory); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'properties')) { return; } if (!is_object($value)) { return; } foreach ($schema->properties as $propertyName => $propertySchema) { $schemaConstraint = $this->factory->createInstanceFor('schema'); if (!property_exists($value, $propertyName)) { continue; } $schemaConstraint->check($value->{$propertyName}, $propertySchema, $path, $i); if ($schemaConstraint->isValid()) { continue; } $this->addErrors($schemaConstraint->getErrors()); } } } factory = $factory ?: new Factory(); $this->initialiseErrorBag($this->factory); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'propertyNames')) { return; } if (!is_object($value)) { return; } if ($schema->propertyNames === true) { return; } $propertyNames = get_object_vars($value); if ($schema->propertyNames === false) { foreach ($propertyNames as $propertyName => $_) { $this->addError(ConstraintError::PROPERTY_NAMES(), $path, ['propertyNames' => $schema->propertyNames, 'violating' => 'false', 'name' => $propertyName]); } return; } if (property_exists($schema->propertyNames, 'maxLength')) { foreach ($propertyNames as $propertyName => $_) { $length = mb_strlen($propertyName); if ($length > $schema->propertyNames->maxLength) { $this->addError(ConstraintError::PROPERTY_NAMES(), $path, ['propertyNames' => $schema->propertyNames, 'violating' => 'maxLength', 'length' => $length, 'name' => $propertyName]); } } } if (property_exists($schema->propertyNames, 'pattern')) { foreach ($propertyNames as $propertyName => $_) { if (!preg_match('/' . str_replace('/', '\/', $schema->propertyNames->pattern) . '/', $propertyName)) { $this->addError(ConstraintError::PROPERTY_NAMES(), $path, ['propertyNames' => $schema->propertyNames, 'violating' => 'pattern', 'name' => $propertyName]); } } } if (property_exists($schema->propertyNames, 'const')) { foreach ($propertyNames as $propertyName => $_) { if ($propertyName !== $schema->propertyNames->const) { $this->addError(ConstraintError::PROPERTY_NAMES(), $path, ['propertyNames' => $schema->propertyNames, 'violating' => 'const', 'name' => $propertyName]); } } } if (property_exists($schema->propertyNames, 'enum')) { $diff = array_diff(array_keys($propertyNames), $schema->propertyNames->enum); foreach ($diff as $propertyName) { $this->addError(ConstraintError::PROPERTY_NAMES(), $path, ['propertyNames' => $schema->propertyNames, 'violating' => 'enum', 'name' => $propertyName]); } } } } factory = $factory ?: new Factory(); $this->initialiseErrorBag($this->factory); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, '$ref')) { return; } try { $refSchema = $this->factory->getSchemaStorage()->resolveRefSchema($schema); } catch (\Exception $e) { return; } $schemaConstraint = $this->factory->createInstanceFor('schema'); $schemaConstraint->check($value, $refSchema, $path, $i); if ($schemaConstraint->isValid()) { return; } $this->addErrors($schemaConstraint->getErrors()); } } initialiseErrorBag($factory ?: new Factory()); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'required')) { return; } if (!is_object($value)) { return; } foreach ($schema->required as $required) { if (property_exists($value, $required)) { continue; } $this->addError(ConstraintError::REQUIRED(), $this->incrementPath($path, $required), ['property' => $required]); } } protected function incrementPath(?JsonPointer $path, $i): JsonPointer { $path = $path ?? new JsonPointer(''); if ($i === null || $i === '') { return $path; } return $path->withPropertyPaths(array_merge($path->getPropertyPaths(), [$i])); } } initialiseErrorBag($factory ?: new Factory()); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'type')) { return; } $schemaTypes = (array) $schema->type; $valueType = strtolower(gettype($value)); $valueIsNumber = $valueType === 'double' || $valueType === 'integer'; $isInteger = $valueIsNumber && fmod($value, 1.0) === 0.0; foreach ($schemaTypes as $type) { if ($valueType === $type) { return; } if ($type === 'number' && $valueIsNumber) { return; } if ($type === 'integer' && $isInteger) { return; } } $this->addError(ConstraintError::TYPE(), $path, ['found' => $valueType, 'expected' => implode(', ', $schemaTypes)]); } } initialiseErrorBag($factory ?: new Factory()); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'uniqueItems')) { return; } if (!is_array($value)) { return; } if ($schema->uniqueItems !== true) { return; } $count = count($value); for ($x = 0; $x < $count - 1; $x++) { for ($y = $x + 1; $y < $count; $y++) { if (DeepComparer::isEqual($value[$x], $value[$y])) { $this->addError(ConstraintError::UNIQUE_ITEMS(), $path); return; } } } } } factory = $factory ?: new Factory(); $this->initialiseErrorBag($this->factory); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'additionalItems')) { return; } if ($schema->additionalItems === true) { return; } if ($schema->additionalItems === false && !property_exists($schema, 'items')) { return; } if (!is_array($value)) { return; } if (!property_exists($schema, 'items')) { return; } if (property_exists($schema, 'items') && is_object($schema->items)) { return; } $additionalItems = array_diff_key($value, property_exists($schema, 'items') ? $schema->items : []); foreach ($additionalItems as $propertyName => $propertyValue) { $schemaConstraint = $this->factory->createInstanceFor('schema'); $schemaConstraint->check($propertyValue, $schema->additionalItems, $path, $i); if ($schemaConstraint->isValid()) { continue; } $this->addError(ConstraintError::ADDITIONAL_ITEMS(), $path, ['item' => $i, 'property' => $propertyName, 'additionalItems' => $schema->additionalItems]); } } } factory = $factory ?: new Factory(); $this->initialiseErrorBag($this->factory); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'additionalProperties')) { return; } if ($schema->additionalProperties === true) { return; } if (!is_object($value)) { return; } $additionalProperties = get_object_vars($value); if (isset($schema->properties)) { $additionalProperties = array_diff_key($additionalProperties, (array) $schema->properties); } if (isset($schema->patternProperties)) { $patterns = array_keys(get_object_vars($schema->patternProperties)); foreach ($additionalProperties as $key => $_) { foreach ($patterns as $pattern) { if (preg_match($this->createPregMatchPattern($pattern), (string) $key)) { unset($additionalProperties[$key]); break; } } } } if (is_object($schema->additionalProperties)) { foreach ($additionalProperties as $key => $additionalPropertiesValue) { $schemaConstraint = $this->factory->createInstanceFor('schema'); $schemaConstraint->check($additionalPropertiesValue, $schema->additionalProperties, $path, $i); if ($schemaConstraint->isValid()) { unset($additionalProperties[$key]); } } } foreach ($additionalProperties as $key => $additionalPropertiesValue) { $this->addError(ConstraintError::ADDITIONAL_PROPERTIES(), $path, ['found' => $additionalPropertiesValue]); } } private function createPregMatchPattern(string $pattern): string { $replacements = [ '\p{digit}' => '\p{Nd}', '\p{Letter}' => '\p{L}', ]; $pattern = str_replace( array_keys($replacements), array_values($replacements), $pattern ); return '/' . str_replace('/', '\/', $pattern) . '/u'; } } factory = $factory ?: new Factory(); $this->initialiseErrorBag($this->factory); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'allOf')) { return; } foreach ($schema->allOf as $allOfSchema) { $schemaConstraint = $this->factory->createInstanceFor('schema'); $schemaConstraint->check($value, $allOfSchema, $path, $i); if ($schemaConstraint->isValid()) { continue; } $this->addError(ConstraintError::ALL_OF(), $path); $this->addErrors($schemaConstraint->getErrors()); } } } factory = $factory ?: new Factory(); $this->initialiseErrorBag($this->factory); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'anyOf')) { return; } foreach ($schema->anyOf as $anyOfSchema) { $schemaConstraint = $this->factory->createInstanceFor('schema'); try { $schemaConstraint->check($value, $anyOfSchema, $path, $i); if ($schemaConstraint->isValid()) { $this->errorBag()->reset(); return; } $this->addErrors($schemaConstraint->getErrors()); } catch (ValidationException $e) { } } $this->addError(ConstraintError::ANY_OF(), $path); } } initialiseErrorBag($factory ?: new Factory()); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'const')) { return; } if (DeepComparer::isEqual($value, $schema->const)) { return; } $this->addError(ConstraintError::CONSTANT(), $path, ['const' => $schema->const]); } } factory = $factory ?: new Factory(); $this->initialiseErrorBag($this->factory); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'contains')) { return; } $properties = []; if (!is_array($value)) { return; } foreach ($value as $propertyName => $propertyValue) { $schemaConstraint = $this->factory->createInstanceFor('schema'); $schemaConstraint->check($propertyValue, $schema->contains, $path, $i); if ($schemaConstraint->isValid()) { return; } } $this->addError(ConstraintError::CONTAINS(), $path, ['contains' => $schema->contains]); } } initialiseErrorBag($factory ?: new Factory()); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'contentMediaType') && !property_exists($schema, 'contentEncoding')) { return; } if (!is_string($value)) { return; } $decodedValue = $value; if (property_exists($schema, 'contentEncoding')) { if ($schema->contentEncoding === 'base64') { if (!preg_match('/^[A-Za-z0-9+\/=]+$/', $decodedValue)) { $this->addError(ConstraintError::CONTENT_ENCODING(), $path, ['contentEncoding' => $schema->contentEncoding]); return; } $decodedValue = base64_decode($decodedValue); } } if (property_exists($schema, 'contentMediaType')) { if ($schema->contentMediaType === 'application/json') { json_decode($decodedValue, false); if (json_last_error() === JSON_ERROR_NONE) { return; } } $this->addError(ConstraintError::CONTENT_MEDIA_TYPE(), $path, ['contentMediaType' => $schema->contentMediaType]); } } } factory = $factory ?: new Factory(); $this->initialiseErrorBag($this->factory); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'dependencies')) { return; } if (!is_object($value)) { return; } foreach ($schema->dependencies as $dependant => $dependencies) { if (!property_exists($value, $dependant)) { continue; } if ($dependencies === true) { continue; } if ($dependencies === false) { $this->addError(ConstraintError::FALSE(), $path, ['dependant' => $dependant]); continue; } if (is_array($dependencies)) { foreach ($dependencies as $dependency) { if (property_exists($value, $dependant) && !property_exists($value, $dependency)) { $this->addError(ConstraintError::DEPENDENCIES(), $path, ['dependant' => $dependant, 'dependency' => $dependency]); } } } if (is_object($dependencies)) { $schemaConstraint = $this->factory->createInstanceFor('schema'); $schemaConstraint->check($value, $dependencies, $path, $i); if (!$schemaConstraint->isValid()) { $this->addErrors($schemaConstraint->getErrors()); } } } } } getSchemaStorage() : new SchemaStorage(), $factory ? $factory->getUriRetriever() : new UriRetriever(), $factory ? $factory->getConfig() : Constraint::CHECK_MODE_NORMAL )); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (is_bool($schema)) { if ($schema === false) { $this->addError(ConstraintError::FALSE(), $path, []); } return; } $this->checkForKeyword('ref', $value, $schema, $path, $i); $this->checkForKeyword('required', $value, $schema, $path, $i); $this->checkForKeyword('contains', $value, $schema, $path, $i); $this->checkForKeyword('properties', $value, $schema, $path, $i); $this->checkForKeyword('propertyNames', $value, $schema, $path, $i); $this->checkForKeyword('patternProperties', $value, $schema, $path, $i); $this->checkForKeyword('type', $value, $schema, $path, $i); $this->checkForKeyword('not', $value, $schema, $path, $i); $this->checkForKeyword('dependencies', $value, $schema, $path, $i); $this->checkForKeyword('allOf', $value, $schema, $path, $i); $this->checkForKeyword('anyOf', $value, $schema, $path, $i); $this->checkForKeyword('oneOf', $value, $schema, $path, $i); $this->checkForKeyword('ifThenElse', $value, $schema, $path, $i); $this->checkForKeyword('additionalProperties', $value, $schema, $path, $i); $this->checkForKeyword('items', $value, $schema, $path, $i); $this->checkForKeyword('additionalItems', $value, $schema, $path, $i); $this->checkForKeyword('uniqueItems', $value, $schema, $path, $i); $this->checkForKeyword('minItems', $value, $schema, $path, $i); $this->checkForKeyword('minProperties', $value, $schema, $path, $i); $this->checkForKeyword('maxProperties', $value, $schema, $path, $i); $this->checkForKeyword('minimum', $value, $schema, $path, $i); $this->checkForKeyword('maximum', $value, $schema, $path, $i); $this->checkForKeyword('minLength', $value, $schema, $path, $i); $this->checkForKeyword('exclusiveMinimum', $value, $schema, $path, $i); $this->checkForKeyword('maxItems', $value, $schema, $path, $i); $this->checkForKeyword('maxLength', $value, $schema, $path, $i); $this->checkForKeyword('exclusiveMaximum', $value, $schema, $path, $i); $this->checkForKeyword('enum', $value, $schema, $path, $i); $this->checkForKeyword('const', $value, $schema, $path, $i); $this->checkForKeyword('multipleOf', $value, $schema, $path, $i); $this->checkForKeyword('format', $value, $schema, $path, $i); $this->checkForKeyword('pattern', $value, $schema, $path, $i); $this->checkForKeyword('content', $value, $schema, $path, $i); } protected function checkForKeyword(string $keyword, $value, $schema = null, ?JsonPointer $path = null, $i = null): void { $validator = $this->factory->createInstanceFor($keyword); $validator->check($value, $schema, $path, $i); $this->addErrors($validator->getErrors()); } } initialiseErrorBag($factory ?: new Factory()); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'enum')) { return; } foreach ($schema->enum as $enumCase) { if (DeepComparer::isEqual($value, $enumCase)) { return; } if (is_numeric($value) && is_numeric($enumCase) && DeepComparer::isEqual((float) $value, (float) $enumCase)) { return; } } $this->addError(ConstraintError::ENUM(), $path, ['enum' => $schema->enum]); } } initialiseErrorBag($factory ?: new Factory()); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'exclusiveMaximum')) { return; } if (!is_numeric($value)) { return; } if ($value < $schema->exclusiveMaximum) { return; } $this->addError(ConstraintError::EXCLUSIVE_MAXIMUM(), $path, ['exclusiveMaximum' => $schema->exclusiveMaximum, 'found' => $value]); } } initialiseErrorBag($factory ?: new Factory()); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'exclusiveMinimum')) { return; } if (!is_numeric($value)) { return; } if ($value > $schema->exclusiveMinimum) { return; } $this->addError(ConstraintError::EXCLUSIVE_MINIMUM(), $path, ['exclusiveMinimum' => $schema->exclusiveMinimum, 'found' => $value]); } } Draft07Constraint::class, 'additionalProperties' => AdditionalPropertiesConstraint::class, 'additionalItems' => AdditionalItemsConstraint::class, 'dependencies' => DependenciesConstraint::class, 'type' => TypeConstraint::class, 'const' => ConstConstraint::class, 'enum' => EnumConstraint::class, 'uniqueItems' => UniqueItemsConstraint::class, 'minItems' => MinItemsConstraint::class, 'minProperties' => MinPropertiesConstraint::class, 'maxProperties' => MaxPropertiesConstraint::class, 'minimum' => MinimumConstraint::class, 'maximum' => MaximumConstraint::class, 'exclusiveMinimum' => ExclusiveMinimumConstraint::class, 'minLength' => MinLengthConstraint::class, 'maxLength' => MaxLengthConstraint::class, 'maxItems' => MaxItemsConstraint::class, 'exclusiveMaximum' => ExclusiveMaximumConstraint::class, 'multipleOf' => MultipleOfConstraint::class, 'required' => RequiredConstraint::class, 'format' => FormatConstraint::class, 'anyOf' => AnyOfConstraint::class, 'allOf' => AllOfConstraint::class, 'oneOf' => OneOfConstraint::class, 'not' => NotConstraint::class, 'ifThenElse' => IfThenElseConstraint::class, 'contains' => ContainsConstraint::class, 'propertyNames' => PropertiesNamesConstraint::class, 'patternProperties' => PatternPropertiesConstraint::class, 'pattern' => PatternConstraint::class, 'properties' => PropertiesConstraint::class, 'items' => ItemsConstraint::class, 'ref' => RefConstraint::class, 'content' => ContentConstraint::class, ]; } initialiseErrorBag($factory ?: new Factory()); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'format')) { return; } if (!is_string($value)) { return; } switch ($schema->format) { case 'date': if (!$this->validateDateTime($value, 'Y-m-d')) { $this->addError(ConstraintError::FORMAT_DATE(), $path, ['date' => $value, 'format' => $schema->format]); } break; case 'time': if (!$this->validateDateTime($value, 'H:i:sp') && !$this->validateDateTime($value, 'H:i:s.up')) { $this->addError(ConstraintError::FORMAT_TIME(), $path, ['time' => $value, 'format' => $schema->format]); } break; case 'date-time': if (!$this->validateRfc3339DateTime($value)) { $this->addError(ConstraintError::FORMAT_DATE_TIME(), $path, ['dateTime' => $value, 'format' => $schema->format]); } break; case 'utc-millisec': if (!$this->validateDateTime($value, 'U')) { $this->addError(ConstraintError::FORMAT_DATE_UTC(), $path, ['value' => $value, 'format' => $schema->format]); } break; case 'regex': if (!$this->validateRegex($value)) { $this->addError(ConstraintError::FORMAT_REGEX(), $path, ['value' => $value, 'format' => $schema->format]); } break; case 'ip-address': case 'ipv4': if (filter_var($value, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV4) === null) { $this->addError(ConstraintError::FORMAT_IP(), $path, ['format' => $schema->format]); } break; case 'ipv6': if (filter_var($value, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV6) === null) { $this->addError(ConstraintError::FORMAT_IP(), $path, ['format' => $schema->format]); } break; case 'color': if (!$this->validateColor($value)) { $this->addError(ConstraintError::FORMAT_COLOR(), $path, ['format' => $schema->format]); } break; case 'style': if (!$this->validateStyle($value)) { $this->addError(ConstraintError::FORMAT_STYLE(), $path, ['format' => $schema->format]); } break; case 'phone': if (!$this->validatePhone($value)) { $this->addError(ConstraintError::FORMAT_PHONE(), $path, ['format' => $schema->format]); } break; case 'uri': if (!UriValidator::isValid($value)) { $this->addError(ConstraintError::FORMAT_URL(), $path, ['format' => $schema->format]); } break; case 'uriref': case 'uri-reference': if (!(UriValidator::isValid($value) || RelativeReferenceValidator::isValid($value))) { $this->addError(ConstraintError::FORMAT_URL(), $path, ['format' => $schema->format]); } break; case 'uri-template': if (!$this->validateUriTemplate($value)) { $this->addError(ConstraintError::FORMAT_URI_TEMPLATE(), $path, ['format' => $schema->format]); } break; case 'email': if (filter_var($value, FILTER_VALIDATE_EMAIL, FILTER_NULL_ON_FAILURE | FILTER_FLAG_EMAIL_UNICODE) === null) { $this->addError(ConstraintError::FORMAT_EMAIL(), $path, ['format' => $schema->format]); } break; case 'host-name': case 'hostname': if (!$this->validateHostname($value)) { $this->addError(ConstraintError::FORMAT_HOSTNAME(), $path, ['format' => $schema->format]); } break; case 'idn-hostname': if (!$this->validateInternationalizedHostname($value)) { $this->addError(ConstraintError::FORMAT_HOSTNAME(), $path, ['format' => $schema->format]); } break; case 'json-pointer': if (!$this->validateJsonPointer($value)) { $this->addError(ConstraintError::FORMAT_JSON_POINTER(), $path, ['format' => $schema->format]); } break; default: break; } } private function validateDateTime(string $datetime, string $format): bool { $datetime = strtoupper($datetime); $isLeap = substr($datetime, 6, 2) === '60'; $input = $datetime; if ($isLeap) { $input = sprintf('%s59%s', substr($datetime, 0, 6), substr($datetime, 8)); } $dt = \DateTimeImmutable::createFromFormat($format, $input); if (!$dt) { return false; } $timezoneOffset = $dt->getTimezone()->getOffset($dt); if ($timezoneOffset >= 86400 || $timezoneOffset <= -86400) { return false; } $expected = $dt->format($format); if ($format === 'H:i:s.up') { $expected = sprintf( '%s%s', rtrim($dt->format('H:i:s.u'), '0'), $dt->format('p') ); } if ($isLeap) { $utcDT = $dt->setTimezone(new DateTimeZone('UTC')); if ($utcDT->format('H:i:s') !== '23:59:59') { return false; } $expected = sprintf('%s60%s', substr($expected, 0, 6), substr($expected, 8)); } return $datetime === $expected; } private function validateRegex(string $regex): bool { return preg_match(self::jsonPatternToPhpRegex($regex), '') !== false; } private static function jsonPatternToPhpRegex(string $pattern): string { return '~' . str_replace('~', '\\~', $pattern) . '~u'; } private function validateColor(string $color): bool { if (in_array(strtolower($color), ['aqua', 'black', 'blue', 'fuchsia', 'gray', 'green', 'lime', 'maroon', 'navy', 'olive', 'orange', 'purple', 'red', 'silver', 'teal', 'white', 'yellow'])) { return true; } return preg_match('/^#([a-f0-9]{3}|[a-f0-9]{6})$/i', $color) !== false; } private function validateStyle(string $style): bool { $properties = explode(';', rtrim($style, ';')); $invalidEntries = preg_grep('/^\s*[-a-z]+\s*:\s*.+$/i', $properties, PREG_GREP_INVERT); return empty($invalidEntries); } private function validatePhone(string $phone): bool { return preg_match('/^\+?(\(\d{3}\)|\d{3}) \d{3} \d{4}$/', $phone) !== false; } private function validateHostname(string $host): bool { $hostnameRegex = '/^(?!-)(?!.*?[^A-Za-z0-9\-\.])(?:(?!-)[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?\.)*(?!-)[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?$/'; return preg_match($hostnameRegex, $host) === 1; } private function validateInternationalizedHostname(string $host): bool { if ($host === '') { return false; } $host = rtrim($host, '.'); $labels = explode('.', $host); $asciiLabels = []; if ($labels === false) { return false; } foreach ($labels as $label) { if ($label === '') { return false; } if ( preg_match('/\x{0375}/u', $label) && !preg_match('/\x{0375}[\x{0370}-\x{03FF}]/u', $label) ) { return false; } if (preg_match('/[\x{05F3}\x{05F4}]/u', $label) && !preg_match('/[\x{0590}-\x{05FF}][\x{05F3}\x{05F4}]/u', $label) ) { return false; } if (str_contains($label, "\u{30FB}") && !preg_match('/[\x{30A0}-\x{30FF}]/u', $label) ) { return false; } $hasArabicIndic = preg_match('/[\x{0660}-\x{0669}]/u', $label); $hasExtArabicIndic = preg_match('/[\x{06F0}-\x{06F9}]/u', $label); if ($hasArabicIndic && $hasExtArabicIndic) { return false; } if (preg_match('/[\x{0964}\x{0965}]/u', $label) && !preg_match('/[\x{0900}-\x{097F}]/u', $label) ) { return false; } if (preg_match('/[\x{200C}\x{200D}]/u', $label)) { return false; } $ascii = idn_to_ascii($label, IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46); if ($ascii === false) { return false; } if (strlen($ascii) > 63) { return false; } if (!preg_match('/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i', $ascii)) { return false; } $asciiLabels[] = $ascii; } $asciiHost = implode('.', $asciiLabels); return strlen($asciiHost) <= 253; } private function validateJsonPointer(string $value): bool { if ($value !== '' && $value[0] !== '/') { return false; } $tokens = explode('/', $value); array_shift($tokens); foreach ($tokens as $token) { if (preg_match('/~(?![01])/', $token)) { return false; } } return true; } private function validateRfc3339DateTime(string $value): bool { $dateTime = Rfc3339::createFromString($value); if (is_null($dateTime)) { return false; } return true; } private function validateUriTemplate(string $value): bool { return preg_match( '/^(?:[^\{\}]*|\{[a-zA-Z0-9_:%\/\.~\-\+\*]+\})*$/', $value ) === 1; } } factory = $factory ?: new Factory(); $this->initialiseErrorBag($this->factory); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'if')) { return; } $schemaConstraint = $this->factory->createInstanceFor('schema'); $ifSchema = $schema->if; if (!is_bool($ifSchema)) { $schemaConstraint->check($value, $ifSchema, $path, $i); $meetsIfConditions = $schemaConstraint->isValid(); $schemaConstraint->reset(); } else { $meetsIfConditions = $ifSchema; } if ($meetsIfConditions) { if (!property_exists($schema, 'then')) { return; } $schemaConstraint->check($value, $schema->then, $path, $i); if ($schemaConstraint->isValid()) { return; } $this->addErrors($schemaConstraint->getErrors()); return; } if (!property_exists($schema, 'else')) { return; } $schemaConstraint->check($value, $schema->else, $path, $i); if ($schemaConstraint->isValid()) { return; } $this->addErrors($schemaConstraint->getErrors()); } } factory = $factory ?: new Factory(); $this->initialiseErrorBag($this->factory); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'items')) { return; } if (!is_array($value)) { return; } foreach ($value as $propertyName => $propertyValue) { $itemSchema = $schema->items; if (is_array($itemSchema)) { if (!array_key_exists($propertyName, $itemSchema)) { continue; } $itemSchema = $itemSchema[$propertyName]; } $schemaConstraint = $this->factory->createInstanceFor('schema'); $schemaConstraint->check($propertyValue, $itemSchema, $path, $i); if ($schemaConstraint->isValid()) { continue; } $this->addErrors($schemaConstraint->getErrors()); } } } initialiseErrorBag($factory ?: new Factory()); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'maxItems')) { return; } if (!is_array($value)) { return; } $count = count($value); if ($count <= $schema->maxItems) { return; } $this->addError(ConstraintError::MAX_ITEMS(), $path, ['maxItems' => $schema->maxItems, 'found' => $count]); } } initialiseErrorBag($factory ?: new Factory()); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'maxLength')) { return; } if (!is_string($value)) { return; } $length = mb_strlen($value); if ($length <= $schema->maxLength) { return; } $this->addError(ConstraintError::LENGTH_MAX(), $path, ['maxLength' => $schema->maxLength, 'found' => $length]); } } initialiseErrorBag($factory ?: new Factory()); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'maxProperties')) { return; } if (!is_object($value)) { return; } $count = count(get_object_vars($value)); if ($count <= $schema->maxProperties) { return; } $this->addError(ConstraintError::PROPERTIES_MAX(), $path, ['maxProperties' => $schema->maxProperties, 'found' => $count]); } } initialiseErrorBag($factory ?: new Factory()); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'maximum')) { return; } if (!is_numeric($value)) { return; } if ($value <= $schema->maximum) { return; } $this->addError(ConstraintError::MAXIMUM(), $path, ['maximum' => $schema->maximum, 'found' => $value]); } } initialiseErrorBag($factory ?: new Factory()); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'minItems')) { return; } if (!is_array($value)) { return; } $count = count($value); if ($count >= $schema->minItems) { return; } $this->addError(ConstraintError::MIN_ITEMS(), $path, ['minItems' => $schema->minItems, 'found' => $count]); } } initialiseErrorBag($factory ?: new Factory()); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'minLength')) { return; } if (!is_string($value)) { return; } $length = mb_strlen($value); if ($length >= $schema->minLength) { return; } $this->addError(ConstraintError::LENGTH_MIN(), $path, ['minLength' => $schema->minLength, 'found' => $length]); } } initialiseErrorBag($factory ?: new Factory()); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'minProperties')) { return; } if (!is_object($value)) { return; } $count = count(get_object_vars($value)); if ($count >= $schema->minProperties) { return; } $this->addError(ConstraintError::PROPERTIES_MIN(), $path, ['minProperties' => $schema->minProperties, 'found' => $count]); } } initialiseErrorBag($factory ?: new Factory()); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'minimum')) { return; } if (!is_numeric($value)) { return; } if ($value >= $schema->minimum) { return; } $this->addError(ConstraintError::MINIMUM(), $path, ['minimum' => $schema->minimum, 'found' => $value]); } } initialiseErrorBag($factory ?: new Factory()); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'multipleOf')) { return; } if (!is_int($schema->multipleOf) && !is_float($schema->multipleOf) && $schema->multipleOf <= 0.0) { return; } if (!is_int($value) && !is_float($value)) { return; } if ($this->isMultipleOf($value, $schema->multipleOf)) { return; } $this->addError(ConstraintError::MULTIPLE_OF(), $path, ['multipleOf' => $schema->multipleOf, 'found' => $value]); } private function isMultipleOf($number1, $number2): bool { $modulus = ($number1 - round($number1 / $number2) * $number2); $precision = 0.0000000001; return -$precision < $modulus && $modulus < $precision; } } factory = $factory ?: new Factory(); $this->initialiseErrorBag($this->factory); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'not')) { return; } $schemaConstraint = $this->factory->createInstanceFor('schema'); $schemaConstraint->check($value, $schema->not, $path, $i); if (!$schemaConstraint->isValid()) { return; } $this->addError(ConstraintError::NOT(), $path); } } factory = $factory ?: new Factory(); $this->initialiseErrorBag($this->factory); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'oneOf')) { return; } $matchedSchema = 0; foreach ($schema->oneOf as $oneOfSchema) { $schemaConstraint = $this->factory->createInstanceFor('schema'); $schemaConstraint->check($value, $oneOfSchema, $path, $i); if ($schemaConstraint->isValid()) { $matchedSchema++; continue; } $this->addErrors($schemaConstraint->getErrors()); } if ($matchedSchema !== 1) { $this->addError(ConstraintError::ONE_OF(), $path); } else { $this->errorBag()->reset(); } } } factory = $factory ?: new Factory(); $this->initialiseErrorBag($this->factory); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'pattern')) { return; } if (!is_string($value)) { return; } $matchPattern = $this->createPregMatchPattern($schema->pattern); if (preg_match($matchPattern, $value) === 1) { return; } $this->addError(ConstraintError::PATTERN(), $path, ['found' => $value, 'pattern' => $schema->pattern]); } private function createPregMatchPattern(string $pattern): string { $replacements = [ '\D' => '[^0-9]', '\d' => '[0-9]', '\p{digit}' => '[0-9]', '\w' => '[A-Za-z0-9_]', '\W' => '[^A-Za-z0-9_]', '\s' => '[\s\x{200B}]', '\p{Letter}' => '\p{L}', ]; $pattern = str_replace( array_keys($replacements), array_values($replacements), $pattern ); return '/' . str_replace('/', '\/', $pattern) . '/u'; } } factory = $factory ?: new Factory(); $this->initialiseErrorBag($this->factory); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'patternProperties')) { return; } if (!is_object($value)) { return; } $properties = get_object_vars($value); foreach ($properties as $propertyName => $propertyValue) { foreach ($schema->patternProperties as $patternPropertyRegex => $patternPropertySchema) { $matchPattern = $this->createPregMatchPattern($patternPropertyRegex); if (preg_match($matchPattern, (string) $propertyName)) { $schemaConstraint = $this->factory->createInstanceFor('schema'); $schemaConstraint->check($propertyValue, $patternPropertySchema, $path, $i); if ($schemaConstraint->isValid()) { continue; } $this->addErrors($schemaConstraint->getErrors()); } } } } private function createPregMatchPattern(string $pattern): string { $replacements = [ '\d' => '[0-9]', '\p{digit}' => '[0-9]', '\p{Letter}' => '\p{L}', ]; $pattern = str_replace( array_keys($replacements), array_values($replacements), $pattern ); return '/' . str_replace('/', '\/', $pattern) . '/u'; } } factory = $factory ?: new Factory(); $this->initialiseErrorBag($this->factory); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'properties')) { return; } if (!is_object($value)) { return; } foreach ($schema->properties as $propertyName => $propertySchema) { $schemaConstraint = $this->factory->createInstanceFor('schema'); if (!property_exists($value, $propertyName)) { continue; } $schemaConstraint->check($value->{$propertyName}, $propertySchema, $path, $i); if ($schemaConstraint->isValid()) { continue; } $this->addErrors($schemaConstraint->getErrors()); } } } factory = $factory ?: new Factory(); $this->initialiseErrorBag($this->factory); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'propertyNames')) { return; } if (!is_object($value)) { return; } if ($schema->propertyNames === true) { return; } $propertyNames = get_object_vars($value); if ($schema->propertyNames === false) { foreach ($propertyNames as $propertyName => $_) { $this->addError(ConstraintError::PROPERTY_NAMES(), $path, ['propertyNames' => $schema->propertyNames, 'violating' => 'false', 'name' => $propertyName]); } return; } if (property_exists($schema->propertyNames, 'maxLength')) { foreach ($propertyNames as $propertyName => $_) { $length = mb_strlen($propertyName); if ($length > $schema->propertyNames->maxLength) { $this->addError(ConstraintError::PROPERTY_NAMES(), $path, ['propertyNames' => $schema->propertyNames, 'violating' => 'maxLength', 'length' => $length, 'name' => $propertyName]); } } } if (property_exists($schema->propertyNames, 'pattern')) { foreach ($propertyNames as $propertyName => $_) { if (!preg_match('/' . str_replace('/', '\/', $schema->propertyNames->pattern) . '/', $propertyName)) { $this->addError(ConstraintError::PROPERTY_NAMES(), $path, ['propertyNames' => $schema->propertyNames, 'violating' => 'pattern', 'name' => $propertyName]); } } } if (property_exists($schema->propertyNames, 'const')) { foreach ($propertyNames as $propertyName => $_) { if ($propertyName !== $schema->propertyNames->const) { $this->addError(ConstraintError::PROPERTY_NAMES(), $path, ['propertyNames' => $schema->propertyNames, 'violating' => 'const', 'name' => $propertyName]); } } } if (property_exists($schema->propertyNames, 'enum')) { $diff = array_diff(array_keys($propertyNames), $schema->propertyNames->enum); foreach ($diff as $propertyName) { $this->addError(ConstraintError::PROPERTY_NAMES(), $path, ['propertyNames' => $schema->propertyNames, 'violating' => 'enum', 'name' => $propertyName]); } } } } factory = $factory ?: new Factory(); $this->initialiseErrorBag($this->factory); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, '$ref')) { return; } try { $refSchema = $this->factory->getSchemaStorage()->resolveRefSchema($schema); } catch (\Exception $e) { return; } $schemaConstraint = $this->factory->createInstanceFor('schema'); $schemaConstraint->check($value, $refSchema, $path, $i); if ($schemaConstraint->isValid()) { return; } $this->addErrors($schemaConstraint->getErrors()); } } initialiseErrorBag($factory ?: new Factory()); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'required')) { return; } if (!is_object($value)) { return; } foreach ($schema->required as $required) { if (property_exists($value, $required)) { continue; } $this->addError(ConstraintError::REQUIRED(), $this->incrementPath($path, $required), ['property' => $required]); } } protected function incrementPath(?JsonPointer $path, $i): JsonPointer { $path = $path ?? new JsonPointer(''); if ($i === null || $i === '') { return $path; } return $path->withPropertyPaths(array_merge($path->getPropertyPaths(), [$i])); } } initialiseErrorBag($factory ?: new Factory()); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'type')) { return; } $schemaTypes = (array) $schema->type; $valueType = strtolower(gettype($value)); $valueIsNumber = $valueType === 'double' || $valueType === 'integer'; $isInteger = $valueIsNumber && fmod($value, 1.0) === 0.0; foreach ($schemaTypes as $type) { if ($valueType === $type) { return; } if ($type === 'number' && $valueIsNumber) { return; } if ($type === 'integer' && $isInteger) { return; } } $this->addError(ConstraintError::TYPE(), $path, ['found' => $valueType, 'expected' => implode(', ', $schemaTypes)]); } } initialiseErrorBag($factory ?: new Factory()); } public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void { if (!property_exists($schema, 'uniqueItems')) { return; } if (!is_array($value)) { return; } if ($schema->uniqueItems !== true) { return; } $count = count($value); for ($x = 0; $x < $count - 1; $x++) { for ($y = $x + 1; $y < $count; $y++) { if (DeepComparer::isEqual($value[$x], $value[$y])) { $this->addError(ConstraintError::UNIQUE_ITEMS(), $path); return; } } } } } required) || !$schema->required)) { return; } $type = gettype($element); foreach ($schema->enum as $enum) { $enumType = gettype($enum); if ($enumType === 'object' && $type === 'array' && $this->factory->getConfig(self::CHECK_MODE_TYPE_CAST) && DeepComparer::isEqual((object) $element, $enum) ) { return; } if (($type === $enumType) && DeepComparer::isEqual($element, $enum)) { return; } if (is_numeric($element) && is_numeric($enum) && DeepComparer::isEqual((float) $element, (float) $enum)) { return; } } $this->addError(ConstraintError::ENUM(), $path, ['enum' => $schema->enum]); } } 'JsonSchema\Constraints\CollectionConstraint', 'collection' => 'JsonSchema\Constraints\CollectionConstraint', 'object' => 'JsonSchema\Constraints\ObjectConstraint', 'type' => 'JsonSchema\Constraints\TypeConstraint', 'undefined' => 'JsonSchema\Constraints\UndefinedConstraint', 'string' => 'JsonSchema\Constraints\StringConstraint', 'number' => 'JsonSchema\Constraints\NumberConstraint', 'enum' => 'JsonSchema\Constraints\EnumConstraint', 'const' => 'JsonSchema\Constraints\ConstConstraint', 'format' => 'JsonSchema\Constraints\FormatConstraint', 'schema' => 'JsonSchema\Constraints\SchemaConstraint', 'validator' => 'JsonSchema\Validator', 'draft06' => Drafts\Draft06\Draft06Constraint::class, 'draft07' => Drafts\Draft07\Draft07Constraint::class, ]; private $instanceCache = []; public function __construct( ?SchemaStorageInterface $schemaStorage = null, ?UriRetrieverInterface $uriRetriever = null, int $checkMode = Constraint::CHECK_MODE_NORMAL ) { $this->setConfig($checkMode); $this->uriRetriever = $uriRetriever ?: new UriRetriever(); $this->schemaStorage = $schemaStorage ?: new SchemaStorage($this->uriRetriever); } public function setConfig(int $checkMode = Constraint::CHECK_MODE_NORMAL): void { $this->checkMode = $checkMode; } public function addConfig(int $options): void { $this->checkMode |= $options; } public function removeConfig(int $options): void { $this->checkMode &= ~$options; } public function getConfig(?int $options = null): int { if ($options === null) { return $this->checkMode; } return $this->checkMode & $options; } public function getUriRetriever(): UriRetrieverInterface { return $this->uriRetriever; } public function getSchemaStorage(): SchemaStorageInterface { return $this->schemaStorage; } public function getTypeCheck(): TypeCheck\TypeCheckInterface { if (!isset($this->typeCheck[$this->checkMode])) { $this->typeCheck[$this->checkMode] = ($this->checkMode & Constraint::CHECK_MODE_TYPE_CAST) ? new TypeCheck\LooseTypeCheck() : new TypeCheck\StrictTypeCheck(); } return $this->typeCheck[$this->checkMode]; } public function setConstraintClass(string $name, string $class): Factory { if (!class_exists($class)) { throw new InvalidArgumentException('Unknown constraint ' . $name); } if (!in_array('JsonSchema\Constraints\ConstraintInterface', class_implements($class))) { throw new InvalidArgumentException('Invalid class ' . $name); } $this->constraintMap[$name] = $class; return $this; } public function createInstanceFor($constraintName) { if (!isset($this->constraintMap[$constraintName])) { throw new InvalidArgumentException('Unknown constraint ' . $constraintName); } if (!isset($this->instanceCache[$constraintName])) { $this->instanceCache[$constraintName] = new $this->constraintMap[$constraintName]($this); } return clone $this->instanceCache[$constraintName]; } public function getErrorContext(): int { return $this->errorContext; } public function setErrorContext(int $errorContext): void { $this->errorContext = $errorContext; } public function getDefaultDialect(): string { return $this->defaultDialect; } public function setDefaultDialect(string $defaultDialect): void { $this->defaultDialect = $defaultDialect; } } format) || $this->factory->getConfig(self::CHECK_MODE_DISABLE_FORMAT)) { return; } switch ($schema->format) { case 'date': if (is_string($element) && !$date = $this->validateDateTime($element, 'Y-m-d')) { $this->addError(ConstraintError::FORMAT_DATE(), $path, [ 'date' => $element, 'format' => $schema->format ] ); } break; case 'time': if (is_string($element) && !$this->validateDateTime($element, 'H:i:s')) { $this->addError(ConstraintError::FORMAT_TIME(), $path, [ 'time' => json_encode($element), 'format' => $schema->format, ] ); } break; case 'date-time': if (is_string($element) && null === Rfc3339::createFromString($element)) { $this->addError(ConstraintError::FORMAT_DATE_TIME(), $path, [ 'dateTime' => json_encode($element), 'format' => $schema->format ] ); } break; case 'utc-millisec': if (!$this->validateDateTime($element, 'U')) { $this->addError(ConstraintError::FORMAT_DATE_UTC(), $path, [ 'value' => $element, 'format' => $schema->format]); } break; case 'regex': if (!$this->validateRegex($element)) { $this->addError(ConstraintError::FORMAT_REGEX(), $path, [ 'value' => $element, 'format' => $schema->format ] ); } break; case 'color': if (!$this->validateColor($element)) { $this->addError(ConstraintError::FORMAT_COLOR(), $path, ['format' => $schema->format]); } break; case 'style': if (!$this->validateStyle($element)) { $this->addError(ConstraintError::FORMAT_STYLE(), $path, ['format' => $schema->format]); } break; case 'phone': if (!$this->validatePhone($element)) { $this->addError(ConstraintError::FORMAT_PHONE(), $path, ['format' => $schema->format]); } break; case 'uri': if (is_string($element) && !UriValidator::isValid($element)) { $this->addError(ConstraintError::FORMAT_URL(), $path, ['format' => $schema->format]); } break; case 'uriref': case 'uri-reference': if (is_string($element) && !(UriValidator::isValid($element) || RelativeReferenceValidator::isValid($element))) { $this->addError(ConstraintError::FORMAT_URL(), $path, ['format' => $schema->format]); } break; case 'email': if (is_string($element) && null === filter_var($element, FILTER_VALIDATE_EMAIL, FILTER_NULL_ON_FAILURE | FILTER_FLAG_EMAIL_UNICODE)) { $this->addError(ConstraintError::FORMAT_EMAIL(), $path, ['format' => $schema->format]); } break; case 'ip-address': case 'ipv4': if (is_string($element) && null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV4)) { $this->addError(ConstraintError::FORMAT_IP(), $path, ['format' => $schema->format]); } break; case 'ipv6': if (is_string($element) && null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV6)) { $this->addError(ConstraintError::FORMAT_IP(), $path, ['format' => $schema->format]); } break; case 'host-name': case 'hostname': if (!$this->validateHostname($element)) { $this->addError(ConstraintError::FORMAT_HOSTNAME(), $path, ['format' => $schema->format]); } break; default: break; } } protected function validateDateTime($datetime, $format) { $dt = \DateTime::createFromFormat($format, (string) $datetime); if (!$dt) { return false; } if ($datetime === $dt->format($format)) { return true; } return false; } protected function validateRegex($regex) { if (!is_string($regex)) { return true; } return false !== @preg_match(self::jsonPatternToPhpRegex($regex), ''); } protected function validateColor($color) { if (!is_string($color)) { return true; } if (in_array(strtolower($color), ['aqua', 'black', 'blue', 'fuchsia', 'gray', 'green', 'lime', 'maroon', 'navy', 'olive', 'orange', 'purple', 'red', 'silver', 'teal', 'white', 'yellow'])) { return true; } return preg_match('/^#([a-f0-9]{3}|[a-f0-9]{6})$/i', $color); } protected function validateStyle($style) { $properties = explode(';', rtrim($style, ';')); $invalidEntries = preg_grep('/^\s*[-a-z]+\s*:\s*.+$/i', $properties, PREG_GREP_INVERT); return empty($invalidEntries); } protected function validatePhone($phone) { return preg_match('/^\+?(\(\d{3}\)|\d{3}) \d{3} \d{4}$/', $phone); } protected function validateHostname($host) { if (!is_string($host)) { return true; } $hostnameRegex = '/^(?!-)(?!.*?[^A-Za-z0-9\-\.])(?:(?!-)[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?\.)*(?!-)[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?$/'; return preg_match($hostnameRegex, $host); } } exclusiveMinimum)) { if (isset($schema->minimum)) { if ($schema->exclusiveMinimum && $element <= $schema->minimum) { $this->addError(ConstraintError::EXCLUSIVE_MINIMUM(), $path, ['minimum' => $schema->minimum]); } elseif ($element < $schema->minimum) { $this->addError(ConstraintError::MINIMUM(), $path, ['minimum' => $schema->minimum]); } } else { $this->addError(ConstraintError::MISSING_MINIMUM(), $path); } } elseif (isset($schema->minimum) && $element < $schema->minimum) { $this->addError(ConstraintError::MINIMUM(), $path, ['minimum' => $schema->minimum]); } if (isset($schema->exclusiveMaximum)) { if (isset($schema->maximum)) { if ($schema->exclusiveMaximum && $element >= $schema->maximum) { $this->addError(ConstraintError::EXCLUSIVE_MAXIMUM(), $path, ['maximum' => $schema->maximum]); } elseif ($element > $schema->maximum) { $this->addError(ConstraintError::MAXIMUM(), $path, ['maximum' => $schema->maximum]); } } else { $this->addError(ConstraintError::MISSING_MAXIMUM(), $path); } } elseif (isset($schema->maximum) && $element > $schema->maximum) { $this->addError(ConstraintError::MAXIMUM(), $path, ['maximum' => $schema->maximum]); } if (isset($schema->divisibleBy) && $this->fmod($element, $schema->divisibleBy) != 0) { $this->addError(ConstraintError::DIVISIBLE_BY(), $path, ['divisibleBy' => $schema->divisibleBy]); } if (isset($schema->multipleOf) && $this->fmod($element, $schema->multipleOf) != 0) { $this->addError(ConstraintError::MULTIPLE_OF(), $path, ['multipleOf' => $schema->multipleOf]); } $this->checkFormat($element, $schema, $path, $i); } private function fmod($number1, $number2) { $modulus = ($number1 - round($number1 / $number2) * $number2); $precision = 0.0000000001; if (-$precision < $modulus && $modulus < $precision) { return 0.0; } return $modulus; } } appliedDefaults = $appliedDefaults; $matches = []; if ($patternProperties) { $matches = $this->validatePatternProperties($element, $path, $patternProperties); } if ($properties) { $this->validateProperties($element, $properties, $path); } $this->validateElement($element, $matches, $schema, $path, $properties, $additionalProp); } public function validatePatternProperties($element, ?JsonPointer $path, $patternProperties) { $matches = []; foreach ($patternProperties as $pregex => $schema) { $fullRegex = self::jsonPatternToPhpRegex($pregex); if (@preg_match($fullRegex, '') === false) { $this->addError(ConstraintError::PREGEX_INVALID(), $path, ['pregex' => $pregex]); continue; } foreach ($element as $i => $value) { if (preg_match($fullRegex, (string) $i)) { $matches[] = $i; $this->checkUndefined($value, $schema ?: new \stdClass(), $path, $i, in_array($i, $this->appliedDefaults)); } } } return $matches; } public function validateElement($element, $matches, $schema = null, ?JsonPointer $path = null, $properties = null, $additionalProp = null) { $this->validateMinMaxConstraint($element, $schema, $path); foreach ($element as $i => $value) { $definition = $this->getProperty($properties, $i); if (!in_array($i, $matches) && $additionalProp === false && $this->inlineSchemaProperty !== $i && !$definition) { $this->addError(ConstraintError::ADDITIONAL_PROPERTIES(), $path, ['property' => $i]); } if (!in_array($i, $matches) && $additionalProp && !$definition) { if ($additionalProp === true) { $this->checkUndefined($value, null, $path, $i, in_array($i, $this->appliedDefaults)); } else { $this->checkUndefined($value, $additionalProp, $path, $i, in_array($i, $this->appliedDefaults)); } } $require = $this->getProperty($definition, 'requires'); if ($require && !$this->getProperty($element, $require)) { $this->addError(ConstraintError::REQUIRES(), $path, [ 'property' => $i, 'requiredProperty' => $require ]); } $property = $this->getProperty($element, $i, $this->factory->createInstanceFor('undefined')); if (is_object($property)) { $this->validateMinMaxConstraint(!($property instanceof UndefinedConstraint) ? $property : $element, $definition, $path); } } } public function validateProperties(&$element, $properties = null, ?JsonPointer $path = null) { $undefinedConstraint = $this->factory->createInstanceFor('undefined'); foreach ($properties as $i => $value) { $property = &$this->getProperty($element, $i, $undefinedConstraint); $definition = $this->getProperty($properties, $i); if (is_object($definition)) { $this->checkUndefined($property, $definition, $path, $i, in_array($i, $this->appliedDefaults)); } } } protected function &getProperty(&$element, $property, $fallback = null) { if (is_array($element) && (isset($element[$property]) || array_key_exists($property, $element)) ) { return $element[$property]; } elseif (is_object($element) && property_exists($element, (string) $property)) { return $element->$property; } return $fallback; } protected function validateMinMaxConstraint($element, $objectDefinition, ?JsonPointer $path = null) { if (!$this->getTypeCheck()::isObject($element)) { return; } if (isset($objectDefinition->minProperties) && is_int($objectDefinition->minProperties)) { if ($this->getTypeCheck()->propertyCount($element) < max(0, $objectDefinition->minProperties)) { $this->addError(ConstraintError::PROPERTIES_MIN(), $path, ['minProperties' => $objectDefinition->minProperties]); } } if (isset($objectDefinition->maxProperties) && is_int($objectDefinition->maxProperties)) { if ($this->getTypeCheck()->propertyCount($element) > max(0, $objectDefinition->maxProperties)) { $this->addError(ConstraintError::PROPERTIES_MAX(), $path, ['maxProperties' => $objectDefinition->maxProperties]); } } } } getTypeCheck()->propertyExists($element, $this->inlineSchemaProperty)) { $validationSchema = $this->getTypeCheck()->propertyGet($element, $this->inlineSchemaProperty); } else { throw new InvalidArgumentException('no schema found to verify against'); } if (is_array($validationSchema)) { $validationSchema = BaseConstraint::arrayToObjectRecursive($validationSchema); } if ($this->factory->getConfig(self::CHECK_MODE_VALIDATE_SCHEMA)) { if (!$this->getTypeCheck()->isObject($validationSchema)) { throw new RuntimeException('Cannot validate the schema of a non-object'); } if ($this->getTypeCheck()->propertyExists($validationSchema, '$schema')) { $schemaSpec = $this->getTypeCheck()->propertyGet($validationSchema, '$schema'); } else { $schemaSpec = self::DEFAULT_SCHEMA_SPEC; } $schemaStorage = $this->factory->getSchemaStorage(); if (!$this->getTypeCheck()->isObject($schemaSpec)) { $schemaSpec = $schemaStorage->getSchema($schemaSpec); } $initialErrorCount = $this->numErrors(); $initialConfig = $this->factory->getConfig(); $initialContext = $this->factory->getErrorContext(); $this->factory->removeConfig(self::CHECK_MODE_VALIDATE_SCHEMA | self::CHECK_MODE_APPLY_DEFAULTS); $this->factory->addConfig(self::CHECK_MODE_TYPE_CAST); $this->factory->setErrorContext(Validator::ERROR_SCHEMA_VALIDATION); try { $this->check($validationSchema, $schemaSpec); } catch (\Exception $e) { if ($this->factory->getConfig(self::CHECK_MODE_EXCEPTIONS)) { throw new InvalidSchemaException('Schema did not pass validation', 0, $e); } } if ($this->numErrors() > $initialErrorCount) { $this->addError(ConstraintError::INVALID_SCHEMA(), $path); } $this->factory->setConfig($initialConfig); $this->factory->setErrorContext($initialContext); } $this->checkUndefined($element, $validationSchema, $path, $i); } } maxLength) && $this->strlen($element) > $schema->maxLength) { $this->addError(ConstraintError::LENGTH_MAX(), $path, [ 'maxLength' => $schema->maxLength, ]); } if (isset($schema->minLength) && $this->strlen($element) < $schema->minLength) { $this->addError(ConstraintError::LENGTH_MIN(), $path, [ 'minLength' => $schema->minLength, ]); } if (isset($schema->pattern) && !preg_match(self::jsonPatternToPhpRegex($schema->pattern), $element)) { $this->addError(ConstraintError::PATTERN(), $path, [ 'pattern' => $schema->pattern, ]); } $this->checkFormat($element, $schema, $path, $i); } private function strlen($string) { if (extension_loaded('mbstring')) { return mb_strlen($string, mb_detect_encoding($string)); } return strlen($string); } } {$property}; } return $value[$property]; } public static function propertySet(&$value, $property, $data) { if (is_object($value)) { $value->{$property} = $data; } else { $value[$property] = $data; } } public static function propertyExists($value, $property) { if (is_object($value)) { return property_exists($value, $property); } return is_array($value) && array_key_exists($property, $value); } public static function propertyCount($value) { if (is_object($value)) { return count(get_object_vars($value)); } return count($value); } private static function isAssociativeArray($arr) { return array_keys($arr) !== range(0, count($arr) - 1); } } {$property}; } public static function propertySet(&$value, $property, $data) { $value->{$property} = $data; } public static function propertyExists($value, $property) { return property_exists($value, $property); } public static function propertyCount($value) { if (!is_object($value)) { return 0; } return count(get_object_vars($value)); } } 'an integer', 'number' => 'a number', 'boolean' => 'a boolean', 'object' => 'an object', 'array' => 'an array', 'string' => 'a string', 'null' => 'a null', 'any' => null, 0 => null, ]; public function check(&$value = null, $schema = null, ?JsonPointer $path = null, $i = null): void { $type = isset($schema->type) ? $schema->type : null; $isValid = false; $coerce = $this->factory->getConfig(self::CHECK_MODE_COERCE_TYPES); $earlyCoerce = $this->factory->getConfig(self::CHECK_MODE_EARLY_COERCE); $wording = []; if (is_array($type)) { $this->validateTypesArray($value, $type, $wording, $isValid, $path, $coerce && $earlyCoerce); if (!$isValid && $coerce && !$earlyCoerce) { $this->validateTypesArray($value, $type, $wording, $isValid, $path, true); } } elseif (is_object($type)) { $this->checkUndefined($value, $type, $path); return; } else { $isValid = $this->validateType($value, $type, $coerce && $earlyCoerce); if (!$isValid && $coerce && !$earlyCoerce) { $isValid = $this->validateType($value, $type, true); } } if ($isValid === false) { if (!is_array($type)) { $this->validateTypeNameWording($type); $wording[] = self::$wording[$type]; } $this->addError(ConstraintError::TYPE(), $path, [ 'found' => gettype($value), 'expected' => $this->implodeWith($wording, ', ', 'or') ]); } } protected function validateTypesArray(&$value, array $type, &$validTypesWording, &$isValid, $path, $coerce = false) { foreach ($type as $tp) { if ($isValid) { return; } if (is_object($tp)) { if (!$isValid) { $validator = $this->factory->createInstanceFor('type'); $subSchema = new \stdClass(); $subSchema->type = $tp; $validator->check($value, $subSchema, $path, null); $error = $validator->getErrors(); $isValid = !(bool) $error; $validTypesWording[] = self::$wording['object']; } } else { $this->validateTypeNameWording($tp); $validTypesWording[] = self::$wording[$tp]; if (!$isValid) { $isValid = $this->validateType($value, $tp, $coerce); } } } } protected function implodeWith(array $elements, $delimiter = ', ', $listEnd = false) { if ($listEnd === false || !isset($elements[1])) { return implode($delimiter, $elements); } $lastElement = array_slice($elements, -1); $firsElements = join($delimiter, array_slice($elements, 0, -1)); $implodedElements = array_merge([$firsElements], $lastElement); return join(" $listEnd ", $implodedElements); } protected function validateTypeNameWording($type) { if (!array_key_exists($type, self::$wording)) { throw new StandardUnexpectedValueException( sprintf( 'No wording for %s available, expected wordings are: [%s]', var_export($type, true), implode(', ', array_filter(self::$wording))) ); } } protected function validateType(&$value, $type, $coerce = false) { if (!$type) { return true; } if ('any' === $type) { return true; } if ('object' === $type) { return $this->getTypeCheck()->isObject($value); } if ('array' === $type) { if ($coerce) { $value = $this->toArray($value); } return $this->getTypeCheck()->isArray($value); } if ('integer' === $type) { if ($coerce) { $value = $this->toInteger($value); } return is_int($value); } if ('number' === $type) { if ($coerce) { $value = $this->toNumber($value); } return is_numeric($value) && !is_string($value); } if ('boolean' === $type) { if ($coerce) { $value = $this->toBoolean($value); } return is_bool($value); } if ('string' === $type) { if ($coerce) { $value = $this->toString($value); } return is_string($value); } if ('null' === $type) { if ($coerce) { $value = $this->toNull($value); } return is_null($value); } throw new InvalidArgumentException((is_object($value) ? 'object' : $value) . ' is an invalid type for ' . $type); } protected function toBoolean($value) { if ($value === 1 || $value === 'true') { return true; } if (is_null($value) || $value === 0 || $value === 'false') { return false; } if ($this->getTypeCheck()->isArray($value) && count($value) === 1) { return $this->toBoolean(reset($value)); } return $value; } protected function toNumber($value) { if (is_numeric($value)) { return $value + 0; } if (is_bool($value) || is_null($value)) { return (int) $value; } if ($this->getTypeCheck()->isArray($value) && count($value) === 1) { return $this->toNumber(reset($value)); } return $value; } protected function toInteger($value) { $numberValue = $this->toNumber($value); if (is_numeric($numberValue) && (int) $numberValue == $numberValue) { return (int) $numberValue; } return $value; } protected function toArray($value) { if (is_scalar($value) || is_null($value)) { return [$value]; } return $value; } protected function toString($value) { if (is_numeric($value)) { return "$value"; } if ($value === true) { return 'true'; } if ($value === false) { return 'false'; } if (is_null($value)) { return ''; } if ($this->getTypeCheck()->isArray($value) && count($value) === 1) { return $this->toString(reset($value)); } return $value; } protected function toNull($value) { if ($value === 0 || $value === false || $value === '') { return null; } if ($this->getTypeCheck()->isArray($value) && count($value) === 1) { return $this->toNull(reset($value)); } return $value; } } incrementPath($path, $i); if ($fromDefault) { $path->setFromDefault(); } $this->validateCommonProperties($value, $schema, $path, $i); $this->validateOfProperties($value, $schema, $path, ''); $this->validateTypes($value, $schema, $path, $i); } public function validateTypes(&$value, $schema, JsonPointer $path, $i = null) { if ($this->getTypeCheck()->isArray($value)) { $this->checkArray($value, $schema, $path, $i); } if (LooseTypeCheck::isObject($value)) { $this->checkObject( $value, $schema, $path, isset($schema->properties) ? $schema->properties : null, isset($schema->additionalProperties) ? $schema->additionalProperties : null, isset($schema->patternProperties) ? $schema->patternProperties : null, $this->appliedDefaults ); } if (is_string($value)) { $this->checkString($value, $schema, $path, $i); } if (is_numeric($value)) { $this->checkNumber($value, $schema, $path, $i); } if (isset($schema->enum)) { $this->checkEnum($value, $schema, $path, $i); } if (isset($schema->const)) { $this->checkConst($value, $schema, $path, $i); } } protected function validateCommonProperties(&$value, $schema, JsonPointer $path, $i = '') { if (isset($schema->extends)) { if (is_string($schema->extends)) { $schema->extends = $this->validateUri($schema, $schema->extends); } if (is_array($schema->extends)) { foreach ($schema->extends as $extends) { $this->checkUndefined($value, $extends, $path, $i); } } else { $this->checkUndefined($value, $schema->extends, $path, $i); } } if (!$path->fromDefault()) { $this->applyDefaultValues($value, $schema, $path); } if ($this->getTypeCheck()->isObject($value)) { if (!($value instanceof self) && isset($schema->required) && is_array($schema->required)) { foreach ($schema->required as $required) { if (!$this->getTypeCheck()->propertyExists($value, $required)) { $this->addError( ConstraintError::REQUIRED(), $this->incrementPath($path, $required), [ 'property' => $required ] ); } } } elseif (isset($schema->required) && !is_array($schema->required)) { if ($schema->required && $value instanceof self) { $propertyPaths = $path->getPropertyPaths(); $propertyName = end($propertyPaths); $this->addError(ConstraintError::REQUIRED(), $path, ['property' => $propertyName]); } } else { if ($value instanceof self) { return; } } } if (!($value instanceof self)) { $this->checkType($value, $schema, $path, $i); } if (isset($schema->disallow)) { $initErrors = $this->getErrors(); $typeSchema = new \stdClass(); $typeSchema->type = $schema->disallow; $this->checkType($value, $typeSchema, $path); if (count($this->getErrors()) == count($initErrors)) { $this->addError(ConstraintError::DISALLOW(), $path); } else { $this->errors = $initErrors; } } if (isset($schema->not)) { $initErrors = $this->getErrors(); $this->checkUndefined($value, $schema->not, $path, $i); if (count($this->getErrors()) == count($initErrors)) { $this->addError(ConstraintError::NOT(), $path); } else { $this->errors = $initErrors; } } if (isset($schema->dependencies) && $this->getTypeCheck()->isObject($value)) { $this->validateDependencies($value, $schema->dependencies, $path); } } private function shouldApplyDefaultValue($requiredOnly, $schema, $name = null, $parentSchema = null) { if (!$requiredOnly) { return true; } if ( $name !== null && isset($parentSchema->required) && is_array($parentSchema->required) && in_array($name, $parentSchema->required) ) { return true; } if (isset($schema->required) && !is_array($schema->required) && $schema->required) { return true; } return false; } protected function applyDefaultValues(&$value, $schema, $path): void { if (!$this->factory->getConfig(self::CHECK_MODE_APPLY_DEFAULTS)) { return; } if (is_bool($schema)) { return; } $requiredOnly = (bool) $this->factory->getConfig(self::CHECK_MODE_ONLY_REQUIRED_DEFAULTS); if (isset($schema->properties) && LooseTypeCheck::isObject($value)) { foreach ($schema->properties as $currentProperty => $propertyDefinition) { $propertyDefinition = $this->factory->getSchemaStorage()->resolveRefSchema($propertyDefinition); if (is_bool($propertyDefinition)) { continue; } if ( !LooseTypeCheck::propertyExists($value, $currentProperty) && property_exists($propertyDefinition, 'default') && $this->shouldApplyDefaultValue($requiredOnly, $propertyDefinition, $currentProperty, $schema) ) { if (is_object($propertyDefinition->default)) { LooseTypeCheck::propertySet($value, $currentProperty, clone $propertyDefinition->default); } else { LooseTypeCheck::propertySet($value, $currentProperty, $propertyDefinition->default); } $this->appliedDefaults[] = $currentProperty; } } } elseif (isset($schema->items) && LooseTypeCheck::isArray($value)) { $items = []; if (LooseTypeCheck::isArray($schema->items)) { $items = $schema->items; } elseif (isset($schema->minItems) && count($value) < $schema->minItems) { $items = array_fill(count($value), $schema->minItems - count($value), $schema->items); } foreach ($items as $currentItem => $itemDefinition) { $itemDefinition = $this->factory->getSchemaStorage()->resolveRefSchema($itemDefinition); if (is_bool($itemDefinition)) { continue; } if ( !array_key_exists($currentItem, $value) && property_exists($itemDefinition, 'default') && $this->shouldApplyDefaultValue($requiredOnly, $itemDefinition)) { if (is_object($itemDefinition->default)) { $value[$currentItem] = clone $itemDefinition->default; } else { $value[$currentItem] = $itemDefinition->default; } } $path->setFromDefault(); } } elseif ( $value instanceof self && property_exists($schema, 'default') && $this->shouldApplyDefaultValue($requiredOnly, $schema)) { $value = is_object($schema->default) ? clone $schema->default : $schema->default; $path->setFromDefault(); } } protected function validateOfProperties(&$value, $schema, JsonPointer $path, $i = '') { if ($value instanceof self) { return; } if (isset($schema->allOf)) { $isValid = true; foreach ($schema->allOf as $allOf) { $initErrors = $this->getErrors(); $this->checkUndefined($value, $allOf, $path, $i); $isValid = $isValid && (count($this->getErrors()) == count($initErrors)); } if (!$isValid) { $this->addError(ConstraintError::ALL_OF(), $path); } } if (isset($schema->anyOf)) { $isValid = false; $startErrors = $this->getErrors(); $coerceOrDefaults = $this->factory->getConfig(self::CHECK_MODE_COERCE_TYPES | self::CHECK_MODE_APPLY_DEFAULTS); foreach ($schema->anyOf as $anyOf) { $initErrors = $this->getErrors(); try { $anyOfValue = $coerceOrDefaults ? DeepCopy::copyOf($value) : $value; $this->checkUndefined($anyOfValue, $anyOf, $path, $i); if ($isValid = (count($this->getErrors()) === count($initErrors))) { $value = $anyOfValue; break; } } catch (ValidationException $e) { $isValid = false; } } if (!$isValid) { $this->addError(ConstraintError::ANY_OF(), $path); } else { $this->errors = $startErrors; } } if (isset($schema->oneOf)) { $allErrors = []; $matchedSchemas = []; $startErrors = $this->getErrors(); $coerceOrDefaults = $this->factory->getConfig(self::CHECK_MODE_COERCE_TYPES | self::CHECK_MODE_APPLY_DEFAULTS); foreach ($schema->oneOf as $oneOf) { try { $this->errors = []; $oneOfValue = $coerceOrDefaults ? DeepCopy::copyOf($value) : $value; $this->checkUndefined($oneOfValue, $oneOf, $path, $i); if (count($this->getErrors()) === 0) { $matchedSchemas[] = ['schema' => $oneOf, 'value' => $oneOfValue]; } $allErrors = array_merge($allErrors, array_values($this->getErrors())); } catch (ValidationException $e) { } } if (count($matchedSchemas) !== 1) { $this->addErrors(array_merge($allErrors, $startErrors)); $this->addError(ConstraintError::ONE_OF(), $path); } else { $this->errors = $startErrors; $value = $matchedSchemas[0]['value']; } } } protected function validateDependencies($value, $dependencies, JsonPointer $path, $i = '') { foreach ($dependencies as $key => $dependency) { if ($this->getTypeCheck()->propertyExists($value, $key)) { if (is_string($dependency)) { if (!$this->getTypeCheck()->propertyExists($value, $dependency)) { $this->addError(ConstraintError::DEPENDENCIES(), $path, [ 'key' => $key, 'dependency' => $dependency ]); } } elseif (is_array($dependency)) { foreach ($dependency as $d) { if (!$this->getTypeCheck()->propertyExists($value, $d)) { $this->addError(ConstraintError::DEPENDENCIES(), $path, [ 'key' => $key, 'dependency' => $dependency ]); } } } elseif (is_object($dependency)) { $this->checkUndefined($value, $dependency, $path, $i); } } } } protected function validateUri($schema, $schemaUri = null) { $resolver = new UriResolver(); $retriever = $this->factory->getUriRetriever(); $jsonSchema = null; if ($resolver->isValid($schemaUri)) { $schemaId = property_exists($schema, 'id') ? $schema->id : null; $jsonSchema = $retriever->retrieve($schemaId, $schemaUri); } return $jsonSchema; } } 'draft03', self::DRAFT_4 => 'draft04', self::DRAFT_6 => 'draft06', self::DRAFT_7 => 'draft07', self::DRAFT_2019_09 => 'draft2019-09', self::DRAFT_2020_12 => 'draft2020-12', ]; private const FALLBACK_MAPPING = [ 'draft3' => self::DRAFT_3, 'draft4' => self::DRAFT_4, 'draft6' => self::DRAFT_6, 'draft7' => self::DRAFT_7, ]; public function toConstraintName(): string { return self::MAPPING[$this->getValue()]; } public static function fromConstraintName(string $name): DraftIdentifiers { $reverseMap = array_flip(self::MAPPING); if (!array_key_exists($name, $reverseMap)) { if (array_key_exists($name, self::FALLBACK_MAPPING)) { return DraftIdentifiers::byValue(self::FALLBACK_MAPPING[$name]); } throw new \InvalidArgumentException("$name is not a valid constraint name."); } return DraftIdentifiers::byValue($reverseMap[$name]); } public function withoutFragment(): string { return rtrim($this->getValue(), '#'); } } factory = $factory; } public function reset(): void { $this->errors = []; $this->errorMask = Validator::ERROR_NONE; } public function getErrors(): array { return $this->errors; } public function addError(ConstraintError $constraint, ?JsonPointer $path = null, array $more = []): void { $message = $constraint->getMessage(); $name = $constraint->getValue(); $error = [ 'property' => $this->convertJsonPointerIntoPropertyPath($path ?: new JsonPointer('')), 'pointer' => ltrim((string) ($path ?: new JsonPointer('')), '#'), 'message' => ucfirst(vsprintf($message, array_map(static function ($val) { if (is_scalar($val)) { return is_bool($val) ? var_export($val, true) : $val; } return json_encode($val); }, array_values($more)))), 'constraint' => [ 'name' => $name, 'params' => $more ], 'context' => $this->factory->getErrorContext(), ]; if ($this->factory->getConfig(Constraint::CHECK_MODE_EXCEPTIONS)) { throw new ValidationException(sprintf('Error validating %s: %s', $error['pointer'], $error['message'])); } $this->errors[] = $error; $this->errorMask |= $error['context']; } public function addErrors(array $errors): void { if (!$errors) { return; } $this->errors = array_merge($this->errors, $errors); $errorMask = &$this->errorMask; array_walk($errors, static function ($error) use (&$errorMask) { $errorMask |= $error['context']; }); } private function convertJsonPointerIntoPropertyPath(JsonPointer $pointer): string { $result = array_map( static function ($path) { return sprintf(is_numeric($path) ? '[%d]' : '.%s', $path); }, $pointer->getPropertyPaths() ); return trim(implode('', $result), '.'); } } errorBag()->getErrors(); } public function addErrors(array $errors): void { $this->errorBag()->addErrors($errors); } public function addError(ConstraintError $constraint, ?JsonPointer $path = null, array $more = []): void { $this->errorBag()->addError($constraint, $path, $more); } public function isValid(): bool { return $this->errorBag()->getErrors() === []; } protected function initialiseErrorBag(Factory $factory): ErrorBag { if (is_null($this->errorBag)) { $this->errorBag = new ErrorBag($factory); } return $this->errorBag; } protected function errorBag(): ErrorBag { if (is_null($this->errorBag)) { throw new \RuntimeException('ErrorBag not initialized'); } return $this->errorBag; } public function __clone() { $this->errorBag()->reset(); } } filename = $splitRef[0]; if (array_key_exists(1, $splitRef)) { $this->propertyPaths = $this->decodePropertyPaths($splitRef[1]); } } private function decodePropertyPaths($propertyPathString) { $paths = []; foreach (explode('/', trim($propertyPathString, '/')) as $path) { $path = $this->decodePath($path); if (is_string($path) && '' !== $path) { $paths[] = $path; } } return $paths; } private function encodePropertyPaths() { return array_map( [$this, 'encodePath'], $this->getPropertyPaths() ); } private function decodePath($path) { return strtr($path, ['~1' => '/', '~0' => '~', '%25' => '%']); } private function encodePath($path) { return strtr($path, ['/' => '~1', '~' => '~0', '%' => '%25']); } public function getFilename() { return $this->filename; } public function getPropertyPaths() { return $this->propertyPaths; } public function withPropertyPaths(array $propertyPaths) { $new = clone $this; $new->propertyPaths = array_map(function ($p): string { return (string) $p; }, $propertyPaths); return $new; } public function getPropertyPathAsString() { return rtrim('#/' . implode('/', $this->encodePropertyPaths()), '/'); } public function __toString() { return $this->getFilename() . $this->getPropertyPathAsString(); } public function setFromDefault(): void { $this->fromDefault = true; } public function fromDefault() { return $this->fromDefault; } } object = $object; } #[\ReturnTypeWillChange] public function current() { $this->initialize(); return $this->data[$this->position]; } public function next(): void { $this->initialize(); $this->position++; } public function key(): int { $this->initialize(); return $this->position; } public function valid(): bool { $this->initialize(); return isset($this->data[$this->position]); } public function rewind(): void { $this->initialize(); $this->position = 0; } public function count(): int { $this->initialize(); return count($this->data); } private function initialize() { if (!$this->initialized) { $this->data = $this->buildDataFromObject($this->object); $this->initialized = true; } } private function buildDataFromObject($object) { $result = []; $stack = new \SplStack(); $stack->push($object); while (!$stack->isEmpty()) { $current = $stack->pop(); if (is_object($current)) { array_push($result, $current); } foreach ($this->getDataFromItem($current) as $propertyName => $propertyValue) { if (is_object($propertyValue) || is_array($propertyValue)) { $stack->push($propertyValue); } } } return $result; } private function getDataFromItem($item) { if (!is_object($item) && !is_array($item)) { return []; } return is_object($item) ? get_object_vars($item) : $item; } } setTimezone(new \DateTimeZone('+00:00')); $oneSecond = new \DateInterval('PT1S'); if ($matches[4] === '60' && $utcDateTime->sub($oneSecond)->format('H:i:s') === '23:59:59') { $dateTime = $dateTime->sub($oneSecond); $matches[1] = str_replace(':60', ':59', $matches[1]); } if ($dateTime->format($inputHasTSeparator ? 'Y-m-d\TH:i:s' : 'Y-m-d H:i:s') !== $matches[1]) { return null; } $mutable = \DateTime::createFromFormat('U.u', $dateTime->format('U.u')); if ($mutable === false) { throw new \RuntimeException('Unable to create DateTime from DateTimeImmutable'); } $mutable->setTimezone($dateTime->getTimezone()); return $mutable; } } uriRetriever = $uriRetriever ?: new UriRetriever(); $this->uriResolver = $uriResolver ?: new UriResolver(); } public function getUriRetriever() { return $this->uriRetriever; } public function getUriResolver() { return $this->uriResolver; } public function addSchema(string $id, $schema = null): void { if (is_null($schema) && $id !== self::INTERNAL_PROVIDED_SCHEMA_URI) { $schema = $this->uriRetriever->retrieve($id); } if (is_array($schema)) { $schema = BaseConstraint::arrayToObjectRecursive($schema); } if (is_object($schema) && property_exists($schema, 'id')) { if ($schema->id === DraftIdentifiers::DRAFT_4) { $schema->properties->id->format = 'uri-reference'; } elseif ($schema->id === DraftIdentifiers::DRAFT_3) { $schema->properties->id->format = 'uri-reference'; $schema->properties->{'$ref'}->format = 'uri-reference'; } } $this->scanForSubschemas($schema, $id); $this->expandRefs($schema, $id); $this->schemas[$id] = $schema; } private function expandRefs(&$schema, ?string $parentId = null, array $propertyStack = []): void { if (!is_object($schema)) { if (is_array($schema)) { foreach ($schema as &$member) { $this->expandRefs($member, $parentId); } } return; } if (property_exists($schema, '$ref') && is_string($schema->{'$ref'})) { $refPointer = new JsonPointer($this->uriResolver->resolve($schema->{'$ref'}, $parentId)); $schema->{'$ref'} = (string) $refPointer; } $parentProperty = array_slice($propertyStack, -1)[0] ?? ''; foreach ($schema as $propertyName => &$member) { if ($parentProperty !== 'properties' && in_array($propertyName, ['enum', 'const'])) { continue; } $schemaId = $this->findSchemaIdInObject($schema); $childId = $parentId; if (is_string($schemaId) && $childId !== $schemaId) { $childId = $this->uriResolver->resolve($schemaId, $childId); } $clonedPropertyStack = $propertyStack; $clonedPropertyStack[] = $propertyName; $this->expandRefs($member, $childId, $clonedPropertyStack); } } public function getSchema(string $id) { if (!array_key_exists($id, $this->schemas)) { $this->addSchema($id); } return $this->schemas[$id]; } public function resolveRef(string $ref, $resolveStack = []) { $jsonPointer = new JsonPointer($ref); $fileName = $jsonPointer->getFilename(); if (!strlen($fileName)) { throw new UnresolvableJsonPointerException(sprintf( "Could not resolve fragment '%s': no file is defined", $jsonPointer->getPropertyPathAsString() )); } $refSchema = $this->getSchema($fileName); foreach ($jsonPointer->getPropertyPaths() as $path) { $path = urldecode($path); if (is_object($refSchema) && property_exists($refSchema, $path)) { $refSchema = $this->resolveRefSchema($refSchema->{$path}, $resolveStack); } elseif (is_array($refSchema) && array_key_exists($path, $refSchema)) { $refSchema = $this->resolveRefSchema($refSchema[$path], $resolveStack); } else { throw new UnresolvableJsonPointerException(sprintf( 'File: %s is found, but could not resolve fragment: %s', $jsonPointer->getFilename(), $jsonPointer->getPropertyPathAsString() )); } } return $refSchema; } public function resolveRefSchema($refSchema, $resolveStack = []) { if (is_object($refSchema) && property_exists($refSchema, '$ref') && is_string($refSchema->{'$ref'})) { if (in_array($refSchema, $resolveStack, true)) { throw new UnresolvableJsonPointerException(sprintf( 'Dereferencing a pointer to %s results in an infinite loop', $refSchema->{'$ref'} )); } $resolveStack[] = $refSchema; return $this->resolveRef($refSchema->{'$ref'}, $resolveStack); } if (is_object($refSchema) && array_keys(get_object_vars($refSchema)) === ['']) { $refSchema = get_object_vars($refSchema)['']; } return $refSchema; } private function scanForSubschemas($schema, string $parentId): void { if (!$schema instanceof \stdClass && !is_array($schema)) { return; } foreach ($schema as $propertyName => $potentialSubSchema) { if (!is_object($potentialSubSchema)) { if (is_array($potentialSubSchema)) { foreach ($potentialSubSchema as $potentialSubSchemaItem) { $this->scanForSubschemas($potentialSubSchemaItem, $parentId); } } continue; } $potentialSubSchemaId = $this->findSchemaIdInObject($potentialSubSchema); if (is_string($potentialSubSchemaId) && property_exists($potentialSubSchema, 'type')) { if (in_array($propertyName, ['enum', 'const'])) { continue; } if (in_array($propertyName, [])) { continue; } $this->addSchema($this->uriResolver->resolve($potentialSubSchemaId, $parentId), $potentialSubSchema); } $this->scanForSubschemas($potentialSubSchema, $parentId); } } private function findSchemaIdInObject(object $schema): ?string { if (property_exists($schema, 'id') && is_string($schema->id)) { return $schema->id; } if (property_exists($schema, '$id') && is_string($schema->{'$id'})) { return $schema->{'$id'}; } return null; } } $value) { if (!array_key_exists($key, $right)) { return false; } if (!self::isEqual($value, $right[$key])) { return false; } } return true; } } 65535)) { return false; } if (!empty($matches[6]) && preg_match('/[<>{}|\\\^`]/', $matches[6])) { return false; } return true; } if (preg_match($nonHierarchicalPattern, $uri, $matches) === 1) { $scheme = strtolower($matches[1]); if ($scheme === 'mailto') { return preg_match($emailPattern, $matches[2]) === 1; } if ($scheme === 'news') { return preg_match($newsGroupPattern, $matches[2]) === 1; } if ($scheme === 'tel') { return preg_match($telPattern, $matches[2]) === 1; } return true; } return false; } } contentType; } } fetchMessageBody($response); $this->fetchContentType($response); if (PHP_VERSION_ID < 80000) { curl_close($ch); } return $this->messageBody; } private function fetchMessageBody($response) { preg_match("/(?:\r\n){2}(.*)$/ms", $response, $match); $this->messageBody = $match[1]; } protected function fetchContentType($response) { if (0 < preg_match("/Content-Type:(\V*)/ims", $response, $match)) { $this->contentType = trim($match[1]); return true; } return false; } } messageBody = $response; if (function_exists('http_get_last_response_headers')) { $httpResponseHeaders = http_get_last_response_headers(); } else { $httpResponseHeaders = $http_response_header ?? []; } if (!empty($httpResponseHeaders)) { $this->fetchContentType($httpResponseHeaders); } else { $this->contentType = null; } return $this->messageBody; } private function fetchContentType(array $headers): bool { foreach (array_reverse($headers) as $header) { if ($this->contentType = self::getContentTypeMatchInHeader($header)) { return true; } } return false; } protected static function getContentTypeMatchInHeader($header) { if (0 < preg_match("/Content-Type:(\V*)/ims", $header, $match)) { return trim($match[1]); } return null; } } schemas = $schemas; $this->contentType = $contentType; } public function retrieve($uri) { if (!array_key_exists($uri, $this->schemas)) { throw new \JsonSchema\Exception\ResourceNotFoundException(sprintf( 'The JSON schema "%s" was not found.', $uri )); } return $this->schemas[$uri]; } } $match[2], 'authority' => $match[4], 'path' => $match[5] ]; } if (7 < count($match)) { $components['query'] = $match[7]; } if (9 < count($match)) { $components['fragment'] = $match[9]; } return $components; } public function generate(array $components) { $uri = $components['scheme'] . '://' . $components['authority'] . $components['path']; if (array_key_exists('query', $components) && strlen($components['query'])) { $uri .= '?' . $components['query']; } if (array_key_exists('fragment', $components)) { $uri .= '#' . $components['fragment']; } return $uri; } public function resolve($uri, $baseUri = null) { if ( !is_null($baseUri) && !filter_var($baseUri, \FILTER_VALIDATE_URL) && !preg_match('|^[^/]+://|u', $baseUri) ) { if (is_file($baseUri)) { $baseUri = 'file://' . realpath($baseUri); } elseif (is_dir($baseUri)) { $baseUri = 'file://' . realpath($baseUri) . '/'; } else { $baseUri = 'file://' . getcwd() . '/' . $baseUri; } } if ($uri == '') { return $baseUri; } $components = $this->parse($uri); $path = $components['path']; if (!empty($components['scheme'])) { return $uri; } $baseComponents = $this->parse($baseUri); $basePath = $baseComponents['path']; $baseComponents['path'] = self::combineRelativePathWithBasePath($path, $basePath); if (isset($components['fragment'])) { $baseComponents['fragment'] = $components['fragment']; } return $this->generate($baseComponents); } public static function combineRelativePathWithBasePath($relativePath, $basePath) { $relativePath = self::normalizePath($relativePath); if (!$relativePath) { return $basePath; } if ($relativePath[0] === '/') { return $relativePath; } if (!$basePath) { throw new UriResolverException(sprintf("Unable to resolve URI '%s' from base '%s'", $relativePath, $basePath)); } $dirname = $basePath[strlen($basePath) - 1] === '/' ? $basePath : dirname($basePath); $combined = rtrim($dirname, '/') . '/' . ltrim($relativePath, '/'); $combinedSegments = explode('/', $combined); $collapsedSegments = []; while ($combinedSegments) { $segment = array_shift($combinedSegments); if ($segment === '..') { if (count($collapsedSegments) <= 1) { throw new UriResolverException(sprintf("Unable to resolve URI '%s' from base '%s'", $relativePath, $basePath)); } array_pop($collapsedSegments); } else { $collapsedSegments[] = $segment; } } return implode('/', $collapsedSegments); } private static function normalizePath($path) { $path = preg_replace('|((?parse($uri); return !empty($components); } } 'package://dist/schema/json-schema-draft-$1.json' ]; protected $allowedInvalidContentTypeEndpoints = [ 'http://json-schema.org/', 'https://json-schema.org/' ]; protected $uriRetriever = null; private $schemaCache = []; public function addInvalidContentTypeEndpoint($endpoint) { $this->allowedInvalidContentTypeEndpoints[] = $endpoint; } public function confirmMediaType($uriRetriever, $uri) { $contentType = $uriRetriever->getContentType(); if (is_null($contentType)) { return; } if (in_array($contentType, [Validator::SCHEMA_MEDIA_TYPE, 'application/json'])) { return; } foreach ($this->allowedInvalidContentTypeEndpoints as $endpoint) { if (!\is_null($uri) && strpos($uri, $endpoint) === 0) { return true; } } throw new InvalidSchemaMediaTypeException(sprintf('Media type %s expected, but %s given', Validator::SCHEMA_MEDIA_TYPE, $contentType)); } public function getUriRetriever() { if (is_null($this->uriRetriever)) { $this->setUriRetriever(new FileGetContents()); } return $this->uriRetriever; } public function resolvePointer($jsonSchema, $uri) { $resolver = new UriResolver(); $parsed = $resolver->parse($uri); if (empty($parsed['fragment'])) { return $jsonSchema; } $path = explode('/', $parsed['fragment']); while ($path) { $pathElement = array_shift($path); if (!empty($pathElement)) { $pathElement = str_replace('~1', '/', $pathElement); $pathElement = str_replace('~0', '~', $pathElement); if (!empty($jsonSchema->$pathElement)) { $jsonSchema = $jsonSchema->$pathElement; } else { throw new ResourceNotFoundException( 'Fragment "' . $parsed['fragment'] . '" not found' . ' in ' . $uri ); } if (!is_object($jsonSchema)) { throw new ResourceNotFoundException( 'Fragment part "' . $pathElement . '" is no object ' . ' in ' . $uri ); } } } return $jsonSchema; } public function retrieve($uri, $baseUri = null, $translate = true) { $resolver = new UriResolver(); $resolvedUri = $fetchUri = $resolver->resolve($uri, $baseUri); $arParts = $resolver->parse($resolvedUri); if (isset($arParts['fragment'])) { unset($arParts['fragment']); $fetchUri = $resolver->generate($arParts); } if ($translate) { $fetchUri = $this->translate($fetchUri); } $jsonSchema = $this->loadSchema($fetchUri); $jsonSchema = $this->resolvePointer($jsonSchema, $resolvedUri); if ($jsonSchema instanceof \stdClass) { $jsonSchema->id = $resolvedUri; } return $jsonSchema; } protected function loadSchema($fetchUri) { if (isset($this->schemaCache[$fetchUri])) { return $this->schemaCache[$fetchUri]; } $uriRetriever = $this->getUriRetriever(); $contents = $this->uriRetriever->retrieve($fetchUri); $this->confirmMediaType($uriRetriever, $fetchUri); $jsonSchema = json_decode($contents); if (JSON_ERROR_NONE < $error = json_last_error()) { throw new JsonDecodingException($error); } $this->schemaCache[$fetchUri] = $jsonSchema; return $jsonSchema; } public function setUriRetriever(UriRetrieverInterface $uriRetriever) { $this->uriRetriever = $uriRetriever; return $this; } public function parse($uri) { preg_match('|^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?|', $uri, $match); $components = []; if (5 < count($match)) { $components = [ 'scheme' => $match[2], 'authority' => $match[4], 'path' => $match[5] ]; } if (7 < count($match)) { $components['query'] = $match[7]; } if (9 < count($match)) { $components['fragment'] = $match[9]; } return $components; } public function generate(array $components) { $uri = $components['scheme'] . '://' . $components['authority'] . $components['path']; if (array_key_exists('query', $components)) { $uri .= $components['query']; } if (array_key_exists('fragment', $components)) { $uri .= $components['fragment']; } return $uri; } public function resolve($uri, $baseUri = null) { $components = $this->parse($uri); $path = $components['path']; if ((array_key_exists('scheme', $components)) && ('http' === $components['scheme'])) { return $uri; } $baseComponents = $this->parse($baseUri); $basePath = $baseComponents['path']; $baseComponents['path'] = UriResolver::combineRelativePathWithBasePath($path, $basePath); return $this->generate($baseComponents); } public function isValid($uri) { $components = $this->parse($uri); return !empty($components); } public function setTranslation($from, $to) { $this->translationMap[$from] = $to; } public function translate($uri) { foreach ($this->translationMap as $from => $to) { $uri = preg_replace($from, $to, $uri); } $uri = preg_replace('|^package://|', sprintf('file://%s/', realpath(__DIR__ . '/../../..')), $uri); return $uri; } } reset(); $initialCheckMode = $this->factory->getConfig(); if ($checkMode !== null) { $this->factory->setConfig($checkMode); } $schemaURI = SchemaStorage::INTERNAL_PROVIDED_SCHEMA_URI; if (LooseTypeCheck::propertyExists($schema, 'id')) { $schemaURI = LooseTypeCheck::propertyGet($schema, 'id'); } if (LooseTypeCheck::propertyExists($schema, '$id')) { $schemaURI = LooseTypeCheck::propertyGet($schema, '$id'); } $this->factory->getSchemaStorage()->addSchema($schemaURI, $schema); $validator = $this->factory->createInstanceFor('schema'); $schema = $this->factory->getSchemaStorage()->getSchema($schemaURI); if (is_bool($schema)) { if ($schema === false) { $this->addError(ConstraintError::FALSE()); } return $this->getErrorMask(); } if ($this->factory->getConfig(Constraint::CHECK_MODE_STRICT)) { $dialect = $this->factory->getDefaultDialect(); if (property_exists($schema, '$schema')) { $dialect = $schema->{'$schema'}; } $validator = $this->factory->createInstanceFor( DraftIdentifiers::byValue($dialect)->toConstraintName() ); } $validator->check($value, $schema); $this->factory->setConfig($initialCheckMode); $this->addErrors(array_unique($validator->getErrors(), SORT_REGULAR)); return $validator->getErrorMask(); } public function check($value, $schema): int { return $this->validate($value, $schema); } public function coerce(&$value, $schema): int { return $this->validate($value, $schema, Constraint::CHECK_MODE_COERCE_TYPES); } } Copyright (c) 2020, Marc Bennewitz All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the organisation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. value = $value; $this->ordinal = $ordinal; } public function __toString(): string { return $this->getName(); } final public function __clone() { throw new LogicException('Enums are not cloneable'); } final public function __sleep() { throw new LogicException('Serialization is not supported by default in this pseudo-enum implementation'); } public function __serialize(): array { throw new LogicException('Serialization is not supported by default in this pseudo-enum implementation'); } final public function __wakeup() { throw new LogicException('Serialization is not supported by default in this pseudo-enum implementation'); } public function __unserialize(array $data): void { throw new LogicException('Serialization is not supported by default in this pseudo-enum implementation'); } final public function getValue() { return $this->value; } final public function getName() { return self::$names[static::class][$this->ordinal ?? $this->getOrdinal()]; } final public function getOrdinal() { if ($this->ordinal === null) { $ordinal = 0; $value = $this->value; $constants = self::$constants[static::class] ?? static::getConstants(); foreach ($constants as $constValue) { if ($value === $constValue) { break; } ++$ordinal; } $this->ordinal = $ordinal; } return $this->ordinal; } final public function is($enumerator) { return $this === $enumerator || $this->value === $enumerator || ($enumerator instanceof static && \get_class($enumerator) === static::class && $enumerator->value === $this->value ); } final public static function get($enumerator) { if ($enumerator instanceof static) { if (\get_class($enumerator) !== static::class) { throw new InvalidArgumentException(sprintf( 'Invalid value of type %s for enumeration %s', \get_class($enumerator), static::class )); } return $enumerator; } return static::byValue($enumerator); } final public static function byValue($value) { $constants = self::$constants[static::class] ?? static::getConstants(); $name = \array_search($value, $constants, true); if ($name === false) { throw new InvalidArgumentException(sprintf( 'Unknown value %s for enumeration %s', \is_scalar($value) ? \var_export($value, true) : 'of type ' . (\is_object($value) ? \get_class($value) : \gettype($value)), static::class )); } $instance = self::$instances[static::class][$name] ?? self::$instances[static::class][$name] = new static($constants[$name]); return $instance; } final public static function byName(string $name) { if (isset(self::$instances[static::class][$name])) { $instance = self::$instances[static::class][$name]; return $instance; } $const = static::class . "::{$name}"; if (!\defined($const)) { throw new InvalidArgumentException("{$const} not defined"); } assert( self::noAmbiguousValues(static::getConstants()), 'Ambiguous enumerator values detected for ' . static::class ); $value = \constant($const); return self::$instances[static::class][$name] = new static($value); } final public static function byOrdinal(int $ordinal) { $constants = self::$constants[static::class] ?? static::getConstants(); if (!isset(self::$names[static::class][$ordinal])) { throw new InvalidArgumentException(\sprintf( 'Invalid ordinal number %s, must between 0 and %s', $ordinal, \count(self::$names[static::class]) - 1 )); } $name = self::$names[static::class][$ordinal]; $instance = self::$instances[static::class][$name] ?? self::$instances[static::class][$name] = new static($constants[$name], $ordinal); return $instance; } final public static function getEnumerators() { if (!isset(self::$names[static::class])) { static::getConstants(); } $byNameFn = [static::class, 'byName']; return \array_map($byNameFn, self::$names[static::class]); } final public static function getValues() { return \array_values(self::$constants[static::class] ?? static::getConstants()); } final public static function getNames() { if (!isset(self::$names[static::class])) { static::getConstants(); } return self::$names[static::class]; } final public static function getOrdinals() { $count = \count(self::$constants[static::class] ?? static::getConstants()); return $count ? \range(0, $count - 1) : []; } final public static function getConstants() { if (isset(self::$constants[static::class])) { return self::$constants[static::class]; } $reflection = new ReflectionClass(static::class); $constants = []; do { $scopeConstants = []; foreach ($reflection->getReflectionConstants() as $reflConstant) { if ($reflConstant->isPublic()) { $scopeConstants[ $reflConstant->getName() ] = $reflConstant->getValue(); } } $constants = $scopeConstants + $constants; } while (($reflection = $reflection->getParentClass()) && $reflection->name !== __CLASS__); assert( self::noAmbiguousValues($constants), 'Ambiguous enumerator values detected for ' . static::class ); self::$names[static::class] = \array_keys($constants); return self::$constants[static::class] = $constants; } private static function noAmbiguousValues($constants) { foreach ($constants as $value) { $names = \array_keys($constants, $value, true); if (\count($names) > 1) { return false; } } return true; } final public static function has($enumerator) { if ($enumerator instanceof static) { return \get_class($enumerator) === static::class; } return static::hasValue($enumerator); } final public static function hasValue($value) { return \in_array($value, self::$constants[static::class] ?? static::getConstants(), true); } final public static function hasName(string $name) { return \defined("static::{$name}"); } final public static function __callStatic(string $method, array $args) { return static::byName($method); } } enumeration = $enumeration; if ($map) { $this->addIterable($map); } } public function __debugInfo(): array { $dbg = (array)$this; $dbg["\0" . self::class . "\0__pairs"] = array_map(function ($k, $v) { return [$k, $v]; }, $this->getKeys(), $this->getValues()); return $dbg; } public function add($enumerator, $value): void { $ord = ($this->enumeration)::get($enumerator)->getOrdinal(); $this->map[$ord] = $value; } public function addIterable(iterable $map): void { $innerMap = $this->map; foreach ($map as $enumerator => $value) { $ord = ($this->enumeration)::get($enumerator)->getOrdinal(); $innerMap[$ord] = $value; } $this->map = $innerMap; } public function remove($enumerator): void { $ord = ($this->enumeration)::get($enumerator)->getOrdinal(); unset($this->map[$ord]); } public function removeIterable(iterable $enumerators): void { $map = $this->map; foreach ($enumerators as $enumerator) { $ord = ($this->enumeration)::get($enumerator)->getOrdinal(); unset($map[$ord]); } $this->map = $map; } public function with($enumerator, $value): self { $clone = clone $this; $clone->add($enumerator, $value); return $clone; } public function withIterable(iterable $map): self { $clone = clone $this; $clone->addIterable($map); return $clone; } public function without($enumerator): self { $clone = clone $this; $clone->remove($enumerator); return $clone; } public function withoutIterable(iterable $enumerators): self { $clone = clone $this; $clone->removeIterable($enumerators); return $clone; } public function getEnumeration(): string { return $this->enumeration; } public function get($enumerator) { $enumerator = ($this->enumeration)::get($enumerator); $ord = $enumerator->getOrdinal(); if (!\array_key_exists($ord, $this->map)) { throw new UnexpectedValueException(sprintf( 'Enumerator %s could not be found', \var_export($enumerator->getValue(), true) )); } return $this->map[$ord]; } public function getKeys(): array { $byOrdinalFn = [$this->enumeration, 'byOrdinal']; return \array_map($byOrdinalFn, \array_keys($this->map)); } public function getValues(): array { return \array_values($this->map); } public function search($value, bool $strict = false) { $ord = \array_search($value, $this->map, $strict); if ($ord !== false) { return ($this->enumeration)::byOrdinal($ord); } return null; } public function has($enumerator): bool { try { $ord = ($this->enumeration)::get($enumerator)->getOrdinal(); return \array_key_exists($ord, $this->map); } catch (InvalidArgumentException $e) { return false; } } public function contains($enumerator): bool { return $this->has($enumerator); } public function offsetExists($enumerator): bool { try { return isset($this->map[($this->enumeration)::get($enumerator)->getOrdinal()]); } catch (InvalidArgumentException $e) { return false; } } #[\ReturnTypeWillChange] public function offsetGet($enumerator) { try { return $this->get($enumerator); } catch (UnexpectedValueException $e) { return null; } } public function offsetSet($enumerator, $value = null): void { $this->add($enumerator, $value); } public function offsetUnset($enumerator): void { $this->remove($enumerator); } public function getIterator(): Iterator { $map = $this->map; foreach ($map as $ordinal => $value) { yield ($this->enumeration)::byOrdinal($ordinal) => $value; } } public function count(): int { return \count($this->map); } public function isEmpty(): bool { return empty($this->map); } } $this->getValue()]; } public function __unserialize(array $data): void { if (!\array_key_exists('value', $data)) { throw new RuntimeException('Missing array key "value"'); } $value = $data['value']; $constants = self::getConstants(); $name = \array_search($value, $constants, true); if ($name === false) { $message = \is_scalar($value) ? 'Unknown value ' . \var_export($value, true) : 'Invalid value of type ' . (\is_object($value) ? \get_class($value) : \gettype($value)); throw new RuntimeException($message); } $class = static::class; $enumerator = $this; $closure = function () use ($class, $name, $value, $enumerator) { if ($value !== null && $this->value !== null) { throw new LogicException('Do not call this directly - please use unserialize($enum) instead'); } $this->value = $value; if (!isset(self::$instances[$class][$name])) { self::$instances[$class][$name] = $enumerator; } }; $closure->bindTo($this, Enum::class)(); } public function serialize(): string { return \serialize($this->getValue()); } public function unserialize($serialized): void { $this->__unserialize(['value' => \unserialize($serialized)]); } } enumeration = $enumeration; $this->enumerationCount = \count($enumeration::getConstants()); if ($this->enumerationCount > \PHP_INT_SIZE * 8) { $this->bitset = $this->emptyBitset = \str_repeat("\0", (int)\ceil($this->enumerationCount / 8)); $this->fnDoGetIterator = 'doGetIteratorBin'; $this->fnDoCount = 'doCountBin'; $this->fnDoGetOrdinals = 'doGetOrdinalsBin'; $this->fnDoGetBit = 'doGetBitBin'; $this->fnDoSetBit = 'doSetBitBin'; $this->fnDoUnsetBit = 'doUnsetBitBin'; $this->fnDoGetBinaryBitsetLe = 'doGetBinaryBitsetLeBin'; $this->fnDoSetBinaryBitsetLe = 'doSetBinaryBitsetLeBin'; } if ($enumerators !== null) { foreach ($enumerators as $enumerator) { $this->{$this->fnDoSetBit}($enumeration::get($enumerator)->getOrdinal()); } } } public function __debugInfo() { $dbg = (array)$this; $dbg["\0" . self::class . "\0__enumerators"] = $this->getValues(); return $dbg; } public function getEnumeration(): string { return $this->enumeration; } public function add($enumerator): void { $this->{$this->fnDoSetBit}(($this->enumeration)::get($enumerator)->getOrdinal()); } public function addIterable(iterable $enumerators): void { $bitset = $this->bitset; try { foreach ($enumerators as $enumerator) { $this->{$this->fnDoSetBit}(($this->enumeration)::get($enumerator)->getOrdinal()); } } catch (\Throwable $e) { $this->bitset = $bitset; throw $e; } } public function remove($enumerator): void { $this->{$this->fnDoUnsetBit}(($this->enumeration)::get($enumerator)->getOrdinal()); } public function attach($enumerator): void { $this->add($enumerator); } public function detach($enumerator): void { $this->remove($enumerator); } public function removeIterable(iterable $enumerators): void { $bitset = $this->bitset; try { foreach ($enumerators as $enumerator) { $this->{$this->fnDoUnsetBit}(($this->enumeration)::get($enumerator)->getOrdinal()); } } catch (\Throwable $e) { $this->bitset = $bitset; throw $e; } } public function setUnion(EnumSet $other): void { if ($this->enumeration !== $other->enumeration) { throw new InvalidArgumentException(\sprintf( 'Other should be of the same enumeration as this %s', $this->enumeration )); } $this->bitset = $this->bitset | $other->bitset; } public function setIntersect(EnumSet $other): void { if ($this->enumeration !== $other->enumeration) { throw new InvalidArgumentException(\sprintf( 'Other should be of the same enumeration as this %s', $this->enumeration )); } $this->bitset = $this->bitset & $other->bitset; } public function setDiff(EnumSet $other): void { if ($this->enumeration !== $other->enumeration) { throw new InvalidArgumentException(\sprintf( 'Other should be of the same enumeration as this %s', $this->enumeration )); } $this->bitset = $this->bitset & ~$other->bitset; } public function setSymDiff(EnumSet $other): void { if ($this->enumeration !== $other->enumeration) { throw new InvalidArgumentException(\sprintf( 'Other should be of the same enumeration as this %s', $this->enumeration )); } $this->bitset = $this->bitset ^ $other->bitset; } public function setBinaryBitsetLe(string $bitset): void { $this->{$this->fnDoSetBinaryBitsetLe}($bitset); } private function doSetBinaryBitsetLeBin($bitset): void { $thisBitset = $this->bitset; $size = \strlen($thisBitset); $sizeIn = \strlen($bitset); if ($sizeIn < $size) { $bitset .= \str_repeat("\0", $size - $sizeIn); } elseif ($sizeIn > $size) { if (\ltrim(\substr($bitset, $size), "\0") !== '') { throw new InvalidArgumentException('out-of-range bits detected'); } $bitset = \substr($bitset, 0, $size); } $lastByteMaxOrd = $this->enumerationCount % 8; if ($lastByteMaxOrd !== 0) { $lastByte = $bitset[-1]; $lastByteExpected = \chr((1 << $lastByteMaxOrd) - 1) & $lastByte; if ($lastByte !== $lastByteExpected) { throw new InvalidArgumentException('out-of-range bits detected'); } $this->bitset = \substr($bitset, 0, -1) . $lastByteExpected; } $this->bitset = $bitset; } private function doSetBinaryBitsetLeInt($bitset): void { $len = \strlen($bitset); $int = 0; for ($i = 0; $i < $len; ++$i) { $ord = \ord($bitset[$i]); if ($ord && $i > \PHP_INT_SIZE - 1) { throw new InvalidArgumentException('out-of-range bits detected'); } $int |= $ord << (8 * $i); } if ($int & (~0 << $this->enumerationCount)) { throw new InvalidArgumentException('out-of-range bits detected'); } $this->bitset = $int; } public function setBinaryBitsetBe(string $bitset): void { $this->{$this->fnDoSetBinaryBitsetLe}(\strrev($bitset)); } public function setBit(int $ordinal, bool $bit): void { if ($ordinal < 0 || $ordinal > $this->enumerationCount) { throw new InvalidArgumentException("Ordinal number must be between 0 and {$this->enumerationCount}"); } if ($bit) { $this->{$this->fnDoSetBit}($ordinal); } else { $this->{$this->fnDoUnsetBit}($ordinal); } } private function doSetBitBin($ordinal): void { $thisBitset = $this->bitset; $byte = (int) ($ordinal / 8); $thisBitset[$byte] = $thisBitset[$byte] | \chr(1 << ($ordinal % 8)); $this->bitset = $thisBitset; } private function doSetBitInt($ordinal): void { $thisBitset = $this->bitset; $this->bitset = $thisBitset | (1 << $ordinal); } private function doUnsetBitBin($ordinal): void { $thisBitset = $this->bitset; $byte = (int) ($ordinal / 8); $thisBitset[$byte] = $thisBitset[$byte] & \chr(~(1 << ($ordinal % 8))); $this->bitset = $thisBitset; } private function doUnsetBitInt($ordinal): void { $thisBitset = $this->bitset; $this->bitset = $thisBitset & ~(1 << $ordinal); } public function with($enumerator): self { $clone = clone $this; $clone->{$this->fnDoSetBit}(($this->enumeration)::get($enumerator)->getOrdinal()); return $clone; } public function withIterable(iterable $enumerators): self { $clone = clone $this; foreach ($enumerators as $enumerator) { $clone->{$this->fnDoSetBit}(($this->enumeration)::get($enumerator)->getOrdinal()); } return $clone; } public function without($enumerator): self { $clone = clone $this; $clone->{$this->fnDoUnsetBit}(($this->enumeration)::get($enumerator)->getOrdinal()); return $clone; } public function withoutIterable(iterable $enumerators): self { $clone = clone $this; foreach ($enumerators as $enumerator) { $clone->{$this->fnDoUnsetBit}(($this->enumeration)::get($enumerator)->getOrdinal()); } return $clone; } public function withUnion(EnumSet $other): self { $clone = clone $this; $clone->setUnion($other); return $clone; } public function union(EnumSet $other): self { return $this->withUnion($other); } public function withIntersect(EnumSet $other): self { $clone = clone $this; $clone->setIntersect($other); return $clone; } public function intersect(EnumSet $other): self { return $this->withIntersect($other); } public function withDiff(EnumSet $other): self { $clone = clone $this; $clone->setDiff($other); return $clone; } public function diff(EnumSet $other): self { return $this->withDiff($other); } public function withSymDiff(EnumSet $other): self { $clone = clone $this; $clone->setSymDiff($other); return $clone; } public function symDiff(EnumSet $other): self { return $this->withSymDiff($other); } public function withBinaryBitsetLe(string $bitset): self { $clone = clone $this; $clone->{$this->fnDoSetBinaryBitsetLe}($bitset); return $clone; } public function withBinaryBitsetBe(string $bitset): self { $clone = $this; $clone->{$this->fnDoSetBinaryBitsetLe}(\strrev($bitset)); return $clone; } public function withBit(int $ordinal, bool $bit): self { $clone = clone $this; $clone->setBit($ordinal, $bit); return $clone; } public function has($enumerator): bool { return $this->{$this->fnDoGetBit}(($this->enumeration)::get($enumerator)->getOrdinal()); } public function contains($enumerator): bool { return $this->has($enumerator); } public function getIterator(): Iterator { return $this->{$this->fnDoGetIterator}(); } private function doGetIteratorBin() { $bitset = $this->bitset; $byteLen = \strlen($bitset); for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) { if ($bitset[$bytePos] === "\0") { continue; } $ord = \ord($bitset[$bytePos]); for ($bitPos = 0; $bitPos < 8; ++$bitPos) { if ($ord & (1 << $bitPos)) { $ordinal = $bytePos * 8 + $bitPos; yield $ordinal => ($this->enumeration)::byOrdinal($ordinal); } } } } private function doGetIteratorInt() { $bitset = $this->bitset; $count = $this->enumerationCount; for ($ordinal = 0; $ordinal < $count; ++$ordinal) { if ($bitset & (1 << $ordinal)) { yield $ordinal => ($this->enumeration)::byOrdinal($ordinal); } } } public function count(): int { return $this->{$this->fnDoCount}(); } private function doCountBin() { $bitset = $this->bitset; $count = 0; $byteLen = \strlen($bitset); for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) { if ($bitset[$bytePos] === "\0") { continue; } $ord = \ord($bitset[$bytePos]); if ($ord & 0b00000001) ++$count; if ($ord & 0b00000010) ++$count; if ($ord & 0b00000100) ++$count; if ($ord & 0b00001000) ++$count; if ($ord & 0b00010000) ++$count; if ($ord & 0b00100000) ++$count; if ($ord & 0b01000000) ++$count; if ($ord & 0b10000000) ++$count; } return $count; } private function doCountInt() { $bitset = $this->bitset; $count = 0; if ($bitset < 0) { $count = 1; $bitset = $bitset & \PHP_INT_MAX; } $phpIntBitSize = \PHP_INT_SIZE * 8; for ($bitPos = 0; $bitPos < $phpIntBitSize; $bitPos += 8) { $bitChk = 0xff << $bitPos; $byte = $bitset & $bitChk; if ($byte) { $byte = $byte >> $bitPos; if ($byte & 0b00000001) ++$count; if ($byte & 0b00000010) ++$count; if ($byte & 0b00000100) ++$count; if ($byte & 0b00001000) ++$count; if ($byte & 0b00010000) ++$count; if ($byte & 0b00100000) ++$count; if ($byte & 0b01000000) ++$count; if ($byte & 0b10000000) ++$count; } if ($bitset <= $bitChk) { break; } } return $count; } public function isEqual(EnumSet $other): bool { return $this->enumeration === $other->enumeration && $this->bitset === $other->bitset; } public function isSubset(EnumSet $other): bool { return $this->enumeration === $other->enumeration && ($this->bitset & $other->bitset) === $this->bitset; } public function isSuperset(EnumSet $other): bool { return $this->enumeration === $other->enumeration && ($this->bitset | $other->bitset) === $this->bitset; } public function isEmpty(): bool { return $this->bitset === $this->emptyBitset; } public function getOrdinals(): array { return $this->{$this->fnDoGetOrdinals}(); } private function doGetOrdinalsBin() { $bitset = $this->bitset; $ordinals = []; $byteLen = \strlen($bitset); for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) { if ($bitset[$bytePos] === "\0") { continue; } $ord = \ord($bitset[$bytePos]); for ($bitPos = 0; $bitPos < 8; ++$bitPos) { if ($ord & (1 << $bitPos)) { $ordinals[] = $bytePos * 8 + $bitPos; } } } return $ordinals; } private function doGetOrdinalsInt() { $bitset = $this->bitset; $ordinals = []; $count = $this->enumerationCount; for ($ordinal = 0; $ordinal < $count; ++$ordinal) { if ($bitset & (1 << $ordinal)) { $ordinals[] = $ordinal; } } return $ordinals; } public function getValues(): array { $enumeration = $this->enumeration; $values = []; foreach ($this->getOrdinals() as $ord) { $values[] = $enumeration::byOrdinal($ord)->getValue(); } return $values; } public function getNames(): array { $enumeration = $this->enumeration; $names = []; foreach ($this->getOrdinals() as $ord) { $names[] = $enumeration::byOrdinal($ord)->getName(); } return $names; } public function getEnumerators(): array { $enumeration = $this->enumeration; $enumerators = []; foreach ($this->getOrdinals() as $ord) { $enumerators[] = $enumeration::byOrdinal($ord); } return $enumerators; } public function getBinaryBitsetLe(): string { return $this->{$this->fnDoGetBinaryBitsetLe}(); } private function doGetBinaryBitsetLeBin() { $bitset = $this->bitset; return $bitset; } private function doGetBinaryBitsetLeInt() { $bin = \pack(\PHP_INT_SIZE === 8 ? 'P' : 'V', $this->bitset); return \substr($bin, 0, (int)\ceil($this->enumerationCount / 8)); } public function getBinaryBitsetBe(): string { return \strrev($this->getBinaryBitsetLe()); } public function getBit(int $ordinal): bool { if ($ordinal < 0 || $ordinal > $this->enumerationCount) { throw new InvalidArgumentException("Ordinal number must be between 0 and {$this->enumerationCount}"); } return $this->{$this->fnDoGetBit}($ordinal); } private function doGetBitBin($ordinal) { $bitset = $this->bitset; return (\ord($bitset[(int) ($ordinal / 8)]) & 1 << ($ordinal % 8)) !== 0; } private function doGetBitInt($ordinal) { $bitset = $this->bitset; return (bool)($bitset & (1 << $ordinal)); } } log(LogLevel::EMERGENCY, $message, $context); } public function alert($message, array $context = array()) { $this->log(LogLevel::ALERT, $message, $context); } public function critical($message, array $context = array()) { $this->log(LogLevel::CRITICAL, $message, $context); } public function error($message, array $context = array()) { $this->log(LogLevel::ERROR, $message, $context); } public function warning($message, array $context = array()) { $this->log(LogLevel::WARNING, $message, $context); } public function notice($message, array $context = array()) { $this->log(LogLevel::NOTICE, $message, $context); } public function info($message, array $context = array()) { $this->log(LogLevel::INFO, $message, $context); } public function debug($message, array $context = array()) { $this->log(LogLevel::DEBUG, $message, $context); } } logger = $logger; } } log(LogLevel::EMERGENCY, $message, $context); } public function alert($message, array $context = array()) { $this->log(LogLevel::ALERT, $message, $context); } public function critical($message, array $context = array()) { $this->log(LogLevel::CRITICAL, $message, $context); } public function error($message, array $context = array()) { $this->log(LogLevel::ERROR, $message, $context); } public function warning($message, array $context = array()) { $this->log(LogLevel::WARNING, $message, $context); } public function notice($message, array $context = array()) { $this->log(LogLevel::NOTICE, $message, $context); } public function info($message, array $context = array()) { $this->log(LogLevel::INFO, $message, $context); } public function debug($message, array $context = array()) { $this->log(LogLevel::DEBUG, $message, $context); } abstract public function log($level, $message, array $context = array()); } assertInstanceOf('Psr\Log\LoggerInterface', $this->getLogger()); } public function testLogsAtAllLevels($level, $message) { $logger = $this->getLogger(); $logger->{$level}($message, array('user' => 'Bob')); $logger->log($level, $message, array('user' => 'Bob')); $expected = array( $level.' message of level '.$level.' with context: Bob', $level.' message of level '.$level.' with context: Bob', ); $this->assertEquals($expected, $this->getLogs()); } public function provideLevelsAndMessages() { return array( LogLevel::EMERGENCY => array(LogLevel::EMERGENCY, 'message of level emergency with context: {user}'), LogLevel::ALERT => array(LogLevel::ALERT, 'message of level alert with context: {user}'), LogLevel::CRITICAL => array(LogLevel::CRITICAL, 'message of level critical with context: {user}'), LogLevel::ERROR => array(LogLevel::ERROR, 'message of level error with context: {user}'), LogLevel::WARNING => array(LogLevel::WARNING, 'message of level warning with context: {user}'), LogLevel::NOTICE => array(LogLevel::NOTICE, 'message of level notice with context: {user}'), LogLevel::INFO => array(LogLevel::INFO, 'message of level info with context: {user}'), LogLevel::DEBUG => array(LogLevel::DEBUG, 'message of level debug with context: {user}'), ); } public function testThrowsOnInvalidLevel() { $logger = $this->getLogger(); $logger->log('invalid level', 'Foo'); } public function testContextReplacement() { $logger = $this->getLogger(); $logger->info('{Message {nothing} {user} {foo.bar} a}', array('user' => 'Bob', 'foo.bar' => 'Bar')); $expected = array('info {Message {nothing} Bob Bar a}'); $this->assertEquals($expected, $this->getLogs()); } public function testObjectCastToString() { if (method_exists($this, 'createPartialMock')) { $dummy = $this->createPartialMock('Psr\Log\Test\DummyTest', array('__toString')); } else { $dummy = $this->getMock('Psr\Log\Test\DummyTest', array('__toString')); } $dummy->expects($this->once()) ->method('__toString') ->will($this->returnValue('DUMMY')); $this->getLogger()->warning($dummy); $expected = array('warning DUMMY'); $this->assertEquals($expected, $this->getLogs()); } public function testContextCanContainAnything() { $closed = fopen('php://memory', 'r'); fclose($closed); $context = array( 'bool' => true, 'null' => null, 'string' => 'Foo', 'int' => 0, 'float' => 0.5, 'nested' => array('with object' => new DummyTest), 'object' => new \DateTime, 'resource' => fopen('php://memory', 'r'), 'closed' => $closed, ); $this->getLogger()->warning('Crazy context data', $context); $expected = array('warning Crazy context data'); $this->assertEquals($expected, $this->getLogs()); } public function testContextExceptionKeyCanBeExceptionOrOtherValues() { $logger = $this->getLogger(); $logger->warning('Random message', array('exception' => 'oops')); $logger->critical('Uncaught Exception!', array('exception' => new \LogicException('Fail'))); $expected = array( 'warning Random message', 'critical Uncaught Exception!' ); $this->assertEquals($expected, $this->getLogs()); } } $level, 'message' => $message, 'context' => $context, ]; $this->recordsByLevel[$record['level']][] = $record; $this->records[] = $record; } public function hasRecords($level) { return isset($this->recordsByLevel[$level]); } public function hasRecord($record, $level) { if (is_string($record)) { $record = ['message' => $record]; } return $this->hasRecordThatPasses(function ($rec) use ($record) { if ($rec['message'] !== $record['message']) { return false; } if (isset($record['context']) && $rec['context'] !== $record['context']) { return false; } return true; }, $level); } public function hasRecordThatContains($message, $level) { return $this->hasRecordThatPasses(function ($rec) use ($message) { return strpos($rec['message'], $message) !== false; }, $level); } public function hasRecordThatMatches($regex, $level) { return $this->hasRecordThatPasses(function ($rec) use ($regex) { return preg_match($regex, $rec['message']) > 0; }, $level); } public function hasRecordThatPasses(callable $predicate, $level) { if (!isset($this->recordsByLevel[$level])) { return false; } foreach ($this->recordsByLevel[$level] as $i => $rec) { if (call_user_func($predicate, $rec, $i)) { return true; } } return false; } public function __call($method, $args) { if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) { $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3]; $level = strtolower($matches[2]); if (method_exists($this, $genericMethod)) { $args[] = $level; return call_user_func_array([$this, $genericMethod], $args); } } throw new \BadMethodCallException('Call to undefined method ' . get_class($this) . '::' . $method . '()'); } public function reset() { $this->records = []; $this->recordsByLevel = []; } } The MIT License (MIT) Copyright (c) 2012 Jan Sorgalla, Christian Lück, Cees-Jan Kiewiet, Chris Boden Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. promise = new Promise(function ($resolve, $reject): void { $this->resolveCallback = $resolve; $this->rejectCallback = $reject; }, $canceller); } public function promise(): PromiseInterface { return $this->promise; } public function resolve($value): void { ($this->resolveCallback)($value); } public function reject(\Throwable $reason): void { ($this->rejectCallback)($reason); } } throwables = $throwables; } public function getThrowables(): array { return $this->throwables; } } started) { return; } $this->started = true; $this->drain(); } public function enqueue($cancellable): void { if (!\is_object($cancellable) || !\method_exists($cancellable, 'then') || !\method_exists($cancellable, 'cancel')) { return; } $length = \array_push($this->queue, $cancellable); if ($this->started && 1 === $length) { $this->drain(); } } private function drain(): void { for ($i = \key($this->queue); isset($this->queue[$i]); $i++) { $cancellable = $this->queue[$i]; assert(\method_exists($cancellable, 'cancel')); $exception = null; try { $cancellable->cancel(); } catch (\Throwable $exception) { } unset($this->queue[$i]); if ($exception) { throw $exception; } } $this->queue = []; } } value = $value; } public function then(?callable $onFulfilled = null, ?callable $onRejected = null): PromiseInterface { if (null === $onFulfilled) { return $this; } try { $result = $onFulfilled($this->value); return resolve($result); } catch (\Throwable $exception) { return new RejectedPromise($exception); } } public function catch(callable $onRejected): PromiseInterface { return $this; } public function finally(callable $onFulfilledOrRejected): PromiseInterface { return $this->then(function ($value) use ($onFulfilledOrRejected): PromiseInterface { return resolve($onFulfilledOrRejected())->then(function () use ($value) { return $value; }); }); } public function cancel(): void { } public function otherwise(callable $onRejected): PromiseInterface { return $this->catch($onRejected); } public function always(callable $onFulfilledOrRejected): PromiseInterface { return $this->finally($onFulfilledOrRejected); } } reason = $reason; } public function __destruct() { if ($this->handled) { return; } $handler = set_rejection_handler(null); if ($handler === null) { $message = 'Unhandled promise rejection with ' . $this->reason; \error_log($message); return; } try { $handler($this->reason); } catch (\Throwable $e) { \preg_match('/^([^:\s]++)(.*+)$/sm', (string) $e, $match); \assert(isset($match[1], $match[2])); $message = 'Fatal error: Uncaught ' . $match[1] . ' from unhandled promise rejection handler' . $match[2]; \error_log($message); exit(255); } } public function then(?callable $onFulfilled = null, ?callable $onRejected = null): PromiseInterface { if (null === $onRejected) { return $this; } $this->handled = true; try { return resolve($onRejected($this->reason)); } catch (\Throwable $exception) { return new RejectedPromise($exception); } } public function catch(callable $onRejected): PromiseInterface { if (!_checkTypehint($onRejected, $this->reason)) { return $this; } return $this->then(null, $onRejected); } public function finally(callable $onFulfilledOrRejected): PromiseInterface { return $this->then(null, function (\Throwable $reason) use ($onFulfilledOrRejected): PromiseInterface { return resolve($onFulfilledOrRejected())->then(function () use ($reason): PromiseInterface { return new RejectedPromise($reason); }); }); } public function cancel(): void { $this->handled = true; } public function otherwise(callable $onRejected): PromiseInterface { return $this->catch($onRejected); } public function always(callable $onFulfilledOrRejected): PromiseInterface { return $this->finally($onFulfilledOrRejected); } } canceller = $canceller; $cb = $resolver; $resolver = $canceller = null; $this->call($cb); } public function then(?callable $onFulfilled = null, ?callable $onRejected = null): PromiseInterface { if (null !== $this->result) { return $this->result->then($onFulfilled, $onRejected); } if (null === $this->canceller) { return new static($this->resolver($onFulfilled, $onRejected)); } $parent = $this; ++$parent->requiredCancelRequests; return new static( $this->resolver($onFulfilled, $onRejected), static function () use (&$parent): void { assert($parent instanceof self); --$parent->requiredCancelRequests; if ($parent->requiredCancelRequests <= 0) { $parent->cancel(); } $parent = null; } ); } public function catch(callable $onRejected): PromiseInterface { return $this->then(null, static function (\Throwable $reason) use ($onRejected) { if (!_checkTypehint($onRejected, $reason)) { return new RejectedPromise($reason); } return $onRejected($reason); }); } public function finally(callable $onFulfilledOrRejected): PromiseInterface { return $this->then(static function ($value) use ($onFulfilledOrRejected): PromiseInterface { return resolve($onFulfilledOrRejected())->then(function () use ($value) { return $value; }); }, static function (\Throwable $reason) use ($onFulfilledOrRejected): PromiseInterface { return resolve($onFulfilledOrRejected())->then(function () use ($reason): RejectedPromise { return new RejectedPromise($reason); }); }); } public function cancel(): void { $this->cancelled = true; $canceller = $this->canceller; $this->canceller = null; $parentCanceller = null; if (null !== $this->result) { if ($this->result instanceof RejectedPromise) { $this->result->cancel(); } $root = $this->unwrap($this->result); if (!$root instanceof self || null !== $root->result) { return; } $root->requiredCancelRequests--; if ($root->requiredCancelRequests <= 0) { $parentCanceller = [$root, 'cancel']; } } if (null !== $canceller) { $this->call($canceller); } if ($parentCanceller) { $parentCanceller(); } } public function otherwise(callable $onRejected): PromiseInterface { return $this->catch($onRejected); } public function always(callable $onFulfilledOrRejected): PromiseInterface { return $this->finally($onFulfilledOrRejected); } private function resolver(?callable $onFulfilled = null, ?callable $onRejected = null): callable { return function (callable $resolve, callable $reject) use ($onFulfilled, $onRejected): void { $this->handlers[] = static function (PromiseInterface $promise) use ($onFulfilled, $onRejected, $resolve, $reject): void { $promise = $promise->then($onFulfilled, $onRejected); if ($promise instanceof self && $promise->result === null) { $promise->handlers[] = static function (PromiseInterface $promise) use ($resolve, $reject): void { $promise->then($resolve, $reject); }; } else { $promise->then($resolve, $reject); } }; }; } private function reject(\Throwable $reason): void { if (null !== $this->result) { return; } $this->settle(reject($reason)); } private function settle(PromiseInterface $result): void { $result = $this->unwrap($result); if ($result === $this) { $result = new RejectedPromise( new \LogicException('Cannot resolve a promise with itself.') ); } if ($result instanceof self) { $result->requiredCancelRequests++; } else { $this->canceller = null; } $handlers = $this->handlers; $this->handlers = []; $this->result = $result; foreach ($handlers as $handler) { $handler($result); } if ($this->cancelled && $result instanceof RejectedPromise) { $result->cancel(); } } private function unwrap(PromiseInterface $promise): PromiseInterface { while ($promise instanceof self && null !== $promise->result) { $promise = $promise->result; } return $promise; } private function call(callable $cb): void { $callback = $cb; $cb = null; if (\is_array($callback)) { $ref = new \ReflectionMethod($callback[0], $callback[1]); } elseif (\is_object($callback) && !$callback instanceof \Closure) { $ref = new \ReflectionMethod($callback, '__invoke'); } else { assert($callback instanceof \Closure || \is_string($callback)); $ref = new \ReflectionFunction($callback); } $args = $ref->getNumberOfParameters(); try { if ($args === 0) { $callback(); } else { $target =& $this; $callback( static function ($value) use (&$target): void { if ($target !== null) { $target->settle(resolve($value)); $target = null; } }, static function (\Throwable $reason) use (&$target): void { if ($target !== null) { $target->reject($reason); $target = null; } } ); } } catch (\Throwable $e) { $target = null; $this->reject($e); } } } then($resolve, $reject); }, $canceller); } return new FulfilledPromise($promiseOrValue); } function reject(\Throwable $reason): PromiseInterface { return new RejectedPromise($reason); } function all(iterable $promisesOrValues): PromiseInterface { $cancellationQueue = new Internal\CancellationQueue(); return new Promise(function (callable $resolve, callable $reject) use ($promisesOrValues, $cancellationQueue): void { $toResolve = 0; $continue = true; $values = []; foreach ($promisesOrValues as $i => $promiseOrValue) { $cancellationQueue->enqueue($promiseOrValue); $values[$i] = null; ++$toResolve; resolve($promiseOrValue)->then( function ($value) use ($i, &$values, &$toResolve, &$continue, $resolve): void { $values[$i] = $value; if (0 === --$toResolve && !$continue) { $resolve($values); } }, function (\Throwable $reason) use (&$continue, $reject): void { $continue = false; $reject($reason); } ); if (!$continue && !\is_array($promisesOrValues)) { break; } } $continue = false; if ($toResolve === 0) { $resolve($values); } }, $cancellationQueue); } function race(iterable $promisesOrValues): PromiseInterface { $cancellationQueue = new Internal\CancellationQueue(); return new Promise(function (callable $resolve, callable $reject) use ($promisesOrValues, $cancellationQueue): void { $continue = true; foreach ($promisesOrValues as $promiseOrValue) { $cancellationQueue->enqueue($promiseOrValue); resolve($promiseOrValue)->then($resolve, $reject)->finally(function () use (&$continue): void { $continue = false; }); if (!$continue && !\is_array($promisesOrValues)) { break; } } }, $cancellationQueue); } function any(iterable $promisesOrValues): PromiseInterface { $cancellationQueue = new Internal\CancellationQueue(); return new Promise(function (callable $resolve, callable $reject) use ($promisesOrValues, $cancellationQueue): void { $toReject = 0; $continue = true; $reasons = []; foreach ($promisesOrValues as $i => $promiseOrValue) { $cancellationQueue->enqueue($promiseOrValue); ++$toReject; resolve($promiseOrValue)->then( function ($value) use ($resolve, &$continue): void { $continue = false; $resolve($value); }, function (\Throwable $reason) use ($i, &$reasons, &$toReject, $reject, &$continue): void { $reasons[$i] = $reason; if (0 === --$toReject && !$continue) { $reject(new CompositeException( $reasons, 'All promises rejected.' )); } } ); if (!$continue && !\is_array($promisesOrValues)) { break; } } $continue = false; if ($toReject === 0 && !$reasons) { $reject(new Exception\LengthException( 'Must contain at least 1 item but contains only 0 items.' )); } elseif ($toReject === 0) { $reject(new CompositeException( $reasons, 'All promises rejected.' )); } }, $cancellationQueue); } function set_rejection_handler(?callable $callback): ?callable { static $current = null; $previous = $current; $current = $callback; return $previous; } function _checkTypehint(callable $callback, \Throwable $reason): bool { if (\is_array($callback)) { $callbackReflection = new \ReflectionMethod($callback[0], $callback[1]); } elseif (\is_object($callback) && !$callback instanceof \Closure) { $callbackReflection = new \ReflectionMethod($callback, '__invoke'); } else { assert($callback instanceof \Closure || \is_string($callback)); $callbackReflection = new \ReflectionFunction($callback); } $parameters = $callbackReflection->getParameters(); if (!isset($parameters[0])) { return true; } $expectedException = $parameters[0]; $type = $expectedException->getType(); $isTypeUnion = true; $types = []; switch (true) { case $type === null: break; case $type instanceof \ReflectionNamedType: $types = [$type]; break; case $type instanceof \ReflectionIntersectionType: $isTypeUnion = false; case $type instanceof \ReflectionUnionType: $types = $type->getTypes(); break; default: throw new \LogicException('Unexpected return value of ReflectionParameter::getType'); } if (empty($types)) { return true; } foreach ($types as $type) { if ($type instanceof \ReflectionIntersectionType) { foreach ($type->getTypes() as $typeToMatch) { assert($typeToMatch instanceof \ReflectionNamedType); $name = $typeToMatch->getName(); if (!($matches = (!$typeToMatch->isBuiltin() && $reason instanceof $name))) { break; } } assert(isset($matches)); } else { assert($type instanceof \ReflectionNamedType); $name = $type->getName(); $matches = !$type->isBuiltin() && $reason instanceof $name; } if ($matches) { if ($isTypeUnion) { return true; } } else { if (!$isTypeUnion) { return false; } } } return $isTypeUnion ? false : true; } details['key']; } public function getDetails() { return $this->details; } } 2, 'JSONString' => 3, 'STRING' => 4, 'JSONNumber' => 5, 'NUMBER' => 6, 'JSONNullLiteral' => 7, 'NULL' => 8, 'JSONBooleanLiteral' => 9, 'TRUE' => 10, 'FALSE' => 11, 'JSONText' => 12, 'JSONValue' => 13, 'EOF' => 14, 'JSONObject' => 15, 'JSONArray' => 16, '{' => 17, '}' => 18, 'JSONMemberList' => 19, 'JSONMember' => 20, ':' => 21, ',' => 22, '[' => 23, ']' => 24, 'JSONElementList' => 25, '$accept' => 0, '$end' => 1, ); private $terminals_ = array( 2 => "error", 4 => "STRING", 6 => "NUMBER", 8 => "NULL", 10 => "TRUE", 11 => "FALSE", 14 => "EOF", 17 => "{", 18 => "}", 21 => ":", 22 => ",", 23 => "[", 24 => "]", ); private $productions_ = array( 1 => array(3, 1), 2 => array(5, 1), 3 => array(7, 1), 4 => array(9, 1), 5 => array(9, 1), 6 => array(12, 2), 7 => array(13, 1), 8 => array(13, 1), 9 => array(13, 1), 10 => array(13, 1), 11 => array(13, 1), 12 => array(13, 1), 13 => array(15, 2), 14 => array(15, 3), 15 => array(20, 3), 16 => array(19, 1), 17 => array(19, 3), 18 => array(16, 2), 19 => array(16, 3), 20 => array(25, 1), 21 => array(25, 3) ); private $table = array( 0 => array( 3 => 5, 4 => array(1,12), 5 => 6, 6 => array(1,13), 7 => 3, 8 => array(1,9), 9 => 4, 10 => array(1,10), 11 => array(1,11), 12 => 1, 13 => 2, 15 => 7, 16 => 8, 17 => array(1,14), 23 => array(1,15)), 1 => array( 1 => array(3)), 2 => array( 14 => array(1,16)), 3 => array( 14 => array(2,7), 18 => array(2,7), 22 => array(2,7), 24 => array(2,7)), 4 => array( 14 => array(2,8), 18 => array(2,8), 22 => array(2,8), 24 => array(2,8)), 5 => array( 14 => array(2,9), 18 => array(2,9), 22 => array(2,9), 24 => array(2,9)), 6 => array( 14 => array(2,10), 18 => array(2,10), 22 => array(2,10), 24 => array(2,10)), 7 => array( 14 => array(2,11), 18 => array(2,11), 22 => array(2,11), 24 => array(2,11)), 8 => array( 14 => array(2,12), 18 => array(2,12), 22 => array(2,12), 24 => array(2,12)), 9 => array( 14 => array(2,3), 18 => array(2,3), 22 => array(2,3), 24 => array(2,3)), 10 => array( 14 => array(2,4), 18 => array(2,4), 22 => array(2,4), 24 => array(2,4)), 11 => array( 14 => array(2,5), 18 => array(2,5), 22 => array(2,5), 24 => array(2,5)), 12 => array( 14 => array(2,1), 18 => array(2,1), 21 => array(2,1), 22 => array(2,1), 24 => array(2,1)), 13 => array( 14 => array(2,2), 18 => array(2,2), 22 => array(2,2), 24 => array(2,2)), 14 => array( 3 => 20, 4 => array(1,12), 18 => array(1,17), 19 => 18, 20 => 19 ), 15 => array( 3 => 5, 4 => array(1,12), 5 => 6, 6 => array(1,13), 7 => 3, 8 => array(1,9), 9 => 4, 10 => array(1,10), 11 => array(1,11), 13 => 23, 15 => 7, 16 => 8, 17 => array(1,14), 23 => array(1,15), 24 => array(1,21), 25 => 22 ), 16 => array( 1 => array(2,6)), 17 => array( 14 => array(2,13), 18 => array(2,13), 22 => array(2,13), 24 => array(2,13)), 18 => array( 18 => array(1,24), 22 => array(1,25)), 19 => array( 18 => array(2,16), 22 => array(2,16)), 20 => array( 21 => array(1,26)), 21 => array( 14 => array(2,18), 18 => array(2,18), 22 => array(2,18), 24 => array(2,18)), 22 => array( 22 => array(1,28), 24 => array(1,27)), 23 => array( 22 => array(2,20), 24 => array(2,20)), 24 => array( 14 => array(2,14), 18 => array(2,14), 22 => array(2,14), 24 => array(2,14)), 25 => array( 3 => 20, 4 => array(1,12), 20 => 29 ), 26 => array( 3 => 5, 4 => array(1,12), 5 => 6, 6 => array(1,13), 7 => 3, 8 => array(1,9), 9 => 4, 10 => array(1,10), 11 => array(1,11), 13 => 30, 15 => 7, 16 => 8, 17 => array(1,14), 23 => array(1,15)), 27 => array( 14 => array(2,19), 18 => array(2,19), 22 => array(2,19), 24 => array(2,19)), 28 => array( 3 => 5, 4 => array(1,12), 5 => 6, 6 => array(1,13), 7 => 3, 8 => array(1,9), 9 => 4, 10 => array(1,10), 11 => array(1,11), 13 => 31, 15 => 7, 16 => 8, 17 => array(1,14), 23 => array(1,15)), 29 => array( 18 => array(2,17), 22 => array(2,17)), 30 => array( 18 => array(2,15), 22 => array(2,15)), 31 => array( 22 => array(2,21), 24 => array(2,21)), ); private $defaultActions = array( 16 => array(2, 6) ); public function lint($input, $flags = 0) { try { $this->parse($input, $flags); } catch (ParsingException $e) { return $e; } return null; } public function parse($input, $flags = 0) { if (($flags & self::ALLOW_DUPLICATE_KEYS_TO_ARRAY) && ($flags & self::ALLOW_DUPLICATE_KEYS)) { throw new \InvalidArgumentException('Only one of ALLOW_DUPLICATE_KEYS and ALLOW_DUPLICATE_KEYS_TO_ARRAY can be used, you passed in both.'); } $this->failOnBOM($input); $this->flags = $flags; $this->stack = array(0); $this->vstack = array(null); $this->lstack = array(); $yytext = ''; $yylineno = 0; $yyleng = 0; $recovering = 0; $this->lexer = new Lexer($flags); $this->lexer->setInput($input); $yyloc = $this->lexer->yylloc; $this->lstack[] = $yyloc; $symbol = null; $preErrorSymbol = null; $action = null; $a = null; $r = null; $p = null; $len = null; $newState = null; $expected = null; $errStr = null; while (true) { $state = $this->stack[\count($this->stack)-1]; if (isset($this->defaultActions[$state])) { $action = $this->defaultActions[$state]; } else { if ($symbol === null) { $symbol = $this->lexer->lex(); } $action = isset($this->table[$state][$symbol]) ? $this->table[$state][$symbol] : false; } if (!$action || !$action[0]) { assert(isset($symbol)); if (!$recovering) { $expected = array(); foreach ($this->table[$state] as $p => $ignore) { if (isset($this->terminals_[$p]) && $p > 2) { $expected[] = "'" . $this->terminals_[$p] . "'"; } } $message = null; if (\in_array("'STRING'", $expected) && \in_array(substr($this->lexer->match, 0, 1), array('"', "'"))) { $message = "Invalid string"; if ("'" === substr($this->lexer->match, 0, 1)) { $message .= ", it appears you used single quotes instead of double quotes"; } elseif (preg_match('{".+?(\\\\[^"bfnrt/\\\\u](...)?)}', $this->lexer->getFullUpcomingInput(), $match)) { $message .= ", it appears you have an unescaped backslash at: ".$match[1]; } elseif (preg_match('{"(?:[^"]+|\\\\")*$}m', $this->lexer->getFullUpcomingInput())) { $message .= ", it appears you forgot to terminate a string, or attempted to write a multiline string which is invalid"; } } $errStr = 'Parse error on line ' . ($yylineno+1) . ":\n"; $errStr .= $this->lexer->showPosition() . "\n"; if ($message) { $errStr .= $message; } else { $errStr .= (\count($expected) > 1) ? "Expected one of: " : "Expected: "; $errStr .= implode(', ', $expected); } if (',' === substr(trim($this->lexer->getPastInput()), -1)) { $errStr .= " - It appears you have an extra trailing comma"; } $this->parseError($errStr, array( 'text' => $this->lexer->match, 'token' => isset($this->terminals_[$symbol]) ? $this->terminals_[$symbol] : $symbol, 'line' => $this->lexer->yylineno, 'loc' => $yyloc, 'expected' => $expected, )); } if ($recovering == 3) { if ($symbol === Lexer::EOF) { throw new ParsingException($errStr ?: 'Parsing halted.'); } $yyleng = $this->lexer->yyleng; $yytext = $this->lexer->yytext; $yylineno = $this->lexer->yylineno; $yyloc = $this->lexer->yylloc; $symbol = $this->lexer->lex(); } while (true) { if (\array_key_exists(Lexer::T_ERROR, $this->table[$state])) { break; } if ($state == 0) { throw new ParsingException($errStr ?: 'Parsing halted.'); } $this->popStack(1); $state = $this->stack[\count($this->stack)-1]; } $preErrorSymbol = $symbol; $symbol = Lexer::T_ERROR; $state = $this->stack[\count($this->stack)-1]; $action = isset($this->table[$state][Lexer::T_ERROR]) ? $this->table[$state][Lexer::T_ERROR] : false; if ($action === false) { throw new \LogicException('No table value found for '.$state.' => '.Lexer::T_ERROR); } $recovering = 3; } if (\is_array($action[0]) && \count($action) > 1) { throw new ParsingException('Parse Error: multiple actions possible at state: ' . $state . ', token: ' . $symbol); } switch ($action[0]) { case 1: assert(isset($symbol)); $this->stack[] = $symbol; $this->vstack[] = $this->lexer->yytext; $this->lstack[] = $this->lexer->yylloc; $this->stack[] = $action[1]; $symbol = null; if (!$preErrorSymbol) { $yyleng = $this->lexer->yyleng; $yytext = $this->lexer->yytext; $yylineno = $this->lexer->yylineno; $yyloc = $this->lexer->yylloc; if ($recovering > 0) { $recovering--; } } else { $symbol = $preErrorSymbol; $preErrorSymbol = null; } break; case 2: $len = $this->productions_[$action[1]][1]; $currentToken = $this->vstack[\count($this->vstack) - $len]; $position = array( 'first_line' => $this->lstack[\count($this->lstack) - ($len ?: 1)]['first_line'], 'last_line' => $this->lstack[\count($this->lstack) - 1]['last_line'], 'first_column' => $this->lstack[\count($this->lstack) - ($len ?: 1)]['first_column'], 'last_column' => $this->lstack[\count($this->lstack) - 1]['last_column'], ); list($newToken, $actionResult) = $this->performAction($currentToken, $yytext, $yyleng, $yylineno, $action[1]); if (!$actionResult instanceof Undefined) { return $actionResult; } if ($len) { $this->popStack($len); } $this->stack[] = $this->productions_[$action[1]][0]; $this->vstack[] = $newToken; $this->lstack[] = $position; $newState = $this->table[$this->stack[\count($this->stack)-2]][$this->stack[\count($this->stack)-1]]; $this->stack[] = $newState; break; case 3: return true; } } } protected function parseError($str, $hash = null) { throw new ParsingException($str, $hash ?: array()); } private function performAction($currentToken, $yytext, $yyleng, $yylineno, $yystate) { $token = $currentToken; $len = \count($this->vstack) - 1; switch ($yystate) { case 1: $yytext = preg_replace_callback('{(?:\\\\["bfnrt/\\\\]|\\\\u[a-fA-F0-9]{4})}', array($this, 'stringInterpolation'), $yytext); $token = $yytext; break; case 2: if (strpos($yytext, 'e') !== false || strpos($yytext, 'E') !== false) { $token = \floatval($yytext); } else { $token = strpos($yytext, '.') === false ? \intval($yytext) : \floatval($yytext); } break; case 3: $token = null; break; case 4: $token = true; break; case 5: $token = false; break; case 6: $token = $this->vstack[$len-1]; return array($token, $token); case 13: if ($this->flags & self::PARSE_TO_ASSOC) { $token = array(); } else { $token = new stdClass; } break; case 14: $token = $this->vstack[$len-1]; break; case 15: $token = array($this->vstack[$len-2], $this->vstack[$len]); break; case 16: assert(\is_array($this->vstack[$len])); if (PHP_VERSION_ID < 70100) { $property = $this->vstack[$len][0] === '' ? '_empty_' : $this->vstack[$len][0]; } else { $property = $this->vstack[$len][0]; } if ($this->flags & self::PARSE_TO_ASSOC) { $token = array(); $token[$property] = $this->vstack[$len][1]; } else { $token = new stdClass; $token->$property = $this->vstack[$len][1]; } break; case 17: assert(\is_array($this->vstack[$len])); if ($this->flags & self::PARSE_TO_ASSOC) { assert(\is_array($this->vstack[$len-2])); $token =& $this->vstack[$len-2]; $key = $this->vstack[$len][0]; if (($this->flags & self::DETECT_KEY_CONFLICTS) && isset($this->vstack[$len-2][$key])) { $errStr = 'Parse error on line ' . ($yylineno+1) . ":\n"; $errStr .= $this->lexer->showPosition() . "\n"; $errStr .= "Duplicate key: ".$this->vstack[$len][0]; throw new DuplicateKeyException($errStr, $this->vstack[$len][0], array('line' => $yylineno+1)); } if (($this->flags & self::ALLOW_DUPLICATE_KEYS) && isset($this->vstack[$len-2][$key])) { $duplicateCount = 1; do { $duplicateKey = $key . '.' . $duplicateCount++; } while (isset($this->vstack[$len-2][$duplicateKey])); $this->vstack[$len-2][$duplicateKey] = $this->vstack[$len][1]; } elseif (($this->flags & self::ALLOW_DUPLICATE_KEYS_TO_ARRAY) && isset($this->vstack[$len-2][$key])) { if (!isset($this->vstack[$len-2][$key]['__duplicates__']) || !is_array($this->vstack[$len-2][$key]['__duplicates__'])) { $this->vstack[$len-2][$key] = array('__duplicates__' => array($this->vstack[$len-2][$key])); } $this->vstack[$len-2][$key]['__duplicates__'][] = $this->vstack[$len][1]; } else { $this->vstack[$len-2][$key] = $this->vstack[$len][1]; } } else { assert($this->vstack[$len-2] instanceof stdClass); $token = $this->vstack[$len-2]; if (PHP_VERSION_ID < 70100) { $key = $this->vstack[$len][0] === '' ? '_empty_' : $this->vstack[$len][0]; } else { $key = $this->vstack[$len][0]; } if (($this->flags & self::DETECT_KEY_CONFLICTS) && isset($this->vstack[$len-2]->$key)) { $errStr = 'Parse error on line ' . ($yylineno+1) . ":\n"; $errStr .= $this->lexer->showPosition() . "\n"; $errStr .= "Duplicate key: ".$this->vstack[$len][0]; throw new DuplicateKeyException($errStr, $this->vstack[$len][0], array('line' => $yylineno+1)); } if (($this->flags & self::ALLOW_DUPLICATE_KEYS) && isset($this->vstack[$len-2]->$key)) { $duplicateCount = 1; do { $duplicateKey = $key . '.' . $duplicateCount++; } while (isset($this->vstack[$len-2]->$duplicateKey)); $this->vstack[$len-2]->$duplicateKey = $this->vstack[$len][1]; } elseif (($this->flags & self::ALLOW_DUPLICATE_KEYS_TO_ARRAY) && isset($this->vstack[$len-2]->$key)) { if (!isset($this->vstack[$len-2]->$key->__duplicates__)) { $this->vstack[$len-2]->$key = (object) array('__duplicates__' => array($this->vstack[$len-2]->$key)); } $this->vstack[$len-2]->$key->__duplicates__[] = $this->vstack[$len][1]; } else { $this->vstack[$len-2]->$key = $this->vstack[$len][1]; } } break; case 18: $token = array(); break; case 19: $token = $this->vstack[$len-1]; break; case 20: $token = array($this->vstack[$len]); break; case 21: assert(\is_array($this->vstack[$len-2])); $this->vstack[$len-2][] = $this->vstack[$len]; $token = $this->vstack[$len-2]; break; } return array($token, new Undefined()); } private function stringInterpolation($match) { switch ($match[0]) { case '\\\\': return '\\'; case '\"': return '"'; case '\b': return \chr(8); case '\f': return \chr(12); case '\n': return "\n"; case '\r': return "\r"; case '\t': return "\t"; case '\/': return "/"; default: return html_entity_decode('&#x'.ltrim(substr($match[0], 2), '0').';', ENT_QUOTES, 'UTF-8'); } } private function popStack($n) { $this->stack = \array_slice($this->stack, 0, - (2 * $n)); $this->vstack = \array_slice($this->vstack, 0, - $n); $this->lstack = \array_slice($this->lstack, 0, - $n); } private function failOnBOM($input) { $bom = "\xEF\xBB\xBF"; if (substr($input, 0, 3) === $bom) { $this->parseError("BOM detected, make sure your input does not include a Unicode Byte-Order-Mark"); } } } '/\G\s*\n\r?/', 1 => '/\G\s+/', 2 => '/\G-?([0-9]|[1-9][0-9]+)(\.[0-9]+)?([eE][+-]?[0-9]+)?\b/', 3 => '{\G"(?>\\\\["bfnrt/\\\\]|\\\\u[a-fA-F0-9]{4}|[^\0-\x1f\\\\"]++)*+"}', 4 => '/\G\{/', 5 => '/\G\}/', 6 => '/\G\[/', 7 => '/\G\]/', 8 => '/\G,/', 9 => '/\G:/', 10 => '/\Gtrue\b/', 11 => '/\Gfalse\b/', 12 => '/\Gnull\b/', 13 => '/\G$/', 14 => '/\G\/\//', 15 => '/\G\/\*/', 16 => '/\G\*\//', 17 => '/\G./', ); private $input; private $more; private $done; private $offset; private $flags; public $match; public $yylineno; public $yyleng; public $yytext; public $yylloc; public function __construct($flags = 0) { $this->flags = $flags; } public function lex() { while (true) { $symbol = $this->next(); switch ($symbol) { case self::T_SKIP_WHITESPACE: case self::T_BREAK_LINE: break; case self::T_COMMENT: case self::T_OPEN_COMMENT: if (!($this->flags & JsonParser::ALLOW_COMMENTS)) { $this->parseError('Lexical error on line ' . ($this->yylineno+1) . ". Comments are not allowed.\n" . $this->showPosition()); } $this->skipUntil($symbol === self::T_COMMENT ? self::T_BREAK_LINE : self::T_CLOSE_COMMENT); if ($this->done) { return 14; } break; case self::T_CLOSE_COMMENT: $this->parseError('Lexical error on line ' . ($this->yylineno+1) . ". Unexpected token.\n" . $this->showPosition()); default: return $symbol; } } } public function setInput($input) { $this->input = $input; $this->more = false; $this->done = false; $this->offset = 0; $this->yylineno = $this->yyleng = 0; $this->yytext = $this->match = ''; $this->yylloc = array('first_line' => 1, 'first_column' => 0, 'last_line' => 1, 'last_column' => 0); return $this; } public function showPosition() { if ($this->yylineno === 0 && $this->offset === 1 && $this->match !== '{') { return $this->match.'...' . "\n^"; } $pre = str_replace("\n", '', $this->getPastInput()); $c = str_repeat('-', max(0, \strlen($pre) - 1)); return $pre . str_replace("\n", '', $this->getUpcomingInput()) . "\n" . $c . "^"; } public function getPastInput() { $pastLength = $this->offset - \strlen($this->match); return ($pastLength > 20 ? '...' : '') . substr($this->input, max(0, $pastLength - 20), min(20, $pastLength)); } public function getUpcomingInput() { $next = $this->match; if (\strlen($next) < 20) { $next .= substr($this->input, $this->offset, 20 - \strlen($next)); } return substr($next, 0, 20) . (\strlen($next) > 20 ? '...' : ''); } public function getFullUpcomingInput() { $next = $this->match; if (substr($next, 0, 1) === '"' && substr_count($next, '"') === 1) { $len = \strlen($this->input); if ($len === $this->offset) { $strEnd = $len; } else { $strEnd = min(strpos($this->input, '"', $this->offset + 1) ?: $len, strpos($this->input, "\n", $this->offset + 1) ?: $len); } $next .= substr($this->input, $this->offset, $strEnd - $this->offset); } elseif (\strlen($next) < 20) { $next .= substr($this->input, $this->offset, 20 - \strlen($next)); } return $next; } protected function parseError($str) { throw new ParsingException($str); } private function skipUntil($token) { $symbol = $this->next(); while ($symbol !== $token && false === $this->done) { $symbol = $this->next(); } } private function next() { if ($this->done) { return self::EOF; } if ($this->offset === \strlen($this->input)) { $this->done = true; } $token = null; $match = null; $col = null; $lines = null; if (!$this->more) { $this->yytext = ''; $this->match = ''; } $rulesLen = count($this->rules); for ($i=0; $i < $rulesLen; $i++) { if (preg_match($this->rules[$i], $this->input, $match, 0, $this->offset)) { $lines = explode("\n", $match[0]); array_shift($lines); $lineCount = \count($lines); $this->yylineno += $lineCount; $this->yylloc = array( 'first_line' => $this->yylloc['last_line'], 'last_line' => $this->yylineno+1, 'first_column' => $this->yylloc['last_column'], 'last_column' => $lineCount > 0 ? \strlen($lines[$lineCount - 1]) : $this->yylloc['last_column'] + \strlen($match[0]), ); $this->yytext .= $match[0]; $this->match .= $match[0]; $this->yyleng = \strlen($this->yytext); $this->more = false; $this->offset += \strlen($match[0]); return $this->performAction($i); } } if ($this->offset === \strlen($this->input)) { return self::EOF; } $this->parseError( 'Lexical error on line ' . ($this->yylineno+1) . ". Unrecognized text.\n" . $this->showPosition() ); } private function performAction($rule) { switch ($rule) { case 0: return self::T_BREAK_LINE; case 1: return self::T_SKIP_WHITESPACE; case 2: return 6; case 3: $this->yytext = substr($this->yytext, 1, $this->yyleng-2); return 4; case 4: return 17; case 5: return 18; case 6: return 23; case 7: return 24; case 8: return 22; case 9: return 21; case 10: return 10; case 11: return 11; case 12: return 8; case 13: return 14; case 14: return self::T_COMMENT; case 15: return self::T_OPEN_COMMENT; case 16: return self::T_CLOSE_COMMENT; case 17: return self::T_INVALID; default: throw new \LogicException('Unsupported rule '.$rule); } } } details = $details; parent::__construct($message); } public function getDetails() { return $this->details; } } = 80000) { $format = '%s -l %s'; } else { $format = '"%s -l %s"'; } $command = sprintf($format, $php, $tmpFile); } else { $command = "'".$php."' -l"; } $descriptorspec = array( 0 => array('pipe', 'r'), 1 => array('pipe', 'w'), 2 => array('pipe', 'w') ); $baseLen = strlen(realpath($path)) + 7 + 1; foreach (new \RecursiveIteratorIterator(new \Phar($path)) as $file) { if ($file->isDir()) { continue; } if (substr($file, -4) === '.php') { $filename = (string) $file; if (in_array(substr($filename, $baseLen), $excludedPaths, true)) { continue; } if ($isWindows) { file_put_contents($tmpFile, file_get_contents($filename)); } $process = proc_open($command, $descriptorspec, $pipes); if (is_resource($process)) { if (!$isWindows) { fwrite($pipes[0], file_get_contents($filename)); } fclose($pipes[0]); $stdout = stream_get_contents($pipes[1]); fclose($pipes[1]); $stderr = stream_get_contents($pipes[2]); fclose($pipes[2]); $exitCode = proc_close($process); if ($exitCode !== 0) { if ($isWindows) { $stderr = str_replace($tmpFile, $filename, $stderr); } throw new \UnexpectedValueException('Failed linting '.$file.': '.$stderr); } } else { throw new \RuntimeException('Could not start linter process'); } } } if ($isWindows) { @unlink($tmpFile); } } private static function escapeWindowsPath($path) { if (strpbrk($path, " ()") !== false) { $path = '"'.$path.'"'; } return $path; } } contents = file_get_contents($file); } public function updateTimestamps($timestamp = null) { if ($timestamp instanceof \DateTime || $timestamp instanceof \DateTimeInterface) { $timestamp = $timestamp->getTimestamp(); } elseif (is_string($timestamp)) { $timestamp = strtotime($timestamp); } elseif (!is_int($timestamp)) { $timestamp = strtotime('1984-12-24T00:00:00Z'); } if (!preg_match('{__HALT_COMPILER\(\);(?: +\?>)?\r?\n}', $this->contents, $match, PREG_OFFSET_CAPTURE)) { throw new \RuntimeException('Could not detect the stub\'s end in the phar'); } $pos = $match[0][1] + strlen($match[0][0]); $stubEnd = $pos + $this->readUint($pos, 4); $pos += 4; $numFiles = $this->readUint($pos, 4); $pos += 4; $pos += 2; $pos += 4; $aliasLength = $this->readUint($pos, 4); $pos += 4 + $aliasLength; $metadataLength = $this->readUint($pos, 4); $pos += 4 + $metadataLength; while ($pos < $stubEnd) { $filenameLength = $this->readUint($pos, 4); $pos += 4 + $filenameLength; $pos += 4; $timeStampBytes = pack('L', $timestamp); $this->contents[$pos + 0] = $timeStampBytes[0]; $this->contents[$pos + 1] = $timeStampBytes[1]; $this->contents[$pos + 2] = $timeStampBytes[2]; $this->contents[$pos + 3] = $timeStampBytes[3]; $pos += 4*4; $metadataLength = $this->readUint($pos, 4); $pos += 4 + $metadataLength; $numFiles--; } if ($numFiles !== 0) { throw new \LogicException('All files were not processed, something must have gone wrong'); } } public function save($path, $signatureAlgo) { $pos = $this->determineSignatureBegin(); $algos = array( \Phar::MD5 => 'md5', \Phar::SHA1 => 'sha1', \Phar::SHA256 => 'sha256', \Phar::SHA512 => 'sha512', ); if (!isset($algos[$signatureAlgo])) { throw new \UnexpectedValueException('Invalid hash algorithm given: '.$signatureAlgo.' expected one of Phar::MD5, Phar::SHA1, Phar::SHA256 or Phar::SHA512'); } $algo = $algos[$signatureAlgo]; $signature = hash($algo, substr($this->contents, 0, $pos), true) . pack('L', $signatureAlgo) . 'GBMB'; $this->contents = substr($this->contents, 0, $pos) . $signature; return file_put_contents($path, $this->contents); } private function readUint($pos, $bytes) { $res = unpack('V', substr($this->contents, $pos, $bytes)); return $res[1]; } private function determineSignatureBegin() { if (!preg_match('{__HALT_COMPILER\(\);(?: +\?>)?\r?\n}', $this->contents, $match, PREG_OFFSET_CAPTURE)) { throw new \RuntimeException('Could not detect the stub\'s end in the phar'); } $pos = $match[0][1] + strlen($match[0][0]); $manifestEnd = $pos + 4 + $this->readUint($pos, 4); $pos += 4; $numFiles = $this->readUint($pos, 4); $pos += 4; $pos += 2; $pos += 4; $aliasLength = $this->readUint($pos, 4); $pos += 4 + $aliasLength; $metadataLength = $this->readUint($pos, 4); $pos += 4 + $metadataLength; $compressedSizes = 0; while (($numFiles > 0) && ($pos < $manifestEnd - 24)) { $filenameLength = $this->readUint($pos, 4); $pos += 4 + $filenameLength; $pos += 2*4; $compressedSizes += $this->readUint($pos, 4); $pos += 3*4; $metadataLength = $this->readUint($pos, 4); $pos += 4 + $metadataLength; $numFiles--; } if ($numFiles !== 0) { throw new \LogicException('All files were not processed, something must have gone wrong'); } return $manifestEnd + $compressedSizes; } } Copyright (c) 2015 Jordi Boggiano Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. signals = $signals; $this->loggerOrCallback = $loggerOrCallback; } private function trigger(string $signalName): void { $this->triggered = $signalName; if ($this->loggerOrCallback instanceof LoggerInterface) { $this->loggerOrCallback->info('Received '.$signalName); } elseif ($this->loggerOrCallback !== null) { ($this->loggerOrCallback)($signalName, $this); } } public function isTriggered(): bool { return $this->triggered !== null; } public function exitWithLastSignal(): void { $signal = $this->triggered ?? 'SIGINT'; $signal = defined($signal) ? constant($signal) : 2; if (function_exists('posix_kill') && function_exists('posix_getpid')) { pcntl_signal($signal, SIG_DFL); posix_kill(posix_getpid(), $signal); } exit(128 + $signal); } public function reset(): void { $this->triggered = null; } public function __destruct() { $this->unregister(); } public static function create(?array $signals = null, $loggerOrCallback = null): self { if ($signals === null) { $signals = [self::SIGINT, self::SIGTERM]; } $signals = array_map(function ($signal) { if (is_int($signal)) { return self::getSignalName($signal); } elseif (!in_array($signal, self::ALL_SIGNALS, true)) { throw new \InvalidArgumentException('$signals must be an array of SIG* constants or self::SIG* constants, got '.var_export($signal, true)); } return $signal; }, (array) $signals); $handler = new self($signals, $loggerOrCallback); if (PHP_VERSION_ID >= 80000) { array_unshift(self::$handlers, WeakReference::create($handler)); } else { array_unshift(self::$handlers, $handler); } if (function_exists('sapi_windows_set_ctrl_handler') && PHP_SAPI === 'cli' && (in_array(self::SIGINT, $signals, true) || in_array(self::SIGBREAK, $signals, true))) { if (null === self::$windowsHandler) { self::$windowsHandler = Closure::fromCallable([self::class, 'handleWindowsSignal']); sapi_windows_set_ctrl_handler(self::$windowsHandler); } } if (function_exists('pcntl_signal') && function_exists('pcntl_async_signals')) { pcntl_async_signals(true); self::registerPcntlHandler($signals); } return $handler; } public function unregister(): void { $signals = $this->signals; $index = false; foreach (self::$handlers as $key => $handler) { if (($handler instanceof WeakReference && $handler->get() === $this) || $handler === $this) { $index = $key; break; } } if ($index === false) { return; } unset(self::$handlers[$index]); if (self::$windowsHandler !== null && (in_array(self::SIGINT, $signals, true) || in_array(self::SIGBREAK, $signals, true))) { if (self::getHandlerFor(self::SIGINT) === null && self::getHandlerFor(self::SIGBREAK) === null) { sapi_windows_set_ctrl_handler(self::$windowsHandler, false); self::$windowsHandler = null; } } if (function_exists('pcntl_signal')) { foreach ($signals as $signal) { if (!defined($signal)) { continue; } if (self::getHandlerFor($signal) !== null) { continue; } pcntl_signal(constant($signal), SIG_DFL); } } } public static function unregisterAll(): void { if (self::$windowsHandler !== null) { sapi_windows_set_ctrl_handler(self::$windowsHandler, false); self::$windowsHandler = null; } foreach (self::$handlers as $key => $handler) { if ($handler instanceof WeakReference) { $handler = $handler->get(); if ($handler === null) { unset(self::$handlers[$key]); continue; } } $handler->unregister(); } } private static function registerPcntlHandler(array $signals): void { static $callable; if ($callable === null) { $callable = Closure::fromCallable([self::class, 'handlePcntlSignal']); } foreach ($signals as $signal) { if (!defined($signal)) { continue; } pcntl_signal(constant($signal), $callable); } } private static function handleWindowsSignal(int $event): void { if (PHP_WINDOWS_EVENT_CTRL_C === $event) { self::callHandlerFor(self::SIGINT); } elseif (PHP_WINDOWS_EVENT_CTRL_BREAK === $event) { self::callHandlerFor(self::SIGBREAK); } } private static function handlePcntlSignal(int $signal): void { self::callHandlerFor(self::getSignalName($signal)); } private static function callHandlerFor(string $signal): void { $handler = self::getHandlerFor($signal); if ($handler !== null) { $handler->trigger($signal); } } private static function getHandlerFor(string $signal): ?self { foreach (self::$handlers as $key => $handler) { if ($handler instanceof WeakReference) { $handler = $handler->get(); if ($handler === null) { unset(self::$handlers[$key]); continue; } } if (in_array($signal, $handler->signals, true)) { return $handler; } } return null; } private static function getSignalName(int $signo): string { static $signals = null; if ($signals === null) { $signals = []; foreach (self::ALL_SIGNALS as $value) { if (defined($value)) { $signals[constant($value)] = $value; } } } if (isset($signals[$signo])) { return $signals[$signo]; } throw new \InvalidArgumentException('Unknown signal #'.$signo); } } name = $name; $this->version = $version; $this->terminal = new Terminal(); $this->defaultCommand = 'list'; if (\defined('SIGINT') && SignalRegistry::isSupported()) { $this->signalRegistry = new SignalRegistry(); $this->signalsToDispatchEvent = [\SIGINT, \SIGTERM, \SIGUSR1, \SIGUSR2]; } } public function setDispatcher(EventDispatcherInterface $dispatcher) { $this->dispatcher = $dispatcher; } public function setCommandLoader(CommandLoaderInterface $commandLoader) { $this->commandLoader = $commandLoader; } public function getSignalRegistry(): SignalRegistry { if (!$this->signalRegistry) { throw new RuntimeException('Signals are not supported. Make sure that the `pcntl` extension is installed and that "pcntl_*" functions are not disabled by your php.ini\'s "disable_functions" directive.'); } return $this->signalRegistry; } public function setSignalsToDispatchEvent(int ...$signalsToDispatchEvent) { $this->signalsToDispatchEvent = $signalsToDispatchEvent; } public function run(?InputInterface $input = null, ?OutputInterface $output = null) { if (\function_exists('putenv')) { @putenv('LINES='.$this->terminal->getHeight()); @putenv('COLUMNS='.$this->terminal->getWidth()); } if (null === $input) { $input = new ArgvInput(); } if (null === $output) { $output = new ConsoleOutput(); } $renderException = function (\Throwable $e) use ($output) { if ($output instanceof ConsoleOutputInterface) { $this->renderThrowable($e, $output->getErrorOutput()); } else { $this->renderThrowable($e, $output); } }; if ($phpHandler = set_exception_handler($renderException)) { restore_exception_handler(); if (!\is_array($phpHandler) || !$phpHandler[0] instanceof ErrorHandler) { $errorHandler = true; } elseif ($errorHandler = $phpHandler[0]->setExceptionHandler($renderException)) { $phpHandler[0]->setExceptionHandler($errorHandler); } } try { $this->configureIO($input, $output); $exitCode = $this->doRun($input, $output); } catch (\Exception $e) { if (!$this->catchExceptions) { throw $e; } $renderException($e); $exitCode = $e->getCode(); if (is_numeric($exitCode)) { $exitCode = (int) $exitCode; if ($exitCode <= 0) { $exitCode = 1; } } else { $exitCode = 1; } } finally { if (!$phpHandler) { if (set_exception_handler($renderException) === $renderException) { restore_exception_handler(); } restore_exception_handler(); } elseif (!$errorHandler) { $finalHandler = $phpHandler[0]->setExceptionHandler(null); if ($finalHandler !== $renderException) { $phpHandler[0]->setExceptionHandler($finalHandler); } } } if ($this->autoExit) { if ($exitCode > 255) { $exitCode = 255; } exit($exitCode); } return $exitCode; } public function doRun(InputInterface $input, OutputInterface $output) { if (true === $input->hasParameterOption(['--version', '-V'], true)) { $output->writeln($this->getLongVersion()); return 0; } try { $input->bind($this->getDefinition()); } catch (ExceptionInterface $e) { } $name = $this->getCommandName($input); if (true === $input->hasParameterOption(['--help', '-h'], true)) { if (!$name) { $name = 'help'; $input = new ArrayInput(['command_name' => $this->defaultCommand]); } else { $this->wantHelps = true; } } if (!$name) { $name = $this->defaultCommand; $definition = $this->getDefinition(); $definition->setArguments(array_merge( $definition->getArguments(), [ 'command' => new InputArgument('command', InputArgument::OPTIONAL, $definition->getArgument('command')->getDescription(), $name), ] )); } try { $this->runningCommand = null; $command = $this->find($name); } catch (\Throwable $e) { if (!($e instanceof CommandNotFoundException && !$e instanceof NamespaceNotFoundException) || 1 !== \count($alternatives = $e->getAlternatives()) || !$input->isInteractive()) { if (null !== $this->dispatcher) { $event = new ConsoleErrorEvent($input, $output, $e); $this->dispatcher->dispatch($event, ConsoleEvents::ERROR); if (0 === $event->getExitCode()) { return 0; } $e = $event->getError(); } throw $e; } $alternative = $alternatives[0]; $style = new SymfonyStyle($input, $output); $output->writeln(''); $formattedBlock = (new FormatterHelper())->formatBlock(sprintf('Command "%s" is not defined.', $name), 'error', true); $output->writeln($formattedBlock); if (!$style->confirm(sprintf('Do you want to run "%s" instead? ', $alternative), false)) { if (null !== $this->dispatcher) { $event = new ConsoleErrorEvent($input, $output, $e); $this->dispatcher->dispatch($event, ConsoleEvents::ERROR); return $event->getExitCode(); } return 1; } $command = $this->find($alternative); } if ($command instanceof LazyCommand) { $command = $command->getCommand(); } $this->runningCommand = $command; $exitCode = $this->doRunCommand($command, $input, $output); $this->runningCommand = null; return $exitCode; } public function reset() { } public function setHelperSet(HelperSet $helperSet) { $this->helperSet = $helperSet; } public function getHelperSet() { if (!$this->helperSet) { $this->helperSet = $this->getDefaultHelperSet(); } return $this->helperSet; } public function setDefinition(InputDefinition $definition) { $this->definition = $definition; } public function getDefinition() { if (!$this->definition) { $this->definition = $this->getDefaultInputDefinition(); } if ($this->singleCommand) { $inputDefinition = $this->definition; $inputDefinition->setArguments(); return $inputDefinition; } return $this->definition; } public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void { if ( CompletionInput::TYPE_ARGUMENT_VALUE === $input->getCompletionType() && 'command' === $input->getCompletionName() ) { $commandNames = []; foreach ($this->all() as $name => $command) { if ($command->isHidden() || $command->getName() !== $name) { continue; } $commandNames[] = $command->getName(); foreach ($command->getAliases() as $name) { $commandNames[] = $name; } } $suggestions->suggestValues(array_filter($commandNames)); return; } if (CompletionInput::TYPE_OPTION_NAME === $input->getCompletionType()) { $suggestions->suggestOptions($this->getDefinition()->getOptions()); return; } } public function getHelp() { return $this->getLongVersion(); } public function areExceptionsCaught() { return $this->catchExceptions; } public function setCatchExceptions(bool $boolean) { $this->catchExceptions = $boolean; } public function isAutoExitEnabled() { return $this->autoExit; } public function setAutoExit(bool $boolean) { $this->autoExit = $boolean; } public function getName() { return $this->name; } public function setName(string $name) { $this->name = $name; } public function getVersion() { return $this->version; } public function setVersion(string $version) { $this->version = $version; } public function getLongVersion() { if ('UNKNOWN' !== $this->getName()) { if ('UNKNOWN' !== $this->getVersion()) { return sprintf('%s %s', $this->getName(), $this->getVersion()); } return $this->getName(); } return 'Console Tool'; } public function register(string $name) { return $this->add(new Command($name)); } public function addCommands(array $commands) { foreach ($commands as $command) { $this->add($command); } } public function add(Command $command) { $this->init(); $command->setApplication($this); if (!$command->isEnabled()) { $command->setApplication(null); return null; } if (!$command instanceof LazyCommand) { $command->getDefinition(); } if (!$command->getName()) { throw new LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_debug_type($command))); } $this->commands[$command->getName()] = $command; foreach ($command->getAliases() as $alias) { $this->commands[$alias] = $command; } return $command; } public function get(string $name) { $this->init(); if (!$this->has($name)) { throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name)); } if (!isset($this->commands[$name])) { throw new CommandNotFoundException(sprintf('The "%s" command cannot be found because it is registered under multiple names. Make sure you don\'t set a different name via constructor or "setName()".', $name)); } $command = $this->commands[$name]; if ($this->wantHelps) { $this->wantHelps = false; $helpCommand = $this->get('help'); $helpCommand->setCommand($command); return $helpCommand; } return $command; } public function has(string $name) { $this->init(); return isset($this->commands[$name]) || ($this->commandLoader && $this->commandLoader->has($name) && $this->add($this->commandLoader->get($name))); } public function getNamespaces() { $namespaces = []; foreach ($this->all() as $command) { if ($command->isHidden()) { continue; } $namespaces[] = $this->extractAllNamespaces($command->getName()); foreach ($command->getAliases() as $alias) { $namespaces[] = $this->extractAllNamespaces($alias); } } return array_values(array_unique(array_filter(array_merge([], ...$namespaces)))); } public function findNamespace(string $namespace) { $allNamespaces = $this->getNamespaces(); $expr = implode('[^:]*:', array_map('preg_quote', explode(':', $namespace))).'[^:]*'; $namespaces = preg_grep('{^'.$expr.'}', $allNamespaces); if (empty($namespaces)) { $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace); if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) { if (1 == \count($alternatives)) { $message .= "\n\nDid you mean this?\n "; } else { $message .= "\n\nDid you mean one of these?\n "; } $message .= implode("\n ", $alternatives); } throw new NamespaceNotFoundException($message, $alternatives); } $exact = \in_array($namespace, $namespaces, true); if (\count($namespaces) > 1 && !$exact) { throw new NamespaceNotFoundException(sprintf("The namespace \"%s\" is ambiguous.\nDid you mean one of these?\n%s.", $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))), array_values($namespaces)); } return $exact ? $namespace : reset($namespaces); } public function find(string $name) { $this->init(); $aliases = []; foreach ($this->commands as $command) { foreach ($command->getAliases() as $alias) { if (!$this->has($alias)) { $this->commands[$alias] = $command; } } } if ($this->has($name)) { return $this->get($name); } $allCommands = $this->commandLoader ? array_merge($this->commandLoader->getNames(), array_keys($this->commands)) : array_keys($this->commands); $expr = implode('[^:]*:', array_map('preg_quote', explode(':', $name))).'[^:]*'; $commands = preg_grep('{^'.$expr.'}', $allCommands); if (empty($commands)) { $commands = preg_grep('{^'.$expr.'}i', $allCommands); } if (empty($commands) || \count(preg_grep('{^'.$expr.'$}i', $commands)) < 1) { if (false !== $pos = strrpos($name, ':')) { $this->findNamespace(substr($name, 0, $pos)); } $message = sprintf('Command "%s" is not defined.', $name); if ($alternatives = $this->findAlternatives($name, $allCommands)) { $alternatives = array_filter($alternatives, function ($name) { return !$this->get($name)->isHidden(); }); if (1 == \count($alternatives)) { $message .= "\n\nDid you mean this?\n "; } else { $message .= "\n\nDid you mean one of these?\n "; } $message .= implode("\n ", $alternatives); } throw new CommandNotFoundException($message, array_values($alternatives)); } if (\count($commands) > 1) { $commandList = $this->commandLoader ? array_merge(array_flip($this->commandLoader->getNames()), $this->commands) : $this->commands; $commands = array_unique(array_filter($commands, function ($nameOrAlias) use (&$commandList, $commands, &$aliases) { if (!$commandList[$nameOrAlias] instanceof Command) { $commandList[$nameOrAlias] = $this->commandLoader->get($nameOrAlias); } $commandName = $commandList[$nameOrAlias]->getName(); $aliases[$nameOrAlias] = $commandName; return $commandName === $nameOrAlias || !\in_array($commandName, $commands); })); } if (\count($commands) > 1) { $usableWidth = $this->terminal->getWidth() - 10; $abbrevs = array_values($commands); $maxLen = 0; foreach ($abbrevs as $abbrev) { $maxLen = max(Helper::width($abbrev), $maxLen); } $abbrevs = array_map(function ($cmd) use ($commandList, $usableWidth, $maxLen, &$commands) { if ($commandList[$cmd]->isHidden()) { unset($commands[array_search($cmd, $commands)]); return false; } $abbrev = str_pad($cmd, $maxLen, ' ').' '.$commandList[$cmd]->getDescription(); return Helper::width($abbrev) > $usableWidth ? Helper::substr($abbrev, 0, $usableWidth - 3).'...' : $abbrev; }, array_values($commands)); if (\count($commands) > 1) { $suggestions = $this->getAbbreviationSuggestions(array_filter($abbrevs)); throw new CommandNotFoundException(sprintf("Command \"%s\" is ambiguous.\nDid you mean one of these?\n%s.", $name, $suggestions), array_values($commands)); } } $command = $this->get(reset($commands)); if ($command->isHidden()) { throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name)); } return $command; } public function all(?string $namespace = null) { $this->init(); if (null === $namespace) { if (!$this->commandLoader) { return $this->commands; } $commands = $this->commands; foreach ($this->commandLoader->getNames() as $name) { if (!isset($commands[$name]) && $this->has($name)) { $commands[$name] = $this->get($name); } } return $commands; } $commands = []; foreach ($this->commands as $name => $command) { if ($namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1)) { $commands[$name] = $command; } } if ($this->commandLoader) { foreach ($this->commandLoader->getNames() as $name) { if (!isset($commands[$name]) && $namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1) && $this->has($name)) { $commands[$name] = $this->get($name); } } } return $commands; } public static function getAbbreviations(array $names) { $abbrevs = []; foreach ($names as $name) { for ($len = \strlen($name); $len > 0; --$len) { $abbrev = substr($name, 0, $len); $abbrevs[$abbrev][] = $name; } } return $abbrevs; } public function renderThrowable(\Throwable $e, OutputInterface $output): void { $output->writeln('', OutputInterface::VERBOSITY_QUIET); $this->doRenderThrowable($e, $output); if (null !== $this->runningCommand) { $output->writeln(sprintf('%s', OutputFormatter::escape(sprintf($this->runningCommand->getSynopsis(), $this->getName()))), OutputInterface::VERBOSITY_QUIET); $output->writeln('', OutputInterface::VERBOSITY_QUIET); } } protected function doRenderThrowable(\Throwable $e, OutputInterface $output): void { do { $message = trim($e->getMessage()); if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { $class = get_debug_type($e); $title = sprintf(' [%s%s] ', $class, 0 !== ($code = $e->getCode()) ? ' ('.$code.')' : ''); $len = Helper::width($title); } else { $len = 0; } if (str_contains($message, "@anonymous\0")) { $message = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)?[0-9a-fA-F]++/', function ($m) { return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0]; }, $message); } $width = $this->terminal->getWidth() ? $this->terminal->getWidth() - 1 : \PHP_INT_MAX; $lines = []; foreach ('' !== $message ? preg_split('/\r?\n/', $message) : [] as $line) { foreach ($this->splitStringByWidth($line, $width - 4) as $line) { $lineLength = Helper::width($line) + 4; $lines[] = [$line, $lineLength]; $len = max($lineLength, $len); } } $messages = []; if (!$e instanceof ExceptionInterface || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { $messages[] = sprintf('%s', OutputFormatter::escape(sprintf('In %s line %s:', basename($e->getFile()) ?: 'n/a', $e->getLine() ?: 'n/a'))); } $messages[] = $emptyLine = sprintf('%s', str_repeat(' ', $len)); if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { $messages[] = sprintf('%s%s', $title, str_repeat(' ', max(0, $len - Helper::width($title)))); } foreach ($lines as $line) { $messages[] = sprintf(' %s %s', OutputFormatter::escape($line[0]), str_repeat(' ', $len - $line[1])); } $messages[] = $emptyLine; $messages[] = ''; $output->writeln($messages, OutputInterface::VERBOSITY_QUIET); if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { $output->writeln('Exception trace:', OutputInterface::VERBOSITY_QUIET); $trace = $e->getTrace(); array_unshift($trace, [ 'function' => '', 'file' => $e->getFile() ?: 'n/a', 'line' => $e->getLine() ?: 'n/a', 'args' => [], ]); for ($i = 0, $count = \count($trace); $i < $count; ++$i) { $class = $trace[$i]['class'] ?? ''; $type = $trace[$i]['type'] ?? ''; $function = $trace[$i]['function'] ?? ''; $file = $trace[$i]['file'] ?? 'n/a'; $line = $trace[$i]['line'] ?? 'n/a'; $output->writeln(sprintf(' %s%s at %s:%s', $class, $function ? $type.$function.'()' : '', $file, $line), OutputInterface::VERBOSITY_QUIET); } $output->writeln('', OutputInterface::VERBOSITY_QUIET); } } while ($e = $e->getPrevious()); } protected function configureIO(InputInterface $input, OutputInterface $output) { if (true === $input->hasParameterOption(['--ansi'], true)) { $output->setDecorated(true); } elseif (true === $input->hasParameterOption(['--no-ansi'], true)) { $output->setDecorated(false); } if (true === $input->hasParameterOption(['--no-interaction', '-n'], true)) { $input->setInteractive(false); } switch ($shellVerbosity = (int) getenv('SHELL_VERBOSITY')) { case -1: $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); break; case 1: $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); break; case 2: $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE); break; case 3: $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG); break; default: $shellVerbosity = 0; break; } if (true === $input->hasParameterOption(['--quiet', '-q'], true)) { $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); $shellVerbosity = -1; } else { if ($input->hasParameterOption('-vvv', true) || $input->hasParameterOption('--verbose=3', true) || 3 === $input->getParameterOption('--verbose', false, true)) { $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG); $shellVerbosity = 3; } elseif ($input->hasParameterOption('-vv', true) || $input->hasParameterOption('--verbose=2', true) || 2 === $input->getParameterOption('--verbose', false, true)) { $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE); $shellVerbosity = 2; } elseif ($input->hasParameterOption('-v', true) || $input->hasParameterOption('--verbose=1', true) || $input->hasParameterOption('--verbose', true) || $input->getParameterOption('--verbose', false, true)) { $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); $shellVerbosity = 1; } } if (-1 === $shellVerbosity) { $input->setInteractive(false); } if (\function_exists('putenv')) { @putenv('SHELL_VERBOSITY='.$shellVerbosity); } $_ENV['SHELL_VERBOSITY'] = $shellVerbosity; $_SERVER['SHELL_VERBOSITY'] = $shellVerbosity; } protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output) { foreach ($command->getHelperSet() as $helper) { if ($helper instanceof InputAwareInterface) { $helper->setInput($input); } } if ($this->signalsToDispatchEvent) { $commandSignals = $command instanceof SignalableCommandInterface ? $command->getSubscribedSignals() : []; if ($commandSignals || null !== $this->dispatcher) { if (!$this->signalRegistry) { throw new RuntimeException('Unable to subscribe to signal events. Make sure that the `pcntl` extension is installed and that "pcntl_*" functions are not disabled by your php.ini\'s "disable_functions" directive.'); } if (Terminal::hasSttyAvailable()) { $sttyMode = shell_exec('stty -g'); foreach ([\SIGINT, \SIGTERM] as $signal) { $this->signalRegistry->register($signal, static function () use ($sttyMode) { shell_exec('stty '.$sttyMode); }); } } } if (null !== $this->dispatcher) { foreach ($this->signalsToDispatchEvent as $signal) { $event = new ConsoleSignalEvent($command, $input, $output, $signal); $this->signalRegistry->register($signal, function ($signal, $hasNext) use ($event) { $this->dispatcher->dispatch($event, ConsoleEvents::SIGNAL); if (!$hasNext) { if (!\in_array($signal, [\SIGUSR1, \SIGUSR2], true)) { exit(0); } } }); } } foreach ($commandSignals as $signal) { $this->signalRegistry->register($signal, [$command, 'handleSignal']); } } if (null === $this->dispatcher) { return $command->run($input, $output); } try { $command->mergeApplicationDefinition(); $input->bind($command->getDefinition()); } catch (ExceptionInterface $e) { } $event = new ConsoleCommandEvent($command, $input, $output); $e = null; try { $this->dispatcher->dispatch($event, ConsoleEvents::COMMAND); if ($event->commandShouldRun()) { $exitCode = $command->run($input, $output); } else { $exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED; } } catch (\Throwable $e) { $event = new ConsoleErrorEvent($input, $output, $e, $command); $this->dispatcher->dispatch($event, ConsoleEvents::ERROR); $e = $event->getError(); if (0 === $exitCode = $event->getExitCode()) { $e = null; } } $event = new ConsoleTerminateEvent($command, $input, $output, $exitCode); $this->dispatcher->dispatch($event, ConsoleEvents::TERMINATE); if (null !== $e) { throw $e; } return $event->getExitCode(); } protected function getCommandName(InputInterface $input) { return $this->singleCommand ? $this->defaultCommand : $input->getFirstArgument(); } protected function getDefaultInputDefinition() { return new InputDefinition([ new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'), new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display help for the given command. When no command is given display help for the '.$this->defaultCommand.' command'), new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'), new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'), new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version'), new InputOption('--ansi', '', InputOption::VALUE_NEGATABLE, 'Force (or disable --no-ansi) ANSI output', null), new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'), ]); } protected function getDefaultCommands() { return [new HelpCommand(), new ListCommand(), new CompleteCommand(), new DumpCompletionCommand()]; } protected function getDefaultHelperSet() { return new HelperSet([ new FormatterHelper(), new DebugFormatterHelper(), new ProcessHelper(), new QuestionHelper(), ]); } private function getAbbreviationSuggestions(array $abbrevs): string { return ' '.implode("\n ", $abbrevs); } public function extractNamespace(string $name, ?int $limit = null) { $parts = explode(':', $name, -1); return implode(':', null === $limit ? $parts : \array_slice($parts, 0, $limit)); } private function findAlternatives(string $name, iterable $collection): array { $threshold = 1e3; $alternatives = []; $collectionParts = []; foreach ($collection as $item) { $collectionParts[$item] = explode(':', $item); } foreach (explode(':', $name) as $i => $subname) { foreach ($collectionParts as $collectionName => $parts) { $exists = isset($alternatives[$collectionName]); if (!isset($parts[$i]) && $exists) { $alternatives[$collectionName] += $threshold; continue; } elseif (!isset($parts[$i])) { continue; } $lev = levenshtein($subname, $parts[$i]); if ($lev <= \strlen($subname) / 3 || '' !== $subname && str_contains($parts[$i], $subname)) { $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev; } elseif ($exists) { $alternatives[$collectionName] += $threshold; } } } foreach ($collection as $item) { $lev = levenshtein($name, $item); if ($lev <= \strlen($name) / 3 || str_contains($item, $name)) { $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev; } } $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; }); ksort($alternatives, \SORT_NATURAL | \SORT_FLAG_CASE); return array_keys($alternatives); } public function setDefaultCommand(string $commandName, bool $isSingleCommand = false) { $this->defaultCommand = explode('|', ltrim($commandName, '|'))[0]; if ($isSingleCommand) { $this->find($commandName); $this->singleCommand = true; } return $this; } public function isSingleCommand(): bool { return $this->singleCommand; } private function splitStringByWidth(string $string, int $width): array { if (false === $encoding = mb_detect_encoding($string, null, true)) { return str_split($string, $width); } $utf8String = mb_convert_encoding($string, 'utf8', $encoding); $lines = []; $line = ''; $offset = 0; while (preg_match('/.{1,10000}/u', $utf8String, $m, 0, $offset)) { $offset += \strlen($m[0]); foreach (preg_split('//u', $m[0]) as $char) { if (mb_strwidth($line.$char, 'utf8') <= $width) { $line .= $char; continue; } $lines[] = str_pad($line, $width); $line = $char; } } $lines[] = \count($lines) ? str_pad($line, $width) : $line; mb_convert_variables($encoding, 'utf8', $lines); return $lines; } private function extractAllNamespaces(string $name): array { $parts = explode(':', $name, -1); $namespaces = []; foreach ($parts as $part) { if (\count($namespaces)) { $namespaces[] = end($namespaces).':'.$part; } else { $namespaces[] = $part; } } return $namespaces; } private function init() { if ($this->initialized) { return; } $this->initialized = true; foreach ($this->getDefaultCommands() as $command) { $this->add($command); } } } name = implode('|', $name); } } '%25', "\r" => '%0D', "\n" => '%0A', ]; private const ESCAPED_PROPERTIES = [ '%' => '%25', "\r" => '%0D', "\n" => '%0A', ':' => '%3A', ',' => '%2C', ]; public function __construct(OutputInterface $output) { $this->output = $output; } public static function isGithubActionEnvironment(): bool { return false !== getenv('GITHUB_ACTIONS'); } public function error(string $message, ?string $file = null, ?int $line = null, ?int $col = null): void { $this->log('error', $message, $file, $line, $col); } public function warning(string $message, ?string $file = null, ?int $line = null, ?int $col = null): void { $this->log('warning', $message, $file, $line, $col); } public function debug(string $message, ?string $file = null, ?int $line = null, ?int $col = null): void { $this->log('debug', $message, $file, $line, $col); } private function log(string $type, string $message, ?string $file = null, ?int $line = null, ?int $col = null): void { $message = strtr($message, self::ESCAPED_DATA); if (!$file) { $this->output->writeln(sprintf('::%s::%s', $type, $message)); return; } $this->output->writeln(sprintf('::%s file=%s,line=%s,col=%s::%s', $type, strtr($file, self::ESCAPED_PROPERTIES), strtr($line ?? 1, self::ESCAPED_PROPERTIES), strtr($col ?? 0, self::ESCAPED_PROPERTIES), $message)); } } 0, 'red' => 1, 'green' => 2, 'yellow' => 3, 'blue' => 4, 'magenta' => 5, 'cyan' => 6, 'white' => 7, 'default' => 9, ]; private const BRIGHT_COLORS = [ 'gray' => 0, 'bright-red' => 1, 'bright-green' => 2, 'bright-yellow' => 3, 'bright-blue' => 4, 'bright-magenta' => 5, 'bright-cyan' => 6, 'bright-white' => 7, ]; private const AVAILABLE_OPTIONS = [ 'bold' => ['set' => 1, 'unset' => 22], 'underscore' => ['set' => 4, 'unset' => 24], 'blink' => ['set' => 5, 'unset' => 25], 'reverse' => ['set' => 7, 'unset' => 27], 'conceal' => ['set' => 8, 'unset' => 28], ]; private $foreground; private $background; private $options = []; public function __construct(string $foreground = '', string $background = '', array $options = []) { $this->foreground = $this->parseColor($foreground); $this->background = $this->parseColor($background, true); foreach ($options as $option) { if (!isset(self::AVAILABLE_OPTIONS[$option])) { throw new InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s).', $option, implode(', ', array_keys(self::AVAILABLE_OPTIONS)))); } $this->options[$option] = self::AVAILABLE_OPTIONS[$option]; } } public function apply(string $text): string { return $this->set().$text.$this->unset(); } public function set(): string { $setCodes = []; if ('' !== $this->foreground) { $setCodes[] = $this->foreground; } if ('' !== $this->background) { $setCodes[] = $this->background; } foreach ($this->options as $option) { $setCodes[] = $option['set']; } if (0 === \count($setCodes)) { return ''; } return sprintf("\033[%sm", implode(';', $setCodes)); } public function unset(): string { $unsetCodes = []; if ('' !== $this->foreground) { $unsetCodes[] = 39; } if ('' !== $this->background) { $unsetCodes[] = 49; } foreach ($this->options as $option) { $unsetCodes[] = $option['unset']; } if (0 === \count($unsetCodes)) { return ''; } return sprintf("\033[%sm", implode(';', $unsetCodes)); } private function parseColor(string $color, bool $background = false): string { if ('' === $color) { return ''; } if ('#' === $color[0]) { $color = substr($color, 1); if (3 === \strlen($color)) { $color = $color[0].$color[0].$color[1].$color[1].$color[2].$color[2]; } if (6 !== \strlen($color)) { throw new InvalidArgumentException(sprintf('Invalid "%s" color.', $color)); } return ($background ? '4' : '3').$this->convertHexColorToAnsi(hexdec($color)); } if (isset(self::COLORS[$color])) { return ($background ? '4' : '3').self::COLORS[$color]; } if (isset(self::BRIGHT_COLORS[$color])) { return ($background ? '10' : '9').self::BRIGHT_COLORS[$color]; } throw new InvalidArgumentException(sprintf('Invalid "%s" color; expected one of (%s).', $color, implode(', ', array_merge(array_keys(self::COLORS), array_keys(self::BRIGHT_COLORS))))); } private function convertHexColorToAnsi(int $color): string { $r = ($color >> 16) & 255; $g = ($color >> 8) & 255; $b = $color & 255; if ('truecolor' !== getenv('COLORTERM')) { return (string) $this->degradeHexColorToAnsi($r, $g, $b); } return sprintf('8;2;%d;%d;%d', $r, $g, $b); } private function degradeHexColorToAnsi(int $r, int $g, int $b): int { if (0 === round($this->getSaturation($r, $g, $b) / 50)) { return 0; } return (round($b / 255) << 2) | (round($g / 255) << 1) | round($r / 255); } private function getSaturation(int $r, int $g, int $b): int { $r = $r / 255; $g = $g / 255; $b = $b / 255; $v = max($r, $g, $b); if (0 === $diff = $v - min($r, $g, $b)) { return 0; } return (int) $diff * 100 / $v; } } = 80000 && $attribute = (new \ReflectionClass($class))->getAttributes(AsCommand::class)) { return $attribute[0]->newInstance()->name; } $r = new \ReflectionProperty($class, 'defaultName'); return $class === $r->class ? static::$defaultName : null; } public static function getDefaultDescription(): ?string { $class = static::class; if (\PHP_VERSION_ID >= 80000 && $attribute = (new \ReflectionClass($class))->getAttributes(AsCommand::class)) { return $attribute[0]->newInstance()->description; } $r = new \ReflectionProperty($class, 'defaultDescription'); return $class === $r->class ? static::$defaultDescription : null; } public function __construct(?string $name = null) { $this->definition = new InputDefinition(); if (null === $name && null !== $name = static::getDefaultName()) { $aliases = explode('|', $name); if ('' === $name = array_shift($aliases)) { $this->setHidden(true); $name = array_shift($aliases); } $this->setAliases($aliases); } if (null !== $name) { $this->setName($name); } if ('' === $this->description) { $this->setDescription(static::getDefaultDescription() ?? ''); } $this->configure(); } public function ignoreValidationErrors() { $this->ignoreValidationErrors = true; } public function setApplication(?Application $application = null) { $this->application = $application; if ($application) { $this->setHelperSet($application->getHelperSet()); } else { $this->helperSet = null; } $this->fullDefinition = null; } public function setHelperSet(HelperSet $helperSet) { $this->helperSet = $helperSet; } public function getHelperSet() { return $this->helperSet; } public function getApplication() { return $this->application; } public function isEnabled() { return true; } protected function configure() { } protected function execute(InputInterface $input, OutputInterface $output) { throw new LogicException('You must override the execute() method in the concrete command class.'); } protected function interact(InputInterface $input, OutputInterface $output) { } protected function initialize(InputInterface $input, OutputInterface $output) { } public function run(InputInterface $input, OutputInterface $output) { $this->mergeApplicationDefinition(); try { $input->bind($this->getDefinition()); } catch (ExceptionInterface $e) { if (!$this->ignoreValidationErrors) { throw $e; } } $this->initialize($input, $output); if (null !== $this->processTitle) { if (\function_exists('cli_set_process_title')) { if (!@cli_set_process_title($this->processTitle)) { if ('Darwin' === \PHP_OS) { $output->writeln('Running "cli_set_process_title" as an unprivileged user is not supported on MacOS.', OutputInterface::VERBOSITY_VERY_VERBOSE); } else { cli_set_process_title($this->processTitle); } } } elseif (\function_exists('setproctitle')) { setproctitle($this->processTitle); } elseif (OutputInterface::VERBOSITY_VERY_VERBOSE === $output->getVerbosity()) { $output->writeln('Install the proctitle PECL to be able to change the process title.'); } } if ($input->isInteractive()) { $this->interact($input, $output); } if ($input->hasArgument('command') && null === $input->getArgument('command')) { $input->setArgument('command', $this->getName()); } $input->validate(); if ($this->code) { $statusCode = ($this->code)($input, $output); } else { $statusCode = $this->execute($input, $output); if (!\is_int($statusCode)) { throw new \TypeError(sprintf('Return value of "%s::execute()" must be of the type int, "%s" returned.', static::class, get_debug_type($statusCode))); } } return is_numeric($statusCode) ? (int) $statusCode : 0; } public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void { } public function setCode(callable $code) { if ($code instanceof \Closure) { $r = new \ReflectionFunction($code); if (null === $r->getClosureThis()) { set_error_handler(static function () {}); try { if ($c = \Closure::bind($code, $this)) { $code = $c; } } finally { restore_error_handler(); } } } $this->code = $code; return $this; } public function mergeApplicationDefinition(bool $mergeArgs = true) { if (null === $this->application) { return; } $this->fullDefinition = new InputDefinition(); $this->fullDefinition->setOptions($this->definition->getOptions()); $this->fullDefinition->addOptions($this->application->getDefinition()->getOptions()); if ($mergeArgs) { $this->fullDefinition->setArguments($this->application->getDefinition()->getArguments()); $this->fullDefinition->addArguments($this->definition->getArguments()); } else { $this->fullDefinition->setArguments($this->definition->getArguments()); } } public function setDefinition($definition) { if ($definition instanceof InputDefinition) { $this->definition = $definition; } else { $this->definition->setDefinition($definition); } $this->fullDefinition = null; return $this; } public function getDefinition() { return $this->fullDefinition ?? $this->getNativeDefinition(); } public function getNativeDefinition() { if (null === $this->definition) { throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', static::class)); } return $this->definition; } public function addArgument(string $name, ?int $mode = null, string $description = '', $default = null) { $this->definition->addArgument(new InputArgument($name, $mode, $description, $default)); if (null !== $this->fullDefinition) { $this->fullDefinition->addArgument(new InputArgument($name, $mode, $description, $default)); } return $this; } public function addOption(string $name, $shortcut = null, ?int $mode = null, string $description = '', $default = null) { $this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default)); if (null !== $this->fullDefinition) { $this->fullDefinition->addOption(new InputOption($name, $shortcut, $mode, $description, $default)); } return $this; } public function setName(string $name) { $this->validateName($name); $this->name = $name; return $this; } public function setProcessTitle(string $title) { $this->processTitle = $title; return $this; } public function getName() { return $this->name; } public function setHidden(bool $hidden ) { $this->hidden = $hidden; return $this; } public function isHidden() { return $this->hidden; } public function setDescription(string $description) { $this->description = $description; return $this; } public function getDescription() { return $this->description; } public function setHelp(string $help) { $this->help = $help; return $this; } public function getHelp() { return $this->help; } public function getProcessedHelp() { $name = $this->name; $isSingleCommand = $this->application && $this->application->isSingleCommand(); $placeholders = [ '%command.name%', '%command.full_name%', ]; $replacements = [ $name, $isSingleCommand ? $_SERVER['PHP_SELF'] : $_SERVER['PHP_SELF'].' '.$name, ]; return str_replace($placeholders, $replacements, $this->getHelp() ?: $this->getDescription()); } public function setAliases(iterable $aliases) { $list = []; foreach ($aliases as $alias) { $this->validateName($alias); $list[] = $alias; } $this->aliases = \is_array($aliases) ? $aliases : $list; return $this; } public function getAliases() { return $this->aliases; } public function getSynopsis(bool $short = false) { $key = $short ? 'short' : 'long'; if (!isset($this->synopsis[$key])) { $this->synopsis[$key] = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis($short))); } return $this->synopsis[$key]; } public function addUsage(string $usage) { if (!str_starts_with($usage, $this->name)) { $usage = sprintf('%s %s', $this->name, $usage); } $this->usages[] = $usage; return $this; } public function getUsages() { return $this->usages; } public function getHelper(string $name) { if (null === $this->helperSet) { throw new LogicException(sprintf('Cannot retrieve helper "%s" because there is no HelperSet defined. Did you forget to add your command to the application or to set the application on the command using the setApplication() method? You can also set the HelperSet directly using the setHelperSet() method.', $name)); } return $this->helperSet->get($name); } private function validateName(string $name) { if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) { throw new InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name)); } } } completionOutputs = $completionOutputs + ['bash' => BashCompletionOutput::class]; parent::__construct(); } protected function configure(): void { $this ->addOption('shell', 's', InputOption::VALUE_REQUIRED, 'The shell type ("'.implode('", "', array_keys($this->completionOutputs)).'")') ->addOption('input', 'i', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'An array of input tokens (e.g. COMP_WORDS or argv)') ->addOption('current', 'c', InputOption::VALUE_REQUIRED, 'The index of the "input" array that the cursor is in (e.g. COMP_CWORD)') ->addOption('symfony', 'S', InputOption::VALUE_REQUIRED, 'The version of the completion script') ; } protected function initialize(InputInterface $input, OutputInterface $output) { $this->isDebug = filter_var(getenv('SYMFONY_COMPLETION_DEBUG'), \FILTER_VALIDATE_BOOLEAN); } protected function execute(InputInterface $input, OutputInterface $output): int { try { $shell = $input->getOption('shell'); if (!$shell) { throw new \RuntimeException('The "--shell" option must be set.'); } if (!$completionOutput = $this->completionOutputs[$shell] ?? false) { throw new \RuntimeException(sprintf('Shell completion is not supported for your shell: "%s" (supported: "%s").', $shell, implode('", "', array_keys($this->completionOutputs)))); } $completionInput = $this->createCompletionInput($input); $suggestions = new CompletionSuggestions(); $this->log([ '', ''.date('Y-m-d H:i:s').'', 'Input: ("|" indicates the cursor position)', ' '.(string) $completionInput, 'Command:', ' '.(string) implode(' ', $_SERVER['argv']), 'Messages:', ]); $command = $this->findCommand($completionInput, $output); if (null === $command) { $this->log(' No command found, completing using the Application class.'); $this->getApplication()->complete($completionInput, $suggestions); } elseif ( $completionInput->mustSuggestArgumentValuesFor('command') && $command->getName() !== $completionInput->getCompletionValue() && !\in_array($completionInput->getCompletionValue(), $command->getAliases(), true) ) { $this->log(' No command found, completing using the Application class.'); $suggestions->suggestValues(array_filter(array_merge([$command->getName()], $command->getAliases()))); } else { $command->mergeApplicationDefinition(); $completionInput->bind($command->getDefinition()); if (CompletionInput::TYPE_OPTION_NAME === $completionInput->getCompletionType()) { $this->log(' Completing option names for the '.\get_class($command instanceof LazyCommand ? $command->getCommand() : $command).' command.'); $suggestions->suggestOptions($command->getDefinition()->getOptions()); } else { $this->log([ ' Completing using the '.\get_class($command instanceof LazyCommand ? $command->getCommand() : $command).' class.', ' Completing '.$completionInput->getCompletionType().' for '.$completionInput->getCompletionName().'', ]); if (null !== $compval = $completionInput->getCompletionValue()) { $this->log(' Current value: '.$compval.''); } $command->complete($completionInput, $suggestions); } } $completionOutput = new $completionOutput(); $this->log('Suggestions:'); if ($options = $suggestions->getOptionSuggestions()) { $this->log(' --'.implode(' --', array_map(function ($o) { return $o->getName(); }, $options))); } elseif ($values = $suggestions->getValueSuggestions()) { $this->log(' '.implode(' ', $values)); } else { $this->log(' No suggestions were provided'); } $completionOutput->write($suggestions, $output); } catch (\Throwable $e) { $this->log([ 'Error!', (string) $e, ]); if ($output->isDebug()) { throw $e; } return 2; } return 0; } private function createCompletionInput(InputInterface $input): CompletionInput { $currentIndex = $input->getOption('current'); if (!$currentIndex || !ctype_digit($currentIndex)) { throw new \RuntimeException('The "--current" option must be set and it must be an integer.'); } $completionInput = CompletionInput::fromTokens($input->getOption('input'), (int) $currentIndex); try { $completionInput->bind($this->getApplication()->getDefinition()); } catch (ExceptionInterface $e) { } return $completionInput; } private function findCommand(CompletionInput $completionInput, OutputInterface $output): ?Command { try { $inputName = $completionInput->getFirstArgument(); if (null === $inputName) { return null; } return $this->getApplication()->find($inputName); } catch (CommandNotFoundException $e) { } return null; } private function log($messages): void { if (!$this->isDebug) { return; } $commandName = basename($_SERVER['argv'][0]); file_put_contents(sys_get_temp_dir().'/sf_'.$commandName.'.log', implode(\PHP_EOL, (array) $messages).\PHP_EOL, \FILE_APPEND); } } mustSuggestArgumentValuesFor('shell')) { $suggestions->suggestValues($this->getSupportedShells()); } } protected function configure() { $fullCommand = $_SERVER['PHP_SELF']; $commandName = basename($fullCommand); $fullCommand = @realpath($fullCommand) ?: $fullCommand; $this ->setHelp(<<%command.name% command dumps the shell completion script required to use shell autocompletion (currently only bash completion is supported). Static installation ------------------- Dump the script to a global completion file and restart your shell: %command.full_name% bash | sudo tee /etc/bash_completion.d/{$commandName} Or dump the script to a local file and source it: %command.full_name% bash > completion.sh # source the file whenever you use the project source completion.sh # or add this line at the end of your "~/.bashrc" file: source /path/to/completion.sh Dynamic installation -------------------- Add this to the end of your shell configuration file (e.g. "~/.bashrc"): eval "$({$fullCommand} completion bash)" EOH ) ->addArgument('shell', InputArgument::OPTIONAL, 'The shell type (e.g. "bash"), the value of the "$SHELL" env var will be used if this is not given') ->addOption('debug', null, InputOption::VALUE_NONE, 'Tail the completion debug log') ; } protected function execute(InputInterface $input, OutputInterface $output): int { $commandName = basename($_SERVER['argv'][0]); if ($input->getOption('debug')) { $this->tailDebugLog($commandName, $output); return 0; } $shell = $input->getArgument('shell') ?? self::guessShell(); $completionFile = __DIR__.'/../Resources/completion.'.$shell; if (!file_exists($completionFile)) { $supportedShells = $this->getSupportedShells(); if ($output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); } if ($shell) { $output->writeln(sprintf('Detected shell "%s", which is not supported by Symfony shell completion (supported shells: "%s").', $shell, implode('", "', $supportedShells))); } else { $output->writeln(sprintf('Shell not detected, Symfony shell completion only supports "%s").', implode('", "', $supportedShells))); } return 2; } $output->write(str_replace(['{{ COMMAND_NAME }}', '{{ VERSION }}'], [$commandName, $this->getApplication()->getVersion()], file_get_contents($completionFile))); return 0; } private static function guessShell(): string { return basename($_SERVER['SHELL'] ?? ''); } private function tailDebugLog(string $commandName, OutputInterface $output): void { $debugFile = sys_get_temp_dir().'/sf_'.$commandName.'.log'; if (!file_exists($debugFile)) { touch($debugFile); } $process = new Process(['tail', '-f', $debugFile], null, null, null, 0); $process->run(function (string $type, string $line) use ($output): void { $output->write($line); }); } private function getSupportedShells(): array { $shells = []; foreach (new \DirectoryIterator(__DIR__.'/../Resources/') as $file) { if (str_starts_with($file->getBasename(), 'completion.') && $file->isFile()) { $shells[] = $file->getExtension(); } } return $shells; } } ignoreValidationErrors(); $this ->setName('help') ->setDefinition([ new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'), new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'), ]) ->setDescription('Display help for a command') ->setHelp(<<<'EOF' The %command.name% command displays help for a given command: %command.full_name% list You can also output the help in other formats by using the --format option: %command.full_name% --format=xml list To display the list of available commands, please use the list command. EOF ) ; } public function setCommand(Command $command) { $this->command = $command; } protected function execute(InputInterface $input, OutputInterface $output) { if (null === $this->command) { $this->command = $this->getApplication()->find($input->getArgument('command_name')); } $helper = new DescriptorHelper(); $helper->describe($output, $this->command, [ 'format' => $input->getOption('format'), 'raw_text' => $input->getOption('raw'), ]); $this->command = null; return 0; } public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void { if ($input->mustSuggestArgumentValuesFor('command_name')) { $descriptor = new ApplicationDescription($this->getApplication()); $suggestions->suggestValues(array_keys($descriptor->getCommands())); return; } if ($input->mustSuggestOptionValuesFor('format')) { $helper = new DescriptorHelper(); $suggestions->suggestValues($helper->getFormats()); } } } setName($name) ->setAliases($aliases) ->setHidden($isHidden) ->setDescription($description); $this->command = $commandFactory; $this->isEnabled = $isEnabled; } public function ignoreValidationErrors(): void { $this->getCommand()->ignoreValidationErrors(); } public function setApplication(?Application $application = null): void { if ($this->command instanceof parent) { $this->command->setApplication($application); } parent::setApplication($application); } public function setHelperSet(HelperSet $helperSet): void { if ($this->command instanceof parent) { $this->command->setHelperSet($helperSet); } parent::setHelperSet($helperSet); } public function isEnabled(): bool { return $this->isEnabled ?? $this->getCommand()->isEnabled(); } public function run(InputInterface $input, OutputInterface $output): int { return $this->getCommand()->run($input, $output); } public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void { $this->getCommand()->complete($input, $suggestions); } public function setCode(callable $code): self { $this->getCommand()->setCode($code); return $this; } public function mergeApplicationDefinition(bool $mergeArgs = true): void { $this->getCommand()->mergeApplicationDefinition($mergeArgs); } public function setDefinition($definition): self { $this->getCommand()->setDefinition($definition); return $this; } public function getDefinition(): InputDefinition { return $this->getCommand()->getDefinition(); } public function getNativeDefinition(): InputDefinition { return $this->getCommand()->getNativeDefinition(); } public function addArgument(string $name, ?int $mode = null, string $description = '', $default = null): self { $this->getCommand()->addArgument($name, $mode, $description, $default); return $this; } public function addOption(string $name, $shortcut = null, ?int $mode = null, string $description = '', $default = null): self { $this->getCommand()->addOption($name, $shortcut, $mode, $description, $default); return $this; } public function setProcessTitle(string $title): self { $this->getCommand()->setProcessTitle($title); return $this; } public function setHelp(string $help): self { $this->getCommand()->setHelp($help); return $this; } public function getHelp(): string { return $this->getCommand()->getHelp(); } public function getProcessedHelp(): string { return $this->getCommand()->getProcessedHelp(); } public function getSynopsis(bool $short = false): string { return $this->getCommand()->getSynopsis($short); } public function addUsage(string $usage): self { $this->getCommand()->addUsage($usage); return $this; } public function getUsages(): array { return $this->getCommand()->getUsages(); } public function getHelper(string $name) { return $this->getCommand()->getHelper($name); } public function getCommand(): parent { if (!$this->command instanceof \Closure) { return $this->command; } $command = $this->command = ($this->command)(); $command->setApplication($this->getApplication()); if (null !== $this->getHelperSet()) { $command->setHelperSet($this->getHelperSet()); } $command->setName($this->getName()) ->setAliases($this->getAliases()) ->setHidden($this->isHidden()) ->setDescription($this->getDescription()); $command->getDefinition(); return $command; } } setName('list') ->setDefinition([ new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'), new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'), new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), new InputOption('short', null, InputOption::VALUE_NONE, 'To skip describing commands\' arguments'), ]) ->setDescription('List commands') ->setHelp(<<<'EOF' The %command.name% command lists all commands: %command.full_name% You can also display the commands for a specific namespace: %command.full_name% test You can also output the information in other formats by using the --format option: %command.full_name% --format=xml It's also possible to get raw list of commands (useful for embedding command runner): %command.full_name% --raw EOF ) ; } protected function execute(InputInterface $input, OutputInterface $output) { $helper = new DescriptorHelper(); $helper->describe($output, $this->getApplication(), [ 'format' => $input->getOption('format'), 'raw_text' => $input->getOption('raw'), 'namespace' => $input->getArgument('namespace'), 'short' => $input->getOption('short'), ]); return 0; } public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void { if ($input->mustSuggestArgumentValuesFor('namespace')) { $descriptor = new ApplicationDescription($this->getApplication()); $suggestions->suggestValues(array_keys($descriptor->getNamespaces())); return; } if ($input->mustSuggestOptionValuesFor('format')) { $helper = new DescriptorHelper(); $suggestions->suggestValues($helper->getFormats()); } } } lock) { throw new LogicException('A lock is already in place.'); } if (SemaphoreStore::isSupported()) { $store = new SemaphoreStore(); } else { $store = new FlockStore(); } $this->lock = (new LockFactory($store))->createLock($name ?: $this->getName()); if (!$this->lock->acquire($blocking)) { $this->lock = null; return false; } return true; } private function release() { if ($this->lock) { $this->lock->release(); $this->lock = null; } } } container = $container; $this->commandMap = $commandMap; } public function get(string $name) { if (!$this->has($name)) { throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name)); } return $this->container->get($this->commandMap[$name]); } public function has(string $name) { return isset($this->commandMap[$name]) && $this->container->has($this->commandMap[$name]); } public function getNames() { return array_keys($this->commandMap); } } factories = $factories; } public function has(string $name) { return isset($this->factories[$name]); } public function get(string $name) { if (!isset($this->factories[$name])) { throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name)); } $factory = $this->factories[$name]; return $factory(); } public function getNames() { return array_keys($this->factories); } } tokens = $tokens; $input->currentIndex = $currentIndex; return $input; } public function bind(InputDefinition $definition): void { parent::bind($definition); $relevantToken = $this->getRelevantToken(); if ('-' === $relevantToken[0]) { [$optionToken, $optionValue] = explode('=', $relevantToken, 2) + ['', '']; $option = $this->getOptionFromToken($optionToken); if (null === $option && !$this->isCursorFree()) { $this->completionType = self::TYPE_OPTION_NAME; $this->completionValue = $relevantToken; return; } if (null !== $option && $option->acceptValue()) { $this->completionType = self::TYPE_OPTION_VALUE; $this->completionName = $option->getName(); $this->completionValue = $optionValue ?: (!str_starts_with($optionToken, '--') ? substr($optionToken, 2) : ''); return; } } $previousToken = $this->tokens[$this->currentIndex - 1]; if ('-' === $previousToken[0] && '' !== trim($previousToken, '-')) { $previousOption = $this->getOptionFromToken($previousToken); if (null !== $previousOption && $previousOption->acceptValue()) { $this->completionType = self::TYPE_OPTION_VALUE; $this->completionName = $previousOption->getName(); $this->completionValue = $relevantToken; return; } } $this->completionType = self::TYPE_ARGUMENT_VALUE; foreach ($this->definition->getArguments() as $argumentName => $argument) { if (!isset($this->arguments[$argumentName])) { break; } $argumentValue = $this->arguments[$argumentName]; $this->completionName = $argumentName; if (\is_array($argumentValue)) { $this->completionValue = $argumentValue ? $argumentValue[array_key_last($argumentValue)] : null; } else { $this->completionValue = $argumentValue; } } if ($this->currentIndex >= \count($this->tokens)) { if (!isset($this->arguments[$argumentName]) || $this->definition->getArgument($argumentName)->isArray()) { $this->completionName = $argumentName; $this->completionValue = ''; } else { $this->completionType = self::TYPE_NONE; $this->completionName = null; $this->completionValue = ''; } } } public function getCompletionType(): string { return $this->completionType; } public function getCompletionName(): ?string { return $this->completionName; } public function getCompletionValue(): string { return $this->completionValue; } public function mustSuggestOptionValuesFor(string $optionName): bool { return self::TYPE_OPTION_VALUE === $this->getCompletionType() && $optionName === $this->getCompletionName(); } public function mustSuggestArgumentValuesFor(string $argumentName): bool { return self::TYPE_ARGUMENT_VALUE === $this->getCompletionType() && $argumentName === $this->getCompletionName(); } protected function parseToken(string $token, bool $parseOptions): bool { try { return parent::parseToken($token, $parseOptions); } catch (RuntimeException $e) { } return $parseOptions; } private function getOptionFromToken(string $optionToken): ?InputOption { $optionName = ltrim($optionToken, '-'); if (!$optionName) { return null; } if ('-' === ($optionToken[1] ?? ' ')) { return $this->definition->hasOption($optionName) ? $this->definition->getOption($optionName) : null; } return $this->definition->hasShortcut($optionName[0]) ? $this->definition->getOptionForShortcut($optionName[0]) : null; } private function getRelevantToken(): string { return $this->tokens[$this->isCursorFree() ? $this->currentIndex - 1 : $this->currentIndex]; } private function isCursorFree(): bool { $nrOfTokens = \count($this->tokens); if ($this->currentIndex > $nrOfTokens) { throw new \LogicException('Current index is invalid, it must be the number of input tokens or one more.'); } return $this->currentIndex >= $nrOfTokens; } public function __toString() { $str = ''; foreach ($this->tokens as $i => $token) { $str .= $token; if ($this->currentIndex === $i) { $str .= '|'; } $str .= ' '; } if ($this->currentIndex > $i) { $str .= '|'; } return rtrim($str); } } valueSuggestions[] = !$value instanceof Suggestion ? new Suggestion($value) : $value; return $this; } public function suggestValues(array $values): self { foreach ($values as $value) { $this->suggestValue($value); } return $this; } public function suggestOption(InputOption $option): self { $this->optionSuggestions[] = $option; return $this; } public function suggestOptions(array $options): self { foreach ($options as $option) { $this->suggestOption($option); } return $this; } public function getOptionSuggestions(): array { return $this->optionSuggestions; } public function getValueSuggestions(): array { return $this->valueSuggestions; } } getValueSuggestions(); foreach ($suggestions->getOptionSuggestions() as $option) { $values[] = '--'.$option->getName(); if ($option->isNegatable()) { $values[] = '--no-'.$option->getName(); } } $output->writeln(implode("\n", $values)); } } value = $value; } public function getValue(): string { return $this->value; } public function __toString(): string { return $this->getValue(); } } self::COMMAND, ConsoleErrorEvent::class => self::ERROR, ConsoleSignalEvent::class => self::SIGNAL, ConsoleTerminateEvent::class => self::TERMINATE, ]; } output = $output; $this->input = $input ?? (\defined('STDIN') ? \STDIN : fopen('php://input', 'r+')); } public function moveUp(int $lines = 1): self { $this->output->write(sprintf("\x1b[%dA", $lines)); return $this; } public function moveDown(int $lines = 1): self { $this->output->write(sprintf("\x1b[%dB", $lines)); return $this; } public function moveRight(int $columns = 1): self { $this->output->write(sprintf("\x1b[%dC", $columns)); return $this; } public function moveLeft(int $columns = 1): self { $this->output->write(sprintf("\x1b[%dD", $columns)); return $this; } public function moveToColumn(int $column): self { $this->output->write(sprintf("\x1b[%dG", $column)); return $this; } public function moveToPosition(int $column, int $row): self { $this->output->write(sprintf("\x1b[%d;%dH", $row + 1, $column)); return $this; } public function savePosition(): self { $this->output->write("\x1b7"); return $this; } public function restorePosition(): self { $this->output->write("\x1b8"); return $this; } public function hide(): self { $this->output->write("\x1b[?25l"); return $this; } public function show(): self { $this->output->write("\x1b[?25h\x1b[?0c"); return $this; } public function clearLine(): self { $this->output->write("\x1b[2K"); return $this; } public function clearLineAfter(): self { $this->output->write("\x1b[K"); return $this; } public function clearOutput(): self { $this->output->write("\x1b[0J"); return $this; } public function clearScreen(): self { $this->output->write("\x1b[2J"); return $this; } public function getCurrentPosition(): array { static $isTtySupported; if (null === $isTtySupported && \function_exists('proc_open')) { $isTtySupported = (bool) @proc_open('echo 1 >/dev/null', [['file', '/dev/tty', 'r'], ['file', '/dev/tty', 'w'], ['file', '/dev/tty', 'w']], $pipes); } if (!$isTtySupported) { return [1, 1]; } $sttyMode = shell_exec('stty -g'); shell_exec('stty -icanon -echo'); @fwrite($this->input, "\033[6n"); $code = trim(fread($this->input, 1024)); shell_exec(sprintf('stty %s', $sttyMode)); sscanf($code, "\033[%d;%dR", $row, $col); return [$col, $row]; } } commandLoaderServiceId = $commandLoaderServiceId; $this->commandTag = $commandTag; $this->noPreloadTag = $noPreloadTag; $this->privateTagName = $privateTagName; } public function process(ContainerBuilder $container) { $commandServices = $container->findTaggedServiceIds($this->commandTag, true); $lazyCommandMap = []; $lazyCommandRefs = []; $serviceIds = []; foreach ($commandServices as $id => $tags) { $definition = $container->getDefinition($id); $definition->addTag($this->noPreloadTag); $class = $container->getParameterBag()->resolveValue($definition->getClass()); if (isset($tags[0]['command'])) { $aliases = $tags[0]['command']; } else { if (!$r = $container->getReflectionClass($class)) { throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); } if (!$r->isSubclassOf(Command::class)) { throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, $this->commandTag, Command::class)); } $aliases = str_replace('%', '%%', $class::getDefaultName() ?? ''); } $aliases = explode('|', $aliases ?? ''); $commandName = array_shift($aliases); if ($isHidden = '' === $commandName) { $commandName = array_shift($aliases); } if (null === $commandName) { if (!$definition->isPublic() || $definition->isPrivate() || $definition->hasTag($this->privateTagName)) { $commandId = 'console.command.public_alias.'.$id; $container->setAlias($commandId, $id)->setPublic(true); $id = $commandId; } $serviceIds[] = $id; continue; } $description = $tags[0]['description'] ?? null; unset($tags[0]); $lazyCommandMap[$commandName] = $id; $lazyCommandRefs[$id] = new TypedReference($id, $class); foreach ($aliases as $alias) { $lazyCommandMap[$alias] = $id; } foreach ($tags as $tag) { if (isset($tag['command'])) { $aliases[] = $tag['command']; $lazyCommandMap[$tag['command']] = $id; } $description = $description ?? $tag['description'] ?? null; } $definition->addMethodCall('setName', [$commandName]); if ($aliases) { $definition->addMethodCall('setAliases', [$aliases]); } if ($isHidden) { $definition->addMethodCall('setHidden', [true]); } if (!$description) { if (!$r = $container->getReflectionClass($class)) { throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); } if (!$r->isSubclassOf(Command::class)) { throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, $this->commandTag, Command::class)); } $description = str_replace('%', '%%', $class::getDefaultDescription() ?? ''); } if ($description) { $definition->addMethodCall('setDescription', [$description]); $container->register('.'.$id.'.lazy', LazyCommand::class) ->setArguments([$commandName, $aliases, $description, $isHidden, new ServiceClosureArgument($lazyCommandRefs[$id])]); $lazyCommandRefs[$id] = new Reference('.'.$id.'.lazy'); } } $container ->register($this->commandLoaderServiceId, ContainerCommandLoader::class) ->setPublic(true) ->addTag($this->noPreloadTag) ->setArguments([ServiceLocatorTagPass::register($container, $lazyCommandRefs), $lazyCommandMap]); $container->setParameter('console.command.ids', $serviceIds); } } application = $application; $this->namespace = $namespace; $this->showHidden = $showHidden; } public function getNamespaces(): array { if (null === $this->namespaces) { $this->inspectApplication(); } return $this->namespaces; } public function getCommands(): array { if (null === $this->commands) { $this->inspectApplication(); } return $this->commands; } public function getCommand(string $name): Command { if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) { throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name)); } return $this->commands[$name] ?? $this->aliases[$name]; } private function inspectApplication() { $this->commands = []; $this->namespaces = []; $all = $this->application->all($this->namespace ? $this->application->findNamespace($this->namespace) : null); foreach ($this->sortCommands($all) as $namespace => $commands) { $names = []; foreach ($commands as $name => $command) { if (!$command->getName() || (!$this->showHidden && $command->isHidden())) { continue; } if ($command->getName() === $name) { $this->commands[$name] = $command; } else { $this->aliases[$name] = $command; } $names[] = $name; } $this->namespaces[$namespace] = ['id' => $namespace, 'commands' => $names]; } } private function sortCommands(array $commands): array { $namespacedCommands = []; $globalCommands = []; $sortedCommands = []; foreach ($commands as $name => $command) { $key = $this->application->extractNamespace($name, 1); if (\in_array($key, ['', self::GLOBAL_NAMESPACE], true)) { $globalCommands[$name] = $command; } else { $namespacedCommands[$key][$name] = $command; } } if ($globalCommands) { ksort($globalCommands); $sortedCommands[self::GLOBAL_NAMESPACE] = $globalCommands; } if ($namespacedCommands) { ksort($namespacedCommands, \SORT_STRING); foreach ($namespacedCommands as $key => $commandsSet) { ksort($commandsSet); $sortedCommands[$key] = $commandsSet; } } return $sortedCommands; } } output = $output; switch (true) { case $object instanceof InputArgument: $this->describeInputArgument($object, $options); break; case $object instanceof InputOption: $this->describeInputOption($object, $options); break; case $object instanceof InputDefinition: $this->describeInputDefinition($object, $options); break; case $object instanceof Command: $this->describeCommand($object, $options); break; case $object instanceof Application: $this->describeApplication($object, $options); break; default: throw new InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_debug_type($object))); } } protected function write(string $content, bool $decorated = false) { $this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW); } abstract protected function describeInputArgument(InputArgument $argument, array $options = []); abstract protected function describeInputOption(InputOption $option, array $options = []); abstract protected function describeInputDefinition(InputDefinition $definition, array $options = []); abstract protected function describeCommand(Command $command, array $options = []); abstract protected function describeApplication(Application $application, array $options = []); } writeData($this->getInputArgumentData($argument), $options); } protected function describeInputOption(InputOption $option, array $options = []) { $this->writeData($this->getInputOptionData($option), $options); if ($option->isNegatable()) { $this->writeData($this->getInputOptionData($option, true), $options); } } protected function describeInputDefinition(InputDefinition $definition, array $options = []) { $this->writeData($this->getInputDefinitionData($definition), $options); } protected function describeCommand(Command $command, array $options = []) { $this->writeData($this->getCommandData($command, $options['short'] ?? false), $options); } protected function describeApplication(Application $application, array $options = []) { $describedNamespace = $options['namespace'] ?? null; $description = new ApplicationDescription($application, $describedNamespace, true); $commands = []; foreach ($description->getCommands() as $command) { $commands[] = $this->getCommandData($command, $options['short'] ?? false); } $data = []; if ('UNKNOWN' !== $application->getName()) { $data['application']['name'] = $application->getName(); if ('UNKNOWN' !== $application->getVersion()) { $data['application']['version'] = $application->getVersion(); } } $data['commands'] = $commands; if ($describedNamespace) { $data['namespace'] = $describedNamespace; } else { $data['namespaces'] = array_values($description->getNamespaces()); } $this->writeData($data, $options); } private function writeData(array $data, array $options) { $flags = $options['json_encoding'] ?? 0; $this->write(json_encode($data, $flags)); } private function getInputArgumentData(InputArgument $argument): array { return [ 'name' => $argument->getName(), 'is_required' => $argument->isRequired(), 'is_array' => $argument->isArray(), 'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $argument->getDescription()), 'default' => \INF === $argument->getDefault() ? 'INF' : $argument->getDefault(), ]; } private function getInputOptionData(InputOption $option, bool $negated = false): array { return $negated ? [ 'name' => '--no-'.$option->getName(), 'shortcut' => '', 'accept_value' => false, 'is_value_required' => false, 'is_multiple' => false, 'description' => 'Negate the "--'.$option->getName().'" option', 'default' => false, ] : [ 'name' => '--'.$option->getName(), 'shortcut' => $option->getShortcut() ? '-'.str_replace('|', '|-', $option->getShortcut()) : '', 'accept_value' => $option->acceptValue(), 'is_value_required' => $option->isValueRequired(), 'is_multiple' => $option->isArray(), 'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $option->getDescription()), 'default' => \INF === $option->getDefault() ? 'INF' : $option->getDefault(), ]; } private function getInputDefinitionData(InputDefinition $definition): array { $inputArguments = []; foreach ($definition->getArguments() as $name => $argument) { $inputArguments[$name] = $this->getInputArgumentData($argument); } $inputOptions = []; foreach ($definition->getOptions() as $name => $option) { $inputOptions[$name] = $this->getInputOptionData($option); if ($option->isNegatable()) { $inputOptions['no-'.$name] = $this->getInputOptionData($option, true); } } return ['arguments' => $inputArguments, 'options' => $inputOptions]; } private function getCommandData(Command $command, bool $short = false): array { $data = [ 'name' => $command->getName(), 'description' => $command->getDescription(), ]; if ($short) { $data += [ 'usage' => $command->getAliases(), ]; } else { $command->mergeApplicationDefinition(false); $data += [ 'usage' => array_merge([$command->getSynopsis()], $command->getUsages(), $command->getAliases()), 'help' => $command->getProcessedHelp(), 'definition' => $this->getInputDefinitionData($command->getDefinition()), ]; } $data['hidden'] = $command->isHidden(); return $data; } } isDecorated(); $output->setDecorated(false); parent::describe($output, $object, $options); $output->setDecorated($decorated); } protected function write(string $content, bool $decorated = true) { parent::write($content, $decorated); } protected function describeInputArgument(InputArgument $argument, array $options = []) { $this->write( '#### `'.($argument->getName() ?: '')."`\n\n" .($argument->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $argument->getDescription())."\n\n" : '') .'* Is required: '.($argument->isRequired() ? 'yes' : 'no')."\n" .'* Is array: '.($argument->isArray() ? 'yes' : 'no')."\n" .'* Default: `'.str_replace("\n", '', var_export($argument->getDefault(), true)).'`' ); } protected function describeInputOption(InputOption $option, array $options = []) { $name = '--'.$option->getName(); if ($option->isNegatable()) { $name .= '|--no-'.$option->getName(); } if ($option->getShortcut()) { $name .= '|-'.str_replace('|', '|-', $option->getShortcut()).''; } $this->write( '#### `'.$name.'`'."\n\n" .($option->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $option->getDescription())."\n\n" : '') .'* Accept value: '.($option->acceptValue() ? 'yes' : 'no')."\n" .'* Is value required: '.($option->isValueRequired() ? 'yes' : 'no')."\n" .'* Is multiple: '.($option->isArray() ? 'yes' : 'no')."\n" .'* Is negatable: '.($option->isNegatable() ? 'yes' : 'no')."\n" .'* Default: `'.str_replace("\n", '', var_export($option->getDefault(), true)).'`' ); } protected function describeInputDefinition(InputDefinition $definition, array $options = []) { if ($showArguments = \count($definition->getArguments()) > 0) { $this->write('### Arguments'); foreach ($definition->getArguments() as $argument) { $this->write("\n\n"); if (null !== $describeInputArgument = $this->describeInputArgument($argument)) { $this->write($describeInputArgument); } } } if (\count($definition->getOptions()) > 0) { if ($showArguments) { $this->write("\n\n"); } $this->write('### Options'); foreach ($definition->getOptions() as $option) { $this->write("\n\n"); if (null !== $describeInputOption = $this->describeInputOption($option)) { $this->write($describeInputOption); } } } } protected function describeCommand(Command $command, array $options = []) { if ($options['short'] ?? false) { $this->write( '`'.$command->getName()."`\n" .str_repeat('-', Helper::width($command->getName()) + 2)."\n\n" .($command->getDescription() ? $command->getDescription()."\n\n" : '') .'### Usage'."\n\n" .array_reduce($command->getAliases(), function ($carry, $usage) { return $carry.'* `'.$usage.'`'."\n"; }) ); return; } $command->mergeApplicationDefinition(false); $this->write( '`'.$command->getName()."`\n" .str_repeat('-', Helper::width($command->getName()) + 2)."\n\n" .($command->getDescription() ? $command->getDescription()."\n\n" : '') .'### Usage'."\n\n" .array_reduce(array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()), function ($carry, $usage) { return $carry.'* `'.$usage.'`'."\n"; }) ); if ($help = $command->getProcessedHelp()) { $this->write("\n"); $this->write($help); } $definition = $command->getDefinition(); if ($definition->getOptions() || $definition->getArguments()) { $this->write("\n\n"); $this->describeInputDefinition($definition); } } protected function describeApplication(Application $application, array $options = []) { $describedNamespace = $options['namespace'] ?? null; $description = new ApplicationDescription($application, $describedNamespace); $title = $this->getApplicationTitle($application); $this->write($title."\n".str_repeat('=', Helper::width($title))); foreach ($description->getNamespaces() as $namespace) { if (ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) { $this->write("\n\n"); $this->write('**'.$namespace['id'].':**'); } $this->write("\n\n"); $this->write(implode("\n", array_map(function ($commandName) use ($description) { return sprintf('* [`%s`](#%s)', $commandName, str_replace(':', '', $description->getCommand($commandName)->getName())); }, $namespace['commands']))); } foreach ($description->getCommands() as $command) { $this->write("\n\n"); if (null !== $describeCommand = $this->describeCommand($command, $options)) { $this->write($describeCommand); } } } private function getApplicationTitle(Application $application): string { if ('UNKNOWN' !== $application->getName()) { if ('UNKNOWN' !== $application->getVersion()) { return sprintf('%s %s', $application->getName(), $application->getVersion()); } return $application->getName(); } return 'Console Tool'; } } getDefault() && (!\is_array($argument->getDefault()) || \count($argument->getDefault()))) { $default = sprintf(' [default: %s]', $this->formatDefaultValue($argument->getDefault())); } else { $default = ''; } $totalWidth = $options['total_width'] ?? Helper::width($argument->getName()); $spacingWidth = $totalWidth - \strlen($argument->getName()); $this->writeText(sprintf(' %s %s%s%s', $argument->getName(), str_repeat(' ', $spacingWidth), preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $argument->getDescription()), $default ), $options); } protected function describeInputOption(InputOption $option, array $options = []) { if ($option->acceptValue() && null !== $option->getDefault() && (!\is_array($option->getDefault()) || \count($option->getDefault()))) { $default = sprintf(' [default: %s]', $this->formatDefaultValue($option->getDefault())); } else { $default = ''; } $value = ''; if ($option->acceptValue()) { $value = '='.strtoupper($option->getName()); if ($option->isValueOptional()) { $value = '['.$value.']'; } } $totalWidth = $options['total_width'] ?? $this->calculateTotalWidthForOptions([$option]); $synopsis = sprintf('%s%s', $option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : ' ', sprintf($option->isNegatable() ? '--%1$s|--no-%1$s' : '--%1$s%2$s', $option->getName(), $value) ); $spacingWidth = $totalWidth - Helper::width($synopsis); $this->writeText(sprintf(' %s %s%s%s%s', $synopsis, str_repeat(' ', $spacingWidth), preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $option->getDescription()), $default, $option->isArray() ? ' (multiple values allowed)' : '' ), $options); } protected function describeInputDefinition(InputDefinition $definition, array $options = []) { $totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions()); foreach ($definition->getArguments() as $argument) { $totalWidth = max($totalWidth, Helper::width($argument->getName())); } if ($definition->getArguments()) { $this->writeText('Arguments:', $options); $this->writeText("\n"); foreach ($definition->getArguments() as $argument) { $this->describeInputArgument($argument, array_merge($options, ['total_width' => $totalWidth])); $this->writeText("\n"); } } if ($definition->getArguments() && $definition->getOptions()) { $this->writeText("\n"); } if ($definition->getOptions()) { $laterOptions = []; $this->writeText('Options:', $options); foreach ($definition->getOptions() as $option) { if (\strlen($option->getShortcut() ?? '') > 1) { $laterOptions[] = $option; continue; } $this->writeText("\n"); $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth])); } foreach ($laterOptions as $option) { $this->writeText("\n"); $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth])); } } } protected function describeCommand(Command $command, array $options = []) { $command->mergeApplicationDefinition(false); if ($description = $command->getDescription()) { $this->writeText('Description:', $options); $this->writeText("\n"); $this->writeText(' '.$description); $this->writeText("\n\n"); } $this->writeText('Usage:', $options); foreach (array_merge([$command->getSynopsis(true)], $command->getAliases(), $command->getUsages()) as $usage) { $this->writeText("\n"); $this->writeText(' '.OutputFormatter::escape($usage), $options); } $this->writeText("\n"); $definition = $command->getDefinition(); if ($definition->getOptions() || $definition->getArguments()) { $this->writeText("\n"); $this->describeInputDefinition($definition, $options); $this->writeText("\n"); } $help = $command->getProcessedHelp(); if ($help && $help !== $description) { $this->writeText("\n"); $this->writeText('Help:', $options); $this->writeText("\n"); $this->writeText(' '.str_replace("\n", "\n ", $help), $options); $this->writeText("\n"); } } protected function describeApplication(Application $application, array $options = []) { $describedNamespace = $options['namespace'] ?? null; $description = new ApplicationDescription($application, $describedNamespace); if (isset($options['raw_text']) && $options['raw_text']) { $width = $this->getColumnWidth($description->getCommands()); foreach ($description->getCommands() as $command) { $this->writeText(sprintf("%-{$width}s %s", $command->getName(), $command->getDescription()), $options); $this->writeText("\n"); } } else { if ('' != $help = $application->getHelp()) { $this->writeText("$help\n\n", $options); } $this->writeText("Usage:\n", $options); $this->writeText(" command [options] [arguments]\n\n", $options); $this->describeInputDefinition(new InputDefinition($application->getDefinition()->getOptions()), $options); $this->writeText("\n"); $this->writeText("\n"); $commands = $description->getCommands(); $namespaces = $description->getNamespaces(); if ($describedNamespace && $namespaces) { $describedNamespaceInfo = reset($namespaces); foreach ($describedNamespaceInfo['commands'] as $name) { $commands[$name] = $description->getCommand($name); } } $width = $this->getColumnWidth(array_merge(...array_values(array_map(function ($namespace) use ($commands) { return array_intersect($namespace['commands'], array_keys($commands)); }, array_values($namespaces))))); if ($describedNamespace) { $this->writeText(sprintf('Available commands for the "%s" namespace:', $describedNamespace), $options); } else { $this->writeText('Available commands:', $options); } foreach ($namespaces as $namespace) { $namespace['commands'] = array_filter($namespace['commands'], function ($name) use ($commands) { return isset($commands[$name]); }); if (!$namespace['commands']) { continue; } if (!$describedNamespace && ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) { $this->writeText("\n"); $this->writeText(' '.$namespace['id'].'', $options); } foreach ($namespace['commands'] as $name) { $this->writeText("\n"); $spacingWidth = $width - Helper::width($name); $command = $commands[$name]; $commandAliases = $name === $command->getName() ? $this->getCommandAliasesText($command) : ''; $this->writeText(sprintf(' %s%s%s', $name, str_repeat(' ', $spacingWidth), $commandAliases.$command->getDescription()), $options); } } $this->writeText("\n"); } } private function writeText(string $content, array $options = []) { $this->write( isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content, isset($options['raw_output']) ? !$options['raw_output'] : true ); } private function getCommandAliasesText(Command $command): string { $text = ''; $aliases = $command->getAliases(); if ($aliases) { $text = '['.implode('|', $aliases).'] '; } return $text; } private function formatDefaultValue($default): string { if (\INF === $default) { return 'INF'; } if (\is_string($default)) { $default = OutputFormatter::escape($default); } elseif (\is_array($default)) { foreach ($default as $key => $value) { if (\is_string($value)) { $default[$key] = OutputFormatter::escape($value); } } } return str_replace('\\\\', '\\', json_encode($default, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE)); } private function getColumnWidth(array $commands): int { $widths = []; foreach ($commands as $command) { if ($command instanceof Command) { $widths[] = Helper::width($command->getName()); foreach ($command->getAliases() as $alias) { $widths[] = Helper::width($alias); } } else { $widths[] = Helper::width($command); } } return $widths ? max($widths) + 2 : 0; } private function calculateTotalWidthForOptions(array $options): int { $totalWidth = 0; foreach ($options as $option) { $nameLength = 1 + max(Helper::width($option->getShortcut()), 1) + 4 + Helper::width($option->getName()); if ($option->isNegatable()) { $nameLength += 6 + Helper::width($option->getName()); } elseif ($option->acceptValue()) { $valueLength = 1 + Helper::width($option->getName()); $valueLength += $option->isValueOptional() ? 2 : 0; $nameLength += $valueLength; } $totalWidth = max($totalWidth, $nameLength); } return $totalWidth; } } appendChild($definitionXML = $dom->createElement('definition')); $definitionXML->appendChild($argumentsXML = $dom->createElement('arguments')); foreach ($definition->getArguments() as $argument) { $this->appendDocument($argumentsXML, $this->getInputArgumentDocument($argument)); } $definitionXML->appendChild($optionsXML = $dom->createElement('options')); foreach ($definition->getOptions() as $option) { $this->appendDocument($optionsXML, $this->getInputOptionDocument($option)); } return $dom; } public function getCommandDocument(Command $command, bool $short = false): \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($commandXML = $dom->createElement('command')); $commandXML->setAttribute('id', $command->getName()); $commandXML->setAttribute('name', $command->getName()); $commandXML->setAttribute('hidden', $command->isHidden() ? 1 : 0); $commandXML->appendChild($usagesXML = $dom->createElement('usages')); $commandXML->appendChild($descriptionXML = $dom->createElement('description')); $descriptionXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getDescription()))); if ($short) { foreach ($command->getAliases() as $usage) { $usagesXML->appendChild($dom->createElement('usage', $usage)); } } else { $command->mergeApplicationDefinition(false); foreach (array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()) as $usage) { $usagesXML->appendChild($dom->createElement('usage', $usage)); } $commandXML->appendChild($helpXML = $dom->createElement('help')); $helpXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getProcessedHelp()))); $definitionXML = $this->getInputDefinitionDocument($command->getDefinition()); $this->appendDocument($commandXML, $definitionXML->getElementsByTagName('definition')->item(0)); } return $dom; } public function getApplicationDocument(Application $application, ?string $namespace = null, bool $short = false): \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($rootXml = $dom->createElement('symfony')); if ('UNKNOWN' !== $application->getName()) { $rootXml->setAttribute('name', $application->getName()); if ('UNKNOWN' !== $application->getVersion()) { $rootXml->setAttribute('version', $application->getVersion()); } } $rootXml->appendChild($commandsXML = $dom->createElement('commands')); $description = new ApplicationDescription($application, $namespace, true); if ($namespace) { $commandsXML->setAttribute('namespace', $namespace); } foreach ($description->getCommands() as $command) { $this->appendDocument($commandsXML, $this->getCommandDocument($command, $short)); } if (!$namespace) { $rootXml->appendChild($namespacesXML = $dom->createElement('namespaces')); foreach ($description->getNamespaces() as $namespaceDescription) { $namespacesXML->appendChild($namespaceArrayXML = $dom->createElement('namespace')); $namespaceArrayXML->setAttribute('id', $namespaceDescription['id']); foreach ($namespaceDescription['commands'] as $name) { $namespaceArrayXML->appendChild($commandXML = $dom->createElement('command')); $commandXML->appendChild($dom->createTextNode($name)); } } } return $dom; } protected function describeInputArgument(InputArgument $argument, array $options = []) { $this->writeDocument($this->getInputArgumentDocument($argument)); } protected function describeInputOption(InputOption $option, array $options = []) { $this->writeDocument($this->getInputOptionDocument($option)); } protected function describeInputDefinition(InputDefinition $definition, array $options = []) { $this->writeDocument($this->getInputDefinitionDocument($definition)); } protected function describeCommand(Command $command, array $options = []) { $this->writeDocument($this->getCommandDocument($command, $options['short'] ?? false)); } protected function describeApplication(Application $application, array $options = []) { $this->writeDocument($this->getApplicationDocument($application, $options['namespace'] ?? null, $options['short'] ?? false)); } private function appendDocument(\DOMNode $parentNode, \DOMNode $importedParent) { foreach ($importedParent->childNodes as $childNode) { $parentNode->appendChild($parentNode->ownerDocument->importNode($childNode, true)); } } private function writeDocument(\DOMDocument $dom) { $dom->formatOutput = true; $this->write($dom->saveXML()); } private function getInputArgumentDocument(InputArgument $argument): \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($objectXML = $dom->createElement('argument')); $objectXML->setAttribute('name', $argument->getName()); $objectXML->setAttribute('is_required', $argument->isRequired() ? 1 : 0); $objectXML->setAttribute('is_array', $argument->isArray() ? 1 : 0); $objectXML->appendChild($descriptionXML = $dom->createElement('description')); $descriptionXML->appendChild($dom->createTextNode($argument->getDescription())); $objectXML->appendChild($defaultsXML = $dom->createElement('defaults')); $defaults = \is_array($argument->getDefault()) ? $argument->getDefault() : (\is_bool($argument->getDefault()) ? [var_export($argument->getDefault(), true)] : ($argument->getDefault() ? [$argument->getDefault()] : [])); foreach ($defaults as $default) { $defaultsXML->appendChild($defaultXML = $dom->createElement('default')); $defaultXML->appendChild($dom->createTextNode($default)); } return $dom; } private function getInputOptionDocument(InputOption $option): \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($objectXML = $dom->createElement('option')); $objectXML->setAttribute('name', '--'.$option->getName()); $pos = strpos($option->getShortcut() ?? '', '|'); if (false !== $pos) { $objectXML->setAttribute('shortcut', '-'.substr($option->getShortcut(), 0, $pos)); $objectXML->setAttribute('shortcuts', '-'.str_replace('|', '|-', $option->getShortcut())); } else { $objectXML->setAttribute('shortcut', $option->getShortcut() ? '-'.$option->getShortcut() : ''); } $objectXML->setAttribute('accept_value', $option->acceptValue() ? 1 : 0); $objectXML->setAttribute('is_value_required', $option->isValueRequired() ? 1 : 0); $objectXML->setAttribute('is_multiple', $option->isArray() ? 1 : 0); $objectXML->appendChild($descriptionXML = $dom->createElement('description')); $descriptionXML->appendChild($dom->createTextNode($option->getDescription())); if ($option->acceptValue()) { $defaults = \is_array($option->getDefault()) ? $option->getDefault() : (\is_bool($option->getDefault()) ? [var_export($option->getDefault(), true)] : ($option->getDefault() ? [$option->getDefault()] : [])); $objectXML->appendChild($defaultsXML = $dom->createElement('defaults')); if (!empty($defaults)) { foreach ($defaults as $default) { $defaultsXML->appendChild($defaultXML = $dom->createElement('default')); $defaultXML->appendChild($dom->createTextNode($default)); } } } if ($option->isNegatable()) { $dom->appendChild($objectXML = $dom->createElement('option')); $objectXML->setAttribute('name', '--no-'.$option->getName()); $objectXML->setAttribute('shortcut', ''); $objectXML->setAttribute('accept_value', 0); $objectXML->setAttribute('is_value_required', 0); $objectXML->setAttribute('is_multiple', 0); $objectXML->appendChild($descriptionXML = $dom->createElement('description')); $descriptionXML->appendChild($dom->createTextNode('Negate the "--'.$option->getName().'" option')); } return $dom; } } commandShouldRun = false; } public function enableCommand(): bool { return $this->commandShouldRun = true; } public function commandShouldRun(): bool { return $this->commandShouldRun; } } error = $error; } public function getError(): \Throwable { return $this->error; } public function setError(\Throwable $error): void { $this->error = $error; } public function setExitCode(int $exitCode): void { $this->exitCode = $exitCode; $r = new \ReflectionProperty($this->error, 'code'); $r->setAccessible(true); $r->setValue($this->error, $this->exitCode); } public function getExitCode(): int { return $this->exitCode ?? (\is_int($this->error->getCode()) && 0 !== $this->error->getCode() ? $this->error->getCode() : 1); } } command = $command; $this->input = $input; $this->output = $output; } public function getCommand() { return $this->command; } public function getInput() { return $this->input; } public function getOutput() { return $this->output; } } handlingSignal = $handlingSignal; } public function getHandlingSignal(): int { return $this->handlingSignal; } } setExitCode($exitCode); } public function setExitCode(int $exitCode): void { $this->exitCode = $exitCode; } public function getExitCode(): int { return $this->exitCode; } } logger = $logger; } public function onConsoleError(ConsoleErrorEvent $event) { if (null === $this->logger) { return; } $error = $event->getError(); if (!$inputString = $this->getInputString($event)) { $this->logger->critical('An error occurred while using the console. Message: "{message}"', ['exception' => $error, 'message' => $error->getMessage()]); return; } $this->logger->critical('Error thrown while running command "{command}". Message: "{message}"', ['exception' => $error, 'command' => $inputString, 'message' => $error->getMessage()]); } public function onConsoleTerminate(ConsoleTerminateEvent $event) { if (null === $this->logger) { return; } $exitCode = $event->getExitCode(); if (0 === $exitCode) { return; } if (!$inputString = $this->getInputString($event)) { $this->logger->debug('The console exited with code "{code}"', ['code' => $exitCode]); return; } $this->logger->debug('Command "{command}" exited with code "{code}"', ['command' => $inputString, 'code' => $exitCode]); } public static function getSubscribedEvents() { return [ ConsoleEvents::ERROR => ['onConsoleError', -128], ConsoleEvents::TERMINATE => ['onConsoleTerminate', -128], ]; } private static function getInputString(ConsoleEvent $event): ?string { $commandName = $event->getCommand() ? $event->getCommand()->getName() : null; $input = $event->getInput(); if (method_exists($input, '__toString')) { if ($commandName) { return str_replace(["'$commandName'", "\"$commandName\""], $commandName, (string) $input); } return (string) $input; } return $commandName; } } alternatives = $alternatives; } public function getAlternatives() { return $this->alternatives; } } style ?? $this->style = new NullOutputFormatterStyle(); } public function hasStyle(string $name): bool { return false; } public function isDecorated(): bool { return false; } public function setDecorated(bool $decorated): void { } public function setStyle(string $name, OutputFormatterStyleInterface $style): void { } } styleStack = clone $this->styleStack; foreach ($this->styles as $key => $value) { $this->styles[$key] = clone $value; } } public static function escape(string $text) { $text = preg_replace('/([^\\\\]|^)([<>])/', '$1\\\\$2', $text); return self::escapeTrailingBackslash($text); } public static function escapeTrailingBackslash(string $text): string { if (str_ends_with($text, '\\')) { $len = \strlen($text); $text = rtrim($text, '\\'); $text = str_replace("\0", '', $text); $text .= str_repeat("\0", $len - \strlen($text)); } return $text; } public function __construct(bool $decorated = false, array $styles = []) { $this->decorated = $decorated; $this->setStyle('error', new OutputFormatterStyle('white', 'red')); $this->setStyle('info', new OutputFormatterStyle('green')); $this->setStyle('comment', new OutputFormatterStyle('yellow')); $this->setStyle('question', new OutputFormatterStyle('black', 'cyan')); foreach ($styles as $name => $style) { $this->setStyle($name, $style); } $this->styleStack = new OutputFormatterStyleStack(); } public function setDecorated(bool $decorated) { $this->decorated = $decorated; } public function isDecorated() { return $this->decorated; } public function setStyle(string $name, OutputFormatterStyleInterface $style) { $this->styles[strtolower($name)] = $style; } public function hasStyle(string $name) { return isset($this->styles[strtolower($name)]); } public function getStyle(string $name) { if (!$this->hasStyle($name)) { throw new InvalidArgumentException(sprintf('Undefined style: "%s".', $name)); } return $this->styles[strtolower($name)]; } public function format(?string $message) { return $this->formatAndWrap($message, 0); } public function formatAndWrap(?string $message, int $width) { if (null === $message) { return ''; } $offset = 0; $output = ''; $openTagRegex = '[a-z](?:[^\\\\<>]*+ | \\\\.)*'; $closeTagRegex = '[a-z][^<>]*+'; $currentLineLength = 0; preg_match_all("#<(($openTagRegex) | /($closeTagRegex)?)>#ix", $message, $matches, \PREG_OFFSET_CAPTURE); foreach ($matches[0] as $i => $match) { $pos = $match[1]; $text = $match[0]; if (0 != $pos && '\\' == $message[$pos - 1]) { continue; } $output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset), $output, $width, $currentLineLength); $offset = $pos + \strlen($text); if ($open = '/' != $text[1]) { $tag = $matches[1][$i][0]; } else { $tag = $matches[3][$i][0] ?? ''; } if (!$open && !$tag) { $this->styleStack->pop(); } elseif (null === $style = $this->createStyleFromString($tag)) { $output .= $this->applyCurrentStyle($text, $output, $width, $currentLineLength); } elseif ($open) { $this->styleStack->push($style); } else { $this->styleStack->pop($style); } } $output .= $this->applyCurrentStyle(substr($message, $offset), $output, $width, $currentLineLength); return strtr($output, ["\0" => '\\', '\\<' => '<', '\\>' => '>']); } public function getStyleStack() { return $this->styleStack; } private function createStyleFromString(string $string): ?OutputFormatterStyleInterface { if (isset($this->styles[$string])) { return $this->styles[$string]; } if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', $string, $matches, \PREG_SET_ORDER)) { return null; } $style = new OutputFormatterStyle(); foreach ($matches as $match) { array_shift($match); $match[0] = strtolower($match[0]); if ('fg' == $match[0]) { $style->setForeground(strtolower($match[1])); } elseif ('bg' == $match[0]) { $style->setBackground(strtolower($match[1])); } elseif ('href' === $match[0]) { $url = preg_replace('{\\\\([<>])}', '$1', $match[1]); $style->setHref($url); } elseif ('options' === $match[0]) { preg_match_all('([^,;]+)', strtolower($match[1]), $options); $options = array_shift($options); foreach ($options as $option) { $style->setOption($option); } } else { return null; } } return $style; } private function applyCurrentStyle(string $text, string $current, int $width, int &$currentLineLength): string { if ('' === $text) { return ''; } if (!$width) { return $this->isDecorated() ? $this->styleStack->getCurrent()->apply($text) : $text; } if (!$currentLineLength && '' !== $current) { $text = ltrim($text); } if ($currentLineLength) { $prefix = substr($text, 0, $i = $width - $currentLineLength)."\n"; $text = substr($text, $i); } else { $prefix = ''; } preg_match('~(\\n)$~', $text, $matches); $text = $prefix.$this->addLineBreaks($text, $width); $text = rtrim($text, "\n").($matches[1] ?? ''); if (!$currentLineLength && '' !== $current && "\n" !== substr($current, -1)) { $text = "\n".$text; } $lines = explode("\n", $text); foreach ($lines as $line) { $currentLineLength += \strlen($line); if ($width <= $currentLineLength) { $currentLineLength = 0; } } if ($this->isDecorated()) { foreach ($lines as $i => $line) { $lines[$i] = $this->styleStack->getCurrent()->apply($line); } } return implode("\n", $lines); } private function addLineBreaks(string $text, int $width): string { $encoding = mb_detect_encoding($text, null, true) ?: 'UTF-8'; return b($text)->toCodePointString($encoding)->wordwrap($width, "\n", true)->toByteString($encoding); } } color = new Color($this->foreground = $foreground ?: '', $this->background = $background ?: '', $this->options = $options); } public function setForeground(?string $color = null) { $this->color = new Color($this->foreground = $color ?: '', $this->background, $this->options); } public function setBackground(?string $color = null) { $this->color = new Color($this->foreground, $this->background = $color ?: '', $this->options); } public function setHref(string $url): void { $this->href = $url; } public function setOption(string $option) { $this->options[] = $option; $this->color = new Color($this->foreground, $this->background, $this->options); } public function unsetOption(string $option) { $pos = array_search($option, $this->options); if (false !== $pos) { unset($this->options[$pos]); } $this->color = new Color($this->foreground, $this->background, $this->options); } public function setOptions(array $options) { $this->color = new Color($this->foreground, $this->background, $this->options = $options); } public function apply(string $text) { if (null === $this->handlesHrefGracefully) { $this->handlesHrefGracefully = 'JetBrains-JediTerm' !== getenv('TERMINAL_EMULATOR') && (!getenv('KONSOLE_VERSION') || (int) getenv('KONSOLE_VERSION') > 201100) && !isset($_SERVER['IDEA_INITIAL_DIRECTORY']); } if (null !== $this->href && $this->handlesHrefGracefully) { $text = "\033]8;;$this->href\033\\$text\033]8;;\033\\"; } return $this->color->apply($text); } } emptyStyle = $emptyStyle ?? new OutputFormatterStyle(); $this->reset(); } public function reset() { $this->styles = []; } public function push(OutputFormatterStyleInterface $style) { $this->styles[] = $style; } public function pop(?OutputFormatterStyleInterface $style = null) { if (empty($this->styles)) { return $this->emptyStyle; } if (null === $style) { return array_pop($this->styles); } foreach (array_reverse($this->styles, true) as $index => $stackedStyle) { if ($style->apply('') === $stackedStyle->apply('')) { $this->styles = \array_slice($this->styles, 0, $index); return $stackedStyle; } } throw new InvalidArgumentException('Incorrectly nested style tag found.'); } public function getCurrent() { if (empty($this->styles)) { return $this->emptyStyle; } return $this->styles[\count($this->styles) - 1]; } public function setEmptyStyle(OutputFormatterStyleInterface $emptyStyle) { $this->emptyStyle = $emptyStyle; return $this; } public function getEmptyStyle() { return $this->emptyStyle; } } started[$id] = ['border' => ++$this->count % \count(self::COLORS)]; return sprintf("%s %s %s\n", $this->getBorder($id), $prefix, $message); } public function progress(string $id, string $buffer, bool $error = false, string $prefix = 'OUT', string $errorPrefix = 'ERR') { $message = ''; if ($error) { if (isset($this->started[$id]['out'])) { $message .= "\n"; unset($this->started[$id]['out']); } if (!isset($this->started[$id]['err'])) { $message .= sprintf('%s %s ', $this->getBorder($id), $errorPrefix); $this->started[$id]['err'] = true; } $message .= str_replace("\n", sprintf("\n%s %s ", $this->getBorder($id), $errorPrefix), $buffer); } else { if (isset($this->started[$id]['err'])) { $message .= "\n"; unset($this->started[$id]['err']); } if (!isset($this->started[$id]['out'])) { $message .= sprintf('%s %s ', $this->getBorder($id), $prefix); $this->started[$id]['out'] = true; } $message .= str_replace("\n", sprintf("\n%s %s ", $this->getBorder($id), $prefix), $buffer); } return $message; } public function stop(string $id, string $message, bool $successful, string $prefix = 'RES') { $trailingEOL = isset($this->started[$id]['out']) || isset($this->started[$id]['err']) ? "\n" : ''; if ($successful) { return sprintf("%s%s %s %s\n", $trailingEOL, $this->getBorder($id), $prefix, $message); } $message = sprintf("%s%s %s %s\n", $trailingEOL, $this->getBorder($id), $prefix, $message); unset($this->started[$id]['out'], $this->started[$id]['err']); return $message; } private function getBorder(string $id): string { return sprintf(' ', self::COLORS[$this->started[$id]['border']]); } public function getName() { return 'debug_formatter'; } } register('txt', new TextDescriptor()) ->register('xml', new XmlDescriptor()) ->register('json', new JsonDescriptor()) ->register('md', new MarkdownDescriptor()) ; } public function describe(OutputInterface $output, ?object $object, array $options = []) { $options = array_merge([ 'raw_text' => false, 'format' => 'txt', ], $options); if (!isset($this->descriptors[$options['format']])) { throw new InvalidArgumentException(sprintf('Unsupported format "%s".', $options['format'])); } $descriptor = $this->descriptors[$options['format']]; $descriptor->describe($output, $object, $options); } public function register(string $format, DescriptorInterface $descriptor) { $this->descriptors[$format] = $descriptor; return $this; } public function getName() { return 'descriptor'; } public function getFormats(): array { return array_keys($this->descriptors); } } output = $output; $this->dumper = $dumper; $this->cloner = $cloner; if (class_exists(CliDumper::class)) { $this->handler = function ($var): string { $dumper = $this->dumper ?? $this->dumper = new CliDumper(null, null, CliDumper::DUMP_LIGHT_ARRAY | CliDumper::DUMP_COMMA_SEPARATOR); $dumper->setColors($this->output->isDecorated()); return rtrim($dumper->dump(($this->cloner ?? $this->cloner = new VarCloner())->cloneVar($var)->withRefHandles(false), true)); }; } else { $this->handler = function ($var): string { switch (true) { case null === $var: return 'null'; case true === $var: return 'true'; case false === $var: return 'false'; case \is_string($var): return '"'.$var.'"'; default: return rtrim(print_r($var, true)); } }; } } public function __invoke($var): string { return ($this->handler)($var); } } [%s] %s', $style, $section, $style, $message); } public function formatBlock($messages, string $style, bool $large = false) { if (!\is_array($messages)) { $messages = [$messages]; } $len = 0; $lines = []; foreach ($messages as $message) { $message = OutputFormatter::escape($message); $lines[] = sprintf($large ? ' %s ' : ' %s ', $message); $len = max(self::width($message) + ($large ? 4 : 2), $len); } $messages = $large ? [str_repeat(' ', $len)] : []; for ($i = 0; isset($lines[$i]); ++$i) { $messages[] = $lines[$i].str_repeat(' ', $len - self::width($lines[$i])); } if ($large) { $messages[] = str_repeat(' ', $len); } for ($i = 0; isset($messages[$i]); ++$i) { $messages[$i] = sprintf('<%s>%s', $style, $messages[$i], $style); } return implode("\n", $messages); } public function truncate(string $message, int $length, string $suffix = '...') { $computedLength = $length - self::width($suffix); if ($computedLength > self::width($message)) { return $message; } return self::substr($message, 0, $length).$suffix; } public function getName() { return 'formatter'; } } helperSet = $helperSet; } public function getHelperSet() { return $this->helperSet; } public static function strlen(?string $string) { trigger_deprecation('symfony/console', '5.3', 'Method "%s()" is deprecated and will be removed in Symfony 6.0. Use Helper::width() or Helper::length() instead.', __METHOD__); return self::width($string); } public static function width(?string $string): int { $string ?? $string = ''; if (preg_match('//u', $string)) { return (new UnicodeString($string))->width(false); } if (false === $encoding = mb_detect_encoding($string, null, true)) { return \strlen($string); } return mb_strwidth($string, $encoding); } public static function length(?string $string): int { $string ?? $string = ''; if (preg_match('//u', $string)) { return (new UnicodeString($string))->length(); } if (false === $encoding = mb_detect_encoding($string, null, true)) { return \strlen($string); } return mb_strlen($string, $encoding); } public static function substr(?string $string, int $from, ?int $length = null) { $string ?? $string = ''; if (false === $encoding = mb_detect_encoding($string, null, true)) { return substr($string, $from, $length); } return mb_substr($string, $from, $length, $encoding); } public static function formatTime($secs) { static $timeFormats = [ [0, '< 1 sec'], [1, '1 sec'], [2, 'secs', 1], [60, '1 min'], [120, 'mins', 60], [3600, '1 hr'], [7200, 'hrs', 3600], [86400, '1 day'], [172800, 'days', 86400], ]; foreach ($timeFormats as $index => $format) { if ($secs >= $format[0]) { if ((isset($timeFormats[$index + 1]) && $secs < $timeFormats[$index + 1][0]) || $index == \count($timeFormats) - 1 ) { if (2 == \count($format)) { return $format[1]; } return floor($secs / $format[2]).' '.$format[1]; } } } } public static function formatMemory(int $memory) { if ($memory >= 1024 * 1024 * 1024) { return sprintf('%.1f GiB', $memory / 1024 / 1024 / 1024); } if ($memory >= 1024 * 1024) { return sprintf('%.1f MiB', $memory / 1024 / 1024); } if ($memory >= 1024) { return sprintf('%d KiB', $memory / 1024); } return sprintf('%d B', $memory); } public static function strlenWithoutDecoration(OutputFormatterInterface $formatter, ?string $string) { trigger_deprecation('symfony/console', '5.3', 'Method "%s()" is deprecated and will be removed in Symfony 6.0. Use Helper::removeDecoration() instead.', __METHOD__); return self::width(self::removeDecoration($formatter, $string)); } public static function removeDecoration(OutputFormatterInterface $formatter, ?string $string) { $isDecorated = $formatter->isDecorated(); $formatter->setDecorated(false); $string = $formatter->format($string ?? ''); $string = preg_replace("/\033\[[^m]*m/", '', $string ?? ''); $string = preg_replace('/\\033]8;[^;]*;[^\\033]*\\033\\\\/', '', $string ?? ''); $formatter->setDecorated($isDecorated); return $string; } } $helper) { $this->set($helper, \is_int($alias) ? null : $alias); } } public function set(HelperInterface $helper, ?string $alias = null) { $this->helpers[$helper->getName()] = $helper; if (null !== $alias) { $this->helpers[$alias] = $helper; } $helper->setHelperSet($this); } public function has(string $name) { return isset($this->helpers[$name]); } public function get(string $name) { if (!$this->has($name)) { throw new InvalidArgumentException(sprintf('The helper "%s" is not defined.', $name)); } return $this->helpers[$name]; } public function setCommand(?Command $command = null) { trigger_deprecation('symfony/console', '5.4', 'Method "%s()" is deprecated.', __METHOD__); $this->command = $command; } public function getCommand() { trigger_deprecation('symfony/console', '5.4', 'Method "%s()" is deprecated.', __METHOD__); return $this->command; } #[\ReturnTypeWillChange] public function getIterator() { return new \ArrayIterator($this->helpers); } } input = $input; } } getErrorOutput(); } $formatter = $this->getHelperSet()->get('debug_formatter'); if ($cmd instanceof Process) { $cmd = [$cmd]; } if (!\is_array($cmd)) { throw new \TypeError(sprintf('The "command" argument of "%s()" must be an array or a "%s" instance, "%s" given.', __METHOD__, Process::class, get_debug_type($cmd))); } if (\is_string($cmd[0] ?? null)) { $process = new Process($cmd); $cmd = []; } elseif (($cmd[0] ?? null) instanceof Process) { $process = $cmd[0]; unset($cmd[0]); } else { throw new \InvalidArgumentException(sprintf('Invalid command provided to "%s()": the command should be an array whose first element is either the path to the binary to run or a "Process" object.', __METHOD__)); } if ($verbosity <= $output->getVerbosity()) { $output->write($formatter->start(spl_object_hash($process), $this->escapeString($process->getCommandLine()))); } if ($output->isDebug()) { $callback = $this->wrapCallback($output, $process, $callback); } $process->run($callback, $cmd); if ($verbosity <= $output->getVerbosity()) { $message = $process->isSuccessful() ? 'Command ran successfully' : sprintf('%s Command did not run successfully', $process->getExitCode()); $output->write($formatter->stop(spl_object_hash($process), $message, $process->isSuccessful())); } if (!$process->isSuccessful() && null !== $error) { $output->writeln(sprintf('%s', $this->escapeString($error))); } return $process; } public function mustRun(OutputInterface $output, $cmd, ?string $error = null, ?callable $callback = null): Process { $process = $this->run($output, $cmd, $error, $callback); if (!$process->isSuccessful()) { throw new ProcessFailedException($process); } return $process; } public function wrapCallback(OutputInterface $output, Process $process, ?callable $callback = null): callable { if ($output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); } $formatter = $this->getHelperSet()->get('debug_formatter'); return function ($type, $buffer) use ($output, $process, $callback, $formatter) { $output->write($formatter->progress(spl_object_hash($process), $this->escapeString($buffer), Process::ERR === $type)); if (null !== $callback) { $callback($type, $buffer); } }; } private function escapeString(string $str): string { return str_replace('<', '\\<', $str); } public function getName(): string { return 'process'; } } '; private $format; private $internalFormat; private $redrawFreq = 1; private $writeCount; private $lastWriteTime; private $minSecondsBetweenRedraws = 0; private $maxSecondsBetweenRedraws = 1; private $output; private $step = 0; private $max; private $startTime; private $stepWidth; private $percent = 0.0; private $messages = []; private $overwrite = true; private $terminal; private $previousMessage; private $cursor; private static $formatters; private static $formats; public function __construct(OutputInterface $output, int $max = 0, float $minSecondsBetweenRedraws = 1 / 25) { if ($output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); } $this->output = $output; $this->setMaxSteps($max); $this->terminal = new Terminal(); if (0 < $minSecondsBetweenRedraws) { $this->redrawFreq = null; $this->minSecondsBetweenRedraws = $minSecondsBetweenRedraws; } if (!$this->output->isDecorated()) { $this->overwrite = false; $this->redrawFreq = null; } $this->startTime = time(); $this->cursor = new Cursor($output); } public static function setPlaceholderFormatterDefinition(string $name, callable $callable): void { if (!self::$formatters) { self::$formatters = self::initPlaceholderFormatters(); } self::$formatters[$name] = $callable; } public static function getPlaceholderFormatterDefinition(string $name): ?callable { if (!self::$formatters) { self::$formatters = self::initPlaceholderFormatters(); } return self::$formatters[$name] ?? null; } public static function setFormatDefinition(string $name, string $format): void { if (!self::$formats) { self::$formats = self::initFormats(); } self::$formats[$name] = $format; } public static function getFormatDefinition(string $name): ?string { if (!self::$formats) { self::$formats = self::initFormats(); } return self::$formats[$name] ?? null; } public function setMessage(string $message, string $name = 'message') { $this->messages[$name] = $message; } public function getMessage(string $name = 'message') { return $this->messages[$name] ?? null; } public function getStartTime(): int { return $this->startTime; } public function getMaxSteps(): int { return $this->max; } public function getProgress(): int { return $this->step; } private function getStepWidth(): int { return $this->stepWidth; } public function getProgressPercent(): float { return $this->percent; } public function getBarOffset(): float { return floor($this->max ? $this->percent * $this->barWidth : (null === $this->redrawFreq ? (int) (min(5, $this->barWidth / 15) * $this->writeCount) : $this->step) % $this->barWidth); } public function getEstimated(): float { if (!$this->step) { return 0; } return round((time() - $this->startTime) / $this->step * $this->max); } public function getRemaining(): float { if (!$this->step) { return 0; } return round((time() - $this->startTime) / $this->step * ($this->max - $this->step)); } public function setBarWidth(int $size) { $this->barWidth = max(1, $size); } public function getBarWidth(): int { return $this->barWidth; } public function setBarCharacter(string $char) { $this->barChar = $char; } public function getBarCharacter(): string { return $this->barChar ?? ($this->max ? '=' : $this->emptyBarChar); } public function setEmptyBarCharacter(string $char) { $this->emptyBarChar = $char; } public function getEmptyBarCharacter(): string { return $this->emptyBarChar; } public function setProgressCharacter(string $char) { $this->progressChar = $char; } public function getProgressCharacter(): string { return $this->progressChar; } public function setFormat(string $format) { $this->format = null; $this->internalFormat = $format; } public function setRedrawFrequency(?int $freq) { $this->redrawFreq = null !== $freq ? max(1, $freq) : null; } public function minSecondsBetweenRedraws(float $seconds): void { $this->minSecondsBetweenRedraws = $seconds; } public function maxSecondsBetweenRedraws(float $seconds): void { $this->maxSecondsBetweenRedraws = $seconds; } public function iterate(iterable $iterable, ?int $max = null): iterable { $this->start($max ?? (is_countable($iterable) ? \count($iterable) : 0)); foreach ($iterable as $key => $value) { yield $key => $value; $this->advance(); } $this->finish(); } public function start(?int $max = null) { $this->startTime = time(); $this->step = 0; $this->percent = 0.0; if (null !== $max) { $this->setMaxSteps($max); } $this->display(); } public function advance(int $step = 1) { $this->setProgress($this->step + $step); } public function setOverwrite(bool $overwrite) { $this->overwrite = $overwrite; } public function setProgress(int $step) { if ($this->max && $step > $this->max) { $this->max = $step; } elseif ($step < 0) { $step = 0; } $redrawFreq = $this->redrawFreq ?? (($this->max ?: 10) / 10); $prevPeriod = (int) ($this->step / $redrawFreq); $currPeriod = (int) ($step / $redrawFreq); $this->step = $step; $this->percent = $this->max ? (float) $this->step / $this->max : 0; $timeInterval = microtime(true) - $this->lastWriteTime; if ($this->max === $step) { $this->display(); return; } if ($timeInterval < $this->minSecondsBetweenRedraws) { return; } if ($prevPeriod !== $currPeriod || $timeInterval >= $this->maxSecondsBetweenRedraws) { $this->display(); } } public function setMaxSteps(int $max) { $this->format = null; $this->max = max(0, $max); $this->stepWidth = $this->max ? Helper::width((string) $this->max) : 4; } public function finish(): void { if (!$this->max) { $this->max = $this->step; } if ($this->step === $this->max && !$this->overwrite) { return; } $this->setProgress($this->max); } public function display(): void { if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) { return; } if (null === $this->format) { $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat()); } $this->overwrite($this->buildLine()); } public function clear(): void { if (!$this->overwrite) { return; } if (null === $this->format) { $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat()); } $this->overwrite(''); } private function setRealFormat(string $format) { if (!$this->max && null !== self::getFormatDefinition($format.'_nomax')) { $this->format = self::getFormatDefinition($format.'_nomax'); } elseif (null !== self::getFormatDefinition($format)) { $this->format = self::getFormatDefinition($format); } else { $this->format = $format; } } private function overwrite(string $message): void { if ($this->previousMessage === $message) { return; } $originalMessage = $message; if ($this->overwrite) { if (null !== $this->previousMessage) { if ($this->output instanceof ConsoleSectionOutput) { $messageLines = explode("\n", $this->previousMessage); $lineCount = \count($messageLines); foreach ($messageLines as $messageLine) { $messageLineLength = Helper::width(Helper::removeDecoration($this->output->getFormatter(), $messageLine)); if ($messageLineLength > $this->terminal->getWidth()) { $lineCount += floor($messageLineLength / $this->terminal->getWidth()); } } $this->output->clear($lineCount); } else { $lineCount = substr_count($this->previousMessage, "\n"); for ($i = 0; $i < $lineCount; ++$i) { $this->cursor->moveToColumn(1); $this->cursor->clearLine(); $this->cursor->moveUp(); } $this->cursor->moveToColumn(1); $this->cursor->clearLine(); } } } elseif ($this->step > 0) { $message = \PHP_EOL.$message; } $this->previousMessage = $originalMessage; $this->lastWriteTime = microtime(true); $this->output->write($message); ++$this->writeCount; } private function determineBestFormat(): string { switch ($this->output->getVerbosity()) { case OutputInterface::VERBOSITY_VERBOSE: return $this->max ? self::FORMAT_VERBOSE : self::FORMAT_VERBOSE_NOMAX; case OutputInterface::VERBOSITY_VERY_VERBOSE: return $this->max ? self::FORMAT_VERY_VERBOSE : self::FORMAT_VERY_VERBOSE_NOMAX; case OutputInterface::VERBOSITY_DEBUG: return $this->max ? self::FORMAT_DEBUG : self::FORMAT_DEBUG_NOMAX; default: return $this->max ? self::FORMAT_NORMAL : self::FORMAT_NORMAL_NOMAX; } } private static function initPlaceholderFormatters(): array { return [ 'bar' => function (self $bar, OutputInterface $output) { $completeBars = $bar->getBarOffset(); $display = str_repeat($bar->getBarCharacter(), $completeBars); if ($completeBars < $bar->getBarWidth()) { $emptyBars = $bar->getBarWidth() - $completeBars - Helper::length(Helper::removeDecoration($output->getFormatter(), $bar->getProgressCharacter())); $display .= $bar->getProgressCharacter().str_repeat($bar->getEmptyBarCharacter(), $emptyBars); } return $display; }, 'elapsed' => function (self $bar) { return Helper::formatTime(time() - $bar->getStartTime()); }, 'remaining' => function (self $bar) { if (!$bar->getMaxSteps()) { throw new LogicException('Unable to display the remaining time if the maximum number of steps is not set.'); } return Helper::formatTime($bar->getRemaining()); }, 'estimated' => function (self $bar) { if (!$bar->getMaxSteps()) { throw new LogicException('Unable to display the estimated time if the maximum number of steps is not set.'); } return Helper::formatTime($bar->getEstimated()); }, 'memory' => function (self $bar) { return Helper::formatMemory(memory_get_usage(true)); }, 'current' => function (self $bar) { return str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', \STR_PAD_LEFT); }, 'max' => function (self $bar) { return $bar->getMaxSteps(); }, 'percent' => function (self $bar) { return floor($bar->getProgressPercent() * 100); }, ]; } private static function initFormats(): array { return [ self::FORMAT_NORMAL => ' %current%/%max% [%bar%] %percent:3s%%', self::FORMAT_NORMAL_NOMAX => ' %current% [%bar%]', self::FORMAT_VERBOSE => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%', self::FORMAT_VERBOSE_NOMAX => ' %current% [%bar%] %elapsed:6s%', self::FORMAT_VERY_VERBOSE => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%', self::FORMAT_VERY_VERBOSE_NOMAX => ' %current% [%bar%] %elapsed:6s%', self::FORMAT_DEBUG => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%', self::FORMAT_DEBUG_NOMAX => ' %current% [%bar%] %elapsed:6s% %memory:6s%', ]; } private function buildLine(): string { $regex = "{%([a-z\-_]+)(?:\:([^%]+))?%}i"; $callback = function ($matches) { if ($formatter = $this::getPlaceholderFormatterDefinition($matches[1])) { $text = $formatter($this, $this->output); } elseif (isset($this->messages[$matches[1]])) { $text = $this->messages[$matches[1]]; } else { return $matches[0]; } if (isset($matches[2])) { $text = sprintf('%'.$matches[2], $text); } return $text; }; $line = preg_replace_callback($regex, $callback, $this->format); $linesLength = array_map(function ($subLine) { return Helper::width(Helper::removeDecoration($this->output->getFormatter(), rtrim($subLine, "\r"))); }, explode("\n", $line)); $linesWidth = max($linesLength); $terminalWidth = $this->terminal->getWidth(); if ($linesWidth <= $terminalWidth) { return $line; } $this->setBarWidth($this->barWidth - $linesWidth + $terminalWidth); return preg_replace_callback($regex, $callback, $this->format); } } ' %indicator% %message%', 'normal_no_ansi' => ' %message%', 'verbose' => ' %indicator% %message% (%elapsed:6s%)', 'verbose_no_ansi' => ' %message% (%elapsed:6s%)', 'very_verbose' => ' %indicator% %message% (%elapsed:6s%, %memory:6s%)', 'very_verbose_no_ansi' => ' %message% (%elapsed:6s%, %memory:6s%)', ]; private $output; private $startTime; private $format; private $message; private $indicatorValues; private $indicatorCurrent; private $indicatorChangeInterval; private $indicatorUpdateTime; private $started = false; private static $formatters; public function __construct(OutputInterface $output, ?string $format = null, int $indicatorChangeInterval = 100, ?array $indicatorValues = null) { $this->output = $output; if (null === $format) { $format = $this->determineBestFormat(); } if (null === $indicatorValues) { $indicatorValues = ['-', '\\', '|', '/']; } $indicatorValues = array_values($indicatorValues); if (2 > \count($indicatorValues)) { throw new InvalidArgumentException('Must have at least 2 indicator value characters.'); } $this->format = self::getFormatDefinition($format); $this->indicatorChangeInterval = $indicatorChangeInterval; $this->indicatorValues = $indicatorValues; $this->startTime = time(); } public function setMessage(?string $message) { $this->message = $message; $this->display(); } public function start(string $message) { if ($this->started) { throw new LogicException('Progress indicator already started.'); } $this->message = $message; $this->started = true; $this->startTime = time(); $this->indicatorUpdateTime = $this->getCurrentTimeInMilliseconds() + $this->indicatorChangeInterval; $this->indicatorCurrent = 0; $this->display(); } public function advance() { if (!$this->started) { throw new LogicException('Progress indicator has not yet been started.'); } if (!$this->output->isDecorated()) { return; } $currentTime = $this->getCurrentTimeInMilliseconds(); if ($currentTime < $this->indicatorUpdateTime) { return; } $this->indicatorUpdateTime = $currentTime + $this->indicatorChangeInterval; ++$this->indicatorCurrent; $this->display(); } public function finish(string $message) { if (!$this->started) { throw new LogicException('Progress indicator has not yet been started.'); } $this->message = $message; $this->display(); $this->output->writeln(''); $this->started = false; } public static function getFormatDefinition(string $name) { return self::FORMATS[$name] ?? null; } public static function setPlaceholderFormatterDefinition(string $name, callable $callable) { if (!self::$formatters) { self::$formatters = self::initPlaceholderFormatters(); } self::$formatters[$name] = $callable; } public static function getPlaceholderFormatterDefinition(string $name) { if (!self::$formatters) { self::$formatters = self::initPlaceholderFormatters(); } return self::$formatters[$name] ?? null; } private function display() { if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) { return; } $this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) { if ($formatter = self::getPlaceholderFormatterDefinition($matches[1])) { return $formatter($this); } return $matches[0]; }, $this->format ?? '')); } private function determineBestFormat(): string { switch ($this->output->getVerbosity()) { case OutputInterface::VERBOSITY_VERBOSE: return $this->output->isDecorated() ? 'verbose' : 'verbose_no_ansi'; case OutputInterface::VERBOSITY_VERY_VERBOSE: case OutputInterface::VERBOSITY_DEBUG: return $this->output->isDecorated() ? 'very_verbose' : 'very_verbose_no_ansi'; default: return $this->output->isDecorated() ? 'normal' : 'normal_no_ansi'; } } private function overwrite(string $message) { if ($this->output->isDecorated()) { $this->output->write("\x0D\x1B[2K"); $this->output->write($message); } else { $this->output->writeln($message); } } private function getCurrentTimeInMilliseconds(): float { return round(microtime(true) * 1000); } private static function initPlaceholderFormatters(): array { return [ 'indicator' => function (self $indicator) { return $indicator->indicatorValues[$indicator->indicatorCurrent % \count($indicator->indicatorValues)]; }, 'message' => function (self $indicator) { return $indicator->message; }, 'elapsed' => function (self $indicator) { return Helper::formatTime(time() - $indicator->startTime); }, 'memory' => function () { return Helper::formatMemory(memory_get_usage(true)); }, ]; } } getErrorOutput(); } if (!$input->isInteractive()) { return $this->getDefaultAnswer($question); } if ($input instanceof StreamableInputInterface && $stream = $input->getStream()) { $this->inputStream = $stream; } try { if (!$question->getValidator()) { return $this->doAsk($output, $question); } $interviewer = function () use ($output, $question) { return $this->doAsk($output, $question); }; return $this->validateAttempts($interviewer, $output, $question); } catch (MissingInputException $exception) { $input->setInteractive(false); if (null === $fallbackOutput = $this->getDefaultAnswer($question)) { throw $exception; } return $fallbackOutput; } } public function getName() { return 'question'; } public static function disableStty() { self::$stty = false; } private function doAsk(OutputInterface $output, Question $question) { $this->writePrompt($output, $question); $inputStream = $this->inputStream ?: \STDIN; $autocomplete = $question->getAutocompleterCallback(); if (null === $autocomplete || !self::$stty || !Terminal::hasSttyAvailable()) { $ret = false; if ($question->isHidden()) { try { $hiddenResponse = $this->getHiddenResponse($output, $inputStream, $question->isTrimmable()); $ret = $question->isTrimmable() ? trim($hiddenResponse) : $hiddenResponse; } catch (RuntimeException $e) { if (!$question->isHiddenFallback()) { throw $e; } } } if (false === $ret) { $isBlocked = stream_get_meta_data($inputStream)['blocked'] ?? true; if (!$isBlocked) { stream_set_blocking($inputStream, true); } $ret = $this->readInput($inputStream, $question); if (!$isBlocked) { stream_set_blocking($inputStream, false); } if (false === $ret) { throw new MissingInputException('Aborted.'); } if ($question->isTrimmable()) { $ret = trim($ret); } } } else { $autocomplete = $this->autocomplete($output, $question, $inputStream, $autocomplete); $ret = $question->isTrimmable() ? trim($autocomplete) : $autocomplete; } if ($output instanceof ConsoleSectionOutput) { $output->addContent($ret); } $ret = \strlen($ret) > 0 ? $ret : $question->getDefault(); if ($normalizer = $question->getNormalizer()) { return $normalizer($ret); } return $ret; } private function getDefaultAnswer(Question $question) { $default = $question->getDefault(); if (null === $default) { return $default; } if ($validator = $question->getValidator()) { return \call_user_func($question->getValidator(), $default); } elseif ($question instanceof ChoiceQuestion) { $choices = $question->getChoices(); if (!$question->isMultiselect()) { return $choices[$default] ?? $default; } $default = explode(',', $default); foreach ($default as $k => $v) { $v = $question->isTrimmable() ? trim($v) : $v; $default[$k] = $choices[$v] ?? $v; } } return $default; } protected function writePrompt(OutputInterface $output, Question $question) { $message = $question->getQuestion(); if ($question instanceof ChoiceQuestion) { $output->writeln(array_merge([ $question->getQuestion(), ], $this->formatChoiceQuestionChoices($question, 'info'))); $message = $question->getPrompt(); } $output->write($message); } protected function formatChoiceQuestionChoices(ChoiceQuestion $question, string $tag) { $messages = []; $maxWidth = max(array_map([__CLASS__, 'width'], array_keys($choices = $question->getChoices()))); foreach ($choices as $key => $value) { $padding = str_repeat(' ', $maxWidth - self::width($key)); $messages[] = sprintf(" [<$tag>%s$padding] %s", $key, $value); } return $messages; } protected function writeError(OutputInterface $output, \Exception $error) { if (null !== $this->getHelperSet() && $this->getHelperSet()->has('formatter')) { $message = $this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error'); } else { $message = ''.$error->getMessage().''; } $output->writeln($message); } private function autocomplete(OutputInterface $output, Question $question, $inputStream, callable $autocomplete): string { $cursor = new Cursor($output, $inputStream); $fullChoice = ''; $ret = ''; $i = 0; $ofs = -1; $matches = $autocomplete($ret); $numMatches = \count($matches); $sttyMode = shell_exec('stty -g'); $isStdin = 'php://stdin' === (stream_get_meta_data($inputStream)['uri'] ?? null); $r = [$inputStream]; $w = []; shell_exec('stty -icanon -echo'); $output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white')); while (!feof($inputStream)) { while ($isStdin && 0 === @stream_select($r, $w, $w, 0, 100)) { $r = [$inputStream]; } $c = fread($inputStream, 1); if (false === $c || ('' === $ret && '' === $c && null === $question->getDefault())) { shell_exec('stty '.$sttyMode); throw new MissingInputException('Aborted.'); } elseif ("\177" === $c) { if (0 === $numMatches && 0 !== $i) { --$i; $cursor->moveLeft(s($fullChoice)->slice(-1)->width(false)); $fullChoice = self::substr($fullChoice, 0, $i); } if (0 === $i) { $ofs = -1; $matches = $autocomplete($ret); $numMatches = \count($matches); } else { $numMatches = 0; } $ret = self::substr($ret, 0, $i); } elseif ("\033" === $c) { $c .= fread($inputStream, 2); if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) { if ('A' === $c[2] && -1 === $ofs) { $ofs = 0; } if (0 === $numMatches) { continue; } $ofs += ('A' === $c[2]) ? -1 : 1; $ofs = ($numMatches + $ofs) % $numMatches; } } elseif (\ord($c) < 32) { if ("\t" === $c || "\n" === $c) { if ($numMatches > 0 && -1 !== $ofs) { $ret = (string) $matches[$ofs]; $remainingCharacters = substr($ret, \strlen(trim($this->mostRecentlyEnteredValue($fullChoice)))); $output->write($remainingCharacters); $fullChoice .= $remainingCharacters; $i = (false === $encoding = mb_detect_encoding($fullChoice, null, true)) ? \strlen($fullChoice) : mb_strlen($fullChoice, $encoding); $matches = array_filter( $autocomplete($ret), function ($match) use ($ret) { return '' === $ret || str_starts_with($match, $ret); } ); $numMatches = \count($matches); $ofs = -1; } if ("\n" === $c) { $output->write($c); break; } $numMatches = 0; } continue; } else { if ("\x80" <= $c) { $c .= fread($inputStream, ["\xC0" => 1, "\xD0" => 1, "\xE0" => 2, "\xF0" => 3][$c & "\xF0"]); } $output->write($c); $ret .= $c; $fullChoice .= $c; ++$i; $tempRet = $ret; if ($question instanceof ChoiceQuestion && $question->isMultiselect()) { $tempRet = $this->mostRecentlyEnteredValue($fullChoice); } $numMatches = 0; $ofs = 0; foreach ($autocomplete($ret) as $value) { if (str_starts_with($value, $tempRet)) { $matches[$numMatches++] = $value; } } } $cursor->clearLineAfter(); if ($numMatches > 0 && -1 !== $ofs) { $cursor->savePosition(); $charactersEntered = \strlen(trim($this->mostRecentlyEnteredValue($fullChoice))); $output->write(''.OutputFormatter::escapeTrailingBackslash(substr($matches[$ofs], $charactersEntered)).''); $cursor->restorePosition(); } } shell_exec('stty '.$sttyMode); return $fullChoice; } private function mostRecentlyEnteredValue(string $entered): string { if (!str_contains($entered, ',')) { return $entered; } $choices = explode(',', $entered); if ('' !== $lastChoice = trim($choices[\count($choices) - 1])) { return $lastChoice; } return $entered; } private function getHiddenResponse(OutputInterface $output, $inputStream, bool $trimmable = true): string { if ('\\' === \DIRECTORY_SEPARATOR) { $exe = __DIR__.'/../Resources/bin/hiddeninput.exe'; if ('phar:' === substr(__FILE__, 0, 5)) { $tmpExe = sys_get_temp_dir().'/hiddeninput.exe'; copy($exe, $tmpExe); $exe = $tmpExe; } $sExec = shell_exec('"'.$exe.'"'); $value = $trimmable ? rtrim($sExec) : $sExec; $output->writeln(''); if (isset($tmpExe)) { unlink($tmpExe); } return $value; } if (self::$stty && Terminal::hasSttyAvailable()) { $sttyMode = shell_exec('stty -g'); shell_exec('stty -echo'); } elseif ($this->isInteractiveInput($inputStream)) { throw new RuntimeException('Unable to hide the response.'); } $value = fgets($inputStream, 4096); if (self::$stty && Terminal::hasSttyAvailable()) { shell_exec('stty '.$sttyMode); } if (false === $value) { throw new MissingInputException('Aborted.'); } if ($trimmable) { $value = trim($value); } $output->writeln(''); return $value; } private function validateAttempts(callable $interviewer, OutputInterface $output, Question $question) { $error = null; $attempts = $question->getMaxAttempts(); while (null === $attempts || $attempts--) { if (null !== $error) { $this->writeError($output, $error); } try { return $question->getValidator()($interviewer()); } catch (RuntimeException $e) { throw $e; } catch (\Exception $error) { } } throw $error; } private function isInteractiveInput($inputStream): bool { if ('php://stdin' !== (stream_get_meta_data($inputStream)['uri'] ?? null)) { return false; } if (null !== self::$stdinIsInteractive) { return self::$stdinIsInteractive; } return self::$stdinIsInteractive = @stream_isatty(fopen('php://stdin', 'r')); } private function readInput($inputStream, Question $question) { if (!$question->isMultiline()) { $cp = $this->setIOCodepage(); $ret = fgets($inputStream, 4096); return $this->resetIOCodepage($cp, $ret); } $multiLineStreamReader = $this->cloneInputStream($inputStream); if (null === $multiLineStreamReader) { return false; } $ret = ''; $cp = $this->setIOCodepage(); while (false !== ($char = fgetc($multiLineStreamReader))) { if (\PHP_EOL === "{$ret}{$char}") { break; } $ret .= $char; } return $this->resetIOCodepage($cp, $ret); } private function setIOCodepage(): int { if (\function_exists('sapi_windows_cp_set')) { $cp = sapi_windows_cp_get(); sapi_windows_cp_set(sapi_windows_cp_get('oem')); return $cp; } return 0; } private function resetIOCodepage(int $cp, $input) { if (0 !== $cp) { sapi_windows_cp_set($cp); if (false !== $input && '' !== $input) { $input = sapi_windows_cp_conv(sapi_windows_cp_get('oem'), $cp, $input); } } return $input; } private function cloneInputStream($inputStream) { $streamMetaData = stream_get_meta_data($inputStream); $seekable = $streamMetaData['seekable'] ?? false; $mode = $streamMetaData['mode'] ?? 'rb'; $uri = $streamMetaData['uri'] ?? null; if (null === $uri) { return null; } $cloneStream = fopen($uri, $mode); if (true === $seekable && !\in_array($mode, ['r', 'rb', 'rt'])) { $offset = ftell($inputStream); rewind($inputStream); stream_copy_to_stream($inputStream, $cloneStream); fseek($inputStream, $offset); fseek($cloneStream, $offset); } return $cloneStream; } } getQuestion()); $default = $question->getDefault(); if ($question->isMultiline()) { $text .= sprintf(' (press %s to continue)', $this->getEofShortcut()); } switch (true) { case null === $default: $text = sprintf(' %s:', $text); break; case $question instanceof ConfirmationQuestion: $text = sprintf(' %s (yes/no) [%s]:', $text, $default ? 'yes' : 'no'); break; case $question instanceof ChoiceQuestion && $question->isMultiselect(): $choices = $question->getChoices(); $default = explode(',', $default); foreach ($default as $key => $value) { $default[$key] = $choices[trim($value)]; } $text = sprintf(' %s [%s]:', $text, OutputFormatter::escape(implode(', ', $default))); break; case $question instanceof ChoiceQuestion: $choices = $question->getChoices(); $text = sprintf(' %s [%s]:', $text, OutputFormatter::escape($choices[$default] ?? $default)); break; default: $text = sprintf(' %s [%s]:', $text, OutputFormatter::escape($default)); } $output->writeln($text); $prompt = ' > '; if ($question instanceof ChoiceQuestion) { $output->writeln($this->formatChoiceQuestionChoices($question, 'comment')); $prompt = $question->getPrompt(); } $output->write($prompt); } protected function writeError(OutputInterface $output, \Exception $error) { if ($output instanceof SymfonyStyle) { $output->newLine(); $output->error($error->getMessage()); return; } parent::writeError($output, $error); } private function getEofShortcut(): string { if ('Windows' === \PHP_OS_FAMILY) { return 'Ctrl+Z then Enter'; } return 'Ctrl+D'; } } output = $output; if (!self::$styles) { self::$styles = self::initStyles(); } $this->setStyle('default'); } public static function setStyleDefinition(string $name, TableStyle $style) { if (!self::$styles) { self::$styles = self::initStyles(); } self::$styles[$name] = $style; } public static function getStyleDefinition(string $name) { if (!self::$styles) { self::$styles = self::initStyles(); } if (isset(self::$styles[$name])) { return self::$styles[$name]; } throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name)); } public function setStyle($name) { $this->style = $this->resolveStyle($name); return $this; } public function getStyle() { return $this->style; } public function setColumnStyle(int $columnIndex, $name) { $this->columnStyles[$columnIndex] = $this->resolveStyle($name); return $this; } public function getColumnStyle(int $columnIndex) { return $this->columnStyles[$columnIndex] ?? $this->getStyle(); } public function setColumnWidth(int $columnIndex, int $width) { $this->columnWidths[$columnIndex] = $width; return $this; } public function setColumnWidths(array $widths) { $this->columnWidths = []; foreach ($widths as $index => $width) { $this->setColumnWidth($index, $width); } return $this; } public function setColumnMaxWidth(int $columnIndex, int $width): self { if (!$this->output->getFormatter() instanceof WrappableOutputFormatterInterface) { throw new \LogicException(sprintf('Setting a maximum column width is only supported when using a "%s" formatter, got "%s".', WrappableOutputFormatterInterface::class, get_debug_type($this->output->getFormatter()))); } $this->columnMaxWidths[$columnIndex] = $width; return $this; } public function setHeaders(array $headers) { $headers = array_values($headers); if (!empty($headers) && !\is_array($headers[0])) { $headers = [$headers]; } $this->headers = $headers; return $this; } public function setRows(array $rows) { $this->rows = []; return $this->addRows($rows); } public function addRows(array $rows) { foreach ($rows as $row) { $this->addRow($row); } return $this; } public function addRow($row) { if ($row instanceof TableSeparator) { $this->rows[] = $row; return $this; } if (!\is_array($row)) { throw new InvalidArgumentException('A row must be an array or a TableSeparator instance.'); } $this->rows[] = array_values($row); return $this; } public function appendRow($row): self { if (!$this->output instanceof ConsoleSectionOutput) { throw new RuntimeException(sprintf('Output should be an instance of "%s" when calling "%s".', ConsoleSectionOutput::class, __METHOD__)); } if ($this->rendered) { $this->output->clear($this->calculateRowCount()); } $this->addRow($row); $this->render(); return $this; } public function setRow($column, array $row) { $this->rows[$column] = $row; return $this; } public function setHeaderTitle(?string $title): self { $this->headerTitle = $title; return $this; } public function setFooterTitle(?string $title): self { $this->footerTitle = $title; return $this; } public function setHorizontal(bool $horizontal = true): self { $this->horizontal = $horizontal; return $this; } public function render() { $divider = new TableSeparator(); if ($this->horizontal) { $rows = []; foreach ($this->headers[0] ?? [] as $i => $header) { $rows[$i] = [$header]; foreach ($this->rows as $row) { if ($row instanceof TableSeparator) { continue; } if (isset($row[$i])) { $rows[$i][] = $row[$i]; } elseif ($rows[$i][0] instanceof TableCell && $rows[$i][0]->getColspan() >= 2) { } else { $rows[$i][] = null; } } } } else { $rows = array_merge($this->headers, [$divider], $this->rows); } $this->calculateNumberOfColumns($rows); $rowGroups = $this->buildTableRows($rows); $this->calculateColumnsWidth($rowGroups); $isHeader = !$this->horizontal; $isFirstRow = $this->horizontal; $hasTitle = (bool) $this->headerTitle; foreach ($rowGroups as $rowGroup) { $isHeaderSeparatorRendered = false; foreach ($rowGroup as $row) { if ($divider === $row) { $isHeader = false; $isFirstRow = true; continue; } if ($row instanceof TableSeparator) { $this->renderRowSeparator(); continue; } if (!$row) { continue; } if ($isHeader && !$isHeaderSeparatorRendered) { $this->renderRowSeparator( $isHeader ? self::SEPARATOR_TOP : self::SEPARATOR_TOP_BOTTOM, $hasTitle ? $this->headerTitle : null, $hasTitle ? $this->style->getHeaderTitleFormat() : null ); $hasTitle = false; $isHeaderSeparatorRendered = true; } if ($isFirstRow) { $this->renderRowSeparator( $isHeader ? self::SEPARATOR_TOP : self::SEPARATOR_TOP_BOTTOM, $hasTitle ? $this->headerTitle : null, $hasTitle ? $this->style->getHeaderTitleFormat() : null ); $isFirstRow = false; $hasTitle = false; } if ($this->horizontal) { $this->renderRow($row, $this->style->getCellRowFormat(), $this->style->getCellHeaderFormat()); } else { $this->renderRow($row, $isHeader ? $this->style->getCellHeaderFormat() : $this->style->getCellRowFormat()); } } } $this->renderRowSeparator(self::SEPARATOR_BOTTOM, $this->footerTitle, $this->style->getFooterTitleFormat()); $this->cleanup(); $this->rendered = true; } private function renderRowSeparator(int $type = self::SEPARATOR_MID, ?string $title = null, ?string $titleFormat = null) { if (0 === $count = $this->numberOfColumns) { return; } $borders = $this->style->getBorderChars(); if (!$borders[0] && !$borders[2] && !$this->style->getCrossingChar()) { return; } $crossings = $this->style->getCrossingChars(); if (self::SEPARATOR_MID === $type) { [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[2], $crossings[8], $crossings[0], $crossings[4]]; } elseif (self::SEPARATOR_TOP === $type) { [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[1], $crossings[2], $crossings[3]]; } elseif (self::SEPARATOR_TOP_BOTTOM === $type) { [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[9], $crossings[10], $crossings[11]]; } else { [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[7], $crossings[6], $crossings[5]]; } $markup = $leftChar; for ($column = 0; $column < $count; ++$column) { $markup .= str_repeat($horizontal, $this->effectiveColumnWidths[$column]); $markup .= $column === $count - 1 ? $rightChar : $midChar; } if (null !== $title) { $titleLength = Helper::width(Helper::removeDecoration($formatter = $this->output->getFormatter(), $formattedTitle = sprintf($titleFormat, $title))); $markupLength = Helper::width($markup); if ($titleLength > $limit = $markupLength - 4) { $titleLength = $limit; $formatLength = Helper::width(Helper::removeDecoration($formatter, sprintf($titleFormat, ''))); $formattedTitle = sprintf($titleFormat, Helper::substr($title, 0, $limit - $formatLength - 3).'...'); } $titleStart = intdiv($markupLength - $titleLength, 2); if (false === mb_detect_encoding($markup, null, true)) { $markup = substr_replace($markup, $formattedTitle, $titleStart, $titleLength); } else { $markup = mb_substr($markup, 0, $titleStart).$formattedTitle.mb_substr($markup, $titleStart + $titleLength); } } $this->output->writeln(sprintf($this->style->getBorderFormat(), $markup)); } private function renderColumnSeparator(int $type = self::BORDER_OUTSIDE): string { $borders = $this->style->getBorderChars(); return sprintf($this->style->getBorderFormat(), self::BORDER_OUTSIDE === $type ? $borders[1] : $borders[3]); } private function renderRow(array $row, string $cellFormat, ?string $firstCellFormat = null) { $rowContent = $this->renderColumnSeparator(self::BORDER_OUTSIDE); $columns = $this->getRowColumns($row); $last = \count($columns) - 1; foreach ($columns as $i => $column) { if ($firstCellFormat && 0 === $i) { $rowContent .= $this->renderCell($row, $column, $firstCellFormat); } else { $rowContent .= $this->renderCell($row, $column, $cellFormat); } $rowContent .= $this->renderColumnSeparator($last === $i ? self::BORDER_OUTSIDE : self::BORDER_INSIDE); } $this->output->writeln($rowContent); } private function renderCell(array $row, int $column, string $cellFormat): string { $cell = $row[$column] ?? ''; $width = $this->effectiveColumnWidths[$column]; if ($cell instanceof TableCell && $cell->getColspan() > 1) { foreach (range($column + 1, $column + $cell->getColspan() - 1) as $nextColumn) { $width += $this->getColumnSeparatorWidth() + $this->effectiveColumnWidths[$nextColumn]; } } if (false !== $encoding = mb_detect_encoding($cell, null, true)) { $width += \strlen($cell) - mb_strwidth($cell, $encoding); } $style = $this->getColumnStyle($column); if ($cell instanceof TableSeparator) { return sprintf($style->getBorderFormat(), str_repeat($style->getBorderChars()[2], $width)); } $width += Helper::length($cell) - Helper::length(Helper::removeDecoration($this->output->getFormatter(), $cell)); $content = sprintf($style->getCellRowContentFormat(), $cell); $padType = $style->getPadType(); if ($cell instanceof TableCell && $cell->getStyle() instanceof TableCellStyle) { $isNotStyledByTag = !preg_match('/^<(\w+|(\w+=[\w,]+;?)*)>.+<\/(\w+|(\w+=\w+;?)*)?>$/', $cell); if ($isNotStyledByTag) { $cellFormat = $cell->getStyle()->getCellFormat(); if (!\is_string($cellFormat)) { $tag = http_build_query($cell->getStyle()->getTagOptions(), '', ';'); $cellFormat = '<'.$tag.'>%s'; } if (strstr($content, '')) { $content = str_replace('', '', $content); $width -= 3; } if (strstr($content, '')) { $content = str_replace('', '', $content); $width -= \strlen(''); } } $padType = $cell->getStyle()->getPadByAlign(); } return sprintf($cellFormat, str_pad($content, $width, $style->getPaddingChar(), $padType)); } private function calculateNumberOfColumns(array $rows) { $columns = [0]; foreach ($rows as $row) { if ($row instanceof TableSeparator) { continue; } $columns[] = $this->getNumberOfColumns($row); } $this->numberOfColumns = max($columns); } private function buildTableRows(array $rows): TableRows { $formatter = $this->output->getFormatter(); $unmergedRows = []; for ($rowKey = 0; $rowKey < \count($rows); ++$rowKey) { $rows = $this->fillNextRows($rows, $rowKey); foreach ($rows[$rowKey] as $column => $cell) { $colspan = $cell instanceof TableCell ? $cell->getColspan() : 1; if (isset($this->columnMaxWidths[$column]) && Helper::width(Helper::removeDecoration($formatter, $cell)) > $this->columnMaxWidths[$column]) { $cell = $formatter->formatAndWrap($cell, $this->columnMaxWidths[$column] * $colspan); } if (!strstr($cell ?? '', "\n")) { continue; } $eol = str_contains($cell ?? '', "\r\n") ? "\r\n" : "\n"; $escaped = implode($eol, array_map([OutputFormatter::class, 'escapeTrailingBackslash'], explode($eol, $cell))); $cell = $cell instanceof TableCell ? new TableCell($escaped, ['colspan' => $cell->getColspan()]) : $escaped; $lines = explode($eol, str_replace($eol, ''.$eol, $cell)); foreach ($lines as $lineKey => $line) { if ($colspan > 1) { $line = new TableCell($line, ['colspan' => $colspan]); } if (0 === $lineKey) { $rows[$rowKey][$column] = $line; } else { if (!\array_key_exists($rowKey, $unmergedRows) || !\array_key_exists($lineKey, $unmergedRows[$rowKey])) { $unmergedRows[$rowKey][$lineKey] = $this->copyRow($rows, $rowKey); } $unmergedRows[$rowKey][$lineKey][$column] = $line; } } } } return new TableRows(function () use ($rows, $unmergedRows): \Traversable { foreach ($rows as $rowKey => $row) { $rowGroup = [$row instanceof TableSeparator ? $row : $this->fillCells($row)]; if (isset($unmergedRows[$rowKey])) { foreach ($unmergedRows[$rowKey] as $row) { $rowGroup[] = $row instanceof TableSeparator ? $row : $this->fillCells($row); } } yield $rowGroup; } }); } private function calculateRowCount(): int { $numberOfRows = \count(iterator_to_array($this->buildTableRows(array_merge($this->headers, [new TableSeparator()], $this->rows)))); if ($this->headers) { ++$numberOfRows; } if (\count($this->rows) > 0) { ++$numberOfRows; } return $numberOfRows; } private function fillNextRows(array $rows, int $line): array { $unmergedRows = []; foreach ($rows[$line] as $column => $cell) { if (null !== $cell && !$cell instanceof TableCell && !\is_scalar($cell) && !(\is_object($cell) && method_exists($cell, '__toString'))) { throw new InvalidArgumentException(sprintf('A cell must be a TableCell, a scalar or an object implementing "__toString()", "%s" given.', get_debug_type($cell))); } if ($cell instanceof TableCell && $cell->getRowspan() > 1) { $nbLines = $cell->getRowspan() - 1; $lines = [$cell]; if (strstr($cell, "\n")) { $eol = str_contains($cell, "\r\n") ? "\r\n" : "\n"; $lines = explode($eol, str_replace($eol, ''.$eol.'', $cell)); $nbLines = \count($lines) > $nbLines ? substr_count($cell, $eol) : $nbLines; $rows[$line][$column] = new TableCell($lines[0], ['colspan' => $cell->getColspan(), 'style' => $cell->getStyle()]); unset($lines[0]); } $unmergedRows = array_replace_recursive(array_fill($line + 1, $nbLines, []), $unmergedRows); foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) { $value = $lines[$unmergedRowKey - $line] ?? ''; $unmergedRows[$unmergedRowKey][$column] = new TableCell($value, ['colspan' => $cell->getColspan(), 'style' => $cell->getStyle()]); if ($nbLines === $unmergedRowKey - $line) { break; } } } } foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) { if (isset($rows[$unmergedRowKey]) && \is_array($rows[$unmergedRowKey]) && ($this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRows[$unmergedRowKey]) <= $this->numberOfColumns)) { foreach ($unmergedRow as $cellKey => $cell) { array_splice($rows[$unmergedRowKey], $cellKey, 0, [$cell]); } } else { $row = $this->copyRow($rows, $unmergedRowKey - 1); foreach ($unmergedRow as $column => $cell) { if (!empty($cell)) { $row[$column] = $unmergedRow[$column]; } } array_splice($rows, $unmergedRowKey, 0, [$row]); } } return $rows; } private function fillCells(iterable $row) { $newRow = []; foreach ($row as $column => $cell) { $newRow[] = $cell; if ($cell instanceof TableCell && $cell->getColspan() > 1) { foreach (range($column + 1, $column + $cell->getColspan() - 1) as $position) { $newRow[] = ''; } } } return $newRow ?: $row; } private function copyRow(array $rows, int $line): array { $row = $rows[$line]; foreach ($row as $cellKey => $cellValue) { $row[$cellKey] = ''; if ($cellValue instanceof TableCell) { $row[$cellKey] = new TableCell('', ['colspan' => $cellValue->getColspan()]); } } return $row; } private function getNumberOfColumns(array $row): int { $columns = \count($row); foreach ($row as $column) { $columns += $column instanceof TableCell ? ($column->getColspan() - 1) : 0; } return $columns; } private function getRowColumns(array $row): array { $columns = range(0, $this->numberOfColumns - 1); foreach ($row as $cellKey => $cell) { if ($cell instanceof TableCell && $cell->getColspan() > 1) { $columns = array_diff($columns, range($cellKey + 1, $cellKey + $cell->getColspan() - 1)); } } return $columns; } private function calculateColumnsWidth(iterable $groups) { for ($column = 0; $column < $this->numberOfColumns; ++$column) { $lengths = []; foreach ($groups as $group) { foreach ($group as $row) { if ($row instanceof TableSeparator) { continue; } foreach ($row as $i => $cell) { if ($cell instanceof TableCell) { $textContent = Helper::removeDecoration($this->output->getFormatter(), $cell); $textLength = Helper::width($textContent); if ($textLength > 0) { $contentColumns = mb_str_split($textContent, ceil($textLength / $cell->getColspan())); foreach ($contentColumns as $position => $content) { $row[$i + $position] = $content; } } } } $lengths[] = $this->getCellWidth($row, $column); } } $this->effectiveColumnWidths[$column] = max($lengths) + Helper::width($this->style->getCellRowContentFormat()) - 2; } } private function getColumnSeparatorWidth(): int { return Helper::width(sprintf($this->style->getBorderFormat(), $this->style->getBorderChars()[3])); } private function getCellWidth(array $row, int $column): int { $cellWidth = 0; if (isset($row[$column])) { $cell = $row[$column]; $cellWidth = Helper::width(Helper::removeDecoration($this->output->getFormatter(), $cell)); } $columnWidth = $this->columnWidths[$column] ?? 0; $cellWidth = max($cellWidth, $columnWidth); return isset($this->columnMaxWidths[$column]) ? min($this->columnMaxWidths[$column], $cellWidth) : $cellWidth; } private function cleanup() { $this->effectiveColumnWidths = []; $this->numberOfColumns = null; } private static function initStyles(): array { $borderless = new TableStyle(); $borderless ->setHorizontalBorderChars('=') ->setVerticalBorderChars(' ') ->setDefaultCrossingChar(' ') ; $compact = new TableStyle(); $compact ->setHorizontalBorderChars('') ->setVerticalBorderChars('') ->setDefaultCrossingChar('') ->setCellRowContentFormat('%s ') ; $styleGuide = new TableStyle(); $styleGuide ->setHorizontalBorderChars('-') ->setVerticalBorderChars(' ') ->setDefaultCrossingChar(' ') ->setCellHeaderFormat('%s') ; $box = (new TableStyle()) ->setHorizontalBorderChars('─') ->setVerticalBorderChars('│') ->setCrossingChars('┼', '┌', '┬', '┐', '┤', '┘', '┴', '└', '├') ; $boxDouble = (new TableStyle()) ->setHorizontalBorderChars('═', '─') ->setVerticalBorderChars('║', '│') ->setCrossingChars('┼', '╔', '╤', '╗', '╢', '╝', '╧', '╚', '╟', '╠', '╪', '╣') ; return [ 'default' => new TableStyle(), 'borderless' => $borderless, 'compact' => $compact, 'symfony-style-guide' => $styleGuide, 'box' => $box, 'box-double' => $boxDouble, ]; } private function resolveStyle($name): TableStyle { if ($name instanceof TableStyle) { return $name; } if (isset(self::$styles[$name])) { return self::$styles[$name]; } throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name)); } } 1, 'colspan' => 1, 'style' => null, ]; public function __construct(string $value = '', array $options = []) { $this->value = $value; if ($diff = array_diff(array_keys($options), array_keys($this->options))) { throw new InvalidArgumentException(sprintf('The TableCell does not support the following options: \'%s\'.', implode('\', \'', $diff))); } if (isset($options['style']) && !$options['style'] instanceof TableCellStyle) { throw new InvalidArgumentException('The style option must be an instance of "TableCellStyle".'); } $this->options = array_merge($this->options, $options); } public function __toString() { return $this->value; } public function getColspan() { return (int) $this->options['colspan']; } public function getRowspan() { return (int) $this->options['rowspan']; } public function getStyle(): ?TableCellStyle { return $this->options['style']; } } \STR_PAD_RIGHT, 'center' => \STR_PAD_BOTH, 'right' => \STR_PAD_LEFT, ]; private $options = [ 'fg' => 'default', 'bg' => 'default', 'options' => null, 'align' => self::DEFAULT_ALIGN, 'cellFormat' => null, ]; public function __construct(array $options = []) { if ($diff = array_diff(array_keys($options), array_keys($this->options))) { throw new InvalidArgumentException(sprintf('The TableCellStyle does not support the following options: \'%s\'.', implode('\', \'', $diff))); } if (isset($options['align']) && !\array_key_exists($options['align'], self::ALIGN_MAP)) { throw new InvalidArgumentException(sprintf('Wrong align value. Value must be following: \'%s\'.', implode('\', \'', array_keys(self::ALIGN_MAP)))); } $this->options = array_merge($this->options, $options); } public function getOptions(): array { return $this->options; } public function getTagOptions() { return array_filter( $this->getOptions(), function ($key) { return \in_array($key, self::TAG_OPTIONS) && isset($this->options[$key]); }, \ARRAY_FILTER_USE_KEY ); } public function getPadByAlign() { return self::ALIGN_MAP[$this->getOptions()['align']]; } public function getCellFormat(): ?string { return $this->getOptions()['cellFormat']; } } generator = $generator; } public function getIterator(): \Traversable { return ($this->generator)(); } } %s '; private $footerTitleFormat = ' %s '; private $cellHeaderFormat = '%s'; private $cellRowFormat = '%s'; private $cellRowContentFormat = ' %s '; private $borderFormat = '%s'; private $padType = \STR_PAD_RIGHT; public function setPaddingChar(string $paddingChar) { if (!$paddingChar) { throw new LogicException('The padding char must not be empty.'); } $this->paddingChar = $paddingChar; return $this; } public function getPaddingChar() { return $this->paddingChar; } public function setHorizontalBorderChars(string $outside, ?string $inside = null): self { $this->horizontalOutsideBorderChar = $outside; $this->horizontalInsideBorderChar = $inside ?? $outside; return $this; } public function setVerticalBorderChars(string $outside, ?string $inside = null): self { $this->verticalOutsideBorderChar = $outside; $this->verticalInsideBorderChar = $inside ?? $outside; return $this; } public function getBorderChars(): array { return [ $this->horizontalOutsideBorderChar, $this->verticalOutsideBorderChar, $this->horizontalInsideBorderChar, $this->verticalInsideBorderChar, ]; } public function setCrossingChars(string $cross, string $topLeft, string $topMid, string $topRight, string $midRight, string $bottomRight, string $bottomMid, string $bottomLeft, string $midLeft, ?string $topLeftBottom = null, ?string $topMidBottom = null, ?string $topRightBottom = null): self { $this->crossingChar = $cross; $this->crossingTopLeftChar = $topLeft; $this->crossingTopMidChar = $topMid; $this->crossingTopRightChar = $topRight; $this->crossingMidRightChar = $midRight; $this->crossingBottomRightChar = $bottomRight; $this->crossingBottomMidChar = $bottomMid; $this->crossingBottomLeftChar = $bottomLeft; $this->crossingMidLeftChar = $midLeft; $this->crossingTopLeftBottomChar = $topLeftBottom ?? $midLeft; $this->crossingTopMidBottomChar = $topMidBottom ?? $cross; $this->crossingTopRightBottomChar = $topRightBottom ?? $midRight; return $this; } public function setDefaultCrossingChar(string $char): self { return $this->setCrossingChars($char, $char, $char, $char, $char, $char, $char, $char, $char); } public function getCrossingChar() { return $this->crossingChar; } public function getCrossingChars(): array { return [ $this->crossingChar, $this->crossingTopLeftChar, $this->crossingTopMidChar, $this->crossingTopRightChar, $this->crossingMidRightChar, $this->crossingBottomRightChar, $this->crossingBottomMidChar, $this->crossingBottomLeftChar, $this->crossingMidLeftChar, $this->crossingTopLeftBottomChar, $this->crossingTopMidBottomChar, $this->crossingTopRightBottomChar, ]; } public function setCellHeaderFormat(string $cellHeaderFormat) { $this->cellHeaderFormat = $cellHeaderFormat; return $this; } public function getCellHeaderFormat() { return $this->cellHeaderFormat; } public function setCellRowFormat(string $cellRowFormat) { $this->cellRowFormat = $cellRowFormat; return $this; } public function getCellRowFormat() { return $this->cellRowFormat; } public function setCellRowContentFormat(string $cellRowContentFormat) { $this->cellRowContentFormat = $cellRowContentFormat; return $this; } public function getCellRowContentFormat() { return $this->cellRowContentFormat; } public function setBorderFormat(string $borderFormat) { $this->borderFormat = $borderFormat; return $this; } public function getBorderFormat() { return $this->borderFormat; } public function setPadType(int $padType) { if (!\in_array($padType, [\STR_PAD_LEFT, \STR_PAD_RIGHT, \STR_PAD_BOTH], true)) { throw new InvalidArgumentException('Invalid padding type. Expected one of (STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH).'); } $this->padType = $padType; return $this; } public function getPadType() { return $this->padType; } public function getHeaderTitleFormat(): string { return $this->headerTitleFormat; } public function setHeaderTitleFormat(string $format): self { $this->headerTitleFormat = $format; return $this; } public function getFooterTitleFormat(): string { return $this->footerTitleFormat; } public function setFooterTitleFormat(string $format): self { $this->footerTitleFormat = $format; return $this; } } tokens = $argv; parent::__construct($definition); } protected function setTokens(array $tokens) { $this->tokens = $tokens; } protected function parse() { $parseOptions = true; $this->parsed = $this->tokens; while (null !== $token = array_shift($this->parsed)) { $parseOptions = $this->parseToken($token, $parseOptions); } } protected function parseToken(string $token, bool $parseOptions): bool { if ($parseOptions && '' == $token) { $this->parseArgument($token); } elseif ($parseOptions && '--' == $token) { return false; } elseif ($parseOptions && str_starts_with($token, '--')) { $this->parseLongOption($token); } elseif ($parseOptions && '-' === $token[0] && '-' !== $token) { $this->parseShortOption($token); } else { $this->parseArgument($token); } return $parseOptions; } private function parseShortOption(string $token) { $name = substr($token, 1); if (\strlen($name) > 1) { if ($this->definition->hasShortcut($name[0]) && $this->definition->getOptionForShortcut($name[0])->acceptValue()) { $this->addShortOption($name[0], substr($name, 1)); } else { $this->parseShortOptionSet($name); } } else { $this->addShortOption($name, null); } } private function parseShortOptionSet(string $name) { $len = \strlen($name); for ($i = 0; $i < $len; ++$i) { if (!$this->definition->hasShortcut($name[$i])) { $encoding = mb_detect_encoding($name, null, true); throw new RuntimeException(sprintf('The "-%s" option does not exist.', false === $encoding ? $name[$i] : mb_substr($name, $i, 1, $encoding))); } $option = $this->definition->getOptionForShortcut($name[$i]); if ($option->acceptValue()) { $this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1)); break; } else { $this->addLongOption($option->getName(), null); } } } private function parseLongOption(string $token) { $name = substr($token, 2); if (false !== $pos = strpos($name, '=')) { if ('' === $value = substr($name, $pos + 1)) { array_unshift($this->parsed, $value); } $this->addLongOption(substr($name, 0, $pos), $value); } else { $this->addLongOption($name, null); } } private function parseArgument(string $token) { $c = \count($this->arguments); if ($this->definition->hasArgument($c)) { $arg = $this->definition->getArgument($c); $this->arguments[$arg->getName()] = $arg->isArray() ? [$token] : $token; } elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) { $arg = $this->definition->getArgument($c - 1); $this->arguments[$arg->getName()][] = $token; } else { $all = $this->definition->getArguments(); $symfonyCommandName = null; if (($inputArgument = $all[$key = array_key_first($all)] ?? null) && 'command' === $inputArgument->getName()) { $symfonyCommandName = $this->arguments['command'] ?? null; unset($all[$key]); } if (\count($all)) { if ($symfonyCommandName) { $message = sprintf('Too many arguments to "%s" command, expected arguments "%s".', $symfonyCommandName, implode('" "', array_keys($all))); } else { $message = sprintf('Too many arguments, expected arguments "%s".', implode('" "', array_keys($all))); } } elseif ($symfonyCommandName) { $message = sprintf('No arguments expected for "%s" command, got "%s".', $symfonyCommandName, $token); } else { $message = sprintf('No arguments expected, got "%s".', $token); } throw new RuntimeException($message); } } private function addShortOption(string $shortcut, $value) { if (!$this->definition->hasShortcut($shortcut)) { throw new RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut)); } $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); } private function addLongOption(string $name, $value) { if (!$this->definition->hasOption($name)) { if (!$this->definition->hasNegation($name)) { throw new RuntimeException(sprintf('The "--%s" option does not exist.', $name)); } $optionName = $this->definition->negationToName($name); if (null !== $value) { throw new RuntimeException(sprintf('The "--%s" option does not accept a value.', $name)); } $this->options[$optionName] = false; return; } $option = $this->definition->getOption($name); if (null !== $value && !$option->acceptValue()) { throw new RuntimeException(sprintf('The "--%s" option does not accept a value.', $name)); } if (\in_array($value, ['', null], true) && $option->acceptValue() && \count($this->parsed)) { $next = array_shift($this->parsed); if ((isset($next[0]) && '-' !== $next[0]) || \in_array($next, ['', null], true)) { $value = $next; } else { array_unshift($this->parsed, $next); } } if (null === $value) { if ($option->isValueRequired()) { throw new RuntimeException(sprintf('The "--%s" option requires a value.', $name)); } if (!$option->isArray() && !$option->isValueOptional()) { $value = true; } } if ($option->isArray()) { $this->options[$name][] = $value; } else { $this->options[$name] = $value; } } public function getFirstArgument() { $isOption = false; foreach ($this->tokens as $i => $token) { if ($token && '-' === $token[0]) { if (str_contains($token, '=') || !isset($this->tokens[$i + 1])) { continue; } $name = '-' === $token[1] ? substr($token, 2) : substr($token, -1); if (!isset($this->options[$name]) && !$this->definition->hasShortcut($name)) { } elseif ((isset($this->options[$name]) || isset($this->options[$name = $this->definition->shortcutToName($name)])) && $this->tokens[$i + 1] === $this->options[$name]) { $isOption = true; } continue; } if ($isOption) { $isOption = false; continue; } return $token; } return null; } public function hasParameterOption($values, bool $onlyParams = false) { $values = (array) $values; foreach ($this->tokens as $token) { if ($onlyParams && '--' === $token) { return false; } foreach ($values as $value) { $leading = str_starts_with($value, '--') ? $value.'=' : $value; if ($token === $value || '' !== $leading && str_starts_with($token, $leading)) { return true; } } } return false; } public function getParameterOption($values, $default = false, bool $onlyParams = false) { $values = (array) $values; $tokens = $this->tokens; while (0 < \count($tokens)) { $token = array_shift($tokens); if ($onlyParams && '--' === $token) { return $default; } foreach ($values as $value) { if ($token === $value) { return array_shift($tokens); } $leading = str_starts_with($value, '--') ? $value.'=' : $value; if ('' !== $leading && str_starts_with($token, $leading)) { return substr($token, \strlen($leading)); } } } return $default; } public function __toString() { $tokens = array_map(function ($token) { if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) { return $match[1].$this->escapeToken($match[2]); } if ($token && '-' !== $token[0]) { return $this->escapeToken($token); } return $token; }, $this->tokens); return implode(' ', $tokens); } } parameters = $parameters; parent::__construct($definition); } public function getFirstArgument() { foreach ($this->parameters as $param => $value) { if ($param && \is_string($param) && '-' === $param[0]) { continue; } return $value; } return null; } public function hasParameterOption($values, bool $onlyParams = false) { $values = (array) $values; foreach ($this->parameters as $k => $v) { if (!\is_int($k)) { $v = $k; } if ($onlyParams && '--' === $v) { return false; } if (\in_array($v, $values)) { return true; } } return false; } public function getParameterOption($values, $default = false, bool $onlyParams = false) { $values = (array) $values; foreach ($this->parameters as $k => $v) { if ($onlyParams && ('--' === $k || (\is_int($k) && '--' === $v))) { return $default; } if (\is_int($k)) { if (\in_array($v, $values)) { return true; } } elseif (\in_array($k, $values)) { return $v; } } return $default; } public function __toString() { $params = []; foreach ($this->parameters as $param => $val) { if ($param && \is_string($param) && '-' === $param[0]) { $glue = ('-' === $param[1]) ? '=' : ' '; if (\is_array($val)) { foreach ($val as $v) { $params[] = $param.('' != $v ? $glue.$this->escapeToken($v) : ''); } } else { $params[] = $param.('' != $val ? $glue.$this->escapeToken($val) : ''); } } else { $params[] = \is_array($val) ? implode(' ', array_map([$this, 'escapeToken'], $val)) : $this->escapeToken($val); } } return implode(' ', $params); } protected function parse() { foreach ($this->parameters as $key => $value) { if ('--' === $key) { return; } if (str_starts_with($key, '--')) { $this->addLongOption(substr($key, 2), $value); } elseif (str_starts_with($key, '-')) { $this->addShortOption(substr($key, 1), $value); } else { $this->addArgument($key, $value); } } } private function addShortOption(string $shortcut, $value) { if (!$this->definition->hasShortcut($shortcut)) { throw new InvalidOptionException(sprintf('The "-%s" option does not exist.', $shortcut)); } $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); } private function addLongOption(string $name, $value) { if (!$this->definition->hasOption($name)) { if (!$this->definition->hasNegation($name)) { throw new InvalidOptionException(sprintf('The "--%s" option does not exist.', $name)); } $optionName = $this->definition->negationToName($name); $this->options[$optionName] = false; return; } $option = $this->definition->getOption($name); if (null === $value) { if ($option->isValueRequired()) { throw new InvalidOptionException(sprintf('The "--%s" option requires a value.', $name)); } if (!$option->isValueOptional()) { $value = true; } } $this->options[$name] = $value; } private function addArgument($name, $value) { if (!$this->definition->hasArgument($name)) { throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); } $this->arguments[$name] = $value; } } definition = new InputDefinition(); } else { $this->bind($definition); $this->validate(); } } public function bind(InputDefinition $definition) { $this->arguments = []; $this->options = []; $this->definition = $definition; $this->parse(); } abstract protected function parse(); public function validate() { $definition = $this->definition; $givenArguments = $this->arguments; $missingArguments = array_filter(array_keys($definition->getArguments()), function ($argument) use ($definition, $givenArguments) { return !\array_key_exists($argument, $givenArguments) && $definition->getArgument($argument)->isRequired(); }); if (\count($missingArguments) > 0) { throw new RuntimeException(sprintf('Not enough arguments (missing: "%s").', implode(', ', $missingArguments))); } } public function isInteractive() { return $this->interactive; } public function setInteractive(bool $interactive) { $this->interactive = $interactive; } public function getArguments() { return array_merge($this->definition->getArgumentDefaults(), $this->arguments); } public function getArgument(string $name) { if (!$this->definition->hasArgument($name)) { throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); } return $this->arguments[$name] ?? $this->definition->getArgument($name)->getDefault(); } public function setArgument(string $name, $value) { if (!$this->definition->hasArgument($name)) { throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); } $this->arguments[$name] = $value; } public function hasArgument(string $name) { return $this->definition->hasArgument($name); } public function getOptions() { return array_merge($this->definition->getOptionDefaults(), $this->options); } public function getOption(string $name) { if ($this->definition->hasNegation($name)) { if (null === $value = $this->getOption($this->definition->negationToName($name))) { return $value; } return !$value; } if (!$this->definition->hasOption($name)) { throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); } return \array_key_exists($name, $this->options) ? $this->options[$name] : $this->definition->getOption($name)->getDefault(); } public function setOption(string $name, $value) { if ($this->definition->hasNegation($name)) { $this->options[$this->definition->negationToName($name)] = !$value; return; } elseif (!$this->definition->hasOption($name)) { throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); } $this->options[$name] = $value; } public function hasOption(string $name) { return $this->definition->hasOption($name) || $this->definition->hasNegation($name); } public function escapeToken(string $token) { return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token); } public function setStream($stream) { $this->stream = $stream; } public function getStream() { return $this->stream; } } 7 || $mode < 1) { throw new InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode)); } $this->name = $name; $this->mode = $mode; $this->description = $description; $this->setDefault($default); } public function getName() { return $this->name; } public function isRequired() { return self::REQUIRED === (self::REQUIRED & $this->mode); } public function isArray() { return self::IS_ARRAY === (self::IS_ARRAY & $this->mode); } public function setDefault($default = null) { if ($this->isRequired() && null !== $default) { throw new LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.'); } if ($this->isArray()) { if (null === $default) { $default = []; } elseif (!\is_array($default)) { throw new LogicException('A default value for an array argument must be an array.'); } } $this->default = $default; } public function getDefault() { return $this->default; } public function getDescription() { return $this->description; } } setDefinition($definition); } public function setDefinition(array $definition) { $arguments = []; $options = []; foreach ($definition as $item) { if ($item instanceof InputOption) { $options[] = $item; } else { $arguments[] = $item; } } $this->setArguments($arguments); $this->setOptions($options); } public function setArguments(array $arguments = []) { $this->arguments = []; $this->requiredCount = 0; $this->lastOptionalArgument = null; $this->lastArrayArgument = null; $this->addArguments($arguments); } public function addArguments(?array $arguments = []) { if (null !== $arguments) { foreach ($arguments as $argument) { $this->addArgument($argument); } } } public function addArgument(InputArgument $argument) { if (isset($this->arguments[$argument->getName()])) { throw new LogicException(sprintf('An argument with name "%s" already exists.', $argument->getName())); } if (null !== $this->lastArrayArgument) { throw new LogicException(sprintf('Cannot add a required argument "%s" after an array argument "%s".', $argument->getName(), $this->lastArrayArgument->getName())); } if ($argument->isRequired() && null !== $this->lastOptionalArgument) { throw new LogicException(sprintf('Cannot add a required argument "%s" after an optional one "%s".', $argument->getName(), $this->lastOptionalArgument->getName())); } if ($argument->isArray()) { $this->lastArrayArgument = $argument; } if ($argument->isRequired()) { ++$this->requiredCount; } else { $this->lastOptionalArgument = $argument; } $this->arguments[$argument->getName()] = $argument; } public function getArgument($name) { if (!$this->hasArgument($name)) { throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); } $arguments = \is_int($name) ? array_values($this->arguments) : $this->arguments; return $arguments[$name]; } public function hasArgument($name) { $arguments = \is_int($name) ? array_values($this->arguments) : $this->arguments; return isset($arguments[$name]); } public function getArguments() { return $this->arguments; } public function getArgumentCount() { return null !== $this->lastArrayArgument ? \PHP_INT_MAX : \count($this->arguments); } public function getArgumentRequiredCount() { return $this->requiredCount; } public function getArgumentDefaults() { $values = []; foreach ($this->arguments as $argument) { $values[$argument->getName()] = $argument->getDefault(); } return $values; } public function setOptions(array $options = []) { $this->options = []; $this->shortcuts = []; $this->negations = []; $this->addOptions($options); } public function addOptions(array $options = []) { foreach ($options as $option) { $this->addOption($option); } } public function addOption(InputOption $option) { if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) { throw new LogicException(sprintf('An option named "%s" already exists.', $option->getName())); } if (isset($this->negations[$option->getName()])) { throw new LogicException(sprintf('An option named "%s" already exists.', $option->getName())); } if ($option->getShortcut()) { foreach (explode('|', $option->getShortcut()) as $shortcut) { if (isset($this->shortcuts[$shortcut]) && !$option->equals($this->options[$this->shortcuts[$shortcut]])) { throw new LogicException(sprintf('An option with shortcut "%s" already exists.', $shortcut)); } } } $this->options[$option->getName()] = $option; if ($option->getShortcut()) { foreach (explode('|', $option->getShortcut()) as $shortcut) { $this->shortcuts[$shortcut] = $option->getName(); } } if ($option->isNegatable()) { $negatedName = 'no-'.$option->getName(); if (isset($this->options[$negatedName])) { throw new LogicException(sprintf('An option named "%s" already exists.', $negatedName)); } $this->negations[$negatedName] = $option->getName(); } } public function getOption(string $name) { if (!$this->hasOption($name)) { throw new InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name)); } return $this->options[$name]; } public function hasOption(string $name) { return isset($this->options[$name]); } public function getOptions() { return $this->options; } public function hasShortcut(string $name) { return isset($this->shortcuts[$name]); } public function hasNegation(string $name): bool { return isset($this->negations[$name]); } public function getOptionForShortcut(string $shortcut) { return $this->getOption($this->shortcutToName($shortcut)); } public function getOptionDefaults() { $values = []; foreach ($this->options as $option) { $values[$option->getName()] = $option->getDefault(); } return $values; } public function shortcutToName(string $shortcut): string { if (!isset($this->shortcuts[$shortcut])) { throw new InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut)); } return $this->shortcuts[$shortcut]; } public function negationToName(string $negation): string { if (!isset($this->negations[$negation])) { throw new InvalidArgumentException(sprintf('The "--%s" option does not exist.', $negation)); } return $this->negations[$negation]; } public function getSynopsis(bool $short = false) { $elements = []; if ($short && $this->getOptions()) { $elements[] = '[options]'; } elseif (!$short) { foreach ($this->getOptions() as $option) { $value = ''; if ($option->acceptValue()) { $value = sprintf( ' %s%s%s', $option->isValueOptional() ? '[' : '', strtoupper($option->getName()), $option->isValueOptional() ? ']' : '' ); } $shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : ''; $negation = $option->isNegatable() ? sprintf('|--no-%s', $option->getName()) : ''; $elements[] = sprintf('[%s--%s%s%s]', $shortcut, $option->getName(), $value, $negation); } } if (\count($elements) && $this->getArguments()) { $elements[] = '[--]'; } $tail = ''; foreach ($this->getArguments() as $argument) { $element = '<'.$argument->getName().'>'; if ($argument->isArray()) { $element .= '...'; } if (!$argument->isRequired()) { $element = '['.$element; $tail .= ']'; } $elements[] = $element; } return implode(' ', $elements).$tail; } } = (self::VALUE_NEGATABLE << 1) || $mode < 1) { throw new InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode)); } $this->name = $name; $this->shortcut = $shortcut; $this->mode = $mode; $this->description = $description; if ($this->isArray() && !$this->acceptValue()) { throw new InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.'); } if ($this->isNegatable() && $this->acceptValue()) { throw new InvalidArgumentException('Impossible to have an option mode VALUE_NEGATABLE if the option also accepts a value.'); } $this->setDefault($default); } public function getShortcut() { return $this->shortcut; } public function getName() { return $this->name; } public function acceptValue() { return $this->isValueRequired() || $this->isValueOptional(); } public function isValueRequired() { return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode); } public function isValueOptional() { return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode); } public function isArray() { return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode); } public function isNegatable(): bool { return self::VALUE_NEGATABLE === (self::VALUE_NEGATABLE & $this->mode); } public function setDefault($default = null) { if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) { throw new LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.'); } if ($this->isArray()) { if (null === $default) { $default = []; } elseif (!\is_array($default)) { throw new LogicException('A default value for an array option must be an array.'); } } $this->default = $this->acceptValue() || $this->isNegatable() ? $default : false; } public function getDefault() { return $this->default; } public function getDescription() { return $this->description; } public function equals(self $option) { return $option->getName() === $this->getName() && $option->getShortcut() === $this->getShortcut() && $option->getDefault() === $this->getDefault() && $option->isNegatable() === $this->isNegatable() && $option->isArray() === $this->isArray() && $option->isValueRequired() === $this->isValueRequired() && $option->isValueOptional() === $this->isValueOptional() ; } } setTokens($this->tokenize($input)); } private function tokenize(string $input): array { $tokens = []; $length = \strlen($input); $cursor = 0; $token = null; while ($cursor < $length) { if ('\\' === $input[$cursor]) { $token .= $input[++$cursor] ?? ''; ++$cursor; continue; } if (preg_match('/\s+/A', $input, $match, 0, $cursor)) { if (null !== $token) { $tokens[] = $token; $token = null; } } elseif (preg_match('/([^="\'\s]+?)(=?)('.self::REGEX_QUOTED_STRING.'+)/A', $input, $match, 0, $cursor)) { $token .= $match[1].$match[2].stripcslashes(str_replace(['"\'', '\'"', '\'\'', '""'], '', substr($match[3], 1, -1))); } elseif (preg_match('/'.self::REGEX_QUOTED_STRING.'/A', $input, $match, 0, $cursor)) { $token .= stripcslashes(substr($match[0], 1, -1)); } elseif (preg_match('/'.self::REGEX_UNQUOTED_STRING.'/A', $input, $match, 0, $cursor)) { $token .= $match[1]; } else { throw new InvalidArgumentException(sprintf('Unable to parse input near "... %s ...".', substr($input, $cursor, 10))); } $cursor += \strlen($match[0]); } if (null !== $token) { $tokens[] = $token; } return $tokens; } } Copyright (c) 2004-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. OutputInterface::VERBOSITY_NORMAL, LogLevel::ALERT => OutputInterface::VERBOSITY_NORMAL, LogLevel::CRITICAL => OutputInterface::VERBOSITY_NORMAL, LogLevel::ERROR => OutputInterface::VERBOSITY_NORMAL, LogLevel::WARNING => OutputInterface::VERBOSITY_NORMAL, LogLevel::NOTICE => OutputInterface::VERBOSITY_VERBOSE, LogLevel::INFO => OutputInterface::VERBOSITY_VERY_VERBOSE, LogLevel::DEBUG => OutputInterface::VERBOSITY_DEBUG, ]; private $formatLevelMap = [ LogLevel::EMERGENCY => self::ERROR, LogLevel::ALERT => self::ERROR, LogLevel::CRITICAL => self::ERROR, LogLevel::ERROR => self::ERROR, LogLevel::WARNING => self::INFO, LogLevel::NOTICE => self::INFO, LogLevel::INFO => self::INFO, LogLevel::DEBUG => self::INFO, ]; private $errored = false; public function __construct(OutputInterface $output, array $verbosityLevelMap = [], array $formatLevelMap = []) { $this->output = $output; $this->verbosityLevelMap = $verbosityLevelMap + $this->verbosityLevelMap; $this->formatLevelMap = $formatLevelMap + $this->formatLevelMap; } public function log($level, $message, array $context = []) { if (!isset($this->verbosityLevelMap[$level])) { throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $level)); } $output = $this->output; if (self::ERROR === $this->formatLevelMap[$level]) { if ($this->output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); } $this->errored = true; } if ($output->getVerbosity() >= $this->verbosityLevelMap[$level]) { $output->writeln(sprintf('<%1$s>[%2$s] %3$s', $this->formatLevelMap[$level], $level, $this->interpolate($message, $context)), $this->verbosityLevelMap[$level]); } } public function hasErrored() { return $this->errored; } private function interpolate(string $message, array $context): string { if (!str_contains($message, '{')) { return $message; } $replacements = []; foreach ($context as $key => $val) { if (null === $val || \is_scalar($val) || (\is_object($val) && method_exists($val, '__toString'))) { $replacements["{{$key}}"] = $val; } elseif ($val instanceof \DateTimeInterface) { $replacements["{{$key}}"] = $val->format(\DateTime::RFC3339); } elseif (\is_object($val)) { $replacements["{{$key}}"] = '[object '.\get_class($val).']'; } else { $replacements["{{$key}}"] = '['.\gettype($val).']'; } } return strtr($message, $replacements); } } buffer; $this->buffer = ''; return $content; } protected function doWrite(string $message, bool $newline) { $this->buffer .= $message; if ($newline) { $this->buffer .= \PHP_EOL; } } } openOutputStream(), $verbosity, $decorated, $formatter); if (null === $formatter) { $this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated); return; } $actualDecorated = $this->isDecorated(); $this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated, $this->getFormatter()); if (null === $decorated) { $this->setDecorated($actualDecorated && $this->stderr->isDecorated()); } } public function section(): ConsoleSectionOutput { return new ConsoleSectionOutput($this->getStream(), $this->consoleSectionOutputs, $this->getVerbosity(), $this->isDecorated(), $this->getFormatter()); } public function setDecorated(bool $decorated) { parent::setDecorated($decorated); $this->stderr->setDecorated($decorated); } public function setFormatter(OutputFormatterInterface $formatter) { parent::setFormatter($formatter); $this->stderr->setFormatter($formatter); } public function setVerbosity(int $level) { parent::setVerbosity($level); $this->stderr->setVerbosity($level); } public function getErrorOutput() { return $this->stderr; } public function setErrorOutput(OutputInterface $error) { $this->stderr = $error; } protected function hasStdoutSupport() { return false === $this->isRunningOS400(); } protected function hasStderrSupport() { return false === $this->isRunningOS400(); } private function isRunningOS400(): bool { $checks = [ \function_exists('php_uname') ? php_uname('s') : '', getenv('OSTYPE'), \PHP_OS, ]; return false !== stripos(implode(';', $checks), 'OS400'); } private function openOutputStream() { if (!$this->hasStdoutSupport()) { return fopen('php://output', 'w'); } return \defined('STDOUT') ? \STDOUT : (@fopen('php://stdout', 'w') ?: fopen('php://output', 'w')); } private function openErrorStream() { if (!$this->hasStderrSupport()) { return fopen('php://output', 'w'); } return \defined('STDERR') ? \STDERR : (@fopen('php://stderr', 'w') ?: fopen('php://output', 'w')); } } sections = &$sections; $this->terminal = new Terminal(); } public function clear(?int $lines = null) { if (empty($this->content) || !$this->isDecorated()) { return; } if ($lines) { array_splice($this->content, -($lines * 2)); } else { $lines = $this->lines; $this->content = []; } $this->lines -= $lines; parent::doWrite($this->popStreamContentUntilCurrentSection($lines), false); } public function overwrite($message) { $this->clear(); $this->writeln($message); } public function getContent(): string { return implode('', $this->content); } public function addContent(string $input) { foreach (explode(\PHP_EOL, $input) as $lineContent) { $this->lines += ceil($this->getDisplayLength($lineContent) / $this->terminal->getWidth()) ?: 1; $this->content[] = $lineContent; $this->content[] = \PHP_EOL; } } protected function doWrite(string $message, bool $newline) { if (!$this->isDecorated()) { parent::doWrite($message, $newline); return; } $erasedContent = $this->popStreamContentUntilCurrentSection(); $this->addContent($message); parent::doWrite($message, true); parent::doWrite($erasedContent, false); } private function popStreamContentUntilCurrentSection(int $numberOfLinesToClearFromCurrentSection = 0): string { $numberOfLinesToClear = $numberOfLinesToClearFromCurrentSection; $erasedContent = []; foreach ($this->sections as $section) { if ($section === $this) { break; } $numberOfLinesToClear += $section->lines; $erasedContent[] = $section->getContent(); } if ($numberOfLinesToClear > 0) { parent::doWrite(sprintf("\x1b[%dA", $numberOfLinesToClear), false); parent::doWrite("\x1b[0J", false); } return implode('', array_reverse($erasedContent)); } private function getDisplayLength(string $text): int { return Helper::width(Helper::removeDecoration($this->getFormatter(), str_replace("\t", ' ', $text))); } } formatter) { return $this->formatter; } return $this->formatter = new NullOutputFormatter(); } public function setDecorated(bool $decorated) { } public function isDecorated() { return false; } public function setVerbosity(int $level) { } public function getVerbosity() { return self::VERBOSITY_QUIET; } public function isQuiet() { return true; } public function isVerbose() { return false; } public function isVeryVerbose() { return false; } public function isDebug() { return false; } public function writeln($messages, int $options = self::OUTPUT_NORMAL) { } public function write($messages, bool $newline = false, int $options = self::OUTPUT_NORMAL) { } } verbosity = $verbosity ?? self::VERBOSITY_NORMAL; $this->formatter = $formatter ?? new OutputFormatter(); $this->formatter->setDecorated($decorated); } public function setFormatter(OutputFormatterInterface $formatter) { $this->formatter = $formatter; } public function getFormatter() { return $this->formatter; } public function setDecorated(bool $decorated) { $this->formatter->setDecorated($decorated); } public function isDecorated() { return $this->formatter->isDecorated(); } public function setVerbosity(int $level) { $this->verbosity = $level; } public function getVerbosity() { return $this->verbosity; } public function isQuiet() { return self::VERBOSITY_QUIET === $this->verbosity; } public function isVerbose() { return self::VERBOSITY_VERBOSE <= $this->verbosity; } public function isVeryVerbose() { return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity; } public function isDebug() { return self::VERBOSITY_DEBUG <= $this->verbosity; } public function writeln($messages, int $options = self::OUTPUT_NORMAL) { $this->write($messages, true, $options); } public function write($messages, bool $newline = false, int $options = self::OUTPUT_NORMAL) { if (!is_iterable($messages)) { $messages = [$messages]; } $types = self::OUTPUT_NORMAL | self::OUTPUT_RAW | self::OUTPUT_PLAIN; $type = $types & $options ?: self::OUTPUT_NORMAL; $verbosities = self::VERBOSITY_QUIET | self::VERBOSITY_NORMAL | self::VERBOSITY_VERBOSE | self::VERBOSITY_VERY_VERBOSE | self::VERBOSITY_DEBUG; $verbosity = $verbosities & $options ?: self::VERBOSITY_NORMAL; if ($verbosity > $this->getVerbosity()) { return; } foreach ($messages as $message) { switch ($type) { case OutputInterface::OUTPUT_NORMAL: $message = $this->formatter->format($message); break; case OutputInterface::OUTPUT_RAW: break; case OutputInterface::OUTPUT_PLAIN: $message = strip_tags($this->formatter->format($message)); break; } $this->doWrite($message ?? '', $newline); } } abstract protected function doWrite(string $message, bool $newline); } stream = $stream; if (null === $decorated) { $decorated = $this->hasColorSupport(); } parent::__construct($verbosity, $decorated, $formatter); } public function getStream() { return $this->stream; } protected function doWrite(string $message, bool $newline) { if ($newline) { $message .= \PHP_EOL; } @fwrite($this->stream, $message); fflush($this->stream); } protected function hasColorSupport() { if ('' !== (($_SERVER['NO_COLOR'] ?? getenv('NO_COLOR'))[0] ?? '')) { return false; } if (!@stream_isatty($this->stream) && !\in_array(strtoupper((string) getenv('MSYSTEM')), ['MINGW32', 'MINGW64'], true)) { return false; } if ('\\' === \DIRECTORY_SEPARATOR && @sapi_windows_vt100_support($this->stream)) { return true; } if ('Hyper' === getenv('TERM_PROGRAM') || false !== getenv('COLORTERM') || false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI') ) { return true; } if ('dumb' === $term = (string) getenv('TERM')) { return false; } return preg_match('/^((screen|xterm|vt100|vt220|putty|rxvt|ansi|cygwin|linux).*)|(.*-256(color)?(-bce)?)$/', $term); } } maxLength = $maxLength; } public function fetch() { $content = $this->buffer; $this->buffer = ''; return $content; } protected function doWrite(string $message, bool $newline) { $this->buffer .= $message; if ($newline) { $this->buffer .= \PHP_EOL; } $this->buffer = substr($this->buffer, 0 - $this->maxLength); } } '; private $errorMessage = 'Value "%s" is invalid'; public function __construct(string $question, array $choices, $default = null) { if (!$choices) { throw new \LogicException('Choice question must have at least 1 choice available.'); } parent::__construct($question, $default); $this->choices = $choices; $this->setValidator($this->getDefaultValidator()); $this->setAutocompleterValues($choices); } public function getChoices() { return $this->choices; } public function setMultiselect(bool $multiselect) { $this->multiselect = $multiselect; $this->setValidator($this->getDefaultValidator()); return $this; } public function isMultiselect() { return $this->multiselect; } public function getPrompt() { return $this->prompt; } public function setPrompt(string $prompt) { $this->prompt = $prompt; return $this; } public function setErrorMessage(string $errorMessage) { $this->errorMessage = $errorMessage; $this->setValidator($this->getDefaultValidator()); return $this; } private function getDefaultValidator(): callable { $choices = $this->choices; $errorMessage = $this->errorMessage; $multiselect = $this->multiselect; $isAssoc = $this->isAssoc($choices); return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) { if ($multiselect) { if (!preg_match('/^[^,]+(?:,[^,]+)*$/', (string) $selected, $matches)) { throw new InvalidArgumentException(sprintf($errorMessage, $selected)); } $selectedChoices = explode(',', (string) $selected); } else { $selectedChoices = [$selected]; } if ($this->isTrimmable()) { foreach ($selectedChoices as $k => $v) { $selectedChoices[$k] = trim((string) $v); } } $multiselectChoices = []; foreach ($selectedChoices as $value) { $results = []; foreach ($choices as $key => $choice) { if ($choice === $value) { $results[] = $key; } } if (\count($results) > 1) { throw new InvalidArgumentException(sprintf('The provided answer is ambiguous. Value should be one of "%s".', implode('" or "', $results))); } $result = array_search($value, $choices); if (!$isAssoc) { if (false !== $result) { $result = $choices[$result]; } elseif (isset($choices[$value])) { $result = $choices[$value]; } } elseif (false === $result && isset($choices[$value])) { $result = $value; } if (false === $result) { throw new InvalidArgumentException(sprintf($errorMessage, $value)); } $multiselectChoices[] = $isAssoc ? (string) $result : $result; } if ($multiselect) { return $multiselectChoices; } return current($multiselectChoices); }; } } trueAnswerRegex = $trueAnswerRegex; $this->setNormalizer($this->getDefaultNormalizer()); } private function getDefaultNormalizer(): callable { $default = $this->getDefault(); $regex = $this->trueAnswerRegex; return function ($answer) use ($default, $regex) { if (\is_bool($answer)) { return $answer; } $answerIsTrue = (bool) preg_match($regex, $answer); if (false === $default) { return $answer && $answerIsTrue; } return '' === $answer || $answerIsTrue; }; } } question = $question; $this->default = $default; } public function getQuestion() { return $this->question; } public function getDefault() { return $this->default; } public function isMultiline(): bool { return $this->multiline; } public function setMultiline(bool $multiline): self { $this->multiline = $multiline; return $this; } public function isHidden() { return $this->hidden; } public function setHidden(bool $hidden) { if ($this->autocompleterCallback) { throw new LogicException('A hidden question cannot use the autocompleter.'); } $this->hidden = $hidden; return $this; } public function isHiddenFallback() { return $this->hiddenFallback; } public function setHiddenFallback(bool $fallback) { $this->hiddenFallback = $fallback; return $this; } public function getAutocompleterValues() { $callback = $this->getAutocompleterCallback(); return $callback ? $callback('') : null; } public function setAutocompleterValues(?iterable $values) { if (\is_array($values)) { $values = $this->isAssoc($values) ? array_merge(array_keys($values), array_values($values)) : array_values($values); $callback = static function () use ($values) { return $values; }; } elseif ($values instanceof \Traversable) { $valueCache = null; $callback = static function () use ($values, &$valueCache) { return $valueCache ?? $valueCache = iterator_to_array($values, false); }; } else { $callback = null; } return $this->setAutocompleterCallback($callback); } public function getAutocompleterCallback(): ?callable { return $this->autocompleterCallback; } public function setAutocompleterCallback(?callable $callback = null): self { if ($this->hidden && null !== $callback) { throw new LogicException('A hidden question cannot use the autocompleter.'); } $this->autocompleterCallback = $callback; return $this; } public function setValidator(?callable $validator = null) { $this->validator = $validator; return $this; } public function getValidator() { return $this->validator; } public function setMaxAttempts(?int $attempts) { if (null !== $attempts && $attempts < 1) { throw new InvalidArgumentException('Maximum number of attempts must be a positive value.'); } $this->attempts = $attempts; return $this; } public function getMaxAttempts() { return $this->attempts; } public function setNormalizer(callable $normalizer) { $this->normalizer = $normalizer; return $this; } public function getNormalizer() { return $this->normalizer; } protected function isAssoc(array $array) { return (bool) \count(array_filter(array_keys($array), 'is_string')); } public function isTrimmable(): bool { return $this->trimmable; } public function setTrimmable(bool $trimmable): self { $this->trimmable = $trimmable; return $this; } } MZ@ !L!This program cannot be run in DOS mode. $,;B;B;B2מ:B2-B2ƞ9B2ў?Ba98B;CB2Ȟ:B2֞:B2Ӟ:BRich;BPELMoO  8 @`?@"P@ Pp!8!@ .text   `.rdata @@.data0@.rsrc @@@.relocP"@Bj$@xj @eEPV @EЃPV @MX @eEP5H @L @YY5\ @EP5` @D @YYP @MMT @3H; 0@uh@l3@$40@5h3@40@h$0@h(0@h 0@ @00@}jYjh"@3ۉ]dp]俀3@SVW0 @;t;u3Fuh4 @3F|3@;u j\Y;|3@u,5|3@h @h @YYtE5<0@|3@;uh @h @lYY|3@9]uSW8 @93@th3@Yt SjS3@$0@ @5$0@5(0@5 0@ 80@9,0@u7P @E MPQYYËeE80@39,0@uPh @9<0@u @E80@øMZf9@t3M<@@8PEuH t uՃv39xtv39j,0@p @jl @YY3@3@ @ t3@ @ p3@ @x3@V=0@u h@ @Yg=0@u j @Y3{U(H1@ D1@@1@<1@581@=41@f`1@f T1@f01@f,1@f%(1@f-$1@X1@EL1@EP1@E\1@0@P1@L0@@0@ D0@0@0@ @0@j?Yj @h!@$ @=0@ujYh ( @P, @ËUE8csmu*xu$@= t=!t="t=@u3]hH@ @3% @jh("@b53@5 @YEu u @YgjYe53@։E53@YYEEPEPu5l @YPUEu֣3@uփ3@E EjYËUuNYH]ËV!@!@W;stЃ;r_^ËV"@"@W;stЃ;r_^% @̋UMMZf9t3]ËA<8PEu3ҹ f9H‹]̋UEH<ASVq3WDv} H ;r X;r B(;r3_^[]̋UjhH"@he@dPSVW0@1E3PEdeEh@*tUE-@Ph@Pt;@$ЃEMd Y_^[]ËE3=‹ËeE3Md Y_^[]% @% @he@d5D$l$l$+SVW0@1E3PeuEEEEdËMd Y__^[]QËUuuu uh@h0@]ËVhh3V t VVVVV^3ËU0@eeSWN@;t t У0@`VEP< @u3u @3 @3 @3EP @E3E3;uO@ u 50@։50@^_[%t @%x @%| @% @% @% @% @% @% @Pd5D$ +d$ SVW(0@3PEuEEdËMd Y__^[]QËM3M%T @T$B J3J3l"@s###)r)b)H)4))(((((()#$%%&d&&$('''''(((6('H(Z(t(('''''l'^'R'F'>'>(0'')@W@@MoOl!@0@0@bad allocationH0@!@RSDSьJ!LZc:\users\seld\documents\visual studio 2010\Projects\hiddeninp\Release\hiddeninp.pdbe@@:@@@@"d"@"# $#&D H#(h ###)r)b)H)4))(((((()#$%%&d&&$('''''(((6('H(Z(t(('''''l'^'R'F'>'>(0'')GetConsoleModeSetConsoleMode;GetStdHandleKERNEL32.dll??$?6DU?$char_traits@D@std@@V?$allocator@D@1@@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@0@AAV10@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@0@@Z?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@AJ?cin@std@@3V?$basic_istream@DU?$char_traits@D@std@@@1@A??$getline@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@YAAAV?$basic_istream@DU?$char_traits@D@std@@@0@AAV10@AAV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@0@@Z??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@P6AAAV01@AAV01@@Z@Z_??1?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@QAE@XZ{??0?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@QAE@XZ?endl@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@1@AAV21@@ZMSVCP90.dll_amsg_exit__getmainargs,_cexit|_exitf_XcptFilterexit__initenv_initterm_initterm_e<_configthreadlocale__setusermatherr _adjust_fdiv__p__commode__p__fmodej_encode_pointer__set_app_typeK_crt_debugger_hookC?terminate@@YAXXZMSVCR90.dll_unlock__dllonexitv_lock_onexit`_decode_pointers_except_handler4_common _invoke_watson?_controlfp_sInterlockedExchange!SleepInterlockedCompareExchange-TerminateProcessGetCurrentProcess>UnhandledExceptionFilterSetUnhandledExceptionFilterIsDebuggerPresentTQueryPerformanceCounterfGetTickCountGetCurrentThreadIdGetCurrentProcessIdOGetSystemTimeAsFileTimes__CxxFrameHandler3N@D$!@ 8Ph  @(CV(4VS_VERSION_INFOStringFileInfob040904b0QFileDescriptionReads from stdin without leaking info to the terminal and outputs back to stdout6 FileVersion1, 0, 0, 08 InternalNamehiddeninputPLegalCopyrightJordi Boggiano - 2012HOriginalFilenamehiddeninput.exe: ProductNameHidden Input: ProductVersion1, 0, 0, 0DVarFileInfo$Translation  PAPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDING@00!0/080F0L0T0^0d0n0{000000000000001#1-1@1J1O1T1v1{1111111111111112"2*23292A2M2_2j2p222222222222 333%303N3T3Z3`3f3l3s3z333333333333333334444%4;4B444444444445!5^5c5555H6M6_6}66677 7*7w7|777778 88=8E8P8V8\8b8h8n8t8z88889 $0001 1t1x12 2@2\2`2h2t20 0# This file is part of the Symfony package. # # (c) Fabien Potencier # # For the full copyright and license information, please view # https://symfony.com/doc/current/contributing/code/license.html _sf_{{ COMMAND_NAME }}() { # Use newline as only separator to allow space in completion values local IFS=$'\n' local sf_cmd="${COMP_WORDS[0]}" # for an alias, get the real script behind it sf_cmd_type=$(type -t $sf_cmd) if [[ $sf_cmd_type == "alias" ]]; then sf_cmd=$(alias $sf_cmd | sed -E "s/alias $sf_cmd='(.*)'/\1/") elif [[ $sf_cmd_type == "file" ]]; then sf_cmd=$(type -p $sf_cmd) fi if [[ $sf_cmd_type != "function" && ! -x $sf_cmd ]]; then return 1 fi local cur prev words cword _get_comp_words_by_ref -n := cur prev words cword local completecmd=("$sf_cmd" "_complete" "--no-interaction" "-sbash" "-c$cword" "-S{{ VERSION }}") for w in ${words[@]}; do w=$(printf -- '%b' "$w") # remove quotes from typed values quote="${w:0:1}" if [ "$quote" == \' ]; then w="${w%\'}" w="${w#\'}" elif [ "$quote" == \" ]; then w="${w%\"}" w="${w#\"}" fi # empty values are ignored if [ ! -z "$w" ]; then completecmd+=("-i$w") fi done local sfcomplete if sfcomplete=$(${completecmd[@]} 2>&1); then local quote suggestions quote=${cur:0:1} # Use single quotes by default if suggestions contains backslash (FQCN) if [ "$quote" == '' ] && [[ "$sfcomplete" =~ \\ ]]; then quote=\' fi if [ "$quote" == \' ]; then # single quotes: no additional escaping (does not accept ' in values) suggestions=$(for s in $sfcomplete; do printf $'%q%q%q\n' "$quote" "$s" "$quote"; done) elif [ "$quote" == \" ]; then # double quotes: double escaping for \ $ ` " suggestions=$(for s in $sfcomplete; do s=${s//\\/\\\\} s=${s//\$/\\\$} s=${s//\`/\\\`} s=${s//\"/\\\"} printf $'%q%q%q\n' "$quote" "$s" "$quote"; done) else # no quotes: double escaping suggestions=$(for s in $sfcomplete; do printf $'%q\n' $(printf '%q' "$s"); done) fi COMPREPLY=($(IFS=$'\n' compgen -W "$suggestions" -- $(printf -- "%q" "$cur"))) __ltrim_colon_completions "$cur" else if [[ "$sfcomplete" != *"Command \"_complete\" is not defined."* ]]; then >&2 echo >&2 echo $sfcomplete fi return 1 fi } complete -F _sf_{{ COMMAND_NAME }} {{ COMMAND_NAME }} signalHandlers[$signal])) { $previousCallback = pcntl_signal_get_handler($signal); if (\is_callable($previousCallback)) { $this->signalHandlers[$signal][] = $previousCallback; } } $this->signalHandlers[$signal][] = $signalHandler; pcntl_signal($signal, [$this, 'handle']); } public static function isSupported(): bool { if (!\function_exists('pcntl_signal')) { return false; } if (\in_array('pcntl_signal', explode(',', \ini_get('disable_functions')))) { return false; } return true; } public function handle(int $signal): void { $count = \count($this->signalHandlers[$signal]); foreach ($this->signalHandlers[$signal] as $i => $signalHandler) { $hasNext = $i !== $count - 1; $signalHandler($signal, $hasNext); } } } version = $version; return $this; } public function setAutoExit(bool $autoExit): self { $this->autoExit = $autoExit; return $this; } public function run(?InputInterface $input = null, ?OutputInterface $output = null): int { if ($this->running) { return parent::run($input, $output); } $application = new Application($this->getName() ?: 'UNKNOWN', $this->version); $application->setAutoExit($this->autoExit); $this->setName($_SERVER['argv'][0]); $application->add($this); $application->setDefaultCommand($this->getName(), true); $this->running = true; try { $ret = $application->run($input, $output); } finally { $this->running = false; } return $ret ?? 1; } } output = $output; } public function newLine(int $count = 1) { $this->output->write(str_repeat(\PHP_EOL, $count)); } public function createProgressBar(int $max = 0) { return new ProgressBar($this->output, $max); } public function write($messages, bool $newline = false, int $type = self::OUTPUT_NORMAL) { $this->output->write($messages, $newline, $type); } public function writeln($messages, int $type = self::OUTPUT_NORMAL) { $this->output->writeln($messages, $type); } public function setVerbosity(int $level) { $this->output->setVerbosity($level); } public function getVerbosity() { return $this->output->getVerbosity(); } public function setDecorated(bool $decorated) { $this->output->setDecorated($decorated); } public function isDecorated() { return $this->output->isDecorated(); } public function setFormatter(OutputFormatterInterface $formatter) { $this->output->setFormatter($formatter); } public function getFormatter() { return $this->output->getFormatter(); } public function isQuiet() { return $this->output->isQuiet(); } public function isVerbose() { return $this->output->isVerbose(); } public function isVeryVerbose() { return $this->output->isVeryVerbose(); } public function isDebug() { return $this->output->isDebug(); } protected function getErrorOutput() { if (!$this->output instanceof ConsoleOutputInterface) { return $this->output; } return $this->output->getErrorOutput(); } } input = $input; $this->bufferedOutput = new TrimmedBufferOutput(\DIRECTORY_SEPARATOR === '\\' ? 4 : 2, $output->getVerbosity(), false, clone $output->getFormatter()); $width = (new Terminal())->getWidth() ?: self::MAX_LINE_LENGTH; $this->lineLength = min($width - (int) (\DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH); parent::__construct($this->output = $output); } public function block($messages, ?string $type = null, ?string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = true) { $messages = \is_array($messages) ? array_values($messages) : [$messages]; $this->autoPrependBlock(); $this->writeln($this->createBlock($messages, $type, $style, $prefix, $padding, $escape)); $this->newLine(); } public function title(string $message) { $this->autoPrependBlock(); $this->writeln([ sprintf('%s', OutputFormatter::escapeTrailingBackslash($message)), sprintf('%s', str_repeat('=', Helper::width(Helper::removeDecoration($this->getFormatter(), $message)))), ]); $this->newLine(); } public function section(string $message) { $this->autoPrependBlock(); $this->writeln([ sprintf('%s', OutputFormatter::escapeTrailingBackslash($message)), sprintf('%s', str_repeat('-', Helper::width(Helper::removeDecoration($this->getFormatter(), $message)))), ]); $this->newLine(); } public function listing(array $elements) { $this->autoPrependText(); $elements = array_map(function ($element) { return sprintf(' * %s', $element); }, $elements); $this->writeln($elements); $this->newLine(); } public function text($message) { $this->autoPrependText(); $messages = \is_array($message) ? array_values($message) : [$message]; foreach ($messages as $message) { $this->writeln(sprintf(' %s', $message)); } } public function comment($message) { $this->block($message, null, null, ' // ', false, false); } public function success($message) { $this->block($message, 'OK', 'fg=black;bg=green', ' ', true); } public function error($message) { $this->block($message, 'ERROR', 'fg=white;bg=red', ' ', true); } public function warning($message) { $this->block($message, 'WARNING', 'fg=black;bg=yellow', ' ', true); } public function note($message) { $this->block($message, 'NOTE', 'fg=yellow', ' ! '); } public function info($message) { $this->block($message, 'INFO', 'fg=green', ' ', true); } public function caution($message) { $this->block($message, 'CAUTION', 'fg=white;bg=red', ' ! ', true); } public function table(array $headers, array $rows) { $this->createTable() ->setHeaders($headers) ->setRows($rows) ->render() ; $this->newLine(); } public function horizontalTable(array $headers, array $rows) { $this->createTable() ->setHorizontal(true) ->setHeaders($headers) ->setRows($rows) ->render() ; $this->newLine(); } public function definitionList(...$list) { $headers = []; $row = []; foreach ($list as $value) { if ($value instanceof TableSeparator) { $headers[] = $value; $row[] = $value; continue; } if (\is_string($value)) { $headers[] = new TableCell($value, ['colspan' => 2]); $row[] = null; continue; } if (!\is_array($value)) { throw new InvalidArgumentException('Value should be an array, string, or an instance of TableSeparator.'); } $headers[] = key($value); $row[] = current($value); } $this->horizontalTable($headers, [$row]); } public function ask(string $question, ?string $default = null, ?callable $validator = null) { $question = new Question($question, $default); $question->setValidator($validator); return $this->askQuestion($question); } public function askHidden(string $question, ?callable $validator = null) { $question = new Question($question); $question->setHidden(true); $question->setValidator($validator); return $this->askQuestion($question); } public function confirm(string $question, bool $default = true) { return $this->askQuestion(new ConfirmationQuestion($question, $default)); } public function choice(string $question, array $choices, $default = null) { if (null !== $default) { $values = array_flip($choices); $default = $values[$default] ?? $default; } return $this->askQuestion(new ChoiceQuestion($question, $choices, $default)); } public function progressStart(int $max = 0) { $this->progressBar = $this->createProgressBar($max); $this->progressBar->start(); } public function progressAdvance(int $step = 1) { $this->getProgressBar()->advance($step); } public function progressFinish() { $this->getProgressBar()->finish(); $this->newLine(2); $this->progressBar = null; } public function createProgressBar(int $max = 0) { $progressBar = parent::createProgressBar($max); if ('\\' !== \DIRECTORY_SEPARATOR || 'Hyper' === getenv('TERM_PROGRAM')) { $progressBar->setEmptyBarCharacter('░'); $progressBar->setProgressCharacter(''); $progressBar->setBarCharacter('▓'); } return $progressBar; } public function progressIterate(iterable $iterable, ?int $max = null): iterable { yield from $this->createProgressBar()->iterate($iterable, $max); $this->newLine(2); } public function askQuestion(Question $question) { if ($this->input->isInteractive()) { $this->autoPrependBlock(); } if (!$this->questionHelper) { $this->questionHelper = new SymfonyQuestionHelper(); } $answer = $this->questionHelper->ask($this->input, $this, $question); if ($this->input->isInteractive()) { $this->newLine(); $this->bufferedOutput->write("\n"); } return $answer; } public function writeln($messages, int $type = self::OUTPUT_NORMAL) { if (!is_iterable($messages)) { $messages = [$messages]; } foreach ($messages as $message) { parent::writeln($message, $type); $this->writeBuffer($message, true, $type); } } public function write($messages, bool $newline = false, int $type = self::OUTPUT_NORMAL) { if (!is_iterable($messages)) { $messages = [$messages]; } foreach ($messages as $message) { parent::write($message, $newline, $type); $this->writeBuffer($message, $newline, $type); } } public function newLine(int $count = 1) { parent::newLine($count); $this->bufferedOutput->write(str_repeat("\n", $count)); } public function getErrorStyle() { return new self($this->input, $this->getErrorOutput()); } public function createTable(): Table { $output = $this->output instanceof ConsoleOutputInterface ? $this->output->section() : $this->output; $style = clone Table::getStyleDefinition('symfony-style-guide'); $style->setCellHeaderFormat('%s'); return (new Table($output))->setStyle($style); } private function getProgressBar(): ProgressBar { if (!$this->progressBar) { throw new RuntimeException('The ProgressBar is not started.'); } return $this->progressBar; } private function autoPrependBlock(): void { $chars = substr(str_replace(\PHP_EOL, "\n", $this->bufferedOutput->fetch()), -2); if (!isset($chars[0])) { $this->newLine(); return; } $this->newLine(2 - substr_count($chars, "\n")); } private function autoPrependText(): void { $fetched = $this->bufferedOutput->fetch(); if (!str_ends_with($fetched, "\n")) { $this->newLine(); } } private function writeBuffer(string $message, bool $newLine, int $type): void { $this->bufferedOutput->write($message, $newLine, $type); } private function createBlock(iterable $messages, ?string $type = null, ?string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = false): array { $indentLength = 0; $prefixLength = Helper::width(Helper::removeDecoration($this->getFormatter(), $prefix)); $lines = []; if (null !== $type) { $type = sprintf('[%s] ', $type); $indentLength = \strlen($type); $lineIndentation = str_repeat(' ', $indentLength); } foreach ($messages as $key => $message) { if ($escape) { $message = OutputFormatter::escape($message); } $decorationLength = Helper::width($message) - Helper::width(Helper::removeDecoration($this->getFormatter(), $message)); $messageLineLength = min($this->lineLength - $prefixLength - $indentLength + $decorationLength, $this->lineLength); $messageLines = explode(\PHP_EOL, wordwrap($message, $messageLineLength, \PHP_EOL, true)); foreach ($messageLines as $messageLine) { $lines[] = $messageLine; } if (\count($messages) > 1 && $key < \count($messages) - 1) { $lines[] = ''; } } $firstLineIndex = 0; if ($padding && $this->isDecorated()) { $firstLineIndex = 1; array_unshift($lines, ''); $lines[] = ''; } foreach ($lines as $i => &$line) { if (null !== $type) { $line = $firstLineIndex === $i ? $type.$line : $lineIndentation.$line; } $line = $prefix.$line; $line .= str_repeat(' ', max($this->lineLength - Helper::width(Helper::removeDecoration($this->getFormatter(), $line)), 0)); if ($style) { $line = sprintf('<%s>%s', $style, $line); } } return $lines; } } '.('\\' === \DIRECTORY_SEPARATOR ? 'NUL' : '/dev/null')); } private static function initDimensions() { if ('\\' === \DIRECTORY_SEPARATOR) { $ansicon = getenv('ANSICON'); if (false !== $ansicon && preg_match('/^(\d+)x(\d+)(?: \((\d+)x(\d+)\))?$/', trim($ansicon), $matches)) { self::$width = (int) $matches[1]; self::$height = isset($matches[4]) ? (int) $matches[4] : (int) $matches[2]; } elseif (!self::hasVt100Support() && self::hasSttyAvailable()) { self::initDimensionsUsingStty(); } elseif (null !== $dimensions = self::getConsoleMode()) { self::$width = (int) $dimensions[0]; self::$height = (int) $dimensions[1]; } } else { self::initDimensionsUsingStty(); } } private static function hasVt100Support(): bool { return \function_exists('sapi_windows_vt100_support') && sapi_windows_vt100_support(fopen('php://stdout', 'w')); } private static function initDimensionsUsingStty() { if ($sttyString = self::getSttyColumns()) { if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) { self::$width = (int) $matches[2]; self::$height = (int) $matches[1]; } elseif (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) { self::$width = (int) $matches[2]; self::$height = (int) $matches[1]; } } } private static function getConsoleMode(): ?array { $info = self::readFromProcess('mode CON'); if (null === $info || !preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) { return null; } return [(int) $matches[2], (int) $matches[1]]; } private static function getSttyColumns(): ?string { return self::readFromProcess('stty -a | grep columns'); } private static function readFromProcess(string $command): ?string { if (!\function_exists('proc_open')) { return null; } $descriptorspec = [ 1 => ['pipe', 'w'], 2 => ['pipe', 'w'], ]; $cp = \function_exists('sapi_windows_cp_set') ? sapi_windows_cp_get() : 0; if (!$process = @proc_open($command, $descriptorspec, $pipes, null, null, ['suppress_errors' => true])) { return null; } $info = stream_get_contents($pipes[1]); fclose($pipes[1]); fclose($pipes[2]); proc_close($process); if ($cp) { sapi_windows_cp_set($cp); } return $info; } } application = $application; } public function run(array $input, array $options = []) { $prevShellVerbosity = getenv('SHELL_VERBOSITY'); try { $this->input = new ArrayInput($input); if (isset($options['interactive'])) { $this->input->setInteractive($options['interactive']); } if ($this->inputs) { $this->input->setStream(self::createStream($this->inputs)); } $this->initOutput($options); return $this->statusCode = $this->application->run($this->input, $this->output); } finally { if (false === $prevShellVerbosity) { if (\function_exists('putenv')) { @putenv('SHELL_VERBOSITY'); } unset($_ENV['SHELL_VERBOSITY']); unset($_SERVER['SHELL_VERBOSITY']); } else { if (\function_exists('putenv')) { @putenv('SHELL_VERBOSITY='.$prevShellVerbosity); } $_ENV['SHELL_VERBOSITY'] = $prevShellVerbosity; $_SERVER['SHELL_VERBOSITY'] = $prevShellVerbosity; } } } } command = $command; } public function complete(array $input): array { $currentIndex = \count($input); if ('' === end($input)) { array_pop($input); } array_unshift($input, $this->command->getName()); $completionInput = CompletionInput::fromTokens($input, $currentIndex); $completionInput->bind($this->command->getDefinition()); $suggestions = new CompletionSuggestions(); $this->command->complete($completionInput, $suggestions); $options = []; foreach ($suggestions->getOptionSuggestions() as $option) { $options[] = '--'.$option->getName(); } return array_map('strval', array_merge($options, $suggestions->getValueSuggestions())); } } command = $command; } public function execute(array $input, array $options = []) { if (!isset($input['command']) && (null !== $application = $this->command->getApplication()) && $application->getDefinition()->hasArgument('command') ) { $input = array_merge(['command' => $this->command->getName()], $input); } $this->input = new ArrayInput($input); $this->input->setStream(self::createStream($this->inputs)); if (isset($options['interactive'])) { $this->input->setInteractive($options['interactive']); } if (!isset($options['decorated'])) { $options['decorated'] = false; } $this->initOutput($options); return $this->statusCode = $this->command->run($this->input, $this->output); } } toString(); } protected function additionalFailureDescription($other): string { $mapping = [ Command::FAILURE => 'Command failed.', Command::INVALID => 'Command was invalid.', ]; return $mapping[$other] ?? sprintf('Command returned exit status %d.', $other); } } output) { throw new \RuntimeException('Output not initialized, did you execute the command before requesting the display?'); } rewind($this->output->getStream()); $display = stream_get_contents($this->output->getStream()); if ($normalize) { $display = str_replace(\PHP_EOL, "\n", $display); } return $display; } public function getErrorOutput(bool $normalize = false) { if (!$this->captureStreamsIndependently) { throw new \LogicException('The error output is not available when the tester is run without "capture_stderr_separately" option set.'); } rewind($this->output->getErrorOutput()->getStream()); $display = stream_get_contents($this->output->getErrorOutput()->getStream()); if ($normalize) { $display = str_replace(\PHP_EOL, "\n", $display); } return $display; } public function getInput() { return $this->input; } public function getOutput() { return $this->output; } public function getStatusCode() { if (null === $this->statusCode) { throw new \RuntimeException('Status code not initialized, did you execute the command before requesting the status code?'); } return $this->statusCode; } public function assertCommandIsSuccessful(string $message = ''): void { Assert::assertThat($this->statusCode, new CommandIsSuccessful(), $message); } public function setInputs(array $inputs) { $this->inputs = $inputs; return $this; } private function initOutput(array $options) { $this->captureStreamsIndependently = \array_key_exists('capture_stderr_separately', $options) && $options['capture_stderr_separately']; if (!$this->captureStreamsIndependently) { $this->output = new StreamOutput(fopen('php://memory', 'w', false)); if (isset($options['decorated'])) { $this->output->setDecorated($options['decorated']); } if (isset($options['verbosity'])) { $this->output->setVerbosity($options['verbosity']); } } else { $this->output = new ConsoleOutput( $options['verbosity'] ?? ConsoleOutput::VERBOSITY_NORMAL, $options['decorated'] ?? null ); $errorOutput = new StreamOutput(fopen('php://memory', 'w', false)); $errorOutput->setFormatter($this->output->getFormatter()); $errorOutput->setVerbosity($this->output->getVerbosity()); $errorOutput->setDecorated($this->output->isDecorated()); $reflectedOutput = new \ReflectionObject($this->output); $strErrProperty = $reflectedOutput->getProperty('stderr'); $strErrProperty->setAccessible(true); $strErrProperty->setValue($this->output, $errorOutput); $reflectedParent = $reflectedOutput->getParentClass(); $streamProperty = $reflectedParent->getProperty('stream'); $streamProperty->setAccessible(true); $streamProperty->setValue($this->output, fopen('php://memory', 'w', false)); } } private static function createStream(array $inputs) { $stream = fopen('php://memory', 'r+', false); foreach ($inputs as $input) { fwrite($stream, $input.\PHP_EOL); } rewind($stream); return $stream; } } Copyright (c) 2020-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. path = $path; parent::__construct($message, $code, $previous); } public function getPath() { return $this->path; } } mkdir(\dirname($targetFile)); $doCopy = true; if (!$overwriteNewerFiles && !parse_url($originFile, \PHP_URL_HOST) && is_file($targetFile)) { $doCopy = filemtime($originFile) > filemtime($targetFile); } if ($doCopy) { if (!$source = self::box('fopen', $originFile, 'r')) { throw new IOException(sprintf('Failed to copy "%s" to "%s" because source file could not be opened for reading: ', $originFile, $targetFile).self::$lastError, 0, null, $originFile); } if (!$target = self::box('fopen', $targetFile, 'w', false, stream_context_create(['ftp' => ['overwrite' => true]]))) { throw new IOException(sprintf('Failed to copy "%s" to "%s" because target file could not be opened for writing: ', $originFile, $targetFile).self::$lastError, 0, null, $originFile); } $bytesCopied = stream_copy_to_stream($source, $target); fclose($source); fclose($target); unset($source, $target); if (!is_file($targetFile)) { throw new IOException(sprintf('Failed to copy "%s" to "%s".', $originFile, $targetFile), 0, null, $originFile); } if ($originIsLocal) { self::box('chmod', $targetFile, fileperms($targetFile) | (fileperms($originFile) & 0111)); self::box('touch', $targetFile, filemtime($originFile)); if ($bytesCopied !== $bytesOrigin = filesize($originFile)) { throw new IOException(sprintf('Failed to copy the whole content of "%s" to "%s" (%g of %g bytes copied).', $originFile, $targetFile, $bytesCopied, $bytesOrigin), 0, null, $originFile); } } } } public function mkdir($dirs, int $mode = 0777) { foreach ($this->toIterable($dirs) as $dir) { if (is_dir($dir)) { continue; } if (!self::box('mkdir', $dir, $mode, true) && !is_dir($dir)) { throw new IOException(sprintf('Failed to create "%s": ', $dir).self::$lastError, 0, null, $dir); } } } public function exists($files) { $maxPathLength = \PHP_MAXPATHLEN - 2; foreach ($this->toIterable($files) as $file) { if (\strlen($file) > $maxPathLength) { throw new IOException(sprintf('Could not check if file exist because path length exceeds %d characters.', $maxPathLength), 0, null, $file); } if (!file_exists($file)) { return false; } } return true; } public function touch($files, ?int $time = null, ?int $atime = null) { foreach ($this->toIterable($files) as $file) { if (!($time ? self::box('touch', $file, $time, $atime) : self::box('touch', $file))) { throw new IOException(sprintf('Failed to touch "%s": ', $file).self::$lastError, 0, null, $file); } } } public function remove($files) { if ($files instanceof \Traversable) { $files = iterator_to_array($files, false); } elseif (!\is_array($files)) { $files = [$files]; } self::doRemove($files, false); } private static function doRemove(array $files, bool $isRecursive): void { $files = array_reverse($files); foreach ($files as $file) { if (is_link($file)) { if (!(self::box('unlink', $file) || '\\' !== \DIRECTORY_SEPARATOR || self::box('rmdir', $file)) && file_exists($file)) { throw new IOException(sprintf('Failed to remove symlink "%s": ', $file).self::$lastError); } } elseif (is_dir($file)) { if (!$isRecursive) { $tmpName = \dirname(realpath($file)).'/.!'.strrev(strtr(base64_encode(random_bytes(2)), '/=', '-!')); if (file_exists($tmpName)) { try { self::doRemove([$tmpName], true); } catch (IOException $e) { } } if (!file_exists($tmpName) && self::box('rename', $file, $tmpName)) { $origFile = $file; $file = $tmpName; } else { $origFile = null; } } $files = new \FilesystemIterator($file, \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS); self::doRemove(iterator_to_array($files, true), true); if (!self::box('rmdir', $file) && file_exists($file) && !$isRecursive) { $lastError = self::$lastError; if (null !== $origFile && self::box('rename', $file, $origFile)) { $file = $origFile; } throw new IOException(sprintf('Failed to remove directory "%s": ', $file).$lastError); } } elseif (!self::box('unlink', $file) && ((self::$lastError && str_contains(self::$lastError, 'Permission denied')) || file_exists($file))) { throw new IOException(sprintf('Failed to remove file "%s": ', $file).self::$lastError); } } } public function chmod($files, int $mode, int $umask = 0000, bool $recursive = false) { foreach ($this->toIterable($files) as $file) { if ((\PHP_VERSION_ID < 80000 || \is_int($mode)) && !self::box('chmod', $file, $mode & ~$umask)) { throw new IOException(sprintf('Failed to chmod file "%s": ', $file).self::$lastError, 0, null, $file); } if ($recursive && is_dir($file) && !is_link($file)) { $this->chmod(new \FilesystemIterator($file), $mode, $umask, true); } } } public function chown($files, $user, bool $recursive = false) { foreach ($this->toIterable($files) as $file) { if ($recursive && is_dir($file) && !is_link($file)) { $this->chown(new \FilesystemIterator($file), $user, true); } if (is_link($file) && \function_exists('lchown')) { if (!self::box('lchown', $file, $user)) { throw new IOException(sprintf('Failed to chown file "%s": ', $file).self::$lastError, 0, null, $file); } } else { if (!self::box('chown', $file, $user)) { throw new IOException(sprintf('Failed to chown file "%s": ', $file).self::$lastError, 0, null, $file); } } } } public function chgrp($files, $group, bool $recursive = false) { foreach ($this->toIterable($files) as $file) { if ($recursive && is_dir($file) && !is_link($file)) { $this->chgrp(new \FilesystemIterator($file), $group, true); } if (is_link($file) && \function_exists('lchgrp')) { if (!self::box('lchgrp', $file, $group)) { throw new IOException(sprintf('Failed to chgrp file "%s": ', $file).self::$lastError, 0, null, $file); } } else { if (!self::box('chgrp', $file, $group)) { throw new IOException(sprintf('Failed to chgrp file "%s": ', $file).self::$lastError, 0, null, $file); } } } } public function rename(string $origin, string $target, bool $overwrite = false) { if (!$overwrite && $this->isReadable($target)) { throw new IOException(sprintf('Cannot rename because the target "%s" already exists.', $target), 0, null, $target); } if (!self::box('rename', $origin, $target)) { if (is_dir($origin)) { $this->mirror($origin, $target, null, ['override' => $overwrite, 'delete' => $overwrite]); $this->remove($origin); return; } throw new IOException(sprintf('Cannot rename "%s" to "%s": ', $origin, $target).self::$lastError, 0, null, $target); } } private function isReadable(string $filename): bool { $maxPathLength = \PHP_MAXPATHLEN - 2; if (\strlen($filename) > $maxPathLength) { throw new IOException(sprintf('Could not check if file is readable because path length exceeds %d characters.', $maxPathLength), 0, null, $filename); } return is_readable($filename); } public function symlink(string $originDir, string $targetDir, bool $copyOnWindows = false) { self::assertFunctionExists('symlink'); if ('\\' === \DIRECTORY_SEPARATOR) { $originDir = strtr($originDir, '/', '\\'); $targetDir = strtr($targetDir, '/', '\\'); if ($copyOnWindows) { $this->mirror($originDir, $targetDir); return; } } $this->mkdir(\dirname($targetDir)); if (is_link($targetDir)) { if (readlink($targetDir) === $originDir) { return; } $this->remove($targetDir); } if (!self::box('symlink', $originDir, $targetDir)) { $this->linkException($originDir, $targetDir, 'symbolic'); } } public function hardlink(string $originFile, $targetFiles) { self::assertFunctionExists('link'); if (!$this->exists($originFile)) { throw new FileNotFoundException(null, 0, null, $originFile); } if (!is_file($originFile)) { throw new FileNotFoundException(sprintf('Origin file "%s" is not a file.', $originFile)); } foreach ($this->toIterable($targetFiles) as $targetFile) { if (is_file($targetFile)) { if (fileinode($originFile) === fileinode($targetFile)) { continue; } $this->remove($targetFile); } if (!self::box('link', $originFile, $targetFile)) { $this->linkException($originFile, $targetFile, 'hard'); } } } private function linkException(string $origin, string $target, string $linkType) { if (self::$lastError) { if ('\\' === \DIRECTORY_SEPARATOR && str_contains(self::$lastError, 'error code(1314)')) { throw new IOException(sprintf('Unable to create "%s" link due to error code 1314: \'A required privilege is not held by the client\'. Do you have the required Administrator-rights?', $linkType), 0, null, $target); } } throw new IOException(sprintf('Failed to create "%s" link from "%s" to "%s": ', $linkType, $origin, $target).self::$lastError, 0, null, $target); } public function readlink(string $path, bool $canonicalize = false) { if (!$canonicalize && !is_link($path)) { return null; } if ($canonicalize) { if (!$this->exists($path)) { return null; } if ('\\' === \DIRECTORY_SEPARATOR && \PHP_VERSION_ID < 70410) { $path = readlink($path); } return realpath($path); } if ('\\' === \DIRECTORY_SEPARATOR && \PHP_VERSION_ID < 70400) { return realpath($path); } return readlink($path); } public function makePathRelative(string $endPath, string $startPath) { if (!$this->isAbsolutePath($startPath)) { throw new InvalidArgumentException(sprintf('The start path "%s" is not absolute.', $startPath)); } if (!$this->isAbsolutePath($endPath)) { throw new InvalidArgumentException(sprintf('The end path "%s" is not absolute.', $endPath)); } if ('\\' === \DIRECTORY_SEPARATOR) { $endPath = str_replace('\\', '/', $endPath); $startPath = str_replace('\\', '/', $startPath); } $splitDriveLetter = function ($path) { return (\strlen($path) > 2 && ':' === $path[1] && '/' === $path[2] && ctype_alpha($path[0])) ? [substr($path, 2), strtoupper($path[0])] : [$path, null]; }; $splitPath = function ($path) { $result = []; foreach (explode('/', trim($path, '/')) as $segment) { if ('..' === $segment) { array_pop($result); } elseif ('.' !== $segment && '' !== $segment) { $result[] = $segment; } } return $result; }; [$endPath, $endDriveLetter] = $splitDriveLetter($endPath); [$startPath, $startDriveLetter] = $splitDriveLetter($startPath); $startPathArr = $splitPath($startPath); $endPathArr = $splitPath($endPath); if ($endDriveLetter && $startDriveLetter && $endDriveLetter != $startDriveLetter) { return $endDriveLetter.':/'.($endPathArr ? implode('/', $endPathArr).'/' : ''); } $index = 0; while (isset($startPathArr[$index]) && isset($endPathArr[$index]) && $startPathArr[$index] === $endPathArr[$index]) { ++$index; } if (1 === \count($startPathArr) && '' === $startPathArr[0]) { $depth = 0; } else { $depth = \count($startPathArr) - $index; } $traverser = str_repeat('../', $depth); $endPathRemainder = implode('/', \array_slice($endPathArr, $index)); $relativePath = $traverser.('' !== $endPathRemainder ? $endPathRemainder.'/' : ''); return '' === $relativePath ? './' : $relativePath; } public function mirror(string $originDir, string $targetDir, ?\Traversable $iterator = null, array $options = []) { $targetDir = rtrim($targetDir, '/\\'); $originDir = rtrim($originDir, '/\\'); $originDirLen = \strlen($originDir); if (!$this->exists($originDir)) { throw new IOException(sprintf('The origin directory specified "%s" was not found.', $originDir), 0, null, $originDir); } if ($this->exists($targetDir) && isset($options['delete']) && $options['delete']) { $deleteIterator = $iterator; if (null === $deleteIterator) { $flags = \FilesystemIterator::SKIP_DOTS; $deleteIterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($targetDir, $flags), \RecursiveIteratorIterator::CHILD_FIRST); } $targetDirLen = \strlen($targetDir); foreach ($deleteIterator as $file) { $origin = $originDir.substr($file->getPathname(), $targetDirLen); if (!$this->exists($origin)) { $this->remove($file); } } } $copyOnWindows = $options['copy_on_windows'] ?? false; if (null === $iterator) { $flags = $copyOnWindows ? \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS : \FilesystemIterator::SKIP_DOTS; $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($originDir, $flags), \RecursiveIteratorIterator::SELF_FIRST); } $this->mkdir($targetDir); $filesCreatedWhileMirroring = []; foreach ($iterator as $file) { if ($file->getPathname() === $targetDir || $file->getRealPath() === $targetDir || isset($filesCreatedWhileMirroring[$file->getRealPath()])) { continue; } $target = $targetDir.substr($file->getPathname(), $originDirLen); $filesCreatedWhileMirroring[$target] = true; if (!$copyOnWindows && is_link($file)) { $this->symlink($file->getLinkTarget(), $target); } elseif (is_dir($file)) { $this->mkdir($target); } elseif (is_file($file)) { $this->copy($file, $target, $options['override'] ?? false); } else { throw new IOException(sprintf('Unable to guess "%s" file type.', $file), 0, null, $file); } } } public function isAbsolutePath(string $file) { return '' !== $file && (strspn($file, '/\\', 0, 1) || (\strlen($file) > 3 && ctype_alpha($file[0]) && ':' === $file[1] && strspn($file, '/\\', 2, 1) ) || null !== parse_url($file, \PHP_URL_SCHEME) ); } public function tempnam(string $dir, string $prefix) { $suffix = \func_num_args() > 2 ? func_get_arg(2) : ''; [$scheme, $hierarchy] = $this->getSchemeAndHierarchy($dir); if ((null === $scheme || 'file' === $scheme || 'gs' === $scheme) && '' === $suffix) { if ($tmpFile = self::box('tempnam', $hierarchy, $prefix)) { if (null !== $scheme && 'gs' !== $scheme) { return $scheme.'://'.$tmpFile; } return $tmpFile; } throw new IOException('A temporary file could not be created: '.self::$lastError); } for ($i = 0; $i < 10; ++$i) { $tmpFile = $dir.'/'.$prefix.uniqid(mt_rand(), true).$suffix; if (!$handle = self::box('fopen', $tmpFile, 'x+')) { continue; } self::box('fclose', $handle); return $tmpFile; } throw new IOException('A temporary file could not be created: '.self::$lastError); } public function dumpFile(string $filename, $content) { if (\is_array($content)) { throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be string or resource, array given.', __METHOD__)); } $dir = \dirname($filename); if (is_link($filename) && $linkTarget = $this->readlink($filename)) { $this->dumpFile(Path::makeAbsolute($linkTarget, $dir), $content); return; } if (!is_dir($dir)) { $this->mkdir($dir); } $tmpFile = $this->tempnam($dir, basename($filename)); try { if (false === self::box('file_put_contents', $tmpFile, $content)) { throw new IOException(sprintf('Failed to write file "%s": ', $filename).self::$lastError, 0, null, $filename); } self::box('chmod', $tmpFile, self::box('fileperms', $filename) ?: 0666 & ~umask()); $this->rename($tmpFile, $filename, true); } finally { if (file_exists($tmpFile)) { if ('\\' === \DIRECTORY_SEPARATOR && !is_writable($tmpFile)) { self::box('chmod', $tmpFile, self::box('fileperms', $tmpFile) | 0200); } self::box('unlink', $tmpFile); } } } public function appendToFile(string $filename, $content) { if (\is_array($content)) { throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be string or resource, array given.', __METHOD__)); } $dir = \dirname($filename); if (!is_dir($dir)) { $this->mkdir($dir); } $lock = \func_num_args() > 2 && func_get_arg(2); if (false === self::box('file_put_contents', $filename, $content, \FILE_APPEND | ($lock ? \LOCK_EX : 0))) { throw new IOException(sprintf('Failed to write file "%s": ', $filename).self::$lastError, 0, null, $filename); } } private function toIterable($files): iterable { return is_iterable($files) ? $files : [$files]; } private function getSchemeAndHierarchy(string $filename): array { $components = explode('://', $filename, 2); return 2 === \count($components) ? [$components[0], $components[1]] : [null, $components[0]]; } private static function assertFunctionExists(string $func): void { if (!\function_exists($func)) { throw new IOException(sprintf('Unable to perform filesystem operation because the "%s()" function has been disabled.', $func)); } } private static function box(string $func, ...$args) { self::assertFunctionExists($func); self::$lastError = null; set_error_handler(__CLASS__.'::handleError'); try { return $func(...$args); } finally { restore_error_handler(); } } public static function handleError(int $type, string $msg) { self::$lastError = $msg; } } Copyright (c) 2004-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. self::CLEANUP_THRESHOLD) { self::$buffer = \array_slice(self::$buffer, -self::CLEANUP_SIZE, null, true); self::$bufferSize = self::CLEANUP_SIZE; } return $canonicalPath; } public static function normalize(string $path): string { return str_replace('\\', '/', $path); } public static function getDirectory(string $path): string { if ('' === $path) { return ''; } $path = self::canonicalize($path); if (false !== $schemeSeparatorPosition = strpos($path, '://')) { $scheme = substr($path, 0, $schemeSeparatorPosition + 3); $path = substr($path, $schemeSeparatorPosition + 3); } else { $scheme = ''; } if (false === $dirSeparatorPosition = strrpos($path, '/')) { return ''; } if (0 === $dirSeparatorPosition) { return $scheme.'/'; } if (2 === $dirSeparatorPosition && ctype_alpha($path[0]) && ':' === $path[1]) { return $scheme.substr($path, 0, 3); } return $scheme.substr($path, 0, $dirSeparatorPosition); } public static function getHomeDirectory(): string { if (getenv('HOME')) { return self::canonicalize(getenv('HOME')); } if (getenv('HOMEDRIVE') && getenv('HOMEPATH')) { return self::canonicalize(getenv('HOMEDRIVE').getenv('HOMEPATH')); } throw new RuntimeException("Cannot find the home directory path: Your environment or operating system isn't supported."); } public static function getRoot(string $path): string { if ('' === $path) { return ''; } if (false !== $schemeSeparatorPosition = strpos($path, '://')) { $scheme = substr($path, 0, $schemeSeparatorPosition + 3); $path = substr($path, $schemeSeparatorPosition + 3); } else { $scheme = ''; } $firstCharacter = $path[0]; if ('/' === $firstCharacter || '\\' === $firstCharacter) { return $scheme.'/'; } $length = \strlen($path); if ($length > 1 && ':' === $path[1] && ctype_alpha($firstCharacter)) { if (2 === $length) { return $scheme.$path.'/'; } if ('/' === $path[2] || '\\' === $path[2]) { return $scheme.$firstCharacter.$path[1].'/'; } } return ''; } public static function getFilenameWithoutExtension(string $path, ?string $extension = null): string { if ('' === $path) { return ''; } if (null !== $extension) { return rtrim(basename($path, $extension), '.'); } return pathinfo($path, \PATHINFO_FILENAME); } public static function getExtension(string $path, bool $forceLowerCase = false): string { if ('' === $path) { return ''; } $extension = pathinfo($path, \PATHINFO_EXTENSION); if ($forceLowerCase) { $extension = self::toLower($extension); } return $extension; } public static function hasExtension(string $path, $extensions = null, bool $ignoreCase = false): bool { if ('' === $path) { return false; } $actualExtension = self::getExtension($path, $ignoreCase); if ([] === $extensions || null === $extensions) { return '' !== $actualExtension; } if (\is_string($extensions)) { $extensions = [$extensions]; } foreach ($extensions as $key => $extension) { if ($ignoreCase) { $extension = self::toLower($extension); } $extensions[$key] = ltrim($extension, '.'); } return \in_array($actualExtension, $extensions, true); } public static function changeExtension(string $path, string $extension): string { if ('' === $path) { return ''; } $actualExtension = self::getExtension($path); $extension = ltrim($extension, '.'); if ('/' === substr($path, -1)) { return $path; } if (empty($actualExtension)) { return $path.('.' === substr($path, -1) ? '' : '.').$extension; } return substr($path, 0, -\strlen($actualExtension)).$extension; } public static function isAbsolute(string $path): bool { if ('' === $path) { return false; } if (false !== ($schemeSeparatorPosition = strpos($path, '://')) && 1 !== $schemeSeparatorPosition) { $path = substr($path, $schemeSeparatorPosition + 3); } $firstCharacter = $path[0]; if ('/' === $firstCharacter || '\\' === $firstCharacter) { return true; } if (\strlen($path) > 1 && ctype_alpha($firstCharacter) && ':' === $path[1]) { if (2 === \strlen($path)) { return true; } if ('/' === $path[2] || '\\' === $path[2]) { return true; } } return false; } public static function isRelative(string $path): bool { return !self::isAbsolute($path); } public static function makeAbsolute(string $path, string $basePath): string { if ('' === $basePath) { throw new InvalidArgumentException(sprintf('The base path must be a non-empty string. Got: "%s".', $basePath)); } if (!self::isAbsolute($basePath)) { throw new InvalidArgumentException(sprintf('The base path "%s" is not an absolute path.', $basePath)); } if (self::isAbsolute($path)) { return self::canonicalize($path); } if (false !== $schemeSeparatorPosition = strpos($basePath, '://')) { $scheme = substr($basePath, 0, $schemeSeparatorPosition + 3); $basePath = substr($basePath, $schemeSeparatorPosition + 3); } else { $scheme = ''; } return $scheme.self::canonicalize(rtrim($basePath, '/\\').'/'.$path); } public static function makeRelative(string $path, string $basePath): string { $path = self::canonicalize($path); $basePath = self::canonicalize($basePath); [$root, $relativePath] = self::split($path); [$baseRoot, $relativeBasePath] = self::split($basePath); if ('' === $root && '' !== $baseRoot) { if ('' === $relativeBasePath) { $relativePath = ltrim($relativePath, './\\'); } return $relativePath; } if ('' !== $root && '' === $baseRoot) { throw new InvalidArgumentException(sprintf('The absolute path "%s" cannot be made relative to the relative path "%s". You should provide an absolute base path instead.', $path, $basePath)); } if ($baseRoot && $root !== $baseRoot) { throw new InvalidArgumentException(sprintf('The path "%s" cannot be made relative to "%s", because they have different roots ("%s" and "%s").', $path, $basePath, $root, $baseRoot)); } if ('' === $relativeBasePath) { return $relativePath; } $parts = explode('/', $relativePath); $baseParts = explode('/', $relativeBasePath); $dotDotPrefix = ''; $match = true; foreach ($baseParts as $index => $basePart) { if ($match && isset($parts[$index]) && $basePart === $parts[$index]) { unset($parts[$index]); continue; } $match = false; $dotDotPrefix .= '../'; } return rtrim($dotDotPrefix.implode('/', $parts), '/'); } public static function isLocal(string $path): bool { return '' !== $path && false === strpos($path, '://'); } public static function getLongestCommonBasePath(string ...$paths): ?string { [$bpRoot, $basePath] = self::split(self::canonicalize(reset($paths))); for (next($paths); null !== key($paths) && '' !== $basePath; next($paths)) { [$root, $path] = self::split(self::canonicalize(current($paths))); if ($root !== $bpRoot) { return null; } while (true) { if ('.' === $basePath) { $basePath = ''; continue 2; } if (0 === strpos($path.'/', $basePath.'/')) { continue 2; } $basePath = \dirname($basePath); } } return $bpRoot.$basePath; } public static function join(string ...$paths): string { $finalPath = null; $wasScheme = false; foreach ($paths as $path) { if ('' === $path) { continue; } if (null === $finalPath) { $finalPath = $path; $wasScheme = (false !== strpos($path, '://')); continue; } if (!\in_array(substr($finalPath, -1), ['/', '\\'])) { $finalPath .= '/'; } $finalPath .= $wasScheme ? $path : ltrim($path, '/'); $wasScheme = false; } if (null === $finalPath) { return ''; } return self::canonicalize($finalPath); } public static function isBasePath(string $basePath, string $ofPath): bool { $basePath = self::canonicalize($basePath); $ofPath = self::canonicalize($ofPath); return 0 === strpos($ofPath.'/', rtrim($basePath, '/').'/'); } private static function findCanonicalParts(string $root, string $pathWithoutRoot): array { $parts = explode('/', $pathWithoutRoot); $canonicalParts = []; foreach ($parts as $part) { if ('.' === $part || '' === $part) { continue; } if ('..' === $part && \count($canonicalParts) > 0 && '..' !== $canonicalParts[\count($canonicalParts) - 1]) { array_pop($canonicalParts); continue; } if ('..' !== $part || '' === $root) { $canonicalParts[] = $part; } } return $canonicalParts; } private static function split(string $path): array { if ('' === $path) { return ['', '']; } if (false !== $schemeSeparatorPosition = strpos($path, '://')) { $root = substr($path, 0, $schemeSeparatorPosition + 3); $path = substr($path, $schemeSeparatorPosition + 3); } else { $root = ''; } $length = \strlen($path); if (0 === strpos($path, '/')) { $root .= '/'; $path = $length > 1 ? substr($path, 1) : ''; } elseif ($length > 1 && ctype_alpha($path[0]) && ':' === $path[1]) { if (2 === $length) { $root .= $path.'/'; $path = ''; } elseif ('/' === $path[2]) { $root .= substr($path, 0, 3); $path = $length > 3 ? substr($path, 3) : ''; } } return [$root, $path]; } private static function toLower(string $string): string { if (false !== $encoding = mb_detect_encoding($string, null, true)) { return mb_strtolower($string, $encoding); } return strtolower($string); } private function __construct() { } } target = $target; $this->doSetOperator($operator); } public function getTarget() { if (null === $this->target) { trigger_deprecation('symfony/finder', '5.4', 'Calling "%s" without initializing the target is deprecated.', __METHOD__); } return $this->target; } public function setTarget(string $target) { trigger_deprecation('symfony/finder', '5.4', '"%s" is deprecated. Set the target via the constructor instead.', __METHOD__); $this->target = $target; } public function getOperator() { return $this->operator; } public function setOperator(string $operator) { trigger_deprecation('symfony/finder', '5.4', '"%s" is deprecated. Set the operator via the constructor instead.', __METHOD__); $this->doSetOperator('' === $operator ? '==' : $operator); } public function test($test) { if (null === $this->target) { trigger_deprecation('symfony/finder', '5.4', 'Calling "%s" without initializing the target is deprecated.', __METHOD__); } switch ($this->operator) { case '>': return $test > $this->target; case '>=': return $test >= $this->target; case '<': return $test < $this->target; case '<=': return $test <= $this->target; case '!=': return $test != $this->target; } return $test == $this->target; } private function doSetOperator(string $operator): void { if (!\in_array($operator, ['>', '<', '>=', '<=', '==', '!='])) { throw new \InvalidArgumentException(sprintf('Invalid operator "%s".', $operator)); } $this->operator = $operator; } } ]=?|after|since|before|until)?\s*(.+?)\s*$#i', $test, $matches)) { throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a date test.', $test)); } try { $date = new \DateTime($matches[2]); $target = $date->format('U'); } catch (\Exception $e) { throw new \InvalidArgumentException(sprintf('"%s" is not a valid date.', $matches[2])); } $operator = $matches[1] ?? '=='; if ('since' === $operator || 'after' === $operator) { $operator = '>'; } if ('until' === $operator || 'before' === $operator) { $operator = '<'; } parent::__construct($target, $operator); } } ]=?)?\s*([0-9\.]+)\s*([kmg]i?)?\s*$#i', $test, $matches)) { throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a number test.', $test ?? 'null')); } $target = $matches[2]; if (!is_numeric($target)) { throw new \InvalidArgumentException(sprintf('Invalid number "%s".', $target)); } if (isset($matches[3])) { switch (strtolower($matches[3])) { case 'k': $target *= 1000; break; case 'ki': $target *= 1024; break; case 'm': $target *= 1000000; break; case 'mi': $target *= 1024 * 1024; break; case 'g': $target *= 1000000000; break; case 'gi': $target *= 1024 * 1024 * 1024; break; } } parent::__construct($target, $matches[1] ?: '=='); } } ignore = static::IGNORE_VCS_FILES | static::IGNORE_DOT_FILES; } public static function create() { return new static(); } public function directories() { $this->mode = Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES; return $this; } public function files() { $this->mode = Iterator\FileTypeFilterIterator::ONLY_FILES; return $this; } public function depth($levels) { foreach ((array) $levels as $level) { $this->depths[] = new Comparator\NumberComparator($level); } return $this; } public function date($dates) { foreach ((array) $dates as $date) { $this->dates[] = new Comparator\DateComparator($date); } return $this; } public function name($patterns) { $this->names = array_merge($this->names, (array) $patterns); return $this; } public function notName($patterns) { $this->notNames = array_merge($this->notNames, (array) $patterns); return $this; } public function contains($patterns) { $this->contains = array_merge($this->contains, (array) $patterns); return $this; } public function notContains($patterns) { $this->notContains = array_merge($this->notContains, (array) $patterns); return $this; } public function path($patterns) { $this->paths = array_merge($this->paths, (array) $patterns); return $this; } public function notPath($patterns) { $this->notPaths = array_merge($this->notPaths, (array) $patterns); return $this; } public function size($sizes) { foreach ((array) $sizes as $size) { $this->sizes[] = new Comparator\NumberComparator($size); } return $this; } public function exclude($dirs) { $this->exclude = array_merge($this->exclude, (array) $dirs); return $this; } public function ignoreDotFiles(bool $ignoreDotFiles) { if ($ignoreDotFiles) { $this->ignore |= static::IGNORE_DOT_FILES; } else { $this->ignore &= ~static::IGNORE_DOT_FILES; } return $this; } public function ignoreVCS(bool $ignoreVCS) { if ($ignoreVCS) { $this->ignore |= static::IGNORE_VCS_FILES; } else { $this->ignore &= ~static::IGNORE_VCS_FILES; } return $this; } public function ignoreVCSIgnored(bool $ignoreVCSIgnored) { if ($ignoreVCSIgnored) { $this->ignore |= static::IGNORE_VCS_IGNORED_FILES; } else { $this->ignore &= ~static::IGNORE_VCS_IGNORED_FILES; } return $this; } public static function addVCSPattern($pattern) { foreach ((array) $pattern as $p) { self::$vcsPatterns[] = $p; } self::$vcsPatterns = array_unique(self::$vcsPatterns); } public function sort(\Closure $closure) { $this->sort = $closure; return $this; } public function sortByName(bool $useNaturalSort = false) { $this->sort = $useNaturalSort ? Iterator\SortableIterator::SORT_BY_NAME_NATURAL : Iterator\SortableIterator::SORT_BY_NAME; return $this; } public function sortByType() { $this->sort = Iterator\SortableIterator::SORT_BY_TYPE; return $this; } public function sortByAccessedTime() { $this->sort = Iterator\SortableIterator::SORT_BY_ACCESSED_TIME; return $this; } public function reverseSorting() { $this->reverseSorting = true; return $this; } public function sortByChangedTime() { $this->sort = Iterator\SortableIterator::SORT_BY_CHANGED_TIME; return $this; } public function sortByModifiedTime() { $this->sort = Iterator\SortableIterator::SORT_BY_MODIFIED_TIME; return $this; } public function filter(\Closure $closure) { $this->filters[] = $closure; return $this; } public function followLinks() { $this->followLinks = true; return $this; } public function ignoreUnreadableDirs(bool $ignore = true) { $this->ignoreUnreadableDirs = $ignore; return $this; } public function in($dirs) { $resolvedDirs = []; foreach ((array) $dirs as $dir) { if (is_dir($dir)) { $resolvedDirs[] = [$this->normalizeDir($dir)]; } elseif ($glob = glob($dir, (\defined('GLOB_BRACE') ? \GLOB_BRACE : 0) | \GLOB_ONLYDIR | \GLOB_NOSORT)) { sort($glob); $resolvedDirs[] = array_map([$this, 'normalizeDir'], $glob); } else { throw new DirectoryNotFoundException(sprintf('The "%s" directory does not exist.', $dir)); } } $this->dirs = array_merge($this->dirs, ...$resolvedDirs); return $this; } #[\ReturnTypeWillChange] public function getIterator() { if (0 === \count($this->dirs) && 0 === \count($this->iterators)) { throw new \LogicException('You must call one of in() or append() methods before iterating over a Finder.'); } if (1 === \count($this->dirs) && 0 === \count($this->iterators)) { $iterator = $this->searchInDirectory($this->dirs[0]); if ($this->sort || $this->reverseSorting) { $iterator = (new Iterator\SortableIterator($iterator, $this->sort, $this->reverseSorting))->getIterator(); } return $iterator; } $iterator = new \AppendIterator(); foreach ($this->dirs as $dir) { $iterator->append(new \IteratorIterator(new LazyIterator(function () use ($dir) { return $this->searchInDirectory($dir); }))); } foreach ($this->iterators as $it) { $iterator->append($it); } if ($this->sort || $this->reverseSorting) { $iterator = (new Iterator\SortableIterator($iterator, $this->sort, $this->reverseSorting))->getIterator(); } return $iterator; } public function append(iterable $iterator) { if ($iterator instanceof \IteratorAggregate) { $this->iterators[] = $iterator->getIterator(); } elseif ($iterator instanceof \Iterator) { $this->iterators[] = $iterator; } elseif (is_iterable($iterator)) { $it = new \ArrayIterator(); foreach ($iterator as $file) { $file = $file instanceof \SplFileInfo ? $file : new \SplFileInfo($file); $it[$file->getPathname()] = $file; } $this->iterators[] = $it; } else { throw new \InvalidArgumentException('Finder::append() method wrong argument type.'); } return $this; } public function hasResults() { foreach ($this->getIterator() as $_) { return true; } return false; } #[\ReturnTypeWillChange] public function count() { return iterator_count($this->getIterator()); } private function searchInDirectory(string $dir): \Iterator { $exclude = $this->exclude; $notPaths = $this->notPaths; if (static::IGNORE_VCS_FILES === (static::IGNORE_VCS_FILES & $this->ignore)) { $exclude = array_merge($exclude, self::$vcsPatterns); } if (static::IGNORE_DOT_FILES === (static::IGNORE_DOT_FILES & $this->ignore)) { $notPaths[] = '#(^|/)\..+(/|$)#'; } $minDepth = 0; $maxDepth = \PHP_INT_MAX; foreach ($this->depths as $comparator) { switch ($comparator->getOperator()) { case '>': $minDepth = $comparator->getTarget() + 1; break; case '>=': $minDepth = $comparator->getTarget(); break; case '<': $maxDepth = $comparator->getTarget() - 1; break; case '<=': $maxDepth = $comparator->getTarget(); break; default: $minDepth = $maxDepth = $comparator->getTarget(); } } $flags = \RecursiveDirectoryIterator::SKIP_DOTS; if ($this->followLinks) { $flags |= \RecursiveDirectoryIterator::FOLLOW_SYMLINKS; } $iterator = new Iterator\RecursiveDirectoryIterator($dir, $flags, $this->ignoreUnreadableDirs); if ($exclude) { $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $exclude); } $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST); if ($minDepth > 0 || $maxDepth < \PHP_INT_MAX) { $iterator = new Iterator\DepthRangeFilterIterator($iterator, $minDepth, $maxDepth); } if ($this->mode) { $iterator = new Iterator\FileTypeFilterIterator($iterator, $this->mode); } if ($this->names || $this->notNames) { $iterator = new Iterator\FilenameFilterIterator($iterator, $this->names, $this->notNames); } if ($this->contains || $this->notContains) { $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains); } if ($this->sizes) { $iterator = new Iterator\SizeRangeFilterIterator($iterator, $this->sizes); } if ($this->dates) { $iterator = new Iterator\DateRangeFilterIterator($iterator, $this->dates); } if ($this->filters) { $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters); } if ($this->paths || $notPaths) { $iterator = new Iterator\PathFilterIterator($iterator, $this->paths, $notPaths); } if (static::IGNORE_VCS_IGNORED_FILES === (static::IGNORE_VCS_IGNORED_FILES & $this->ignore)) { $iterator = new Iterator\VcsIgnoredFilterIterator($iterator, $dir); } return $iterator; } private function normalizeDir(string $dir): string { if ('/' === $dir) { return $dir; } $dir = rtrim($dir, '/'.\DIRECTORY_SEPARATOR); if (preg_match('#^(ssh2\.)?s?ftp://#', $dir)) { $dir .= '/'; } return $dir; } } filters = $filters; parent::__construct($iterator); } #[\ReturnTypeWillChange] public function accept() { $fileinfo = $this->current(); foreach ($this->filters as $filter) { if (false === $filter($fileinfo)) { return false; } } return true; } } comparators = $comparators; parent::__construct($iterator); } #[\ReturnTypeWillChange] public function accept() { $fileinfo = $this->current(); if (!file_exists($fileinfo->getPathname())) { return false; } $filedate = $fileinfo->getMTime(); foreach ($this->comparators as $compare) { if (!$compare->test($filedate)) { return false; } } return true; } } minDepth = $minDepth; $iterator->setMaxDepth(\PHP_INT_MAX === $maxDepth ? -1 : $maxDepth); parent::__construct($iterator); } #[\ReturnTypeWillChange] public function accept() { return $this->getInnerIterator()->getDepth() >= $this->minDepth; } } iterator = $iterator; $this->isRecursive = $iterator instanceof \RecursiveIterator; $patterns = []; foreach ($directories as $directory) { $directory = rtrim($directory, '/'); if (!$this->isRecursive || str_contains($directory, '/')) { $patterns[] = preg_quote($directory, '#'); } else { $this->excludedDirs[$directory] = true; } } if ($patterns) { $this->excludedPattern = '#(?:^|/)(?:'.implode('|', $patterns).')(?:/|$)#'; } parent::__construct($iterator); } #[\ReturnTypeWillChange] public function accept() { if ($this->isRecursive && isset($this->excludedDirs[$this->getFilename()]) && $this->isDir()) { return false; } if ($this->excludedPattern) { $path = $this->isDir() ? $this->current()->getRelativePathname() : $this->current()->getRelativePath(); $path = str_replace('\\', '/', $path); return !preg_match($this->excludedPattern, $path); } return true; } #[\ReturnTypeWillChange] public function hasChildren() { return $this->isRecursive && $this->iterator->hasChildren(); } #[\ReturnTypeWillChange] public function getChildren() { $children = new self($this->iterator->getChildren(), []); $children->excludedDirs = $this->excludedDirs; $children->excludedPattern = $this->excludedPattern; return $children; } } mode = $mode; parent::__construct($iterator); } #[\ReturnTypeWillChange] public function accept() { $fileinfo = $this->current(); if (self::ONLY_DIRECTORIES === (self::ONLY_DIRECTORIES & $this->mode) && $fileinfo->isFile()) { return false; } elseif (self::ONLY_FILES === (self::ONLY_FILES & $this->mode) && $fileinfo->isDir()) { return false; } return true; } } matchRegexps && !$this->noMatchRegexps) { return true; } $fileinfo = $this->current(); if ($fileinfo->isDir() || !$fileinfo->isReadable()) { return false; } $content = $fileinfo->getContents(); if (!$content) { return false; } return $this->isAccepted($content); } protected function toRegex(string $str) { return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; } } isAccepted($this->current()->getFilename()); } protected function toRegex(string $str) { return $this->isRegex($str) ? $str : Glob::toRegex($str); } } iteratorFactory = $iteratorFactory; } public function getIterator(): \Traversable { yield from ($this->iteratorFactory)(); } } matchRegexps[] = $this->toRegex($pattern); } foreach ($noMatchPatterns as $pattern) { $this->noMatchRegexps[] = $this->toRegex($pattern); } parent::__construct($iterator); } protected function isAccepted(string $string) { foreach ($this->noMatchRegexps as $regex) { if (preg_match($regex, $string)) { return false; } } if ($this->matchRegexps) { foreach ($this->matchRegexps as $regex) { if (preg_match($regex, $string)) { return true; } } return false; } return true; } protected function isRegex(string $str) { $availableModifiers = 'imsxuADU'; if (\PHP_VERSION_ID >= 80200) { $availableModifiers .= 'n'; } if (preg_match('/^(.{3,}?)['.$availableModifiers.']*$/', $str, $m)) { $start = substr($m[1], 0, 1); $end = substr($m[1], -1); if ($start === $end) { return !preg_match('/[*?[:alnum:] \\\\]/', $start); } foreach ([['{', '}'], ['(', ')'], ['[', ']'], ['<', '>']] as $delimiters) { if ($start === $delimiters[0] && $end === $delimiters[1]) { return true; } } } return false; } abstract protected function toRegex(string $str); } current()->getRelativePathname(); if ('\\' === \DIRECTORY_SEPARATOR) { $filename = str_replace('\\', '/', $filename); } return $this->isAccepted($filename); } protected function toRegex(string $str) { return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; } } ignoreUnreadableDirs = $ignoreUnreadableDirs; $this->rootPath = $path; if ('/' !== \DIRECTORY_SEPARATOR && !($flags & self::UNIX_PATHS)) { $this->directorySeparator = \DIRECTORY_SEPARATOR; } } #[\ReturnTypeWillChange] public function current() { if (null === $subPathname = $this->subPath) { $subPathname = $this->subPath = $this->getSubPath(); } if ('' !== $subPathname) { $subPathname .= $this->directorySeparator; } $subPathname .= $this->getFilename(); $basePath = $this->rootPath; if ('/' !== $basePath && !str_ends_with($basePath, $this->directorySeparator) && !str_ends_with($basePath, '/')) { $basePath .= $this->directorySeparator; } return new SplFileInfo($basePath.$subPathname, $this->subPath, $subPathname); } #[\ReturnTypeWillChange] public function hasChildren($allowLinks = false) { $hasChildren = parent::hasChildren($allowLinks); if (!$hasChildren || !$this->ignoreUnreadableDirs) { return $hasChildren; } try { parent::getChildren(); return true; } catch (\UnexpectedValueException $e) { return false; } } #[\ReturnTypeWillChange] public function getChildren() { try { $children = parent::getChildren(); if ($children instanceof self) { $children->ignoreUnreadableDirs = $this->ignoreUnreadableDirs; $children->rootPath = $this->rootPath; } return $children; } catch (\UnexpectedValueException $e) { throw new AccessDeniedException($e->getMessage(), $e->getCode(), $e); } } #[\ReturnTypeWillChange] public function next() { $this->ignoreFirstRewind = false; parent::next(); } #[\ReturnTypeWillChange] public function rewind() { if ($this->ignoreFirstRewind) { $this->ignoreFirstRewind = false; return; } parent::rewind(); } } comparators = $comparators; parent::__construct($iterator); } #[\ReturnTypeWillChange] public function accept() { $fileinfo = $this->current(); if (!$fileinfo->isFile()) { return true; } $filesize = $fileinfo->getSize(); foreach ($this->comparators as $compare) { if (!$compare->test($filesize)) { return false; } } return true; } } iterator = $iterator; $order = $reverseOrder ? -1 : 1; if (self::SORT_BY_NAME === $sort) { $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); }; } elseif (self::SORT_BY_NAME_NATURAL === $sort) { $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { return $order * strnatcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); }; } elseif (self::SORT_BY_TYPE === $sort) { $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { if ($a->isDir() && $b->isFile()) { return -$order; } elseif ($a->isFile() && $b->isDir()) { return $order; } return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); }; } elseif (self::SORT_BY_ACCESSED_TIME === $sort) { $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { return $order * ($a->getATime() - $b->getATime()); }; } elseif (self::SORT_BY_CHANGED_TIME === $sort) { $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { return $order * ($a->getCTime() - $b->getCTime()); }; } elseif (self::SORT_BY_MODIFIED_TIME === $sort) { $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { return $order * ($a->getMTime() - $b->getMTime()); }; } elseif (self::SORT_BY_NONE === $sort) { $this->sort = $order; } elseif (\is_callable($sort)) { $this->sort = $reverseOrder ? static function (\SplFileInfo $a, \SplFileInfo $b) use ($sort) { return -$sort($a, $b); } : $sort; } else { throw new \InvalidArgumentException('The SortableIterator takes a PHP callable or a valid built-in sort algorithm as an argument.'); } } #[\ReturnTypeWillChange] public function getIterator() { if (1 === $this->sort) { return $this->iterator; } $array = iterator_to_array($this->iterator, true); if (-1 === $this->sort) { $array = array_reverse($array); } else { uasort($array, $this->sort); } return new \ArrayIterator($array); } } baseDir = $this->normalizePath($baseDir); parent::__construct($iterator); } public function accept(): bool { $file = $this->current(); $fileRealPath = $this->normalizePath($file->getRealPath()); return !$this->isIgnored($fileRealPath); } private function isIgnored(string $fileRealPath): bool { if (is_dir($fileRealPath) && !str_ends_with($fileRealPath, '/')) { $fileRealPath .= '/'; } if (isset($this->ignoredPathsCache[$fileRealPath])) { return $this->ignoredPathsCache[$fileRealPath]; } $ignored = false; foreach ($this->parentsDirectoryDownward($fileRealPath) as $parentDirectory) { if ($this->isIgnored($parentDirectory)) { break; } $fileRelativePath = substr($fileRealPath, \strlen($parentDirectory) + 1); if (null === $regexps = $this->readGitignoreFile("{$parentDirectory}/.gitignore")) { continue; } [$exclusionRegex, $inclusionRegex] = $regexps; if (preg_match($exclusionRegex, $fileRelativePath)) { $ignored = true; continue; } if (preg_match($inclusionRegex, $fileRelativePath)) { $ignored = false; } } return $this->ignoredPathsCache[$fileRealPath] = $ignored; } private function parentsDirectoryDownward(string $fileRealPath): array { $parentDirectories = []; $parentDirectory = $fileRealPath; while (true) { $newParentDirectory = \dirname($parentDirectory); if ($newParentDirectory === $parentDirectory) { break; } $parentDirectory = $newParentDirectory; if (0 !== strpos($parentDirectory, $this->baseDir)) { break; } $parentDirectories[] = $parentDirectory; } return array_reverse($parentDirectories); } private function readGitignoreFile(string $path): ?array { if (\array_key_exists($path, $this->gitignoreFilesCache)) { return $this->gitignoreFilesCache[$path]; } if (!file_exists($path)) { return $this->gitignoreFilesCache[$path] = null; } if (!is_file($path) || !is_readable($path)) { throw new \RuntimeException("The \"ignoreVCSIgnored\" option cannot be used by the Finder as the \"{$path}\" file is not readable."); } $gitignoreFileContent = file_get_contents($path); return $this->gitignoreFilesCache[$path] = [ Gitignore::toRegex($gitignoreFileContent), Gitignore::toRegexMatchingNegatedPatterns($gitignoreFileContent), ]; } private function normalizePath(string $path): string { if ('\\' === \DIRECTORY_SEPARATOR) { return str_replace('\\', '/', $path); } return $path; } } Copyright (c) 2004-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. relativePath = $relativePath; $this->relativePathname = $relativePathname; } public function getRelativePath() { return $this->relativePath; } public function getRelativePathname() { return $this->relativePathname; } public function getFilenameWithoutExtension(): string { $filename = $this->getFilename(); return pathinfo($filename, \PATHINFO_FILENAME); } public function getContents() { set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); try { $content = file_get_contents($this->getPathname()); } finally { restore_error_handler(); } if (false === $content) { throw new \RuntimeException($error); } return $content; } } = 80100 && !\is_string($int)) { @trigger_error($function.'(): Argument of type '.get_debug_type($int).' will be interpreted as string in the future', \E_USER_DEPRECATED); } if (!\is_int($int)) { return $int; } if ($int < -128 || $int > 255) { return (string) $int; } if ($int < 0) { $int += 256; } return \chr($int); } } Copyright (c) 2018-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. = 80000) { return require __DIR__.'/bootstrap80.php'; } if (!function_exists('ctype_alnum')) { function ctype_alnum($text) { return p\Ctype::ctype_alnum($text); } } if (!function_exists('ctype_alpha')) { function ctype_alpha($text) { return p\Ctype::ctype_alpha($text); } } if (!function_exists('ctype_cntrl')) { function ctype_cntrl($text) { return p\Ctype::ctype_cntrl($text); } } if (!function_exists('ctype_digit')) { function ctype_digit($text) { return p\Ctype::ctype_digit($text); } } if (!function_exists('ctype_graph')) { function ctype_graph($text) { return p\Ctype::ctype_graph($text); } } if (!function_exists('ctype_lower')) { function ctype_lower($text) { return p\Ctype::ctype_lower($text); } } if (!function_exists('ctype_print')) { function ctype_print($text) { return p\Ctype::ctype_print($text); } } if (!function_exists('ctype_punct')) { function ctype_punct($text) { return p\Ctype::ctype_punct($text); } } if (!function_exists('ctype_space')) { function ctype_space($text) { return p\Ctype::ctype_space($text); } } if (!function_exists('ctype_upper')) { function ctype_upper($text) { return p\Ctype::ctype_upper($text); } } if (!function_exists('ctype_xdigit')) { function ctype_xdigit($text) { return p\Ctype::ctype_xdigit($text); } } = 10.44) ? '\X' : Grapheme::GRAPHEME_CLUSTER_RX); final class Grapheme { public const GRAPHEME_CLUSTER_RX = '(?:\r\n|[\x{1F1E6}-\x{1F1FF}][\x{1F1E6}-\x{1F1FF}]?|(?:[ -~\x{200C}\x{200D}]|[ᆨ-ᇹ]+|[ᄀ-ᅟ]*(?:[가개갸걔거게겨계고과괘괴교구궈궤귀규그긔기까깨꺄꺠꺼께껴꼐꼬꽈꽤꾀꾜꾸꿔꿰뀌뀨끄끠끼나내냐냬너네녀녜노놔놰뇌뇨누눠눼뉘뉴느늬니다대댜댸더데뎌뎨도돠돼되됴두둬뒈뒤듀드듸디따때땨떄떠떼뗘뗴또똬뙈뙤뚀뚜뚸뛔뛰뜌뜨띄띠라래랴럐러레려례로롸뢔뢰료루뤄뤠뤼류르릐리마매먀먜머메며몌모뫄뫠뫼묘무뭐뭬뮈뮤므믜미바배뱌뱨버베벼볘보봐봬뵈뵤부붜붸뷔뷰브븨비빠빼뺘뺴뻐뻬뼈뼤뽀뽜뽸뾔뾰뿌뿨쀄쀠쀼쁘쁴삐사새샤섀서세셔셰소솨쇄쇠쇼수숴쉐쉬슈스싀시싸쌔쌰썌써쎄쎠쎼쏘쏴쐐쐬쑈쑤쒀쒜쒸쓔쓰씌씨아애야얘어에여예오와왜외요우워웨위유으의이자재쟈쟤저제져졔조좌좨죄죠주줘줴쥐쥬즈즤지짜째쨔쨰쩌쩨쪄쪠쪼쫘쫴쬐쬬쭈쭤쮀쮜쮸쯔쯰찌차채챠챼처체쳐쳬초촤쵀최쵸추춰췌취츄츠츼치카캐캬컈커케켜켸코콰쾌쾨쿄쿠쿼퀘퀴큐크킈키타태탸턔터테텨톄토톼퇘퇴툐투퉈퉤튀튜트틔티파패퍄퍠퍼페펴폐포퐈퐤푀표푸풔풰퓌퓨프픠피하해햐햬허헤혀혜호화홰회효후훠훼휘휴흐희히]?[ᅠ-ᆢ]+|[가-힣])[ᆨ-ᇹ]*|[ᄀ-ᅟ]+|[^\p{Cc}\p{Cf}\p{Zl}\p{Zp}])[\p{Mn}\p{Mc}\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{1D165}\x{1D16E}-\x{1D172}\x{1F3FB}-\x{1F3FF}\x{FE0E}-\x{FE0F}\x{E0020}-\x{E007F}]*(?:\x{200D}(?:[\x{1F1E6}-\x{1F1FF}]|[ -~\x{200C}\x{200D}]|[ᆨ-ᇹ]+|[ᄀ-ᅟ]*(?:[가-힣]?[ᅠ-ᆢ]+|[가-힣])[ᆨ-ᇹ]*|[ᄀ-ᅟ]+|[^\p{Cc}\p{Cf}\p{Zl}\p{Zp}])[\p{Mn}\p{Mc}\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{1D165}\x{1D16E}-\x{1D172}\x{1F3FB}-\x{1F3FF}\x{FE0E}-\x{FE0F}\x{E0020}-\x{E007F}]*)*|[\p{Cc}\p{Cf}\p{Zl}\p{Zp}])'; private const CASE_FOLD = [ ['µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"], ['μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1", 'ι'], ]; public static function grapheme_extract($s, $size, $type = \GRAPHEME_EXTR_COUNT, $start = 0, &$next = 0) { if (0 > $start) { $start = \strlen($s) + $start; } if (!\is_scalar($s)) { $hasError = false; set_error_handler(static function () use (&$hasError) { $hasError = true; }); $next = substr($s, $start); restore_error_handler(); if ($hasError) { substr($s, $start); $s = ''; } else { $s = $next; } } else { $s = substr($s, $start); } $size = (int) $size; $type = (int) $type; $start = (int) $start; if (\GRAPHEME_EXTR_COUNT !== $type && \GRAPHEME_EXTR_MAXBYTES !== $type && \GRAPHEME_EXTR_MAXCHARS !== $type) { if (80000 > \PHP_VERSION_ID) { return false; } throw new \ValueError('grapheme_extract(): Argument #3 ($type) must be one of GRAPHEME_EXTR_COUNT, GRAPHEME_EXTR_MAXBYTES, or GRAPHEME_EXTR_MAXCHARS'); } if (!isset($s[0]) || 0 > $size || 0 > $start) { return false; } if (0 === $size) { return ''; } $next = $start; $s = preg_split('/('.SYMFONY_GRAPHEME_CLUSTER_RX.')/u', "\r\n".$s, $size + 1, \PREG_SPLIT_NO_EMPTY | \PREG_SPLIT_DELIM_CAPTURE); if (!isset($s[1])) { return false; } $i = 1; $ret = ''; do { if (\GRAPHEME_EXTR_COUNT === $type) { --$size; } elseif (\GRAPHEME_EXTR_MAXBYTES === $type) { $size -= \strlen($s[$i]); } else { $size -= iconv_strlen($s[$i], 'UTF-8//IGNORE'); } if ($size >= 0) { $ret .= $s[$i]; } } while (isset($s[++$i]) && $size > 0); $next += \strlen($ret); return $ret; } public static function grapheme_strlen($s) { preg_replace('/'.SYMFONY_GRAPHEME_CLUSTER_RX.'/u', '', $s, -1, $len); return 0 === $len && '' !== $s ? null : $len; } public static function grapheme_substr($s, $start, $len = null) { if (null === $len) { $len = 2147483647; } preg_match_all('/'.SYMFONY_GRAPHEME_CLUSTER_RX.'/u', $s, $s); $slen = \count($s[0]); $start = (int) $start; if (0 > $start) { $start += $slen; } if (0 > $start) { if (\PHP_VERSION_ID < 80000) { return false; } $start = 0; } if ($start >= $slen) { return \PHP_VERSION_ID >= 80000 ? '' : false; } $rem = $slen - $start; if (0 > $len) { $len += $rem; } if (0 === $len) { return ''; } if (0 > $len) { return \PHP_VERSION_ID >= 80000 ? '' : false; } if ($len > $rem) { $len = $rem; } return implode('', \array_slice($s[0], $start, $len)); } public static function grapheme_strpos($s, $needle, $offset = 0) { return self::grapheme_position($s, $needle, $offset, 0); } public static function grapheme_stripos($s, $needle, $offset = 0) { return self::grapheme_position($s, $needle, $offset, 1); } public static function grapheme_strrpos($s, $needle, $offset = 0) { return self::grapheme_position($s, $needle, $offset, 2); } public static function grapheme_strripos($s, $needle, $offset = 0) { return self::grapheme_position($s, $needle, $offset, 3); } public static function grapheme_stristr($s, $needle, $beforeNeedle = false) { return mb_stristr($s, $needle, $beforeNeedle, 'UTF-8'); } public static function grapheme_strstr($s, $needle, $beforeNeedle = false) { return mb_strstr($s, $needle, $beforeNeedle, 'UTF-8'); } public static function grapheme_str_split($s, $len = 1) { if (0 > $len || 1073741823 < $len) { if (80000 > \PHP_VERSION_ID) { return false; } throw new \ValueError('grapheme_str_split(): Argument #2 ($length) must be greater than 0 and less than or equal to 1073741823.'); } if ('' === $s) { return []; } if (!preg_match_all('/('.SYMFONY_GRAPHEME_CLUSTER_RX.')/u', $s, $matches)) { return false; } if (1 === $len) { return $matches[0]; } $chunks = array_chunk($matches[0], $len); foreach ($chunks as &$chunk) { $chunk = implode('', $chunk); } return $chunks; } public static function grapheme_levenshtein($s1, $s2, $insertion_cost = 1, $replacement_cost = 1, $deletion_cost = 1) { if (!preg_match('//u', $s1) || !preg_match('//u', $s2)) { return false; } if (0 > $insertion_cost || 0 > $replacement_cost || 0 > $deletion_cost) { if (80000 > \PHP_VERSION_ID) { return false; } throw new \ValueError('grapheme_levenshtein(): Argument #3 ($insertion_cost), #4 ($replacement_cost), and #5 ($deletion_cost) must be greater than or equal to 0'); } preg_match_all('/'.SYMFONY_GRAPHEME_CLUSTER_RX.'/u', $s1, $s1); preg_match_all('/'.SYMFONY_GRAPHEME_CLUSTER_RX.'/u', $s2, $s2); $s1 = $s1[0]; $s2 = $s2[0]; $l1 = \count($s1); $l2 = \count($s2); if (0 === $l1) { return $l2 * $insertion_cost; } if (0 === $l2) { return $l1 * $deletion_cost; } $dp = array_fill(0, $l1 + 1, array_fill(0, $l2 + 1, 0)); for ($i = 1; $i <= $l1; ++$i) { $dp[$i][0] = $dp[$i - 1][0] + $deletion_cost; } for ($j = 1; $j <= $l2; ++$j) { $dp[0][$j] = $dp[0][$j - 1] + $insertion_cost; } for ($i = 1; $i <= $l1; ++$i) { for ($j = 1; $j <= $l2; ++$j) { $cost = ($s1[$i - 1] === $s2[$j - 1]) ? 0 : $replacement_cost; $dp[$i][$j] = min($dp[$i - 1][$j] + $deletion_cost, $dp[$i][$j - 1] + $insertion_cost, $dp[$i - 1][$j - 1] + $cost); } } return $dp[$l1][$l2]; } private static function grapheme_position($s, $needle, $offset, $mode) { $needle = (string) $needle; if (80000 > \PHP_VERSION_ID && !preg_match('/./us', $needle)) { return false; } $s = (string) $s; if (!preg_match('/./us', $s)) { return false; } if ($offset > 0) { $s = self::grapheme_substr($s, $offset); } elseif ($offset < 0) { if (2 > $mode) { $offset += self::grapheme_strlen($s); $s = self::grapheme_substr($s, $offset); if (0 > $offset) { $offset = 0; } } elseif (0 > $offset += self::grapheme_strlen($needle)) { $s = self::grapheme_substr($s, 0, $offset); $offset = 0; } else { $offset = 0; } } $caseInsensitive = $mode & 1; $reverse = $mode & 2; if ($caseInsensitive) { $mode = \defined('MB_CASE_FOLD_SIMPLE') ? \MB_CASE_FOLD_SIMPLE : \MB_CASE_LOWER; $s = mb_convert_case($s, $mode, 'UTF-8'); $needle = mb_convert_case($needle, $mode, 'UTF-8'); if (!\defined('MB_CASE_FOLD_SIMPLE')) { $s = str_replace(self::CASE_FOLD[0], self::CASE_FOLD[1], $s); $needle = str_replace(self::CASE_FOLD[0], self::CASE_FOLD[1], $needle); } } if ($reverse) { $needlePos = strrpos($s, $needle); } else { $needlePos = strpos($s, $needle); } return false !== $needlePos ? self::grapheme_strlen(substr($s, 0, $needlePos)) + $offset : false; } public static function grapheme_strrev(string $string) { if (!preg_match('//u', $string)) { return false; } $units = grapheme_str_split($string); if (false === $units) { return false; } return implode('', array_reverse($units)); } } Copyright (c) 2015-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. = 80000) { return require __DIR__.'/bootstrap80.php'; } if (!defined('GRAPHEME_EXTR_COUNT')) { define('GRAPHEME_EXTR_COUNT', 0); } if (!defined('GRAPHEME_EXTR_MAXBYTES')) { define('GRAPHEME_EXTR_MAXBYTES', 1); } if (!defined('GRAPHEME_EXTR_MAXCHARS')) { define('GRAPHEME_EXTR_MAXCHARS', 2); } if (!function_exists('grapheme_extract')) { function grapheme_extract($haystack, $size, $type = 0, $start = 0, &$next = 0) { return p\Grapheme::grapheme_extract($haystack, $size, $type, $start, $next); } } if (!function_exists('grapheme_stripos')) { function grapheme_stripos($haystack, $needle, $offset = 0) { return p\Grapheme::grapheme_stripos($haystack, $needle, $offset); } } if (!function_exists('grapheme_stristr')) { function grapheme_stristr($haystack, $needle, $beforeNeedle = false) { return p\Grapheme::grapheme_stristr($haystack, $needle, $beforeNeedle); } } if (!function_exists('grapheme_strlen')) { function grapheme_strlen($input) { return p\Grapheme::grapheme_strlen($input); } } if (!function_exists('grapheme_strpos')) { function grapheme_strpos($haystack, $needle, $offset = 0) { return p\Grapheme::grapheme_strpos($haystack, $needle, $offset); } } if (!function_exists('grapheme_strripos')) { function grapheme_strripos($haystack, $needle, $offset = 0) { return p\Grapheme::grapheme_strripos($haystack, $needle, $offset); } } if (!function_exists('grapheme_strrpos')) { function grapheme_strrpos($haystack, $needle, $offset = 0) { return p\Grapheme::grapheme_strrpos($haystack, $needle, $offset); } } if (!function_exists('grapheme_strstr')) { function grapheme_strstr($haystack, $needle, $beforeNeedle = false) { return p\Grapheme::grapheme_strstr($haystack, $needle, $beforeNeedle); } } if (!function_exists('grapheme_substr')) { function grapheme_substr($string, $offset, $length = null) { return p\Grapheme::grapheme_substr($string, $offset, $length); } } if (!function_exists('grapheme_str_split')) { function grapheme_str_split(string $string, int $length = 1) { return p\Grapheme::grapheme_str_split($string, $length); } } if (!function_exists('grapheme_levenshtein')) { function grapheme_levenshtein(string $string1, string $string2, int $insertion_cost = 1, int $replacement_cost = 1, int $deletion_cost = 1, string $locale = '') { return p\Grapheme::grapheme_levenshtein($string1, $string2, $insertion_cost, $replacement_cost, $deletion_cost); } } if (!function_exists('grapheme_strrev')) { function grapheme_strrev(string $string) { return p\Grapheme::grapheme_strrev($string); } } = 80500) { return require __DIR__.'/bootstrap85.php'; } if (!function_exists('grapheme_stripos')) { function grapheme_stripos(?string $haystack, ?string $needle, ?int $offset = 0): int|false { return p\Grapheme::grapheme_stripos((string) $haystack, (string) $needle, (int) $offset); } } if (!function_exists('grapheme_stristr')) { function grapheme_stristr(?string $haystack, ?string $needle, ?bool $beforeNeedle = false): string|false { return p\Grapheme::grapheme_stristr((string) $haystack, (string) $needle, (bool) $beforeNeedle); } } if (!function_exists('grapheme_strlen')) { function grapheme_strlen(?string $string): int|false|null { return p\Grapheme::grapheme_strlen((string) $string); } } if (!function_exists('grapheme_strpos')) { function grapheme_strpos(?string $haystack, ?string $needle, ?int $offset = 0): int|false { return p\Grapheme::grapheme_strpos((string) $haystack, (string) $needle, (int) $offset); } } if (!function_exists('grapheme_strripos')) { function grapheme_strripos(?string $haystack, ?string $needle, ?int $offset = 0): int|false { return p\Grapheme::grapheme_strripos((string) $haystack, (string) $needle, (int) $offset); } } if (!function_exists('grapheme_strrpos')) { function grapheme_strrpos(?string $haystack, ?string $needle, ?int $offset = 0): int|false { return p\Grapheme::grapheme_strrpos((string) $haystack, (string) $needle, (int) $offset); } } if (!function_exists('grapheme_strstr')) { function grapheme_strstr(?string $haystack, ?string $needle, ?bool $beforeNeedle = false): string|false { return p\Grapheme::grapheme_strstr((string) $haystack, (string) $needle, (bool) $beforeNeedle); } } if (!function_exists('grapheme_substr')) { function grapheme_substr(?string $string, ?int $offset, ?int $length = null): string|false { return p\Grapheme::grapheme_substr((string) $string, (int) $offset, $length); } } 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; 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"; public static function isNormalized(string $s, int $form = self::FORM_C) { if (!\in_array($form, [self::NFD, self::NFKD, self::NFC, self::NFKC])) { return false; } if (!isset($s[strspn($s, self::$ASCII)])) { return true; } if (self::NFC == $form && preg_match('//u', $s) && !preg_match('/[^\x00-\x{2FF}]/u', $s)) { return true; } return self::normalize($s, $form) === $s; } public static function getRawDecomposition(string $s, int $form = self::FORM_C) { if ('' === $s || !preg_match('//u', $s)) { return null; } $ulen = $s[0] < "\x80" ? 1 : (self::$ulenMask[$s[0] & "\xF0"] ?? 0); if (!$ulen || \strlen($s) !== $ulen) { return null; } if (self::NFC !== $form && self::NFD !== $form && self::NFKC !== $form && self::NFKD !== $form) { return ''; } if ($s >= "\xEA\xB0\x80" && $s <= "\xED\x9E\xA3") { $u = unpack('C*', $s); $j = (($u[1] - 224) << 12) + (($u[2] - 128) << 6) + $u[3] - 0xAC80; if ($t = $j % 28) { $j -= $t; $lv = 0xAC00 + $j; $r = \chr(0xE0 | $lv >> 12).\chr(0x80 | $lv >> 6 & 0x3F).\chr(0x80 | $lv & 0x3F); $r .= $t < 25 ? ("\xE1\x86".\chr(0xA7 + $t)) : ("\xE1\x87".\chr(0x67 + $t)); return $r; } return "\xE1\x84".\chr(0x80 + (int) ($j / 588)) ."\xE1\x85".\chr(0xA1 + (int) (($j % 588) / 28)); } if (null === self::$rawD) { self::$rawD = self::getData('rawCanonicalDecomposition'); } if (isset(self::$rawD[$s])) { return self::$rawD[$s]; } if (self::NFKC === $form || self::NFKD === $form) { if (null === self::$rawKD) { self::$rawKD = self::getData('rawCompatibilityDecomposition'); } return self::$rawKD[$s] ?? null; } return null; } public static function normalize(string $s, int $form = self::FORM_C) { if (!preg_match('//u', $s)) { return false; } switch ($form) { case self::NFC: $C = true; $K = false; break; case self::NFD: $C = false; $K = false; break; case self::NFKC: $C = true; $K = true; break; case self::NFKD: $C = false; $K = true; break; default: if (\defined('Normalizer::NONE') && \Normalizer::NONE == $form) { return $s; } if (80000 > \PHP_VERSION_ID) { return false; } throw new \ValueError('normalizer_normalize(): Argument #2 ($form) must be a a valid normalization form'); } if ('' === $s) { return ''; } if ($K && null === self::$KD) { self::$KD = self::getData('compatibilityDecomposition'); } if (null === self::$D) { self::$D = self::getData('canonicalDecomposition'); self::$cC = self::getData('combiningClass'); } if (null !== $mbEncoding = (2 & (int) \ini_get('mbstring.func_overload')) ? mb_internal_encoding() : null) { mb_internal_encoding('8bit'); } $r = self::decompose($s, $K); if ($C) { if (null === self::$C) { self::$C = self::getData('canonicalComposition'); } $r = self::recompose($r); } if (null !== $mbEncoding) { mb_internal_encoding($mbEncoding); } return $r; } private static function recompose($s) { $ASCII = self::$ASCII; $compMap = self::$C; $combClass = self::$cC; $ulenMask = self::$ulenMask; $result = $tail = ''; $i = $s[0] < "\x80" ? 1 : $ulenMask[$s[0] & "\xF0"]; $len = \strlen($s); $lastUchr = substr($s, 0, $i); $lastUcls = isset($combClass[$lastUchr]) ? 256 : 0; while ($i < $len) { if ($s[$i] < "\x80") { if ($tail) { $lastUchr .= $tail; $tail = ''; } if ($j = strspn($s, $ASCII, $i + 1)) { $lastUchr .= substr($s, $i, $j); $i += $j; } $result .= $lastUchr; $lastUchr = $s[$i]; $lastUcls = 0; ++$i; continue; } $ulen = $ulenMask[$s[$i] & "\xF0"]; $uchr = substr($s, $i, $ulen); if ($lastUchr < "\xE1\x84\x80" || "\xE1\x84\x92" < $lastUchr || $uchr < "\xE1\x85\xA1" || "\xE1\x85\xB5" < $uchr || $lastUcls) { $ucls = $combClass[$uchr] ?? 0; if (isset($compMap[$lastUchr.$uchr]) && (!$lastUcls || $lastUcls < $ucls)) { $lastUchr = $compMap[$lastUchr.$uchr]; } elseif ($lastUcls = $ucls) { $tail .= $uchr; } else { if ($tail) { $lastUchr .= $tail; $tail = ''; } $result .= $lastUchr; $lastUchr = $uchr; } } else { $L = \ord($lastUchr[2]) - 0x80; $V = \ord($uchr[2]) - 0xA1; $T = 0; $uchr = substr($s, $i + $ulen, 3); if ("\xE1\x86\xA7" <= $uchr && $uchr <= "\xE1\x87\x82") { $T = \ord($uchr[2]) - 0xA7; 0 > $T && $T += 0x40; $ulen += 3; } $L = 0xAC00 + ($L * 21 + $V) * 28 + $T; $lastUchr = \chr(0xE0 | $L >> 12).\chr(0x80 | $L >> 6 & 0x3F).\chr(0x80 | $L & 0x3F); } $i += $ulen; } return $result.$lastUchr.$tail; } private static function decompose($s, $c) { $result = ''; $ASCII = self::$ASCII; $decompMap = self::$D; $combClass = self::$cC; $ulenMask = self::$ulenMask; if ($c) { $compatMap = self::$KD; } $c = []; $i = 0; $len = \strlen($s); while ($i < $len) { if ($s[$i] < "\x80") { if ($c) { ksort($c); $result .= implode('', $c); $c = []; } $j = 1 + strspn($s, $ASCII, $i + 1); $result .= substr($s, $i, $j); $i += $j; continue; } $ulen = $ulenMask[$s[$i] & "\xF0"]; $uchr = substr($s, $i, $ulen); $i += $ulen; if ($uchr < "\xEA\xB0\x80" || "\xED\x9E\xA3" < $uchr) { if ($uchr !== $j = $compatMap[$uchr] ?? ($decompMap[$uchr] ?? $uchr)) { $uchr = $j; $j = \strlen($uchr); $ulen = $uchr[0] < "\x80" ? 1 : $ulenMask[$uchr[0] & "\xF0"]; if ($ulen != $j) { $j -= $ulen; $i -= $j; if (0 > $i) { $s = str_repeat(' ', -$i).$s; $len -= $i; $i = 0; } while ($j--) { $s[$i + $j] = $uchr[$ulen + $j]; } $uchr = substr($uchr, 0, $ulen); } } if (isset($combClass[$uchr])) { if (!isset($c[$combClass[$uchr]])) { $c[$combClass[$uchr]] = ''; } $c[$combClass[$uchr]] .= $uchr; continue; } } else { $uchr = unpack('C*', $uchr); $j = (($uchr[1] - 224) << 12) + (($uchr[2] - 128) << 6) + $uchr[3] - 0xAC80; $uchr = "\xE1\x84".\chr(0x80 + (int) ($j / 588)) ."\xE1\x85".\chr(0xA1 + (int) (($j % 588) / 28)); if ($j %= 28) { $uchr .= $j < 25 ? ("\xE1\x86".\chr(0xA7 + $j)) : ("\xE1\x87".\chr(0x67 + $j)); } } if ($c) { ksort($c); $result .= implode('', $c); $c = []; } $result .= $uchr; } if ($c) { ksort($c); $result .= implode('', $c); } return $result; } private static function getData($file) { if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) { return require $file; } return false; } } 'À', 'Á' => 'Á', 'Â' => 'Â', 'Ã' => 'Ã', 'Ä' => 'Ä', 'Å' => 'Å', 'Ç' => 'Ç', 'È' => 'È', 'É' => 'É', 'Ê' => 'Ê', 'Ë' => 'Ë', 'Ì' => 'Ì', 'Í' => 'Í', 'Î' => 'Î', 'Ï' => 'Ï', 'Ñ' => 'Ñ', 'Ò' => 'Ò', 'Ó' => 'Ó', 'Ô' => 'Ô', 'Õ' => 'Õ', 'Ö' => 'Ö', 'Ù' => 'Ù', 'Ú' => 'Ú', 'Û' => 'Û', 'Ü' => 'Ü', 'Ý' => 'Ý', 'à' => 'à', 'á' => 'á', 'â' => 'â', 'ã' => 'ã', 'ä' => 'ä', 'å' => 'å', 'ç' => 'ç', 'è' => 'è', 'é' => 'é', 'ê' => 'ê', 'ë' => 'ë', 'ì' => 'ì', 'í' => 'í', 'î' => 'î', 'ï' => 'ï', 'ñ' => 'ñ', 'ò' => 'ò', 'ó' => 'ó', 'ô' => 'ô', 'õ' => 'õ', 'ö' => 'ö', 'ù' => 'ù', 'ú' => 'ú', 'û' => 'û', 'ü' => 'ü', 'ý' => 'ý', 'ÿ' => 'ÿ', 'Ā' => 'Ā', 'ā' => 'ā', 'Ă' => 'Ă', 'ă' => 'ă', 'Ą' => 'Ą', 'ą' => 'ą', 'Ć' => 'Ć', 'ć' => 'ć', 'Ĉ' => 'Ĉ', 'ĉ' => 'ĉ', 'Ċ' => 'Ċ', 'ċ' => 'ċ', 'Č' => 'Č', 'č' => 'č', 'Ď' => 'Ď', 'ď' => 'ď', 'Ē' => 'Ē', 'ē' => 'ē', 'Ĕ' => 'Ĕ', 'ĕ' => 'ĕ', 'Ė' => 'Ė', 'ė' => 'ė', 'Ę' => 'Ę', 'ę' => 'ę', 'Ě' => 'Ě', 'ě' => 'ě', 'Ĝ' => 'Ĝ', 'ĝ' => 'ĝ', 'Ğ' => 'Ğ', 'ğ' => 'ğ', 'Ġ' => 'Ġ', 'ġ' => 'ġ', 'Ģ' => 'Ģ', 'ģ' => 'ģ', 'Ĥ' => 'Ĥ', 'ĥ' => 'ĥ', 'Ĩ' => 'Ĩ', 'ĩ' => 'ĩ', 'Ī' => 'Ī', 'ī' => 'ī', 'Ĭ' => 'Ĭ', 'ĭ' => 'ĭ', 'Į' => 'Į', 'į' => 'į', 'İ' => 'İ', 'Ĵ' => 'Ĵ', 'ĵ' => 'ĵ', 'Ķ' => 'Ķ', 'ķ' => 'ķ', 'Ĺ' => 'Ĺ', 'ĺ' => 'ĺ', 'Ļ' => 'Ļ', 'ļ' => 'ļ', 'Ľ' => 'Ľ', 'ľ' => 'ľ', 'Ń' => 'Ń', 'ń' => 'ń', 'Ņ' => 'Ņ', 'ņ' => 'ņ', 'Ň' => 'Ň', 'ň' => 'ň', 'Ō' => 'Ō', 'ō' => 'ō', 'Ŏ' => 'Ŏ', 'ŏ' => 'ŏ', 'Ő' => 'Ő', 'ő' => 'ő', 'Ŕ' => 'Ŕ', 'ŕ' => 'ŕ', 'Ŗ' => 'Ŗ', 'ŗ' => 'ŗ', 'Ř' => 'Ř', 'ř' => 'ř', 'Ś' => 'Ś', 'ś' => 'ś', 'Ŝ' => 'Ŝ', 'ŝ' => 'ŝ', 'Ş' => 'Ş', 'ş' => 'ş', 'Š' => 'Š', 'š' => 'š', 'Ţ' => 'Ţ', 'ţ' => 'ţ', 'Ť' => 'Ť', 'ť' => 'ť', 'Ũ' => 'Ũ', 'ũ' => 'ũ', 'Ū' => 'Ū', 'ū' => 'ū', 'Ŭ' => 'Ŭ', 'ŭ' => 'ŭ', 'Ů' => 'Ů', 'ů' => 'ů', 'Ű' => 'Ű', 'ű' => 'ű', 'Ų' => 'Ų', 'ų' => 'ų', 'Ŵ' => 'Ŵ', 'ŵ' => 'ŵ', 'Ŷ' => 'Ŷ', 'ŷ' => 'ŷ', 'Ÿ' => 'Ÿ', 'Ź' => 'Ź', 'ź' => 'ź', 'Ż' => 'Ż', 'ż' => 'ż', 'Ž' => 'Ž', 'ž' => 'ž', 'Ơ' => 'Ơ', 'ơ' => 'ơ', 'Ư' => 'Ư', 'ư' => 'ư', 'Ǎ' => 'Ǎ', 'ǎ' => 'ǎ', 'Ǐ' => 'Ǐ', 'ǐ' => 'ǐ', 'Ǒ' => 'Ǒ', 'ǒ' => 'ǒ', 'Ǔ' => 'Ǔ', 'ǔ' => 'ǔ', 'Ǖ' => 'Ǖ', 'ǖ' => 'ǖ', 'Ǘ' => 'Ǘ', 'ǘ' => 'ǘ', 'Ǚ' => 'Ǚ', 'ǚ' => 'ǚ', 'Ǜ' => 'Ǜ', 'ǜ' => 'ǜ', 'Ǟ' => 'Ǟ', 'ǟ' => 'ǟ', 'Ǡ' => 'Ǡ', 'ǡ' => 'ǡ', 'Ǣ' => 'Ǣ', 'ǣ' => 'ǣ', 'Ǧ' => 'Ǧ', 'ǧ' => 'ǧ', 'Ǩ' => 'Ǩ', 'ǩ' => 'ǩ', 'Ǫ' => 'Ǫ', 'ǫ' => 'ǫ', 'Ǭ' => 'Ǭ', 'ǭ' => 'ǭ', 'Ǯ' => 'Ǯ', 'ǯ' => 'ǯ', 'ǰ' => 'ǰ', 'Ǵ' => 'Ǵ', 'ǵ' => 'ǵ', 'Ǹ' => 'Ǹ', 'ǹ' => 'ǹ', 'Ǻ' => 'Ǻ', 'ǻ' => 'ǻ', 'Ǽ' => 'Ǽ', 'ǽ' => 'ǽ', 'Ǿ' => 'Ǿ', 'ǿ' => 'ǿ', 'Ȁ' => 'Ȁ', 'ȁ' => 'ȁ', 'Ȃ' => 'Ȃ', 'ȃ' => 'ȃ', 'Ȅ' => 'Ȅ', 'ȅ' => 'ȅ', 'Ȇ' => 'Ȇ', 'ȇ' => 'ȇ', 'Ȉ' => 'Ȉ', 'ȉ' => 'ȉ', 'Ȋ' => 'Ȋ', 'ȋ' => 'ȋ', 'Ȍ' => 'Ȍ', 'ȍ' => 'ȍ', 'Ȏ' => 'Ȏ', 'ȏ' => 'ȏ', 'Ȑ' => 'Ȑ', 'ȑ' => 'ȑ', 'Ȓ' => 'Ȓ', 'ȓ' => 'ȓ', 'Ȕ' => 'Ȕ', 'ȕ' => 'ȕ', 'Ȗ' => 'Ȗ', 'ȗ' => 'ȗ', 'Ș' => 'Ș', 'ș' => 'ș', 'Ț' => 'Ț', 'ț' => 'ț', 'Ȟ' => 'Ȟ', 'ȟ' => 'ȟ', 'Ȧ' => 'Ȧ', 'ȧ' => 'ȧ', 'Ȩ' => 'Ȩ', 'ȩ' => 'ȩ', 'Ȫ' => 'Ȫ', 'ȫ' => 'ȫ', 'Ȭ' => 'Ȭ', 'ȭ' => 'ȭ', 'Ȯ' => 'Ȯ', 'ȯ' => 'ȯ', 'Ȱ' => 'Ȱ', 'ȱ' => 'ȱ', 'Ȳ' => 'Ȳ', 'ȳ' => 'ȳ', '΅' => '΅', 'Ά' => 'Ά', 'Έ' => 'Έ', 'Ή' => 'Ή', 'Ί' => 'Ί', 'Ό' => 'Ό', 'Ύ' => 'Ύ', 'Ώ' => 'Ώ', 'ΐ' => 'ΐ', 'Ϊ' => 'Ϊ', 'Ϋ' => 'Ϋ', 'ά' => 'ά', 'έ' => 'έ', 'ή' => 'ή', 'ί' => 'ί', 'ΰ' => 'ΰ', 'ϊ' => 'ϊ', 'ϋ' => 'ϋ', 'ό' => 'ό', 'ύ' => 'ύ', 'ώ' => 'ώ', 'ϓ' => 'ϓ', 'ϔ' => 'ϔ', 'Ѐ' => 'Ѐ', 'Ё' => 'Ё', 'Ѓ' => 'Ѓ', 'Ї' => 'Ї', 'Ќ' => 'Ќ', 'Ѝ' => 'Ѝ', 'Ў' => 'Ў', 'Й' => 'Й', 'й' => 'й', 'ѐ' => 'ѐ', 'ё' => 'ё', 'ѓ' => 'ѓ', 'ї' => 'ї', 'ќ' => 'ќ', 'ѝ' => 'ѝ', 'ў' => 'ў', 'Ѷ' => 'Ѷ', 'ѷ' => 'ѷ', 'Ӂ' => 'Ӂ', 'ӂ' => 'ӂ', 'Ӑ' => 'Ӑ', 'ӑ' => 'ӑ', 'Ӓ' => 'Ӓ', 'ӓ' => 'ӓ', 'Ӗ' => 'Ӗ', 'ӗ' => 'ӗ', 'Ӛ' => 'Ӛ', 'ӛ' => 'ӛ', 'Ӝ' => 'Ӝ', 'ӝ' => 'ӝ', 'Ӟ' => 'Ӟ', 'ӟ' => 'ӟ', 'Ӣ' => 'Ӣ', 'ӣ' => 'ӣ', 'Ӥ' => 'Ӥ', 'ӥ' => 'ӥ', 'Ӧ' => 'Ӧ', 'ӧ' => 'ӧ', 'Ӫ' => 'Ӫ', 'ӫ' => 'ӫ', 'Ӭ' => 'Ӭ', 'ӭ' => 'ӭ', 'Ӯ' => 'Ӯ', 'ӯ' => 'ӯ', 'Ӱ' => 'Ӱ', 'ӱ' => 'ӱ', 'Ӳ' => 'Ӳ', 'ӳ' => 'ӳ', 'Ӵ' => 'Ӵ', 'ӵ' => 'ӵ', 'Ӹ' => 'Ӹ', 'ӹ' => 'ӹ', 'آ' => 'آ', 'أ' => 'أ', 'ؤ' => 'ؤ', 'إ' => 'إ', 'ئ' => 'ئ', 'ۀ' => 'ۀ', 'ۂ' => 'ۂ', 'ۓ' => 'ۓ', 'ऩ' => 'ऩ', 'ऱ' => 'ऱ', 'ऴ' => 'ऴ', 'ো' => 'ো', 'ৌ' => 'ৌ', 'ୈ' => 'ୈ', 'ୋ' => 'ୋ', 'ୌ' => 'ୌ', 'ஔ' => 'ஔ', 'ொ' => 'ொ', 'ோ' => 'ோ', 'ௌ' => 'ௌ', 'ై' => 'ై', 'ೀ' => 'ೀ', 'ೇ' => 'ೇ', 'ೈ' => 'ೈ', 'ೊ' => 'ೊ', 'ೋ' => 'ೋ', 'ൊ' => 'ൊ', 'ോ' => 'ോ', 'ൌ' => 'ൌ', 'ේ' => 'ේ', 'ො' => 'ො', 'ෝ' => 'ෝ', 'ෞ' => 'ෞ', 'ဦ' => 'ဦ', 'ᬆ' => 'ᬆ', 'ᬈ' => 'ᬈ', 'ᬊ' => 'ᬊ', 'ᬌ' => 'ᬌ', 'ᬎ' => 'ᬎ', 'ᬒ' => 'ᬒ', 'ᬻ' => 'ᬻ', 'ᬽ' => 'ᬽ', 'ᭀ' => 'ᭀ', 'ᭁ' => 'ᭁ', 'ᭃ' => 'ᭃ', 'Ḁ' => 'Ḁ', 'ḁ' => 'ḁ', 'Ḃ' => 'Ḃ', 'ḃ' => 'ḃ', 'Ḅ' => 'Ḅ', 'ḅ' => 'ḅ', 'Ḇ' => 'Ḇ', 'ḇ' => 'ḇ', 'Ḉ' => 'Ḉ', 'ḉ' => 'ḉ', 'Ḋ' => 'Ḋ', 'ḋ' => 'ḋ', 'Ḍ' => 'Ḍ', 'ḍ' => 'ḍ', 'Ḏ' => 'Ḏ', 'ḏ' => 'ḏ', 'Ḑ' => 'Ḑ', 'ḑ' => 'ḑ', 'Ḓ' => 'Ḓ', 'ḓ' => 'ḓ', 'Ḕ' => 'Ḕ', 'ḕ' => 'ḕ', 'Ḗ' => 'Ḗ', 'ḗ' => 'ḗ', 'Ḙ' => 'Ḙ', 'ḙ' => 'ḙ', 'Ḛ' => 'Ḛ', 'ḛ' => 'ḛ', 'Ḝ' => 'Ḝ', 'ḝ' => 'ḝ', 'Ḟ' => 'Ḟ', 'ḟ' => 'ḟ', 'Ḡ' => 'Ḡ', 'ḡ' => 'ḡ', 'Ḣ' => 'Ḣ', 'ḣ' => 'ḣ', 'Ḥ' => 'Ḥ', 'ḥ' => 'ḥ', 'Ḧ' => 'Ḧ', 'ḧ' => 'ḧ', 'Ḩ' => 'Ḩ', 'ḩ' => 'ḩ', 'Ḫ' => 'Ḫ', 'ḫ' => 'ḫ', 'Ḭ' => 'Ḭ', 'ḭ' => 'ḭ', 'Ḯ' => 'Ḯ', 'ḯ' => 'ḯ', 'Ḱ' => 'Ḱ', 'ḱ' => 'ḱ', 'Ḳ' => 'Ḳ', 'ḳ' => 'ḳ', 'Ḵ' => 'Ḵ', 'ḵ' => 'ḵ', 'Ḷ' => 'Ḷ', 'ḷ' => 'ḷ', 'Ḹ' => 'Ḹ', 'ḹ' => 'ḹ', 'Ḻ' => 'Ḻ', 'ḻ' => 'ḻ', 'Ḽ' => 'Ḽ', 'ḽ' => 'ḽ', 'Ḿ' => 'Ḿ', 'ḿ' => 'ḿ', 'Ṁ' => 'Ṁ', 'ṁ' => 'ṁ', 'Ṃ' => 'Ṃ', 'ṃ' => 'ṃ', 'Ṅ' => 'Ṅ', 'ṅ' => 'ṅ', 'Ṇ' => 'Ṇ', 'ṇ' => 'ṇ', 'Ṉ' => 'Ṉ', 'ṉ' => 'ṉ', 'Ṋ' => 'Ṋ', 'ṋ' => 'ṋ', 'Ṍ' => 'Ṍ', 'ṍ' => 'ṍ', 'Ṏ' => 'Ṏ', 'ṏ' => 'ṏ', 'Ṑ' => 'Ṑ', 'ṑ' => 'ṑ', 'Ṓ' => 'Ṓ', 'ṓ' => 'ṓ', 'Ṕ' => 'Ṕ', 'ṕ' => 'ṕ', 'Ṗ' => 'Ṗ', 'ṗ' => 'ṗ', 'Ṙ' => 'Ṙ', 'ṙ' => 'ṙ', 'Ṛ' => 'Ṛ', 'ṛ' => 'ṛ', 'Ṝ' => 'Ṝ', 'ṝ' => 'ṝ', 'Ṟ' => 'Ṟ', 'ṟ' => 'ṟ', 'Ṡ' => 'Ṡ', 'ṡ' => 'ṡ', 'Ṣ' => 'Ṣ', 'ṣ' => 'ṣ', 'Ṥ' => 'Ṥ', 'ṥ' => 'ṥ', 'Ṧ' => 'Ṧ', 'ṧ' => 'ṧ', 'Ṩ' => 'Ṩ', 'ṩ' => 'ṩ', 'Ṫ' => 'Ṫ', 'ṫ' => 'ṫ', 'Ṭ' => 'Ṭ', 'ṭ' => 'ṭ', 'Ṯ' => 'Ṯ', 'ṯ' => 'ṯ', 'Ṱ' => 'Ṱ', 'ṱ' => 'ṱ', 'Ṳ' => 'Ṳ', 'ṳ' => 'ṳ', 'Ṵ' => 'Ṵ', 'ṵ' => 'ṵ', 'Ṷ' => 'Ṷ', 'ṷ' => 'ṷ', 'Ṹ' => 'Ṹ', 'ṹ' => 'ṹ', 'Ṻ' => 'Ṻ', 'ṻ' => 'ṻ', 'Ṽ' => 'Ṽ', 'ṽ' => 'ṽ', 'Ṿ' => 'Ṿ', 'ṿ' => 'ṿ', 'Ẁ' => 'Ẁ', 'ẁ' => 'ẁ', 'Ẃ' => 'Ẃ', 'ẃ' => 'ẃ', 'Ẅ' => 'Ẅ', 'ẅ' => 'ẅ', 'Ẇ' => 'Ẇ', 'ẇ' => 'ẇ', 'Ẉ' => 'Ẉ', 'ẉ' => 'ẉ', 'Ẋ' => 'Ẋ', 'ẋ' => 'ẋ', 'Ẍ' => 'Ẍ', 'ẍ' => 'ẍ', 'Ẏ' => 'Ẏ', 'ẏ' => 'ẏ', 'Ẑ' => 'Ẑ', 'ẑ' => 'ẑ', 'Ẓ' => 'Ẓ', 'ẓ' => 'ẓ', 'Ẕ' => 'Ẕ', 'ẕ' => 'ẕ', 'ẖ' => 'ẖ', 'ẗ' => 'ẗ', 'ẘ' => 'ẘ', 'ẙ' => 'ẙ', 'ẛ' => 'ẛ', 'Ạ' => 'Ạ', 'ạ' => 'ạ', 'Ả' => 'Ả', 'ả' => 'ả', 'Ấ' => 'Ấ', 'ấ' => 'ấ', 'Ầ' => 'Ầ', 'ầ' => 'ầ', 'Ẩ' => 'Ẩ', 'ẩ' => 'ẩ', 'Ẫ' => 'Ẫ', 'ẫ' => 'ẫ', 'Ậ' => 'Ậ', 'ậ' => 'ậ', 'Ắ' => 'Ắ', 'ắ' => 'ắ', 'Ằ' => 'Ằ', 'ằ' => 'ằ', 'Ẳ' => 'Ẳ', 'ẳ' => 'ẳ', 'Ẵ' => 'Ẵ', 'ẵ' => 'ẵ', 'Ặ' => 'Ặ', 'ặ' => 'ặ', 'Ẹ' => 'Ẹ', 'ẹ' => 'ẹ', 'Ẻ' => 'Ẻ', 'ẻ' => 'ẻ', 'Ẽ' => 'Ẽ', 'ẽ' => 'ẽ', 'Ế' => 'Ế', 'ế' => 'ế', 'Ề' => 'Ề', 'ề' => 'ề', 'Ể' => 'Ể', 'ể' => 'ể', 'Ễ' => 'Ễ', 'ễ' => 'ễ', 'Ệ' => 'Ệ', 'ệ' => 'ệ', 'Ỉ' => 'Ỉ', 'ỉ' => 'ỉ', 'Ị' => 'Ị', 'ị' => 'ị', 'Ọ' => 'Ọ', 'ọ' => 'ọ', 'Ỏ' => 'Ỏ', 'ỏ' => 'ỏ', 'Ố' => 'Ố', 'ố' => 'ố', 'Ồ' => 'Ồ', 'ồ' => 'ồ', 'Ổ' => 'Ổ', 'ổ' => 'ổ', 'Ỗ' => 'Ỗ', 'ỗ' => 'ỗ', 'Ộ' => 'Ộ', 'ộ' => 'ộ', 'Ớ' => 'Ớ', 'ớ' => 'ớ', 'Ờ' => 'Ờ', 'ờ' => 'ờ', 'Ở' => 'Ở', 'ở' => 'ở', 'Ỡ' => 'Ỡ', 'ỡ' => 'ỡ', 'Ợ' => 'Ợ', 'ợ' => 'ợ', 'Ụ' => 'Ụ', 'ụ' => 'ụ', 'Ủ' => 'Ủ', 'ủ' => 'ủ', 'Ứ' => 'Ứ', 'ứ' => 'ứ', 'Ừ' => 'Ừ', 'ừ' => 'ừ', 'Ử' => 'Ử', 'ử' => 'ử', 'Ữ' => 'Ữ', 'ữ' => 'ữ', 'Ự' => 'Ự', 'ự' => 'ự', 'Ỳ' => 'Ỳ', 'ỳ' => 'ỳ', 'Ỵ' => 'Ỵ', 'ỵ' => 'ỵ', 'Ỷ' => 'Ỷ', 'ỷ' => 'ỷ', 'Ỹ' => 'Ỹ', 'ỹ' => 'ỹ', 'ἀ' => 'ἀ', 'ἁ' => 'ἁ', 'ἂ' => 'ἂ', 'ἃ' => 'ἃ', 'ἄ' => 'ἄ', 'ἅ' => 'ἅ', 'ἆ' => 'ἆ', 'ἇ' => 'ἇ', 'Ἀ' => 'Ἀ', 'Ἁ' => 'Ἁ', 'Ἂ' => 'Ἂ', 'Ἃ' => 'Ἃ', 'Ἄ' => 'Ἄ', 'Ἅ' => 'Ἅ', 'Ἆ' => 'Ἆ', 'Ἇ' => 'Ἇ', 'ἐ' => 'ἐ', 'ἑ' => 'ἑ', 'ἒ' => 'ἒ', 'ἓ' => 'ἓ', 'ἔ' => 'ἔ', 'ἕ' => 'ἕ', 'Ἐ' => 'Ἐ', 'Ἑ' => 'Ἑ', 'Ἒ' => 'Ἒ', 'Ἓ' => 'Ἓ', 'Ἔ' => 'Ἔ', 'Ἕ' => 'Ἕ', 'ἠ' => 'ἠ', 'ἡ' => 'ἡ', 'ἢ' => 'ἢ', 'ἣ' => 'ἣ', 'ἤ' => 'ἤ', 'ἥ' => 'ἥ', 'ἦ' => 'ἦ', 'ἧ' => 'ἧ', 'Ἠ' => 'Ἠ', 'Ἡ' => 'Ἡ', 'Ἢ' => 'Ἢ', 'Ἣ' => 'Ἣ', 'Ἤ' => 'Ἤ', 'Ἥ' => 'Ἥ', 'Ἦ' => 'Ἦ', 'Ἧ' => 'Ἧ', 'ἰ' => 'ἰ', 'ἱ' => 'ἱ', 'ἲ' => 'ἲ', 'ἳ' => 'ἳ', 'ἴ' => 'ἴ', 'ἵ' => 'ἵ', 'ἶ' => 'ἶ', 'ἷ' => 'ἷ', 'Ἰ' => 'Ἰ', 'Ἱ' => 'Ἱ', 'Ἲ' => 'Ἲ', 'Ἳ' => 'Ἳ', 'Ἴ' => 'Ἴ', 'Ἵ' => 'Ἵ', 'Ἶ' => 'Ἶ', 'Ἷ' => 'Ἷ', 'ὀ' => 'ὀ', 'ὁ' => 'ὁ', 'ὂ' => 'ὂ', 'ὃ' => 'ὃ', 'ὄ' => 'ὄ', 'ὅ' => 'ὅ', 'Ὀ' => 'Ὀ', 'Ὁ' => 'Ὁ', 'Ὂ' => 'Ὂ', 'Ὃ' => 'Ὃ', 'Ὄ' => 'Ὄ', 'Ὅ' => 'Ὅ', 'ὐ' => 'ὐ', 'ὑ' => 'ὑ', 'ὒ' => 'ὒ', 'ὓ' => 'ὓ', 'ὔ' => 'ὔ', 'ὕ' => 'ὕ', 'ὖ' => 'ὖ', 'ὗ' => 'ὗ', 'Ὑ' => 'Ὑ', 'Ὓ' => 'Ὓ', 'Ὕ' => 'Ὕ', 'Ὗ' => 'Ὗ', 'ὠ' => 'ὠ', 'ὡ' => 'ὡ', 'ὢ' => 'ὢ', 'ὣ' => 'ὣ', 'ὤ' => 'ὤ', 'ὥ' => 'ὥ', 'ὦ' => 'ὦ', 'ὧ' => 'ὧ', 'Ὠ' => 'Ὠ', 'Ὡ' => 'Ὡ', 'Ὢ' => 'Ὢ', 'Ὣ' => 'Ὣ', 'Ὤ' => 'Ὤ', 'Ὥ' => 'Ὥ', 'Ὦ' => 'Ὦ', 'Ὧ' => 'Ὧ', 'ὰ' => 'ὰ', 'ὲ' => 'ὲ', 'ὴ' => 'ὴ', 'ὶ' => 'ὶ', 'ὸ' => 'ὸ', 'ὺ' => 'ὺ', 'ὼ' => 'ὼ', 'ᾀ' => 'ᾀ', 'ᾁ' => 'ᾁ', 'ᾂ' => 'ᾂ', 'ᾃ' => 'ᾃ', 'ᾄ' => 'ᾄ', 'ᾅ' => 'ᾅ', 'ᾆ' => 'ᾆ', 'ᾇ' => 'ᾇ', 'ᾈ' => 'ᾈ', 'ᾉ' => 'ᾉ', 'ᾊ' => 'ᾊ', 'ᾋ' => 'ᾋ', 'ᾌ' => 'ᾌ', 'ᾍ' => 'ᾍ', 'ᾎ' => 'ᾎ', 'ᾏ' => 'ᾏ', 'ᾐ' => 'ᾐ', 'ᾑ' => 'ᾑ', 'ᾒ' => 'ᾒ', 'ᾓ' => 'ᾓ', 'ᾔ' => 'ᾔ', 'ᾕ' => 'ᾕ', 'ᾖ' => 'ᾖ', 'ᾗ' => 'ᾗ', 'ᾘ' => 'ᾘ', 'ᾙ' => 'ᾙ', 'ᾚ' => 'ᾚ', 'ᾛ' => 'ᾛ', 'ᾜ' => 'ᾜ', 'ᾝ' => 'ᾝ', 'ᾞ' => 'ᾞ', 'ᾟ' => 'ᾟ', 'ᾠ' => 'ᾠ', 'ᾡ' => 'ᾡ', 'ᾢ' => 'ᾢ', 'ᾣ' => 'ᾣ', 'ᾤ' => 'ᾤ', 'ᾥ' => 'ᾥ', 'ᾦ' => 'ᾦ', 'ᾧ' => 'ᾧ', 'ᾨ' => 'ᾨ', 'ᾩ' => 'ᾩ', 'ᾪ' => 'ᾪ', 'ᾫ' => 'ᾫ', 'ᾬ' => 'ᾬ', 'ᾭ' => 'ᾭ', 'ᾮ' => 'ᾮ', 'ᾯ' => 'ᾯ', 'ᾰ' => 'ᾰ', 'ᾱ' => 'ᾱ', 'ᾲ' => 'ᾲ', 'ᾳ' => 'ᾳ', 'ᾴ' => 'ᾴ', 'ᾶ' => 'ᾶ', 'ᾷ' => 'ᾷ', 'Ᾰ' => 'Ᾰ', 'Ᾱ' => 'Ᾱ', 'Ὰ' => 'Ὰ', 'ᾼ' => 'ᾼ', '῁' => '῁', 'ῂ' => 'ῂ', 'ῃ' => 'ῃ', 'ῄ' => 'ῄ', 'ῆ' => 'ῆ', 'ῇ' => 'ῇ', 'Ὲ' => 'Ὲ', 'Ὴ' => 'Ὴ', 'ῌ' => 'ῌ', '῍' => '῍', '῎' => '῎', '῏' => '῏', 'ῐ' => 'ῐ', 'ῑ' => 'ῑ', 'ῒ' => 'ῒ', 'ῖ' => 'ῖ', 'ῗ' => 'ῗ', 'Ῐ' => 'Ῐ', 'Ῑ' => 'Ῑ', 'Ὶ' => 'Ὶ', '῝' => '῝', '῞' => '῞', '῟' => '῟', 'ῠ' => 'ῠ', 'ῡ' => 'ῡ', 'ῢ' => 'ῢ', 'ῤ' => 'ῤ', 'ῥ' => 'ῥ', 'ῦ' => 'ῦ', 'ῧ' => 'ῧ', 'Ῠ' => 'Ῠ', 'Ῡ' => 'Ῡ', 'Ὺ' => 'Ὺ', 'Ῥ' => 'Ῥ', '῭' => '῭', 'ῲ' => 'ῲ', 'ῳ' => 'ῳ', 'ῴ' => 'ῴ', 'ῶ' => 'ῶ', 'ῷ' => 'ῷ', 'Ὸ' => 'Ὸ', 'Ὼ' => 'Ὼ', 'ῼ' => 'ῼ', '↚' => '↚', '↛' => '↛', '↮' => '↮', '⇍' => '⇍', '⇎' => '⇎', '⇏' => '⇏', '∄' => '∄', '∉' => '∉', '∌' => '∌', '∤' => '∤', '∦' => '∦', '≁' => '≁', '≄' => '≄', '≇' => '≇', '≉' => '≉', '≠' => '≠', '≢' => '≢', '≭' => '≭', '≮' => '≮', '≯' => '≯', '≰' => '≰', '≱' => '≱', '≴' => '≴', '≵' => '≵', '≸' => '≸', '≹' => '≹', '⊀' => '⊀', '⊁' => '⊁', '⊄' => '⊄', '⊅' => '⊅', '⊈' => '⊈', '⊉' => '⊉', '⊬' => '⊬', '⊭' => '⊭', '⊮' => '⊮', '⊯' => '⊯', '⋠' => '⋠', '⋡' => '⋡', '⋢' => '⋢', '⋣' => '⋣', '⋪' => '⋪', '⋫' => '⋫', '⋬' => '⋬', '⋭' => '⋭', 'が' => 'が', 'ぎ' => 'ぎ', 'ぐ' => 'ぐ', 'げ' => 'げ', 'ご' => 'ご', 'ざ' => 'ざ', 'じ' => 'じ', 'ず' => 'ず', 'ぜ' => 'ぜ', 'ぞ' => 'ぞ', 'だ' => 'だ', 'ぢ' => 'ぢ', 'づ' => 'づ', 'で' => 'で', 'ど' => 'ど', 'ば' => 'ば', 'ぱ' => 'ぱ', 'び' => 'び', 'ぴ' => 'ぴ', 'ぶ' => 'ぶ', 'ぷ' => 'ぷ', 'べ' => 'べ', 'ぺ' => 'ぺ', 'ぼ' => 'ぼ', 'ぽ' => 'ぽ', 'ゔ' => 'ゔ', 'ゞ' => 'ゞ', 'ガ' => 'ガ', 'ギ' => 'ギ', 'グ' => 'グ', 'ゲ' => 'ゲ', 'ゴ' => 'ゴ', 'ザ' => 'ザ', 'ジ' => 'ジ', 'ズ' => 'ズ', 'ゼ' => 'ゼ', 'ゾ' => 'ゾ', 'ダ' => 'ダ', 'ヂ' => 'ヂ', 'ヅ' => 'ヅ', 'デ' => 'デ', 'ド' => 'ド', 'バ' => 'バ', 'パ' => 'パ', 'ビ' => 'ビ', 'ピ' => 'ピ', 'ブ' => 'ブ', 'プ' => 'プ', 'ベ' => 'ベ', 'ペ' => 'ペ', 'ボ' => 'ボ', 'ポ' => 'ポ', 'ヴ' => 'ヴ', 'ヷ' => 'ヷ', 'ヸ' => 'ヸ', 'ヹ' => 'ヹ', 'ヺ' => 'ヺ', 'ヾ' => 'ヾ', '𑂚' => '𑂚', '𑂜' => '𑂜', '𑂫' => '𑂫', '𑄮' => '𑄮', '𑄯' => '𑄯', '𑍋' => '𑍋', '𑍌' => '𑍌', '𑒻' => '𑒻', '𑒼' => '𑒼', '𑒾' => '𑒾', '𑖺' => '𑖺', '𑖻' => '𑖻', '𑤸' => '𑤸', ); 'À', 'Á' => 'Á', 'Â' => 'Â', 'Ã' => 'Ã', 'Ä' => 'Ä', 'Å' => 'Å', 'Ç' => 'Ç', 'È' => 'È', 'É' => 'É', 'Ê' => 'Ê', 'Ë' => 'Ë', 'Ì' => 'Ì', 'Í' => 'Í', 'Î' => 'Î', 'Ï' => 'Ï', 'Ñ' => 'Ñ', 'Ò' => 'Ò', 'Ó' => 'Ó', 'Ô' => 'Ô', 'Õ' => 'Õ', 'Ö' => 'Ö', 'Ù' => 'Ù', 'Ú' => 'Ú', 'Û' => 'Û', 'Ü' => 'Ü', 'Ý' => 'Ý', 'à' => 'à', 'á' => 'á', 'â' => 'â', 'ã' => 'ã', 'ä' => 'ä', 'å' => 'å', 'ç' => 'ç', 'è' => 'è', 'é' => 'é', 'ê' => 'ê', 'ë' => 'ë', 'ì' => 'ì', 'í' => 'í', 'î' => 'î', 'ï' => 'ï', 'ñ' => 'ñ', 'ò' => 'ò', 'ó' => 'ó', 'ô' => 'ô', 'õ' => 'õ', 'ö' => 'ö', 'ù' => 'ù', 'ú' => 'ú', 'û' => 'û', 'ü' => 'ü', 'ý' => 'ý', 'ÿ' => 'ÿ', 'Ā' => 'Ā', 'ā' => 'ā', 'Ă' => 'Ă', 'ă' => 'ă', 'Ą' => 'Ą', 'ą' => 'ą', 'Ć' => 'Ć', 'ć' => 'ć', 'Ĉ' => 'Ĉ', 'ĉ' => 'ĉ', 'Ċ' => 'Ċ', 'ċ' => 'ċ', 'Č' => 'Č', 'č' => 'č', 'Ď' => 'Ď', 'ď' => 'ď', 'Ē' => 'Ē', 'ē' => 'ē', 'Ĕ' => 'Ĕ', 'ĕ' => 'ĕ', 'Ė' => 'Ė', 'ė' => 'ė', 'Ę' => 'Ę', 'ę' => 'ę', 'Ě' => 'Ě', 'ě' => 'ě', 'Ĝ' => 'Ĝ', 'ĝ' => 'ĝ', 'Ğ' => 'Ğ', 'ğ' => 'ğ', 'Ġ' => 'Ġ', 'ġ' => 'ġ', 'Ģ' => 'Ģ', 'ģ' => 'ģ', 'Ĥ' => 'Ĥ', 'ĥ' => 'ĥ', 'Ĩ' => 'Ĩ', 'ĩ' => 'ĩ', 'Ī' => 'Ī', 'ī' => 'ī', 'Ĭ' => 'Ĭ', 'ĭ' => 'ĭ', 'Į' => 'Į', 'į' => 'į', 'İ' => 'İ', 'Ĵ' => 'Ĵ', 'ĵ' => 'ĵ', 'Ķ' => 'Ķ', 'ķ' => 'ķ', 'Ĺ' => 'Ĺ', 'ĺ' => 'ĺ', 'Ļ' => 'Ļ', 'ļ' => 'ļ', 'Ľ' => 'Ľ', 'ľ' => 'ľ', 'Ń' => 'Ń', 'ń' => 'ń', 'Ņ' => 'Ņ', 'ņ' => 'ņ', 'Ň' => 'Ň', 'ň' => 'ň', 'Ō' => 'Ō', 'ō' => 'ō', 'Ŏ' => 'Ŏ', 'ŏ' => 'ŏ', 'Ő' => 'Ő', 'ő' => 'ő', 'Ŕ' => 'Ŕ', 'ŕ' => 'ŕ', 'Ŗ' => 'Ŗ', 'ŗ' => 'ŗ', 'Ř' => 'Ř', 'ř' => 'ř', 'Ś' => 'Ś', 'ś' => 'ś', 'Ŝ' => 'Ŝ', 'ŝ' => 'ŝ', 'Ş' => 'Ş', 'ş' => 'ş', 'Š' => 'Š', 'š' => 'š', 'Ţ' => 'Ţ', 'ţ' => 'ţ', 'Ť' => 'Ť', 'ť' => 'ť', 'Ũ' => 'Ũ', 'ũ' => 'ũ', 'Ū' => 'Ū', 'ū' => 'ū', 'Ŭ' => 'Ŭ', 'ŭ' => 'ŭ', 'Ů' => 'Ů', 'ů' => 'ů', 'Ű' => 'Ű', 'ű' => 'ű', 'Ų' => 'Ų', 'ų' => 'ų', 'Ŵ' => 'Ŵ', 'ŵ' => 'ŵ', 'Ŷ' => 'Ŷ', 'ŷ' => 'ŷ', 'Ÿ' => 'Ÿ', 'Ź' => 'Ź', 'ź' => 'ź', 'Ż' => 'Ż', 'ż' => 'ż', 'Ž' => 'Ž', 'ž' => 'ž', 'Ơ' => 'Ơ', 'ơ' => 'ơ', 'Ư' => 'Ư', 'ư' => 'ư', 'Ǎ' => 'Ǎ', 'ǎ' => 'ǎ', 'Ǐ' => 'Ǐ', 'ǐ' => 'ǐ', 'Ǒ' => 'Ǒ', 'ǒ' => 'ǒ', 'Ǔ' => 'Ǔ', 'ǔ' => 'ǔ', 'Ǖ' => 'Ǖ', 'ǖ' => 'ǖ', 'Ǘ' => 'Ǘ', 'ǘ' => 'ǘ', 'Ǚ' => 'Ǚ', 'ǚ' => 'ǚ', 'Ǜ' => 'Ǜ', 'ǜ' => 'ǜ', 'Ǟ' => 'Ǟ', 'ǟ' => 'ǟ', 'Ǡ' => 'Ǡ', 'ǡ' => 'ǡ', 'Ǣ' => 'Ǣ', 'ǣ' => 'ǣ', 'Ǧ' => 'Ǧ', 'ǧ' => 'ǧ', 'Ǩ' => 'Ǩ', 'ǩ' => 'ǩ', 'Ǫ' => 'Ǫ', 'ǫ' => 'ǫ', 'Ǭ' => 'Ǭ', 'ǭ' => 'ǭ', 'Ǯ' => 'Ǯ', 'ǯ' => 'ǯ', 'ǰ' => 'ǰ', 'Ǵ' => 'Ǵ', 'ǵ' => 'ǵ', 'Ǹ' => 'Ǹ', 'ǹ' => 'ǹ', 'Ǻ' => 'Ǻ', 'ǻ' => 'ǻ', 'Ǽ' => 'Ǽ', 'ǽ' => 'ǽ', 'Ǿ' => 'Ǿ', 'ǿ' => 'ǿ', 'Ȁ' => 'Ȁ', 'ȁ' => 'ȁ', 'Ȃ' => 'Ȃ', 'ȃ' => 'ȃ', 'Ȅ' => 'Ȅ', 'ȅ' => 'ȅ', 'Ȇ' => 'Ȇ', 'ȇ' => 'ȇ', 'Ȉ' => 'Ȉ', 'ȉ' => 'ȉ', 'Ȋ' => 'Ȋ', 'ȋ' => 'ȋ', 'Ȍ' => 'Ȍ', 'ȍ' => 'ȍ', 'Ȏ' => 'Ȏ', 'ȏ' => 'ȏ', 'Ȑ' => 'Ȑ', 'ȑ' => 'ȑ', 'Ȓ' => 'Ȓ', 'ȓ' => 'ȓ', 'Ȕ' => 'Ȕ', 'ȕ' => 'ȕ', 'Ȗ' => 'Ȗ', 'ȗ' => 'ȗ', 'Ș' => 'Ș', 'ș' => 'ș', 'Ț' => 'Ț', 'ț' => 'ț', 'Ȟ' => 'Ȟ', 'ȟ' => 'ȟ', 'Ȧ' => 'Ȧ', 'ȧ' => 'ȧ', 'Ȩ' => 'Ȩ', 'ȩ' => 'ȩ', 'Ȫ' => 'Ȫ', 'ȫ' => 'ȫ', 'Ȭ' => 'Ȭ', 'ȭ' => 'ȭ', 'Ȯ' => 'Ȯ', 'ȯ' => 'ȯ', 'Ȱ' => 'Ȱ', 'ȱ' => 'ȱ', 'Ȳ' => 'Ȳ', 'ȳ' => 'ȳ', '̀' => '̀', '́' => '́', '̓' => '̓', '̈́' => '̈́', 'ʹ' => 'ʹ', ';' => ';', '΅' => '΅', 'Ά' => 'Ά', '·' => '·', 'Έ' => 'Έ', 'Ή' => 'Ή', 'Ί' => 'Ί', 'Ό' => 'Ό', 'Ύ' => 'Ύ', 'Ώ' => 'Ώ', 'ΐ' => 'ΐ', 'Ϊ' => 'Ϊ', 'Ϋ' => 'Ϋ', 'ά' => 'ά', 'έ' => 'έ', 'ή' => 'ή', 'ί' => 'ί', 'ΰ' => 'ΰ', 'ϊ' => 'ϊ', 'ϋ' => 'ϋ', 'ό' => 'ό', 'ύ' => 'ύ', 'ώ' => 'ώ', 'ϓ' => 'ϓ', 'ϔ' => 'ϔ', 'Ѐ' => 'Ѐ', 'Ё' => 'Ё', 'Ѓ' => 'Ѓ', 'Ї' => 'Ї', 'Ќ' => 'Ќ', 'Ѝ' => 'Ѝ', 'Ў' => 'Ў', 'Й' => 'Й', 'й' => 'й', 'ѐ' => 'ѐ', 'ё' => 'ё', 'ѓ' => 'ѓ', 'ї' => 'ї', 'ќ' => 'ќ', 'ѝ' => 'ѝ', 'ў' => 'ў', 'Ѷ' => 'Ѷ', 'ѷ' => 'ѷ', 'Ӂ' => 'Ӂ', 'ӂ' => 'ӂ', 'Ӑ' => 'Ӑ', 'ӑ' => 'ӑ', 'Ӓ' => 'Ӓ', 'ӓ' => 'ӓ', 'Ӗ' => 'Ӗ', 'ӗ' => 'ӗ', 'Ӛ' => 'Ӛ', 'ӛ' => 'ӛ', 'Ӝ' => 'Ӝ', 'ӝ' => 'ӝ', 'Ӟ' => 'Ӟ', 'ӟ' => 'ӟ', 'Ӣ' => 'Ӣ', 'ӣ' => 'ӣ', 'Ӥ' => 'Ӥ', 'ӥ' => 'ӥ', 'Ӧ' => 'Ӧ', 'ӧ' => 'ӧ', 'Ӫ' => 'Ӫ', 'ӫ' => 'ӫ', 'Ӭ' => 'Ӭ', 'ӭ' => 'ӭ', 'Ӯ' => 'Ӯ', 'ӯ' => 'ӯ', 'Ӱ' => 'Ӱ', 'ӱ' => 'ӱ', 'Ӳ' => 'Ӳ', 'ӳ' => 'ӳ', 'Ӵ' => 'Ӵ', 'ӵ' => 'ӵ', 'Ӹ' => 'Ӹ', 'ӹ' => 'ӹ', 'آ' => 'آ', 'أ' => 'أ', 'ؤ' => 'ؤ', 'إ' => 'إ', 'ئ' => 'ئ', 'ۀ' => 'ۀ', 'ۂ' => 'ۂ', 'ۓ' => 'ۓ', 'ऩ' => 'ऩ', 'ऱ' => 'ऱ', 'ऴ' => 'ऴ', 'क़' => 'क़', 'ख़' => 'ख़', 'ग़' => 'ग़', 'ज़' => 'ज़', 'ड़' => 'ड़', 'ढ़' => 'ढ़', 'फ़' => 'फ़', 'य़' => 'य़', 'ো' => 'ো', 'ৌ' => 'ৌ', 'ড়' => 'ড়', 'ঢ়' => 'ঢ়', 'য়' => 'য়', 'ਲ਼' => 'ਲ਼', 'ਸ਼' => 'ਸ਼', 'ਖ਼' => 'ਖ਼', 'ਗ਼' => 'ਗ਼', 'ਜ਼' => 'ਜ਼', 'ਫ਼' => 'ਫ਼', 'ୈ' => 'ୈ', 'ୋ' => 'ୋ', 'ୌ' => 'ୌ', 'ଡ଼' => 'ଡ଼', 'ଢ଼' => 'ଢ଼', 'ஔ' => 'ஔ', 'ொ' => 'ொ', 'ோ' => 'ோ', 'ௌ' => 'ௌ', 'ై' => 'ై', 'ೀ' => 'ೀ', 'ೇ' => 'ೇ', 'ೈ' => 'ೈ', 'ೊ' => 'ೊ', 'ೋ' => 'ೋ', 'ൊ' => 'ൊ', 'ോ' => 'ോ', 'ൌ' => 'ൌ', 'ේ' => 'ේ', 'ො' => 'ො', 'ෝ' => 'ෝ', 'ෞ' => 'ෞ', 'གྷ' => 'གྷ', 'ཌྷ' => 'ཌྷ', 'དྷ' => 'དྷ', 'བྷ' => 'བྷ', 'ཛྷ' => 'ཛྷ', 'ཀྵ' => 'ཀྵ', 'ཱི' => 'ཱི', 'ཱུ' => 'ཱུ', 'ྲྀ' => 'ྲྀ', 'ླྀ' => 'ླྀ', 'ཱྀ' => 'ཱྀ', 'ྒྷ' => 'ྒྷ', 'ྜྷ' => 'ྜྷ', 'ྡྷ' => 'ྡྷ', 'ྦྷ' => 'ྦྷ', 'ྫྷ' => 'ྫྷ', 'ྐྵ' => 'ྐྵ', 'ဦ' => 'ဦ', 'ᬆ' => 'ᬆ', 'ᬈ' => 'ᬈ', 'ᬊ' => 'ᬊ', 'ᬌ' => 'ᬌ', 'ᬎ' => 'ᬎ', 'ᬒ' => 'ᬒ', 'ᬻ' => 'ᬻ', 'ᬽ' => 'ᬽ', 'ᭀ' => 'ᭀ', 'ᭁ' => 'ᭁ', 'ᭃ' => 'ᭃ', 'Ḁ' => 'Ḁ', 'ḁ' => 'ḁ', 'Ḃ' => 'Ḃ', 'ḃ' => 'ḃ', 'Ḅ' => 'Ḅ', 'ḅ' => 'ḅ', 'Ḇ' => 'Ḇ', 'ḇ' => 'ḇ', 'Ḉ' => 'Ḉ', 'ḉ' => 'ḉ', 'Ḋ' => 'Ḋ', 'ḋ' => 'ḋ', 'Ḍ' => 'Ḍ', 'ḍ' => 'ḍ', 'Ḏ' => 'Ḏ', 'ḏ' => 'ḏ', 'Ḑ' => 'Ḑ', 'ḑ' => 'ḑ', 'Ḓ' => 'Ḓ', 'ḓ' => 'ḓ', 'Ḕ' => 'Ḕ', 'ḕ' => 'ḕ', 'Ḗ' => 'Ḗ', 'ḗ' => 'ḗ', 'Ḙ' => 'Ḙ', 'ḙ' => 'ḙ', 'Ḛ' => 'Ḛ', 'ḛ' => 'ḛ', 'Ḝ' => 'Ḝ', 'ḝ' => 'ḝ', 'Ḟ' => 'Ḟ', 'ḟ' => 'ḟ', 'Ḡ' => 'Ḡ', 'ḡ' => 'ḡ', 'Ḣ' => 'Ḣ', 'ḣ' => 'ḣ', 'Ḥ' => 'Ḥ', 'ḥ' => 'ḥ', 'Ḧ' => 'Ḧ', 'ḧ' => 'ḧ', 'Ḩ' => 'Ḩ', 'ḩ' => 'ḩ', 'Ḫ' => 'Ḫ', 'ḫ' => 'ḫ', 'Ḭ' => 'Ḭ', 'ḭ' => 'ḭ', 'Ḯ' => 'Ḯ', 'ḯ' => 'ḯ', 'Ḱ' => 'Ḱ', 'ḱ' => 'ḱ', 'Ḳ' => 'Ḳ', 'ḳ' => 'ḳ', 'Ḵ' => 'Ḵ', 'ḵ' => 'ḵ', 'Ḷ' => 'Ḷ', 'ḷ' => 'ḷ', 'Ḹ' => 'Ḹ', 'ḹ' => 'ḹ', 'Ḻ' => 'Ḻ', 'ḻ' => 'ḻ', 'Ḽ' => 'Ḽ', 'ḽ' => 'ḽ', 'Ḿ' => 'Ḿ', 'ḿ' => 'ḿ', 'Ṁ' => 'Ṁ', 'ṁ' => 'ṁ', 'Ṃ' => 'Ṃ', 'ṃ' => 'ṃ', 'Ṅ' => 'Ṅ', 'ṅ' => 'ṅ', 'Ṇ' => 'Ṇ', 'ṇ' => 'ṇ', 'Ṉ' => 'Ṉ', 'ṉ' => 'ṉ', 'Ṋ' => 'Ṋ', 'ṋ' => 'ṋ', 'Ṍ' => 'Ṍ', 'ṍ' => 'ṍ', 'Ṏ' => 'Ṏ', 'ṏ' => 'ṏ', 'Ṑ' => 'Ṑ', 'ṑ' => 'ṑ', 'Ṓ' => 'Ṓ', 'ṓ' => 'ṓ', 'Ṕ' => 'Ṕ', 'ṕ' => 'ṕ', 'Ṗ' => 'Ṗ', 'ṗ' => 'ṗ', 'Ṙ' => 'Ṙ', 'ṙ' => 'ṙ', 'Ṛ' => 'Ṛ', 'ṛ' => 'ṛ', 'Ṝ' => 'Ṝ', 'ṝ' => 'ṝ', 'Ṟ' => 'Ṟ', 'ṟ' => 'ṟ', 'Ṡ' => 'Ṡ', 'ṡ' => 'ṡ', 'Ṣ' => 'Ṣ', 'ṣ' => 'ṣ', 'Ṥ' => 'Ṥ', 'ṥ' => 'ṥ', 'Ṧ' => 'Ṧ', 'ṧ' => 'ṧ', 'Ṩ' => 'Ṩ', 'ṩ' => 'ṩ', 'Ṫ' => 'Ṫ', 'ṫ' => 'ṫ', 'Ṭ' => 'Ṭ', 'ṭ' => 'ṭ', 'Ṯ' => 'Ṯ', 'ṯ' => 'ṯ', 'Ṱ' => 'Ṱ', 'ṱ' => 'ṱ', 'Ṳ' => 'Ṳ', 'ṳ' => 'ṳ', 'Ṵ' => 'Ṵ', 'ṵ' => 'ṵ', 'Ṷ' => 'Ṷ', 'ṷ' => 'ṷ', 'Ṹ' => 'Ṹ', 'ṹ' => 'ṹ', 'Ṻ' => 'Ṻ', 'ṻ' => 'ṻ', 'Ṽ' => 'Ṽ', 'ṽ' => 'ṽ', 'Ṿ' => 'Ṿ', 'ṿ' => 'ṿ', 'Ẁ' => 'Ẁ', 'ẁ' => 'ẁ', 'Ẃ' => 'Ẃ', 'ẃ' => 'ẃ', 'Ẅ' => 'Ẅ', 'ẅ' => 'ẅ', 'Ẇ' => 'Ẇ', 'ẇ' => 'ẇ', 'Ẉ' => 'Ẉ', 'ẉ' => 'ẉ', 'Ẋ' => 'Ẋ', 'ẋ' => 'ẋ', 'Ẍ' => 'Ẍ', 'ẍ' => 'ẍ', 'Ẏ' => 'Ẏ', 'ẏ' => 'ẏ', 'Ẑ' => 'Ẑ', 'ẑ' => 'ẑ', 'Ẓ' => 'Ẓ', 'ẓ' => 'ẓ', 'Ẕ' => 'Ẕ', 'ẕ' => 'ẕ', 'ẖ' => 'ẖ', 'ẗ' => 'ẗ', 'ẘ' => 'ẘ', 'ẙ' => 'ẙ', 'ẛ' => 'ẛ', 'Ạ' => 'Ạ', 'ạ' => 'ạ', 'Ả' => 'Ả', 'ả' => 'ả', 'Ấ' => 'Ấ', 'ấ' => 'ấ', 'Ầ' => 'Ầ', 'ầ' => 'ầ', 'Ẩ' => 'Ẩ', 'ẩ' => 'ẩ', 'Ẫ' => 'Ẫ', 'ẫ' => 'ẫ', 'Ậ' => 'Ậ', 'ậ' => 'ậ', 'Ắ' => 'Ắ', 'ắ' => 'ắ', 'Ằ' => 'Ằ', 'ằ' => 'ằ', 'Ẳ' => 'Ẳ', 'ẳ' => 'ẳ', 'Ẵ' => 'Ẵ', 'ẵ' => 'ẵ', 'Ặ' => 'Ặ', 'ặ' => 'ặ', 'Ẹ' => 'Ẹ', 'ẹ' => 'ẹ', 'Ẻ' => 'Ẻ', 'ẻ' => 'ẻ', 'Ẽ' => 'Ẽ', 'ẽ' => 'ẽ', 'Ế' => 'Ế', 'ế' => 'ế', 'Ề' => 'Ề', 'ề' => 'ề', 'Ể' => 'Ể', 'ể' => 'ể', 'Ễ' => 'Ễ', 'ễ' => 'ễ', 'Ệ' => 'Ệ', 'ệ' => 'ệ', 'Ỉ' => 'Ỉ', 'ỉ' => 'ỉ', 'Ị' => 'Ị', 'ị' => 'ị', 'Ọ' => 'Ọ', 'ọ' => 'ọ', 'Ỏ' => 'Ỏ', 'ỏ' => 'ỏ', 'Ố' => 'Ố', 'ố' => 'ố', 'Ồ' => 'Ồ', 'ồ' => 'ồ', 'Ổ' => 'Ổ', 'ổ' => 'ổ', 'Ỗ' => 'Ỗ', 'ỗ' => 'ỗ', 'Ộ' => 'Ộ', 'ộ' => 'ộ', 'Ớ' => 'Ớ', 'ớ' => 'ớ', 'Ờ' => 'Ờ', 'ờ' => 'ờ', 'Ở' => 'Ở', 'ở' => 'ở', 'Ỡ' => 'Ỡ', 'ỡ' => 'ỡ', 'Ợ' => 'Ợ', 'ợ' => 'ợ', 'Ụ' => 'Ụ', 'ụ' => 'ụ', 'Ủ' => 'Ủ', 'ủ' => 'ủ', 'Ứ' => 'Ứ', 'ứ' => 'ứ', 'Ừ' => 'Ừ', 'ừ' => 'ừ', 'Ử' => 'Ử', 'ử' => 'ử', 'Ữ' => 'Ữ', 'ữ' => 'ữ', 'Ự' => 'Ự', 'ự' => 'ự', 'Ỳ' => 'Ỳ', 'ỳ' => 'ỳ', 'Ỵ' => 'Ỵ', 'ỵ' => 'ỵ', 'Ỷ' => 'Ỷ', 'ỷ' => 'ỷ', 'Ỹ' => 'Ỹ', 'ỹ' => 'ỹ', 'ἀ' => 'ἀ', 'ἁ' => 'ἁ', 'ἂ' => 'ἂ', 'ἃ' => 'ἃ', 'ἄ' => 'ἄ', 'ἅ' => 'ἅ', 'ἆ' => 'ἆ', 'ἇ' => 'ἇ', 'Ἀ' => 'Ἀ', 'Ἁ' => 'Ἁ', 'Ἂ' => 'Ἂ', 'Ἃ' => 'Ἃ', 'Ἄ' => 'Ἄ', 'Ἅ' => 'Ἅ', 'Ἆ' => 'Ἆ', 'Ἇ' => 'Ἇ', 'ἐ' => 'ἐ', 'ἑ' => 'ἑ', 'ἒ' => 'ἒ', 'ἓ' => 'ἓ', 'ἔ' => 'ἔ', 'ἕ' => 'ἕ', 'Ἐ' => 'Ἐ', 'Ἑ' => 'Ἑ', 'Ἒ' => 'Ἒ', 'Ἓ' => 'Ἓ', 'Ἔ' => 'Ἔ', 'Ἕ' => 'Ἕ', 'ἠ' => 'ἠ', 'ἡ' => 'ἡ', 'ἢ' => 'ἢ', 'ἣ' => 'ἣ', 'ἤ' => 'ἤ', 'ἥ' => 'ἥ', 'ἦ' => 'ἦ', 'ἧ' => 'ἧ', 'Ἠ' => 'Ἠ', 'Ἡ' => 'Ἡ', 'Ἢ' => 'Ἢ', 'Ἣ' => 'Ἣ', 'Ἤ' => 'Ἤ', 'Ἥ' => 'Ἥ', 'Ἦ' => 'Ἦ', 'Ἧ' => 'Ἧ', 'ἰ' => 'ἰ', 'ἱ' => 'ἱ', 'ἲ' => 'ἲ', 'ἳ' => 'ἳ', 'ἴ' => 'ἴ', 'ἵ' => 'ἵ', 'ἶ' => 'ἶ', 'ἷ' => 'ἷ', 'Ἰ' => 'Ἰ', 'Ἱ' => 'Ἱ', 'Ἲ' => 'Ἲ', 'Ἳ' => 'Ἳ', 'Ἴ' => 'Ἴ', 'Ἵ' => 'Ἵ', 'Ἶ' => 'Ἶ', 'Ἷ' => 'Ἷ', 'ὀ' => 'ὀ', 'ὁ' => 'ὁ', 'ὂ' => 'ὂ', 'ὃ' => 'ὃ', 'ὄ' => 'ὄ', 'ὅ' => 'ὅ', 'Ὀ' => 'Ὀ', 'Ὁ' => 'Ὁ', 'Ὂ' => 'Ὂ', 'Ὃ' => 'Ὃ', 'Ὄ' => 'Ὄ', 'Ὅ' => 'Ὅ', 'ὐ' => 'ὐ', 'ὑ' => 'ὑ', 'ὒ' => 'ὒ', 'ὓ' => 'ὓ', 'ὔ' => 'ὔ', 'ὕ' => 'ὕ', 'ὖ' => 'ὖ', 'ὗ' => 'ὗ', 'Ὑ' => 'Ὑ', 'Ὓ' => 'Ὓ', 'Ὕ' => 'Ὕ', 'Ὗ' => 'Ὗ', 'ὠ' => 'ὠ', 'ὡ' => 'ὡ', 'ὢ' => 'ὢ', 'ὣ' => 'ὣ', 'ὤ' => 'ὤ', 'ὥ' => 'ὥ', 'ὦ' => 'ὦ', 'ὧ' => 'ὧ', 'Ὠ' => 'Ὠ', 'Ὡ' => 'Ὡ', 'Ὢ' => 'Ὢ', 'Ὣ' => 'Ὣ', 'Ὤ' => 'Ὤ', 'Ὥ' => 'Ὥ', 'Ὦ' => 'Ὦ', 'Ὧ' => 'Ὧ', 'ὰ' => 'ὰ', 'ά' => 'ά', 'ὲ' => 'ὲ', 'έ' => 'έ', 'ὴ' => 'ὴ', 'ή' => 'ή', 'ὶ' => 'ὶ', 'ί' => 'ί', 'ὸ' => 'ὸ', 'ό' => 'ό', 'ὺ' => 'ὺ', 'ύ' => 'ύ', 'ὼ' => 'ὼ', 'ώ' => 'ώ', 'ᾀ' => 'ᾀ', 'ᾁ' => 'ᾁ', 'ᾂ' => 'ᾂ', 'ᾃ' => 'ᾃ', 'ᾄ' => 'ᾄ', 'ᾅ' => 'ᾅ', 'ᾆ' => 'ᾆ', 'ᾇ' => 'ᾇ', 'ᾈ' => 'ᾈ', 'ᾉ' => 'ᾉ', 'ᾊ' => 'ᾊ', 'ᾋ' => 'ᾋ', 'ᾌ' => 'ᾌ', 'ᾍ' => 'ᾍ', 'ᾎ' => 'ᾎ', 'ᾏ' => 'ᾏ', 'ᾐ' => 'ᾐ', 'ᾑ' => 'ᾑ', 'ᾒ' => 'ᾒ', 'ᾓ' => 'ᾓ', 'ᾔ' => 'ᾔ', 'ᾕ' => 'ᾕ', 'ᾖ' => 'ᾖ', 'ᾗ' => 'ᾗ', 'ᾘ' => 'ᾘ', 'ᾙ' => 'ᾙ', 'ᾚ' => 'ᾚ', 'ᾛ' => 'ᾛ', 'ᾜ' => 'ᾜ', 'ᾝ' => 'ᾝ', 'ᾞ' => 'ᾞ', 'ᾟ' => 'ᾟ', 'ᾠ' => 'ᾠ', 'ᾡ' => 'ᾡ', 'ᾢ' => 'ᾢ', 'ᾣ' => 'ᾣ', 'ᾤ' => 'ᾤ', 'ᾥ' => 'ᾥ', 'ᾦ' => 'ᾦ', 'ᾧ' => 'ᾧ', 'ᾨ' => 'ᾨ', 'ᾩ' => 'ᾩ', 'ᾪ' => 'ᾪ', 'ᾫ' => 'ᾫ', 'ᾬ' => 'ᾬ', 'ᾭ' => 'ᾭ', 'ᾮ' => 'ᾮ', 'ᾯ' => 'ᾯ', 'ᾰ' => 'ᾰ', 'ᾱ' => 'ᾱ', 'ᾲ' => 'ᾲ', 'ᾳ' => 'ᾳ', 'ᾴ' => 'ᾴ', 'ᾶ' => 'ᾶ', 'ᾷ' => 'ᾷ', 'Ᾰ' => 'Ᾰ', 'Ᾱ' => 'Ᾱ', 'Ὰ' => 'Ὰ', 'Ά' => 'Ά', 'ᾼ' => 'ᾼ', 'ι' => 'ι', '῁' => '῁', 'ῂ' => 'ῂ', 'ῃ' => 'ῃ', 'ῄ' => 'ῄ', 'ῆ' => 'ῆ', 'ῇ' => 'ῇ', 'Ὲ' => 'Ὲ', 'Έ' => 'Έ', 'Ὴ' => 'Ὴ', 'Ή' => 'Ή', 'ῌ' => 'ῌ', '῍' => '῍', '῎' => '῎', '῏' => '῏', 'ῐ' => 'ῐ', 'ῑ' => 'ῑ', 'ῒ' => 'ῒ', 'ΐ' => 'ΐ', 'ῖ' => 'ῖ', 'ῗ' => 'ῗ', 'Ῐ' => 'Ῐ', 'Ῑ' => 'Ῑ', 'Ὶ' => 'Ὶ', 'Ί' => 'Ί', '῝' => '῝', '῞' => '῞', '῟' => '῟', 'ῠ' => 'ῠ', 'ῡ' => 'ῡ', 'ῢ' => 'ῢ', 'ΰ' => 'ΰ', 'ῤ' => 'ῤ', 'ῥ' => 'ῥ', 'ῦ' => 'ῦ', 'ῧ' => 'ῧ', 'Ῠ' => 'Ῠ', 'Ῡ' => 'Ῡ', 'Ὺ' => 'Ὺ', 'Ύ' => 'Ύ', 'Ῥ' => 'Ῥ', '῭' => '῭', '΅' => '΅', '`' => '`', 'ῲ' => 'ῲ', 'ῳ' => 'ῳ', 'ῴ' => 'ῴ', 'ῶ' => 'ῶ', 'ῷ' => 'ῷ', 'Ὸ' => 'Ὸ', 'Ό' => 'Ό', 'Ὼ' => 'Ὼ', 'Ώ' => 'Ώ', 'ῼ' => 'ῼ', '´' => '´', ' ' => ' ', ' ' => ' ', 'Ω' => 'Ω', 'K' => 'K', 'Å' => 'Å', '↚' => '↚', '↛' => '↛', '↮' => '↮', '⇍' => '⇍', '⇎' => '⇎', '⇏' => '⇏', '∄' => '∄', '∉' => '∉', '∌' => '∌', '∤' => '∤', '∦' => '∦', '≁' => '≁', '≄' => '≄', '≇' => '≇', '≉' => '≉', '≠' => '≠', '≢' => '≢', '≭' => '≭', '≮' => '≮', '≯' => '≯', '≰' => '≰', '≱' => '≱', '≴' => '≴', '≵' => '≵', '≸' => '≸', '≹' => '≹', '⊀' => '⊀', '⊁' => '⊁', '⊄' => '⊄', '⊅' => '⊅', '⊈' => '⊈', '⊉' => '⊉', '⊬' => '⊬', '⊭' => '⊭', '⊮' => '⊮', '⊯' => '⊯', '⋠' => '⋠', '⋡' => '⋡', '⋢' => '⋢', '⋣' => '⋣', '⋪' => '⋪', '⋫' => '⋫', '⋬' => '⋬', '⋭' => '⋭', '〈' => '〈', '〉' => '〉', '⫝̸' => '⫝̸', 'が' => 'が', 'ぎ' => 'ぎ', 'ぐ' => 'ぐ', 'げ' => 'げ', 'ご' => 'ご', 'ざ' => 'ざ', 'じ' => 'じ', 'ず' => 'ず', 'ぜ' => 'ぜ', 'ぞ' => 'ぞ', 'だ' => 'だ', 'ぢ' => 'ぢ', 'づ' => 'づ', 'で' => 'で', 'ど' => 'ど', 'ば' => 'ば', 'ぱ' => 'ぱ', 'び' => 'び', 'ぴ' => 'ぴ', 'ぶ' => 'ぶ', 'ぷ' => 'ぷ', 'べ' => 'べ', 'ぺ' => 'ぺ', 'ぼ' => 'ぼ', 'ぽ' => 'ぽ', 'ゔ' => 'ゔ', 'ゞ' => 'ゞ', 'ガ' => 'ガ', 'ギ' => 'ギ', 'グ' => 'グ', 'ゲ' => 'ゲ', 'ゴ' => 'ゴ', 'ザ' => 'ザ', 'ジ' => 'ジ', 'ズ' => 'ズ', 'ゼ' => 'ゼ', 'ゾ' => 'ゾ', 'ダ' => 'ダ', 'ヂ' => 'ヂ', 'ヅ' => 'ヅ', 'デ' => 'デ', 'ド' => 'ド', 'バ' => 'バ', 'パ' => 'パ', 'ビ' => 'ビ', 'ピ' => 'ピ', 'ブ' => 'ブ', 'プ' => 'プ', 'ベ' => 'ベ', 'ペ' => 'ペ', 'ボ' => 'ボ', 'ポ' => 'ポ', 'ヴ' => 'ヴ', 'ヷ' => 'ヷ', 'ヸ' => 'ヸ', 'ヹ' => 'ヹ', 'ヺ' => 'ヺ', 'ヾ' => 'ヾ', '豈' => '豈', '更' => '更', '車' => '車', '賈' => '賈', '滑' => '滑', '串' => '串', '句' => '句', '龜' => '龜', '龜' => '龜', '契' => '契', '金' => '金', '喇' => '喇', '奈' => '奈', '懶' => '懶', '癩' => '癩', '羅' => '羅', '蘿' => '蘿', '螺' => '螺', '裸' => '裸', '邏' => '邏', '樂' => '樂', '洛' => '洛', '烙' => '烙', '珞' => '珞', '落' => '落', '酪' => '酪', '駱' => '駱', '亂' => '亂', '卵' => '卵', '欄' => '欄', '爛' => '爛', '蘭' => '蘭', '鸞' => '鸞', '嵐' => '嵐', '濫' => '濫', '藍' => '藍', '襤' => '襤', '拉' => '拉', '臘' => '臘', '蠟' => '蠟', '廊' => '廊', '朗' => '朗', '浪' => '浪', '狼' => '狼', '郎' => '郎', '來' => '來', '冷' => '冷', '勞' => '勞', '擄' => '擄', '櫓' => '櫓', '爐' => '爐', '盧' => '盧', '老' => '老', '蘆' => '蘆', '虜' => '虜', '路' => '路', '露' => '露', '魯' => '魯', '鷺' => '鷺', '碌' => '碌', '祿' => '祿', '綠' => '綠', '菉' => '菉', '錄' => '錄', '鹿' => '鹿', '論' => '論', '壟' => '壟', '弄' => '弄', '籠' => '籠', '聾' => '聾', '牢' => '牢', '磊' => '磊', '賂' => '賂', '雷' => '雷', '壘' => '壘', '屢' => '屢', '樓' => '樓', '淚' => '淚', '漏' => '漏', '累' => '累', '縷' => '縷', '陋' => '陋', '勒' => '勒', '肋' => '肋', '凜' => '凜', '凌' => '凌', '稜' => '稜', '綾' => '綾', '菱' => '菱', '陵' => '陵', '讀' => '讀', '拏' => '拏', '樂' => '樂', '諾' => '諾', '丹' => '丹', '寧' => '寧', '怒' => '怒', '率' => '率', '異' => '異', '北' => '北', '磻' => '磻', '便' => '便', '復' => '復', '不' => '不', '泌' => '泌', '數' => '數', '索' => '索', '參' => '參', '塞' => '塞', '省' => '省', '葉' => '葉', '說' => '說', '殺' => '殺', '辰' => '辰', '沈' => '沈', '拾' => '拾', '若' => '若', '掠' => '掠', '略' => '略', '亮' => '亮', '兩' => '兩', '凉' => '凉', '梁' => '梁', '糧' => '糧', '良' => '良', '諒' => '諒', '量' => '量', '勵' => '勵', '呂' => '呂', '女' => '女', '廬' => '廬', '旅' => '旅', '濾' => '濾', '礪' => '礪', '閭' => '閭', '驪' => '驪', '麗' => '麗', '黎' => '黎', '力' => '力', '曆' => '曆', '歷' => '歷', '轢' => '轢', '年' => '年', '憐' => '憐', '戀' => '戀', '撚' => '撚', '漣' => '漣', '煉' => '煉', '璉' => '璉', '秊' => '秊', '練' => '練', '聯' => '聯', '輦' => '輦', '蓮' => '蓮', '連' => '連', '鍊' => '鍊', '列' => '列', '劣' => '劣', '咽' => '咽', '烈' => '烈', '裂' => '裂', '說' => '說', '廉' => '廉', '念' => '念', '捻' => '捻', '殮' => '殮', '簾' => '簾', '獵' => '獵', '令' => '令', '囹' => '囹', '寧' => '寧', '嶺' => '嶺', '怜' => '怜', '玲' => '玲', '瑩' => '瑩', '羚' => '羚', '聆' => '聆', '鈴' => '鈴', '零' => '零', '靈' => '靈', '領' => '領', '例' => '例', '禮' => '禮', '醴' => '醴', '隸' => '隸', '惡' => '惡', '了' => '了', '僚' => '僚', '寮' => '寮', '尿' => '尿', '料' => '料', '樂' => '樂', '燎' => '燎', '療' => '療', '蓼' => '蓼', '遼' => '遼', '龍' => '龍', '暈' => '暈', '阮' => '阮', '劉' => '劉', '杻' => '杻', '柳' => '柳', '流' => '流', '溜' => '溜', '琉' => '琉', '留' => '留', '硫' => '硫', '紐' => '紐', '類' => '類', '六' => '六', '戮' => '戮', '陸' => '陸', '倫' => '倫', '崙' => '崙', '淪' => '淪', '輪' => '輪', '律' => '律', '慄' => '慄', '栗' => '栗', '率' => '率', '隆' => '隆', '利' => '利', '吏' => '吏', '履' => '履', '易' => '易', '李' => '李', '梨' => '梨', '泥' => '泥', '理' => '理', '痢' => '痢', '罹' => '罹', '裏' => '裏', '裡' => '裡', '里' => '里', '離' => '離', '匿' => '匿', '溺' => '溺', '吝' => '吝', '燐' => '燐', '璘' => '璘', '藺' => '藺', '隣' => '隣', '鱗' => '鱗', '麟' => '麟', '林' => '林', '淋' => '淋', '臨' => '臨', '立' => '立', '笠' => '笠', '粒' => '粒', '狀' => '狀', '炙' => '炙', '識' => '識', '什' => '什', '茶' => '茶', '刺' => '刺', '切' => '切', '度' => '度', '拓' => '拓', '糖' => '糖', '宅' => '宅', '洞' => '洞', '暴' => '暴', '輻' => '輻', '行' => '行', '降' => '降', '見' => '見', '廓' => '廓', '兀' => '兀', '嗀' => '嗀', '塚' => '塚', '晴' => '晴', '凞' => '凞', '猪' => '猪', '益' => '益', '礼' => '礼', '神' => '神', '祥' => '祥', '福' => '福', '靖' => '靖', '精' => '精', '羽' => '羽', '蘒' => '蘒', '諸' => '諸', '逸' => '逸', '都' => '都', '飯' => '飯', '飼' => '飼', '館' => '館', '鶴' => '鶴', '郞' => '郞', '隷' => '隷', '侮' => '侮', '僧' => '僧', '免' => '免', '勉' => '勉', '勤' => '勤', '卑' => '卑', '喝' => '喝', '嘆' => '嘆', '器' => '器', '塀' => '塀', '墨' => '墨', '層' => '層', '屮' => '屮', '悔' => '悔', '慨' => '慨', '憎' => '憎', '懲' => '懲', '敏' => '敏', '既' => '既', '暑' => '暑', '梅' => '梅', '海' => '海', '渚' => '渚', '漢' => '漢', '煮' => '煮', '爫' => '爫', '琢' => '琢', '碑' => '碑', '社' => '社', '祉' => '祉', '祈' => '祈', '祐' => '祐', '祖' => '祖', '祝' => '祝', '禍' => '禍', '禎' => '禎', '穀' => '穀', '突' => '突', '節' => '節', '練' => '練', '縉' => '縉', '繁' => '繁', '署' => '署', '者' => '者', '臭' => '臭', '艹' => '艹', '艹' => '艹', '著' => '著', '褐' => '褐', '視' => '視', '謁' => '謁', '謹' => '謹', '賓' => '賓', '贈' => '贈', '辶' => '辶', '逸' => '逸', '難' => '難', '響' => '響', '頻' => '頻', '恵' => '恵', '𤋮' => '𤋮', '舘' => '舘', '並' => '並', '况' => '况', '全' => '全', '侀' => '侀', '充' => '充', '冀' => '冀', '勇' => '勇', '勺' => '勺', '喝' => '喝', '啕' => '啕', '喙' => '喙', '嗢' => '嗢', '塚' => '塚', '墳' => '墳', '奄' => '奄', '奔' => '奔', '婢' => '婢', '嬨' => '嬨', '廒' => '廒', '廙' => '廙', '彩' => '彩', '徭' => '徭', '惘' => '惘', '慎' => '慎', '愈' => '愈', '憎' => '憎', '慠' => '慠', '懲' => '懲', '戴' => '戴', '揄' => '揄', '搜' => '搜', '摒' => '摒', '敖' => '敖', '晴' => '晴', '朗' => '朗', '望' => '望', '杖' => '杖', '歹' => '歹', '殺' => '殺', '流' => '流', '滛' => '滛', '滋' => '滋', '漢' => '漢', '瀞' => '瀞', '煮' => '煮', '瞧' => '瞧', '爵' => '爵', '犯' => '犯', '猪' => '猪', '瑱' => '瑱', '甆' => '甆', '画' => '画', '瘝' => '瘝', '瘟' => '瘟', '益' => '益', '盛' => '盛', '直' => '直', '睊' => '睊', '着' => '着', '磌' => '磌', '窱' => '窱', '節' => '節', '类' => '类', '絛' => '絛', '練' => '練', '缾' => '缾', '者' => '者', '荒' => '荒', '華' => '華', '蝹' => '蝹', '襁' => '襁', '覆' => '覆', '視' => '視', '調' => '調', '諸' => '諸', '請' => '請', '謁' => '謁', '諾' => '諾', '諭' => '諭', '謹' => '謹', '變' => '變', '贈' => '贈', '輸' => '輸', '遲' => '遲', '醙' => '醙', '鉶' => '鉶', '陼' => '陼', '難' => '難', '靖' => '靖', '韛' => '韛', '響' => '響', '頋' => '頋', '頻' => '頻', '鬒' => '鬒', '龜' => '龜', '𢡊' => '𢡊', '𢡄' => '𢡄', '𣏕' => '𣏕', '㮝' => '㮝', '䀘' => '䀘', '䀹' => '䀹', '𥉉' => '𥉉', '𥳐' => '𥳐', '𧻓' => '𧻓', '齃' => '齃', '龎' => '龎', 'יִ' => 'יִ', 'ײַ' => 'ײַ', 'שׁ' => 'שׁ', 'שׂ' => 'שׂ', 'שּׁ' => 'שּׁ', 'שּׂ' => 'שּׂ', 'אַ' => 'אַ', 'אָ' => 'אָ', 'אּ' => 'אּ', 'בּ' => 'בּ', 'גּ' => 'גּ', 'דּ' => 'דּ', 'הּ' => 'הּ', 'וּ' => 'וּ', 'זּ' => 'זּ', 'טּ' => 'טּ', 'יּ' => 'יּ', 'ךּ' => 'ךּ', 'כּ' => 'כּ', 'לּ' => 'לּ', 'מּ' => 'מּ', 'נּ' => 'נּ', 'סּ' => 'סּ', 'ףּ' => 'ףּ', 'פּ' => 'פּ', 'צּ' => 'צּ', 'קּ' => 'קּ', 'רּ' => 'רּ', 'שּ' => 'שּ', 'תּ' => 'תּ', 'וֹ' => 'וֹ', 'בֿ' => 'בֿ', 'כֿ' => 'כֿ', 'פֿ' => 'פֿ', '𑂚' => '𑂚', '𑂜' => '𑂜', '𑂫' => '𑂫', '𑄮' => '𑄮', '𑄯' => '𑄯', '𑍋' => '𑍋', '𑍌' => '𑍌', '𑒻' => '𑒻', '𑒼' => '𑒼', '𑒾' => '𑒾', '𑖺' => '𑖺', '𑖻' => '𑖻', '𑤸' => '𑤸', '𝅗𝅥' => '𝅗𝅥', '𝅘𝅥' => '𝅘𝅥', '𝅘𝅥𝅮' => '𝅘𝅥𝅮', '𝅘𝅥𝅯' => '𝅘𝅥𝅯', '𝅘𝅥𝅰' => '𝅘𝅥𝅰', '𝅘𝅥𝅱' => '𝅘𝅥𝅱', '𝅘𝅥𝅲' => '𝅘𝅥𝅲', '𝆹𝅥' => '𝆹𝅥', '𝆺𝅥' => '𝆺𝅥', '𝆹𝅥𝅮' => '𝆹𝅥𝅮', '𝆺𝅥𝅮' => '𝆺𝅥𝅮', '𝆹𝅥𝅯' => '𝆹𝅥𝅯', '𝆺𝅥𝅯' => '𝆺𝅥𝅯', '丽' => '丽', '丸' => '丸', '乁' => '乁', '𠄢' => '𠄢', '你' => '你', '侮' => '侮', '侻' => '侻', '倂' => '倂', '偺' => '偺', '備' => '備', '僧' => '僧', '像' => '像', '㒞' => '㒞', '𠘺' => '𠘺', '免' => '免', '兔' => '兔', '兤' => '兤', '具' => '具', '𠔜' => '𠔜', '㒹' => '㒹', '內' => '內', '再' => '再', '𠕋' => '𠕋', '冗' => '冗', '冤' => '冤', '仌' => '仌', '冬' => '冬', '况' => '况', '𩇟' => '𩇟', '凵' => '凵', '刃' => '刃', '㓟' => '㓟', '刻' => '刻', '剆' => '剆', '割' => '割', '剷' => '剷', '㔕' => '㔕', '勇' => '勇', '勉' => '勉', '勤' => '勤', '勺' => '勺', '包' => '包', '匆' => '匆', '北' => '北', '卉' => '卉', '卑' => '卑', '博' => '博', '即' => '即', '卽' => '卽', '卿' => '卿', '卿' => '卿', '卿' => '卿', '𠨬' => '𠨬', '灰' => '灰', '及' => '及', '叟' => '叟', '𠭣' => '𠭣', '叫' => '叫', '叱' => '叱', '吆' => '吆', '咞' => '咞', '吸' => '吸', '呈' => '呈', '周' => '周', '咢' => '咢', '哶' => '哶', '唐' => '唐', '啓' => '啓', '啣' => '啣', '善' => '善', '善' => '善', '喙' => '喙', '喫' => '喫', '喳' => '喳', '嗂' => '嗂', '圖' => '圖', '嘆' => '嘆', '圗' => '圗', '噑' => '噑', '噴' => '噴', '切' => '切', '壮' => '壮', '城' => '城', '埴' => '埴', '堍' => '堍', '型' => '型', '堲' => '堲', '報' => '報', '墬' => '墬', '𡓤' => '𡓤', '売' => '売', '壷' => '壷', '夆' => '夆', '多' => '多', '夢' => '夢', '奢' => '奢', '𡚨' => '𡚨', '𡛪' => '𡛪', '姬' => '姬', '娛' => '娛', '娧' => '娧', '姘' => '姘', '婦' => '婦', '㛮' => '㛮', '㛼' => '㛼', '嬈' => '嬈', '嬾' => '嬾', '嬾' => '嬾', '𡧈' => '𡧈', '寃' => '寃', '寘' => '寘', '寧' => '寧', '寳' => '寳', '𡬘' => '𡬘', '寿' => '寿', '将' => '将', '当' => '当', '尢' => '尢', '㞁' => '㞁', '屠' => '屠', '屮' => '屮', '峀' => '峀', '岍' => '岍', '𡷤' => '𡷤', '嵃' => '嵃', '𡷦' => '𡷦', '嵮' => '嵮', '嵫' => '嵫', '嵼' => '嵼', '巡' => '巡', '巢' => '巢', '㠯' => '㠯', '巽' => '巽', '帨' => '帨', '帽' => '帽', '幩' => '幩', '㡢' => '㡢', '𢆃' => '𢆃', '㡼' => '㡼', '庰' => '庰', '庳' => '庳', '庶' => '庶', '廊' => '廊', '𪎒' => '𪎒', '廾' => '廾', '𢌱' => '𢌱', '𢌱' => '𢌱', '舁' => '舁', '弢' => '弢', '弢' => '弢', '㣇' => '㣇', '𣊸' => '𣊸', '𦇚' => '𦇚', '形' => '形', '彫' => '彫', '㣣' => '㣣', '徚' => '徚', '忍' => '忍', '志' => '志', '忹' => '忹', '悁' => '悁', '㤺' => '㤺', '㤜' => '㤜', '悔' => '悔', '𢛔' => '𢛔', '惇' => '惇', '慈' => '慈', '慌' => '慌', '慎' => '慎', '慌' => '慌', '慺' => '慺', '憎' => '憎', '憲' => '憲', '憤' => '憤', '憯' => '憯', '懞' => '懞', '懲' => '懲', '懶' => '懶', '成' => '成', '戛' => '戛', '扝' => '扝', '抱' => '抱', '拔' => '拔', '捐' => '捐', '𢬌' => '𢬌', '挽' => '挽', '拼' => '拼', '捨' => '捨', '掃' => '掃', '揤' => '揤', '𢯱' => '𢯱', '搢' => '搢', '揅' => '揅', '掩' => '掩', '㨮' => '㨮', '摩' => '摩', '摾' => '摾', '撝' => '撝', '摷' => '摷', '㩬' => '㩬', '敏' => '敏', '敬' => '敬', '𣀊' => '𣀊', '旣' => '旣', '書' => '書', '晉' => '晉', '㬙' => '㬙', '暑' => '暑', '㬈' => '㬈', '㫤' => '㫤', '冒' => '冒', '冕' => '冕', '最' => '最', '暜' => '暜', '肭' => '肭', '䏙' => '䏙', '朗' => '朗', '望' => '望', '朡' => '朡', '杞' => '杞', '杓' => '杓', '𣏃' => '𣏃', '㭉' => '㭉', '柺' => '柺', '枅' => '枅', '桒' => '桒', '梅' => '梅', '𣑭' => '𣑭', '梎' => '梎', '栟' => '栟', '椔' => '椔', '㮝' => '㮝', '楂' => '楂', '榣' => '榣', '槪' => '槪', '檨' => '檨', '𣚣' => '𣚣', '櫛' => '櫛', '㰘' => '㰘', '次' => '次', '𣢧' => '𣢧', '歔' => '歔', '㱎' => '㱎', '歲' => '歲', '殟' => '殟', '殺' => '殺', '殻' => '殻', '𣪍' => '𣪍', '𡴋' => '𡴋', '𣫺' => '𣫺', '汎' => '汎', '𣲼' => '𣲼', '沿' => '沿', '泍' => '泍', '汧' => '汧', '洖' => '洖', '派' => '派', '海' => '海', '流' => '流', '浩' => '浩', '浸' => '浸', '涅' => '涅', '𣴞' => '𣴞', '洴' => '洴', '港' => '港', '湮' => '湮', '㴳' => '㴳', '滋' => '滋', '滇' => '滇', '𣻑' => '𣻑', '淹' => '淹', '潮' => '潮', '𣽞' => '𣽞', '𣾎' => '𣾎', '濆' => '濆', '瀹' => '瀹', '瀞' => '瀞', '瀛' => '瀛', '㶖' => '㶖', '灊' => '灊', '災' => '災', '灷' => '灷', '炭' => '炭', '𠔥' => '𠔥', '煅' => '煅', '𤉣' => '𤉣', '熜' => '熜', '𤎫' => '𤎫', '爨' => '爨', '爵' => '爵', '牐' => '牐', '𤘈' => '𤘈', '犀' => '犀', '犕' => '犕', '𤜵' => '𤜵', '𤠔' => '𤠔', '獺' => '獺', '王' => '王', '㺬' => '㺬', '玥' => '玥', '㺸' => '㺸', '㺸' => '㺸', '瑇' => '瑇', '瑜' => '瑜', '瑱' => '瑱', '璅' => '璅', '瓊' => '瓊', '㼛' => '㼛', '甤' => '甤', '𤰶' => '𤰶', '甾' => '甾', '𤲒' => '𤲒', '異' => '異', '𢆟' => '𢆟', '瘐' => '瘐', '𤾡' => '𤾡', '𤾸' => '𤾸', '𥁄' => '𥁄', '㿼' => '㿼', '䀈' => '䀈', '直' => '直', '𥃳' => '𥃳', '𥃲' => '𥃲', '𥄙' => '𥄙', '𥄳' => '𥄳', '眞' => '眞', '真' => '真', '真' => '真', '睊' => '睊', '䀹' => '䀹', '瞋' => '瞋', '䁆' => '䁆', '䂖' => '䂖', '𥐝' => '𥐝', '硎' => '硎', '碌' => '碌', '磌' => '磌', '䃣' => '䃣', '𥘦' => '𥘦', '祖' => '祖', '𥚚' => '𥚚', '𥛅' => '𥛅', '福' => '福', '秫' => '秫', '䄯' => '䄯', '穀' => '穀', '穊' => '穊', '穏' => '穏', '𥥼' => '𥥼', '𥪧' => '𥪧', '𥪧' => '𥪧', '竮' => '竮', '䈂' => '䈂', '𥮫' => '𥮫', '篆' => '篆', '築' => '築', '䈧' => '䈧', '𥲀' => '𥲀', '糒' => '糒', '䊠' => '䊠', '糨' => '糨', '糣' => '糣', '紀' => '紀', '𥾆' => '𥾆', '絣' => '絣', '䌁' => '䌁', '緇' => '緇', '縂' => '縂', '繅' => '繅', '䌴' => '䌴', '𦈨' => '𦈨', '𦉇' => '𦉇', '䍙' => '䍙', '𦋙' => '𦋙', '罺' => '罺', '𦌾' => '𦌾', '羕' => '羕', '翺' => '翺', '者' => '者', '𦓚' => '𦓚', '𦔣' => '𦔣', '聠' => '聠', '𦖨' => '𦖨', '聰' => '聰', '𣍟' => '𣍟', '䏕' => '䏕', '育' => '育', '脃' => '脃', '䐋' => '䐋', '脾' => '脾', '媵' => '媵', '𦞧' => '𦞧', '𦞵' => '𦞵', '𣎓' => '𣎓', '𣎜' => '𣎜', '舁' => '舁', '舄' => '舄', '辞' => '辞', '䑫' => '䑫', '芑' => '芑', '芋' => '芋', '芝' => '芝', '劳' => '劳', '花' => '花', '芳' => '芳', '芽' => '芽', '苦' => '苦', '𦬼' => '𦬼', '若' => '若', '茝' => '茝', '荣' => '荣', '莭' => '莭', '茣' => '茣', '莽' => '莽', '菧' => '菧', '著' => '著', '荓' => '荓', '菊' => '菊', '菌' => '菌', '菜' => '菜', '𦰶' => '𦰶', '𦵫' => '𦵫', '𦳕' => '𦳕', '䔫' => '䔫', '蓱' => '蓱', '蓳' => '蓳', '蔖' => '蔖', '𧏊' => '𧏊', '蕤' => '蕤', '𦼬' => '𦼬', '䕝' => '䕝', '䕡' => '䕡', '𦾱' => '𦾱', '𧃒' => '𧃒', '䕫' => '䕫', '虐' => '虐', '虜' => '虜', '虧' => '虧', '虩' => '虩', '蚩' => '蚩', '蚈' => '蚈', '蜎' => '蜎', '蛢' => '蛢', '蝹' => '蝹', '蜨' => '蜨', '蝫' => '蝫', '螆' => '螆', '䗗' => '䗗', '蟡' => '蟡', '蠁' => '蠁', '䗹' => '䗹', '衠' => '衠', '衣' => '衣', '𧙧' => '𧙧', '裗' => '裗', '裞' => '裞', '䘵' => '䘵', '裺' => '裺', '㒻' => '㒻', '𧢮' => '𧢮', '𧥦' => '𧥦', '䚾' => '䚾', '䛇' => '䛇', '誠' => '誠', '諭' => '諭', '變' => '變', '豕' => '豕', '𧲨' => '𧲨', '貫' => '貫', '賁' => '賁', '贛' => '贛', '起' => '起', '𧼯' => '𧼯', '𠠄' => '𠠄', '跋' => '跋', '趼' => '趼', '跰' => '跰', '𠣞' => '𠣞', '軔' => '軔', '輸' => '輸', '𨗒' => '𨗒', '𨗭' => '𨗭', '邔' => '邔', '郱' => '郱', '鄑' => '鄑', '𨜮' => '𨜮', '鄛' => '鄛', '鈸' => '鈸', '鋗' => '鋗', '鋘' => '鋘', '鉼' => '鉼', '鏹' => '鏹', '鐕' => '鐕', '𨯺' => '𨯺', '開' => '開', '䦕' => '䦕', '閷' => '閷', '𨵷' => '𨵷', '䧦' => '䧦', '雃' => '雃', '嶲' => '嶲', '霣' => '霣', '𩅅' => '𩅅', '𩈚' => '𩈚', '䩮' => '䩮', '䩶' => '䩶', '韠' => '韠', '𩐊' => '𩐊', '䪲' => '䪲', '𩒖' => '𩒖', '頋' => '頋', '頋' => '頋', '頩' => '頩', '𩖶' => '𩖶', '飢' => '飢', '䬳' => '䬳', '餩' => '餩', '馧' => '馧', '駂' => '駂', '駾' => '駾', '䯎' => '䯎', '𩬰' => '𩬰', '鬒' => '鬒', '鱀' => '鱀', '鳽' => '鳽', '䳎' => '䳎', '䳭' => '䳭', '鵧' => '鵧', '𪃎' => '𪃎', '䳸' => '䳸', '𪄅' => '𪄅', '𪈎' => '𪈎', '𪊑' => '𪊑', '麻' => '麻', '䵖' => '䵖', '黹' => '黹', '黾' => '黾', '鼅' => '鼅', '鼏' => '鼏', '鼖' => '鼖', '鼻' => '鼻', '𪘀' => '𪘀', ); 230, '́' => 230, '̂' => 230, '̃' => 230, '̄' => 230, '̅' => 230, '̆' => 230, '̇' => 230, '̈' => 230, '̉' => 230, '̊' => 230, '̋' => 230, '̌' => 230, '̍' => 230, '̎' => 230, '̏' => 230, '̐' => 230, '̑' => 230, '̒' => 230, '̓' => 230, '̔' => 230, '̕' => 232, '̖' => 220, '̗' => 220, '̘' => 220, '̙' => 220, '̚' => 232, '̛' => 216, '̜' => 220, '̝' => 220, '̞' => 220, '̟' => 220, '̠' => 220, '̡' => 202, '̢' => 202, '̣' => 220, '̤' => 220, '̥' => 220, '̦' => 220, '̧' => 202, '̨' => 202, '̩' => 220, '̪' => 220, '̫' => 220, '̬' => 220, '̭' => 220, '̮' => 220, '̯' => 220, '̰' => 220, '̱' => 220, '̲' => 220, '̳' => 220, '̴' => 1, '̵' => 1, '̶' => 1, '̷' => 1, '̸' => 1, '̹' => 220, '̺' => 220, '̻' => 220, '̼' => 220, '̽' => 230, '̾' => 230, '̿' => 230, '̀' => 230, '́' => 230, '͂' => 230, '̓' => 230, '̈́' => 230, 'ͅ' => 240, '͆' => 230, '͇' => 220, '͈' => 220, '͉' => 220, '͊' => 230, '͋' => 230, '͌' => 230, '͍' => 220, '͎' => 220, '͐' => 230, '͑' => 230, '͒' => 230, '͓' => 220, '͔' => 220, '͕' => 220, '͖' => 220, '͗' => 230, '͘' => 232, '͙' => 220, '͚' => 220, '͛' => 230, '͜' => 233, '͝' => 234, '͞' => 234, '͟' => 233, '͠' => 234, '͡' => 234, '͢' => 233, 'ͣ' => 230, 'ͤ' => 230, 'ͥ' => 230, 'ͦ' => 230, 'ͧ' => 230, 'ͨ' => 230, 'ͩ' => 230, 'ͪ' => 230, 'ͫ' => 230, 'ͬ' => 230, 'ͭ' => 230, 'ͮ' => 230, 'ͯ' => 230, '҃' => 230, '҄' => 230, '҅' => 230, '҆' => 230, '҇' => 230, '֑' => 220, '֒' => 230, '֓' => 230, '֔' => 230, '֕' => 230, '֖' => 220, '֗' => 230, '֘' => 230, '֙' => 230, '֚' => 222, '֛' => 220, '֜' => 230, '֝' => 230, '֞' => 230, '֟' => 230, '֠' => 230, '֡' => 230, '֢' => 220, '֣' => 220, '֤' => 220, '֥' => 220, '֦' => 220, '֧' => 220, '֨' => 230, '֩' => 230, '֪' => 220, '֫' => 230, '֬' => 230, '֭' => 222, '֮' => 228, '֯' => 230, 'ְ' => 10, 'ֱ' => 11, 'ֲ' => 12, 'ֳ' => 13, 'ִ' => 14, 'ֵ' => 15, 'ֶ' => 16, 'ַ' => 17, 'ָ' => 18, 'ֹ' => 19, 'ֺ' => 19, 'ֻ' => 20, 'ּ' => 21, 'ֽ' => 22, 'ֿ' => 23, 'ׁ' => 24, 'ׂ' => 25, 'ׄ' => 230, 'ׅ' => 220, 'ׇ' => 18, 'ؐ' => 230, 'ؑ' => 230, 'ؒ' => 230, 'ؓ' => 230, 'ؔ' => 230, 'ؕ' => 230, 'ؖ' => 230, 'ؗ' => 230, 'ؘ' => 30, 'ؙ' => 31, 'ؚ' => 32, 'ً' => 27, 'ٌ' => 28, 'ٍ' => 29, 'َ' => 30, 'ُ' => 31, 'ِ' => 32, 'ّ' => 33, 'ْ' => 34, 'ٓ' => 230, 'ٔ' => 230, 'ٕ' => 220, 'ٖ' => 220, 'ٗ' => 230, '٘' => 230, 'ٙ' => 230, 'ٚ' => 230, 'ٛ' => 230, 'ٜ' => 220, 'ٝ' => 230, 'ٞ' => 230, 'ٟ' => 220, 'ٰ' => 35, 'ۖ' => 230, 'ۗ' => 230, 'ۘ' => 230, 'ۙ' => 230, 'ۚ' => 230, 'ۛ' => 230, 'ۜ' => 230, '۟' => 230, '۠' => 230, 'ۡ' => 230, 'ۢ' => 230, 'ۣ' => 220, 'ۤ' => 230, 'ۧ' => 230, 'ۨ' => 230, '۪' => 220, '۫' => 230, '۬' => 230, 'ۭ' => 220, 'ܑ' => 36, 'ܰ' => 230, 'ܱ' => 220, 'ܲ' => 230, 'ܳ' => 230, 'ܴ' => 220, 'ܵ' => 230, 'ܶ' => 230, 'ܷ' => 220, 'ܸ' => 220, 'ܹ' => 220, 'ܺ' => 230, 'ܻ' => 220, 'ܼ' => 220, 'ܽ' => 230, 'ܾ' => 220, 'ܿ' => 230, '݀' => 230, '݁' => 230, '݂' => 220, '݃' => 230, '݄' => 220, '݅' => 230, '݆' => 220, '݇' => 230, '݈' => 220, '݉' => 230, '݊' => 230, '߫' => 230, '߬' => 230, '߭' => 230, '߮' => 230, '߯' => 230, '߰' => 230, '߱' => 230, '߲' => 220, '߳' => 230, '߽' => 220, 'ࠖ' => 230, 'ࠗ' => 230, '࠘' => 230, '࠙' => 230, 'ࠛ' => 230, 'ࠜ' => 230, 'ࠝ' => 230, 'ࠞ' => 230, 'ࠟ' => 230, 'ࠠ' => 230, 'ࠡ' => 230, 'ࠢ' => 230, 'ࠣ' => 230, 'ࠥ' => 230, 'ࠦ' => 230, 'ࠧ' => 230, 'ࠩ' => 230, 'ࠪ' => 230, 'ࠫ' => 230, 'ࠬ' => 230, '࠭' => 230, '࡙' => 220, '࡚' => 220, '࡛' => 220, '࣓' => 220, 'ࣔ' => 230, 'ࣕ' => 230, 'ࣖ' => 230, 'ࣗ' => 230, 'ࣘ' => 230, 'ࣙ' => 230, 'ࣚ' => 230, 'ࣛ' => 230, 'ࣜ' => 230, 'ࣝ' => 230, 'ࣞ' => 230, 'ࣟ' => 230, '࣠' => 230, '࣡' => 230, 'ࣣ' => 220, 'ࣤ' => 230, 'ࣥ' => 230, 'ࣦ' => 220, 'ࣧ' => 230, 'ࣨ' => 230, 'ࣩ' => 220, '࣪' => 230, '࣫' => 230, '࣬' => 230, '࣭' => 220, '࣮' => 220, '࣯' => 220, 'ࣰ' => 27, 'ࣱ' => 28, 'ࣲ' => 29, 'ࣳ' => 230, 'ࣴ' => 230, 'ࣵ' => 230, 'ࣶ' => 220, 'ࣷ' => 230, 'ࣸ' => 230, 'ࣹ' => 220, 'ࣺ' => 220, 'ࣻ' => 230, 'ࣼ' => 230, 'ࣽ' => 230, 'ࣾ' => 230, 'ࣿ' => 230, '़' => 7, '्' => 9, '॑' => 230, '॒' => 220, '॓' => 230, '॔' => 230, '়' => 7, '্' => 9, '৾' => 230, '਼' => 7, '੍' => 9, '઼' => 7, '્' => 9, '଼' => 7, '୍' => 9, '்' => 9, '్' => 9, 'ౕ' => 84, 'ౖ' => 91, '಼' => 7, '್' => 9, '഻' => 9, '഼' => 9, '്' => 9, '්' => 9, 'ุ' => 103, 'ู' => 103, 'ฺ' => 9, '่' => 107, '้' => 107, '๊' => 107, '๋' => 107, 'ຸ' => 118, 'ູ' => 118, '຺' => 9, '່' => 122, '້' => 122, '໊' => 122, '໋' => 122, '༘' => 220, '༙' => 220, '༵' => 220, '༷' => 220, '༹' => 216, 'ཱ' => 129, 'ི' => 130, 'ུ' => 132, 'ེ' => 130, 'ཻ' => 130, 'ོ' => 130, 'ཽ' => 130, 'ྀ' => 130, 'ྂ' => 230, 'ྃ' => 230, '྄' => 9, '྆' => 230, '྇' => 230, '࿆' => 220, '့' => 7, '္' => 9, '်' => 9, 'ႍ' => 220, '፝' => 230, '፞' => 230, '፟' => 230, '᜔' => 9, '᜴' => 9, '្' => 9, '៝' => 230, 'ᢩ' => 228, '᤹' => 222, '᤺' => 230, '᤻' => 220, 'ᨗ' => 230, 'ᨘ' => 220, '᩠' => 9, '᩵' => 230, '᩶' => 230, '᩷' => 230, '᩸' => 230, '᩹' => 230, '᩺' => 230, '᩻' => 230, '᩼' => 230, '᩿' => 220, '᪰' => 230, '᪱' => 230, '᪲' => 230, '᪳' => 230, '᪴' => 230, '᪵' => 220, '᪶' => 220, '᪷' => 220, '᪸' => 220, '᪹' => 220, '᪺' => 220, '᪻' => 230, '᪼' => 230, '᪽' => 220, 'ᪿ' => 220, 'ᫀ' => 220, '᬴' => 7, '᭄' => 9, '᭫' => 230, '᭬' => 220, '᭭' => 230, '᭮' => 230, '᭯' => 230, '᭰' => 230, '᭱' => 230, '᭲' => 230, '᭳' => 230, '᮪' => 9, '᮫' => 9, '᯦' => 7, '᯲' => 9, '᯳' => 9, '᰷' => 7, '᳐' => 230, '᳑' => 230, '᳒' => 230, '᳔' => 1, '᳕' => 220, '᳖' => 220, '᳗' => 220, '᳘' => 220, '᳙' => 220, '᳚' => 230, '᳛' => 230, '᳜' => 220, '᳝' => 220, '᳞' => 220, '᳟' => 220, '᳠' => 230, '᳢' => 1, '᳣' => 1, '᳤' => 1, '᳥' => 1, '᳦' => 1, '᳧' => 1, '᳨' => 1, '᳭' => 220, '᳴' => 230, '᳸' => 230, '᳹' => 230, '᷀' => 230, '᷁' => 230, '᷂' => 220, '᷃' => 230, '᷄' => 230, '᷅' => 230, '᷆' => 230, '᷇' => 230, '᷈' => 230, '᷉' => 230, '᷊' => 220, '᷋' => 230, '᷌' => 230, '᷍' => 234, '᷎' => 214, '᷏' => 220, '᷐' => 202, '᷑' => 230, '᷒' => 230, 'ᷓ' => 230, 'ᷔ' => 230, 'ᷕ' => 230, 'ᷖ' => 230, 'ᷗ' => 230, 'ᷘ' => 230, 'ᷙ' => 230, 'ᷚ' => 230, 'ᷛ' => 230, 'ᷜ' => 230, 'ᷝ' => 230, 'ᷞ' => 230, 'ᷟ' => 230, 'ᷠ' => 230, 'ᷡ' => 230, 'ᷢ' => 230, 'ᷣ' => 230, 'ᷤ' => 230, 'ᷥ' => 230, 'ᷦ' => 230, 'ᷧ' => 230, 'ᷨ' => 230, 'ᷩ' => 230, 'ᷪ' => 230, 'ᷫ' => 230, 'ᷬ' => 230, 'ᷭ' => 230, 'ᷮ' => 230, 'ᷯ' => 230, 'ᷰ' => 230, 'ᷱ' => 230, 'ᷲ' => 230, 'ᷳ' => 230, 'ᷴ' => 230, '᷵' => 230, '᷶' => 232, '᷷' => 228, '᷸' => 228, '᷹' => 220, '᷻' => 230, '᷼' => 233, '᷽' => 220, '᷾' => 230, '᷿' => 220, '⃐' => 230, '⃑' => 230, '⃒' => 1, '⃓' => 1, '⃔' => 230, '⃕' => 230, '⃖' => 230, '⃗' => 230, '⃘' => 1, '⃙' => 1, '⃚' => 1, '⃛' => 230, '⃜' => 230, '⃡' => 230, '⃥' => 1, '⃦' => 1, '⃧' => 230, '⃨' => 220, '⃩' => 230, '⃪' => 1, '⃫' => 1, '⃬' => 220, '⃭' => 220, '⃮' => 220, '⃯' => 220, '⃰' => 230, '⳯' => 230, '⳰' => 230, '⳱' => 230, '⵿' => 9, 'ⷠ' => 230, 'ⷡ' => 230, 'ⷢ' => 230, 'ⷣ' => 230, 'ⷤ' => 230, 'ⷥ' => 230, 'ⷦ' => 230, 'ⷧ' => 230, 'ⷨ' => 230, 'ⷩ' => 230, 'ⷪ' => 230, 'ⷫ' => 230, 'ⷬ' => 230, 'ⷭ' => 230, 'ⷮ' => 230, 'ⷯ' => 230, 'ⷰ' => 230, 'ⷱ' => 230, 'ⷲ' => 230, 'ⷳ' => 230, 'ⷴ' => 230, 'ⷵ' => 230, 'ⷶ' => 230, 'ⷷ' => 230, 'ⷸ' => 230, 'ⷹ' => 230, 'ⷺ' => 230, 'ⷻ' => 230, 'ⷼ' => 230, 'ⷽ' => 230, 'ⷾ' => 230, 'ⷿ' => 230, '〪' => 218, '〫' => 228, '〬' => 232, '〭' => 222, '〮' => 224, '〯' => 224, '゙' => 8, '゚' => 8, '꙯' => 230, 'ꙴ' => 230, 'ꙵ' => 230, 'ꙶ' => 230, 'ꙷ' => 230, 'ꙸ' => 230, 'ꙹ' => 230, 'ꙺ' => 230, 'ꙻ' => 230, '꙼' => 230, '꙽' => 230, 'ꚞ' => 230, 'ꚟ' => 230, '꛰' => 230, '꛱' => 230, '꠆' => 9, '꠬' => 9, '꣄' => 9, '꣠' => 230, '꣡' => 230, '꣢' => 230, '꣣' => 230, '꣤' => 230, '꣥' => 230, '꣦' => 230, '꣧' => 230, '꣨' => 230, '꣩' => 230, '꣪' => 230, '꣫' => 230, '꣬' => 230, '꣭' => 230, '꣮' => 230, '꣯' => 230, '꣰' => 230, '꣱' => 230, '꤫' => 220, '꤬' => 220, '꤭' => 220, '꥓' => 9, '꦳' => 7, '꧀' => 9, 'ꪰ' => 230, 'ꪲ' => 230, 'ꪳ' => 230, 'ꪴ' => 220, 'ꪷ' => 230, 'ꪸ' => 230, 'ꪾ' => 230, '꪿' => 230, '꫁' => 230, '꫶' => 9, '꯭' => 9, 'ﬞ' => 26, '︠' => 230, '︡' => 230, '︢' => 230, '︣' => 230, '︤' => 230, '︥' => 230, '︦' => 230, '︧' => 220, '︨' => 220, '︩' => 220, '︪' => 220, '︫' => 220, '︬' => 220, '︭' => 220, '︮' => 230, '︯' => 230, '𐇽' => 220, '𐋠' => 220, '𐍶' => 230, '𐍷' => 230, '𐍸' => 230, '𐍹' => 230, '𐍺' => 230, '𐨍' => 220, '𐨏' => 230, '𐨸' => 230, '𐨹' => 1, '𐨺' => 220, '𐨿' => 9, '𐫥' => 230, '𐫦' => 220, '𐴤' => 230, '𐴥' => 230, '𐴦' => 230, '𐴧' => 230, '𐺫' => 230, '𐺬' => 230, '𐽆' => 220, '𐽇' => 220, '𐽈' => 230, '𐽉' => 230, '𐽊' => 230, '𐽋' => 220, '𐽌' => 230, '𐽍' => 220, '𐽎' => 220, '𐽏' => 220, '𐽐' => 220, '𑁆' => 9, '𑁿' => 9, '𑂹' => 9, '𑂺' => 7, '𑄀' => 230, '𑄁' => 230, '𑄂' => 230, '𑄳' => 9, '𑄴' => 9, '𑅳' => 7, '𑇀' => 9, '𑇊' => 7, '𑈵' => 9, '𑈶' => 7, '𑋩' => 7, '𑋪' => 9, '𑌻' => 7, '𑌼' => 7, '𑍍' => 9, '𑍦' => 230, '𑍧' => 230, '𑍨' => 230, '𑍩' => 230, '𑍪' => 230, '𑍫' => 230, '𑍬' => 230, '𑍰' => 230, '𑍱' => 230, '𑍲' => 230, '𑍳' => 230, '𑍴' => 230, '𑑂' => 9, '𑑆' => 7, '𑑞' => 230, '𑓂' => 9, '𑓃' => 7, '𑖿' => 9, '𑗀' => 7, '𑘿' => 9, '𑚶' => 9, '𑚷' => 7, '𑜫' => 9, '𑠹' => 9, '𑠺' => 7, '𑤽' => 9, '𑤾' => 9, '𑥃' => 7, '𑧠' => 9, '𑨴' => 9, '𑩇' => 9, '𑪙' => 9, '𑰿' => 9, '𑵂' => 7, '𑵄' => 9, '𑵅' => 9, '𑶗' => 9, '𖫰' => 1, '𖫱' => 1, '𖫲' => 1, '𖫳' => 1, '𖫴' => 1, '𖬰' => 230, '𖬱' => 230, '𖬲' => 230, '𖬳' => 230, '𖬴' => 230, '𖬵' => 230, '𖬶' => 230, '𖿰' => 6, '𖿱' => 6, '𛲞' => 1, '𝅥' => 216, '𝅦' => 216, '𝅧' => 1, '𝅨' => 1, '𝅩' => 1, '𝅭' => 226, '𝅮' => 216, '𝅯' => 216, '𝅰' => 216, '𝅱' => 216, '𝅲' => 216, '𝅻' => 220, '𝅼' => 220, '𝅽' => 220, '𝅾' => 220, '𝅿' => 220, '𝆀' => 220, '𝆁' => 220, '𝆂' => 220, '𝆅' => 230, '𝆆' => 230, '𝆇' => 230, '𝆈' => 230, '𝆉' => 230, '𝆊' => 220, '𝆋' => 220, '𝆪' => 230, '𝆫' => 230, '𝆬' => 230, '𝆭' => 230, '𝉂' => 230, '𝉃' => 230, '𝉄' => 230, '𞀀' => 230, '𞀁' => 230, '𞀂' => 230, '𞀃' => 230, '𞀄' => 230, '𞀅' => 230, '𞀆' => 230, '𞀈' => 230, '𞀉' => 230, '𞀊' => 230, '𞀋' => 230, '𞀌' => 230, '𞀍' => 230, '𞀎' => 230, '𞀏' => 230, '𞀐' => 230, '𞀑' => 230, '𞀒' => 230, '𞀓' => 230, '𞀔' => 230, '𞀕' => 230, '𞀖' => 230, '𞀗' => 230, '𞀘' => 230, '𞀛' => 230, '𞀜' => 230, '𞀝' => 230, '𞀞' => 230, '𞀟' => 230, '𞀠' => 230, '𞀡' => 230, '𞀣' => 230, '𞀤' => 230, '𞀦' => 230, '𞀧' => 230, '𞀨' => 230, '𞀩' => 230, '𞀪' => 230, '𞄰' => 230, '𞄱' => 230, '𞄲' => 230, '𞄳' => 230, '𞄴' => 230, '𞄵' => 230, '𞄶' => 230, '𞋬' => 230, '𞋭' => 230, '𞋮' => 230, '𞋯' => 230, '𞣐' => 220, '𞣑' => 220, '𞣒' => 220, '𞣓' => 220, '𞣔' => 220, '𞣕' => 220, '𞣖' => 220, '𞥄' => 230, '𞥅' => 230, '𞥆' => 230, '𞥇' => 230, '𞥈' => 230, '𞥉' => 230, '𞥊' => 7, ); ' ', '¨' => ' ̈', 'ª' => 'a', '¯' => ' ̄', '²' => '2', '³' => '3', '´' => ' ́', 'µ' => 'μ', '¸' => ' ̧', '¹' => '1', 'º' => 'o', '¼' => '1⁄4', '½' => '1⁄2', '¾' => '3⁄4', 'IJ' => 'IJ', 'ij' => 'ij', 'Ŀ' => 'L·', 'ŀ' => 'l·', 'ʼn' => 'ʼn', 'ſ' => 's', 'DŽ' => 'DŽ', 'Dž' => 'Dž', 'dž' => 'dž', 'LJ' => 'LJ', 'Lj' => 'Lj', 'lj' => 'lj', 'NJ' => 'NJ', 'Nj' => 'Nj', 'nj' => 'nj', 'DZ' => 'DZ', 'Dz' => 'Dz', 'dz' => 'dz', 'ʰ' => 'h', 'ʱ' => 'ɦ', 'ʲ' => 'j', 'ʳ' => 'r', 'ʴ' => 'ɹ', 'ʵ' => 'ɻ', 'ʶ' => 'ʁ', 'ʷ' => 'w', 'ʸ' => 'y', '˘' => ' ̆', '˙' => ' ̇', '˚' => ' ̊', '˛' => ' ̨', '˜' => ' ̃', '˝' => ' ̋', 'ˠ' => 'ɣ', 'ˡ' => 'l', 'ˢ' => 's', 'ˣ' => 'x', 'ˤ' => 'ʕ', 'ͺ' => ' ͅ', '΄' => ' ́', '΅' => ' ̈́', 'ϐ' => 'β', 'ϑ' => 'θ', 'ϒ' => 'Υ', 'ϓ' => 'Ύ', 'ϔ' => 'Ϋ', 'ϕ' => 'φ', 'ϖ' => 'π', 'ϰ' => 'κ', 'ϱ' => 'ρ', 'ϲ' => 'ς', 'ϴ' => 'Θ', 'ϵ' => 'ε', 'Ϲ' => 'Σ', 'և' => 'եւ', 'ٵ' => 'اٴ', 'ٶ' => 'وٴ', 'ٷ' => 'ۇٴ', 'ٸ' => 'يٴ', 'ำ' => 'ํา', 'ຳ' => 'ໍາ', 'ໜ' => 'ຫນ', 'ໝ' => 'ຫມ', '༌' => '་', 'ཷ' => 'ྲཱྀ', 'ཹ' => 'ླཱྀ', 'ჼ' => 'ნ', 'ᴬ' => 'A', 'ᴭ' => 'Æ', 'ᴮ' => 'B', 'ᴰ' => 'D', 'ᴱ' => 'E', 'ᴲ' => 'Ǝ', 'ᴳ' => 'G', 'ᴴ' => 'H', 'ᴵ' => 'I', 'ᴶ' => 'J', 'ᴷ' => 'K', 'ᴸ' => 'L', 'ᴹ' => 'M', 'ᴺ' => 'N', 'ᴼ' => 'O', 'ᴽ' => 'Ȣ', 'ᴾ' => 'P', 'ᴿ' => 'R', 'ᵀ' => 'T', 'ᵁ' => 'U', 'ᵂ' => 'W', 'ᵃ' => 'a', 'ᵄ' => 'ɐ', 'ᵅ' => 'ɑ', 'ᵆ' => 'ᴂ', 'ᵇ' => 'b', 'ᵈ' => 'd', 'ᵉ' => 'e', 'ᵊ' => 'ə', 'ᵋ' => 'ɛ', 'ᵌ' => 'ɜ', 'ᵍ' => 'g', 'ᵏ' => 'k', 'ᵐ' => 'm', 'ᵑ' => 'ŋ', 'ᵒ' => 'o', 'ᵓ' => 'ɔ', 'ᵔ' => 'ᴖ', 'ᵕ' => 'ᴗ', 'ᵖ' => 'p', 'ᵗ' => 't', 'ᵘ' => 'u', 'ᵙ' => 'ᴝ', 'ᵚ' => 'ɯ', 'ᵛ' => 'v', 'ᵜ' => 'ᴥ', 'ᵝ' => 'β', 'ᵞ' => 'γ', 'ᵟ' => 'δ', 'ᵠ' => 'φ', 'ᵡ' => 'χ', 'ᵢ' => 'i', 'ᵣ' => 'r', 'ᵤ' => 'u', 'ᵥ' => 'v', 'ᵦ' => 'β', 'ᵧ' => 'γ', 'ᵨ' => 'ρ', 'ᵩ' => 'φ', 'ᵪ' => 'χ', 'ᵸ' => 'н', 'ᶛ' => 'ɒ', 'ᶜ' => 'c', 'ᶝ' => 'ɕ', 'ᶞ' => 'ð', 'ᶟ' => 'ɜ', 'ᶠ' => 'f', 'ᶡ' => 'ɟ', 'ᶢ' => 'ɡ', 'ᶣ' => 'ɥ', 'ᶤ' => 'ɨ', 'ᶥ' => 'ɩ', 'ᶦ' => 'ɪ', 'ᶧ' => 'ᵻ', 'ᶨ' => 'ʝ', 'ᶩ' => 'ɭ', 'ᶪ' => 'ᶅ', 'ᶫ' => 'ʟ', 'ᶬ' => 'ɱ', 'ᶭ' => 'ɰ', 'ᶮ' => 'ɲ', 'ᶯ' => 'ɳ', 'ᶰ' => 'ɴ', 'ᶱ' => 'ɵ', 'ᶲ' => 'ɸ', 'ᶳ' => 'ʂ', 'ᶴ' => 'ʃ', 'ᶵ' => 'ƫ', 'ᶶ' => 'ʉ', 'ᶷ' => 'ʊ', 'ᶸ' => 'ᴜ', 'ᶹ' => 'ʋ', 'ᶺ' => 'ʌ', 'ᶻ' => 'z', 'ᶼ' => 'ʐ', 'ᶽ' => 'ʑ', 'ᶾ' => 'ʒ', 'ᶿ' => 'θ', 'ẚ' => 'aʾ', 'ẛ' => 'ṡ', '᾽' => ' ̓', '᾿' => ' ̓', '῀' => ' ͂', '῁' => ' ̈͂', '῍' => ' ̓̀', '῎' => ' ̓́', '῏' => ' ̓͂', '῝' => ' ̔̀', '῞' => ' ̔́', '῟' => ' ̔͂', '῭' => ' ̈̀', '΅' => ' ̈́', '´' => ' ́', '῾' => ' ̔', ' ' => ' ', ' ' => ' ', ' ' => ' ', ' ' => ' ', ' ' => ' ', ' ' => ' ', ' ' => ' ', ' ' => ' ', ' ' => ' ', ' ' => ' ', ' ' => ' ', '‑' => '‐', '‗' => ' ̳', '․' => '.', '‥' => '..', '…' => '...', ' ' => ' ', '″' => '′′', '‴' => '′′′', '‶' => '‵‵', '‷' => '‵‵‵', '‼' => '!!', '‾' => ' ̅', '⁇' => '??', '⁈' => '?!', '⁉' => '!?', '⁗' => '′′′′', ' ' => ' ', '⁰' => '0', 'ⁱ' => 'i', '⁴' => '4', '⁵' => '5', '⁶' => '6', '⁷' => '7', '⁸' => '8', '⁹' => '9', '⁺' => '+', '⁻' => '−', '⁼' => '=', '⁽' => '(', '⁾' => ')', 'ⁿ' => 'n', '₀' => '0', '₁' => '1', '₂' => '2', '₃' => '3', '₄' => '4', '₅' => '5', '₆' => '6', '₇' => '7', '₈' => '8', '₉' => '9', '₊' => '+', '₋' => '−', '₌' => '=', '₍' => '(', '₎' => ')', 'ₐ' => 'a', 'ₑ' => 'e', 'ₒ' => 'o', 'ₓ' => 'x', 'ₔ' => 'ə', 'ₕ' => 'h', 'ₖ' => 'k', 'ₗ' => 'l', 'ₘ' => 'm', 'ₙ' => 'n', 'ₚ' => 'p', 'ₛ' => 's', 'ₜ' => 't', '₨' => 'Rs', '℀' => 'a/c', '℁' => 'a/s', 'ℂ' => 'C', '℃' => '°C', '℅' => 'c/o', '℆' => 'c/u', 'ℇ' => 'Ɛ', '℉' => '°F', 'ℊ' => 'g', 'ℋ' => 'H', 'ℌ' => 'H', 'ℍ' => 'H', 'ℎ' => 'h', 'ℏ' => 'ħ', 'ℐ' => 'I', 'ℑ' => 'I', 'ℒ' => 'L', 'ℓ' => 'l', 'ℕ' => 'N', '№' => 'No', 'ℙ' => 'P', 'ℚ' => 'Q', 'ℛ' => 'R', 'ℜ' => 'R', 'ℝ' => 'R', '℠' => 'SM', '℡' => 'TEL', '™' => 'TM', 'ℤ' => 'Z', 'ℨ' => 'Z', 'ℬ' => 'B', 'ℭ' => 'C', 'ℯ' => 'e', 'ℰ' => 'E', 'ℱ' => 'F', 'ℳ' => 'M', 'ℴ' => 'o', 'ℵ' => 'א', 'ℶ' => 'ב', 'ℷ' => 'ג', 'ℸ' => 'ד', 'ℹ' => 'i', '℻' => 'FAX', 'ℼ' => 'π', 'ℽ' => 'γ', 'ℾ' => 'Γ', 'ℿ' => 'Π', '⅀' => '∑', 'ⅅ' => 'D', 'ⅆ' => 'd', 'ⅇ' => 'e', 'ⅈ' => 'i', 'ⅉ' => 'j', '⅐' => '1⁄7', '⅑' => '1⁄9', '⅒' => '1⁄10', '⅓' => '1⁄3', '⅔' => '2⁄3', '⅕' => '1⁄5', '⅖' => '2⁄5', '⅗' => '3⁄5', '⅘' => '4⁄5', '⅙' => '1⁄6', '⅚' => '5⁄6', '⅛' => '1⁄8', '⅜' => '3⁄8', '⅝' => '5⁄8', '⅞' => '7⁄8', '⅟' => '1⁄', 'Ⅰ' => 'I', 'Ⅱ' => 'II', 'Ⅲ' => 'III', 'Ⅳ' => 'IV', 'Ⅴ' => 'V', 'Ⅵ' => 'VI', 'Ⅶ' => 'VII', 'Ⅷ' => 'VIII', 'Ⅸ' => 'IX', 'Ⅹ' => 'X', 'Ⅺ' => 'XI', 'Ⅻ' => 'XII', 'Ⅼ' => 'L', 'Ⅽ' => 'C', 'Ⅾ' => 'D', 'Ⅿ' => 'M', 'ⅰ' => 'i', 'ⅱ' => 'ii', 'ⅲ' => 'iii', 'ⅳ' => 'iv', 'ⅴ' => 'v', 'ⅵ' => 'vi', 'ⅶ' => 'vii', 'ⅷ' => 'viii', 'ⅸ' => 'ix', 'ⅹ' => 'x', 'ⅺ' => 'xi', 'ⅻ' => 'xii', 'ⅼ' => 'l', 'ⅽ' => 'c', 'ⅾ' => 'd', 'ⅿ' => 'm', '↉' => '0⁄3', '∬' => '∫∫', '∭' => '∫∫∫', '∯' => '∮∮', '∰' => '∮∮∮', '①' => '1', '②' => '2', '③' => '3', '④' => '4', '⑤' => '5', '⑥' => '6', '⑦' => '7', '⑧' => '8', '⑨' => '9', '⑩' => '10', '⑪' => '11', '⑫' => '12', '⑬' => '13', '⑭' => '14', '⑮' => '15', '⑯' => '16', '⑰' => '17', '⑱' => '18', '⑲' => '19', '⑳' => '20', '⑴' => '(1)', '⑵' => '(2)', '⑶' => '(3)', '⑷' => '(4)', '⑸' => '(5)', '⑹' => '(6)', '⑺' => '(7)', '⑻' => '(8)', '⑼' => '(9)', '⑽' => '(10)', '⑾' => '(11)', '⑿' => '(12)', '⒀' => '(13)', '⒁' => '(14)', '⒂' => '(15)', '⒃' => '(16)', '⒄' => '(17)', '⒅' => '(18)', '⒆' => '(19)', '⒇' => '(20)', '⒈' => '1.', '⒉' => '2.', '⒊' => '3.', '⒋' => '4.', '⒌' => '5.', '⒍' => '6.', '⒎' => '7.', '⒏' => '8.', '⒐' => '9.', '⒑' => '10.', '⒒' => '11.', '⒓' => '12.', '⒔' => '13.', '⒕' => '14.', '⒖' => '15.', '⒗' => '16.', '⒘' => '17.', '⒙' => '18.', '⒚' => '19.', '⒛' => '20.', '⒜' => '(a)', '⒝' => '(b)', '⒞' => '(c)', '⒟' => '(d)', '⒠' => '(e)', '⒡' => '(f)', '⒢' => '(g)', '⒣' => '(h)', '⒤' => '(i)', '⒥' => '(j)', '⒦' => '(k)', '⒧' => '(l)', '⒨' => '(m)', '⒩' => '(n)', '⒪' => '(o)', '⒫' => '(p)', '⒬' => '(q)', '⒭' => '(r)', '⒮' => '(s)', '⒯' => '(t)', '⒰' => '(u)', '⒱' => '(v)', '⒲' => '(w)', '⒳' => '(x)', '⒴' => '(y)', '⒵' => '(z)', 'Ⓐ' => 'A', 'Ⓑ' => 'B', 'Ⓒ' => 'C', 'Ⓓ' => 'D', 'Ⓔ' => 'E', 'Ⓕ' => 'F', 'Ⓖ' => 'G', 'Ⓗ' => 'H', 'Ⓘ' => 'I', 'Ⓙ' => 'J', 'Ⓚ' => 'K', 'Ⓛ' => 'L', 'Ⓜ' => 'M', 'Ⓝ' => 'N', 'Ⓞ' => 'O', 'Ⓟ' => 'P', 'Ⓠ' => 'Q', 'Ⓡ' => 'R', 'Ⓢ' => 'S', 'Ⓣ' => 'T', 'Ⓤ' => 'U', 'Ⓥ' => 'V', 'Ⓦ' => 'W', 'Ⓧ' => 'X', 'Ⓨ' => 'Y', 'Ⓩ' => 'Z', 'ⓐ' => 'a', 'ⓑ' => 'b', 'ⓒ' => 'c', 'ⓓ' => 'd', 'ⓔ' => 'e', 'ⓕ' => 'f', 'ⓖ' => 'g', 'ⓗ' => 'h', 'ⓘ' => 'i', 'ⓙ' => 'j', 'ⓚ' => 'k', 'ⓛ' => 'l', 'ⓜ' => 'm', 'ⓝ' => 'n', 'ⓞ' => 'o', 'ⓟ' => 'p', 'ⓠ' => 'q', 'ⓡ' => 'r', 'ⓢ' => 's', 'ⓣ' => 't', 'ⓤ' => 'u', 'ⓥ' => 'v', 'ⓦ' => 'w', 'ⓧ' => 'x', 'ⓨ' => 'y', 'ⓩ' => 'z', '⓪' => '0', '⨌' => '∫∫∫∫', '⩴' => '::=', '⩵' => '==', '⩶' => '===', 'ⱼ' => 'j', 'ⱽ' => 'V', 'ⵯ' => 'ⵡ', '⺟' => '母', '⻳' => '龟', '⼀' => '一', '⼁' => '丨', '⼂' => '丶', '⼃' => '丿', '⼄' => '乙', '⼅' => '亅', '⼆' => '二', '⼇' => '亠', '⼈' => '人', '⼉' => '儿', '⼊' => '入', '⼋' => '八', '⼌' => '冂', '⼍' => '冖', '⼎' => '冫', '⼏' => '几', '⼐' => '凵', '⼑' => '刀', '⼒' => '力', '⼓' => '勹', '⼔' => '匕', '⼕' => '匚', '⼖' => '匸', '⼗' => '十', '⼘' => '卜', '⼙' => '卩', '⼚' => '厂', '⼛' => '厶', '⼜' => '又', '⼝' => '口', '⼞' => '囗', '⼟' => '土', '⼠' => '士', '⼡' => '夂', '⼢' => '夊', '⼣' => '夕', '⼤' => '大', '⼥' => '女', '⼦' => '子', '⼧' => '宀', '⼨' => '寸', '⼩' => '小', '⼪' => '尢', '⼫' => '尸', '⼬' => '屮', '⼭' => '山', '⼮' => '巛', '⼯' => '工', '⼰' => '己', '⼱' => '巾', '⼲' => '干', '⼳' => '幺', '⼴' => '广', '⼵' => '廴', '⼶' => '廾', '⼷' => '弋', '⼸' => '弓', '⼹' => '彐', '⼺' => '彡', '⼻' => '彳', '⼼' => '心', '⼽' => '戈', '⼾' => '戶', '⼿' => '手', '⽀' => '支', '⽁' => '攴', '⽂' => '文', '⽃' => '斗', '⽄' => '斤', '⽅' => '方', '⽆' => '无', '⽇' => '日', '⽈' => '曰', '⽉' => '月', '⽊' => '木', '⽋' => '欠', '⽌' => '止', '⽍' => '歹', '⽎' => '殳', '⽏' => '毋', '⽐' => '比', '⽑' => '毛', '⽒' => '氏', '⽓' => '气', '⽔' => '水', '⽕' => '火', '⽖' => '爪', '⽗' => '父', '⽘' => '爻', '⽙' => '爿', '⽚' => '片', '⽛' => '牙', '⽜' => '牛', '⽝' => '犬', '⽞' => '玄', '⽟' => '玉', '⽠' => '瓜', '⽡' => '瓦', '⽢' => '甘', '⽣' => '生', '⽤' => '用', '⽥' => '田', '⽦' => '疋', '⽧' => '疒', '⽨' => '癶', '⽩' => '白', '⽪' => '皮', '⽫' => '皿', '⽬' => '目', '⽭' => '矛', '⽮' => '矢', '⽯' => '石', '⽰' => '示', '⽱' => '禸', '⽲' => '禾', '⽳' => '穴', '⽴' => '立', '⽵' => '竹', '⽶' => '米', '⽷' => '糸', '⽸' => '缶', '⽹' => '网', '⽺' => '羊', '⽻' => '羽', '⽼' => '老', '⽽' => '而', '⽾' => '耒', '⽿' => '耳', '⾀' => '聿', '⾁' => '肉', '⾂' => '臣', '⾃' => '自', '⾄' => '至', '⾅' => '臼', '⾆' => '舌', '⾇' => '舛', '⾈' => '舟', '⾉' => '艮', '⾊' => '色', '⾋' => '艸', '⾌' => '虍', '⾍' => '虫', '⾎' => '血', '⾏' => '行', '⾐' => '衣', '⾑' => '襾', '⾒' => '見', '⾓' => '角', '⾔' => '言', '⾕' => '谷', '⾖' => '豆', '⾗' => '豕', '⾘' => '豸', '⾙' => '貝', '⾚' => '赤', '⾛' => '走', '⾜' => '足', '⾝' => '身', '⾞' => '車', '⾟' => '辛', '⾠' => '辰', '⾡' => '辵', '⾢' => '邑', '⾣' => '酉', '⾤' => '釆', '⾥' => '里', '⾦' => '金', '⾧' => '長', '⾨' => '門', '⾩' => '阜', '⾪' => '隶', '⾫' => '隹', '⾬' => '雨', '⾭' => '靑', '⾮' => '非', '⾯' => '面', '⾰' => '革', '⾱' => '韋', '⾲' => '韭', '⾳' => '音', '⾴' => '頁', '⾵' => '風', '⾶' => '飛', '⾷' => '食', '⾸' => '首', '⾹' => '香', '⾺' => '馬', '⾻' => '骨', '⾼' => '高', '⾽' => '髟', '⾾' => '鬥', '⾿' => '鬯', '⿀' => '鬲', '⿁' => '鬼', '⿂' => '魚', '⿃' => '鳥', '⿄' => '鹵', '⿅' => '鹿', '⿆' => '麥', '⿇' => '麻', '⿈' => '黃', '⿉' => '黍', '⿊' => '黑', '⿋' => '黹', '⿌' => '黽', '⿍' => '鼎', '⿎' => '鼓', '⿏' => '鼠', '⿐' => '鼻', '⿑' => '齊', '⿒' => '齒', '⿓' => '龍', '⿔' => '龜', '⿕' => '龠', ' ' => ' ', '〶' => '〒', '〸' => '十', '〹' => '卄', '〺' => '卅', '゛' => ' ゙', '゜' => ' ゚', 'ゟ' => 'より', 'ヿ' => 'コト', 'ㄱ' => 'ᄀ', 'ㄲ' => 'ᄁ', 'ㄳ' => 'ᆪ', 'ㄴ' => 'ᄂ', 'ㄵ' => 'ᆬ', 'ㄶ' => 'ᆭ', 'ㄷ' => 'ᄃ', 'ㄸ' => 'ᄄ', 'ㄹ' => 'ᄅ', 'ㄺ' => 'ᆰ', 'ㄻ' => 'ᆱ', 'ㄼ' => 'ᆲ', 'ㄽ' => 'ᆳ', 'ㄾ' => 'ᆴ', 'ㄿ' => 'ᆵ', 'ㅀ' => 'ᄚ', 'ㅁ' => 'ᄆ', 'ㅂ' => 'ᄇ', 'ㅃ' => 'ᄈ', 'ㅄ' => 'ᄡ', 'ㅅ' => 'ᄉ', 'ㅆ' => 'ᄊ', 'ㅇ' => 'ᄋ', 'ㅈ' => 'ᄌ', 'ㅉ' => 'ᄍ', 'ㅊ' => 'ᄎ', 'ㅋ' => 'ᄏ', 'ㅌ' => 'ᄐ', 'ㅍ' => 'ᄑ', 'ㅎ' => 'ᄒ', 'ㅏ' => 'ᅡ', 'ㅐ' => 'ᅢ', 'ㅑ' => 'ᅣ', 'ㅒ' => 'ᅤ', 'ㅓ' => 'ᅥ', 'ㅔ' => 'ᅦ', 'ㅕ' => 'ᅧ', 'ㅖ' => 'ᅨ', 'ㅗ' => 'ᅩ', 'ㅘ' => 'ᅪ', 'ㅙ' => 'ᅫ', 'ㅚ' => 'ᅬ', 'ㅛ' => 'ᅭ', 'ㅜ' => 'ᅮ', 'ㅝ' => 'ᅯ', 'ㅞ' => 'ᅰ', 'ㅟ' => 'ᅱ', 'ㅠ' => 'ᅲ', 'ㅡ' => 'ᅳ', 'ㅢ' => 'ᅴ', 'ㅣ' => 'ᅵ', 'ㅤ' => 'ᅠ', 'ㅥ' => 'ᄔ', 'ㅦ' => 'ᄕ', 'ㅧ' => 'ᇇ', 'ㅨ' => 'ᇈ', 'ㅩ' => 'ᇌ', 'ㅪ' => 'ᇎ', 'ㅫ' => 'ᇓ', 'ㅬ' => 'ᇗ', 'ㅭ' => 'ᇙ', 'ㅮ' => 'ᄜ', 'ㅯ' => 'ᇝ', 'ㅰ' => 'ᇟ', 'ㅱ' => 'ᄝ', 'ㅲ' => 'ᄞ', 'ㅳ' => 'ᄠ', 'ㅴ' => 'ᄢ', 'ㅵ' => 'ᄣ', 'ㅶ' => 'ᄧ', 'ㅷ' => 'ᄩ', 'ㅸ' => 'ᄫ', 'ㅹ' => 'ᄬ', 'ㅺ' => 'ᄭ', 'ㅻ' => 'ᄮ', 'ㅼ' => 'ᄯ', 'ㅽ' => 'ᄲ', 'ㅾ' => 'ᄶ', 'ㅿ' => 'ᅀ', 'ㆀ' => 'ᅇ', 'ㆁ' => 'ᅌ', 'ㆂ' => 'ᇱ', 'ㆃ' => 'ᇲ', 'ㆄ' => 'ᅗ', 'ㆅ' => 'ᅘ', 'ㆆ' => 'ᅙ', 'ㆇ' => 'ᆄ', 'ㆈ' => 'ᆅ', 'ㆉ' => 'ᆈ', 'ㆊ' => 'ᆑ', 'ㆋ' => 'ᆒ', 'ㆌ' => 'ᆔ', 'ㆍ' => 'ᆞ', 'ㆎ' => 'ᆡ', '㆒' => '一', '㆓' => '二', '㆔' => '三', '㆕' => '四', '㆖' => '上', '㆗' => '中', '㆘' => '下', '㆙' => '甲', '㆚' => '乙', '㆛' => '丙', '㆜' => '丁', '㆝' => '天', '㆞' => '地', '㆟' => '人', '㈀' => '(ᄀ)', '㈁' => '(ᄂ)', '㈂' => '(ᄃ)', '㈃' => '(ᄅ)', '㈄' => '(ᄆ)', '㈅' => '(ᄇ)', '㈆' => '(ᄉ)', '㈇' => '(ᄋ)', '㈈' => '(ᄌ)', '㈉' => '(ᄎ)', '㈊' => '(ᄏ)', '㈋' => '(ᄐ)', '㈌' => '(ᄑ)', '㈍' => '(ᄒ)', '㈎' => '(가)', '㈏' => '(나)', '㈐' => '(다)', '㈑' => '(라)', '㈒' => '(마)', '㈓' => '(바)', '㈔' => '(사)', '㈕' => '(아)', '㈖' => '(자)', '㈗' => '(차)', '㈘' => '(카)', '㈙' => '(타)', '㈚' => '(파)', '㈛' => '(하)', '㈜' => '(주)', '㈝' => '(오전)', '㈞' => '(오후)', '㈠' => '(一)', '㈡' => '(二)', '㈢' => '(三)', '㈣' => '(四)', '㈤' => '(五)', '㈥' => '(六)', '㈦' => '(七)', '㈧' => '(八)', '㈨' => '(九)', '㈩' => '(十)', '㈪' => '(月)', '㈫' => '(火)', '㈬' => '(水)', '㈭' => '(木)', '㈮' => '(金)', '㈯' => '(土)', '㈰' => '(日)', '㈱' => '(株)', '㈲' => '(有)', '㈳' => '(社)', '㈴' => '(名)', '㈵' => '(特)', '㈶' => '(財)', '㈷' => '(祝)', '㈸' => '(労)', '㈹' => '(代)', '㈺' => '(呼)', '㈻' => '(学)', '㈼' => '(監)', '㈽' => '(企)', '㈾' => '(資)', '㈿' => '(協)', '㉀' => '(祭)', '㉁' => '(休)', '㉂' => '(自)', '㉃' => '(至)', '㉄' => '問', '㉅' => '幼', '㉆' => '文', '㉇' => '箏', '㉐' => 'PTE', '㉑' => '21', '㉒' => '22', '㉓' => '23', '㉔' => '24', '㉕' => '25', '㉖' => '26', '㉗' => '27', '㉘' => '28', '㉙' => '29', '㉚' => '30', '㉛' => '31', '㉜' => '32', '㉝' => '33', '㉞' => '34', '㉟' => '35', '㉠' => 'ᄀ', '㉡' => 'ᄂ', '㉢' => 'ᄃ', '㉣' => 'ᄅ', '㉤' => 'ᄆ', '㉥' => 'ᄇ', '㉦' => 'ᄉ', '㉧' => 'ᄋ', '㉨' => 'ᄌ', '㉩' => 'ᄎ', '㉪' => 'ᄏ', '㉫' => 'ᄐ', '㉬' => 'ᄑ', '㉭' => 'ᄒ', '㉮' => '가', '㉯' => '나', '㉰' => '다', '㉱' => '라', '㉲' => '마', '㉳' => '바', '㉴' => '사', '㉵' => '아', '㉶' => '자', '㉷' => '차', '㉸' => '카', '㉹' => '타', '㉺' => '파', '㉻' => '하', '㉼' => '참고', '㉽' => '주의', '㉾' => '우', '㊀' => '一', '㊁' => '二', '㊂' => '三', '㊃' => '四', '㊄' => '五', '㊅' => '六', '㊆' => '七', '㊇' => '八', '㊈' => '九', '㊉' => '十', '㊊' => '月', '㊋' => '火', '㊌' => '水', '㊍' => '木', '㊎' => '金', '㊏' => '土', '㊐' => '日', '㊑' => '株', '㊒' => '有', '㊓' => '社', '㊔' => '名', '㊕' => '特', '㊖' => '財', '㊗' => '祝', '㊘' => '労', '㊙' => '秘', '㊚' => '男', '㊛' => '女', '㊜' => '適', '㊝' => '優', '㊞' => '印', '㊟' => '注', '㊠' => '項', '㊡' => '休', '㊢' => '写', '㊣' => '正', '㊤' => '上', '㊥' => '中', '㊦' => '下', '㊧' => '左', '㊨' => '右', '㊩' => '医', '㊪' => '宗', '㊫' => '学', '㊬' => '監', '㊭' => '企', '㊮' => '資', '㊯' => '協', '㊰' => '夜', '㊱' => '36', '㊲' => '37', '㊳' => '38', '㊴' => '39', '㊵' => '40', '㊶' => '41', '㊷' => '42', '㊸' => '43', '㊹' => '44', '㊺' => '45', '㊻' => '46', '㊼' => '47', '㊽' => '48', '㊾' => '49', '㊿' => '50', '㋀' => '1月', '㋁' => '2月', '㋂' => '3月', '㋃' => '4月', '㋄' => '5月', '㋅' => '6月', '㋆' => '7月', '㋇' => '8月', '㋈' => '9月', '㋉' => '10月', '㋊' => '11月', '㋋' => '12月', '㋌' => 'Hg', '㋍' => 'erg', '㋎' => 'eV', '㋏' => 'LTD', '㋐' => 'ア', '㋑' => 'イ', '㋒' => 'ウ', '㋓' => 'エ', '㋔' => 'オ', '㋕' => 'カ', '㋖' => 'キ', '㋗' => 'ク', '㋘' => 'ケ', '㋙' => 'コ', '㋚' => 'サ', '㋛' => 'シ', '㋜' => 'ス', '㋝' => 'セ', '㋞' => 'ソ', '㋟' => 'タ', '㋠' => 'チ', '㋡' => 'ツ', '㋢' => 'テ', '㋣' => 'ト', '㋤' => 'ナ', '㋥' => 'ニ', '㋦' => 'ヌ', '㋧' => 'ネ', '㋨' => 'ノ', '㋩' => 'ハ', '㋪' => 'ヒ', '㋫' => 'フ', '㋬' => 'ヘ', '㋭' => 'ホ', '㋮' => 'マ', '㋯' => 'ミ', '㋰' => 'ム', '㋱' => 'メ', '㋲' => 'モ', '㋳' => 'ヤ', '㋴' => 'ユ', '㋵' => 'ヨ', '㋶' => 'ラ', '㋷' => 'リ', '㋸' => 'ル', '㋹' => 'レ', '㋺' => 'ロ', '㋻' => 'ワ', '㋼' => 'ヰ', '㋽' => 'ヱ', '㋾' => 'ヲ', '㋿' => '令和', '㌀' => 'アパート', '㌁' => 'アルファ', '㌂' => 'アンペア', '㌃' => 'アール', '㌄' => 'イニング', '㌅' => 'インチ', '㌆' => 'ウォン', '㌇' => 'エスクード', '㌈' => 'エーカー', '㌉' => 'オンス', '㌊' => 'オーム', '㌋' => 'カイリ', '㌌' => 'カラット', '㌍' => 'カロリー', '㌎' => 'ガロン', '㌏' => 'ガンマ', '㌐' => 'ギガ', '㌑' => 'ギニー', '㌒' => 'キュリー', '㌓' => 'ギルダー', '㌔' => 'キロ', '㌕' => 'キログラム', '㌖' => 'キロメートル', '㌗' => 'キロワット', '㌘' => 'グラム', '㌙' => 'グラムトン', '㌚' => 'クルゼイロ', '㌛' => 'クローネ', '㌜' => 'ケース', '㌝' => 'コルナ', '㌞' => 'コーポ', '㌟' => 'サイクル', '㌠' => 'サンチーム', '㌡' => 'シリング', '㌢' => 'センチ', '㌣' => 'セント', '㌤' => 'ダース', '㌥' => 'デシ', '㌦' => 'ドル', '㌧' => 'トン', '㌨' => 'ナノ', '㌩' => 'ノット', '㌪' => 'ハイツ', '㌫' => 'パーセント', '㌬' => 'パーツ', '㌭' => 'バーレル', '㌮' => 'ピアストル', '㌯' => 'ピクル', '㌰' => 'ピコ', '㌱' => 'ビル', '㌲' => 'ファラッド', '㌳' => 'フィート', '㌴' => 'ブッシェル', '㌵' => 'フラン', '㌶' => 'ヘクタール', '㌷' => 'ペソ', '㌸' => 'ペニヒ', '㌹' => 'ヘルツ', '㌺' => 'ペンス', '㌻' => 'ページ', '㌼' => 'ベータ', '㌽' => 'ポイント', '㌾' => 'ボルト', '㌿' => 'ホン', '㍀' => 'ポンド', '㍁' => 'ホール', '㍂' => 'ホーン', '㍃' => 'マイクロ', '㍄' => 'マイル', '㍅' => 'マッハ', '㍆' => 'マルク', '㍇' => 'マンション', '㍈' => 'ミクロン', '㍉' => 'ミリ', '㍊' => 'ミリバール', '㍋' => 'メガ', '㍌' => 'メガトン', '㍍' => 'メートル', '㍎' => 'ヤード', '㍏' => 'ヤール', '㍐' => 'ユアン', '㍑' => 'リットル', '㍒' => 'リラ', '㍓' => 'ルピー', '㍔' => 'ルーブル', '㍕' => 'レム', '㍖' => 'レントゲン', '㍗' => 'ワット', '㍘' => '0点', '㍙' => '1点', '㍚' => '2点', '㍛' => '3点', '㍜' => '4点', '㍝' => '5点', '㍞' => '6点', '㍟' => '7点', '㍠' => '8点', '㍡' => '9点', '㍢' => '10点', '㍣' => '11点', '㍤' => '12点', '㍥' => '13点', '㍦' => '14点', '㍧' => '15点', '㍨' => '16点', '㍩' => '17点', '㍪' => '18点', '㍫' => '19点', '㍬' => '20点', '㍭' => '21点', '㍮' => '22点', '㍯' => '23点', '㍰' => '24点', '㍱' => 'hPa', '㍲' => 'da', '㍳' => 'AU', '㍴' => 'bar', '㍵' => 'oV', '㍶' => 'pc', '㍷' => 'dm', '㍸' => 'dm2', '㍹' => 'dm3', '㍺' => 'IU', '㍻' => '平成', '㍼' => '昭和', '㍽' => '大正', '㍾' => '明治', '㍿' => '株式会社', '㎀' => 'pA', '㎁' => 'nA', '㎂' => 'μA', '㎃' => 'mA', '㎄' => 'kA', '㎅' => 'KB', '㎆' => 'MB', '㎇' => 'GB', '㎈' => 'cal', '㎉' => 'kcal', '㎊' => 'pF', '㎋' => 'nF', '㎌' => 'μF', '㎍' => 'μg', '㎎' => 'mg', '㎏' => 'kg', '㎐' => 'Hz', '㎑' => 'kHz', '㎒' => 'MHz', '㎓' => 'GHz', '㎔' => 'THz', '㎕' => 'μl', '㎖' => 'ml', '㎗' => 'dl', '㎘' => 'kl', '㎙' => 'fm', '㎚' => 'nm', '㎛' => 'μm', '㎜' => 'mm', '㎝' => 'cm', '㎞' => 'km', '㎟' => 'mm2', '㎠' => 'cm2', '㎡' => 'm2', '㎢' => 'km2', '㎣' => 'mm3', '㎤' => 'cm3', '㎥' => 'm3', '㎦' => 'km3', '㎧' => 'm∕s', '㎨' => 'm∕s2', '㎩' => 'Pa', '㎪' => 'kPa', '㎫' => 'MPa', '㎬' => 'GPa', '㎭' => 'rad', '㎮' => 'rad∕s', '㎯' => 'rad∕s2', '㎰' => 'ps', '㎱' => 'ns', '㎲' => 'μs', '㎳' => 'ms', '㎴' => 'pV', '㎵' => 'nV', '㎶' => 'μV', '㎷' => 'mV', '㎸' => 'kV', '㎹' => 'MV', '㎺' => 'pW', '㎻' => 'nW', '㎼' => 'μW', '㎽' => 'mW', '㎾' => 'kW', '㎿' => 'MW', '㏀' => 'kΩ', '㏁' => 'MΩ', '㏂' => 'a.m.', '㏃' => 'Bq', '㏄' => 'cc', '㏅' => 'cd', '㏆' => 'C∕kg', '㏇' => 'Co.', '㏈' => 'dB', '㏉' => 'Gy', '㏊' => 'ha', '㏋' => 'HP', '㏌' => 'in', '㏍' => 'KK', '㏎' => 'KM', '㏏' => 'kt', '㏐' => 'lm', '㏑' => 'ln', '㏒' => 'log', '㏓' => 'lx', '㏔' => 'mb', '㏕' => 'mil', '㏖' => 'mol', '㏗' => 'PH', '㏘' => 'p.m.', '㏙' => 'PPM', '㏚' => 'PR', '㏛' => 'sr', '㏜' => 'Sv', '㏝' => 'Wb', '㏞' => 'V∕m', '㏟' => 'A∕m', '㏠' => '1日', '㏡' => '2日', '㏢' => '3日', '㏣' => '4日', '㏤' => '5日', '㏥' => '6日', '㏦' => '7日', '㏧' => '8日', '㏨' => '9日', '㏩' => '10日', '㏪' => '11日', '㏫' => '12日', '㏬' => '13日', '㏭' => '14日', '㏮' => '15日', '㏯' => '16日', '㏰' => '17日', '㏱' => '18日', '㏲' => '19日', '㏳' => '20日', '㏴' => '21日', '㏵' => '22日', '㏶' => '23日', '㏷' => '24日', '㏸' => '25日', '㏹' => '26日', '㏺' => '27日', '㏻' => '28日', '㏼' => '29日', '㏽' => '30日', '㏾' => '31日', '㏿' => 'gal', 'ꚜ' => 'ъ', 'ꚝ' => 'ь', 'ꝰ' => 'ꝯ', 'ꟸ' => 'Ħ', 'ꟹ' => 'œ', 'ꭜ' => 'ꜧ', 'ꭝ' => 'ꬷ', 'ꭞ' => 'ɫ', 'ꭟ' => 'ꭒ', 'ꭩ' => 'ʍ', 'ff' => 'ff', 'fi' => 'fi', 'fl' => 'fl', 'ffi' => 'ffi', 'ffl' => 'ffl', 'ſt' => 'st', 'st' => 'st', 'ﬓ' => 'մն', 'ﬔ' => 'մե', 'ﬕ' => 'մի', 'ﬖ' => 'վն', 'ﬗ' => 'մխ', 'ﬠ' => 'ע', 'ﬡ' => 'א', 'ﬢ' => 'ד', 'ﬣ' => 'ה', 'ﬤ' => 'כ', 'ﬥ' => 'ל', 'ﬦ' => 'ם', 'ﬧ' => 'ר', 'ﬨ' => 'ת', '﬩' => '+', 'ﭏ' => 'אל', 'ﭐ' => 'ٱ', 'ﭑ' => 'ٱ', 'ﭒ' => 'ٻ', 'ﭓ' => 'ٻ', 'ﭔ' => 'ٻ', 'ﭕ' => 'ٻ', 'ﭖ' => 'پ', 'ﭗ' => 'پ', 'ﭘ' => 'پ', 'ﭙ' => 'پ', 'ﭚ' => 'ڀ', 'ﭛ' => 'ڀ', 'ﭜ' => 'ڀ', 'ﭝ' => 'ڀ', 'ﭞ' => 'ٺ', 'ﭟ' => 'ٺ', 'ﭠ' => 'ٺ', 'ﭡ' => 'ٺ', 'ﭢ' => 'ٿ', 'ﭣ' => 'ٿ', 'ﭤ' => 'ٿ', 'ﭥ' => 'ٿ', 'ﭦ' => 'ٹ', 'ﭧ' => 'ٹ', 'ﭨ' => 'ٹ', 'ﭩ' => 'ٹ', 'ﭪ' => 'ڤ', 'ﭫ' => 'ڤ', 'ﭬ' => 'ڤ', 'ﭭ' => 'ڤ', 'ﭮ' => 'ڦ', 'ﭯ' => 'ڦ', 'ﭰ' => 'ڦ', 'ﭱ' => 'ڦ', 'ﭲ' => 'ڄ', 'ﭳ' => 'ڄ', 'ﭴ' => 'ڄ', 'ﭵ' => 'ڄ', 'ﭶ' => 'ڃ', 'ﭷ' => 'ڃ', 'ﭸ' => 'ڃ', 'ﭹ' => 'ڃ', 'ﭺ' => 'چ', 'ﭻ' => 'چ', 'ﭼ' => 'چ', 'ﭽ' => 'چ', 'ﭾ' => 'ڇ', 'ﭿ' => 'ڇ', 'ﮀ' => 'ڇ', 'ﮁ' => 'ڇ', 'ﮂ' => 'ڍ', 'ﮃ' => 'ڍ', 'ﮄ' => 'ڌ', 'ﮅ' => 'ڌ', 'ﮆ' => 'ڎ', 'ﮇ' => 'ڎ', 'ﮈ' => 'ڈ', 'ﮉ' => 'ڈ', 'ﮊ' => 'ژ', 'ﮋ' => 'ژ', 'ﮌ' => 'ڑ', 'ﮍ' => 'ڑ', 'ﮎ' => 'ک', 'ﮏ' => 'ک', 'ﮐ' => 'ک', 'ﮑ' => 'ک', 'ﮒ' => 'گ', 'ﮓ' => 'گ', 'ﮔ' => 'گ', 'ﮕ' => 'گ', 'ﮖ' => 'ڳ', 'ﮗ' => 'ڳ', 'ﮘ' => 'ڳ', 'ﮙ' => 'ڳ', 'ﮚ' => 'ڱ', 'ﮛ' => 'ڱ', 'ﮜ' => 'ڱ', 'ﮝ' => 'ڱ', 'ﮞ' => 'ں', 'ﮟ' => 'ں', 'ﮠ' => 'ڻ', 'ﮡ' => 'ڻ', 'ﮢ' => 'ڻ', 'ﮣ' => 'ڻ', 'ﮤ' => 'ۀ', 'ﮥ' => 'ۀ', 'ﮦ' => 'ہ', 'ﮧ' => 'ہ', 'ﮨ' => 'ہ', 'ﮩ' => 'ہ', 'ﮪ' => 'ھ', 'ﮫ' => 'ھ', 'ﮬ' => 'ھ', 'ﮭ' => 'ھ', 'ﮮ' => 'ے', 'ﮯ' => 'ے', 'ﮰ' => 'ۓ', 'ﮱ' => 'ۓ', 'ﯓ' => 'ڭ', 'ﯔ' => 'ڭ', 'ﯕ' => 'ڭ', 'ﯖ' => 'ڭ', 'ﯗ' => 'ۇ', 'ﯘ' => 'ۇ', 'ﯙ' => 'ۆ', 'ﯚ' => 'ۆ', 'ﯛ' => 'ۈ', 'ﯜ' => 'ۈ', 'ﯝ' => 'ۇٴ', 'ﯞ' => 'ۋ', 'ﯟ' => 'ۋ', 'ﯠ' => 'ۅ', 'ﯡ' => 'ۅ', 'ﯢ' => 'ۉ', 'ﯣ' => 'ۉ', 'ﯤ' => 'ې', 'ﯥ' => 'ې', 'ﯦ' => 'ې', 'ﯧ' => 'ې', 'ﯨ' => 'ى', 'ﯩ' => 'ى', 'ﯪ' => 'ئا', 'ﯫ' => 'ئا', 'ﯬ' => 'ئە', 'ﯭ' => 'ئە', 'ﯮ' => 'ئو', 'ﯯ' => 'ئو', 'ﯰ' => 'ئۇ', 'ﯱ' => 'ئۇ', 'ﯲ' => 'ئۆ', 'ﯳ' => 'ئۆ', 'ﯴ' => 'ئۈ', 'ﯵ' => 'ئۈ', 'ﯶ' => 'ئې', 'ﯷ' => 'ئې', 'ﯸ' => 'ئې', 'ﯹ' => 'ئى', 'ﯺ' => 'ئى', 'ﯻ' => 'ئى', 'ﯼ' => 'ی', 'ﯽ' => 'ی', 'ﯾ' => 'ی', 'ﯿ' => 'ی', 'ﰀ' => 'ئج', 'ﰁ' => 'ئح', 'ﰂ' => 'ئم', 'ﰃ' => 'ئى', 'ﰄ' => 'ئي', 'ﰅ' => 'بج', 'ﰆ' => 'بح', 'ﰇ' => 'بخ', 'ﰈ' => 'بم', 'ﰉ' => 'بى', 'ﰊ' => 'بي', 'ﰋ' => 'تج', 'ﰌ' => 'تح', 'ﰍ' => 'تخ', 'ﰎ' => 'تم', 'ﰏ' => 'تى', 'ﰐ' => 'تي', 'ﰑ' => 'ثج', 'ﰒ' => 'ثم', 'ﰓ' => 'ثى', 'ﰔ' => 'ثي', 'ﰕ' => 'جح', 'ﰖ' => 'جم', 'ﰗ' => 'حج', 'ﰘ' => 'حم', 'ﰙ' => 'خج', 'ﰚ' => 'خح', 'ﰛ' => 'خم', 'ﰜ' => 'سج', 'ﰝ' => 'سح', 'ﰞ' => 'سخ', 'ﰟ' => 'سم', 'ﰠ' => 'صح', 'ﰡ' => 'صم', 'ﰢ' => 'ضج', 'ﰣ' => 'ضح', 'ﰤ' => 'ضخ', 'ﰥ' => 'ضم', 'ﰦ' => 'طح', 'ﰧ' => 'طم', 'ﰨ' => 'ظم', 'ﰩ' => 'عج', 'ﰪ' => 'عم', 'ﰫ' => 'غج', 'ﰬ' => 'غم', 'ﰭ' => 'فج', 'ﰮ' => 'فح', 'ﰯ' => 'فخ', 'ﰰ' => 'فم', 'ﰱ' => 'فى', 'ﰲ' => 'في', 'ﰳ' => 'قح', 'ﰴ' => 'قم', 'ﰵ' => 'قى', 'ﰶ' => 'قي', 'ﰷ' => 'كا', 'ﰸ' => 'كج', 'ﰹ' => 'كح', 'ﰺ' => 'كخ', 'ﰻ' => 'كل', 'ﰼ' => 'كم', 'ﰽ' => 'كى', 'ﰾ' => 'كي', 'ﰿ' => 'لج', 'ﱀ' => 'لح', 'ﱁ' => 'لخ', 'ﱂ' => 'لم', 'ﱃ' => 'لى', 'ﱄ' => 'لي', 'ﱅ' => 'مج', 'ﱆ' => 'مح', 'ﱇ' => 'مخ', 'ﱈ' => 'مم', 'ﱉ' => 'مى', 'ﱊ' => 'مي', 'ﱋ' => 'نج', 'ﱌ' => 'نح', 'ﱍ' => 'نخ', 'ﱎ' => 'نم', 'ﱏ' => 'نى', 'ﱐ' => 'ني', 'ﱑ' => 'هج', 'ﱒ' => 'هم', 'ﱓ' => 'هى', 'ﱔ' => 'هي', 'ﱕ' => 'يج', 'ﱖ' => 'يح', 'ﱗ' => 'يخ', 'ﱘ' => 'يم', 'ﱙ' => 'يى', 'ﱚ' => 'يي', 'ﱛ' => 'ذٰ', 'ﱜ' => 'رٰ', 'ﱝ' => 'ىٰ', 'ﱞ' => ' ٌّ', 'ﱟ' => ' ٍّ', 'ﱠ' => ' َّ', 'ﱡ' => ' ُّ', 'ﱢ' => ' ِّ', 'ﱣ' => ' ّٰ', 'ﱤ' => 'ئر', 'ﱥ' => 'ئز', 'ﱦ' => 'ئم', 'ﱧ' => 'ئن', 'ﱨ' => 'ئى', 'ﱩ' => 'ئي', 'ﱪ' => 'بر', 'ﱫ' => 'بز', 'ﱬ' => 'بم', 'ﱭ' => 'بن', 'ﱮ' => 'بى', 'ﱯ' => 'بي', 'ﱰ' => 'تر', 'ﱱ' => 'تز', 'ﱲ' => 'تم', 'ﱳ' => 'تن', 'ﱴ' => 'تى', 'ﱵ' => 'تي', 'ﱶ' => 'ثر', 'ﱷ' => 'ثز', 'ﱸ' => 'ثم', 'ﱹ' => 'ثن', 'ﱺ' => 'ثى', 'ﱻ' => 'ثي', 'ﱼ' => 'فى', 'ﱽ' => 'في', 'ﱾ' => 'قى', 'ﱿ' => 'قي', 'ﲀ' => 'كا', 'ﲁ' => 'كل', 'ﲂ' => 'كم', 'ﲃ' => 'كى', 'ﲄ' => 'كي', 'ﲅ' => 'لم', 'ﲆ' => 'لى', 'ﲇ' => 'لي', 'ﲈ' => 'ما', 'ﲉ' => 'مم', 'ﲊ' => 'نر', 'ﲋ' => 'نز', 'ﲌ' => 'نم', 'ﲍ' => 'نن', 'ﲎ' => 'نى', 'ﲏ' => 'ني', 'ﲐ' => 'ىٰ', 'ﲑ' => 'ير', 'ﲒ' => 'يز', 'ﲓ' => 'يم', 'ﲔ' => 'ين', 'ﲕ' => 'يى', 'ﲖ' => 'يي', 'ﲗ' => 'ئج', 'ﲘ' => 'ئح', 'ﲙ' => 'ئخ', 'ﲚ' => 'ئم', 'ﲛ' => 'ئه', 'ﲜ' => 'بج', 'ﲝ' => 'بح', 'ﲞ' => 'بخ', 'ﲟ' => 'بم', 'ﲠ' => 'به', 'ﲡ' => 'تج', 'ﲢ' => 'تح', 'ﲣ' => 'تخ', 'ﲤ' => 'تم', 'ﲥ' => 'ته', 'ﲦ' => 'ثم', 'ﲧ' => 'جح', 'ﲨ' => 'جم', 'ﲩ' => 'حج', 'ﲪ' => 'حم', 'ﲫ' => 'خج', 'ﲬ' => 'خم', 'ﲭ' => 'سج', 'ﲮ' => 'سح', 'ﲯ' => 'سخ', 'ﲰ' => 'سم', 'ﲱ' => 'صح', 'ﲲ' => 'صخ', 'ﲳ' => 'صم', 'ﲴ' => 'ضج', 'ﲵ' => 'ضح', 'ﲶ' => 'ضخ', 'ﲷ' => 'ضم', 'ﲸ' => 'طح', 'ﲹ' => 'ظم', 'ﲺ' => 'عج', 'ﲻ' => 'عم', 'ﲼ' => 'غج', 'ﲽ' => 'غم', 'ﲾ' => 'فج', 'ﲿ' => 'فح', 'ﳀ' => 'فخ', 'ﳁ' => 'فم', 'ﳂ' => 'قح', 'ﳃ' => 'قم', 'ﳄ' => 'كج', 'ﳅ' => 'كح', 'ﳆ' => 'كخ', 'ﳇ' => 'كل', 'ﳈ' => 'كم', 'ﳉ' => 'لج', 'ﳊ' => 'لح', 'ﳋ' => 'لخ', 'ﳌ' => 'لم', 'ﳍ' => 'له', 'ﳎ' => 'مج', 'ﳏ' => 'مح', 'ﳐ' => 'مخ', 'ﳑ' => 'مم', 'ﳒ' => 'نج', 'ﳓ' => 'نح', 'ﳔ' => 'نخ', 'ﳕ' => 'نم', 'ﳖ' => 'نه', 'ﳗ' => 'هج', 'ﳘ' => 'هم', 'ﳙ' => 'هٰ', 'ﳚ' => 'يج', 'ﳛ' => 'يح', 'ﳜ' => 'يخ', 'ﳝ' => 'يم', 'ﳞ' => 'يه', 'ﳟ' => 'ئم', 'ﳠ' => 'ئه', 'ﳡ' => 'بم', 'ﳢ' => 'به', 'ﳣ' => 'تم', 'ﳤ' => 'ته', 'ﳥ' => 'ثم', 'ﳦ' => 'ثه', 'ﳧ' => 'سم', 'ﳨ' => 'سه', 'ﳩ' => 'شم', 'ﳪ' => 'شه', 'ﳫ' => 'كل', 'ﳬ' => 'كم', 'ﳭ' => 'لم', 'ﳮ' => 'نم', 'ﳯ' => 'نه', 'ﳰ' => 'يم', 'ﳱ' => 'يه', 'ﳲ' => 'ـَّ', 'ﳳ' => 'ـُّ', 'ﳴ' => 'ـِّ', 'ﳵ' => 'طى', 'ﳶ' => 'طي', 'ﳷ' => 'عى', 'ﳸ' => 'عي', 'ﳹ' => 'غى', 'ﳺ' => 'غي', 'ﳻ' => 'سى', 'ﳼ' => 'سي', 'ﳽ' => 'شى', 'ﳾ' => 'شي', 'ﳿ' => 'حى', 'ﴀ' => 'حي', 'ﴁ' => 'جى', 'ﴂ' => 'جي', 'ﴃ' => 'خى', 'ﴄ' => 'خي', 'ﴅ' => 'صى', 'ﴆ' => 'صي', 'ﴇ' => 'ضى', 'ﴈ' => 'ضي', 'ﴉ' => 'شج', 'ﴊ' => 'شح', 'ﴋ' => 'شخ', 'ﴌ' => 'شم', 'ﴍ' => 'شر', 'ﴎ' => 'سر', 'ﴏ' => 'صر', 'ﴐ' => 'ضر', 'ﴑ' => 'طى', 'ﴒ' => 'طي', 'ﴓ' => 'عى', 'ﴔ' => 'عي', 'ﴕ' => 'غى', 'ﴖ' => 'غي', 'ﴗ' => 'سى', 'ﴘ' => 'سي', 'ﴙ' => 'شى', 'ﴚ' => 'شي', 'ﴛ' => 'حى', 'ﴜ' => 'حي', 'ﴝ' => 'جى', 'ﴞ' => 'جي', 'ﴟ' => 'خى', 'ﴠ' => 'خي', 'ﴡ' => 'صى', 'ﴢ' => 'صي', 'ﴣ' => 'ضى', 'ﴤ' => 'ضي', 'ﴥ' => 'شج', 'ﴦ' => 'شح', 'ﴧ' => 'شخ', 'ﴨ' => 'شم', 'ﴩ' => 'شر', 'ﴪ' => 'سر', 'ﴫ' => 'صر', 'ﴬ' => 'ضر', 'ﴭ' => 'شج', 'ﴮ' => 'شح', 'ﴯ' => 'شخ', 'ﴰ' => 'شم', 'ﴱ' => 'سه', 'ﴲ' => 'شه', 'ﴳ' => 'طم', 'ﴴ' => 'سج', 'ﴵ' => 'سح', 'ﴶ' => 'سخ', 'ﴷ' => 'شج', 'ﴸ' => 'شح', 'ﴹ' => 'شخ', 'ﴺ' => 'طم', 'ﴻ' => 'ظم', 'ﴼ' => 'اً', 'ﴽ' => 'اً', 'ﵐ' => 'تجم', 'ﵑ' => 'تحج', 'ﵒ' => 'تحج', 'ﵓ' => 'تحم', 'ﵔ' => 'تخم', 'ﵕ' => 'تمج', 'ﵖ' => 'تمح', 'ﵗ' => 'تمخ', 'ﵘ' => 'جمح', 'ﵙ' => 'جمح', 'ﵚ' => 'حمي', 'ﵛ' => 'حمى', 'ﵜ' => 'سحج', 'ﵝ' => 'سجح', 'ﵞ' => 'سجى', 'ﵟ' => 'سمح', 'ﵠ' => 'سمح', 'ﵡ' => 'سمج', 'ﵢ' => 'سمم', 'ﵣ' => 'سمم', 'ﵤ' => 'صحح', 'ﵥ' => 'صحح', 'ﵦ' => 'صمم', 'ﵧ' => 'شحم', 'ﵨ' => 'شحم', 'ﵩ' => 'شجي', 'ﵪ' => 'شمخ', 'ﵫ' => 'شمخ', 'ﵬ' => 'شمم', 'ﵭ' => 'شمم', 'ﵮ' => 'ضحى', 'ﵯ' => 'ضخم', 'ﵰ' => 'ضخم', 'ﵱ' => 'طمح', 'ﵲ' => 'طمح', 'ﵳ' => 'طمم', 'ﵴ' => 'طمي', 'ﵵ' => 'عجم', 'ﵶ' => 'عمم', 'ﵷ' => 'عمم', 'ﵸ' => 'عمى', 'ﵹ' => 'غمم', 'ﵺ' => 'غمي', 'ﵻ' => 'غمى', 'ﵼ' => 'فخم', 'ﵽ' => 'فخم', 'ﵾ' => 'قمح', 'ﵿ' => 'قمم', 'ﶀ' => 'لحم', 'ﶁ' => 'لحي', 'ﶂ' => 'لحى', 'ﶃ' => 'لجج', 'ﶄ' => 'لجج', 'ﶅ' => 'لخم', 'ﶆ' => 'لخم', 'ﶇ' => 'لمح', 'ﶈ' => 'لمح', 'ﶉ' => 'محج', 'ﶊ' => 'محم', 'ﶋ' => 'محي', 'ﶌ' => 'مجح', 'ﶍ' => 'مجم', 'ﶎ' => 'مخج', 'ﶏ' => 'مخم', 'ﶒ' => 'مجخ', 'ﶓ' => 'همج', 'ﶔ' => 'همم', 'ﶕ' => 'نحم', 'ﶖ' => 'نحى', 'ﶗ' => 'نجم', 'ﶘ' => 'نجم', 'ﶙ' => 'نجى', 'ﶚ' => 'نمي', 'ﶛ' => 'نمى', 'ﶜ' => 'يمم', 'ﶝ' => 'يمم', 'ﶞ' => 'بخي', 'ﶟ' => 'تجي', 'ﶠ' => 'تجى', 'ﶡ' => 'تخي', 'ﶢ' => 'تخى', 'ﶣ' => 'تمي', 'ﶤ' => 'تمى', 'ﶥ' => 'جمي', 'ﶦ' => 'جحى', 'ﶧ' => 'جمى', 'ﶨ' => 'سخى', 'ﶩ' => 'صحي', 'ﶪ' => 'شحي', 'ﶫ' => 'ضحي', 'ﶬ' => 'لجي', 'ﶭ' => 'لمي', 'ﶮ' => 'يحي', 'ﶯ' => 'يجي', 'ﶰ' => 'يمي', 'ﶱ' => 'ممي', 'ﶲ' => 'قمي', 'ﶳ' => 'نحي', 'ﶴ' => 'قمح', 'ﶵ' => 'لحم', 'ﶶ' => 'عمي', 'ﶷ' => 'كمي', 'ﶸ' => 'نجح', 'ﶹ' => 'مخي', 'ﶺ' => 'لجم', 'ﶻ' => 'كمم', 'ﶼ' => 'لجم', 'ﶽ' => 'نجح', 'ﶾ' => 'جحي', 'ﶿ' => 'حجي', 'ﷀ' => 'مجي', 'ﷁ' => 'فمي', 'ﷂ' => 'بحي', 'ﷃ' => 'كمم', 'ﷄ' => 'عجم', 'ﷅ' => 'صمم', 'ﷆ' => 'سخي', 'ﷇ' => 'نجي', 'ﷰ' => 'صلے', 'ﷱ' => 'قلے', 'ﷲ' => 'الله', 'ﷳ' => 'اكبر', 'ﷴ' => 'محمد', 'ﷵ' => 'صلعم', 'ﷶ' => 'رسول', 'ﷷ' => 'عليه', 'ﷸ' => 'وسلم', 'ﷹ' => 'صلى', 'ﷺ' => 'صلى الله عليه وسلم', 'ﷻ' => 'جل جلاله', '﷼' => 'ریال', '︐' => ',', '︑' => '、', '︒' => '。', '︓' => ':', '︔' => ';', '︕' => '!', '︖' => '?', '︗' => '〖', '︘' => '〗', '︙' => '...', '︰' => '..', '︱' => '—', '︲' => '–', '︳' => '_', '︴' => '_', '︵' => '(', '︶' => ')', '︷' => '{', '︸' => '}', '︹' => '〔', '︺' => '〕', '︻' => '【', '︼' => '】', '︽' => '《', '︾' => '》', '︿' => '〈', '﹀' => '〉', '﹁' => '「', '﹂' => '」', '﹃' => '『', '﹄' => '』', '﹇' => '[', '﹈' => ']', '﹉' => ' ̅', '﹊' => ' ̅', '﹋' => ' ̅', '﹌' => ' ̅', '﹍' => '_', '﹎' => '_', '﹏' => '_', '﹐' => ',', '﹑' => '、', '﹒' => '.', '﹔' => ';', '﹕' => ':', '﹖' => '?', '﹗' => '!', '﹘' => '—', '﹙' => '(', '﹚' => ')', '﹛' => '{', '﹜' => '}', '﹝' => '〔', '﹞' => '〕', '﹟' => '#', '﹠' => '&', '﹡' => '*', '﹢' => '+', '﹣' => '-', '﹤' => '<', '﹥' => '>', '﹦' => '=', '﹨' => '\\', '﹩' => '$', '﹪' => '%', '﹫' => '@', 'ﹰ' => ' ً', 'ﹱ' => 'ـً', 'ﹲ' => ' ٌ', 'ﹴ' => ' ٍ', 'ﹶ' => ' َ', 'ﹷ' => 'ـَ', 'ﹸ' => ' ُ', 'ﹹ' => 'ـُ', 'ﹺ' => ' ِ', 'ﹻ' => 'ـِ', 'ﹼ' => ' ّ', 'ﹽ' => 'ـّ', 'ﹾ' => ' ْ', 'ﹿ' => 'ـْ', 'ﺀ' => 'ء', 'ﺁ' => 'آ', 'ﺂ' => 'آ', 'ﺃ' => 'أ', 'ﺄ' => 'أ', 'ﺅ' => 'ؤ', 'ﺆ' => 'ؤ', 'ﺇ' => 'إ', 'ﺈ' => 'إ', 'ﺉ' => 'ئ', 'ﺊ' => 'ئ', 'ﺋ' => 'ئ', 'ﺌ' => 'ئ', 'ﺍ' => 'ا', 'ﺎ' => 'ا', 'ﺏ' => 'ب', 'ﺐ' => 'ب', 'ﺑ' => 'ب', 'ﺒ' => 'ب', 'ﺓ' => 'ة', 'ﺔ' => 'ة', 'ﺕ' => 'ت', 'ﺖ' => 'ت', 'ﺗ' => 'ت', 'ﺘ' => 'ت', 'ﺙ' => 'ث', 'ﺚ' => 'ث', 'ﺛ' => 'ث', 'ﺜ' => 'ث', 'ﺝ' => 'ج', 'ﺞ' => 'ج', 'ﺟ' => 'ج', 'ﺠ' => 'ج', 'ﺡ' => 'ح', 'ﺢ' => 'ح', 'ﺣ' => 'ح', 'ﺤ' => 'ح', 'ﺥ' => 'خ', 'ﺦ' => 'خ', 'ﺧ' => 'خ', 'ﺨ' => 'خ', 'ﺩ' => 'د', 'ﺪ' => 'د', 'ﺫ' => 'ذ', 'ﺬ' => 'ذ', 'ﺭ' => 'ر', 'ﺮ' => 'ر', 'ﺯ' => 'ز', 'ﺰ' => 'ز', 'ﺱ' => 'س', 'ﺲ' => 'س', 'ﺳ' => 'س', 'ﺴ' => 'س', 'ﺵ' => 'ش', 'ﺶ' => 'ش', 'ﺷ' => 'ش', 'ﺸ' => 'ش', 'ﺹ' => 'ص', 'ﺺ' => 'ص', 'ﺻ' => 'ص', 'ﺼ' => 'ص', 'ﺽ' => 'ض', 'ﺾ' => 'ض', 'ﺿ' => 'ض', 'ﻀ' => 'ض', 'ﻁ' => 'ط', 'ﻂ' => 'ط', 'ﻃ' => 'ط', 'ﻄ' => 'ط', 'ﻅ' => 'ظ', 'ﻆ' => 'ظ', 'ﻇ' => 'ظ', 'ﻈ' => 'ظ', 'ﻉ' => 'ع', 'ﻊ' => 'ع', 'ﻋ' => 'ع', 'ﻌ' => 'ع', 'ﻍ' => 'غ', 'ﻎ' => 'غ', 'ﻏ' => 'غ', 'ﻐ' => 'غ', 'ﻑ' => 'ف', 'ﻒ' => 'ف', 'ﻓ' => 'ف', 'ﻔ' => 'ف', 'ﻕ' => 'ق', 'ﻖ' => 'ق', 'ﻗ' => 'ق', 'ﻘ' => 'ق', 'ﻙ' => 'ك', 'ﻚ' => 'ك', 'ﻛ' => 'ك', 'ﻜ' => 'ك', 'ﻝ' => 'ل', 'ﻞ' => 'ل', 'ﻟ' => 'ل', 'ﻠ' => 'ل', 'ﻡ' => 'م', 'ﻢ' => 'م', 'ﻣ' => 'م', 'ﻤ' => 'م', 'ﻥ' => 'ن', 'ﻦ' => 'ن', 'ﻧ' => 'ن', 'ﻨ' => 'ن', 'ﻩ' => 'ه', 'ﻪ' => 'ه', 'ﻫ' => 'ه', 'ﻬ' => 'ه', 'ﻭ' => 'و', 'ﻮ' => 'و', 'ﻯ' => 'ى', 'ﻰ' => 'ى', 'ﻱ' => 'ي', 'ﻲ' => 'ي', 'ﻳ' => 'ي', 'ﻴ' => 'ي', 'ﻵ' => 'لآ', 'ﻶ' => 'لآ', 'ﻷ' => 'لأ', 'ﻸ' => 'لأ', 'ﻹ' => 'لإ', 'ﻺ' => 'لإ', 'ﻻ' => 'لا', 'ﻼ' => 'لا', '!' => '!', '"' => '"', '#' => '#', '$' => '$', '%' => '%', '&' => '&', ''' => '\'', '(' => '(', ')' => ')', '*' => '*', '+' => '+', ',' => ',', '-' => '-', '.' => '.', '/' => '/', '0' => '0', '1' => '1', '2' => '2', '3' => '3', '4' => '4', '5' => '5', '6' => '6', '7' => '7', '8' => '8', '9' => '9', ':' => ':', ';' => ';', '<' => '<', '=' => '=', '>' => '>', '?' => '?', '@' => '@', 'A' => 'A', 'B' => 'B', 'C' => 'C', 'D' => 'D', 'E' => 'E', 'F' => 'F', 'G' => 'G', 'H' => 'H', 'I' => 'I', 'J' => 'J', 'K' => 'K', 'L' => 'L', 'M' => 'M', 'N' => 'N', 'O' => 'O', 'P' => 'P', 'Q' => 'Q', 'R' => 'R', 'S' => 'S', 'T' => 'T', 'U' => 'U', 'V' => 'V', 'W' => 'W', 'X' => 'X', 'Y' => 'Y', 'Z' => 'Z', '[' => '[', '\' => '\\', ']' => ']', '^' => '^', '_' => '_', '`' => '`', 'a' => 'a', 'b' => 'b', 'c' => 'c', 'd' => 'd', 'e' => 'e', 'f' => 'f', 'g' => 'g', 'h' => 'h', 'i' => 'i', 'j' => 'j', 'k' => 'k', 'l' => 'l', 'm' => 'm', 'n' => 'n', 'o' => 'o', 'p' => 'p', 'q' => 'q', 'r' => 'r', 's' => 's', 't' => 't', 'u' => 'u', 'v' => 'v', 'w' => 'w', 'x' => 'x', 'y' => 'y', 'z' => 'z', '{' => '{', '|' => '|', '}' => '}', '~' => '~', '⦅' => '⦅', '⦆' => '⦆', '。' => '。', '「' => '「', '」' => '」', '、' => '、', '・' => '・', 'ヲ' => 'ヲ', 'ァ' => 'ァ', 'ィ' => 'ィ', 'ゥ' => 'ゥ', 'ェ' => 'ェ', 'ォ' => 'ォ', 'ャ' => 'ャ', 'ュ' => 'ュ', 'ョ' => 'ョ', 'ッ' => 'ッ', 'ー' => 'ー', 'ア' => 'ア', 'イ' => 'イ', 'ウ' => 'ウ', 'エ' => 'エ', 'オ' => 'オ', 'カ' => 'カ', 'キ' => 'キ', 'ク' => 'ク', 'ケ' => 'ケ', 'コ' => 'コ', 'サ' => 'サ', 'シ' => 'シ', 'ス' => 'ス', 'セ' => 'セ', 'ソ' => 'ソ', 'タ' => 'タ', 'チ' => 'チ', 'ツ' => 'ツ', 'テ' => 'テ', 'ト' => 'ト', 'ナ' => 'ナ', 'ニ' => 'ニ', 'ヌ' => 'ヌ', 'ネ' => 'ネ', 'ノ' => 'ノ', 'ハ' => 'ハ', 'ヒ' => 'ヒ', 'フ' => 'フ', 'ヘ' => 'ヘ', 'ホ' => 'ホ', 'マ' => 'マ', 'ミ' => 'ミ', 'ム' => 'ム', 'メ' => 'メ', 'モ' => 'モ', 'ヤ' => 'ヤ', 'ユ' => 'ユ', 'ヨ' => 'ヨ', 'ラ' => 'ラ', 'リ' => 'リ', 'ル' => 'ル', 'レ' => 'レ', 'ロ' => 'ロ', 'ワ' => 'ワ', 'ン' => 'ン', '゙' => '゙', '゚' => '゚', 'ᅠ' => 'ᅠ', 'ᄀ' => 'ᄀ', 'ᄁ' => 'ᄁ', 'ᆪ' => 'ᆪ', 'ᄂ' => 'ᄂ', 'ᆬ' => 'ᆬ', 'ᆭ' => 'ᆭ', 'ᄃ' => 'ᄃ', 'ᄄ' => 'ᄄ', 'ᄅ' => 'ᄅ', 'ᆰ' => 'ᆰ', 'ᆱ' => 'ᆱ', 'ᆲ' => 'ᆲ', 'ᆳ' => 'ᆳ', 'ᆴ' => 'ᆴ', 'ᆵ' => 'ᆵ', 'ᄚ' => 'ᄚ', 'ᄆ' => 'ᄆ', 'ᄇ' => 'ᄇ', 'ᄈ' => 'ᄈ', 'ᄡ' => 'ᄡ', 'ᄉ' => 'ᄉ', 'ᄊ' => 'ᄊ', 'ᄋ' => 'ᄋ', 'ᄌ' => 'ᄌ', 'ᄍ' => 'ᄍ', 'ᄎ' => 'ᄎ', 'ᄏ' => 'ᄏ', 'ᄐ' => 'ᄐ', 'ᄑ' => 'ᄑ', 'ᄒ' => 'ᄒ', 'ᅡ' => 'ᅡ', 'ᅢ' => 'ᅢ', 'ᅣ' => 'ᅣ', 'ᅤ' => 'ᅤ', 'ᅥ' => 'ᅥ', 'ᅦ' => 'ᅦ', 'ᅧ' => 'ᅧ', 'ᅨ' => 'ᅨ', 'ᅩ' => 'ᅩ', 'ᅪ' => 'ᅪ', 'ᅫ' => 'ᅫ', 'ᅬ' => 'ᅬ', 'ᅭ' => 'ᅭ', 'ᅮ' => 'ᅮ', 'ᅯ' => 'ᅯ', 'ᅰ' => 'ᅰ', 'ᅱ' => 'ᅱ', 'ᅲ' => 'ᅲ', 'ᅳ' => 'ᅳ', 'ᅴ' => 'ᅴ', 'ᅵ' => 'ᅵ', '¢' => '¢', '£' => '£', '¬' => '¬', ' ̄' => ' ̄', '¦' => '¦', '¥' => '¥', '₩' => '₩', '│' => '│', '←' => '←', '↑' => '↑', '→' => '→', '↓' => '↓', '■' => '■', '○' => '○', '𝐀' => 'A', '𝐁' => 'B', '𝐂' => 'C', '𝐃' => 'D', '𝐄' => 'E', '𝐅' => 'F', '𝐆' => 'G', '𝐇' => 'H', '𝐈' => 'I', '𝐉' => 'J', '𝐊' => 'K', '𝐋' => 'L', '𝐌' => 'M', '𝐍' => 'N', '𝐎' => 'O', '𝐏' => 'P', '𝐐' => 'Q', '𝐑' => 'R', '𝐒' => 'S', '𝐓' => 'T', '𝐔' => 'U', '𝐕' => 'V', '𝐖' => 'W', '𝐗' => 'X', '𝐘' => 'Y', '𝐙' => 'Z', '𝐚' => 'a', '𝐛' => 'b', '𝐜' => 'c', '𝐝' => 'd', '𝐞' => 'e', '𝐟' => 'f', '𝐠' => 'g', '𝐡' => 'h', '𝐢' => 'i', '𝐣' => 'j', '𝐤' => 'k', '𝐥' => 'l', '𝐦' => 'm', '𝐧' => 'n', '𝐨' => 'o', '𝐩' => 'p', '𝐪' => 'q', '𝐫' => 'r', '𝐬' => 's', '𝐭' => 't', '𝐮' => 'u', '𝐯' => 'v', '𝐰' => 'w', '𝐱' => 'x', '𝐲' => 'y', '𝐳' => 'z', '𝐴' => 'A', '𝐵' => 'B', '𝐶' => 'C', '𝐷' => 'D', '𝐸' => 'E', '𝐹' => 'F', '𝐺' => 'G', '𝐻' => 'H', '𝐼' => 'I', '𝐽' => 'J', '𝐾' => 'K', '𝐿' => 'L', '𝑀' => 'M', '𝑁' => 'N', '𝑂' => 'O', '𝑃' => 'P', '𝑄' => 'Q', '𝑅' => 'R', '𝑆' => 'S', '𝑇' => 'T', '𝑈' => 'U', '𝑉' => 'V', '𝑊' => 'W', '𝑋' => 'X', '𝑌' => 'Y', '𝑍' => 'Z', '𝑎' => 'a', '𝑏' => 'b', '𝑐' => 'c', '𝑑' => 'd', '𝑒' => 'e', '𝑓' => 'f', '𝑔' => 'g', '𝑖' => 'i', '𝑗' => 'j', '𝑘' => 'k', '𝑙' => 'l', '𝑚' => 'm', '𝑛' => 'n', '𝑜' => 'o', '𝑝' => 'p', '𝑞' => 'q', '𝑟' => 'r', '𝑠' => 's', '𝑡' => 't', '𝑢' => 'u', '𝑣' => 'v', '𝑤' => 'w', '𝑥' => 'x', '𝑦' => 'y', '𝑧' => 'z', '𝑨' => 'A', '𝑩' => 'B', '𝑪' => 'C', '𝑫' => 'D', '𝑬' => 'E', '𝑭' => 'F', '𝑮' => 'G', '𝑯' => 'H', '𝑰' => 'I', '𝑱' => 'J', '𝑲' => 'K', '𝑳' => 'L', '𝑴' => 'M', '𝑵' => 'N', '𝑶' => 'O', '𝑷' => 'P', '𝑸' => 'Q', '𝑹' => 'R', '𝑺' => 'S', '𝑻' => 'T', '𝑼' => 'U', '𝑽' => 'V', '𝑾' => 'W', '𝑿' => 'X', '𝒀' => 'Y', '𝒁' => 'Z', '𝒂' => 'a', '𝒃' => 'b', '𝒄' => 'c', '𝒅' => 'd', '𝒆' => 'e', '𝒇' => 'f', '𝒈' => 'g', '𝒉' => 'h', '𝒊' => 'i', '𝒋' => 'j', '𝒌' => 'k', '𝒍' => 'l', '𝒎' => 'm', '𝒏' => 'n', '𝒐' => 'o', '𝒑' => 'p', '𝒒' => 'q', '𝒓' => 'r', '𝒔' => 's', '𝒕' => 't', '𝒖' => 'u', '𝒗' => 'v', '𝒘' => 'w', '𝒙' => 'x', '𝒚' => 'y', '𝒛' => 'z', '𝒜' => 'A', '𝒞' => 'C', '𝒟' => 'D', '𝒢' => 'G', '𝒥' => 'J', '𝒦' => 'K', '𝒩' => 'N', '𝒪' => 'O', '𝒫' => 'P', '𝒬' => 'Q', '𝒮' => 'S', '𝒯' => 'T', '𝒰' => 'U', '𝒱' => 'V', '𝒲' => 'W', '𝒳' => 'X', '𝒴' => 'Y', '𝒵' => 'Z', '𝒶' => 'a', '𝒷' => 'b', '𝒸' => 'c', '𝒹' => 'd', '𝒻' => 'f', '𝒽' => 'h', '𝒾' => 'i', '𝒿' => 'j', '𝓀' => 'k', '𝓁' => 'l', '𝓂' => 'm', '𝓃' => 'n', '𝓅' => 'p', '𝓆' => 'q', '𝓇' => 'r', '𝓈' => 's', '𝓉' => 't', '𝓊' => 'u', '𝓋' => 'v', '𝓌' => 'w', '𝓍' => 'x', '𝓎' => 'y', '𝓏' => 'z', '𝓐' => 'A', '𝓑' => 'B', '𝓒' => 'C', '𝓓' => 'D', '𝓔' => 'E', '𝓕' => 'F', '𝓖' => 'G', '𝓗' => 'H', '𝓘' => 'I', '𝓙' => 'J', '𝓚' => 'K', '𝓛' => 'L', '𝓜' => 'M', '𝓝' => 'N', '𝓞' => 'O', '𝓟' => 'P', '𝓠' => 'Q', '𝓡' => 'R', '𝓢' => 'S', '𝓣' => 'T', '𝓤' => 'U', '𝓥' => 'V', '𝓦' => 'W', '𝓧' => 'X', '𝓨' => 'Y', '𝓩' => 'Z', '𝓪' => 'a', '𝓫' => 'b', '𝓬' => 'c', '𝓭' => 'd', '𝓮' => 'e', '𝓯' => 'f', '𝓰' => 'g', '𝓱' => 'h', '𝓲' => 'i', '𝓳' => 'j', '𝓴' => 'k', '𝓵' => 'l', '𝓶' => 'm', '𝓷' => 'n', '𝓸' => 'o', '𝓹' => 'p', '𝓺' => 'q', '𝓻' => 'r', '𝓼' => 's', '𝓽' => 't', '𝓾' => 'u', '𝓿' => 'v', '𝔀' => 'w', '𝔁' => 'x', '𝔂' => 'y', '𝔃' => 'z', '𝔄' => 'A', '𝔅' => 'B', '𝔇' => 'D', '𝔈' => 'E', '𝔉' => 'F', '𝔊' => 'G', '𝔍' => 'J', '𝔎' => 'K', '𝔏' => 'L', '𝔐' => 'M', '𝔑' => 'N', '𝔒' => 'O', '𝔓' => 'P', '𝔔' => 'Q', '𝔖' => 'S', '𝔗' => 'T', '𝔘' => 'U', '𝔙' => 'V', '𝔚' => 'W', '𝔛' => 'X', '𝔜' => 'Y', '𝔞' => 'a', '𝔟' => 'b', '𝔠' => 'c', '𝔡' => 'd', '𝔢' => 'e', '𝔣' => 'f', '𝔤' => 'g', '𝔥' => 'h', '𝔦' => 'i', '𝔧' => 'j', '𝔨' => 'k', '𝔩' => 'l', '𝔪' => 'm', '𝔫' => 'n', '𝔬' => 'o', '𝔭' => 'p', '𝔮' => 'q', '𝔯' => 'r', '𝔰' => 's', '𝔱' => 't', '𝔲' => 'u', '𝔳' => 'v', '𝔴' => 'w', '𝔵' => 'x', '𝔶' => 'y', '𝔷' => 'z', '𝔸' => 'A', '𝔹' => 'B', '𝔻' => 'D', '𝔼' => 'E', '𝔽' => 'F', '𝔾' => 'G', '𝕀' => 'I', '𝕁' => 'J', '𝕂' => 'K', '𝕃' => 'L', '𝕄' => 'M', '𝕆' => 'O', '𝕊' => 'S', '𝕋' => 'T', '𝕌' => 'U', '𝕍' => 'V', '𝕎' => 'W', '𝕏' => 'X', '𝕐' => 'Y', '𝕒' => 'a', '𝕓' => 'b', '𝕔' => 'c', '𝕕' => 'd', '𝕖' => 'e', '𝕗' => 'f', '𝕘' => 'g', '𝕙' => 'h', '𝕚' => 'i', '𝕛' => 'j', '𝕜' => 'k', '𝕝' => 'l', '𝕞' => 'm', '𝕟' => 'n', '𝕠' => 'o', '𝕡' => 'p', '𝕢' => 'q', '𝕣' => 'r', '𝕤' => 's', '𝕥' => 't', '𝕦' => 'u', '𝕧' => 'v', '𝕨' => 'w', '𝕩' => 'x', '𝕪' => 'y', '𝕫' => 'z', '𝕬' => 'A', '𝕭' => 'B', '𝕮' => 'C', '𝕯' => 'D', '𝕰' => 'E', '𝕱' => 'F', '𝕲' => 'G', '𝕳' => 'H', '𝕴' => 'I', '𝕵' => 'J', '𝕶' => 'K', '𝕷' => 'L', '𝕸' => 'M', '𝕹' => 'N', '𝕺' => 'O', '𝕻' => 'P', '𝕼' => 'Q', '𝕽' => 'R', '𝕾' => 'S', '𝕿' => 'T', '𝖀' => 'U', '𝖁' => 'V', '𝖂' => 'W', '𝖃' => 'X', '𝖄' => 'Y', '𝖅' => 'Z', '𝖆' => 'a', '𝖇' => 'b', '𝖈' => 'c', '𝖉' => 'd', '𝖊' => 'e', '𝖋' => 'f', '𝖌' => 'g', '𝖍' => 'h', '𝖎' => 'i', '𝖏' => 'j', '𝖐' => 'k', '𝖑' => 'l', '𝖒' => 'm', '𝖓' => 'n', '𝖔' => 'o', '𝖕' => 'p', '𝖖' => 'q', '𝖗' => 'r', '𝖘' => 's', '𝖙' => 't', '𝖚' => 'u', '𝖛' => 'v', '𝖜' => 'w', '𝖝' => 'x', '𝖞' => 'y', '𝖟' => 'z', '𝖠' => 'A', '𝖡' => 'B', '𝖢' => 'C', '𝖣' => 'D', '𝖤' => 'E', '𝖥' => 'F', '𝖦' => 'G', '𝖧' => 'H', '𝖨' => 'I', '𝖩' => 'J', '𝖪' => 'K', '𝖫' => 'L', '𝖬' => 'M', '𝖭' => 'N', '𝖮' => 'O', '𝖯' => 'P', '𝖰' => 'Q', '𝖱' => 'R', '𝖲' => 'S', '𝖳' => 'T', '𝖴' => 'U', '𝖵' => 'V', '𝖶' => 'W', '𝖷' => 'X', '𝖸' => 'Y', '𝖹' => 'Z', '𝖺' => 'a', '𝖻' => 'b', '𝖼' => 'c', '𝖽' => 'd', '𝖾' => 'e', '𝖿' => 'f', '𝗀' => 'g', '𝗁' => 'h', '𝗂' => 'i', '𝗃' => 'j', '𝗄' => 'k', '𝗅' => 'l', '𝗆' => 'm', '𝗇' => 'n', '𝗈' => 'o', '𝗉' => 'p', '𝗊' => 'q', '𝗋' => 'r', '𝗌' => 's', '𝗍' => 't', '𝗎' => 'u', '𝗏' => 'v', '𝗐' => 'w', '𝗑' => 'x', '𝗒' => 'y', '𝗓' => 'z', '𝗔' => 'A', '𝗕' => 'B', '𝗖' => 'C', '𝗗' => 'D', '𝗘' => 'E', '𝗙' => 'F', '𝗚' => 'G', '𝗛' => 'H', '𝗜' => 'I', '𝗝' => 'J', '𝗞' => 'K', '𝗟' => 'L', '𝗠' => 'M', '𝗡' => 'N', '𝗢' => 'O', '𝗣' => 'P', '𝗤' => 'Q', '𝗥' => 'R', '𝗦' => 'S', '𝗧' => 'T', '𝗨' => 'U', '𝗩' => 'V', '𝗪' => 'W', '𝗫' => 'X', '𝗬' => 'Y', '𝗭' => 'Z', '𝗮' => 'a', '𝗯' => 'b', '𝗰' => 'c', '𝗱' => 'd', '𝗲' => 'e', '𝗳' => 'f', '𝗴' => 'g', '𝗵' => 'h', '𝗶' => 'i', '𝗷' => 'j', '𝗸' => 'k', '𝗹' => 'l', '𝗺' => 'm', '𝗻' => 'n', '𝗼' => 'o', '𝗽' => 'p', '𝗾' => 'q', '𝗿' => 'r', '𝘀' => 's', '𝘁' => 't', '𝘂' => 'u', '𝘃' => 'v', '𝘄' => 'w', '𝘅' => 'x', '𝘆' => 'y', '𝘇' => 'z', '𝘈' => 'A', '𝘉' => 'B', '𝘊' => 'C', '𝘋' => 'D', '𝘌' => 'E', '𝘍' => 'F', '𝘎' => 'G', '𝘏' => 'H', '𝘐' => 'I', '𝘑' => 'J', '𝘒' => 'K', '𝘓' => 'L', '𝘔' => 'M', '𝘕' => 'N', '𝘖' => 'O', '𝘗' => 'P', '𝘘' => 'Q', '𝘙' => 'R', '𝘚' => 'S', '𝘛' => 'T', '𝘜' => 'U', '𝘝' => 'V', '𝘞' => 'W', '𝘟' => 'X', '𝘠' => 'Y', '𝘡' => 'Z', '𝘢' => 'a', '𝘣' => 'b', '𝘤' => 'c', '𝘥' => 'd', '𝘦' => 'e', '𝘧' => 'f', '𝘨' => 'g', '𝘩' => 'h', '𝘪' => 'i', '𝘫' => 'j', '𝘬' => 'k', '𝘭' => 'l', '𝘮' => 'm', '𝘯' => 'n', '𝘰' => 'o', '𝘱' => 'p', '𝘲' => 'q', '𝘳' => 'r', '𝘴' => 's', '𝘵' => 't', '𝘶' => 'u', '𝘷' => 'v', '𝘸' => 'w', '𝘹' => 'x', '𝘺' => 'y', '𝘻' => 'z', '𝘼' => 'A', '𝘽' => 'B', '𝘾' => 'C', '𝘿' => 'D', '𝙀' => 'E', '𝙁' => 'F', '𝙂' => 'G', '𝙃' => 'H', '𝙄' => 'I', '𝙅' => 'J', '𝙆' => 'K', '𝙇' => 'L', '𝙈' => 'M', '𝙉' => 'N', '𝙊' => 'O', '𝙋' => 'P', '𝙌' => 'Q', '𝙍' => 'R', '𝙎' => 'S', '𝙏' => 'T', '𝙐' => 'U', '𝙑' => 'V', '𝙒' => 'W', '𝙓' => 'X', '𝙔' => 'Y', '𝙕' => 'Z', '𝙖' => 'a', '𝙗' => 'b', '𝙘' => 'c', '𝙙' => 'd', '𝙚' => 'e', '𝙛' => 'f', '𝙜' => 'g', '𝙝' => 'h', '𝙞' => 'i', '𝙟' => 'j', '𝙠' => 'k', '𝙡' => 'l', '𝙢' => 'm', '𝙣' => 'n', '𝙤' => 'o', '𝙥' => 'p', '𝙦' => 'q', '𝙧' => 'r', '𝙨' => 's', '𝙩' => 't', '𝙪' => 'u', '𝙫' => 'v', '𝙬' => 'w', '𝙭' => 'x', '𝙮' => 'y', '𝙯' => 'z', '𝙰' => 'A', '𝙱' => 'B', '𝙲' => 'C', '𝙳' => 'D', '𝙴' => 'E', '𝙵' => 'F', '𝙶' => 'G', '𝙷' => 'H', '𝙸' => 'I', '𝙹' => 'J', '𝙺' => 'K', '𝙻' => 'L', '𝙼' => 'M', '𝙽' => 'N', '𝙾' => 'O', '𝙿' => 'P', '𝚀' => 'Q', '𝚁' => 'R', '𝚂' => 'S', '𝚃' => 'T', '𝚄' => 'U', '𝚅' => 'V', '𝚆' => 'W', '𝚇' => 'X', '𝚈' => 'Y', '𝚉' => 'Z', '𝚊' => 'a', '𝚋' => 'b', '𝚌' => 'c', '𝚍' => 'd', '𝚎' => 'e', '𝚏' => 'f', '𝚐' => 'g', '𝚑' => 'h', '𝚒' => 'i', '𝚓' => 'j', '𝚔' => 'k', '𝚕' => 'l', '𝚖' => 'm', '𝚗' => 'n', '𝚘' => 'o', '𝚙' => 'p', '𝚚' => 'q', '𝚛' => 'r', '𝚜' => 's', '𝚝' => 't', '𝚞' => 'u', '𝚟' => 'v', '𝚠' => 'w', '𝚡' => 'x', '𝚢' => 'y', '𝚣' => 'z', '𝚤' => 'ı', '𝚥' => 'ȷ', '𝚨' => 'Α', '𝚩' => 'Β', '𝚪' => 'Γ', '𝚫' => 'Δ', '𝚬' => 'Ε', '𝚭' => 'Ζ', '𝚮' => 'Η', '𝚯' => 'Θ', '𝚰' => 'Ι', '𝚱' => 'Κ', '𝚲' => 'Λ', '𝚳' => 'Μ', '𝚴' => 'Ν', '𝚵' => 'Ξ', '𝚶' => 'Ο', '𝚷' => 'Π', '𝚸' => 'Ρ', '𝚹' => 'Θ', '𝚺' => 'Σ', '𝚻' => 'Τ', '𝚼' => 'Υ', '𝚽' => 'Φ', '𝚾' => 'Χ', '𝚿' => 'Ψ', '𝛀' => 'Ω', '𝛁' => '∇', '𝛂' => 'α', '𝛃' => 'β', '𝛄' => 'γ', '𝛅' => 'δ', '𝛆' => 'ε', '𝛇' => 'ζ', '𝛈' => 'η', '𝛉' => 'θ', '𝛊' => 'ι', '𝛋' => 'κ', '𝛌' => 'λ', '𝛍' => 'μ', '𝛎' => 'ν', '𝛏' => 'ξ', '𝛐' => 'ο', '𝛑' => 'π', '𝛒' => 'ρ', '𝛓' => 'ς', '𝛔' => 'σ', '𝛕' => 'τ', '𝛖' => 'υ', '𝛗' => 'φ', '𝛘' => 'χ', '𝛙' => 'ψ', '𝛚' => 'ω', '𝛛' => '∂', '𝛜' => 'ε', '𝛝' => 'θ', '𝛞' => 'κ', '𝛟' => 'φ', '𝛠' => 'ρ', '𝛡' => 'π', '𝛢' => 'Α', '𝛣' => 'Β', '𝛤' => 'Γ', '𝛥' => 'Δ', '𝛦' => 'Ε', '𝛧' => 'Ζ', '𝛨' => 'Η', '𝛩' => 'Θ', '𝛪' => 'Ι', '𝛫' => 'Κ', '𝛬' => 'Λ', '𝛭' => 'Μ', '𝛮' => 'Ν', '𝛯' => 'Ξ', '𝛰' => 'Ο', '𝛱' => 'Π', '𝛲' => 'Ρ', '𝛳' => 'Θ', '𝛴' => 'Σ', '𝛵' => 'Τ', '𝛶' => 'Υ', '𝛷' => 'Φ', '𝛸' => 'Χ', '𝛹' => 'Ψ', '𝛺' => 'Ω', '𝛻' => '∇', '𝛼' => 'α', '𝛽' => 'β', '𝛾' => 'γ', '𝛿' => 'δ', '𝜀' => 'ε', '𝜁' => 'ζ', '𝜂' => 'η', '𝜃' => 'θ', '𝜄' => 'ι', '𝜅' => 'κ', '𝜆' => 'λ', '𝜇' => 'μ', '𝜈' => 'ν', '𝜉' => 'ξ', '𝜊' => 'ο', '𝜋' => 'π', '𝜌' => 'ρ', '𝜍' => 'ς', '𝜎' => 'σ', '𝜏' => 'τ', '𝜐' => 'υ', '𝜑' => 'φ', '𝜒' => 'χ', '𝜓' => 'ψ', '𝜔' => 'ω', '𝜕' => '∂', '𝜖' => 'ε', '𝜗' => 'θ', '𝜘' => 'κ', '𝜙' => 'φ', '𝜚' => 'ρ', '𝜛' => 'π', '𝜜' => 'Α', '𝜝' => 'Β', '𝜞' => 'Γ', '𝜟' => 'Δ', '𝜠' => 'Ε', '𝜡' => 'Ζ', '𝜢' => 'Η', '𝜣' => 'Θ', '𝜤' => 'Ι', '𝜥' => 'Κ', '𝜦' => 'Λ', '𝜧' => 'Μ', '𝜨' => 'Ν', '𝜩' => 'Ξ', '𝜪' => 'Ο', '𝜫' => 'Π', '𝜬' => 'Ρ', '𝜭' => 'Θ', '𝜮' => 'Σ', '𝜯' => 'Τ', '𝜰' => 'Υ', '𝜱' => 'Φ', '𝜲' => 'Χ', '𝜳' => 'Ψ', '𝜴' => 'Ω', '𝜵' => '∇', '𝜶' => 'α', '𝜷' => 'β', '𝜸' => 'γ', '𝜹' => 'δ', '𝜺' => 'ε', '𝜻' => 'ζ', '𝜼' => 'η', '𝜽' => 'θ', '𝜾' => 'ι', '𝜿' => 'κ', '𝝀' => 'λ', '𝝁' => 'μ', '𝝂' => 'ν', '𝝃' => 'ξ', '𝝄' => 'ο', '𝝅' => 'π', '𝝆' => 'ρ', '𝝇' => 'ς', '𝝈' => 'σ', '𝝉' => 'τ', '𝝊' => 'υ', '𝝋' => 'φ', '𝝌' => 'χ', '𝝍' => 'ψ', '𝝎' => 'ω', '𝝏' => '∂', '𝝐' => 'ε', '𝝑' => 'θ', '𝝒' => 'κ', '𝝓' => 'φ', '𝝔' => 'ρ', '𝝕' => 'π', '𝝖' => 'Α', '𝝗' => 'Β', '𝝘' => 'Γ', '𝝙' => 'Δ', '𝝚' => 'Ε', '𝝛' => 'Ζ', '𝝜' => 'Η', '𝝝' => 'Θ', '𝝞' => 'Ι', '𝝟' => 'Κ', '𝝠' => 'Λ', '𝝡' => 'Μ', '𝝢' => 'Ν', '𝝣' => 'Ξ', '𝝤' => 'Ο', '𝝥' => 'Π', '𝝦' => 'Ρ', '𝝧' => 'Θ', '𝝨' => 'Σ', '𝝩' => 'Τ', '𝝪' => 'Υ', '𝝫' => 'Φ', '𝝬' => 'Χ', '𝝭' => 'Ψ', '𝝮' => 'Ω', '𝝯' => '∇', '𝝰' => 'α', '𝝱' => 'β', '𝝲' => 'γ', '𝝳' => 'δ', '𝝴' => 'ε', '𝝵' => 'ζ', '𝝶' => 'η', '𝝷' => 'θ', '𝝸' => 'ι', '𝝹' => 'κ', '𝝺' => 'λ', '𝝻' => 'μ', '𝝼' => 'ν', '𝝽' => 'ξ', '𝝾' => 'ο', '𝝿' => 'π', '𝞀' => 'ρ', '𝞁' => 'ς', '𝞂' => 'σ', '𝞃' => 'τ', '𝞄' => 'υ', '𝞅' => 'φ', '𝞆' => 'χ', '𝞇' => 'ψ', '𝞈' => 'ω', '𝞉' => '∂', '𝞊' => 'ε', '𝞋' => 'θ', '𝞌' => 'κ', '𝞍' => 'φ', '𝞎' => 'ρ', '𝞏' => 'π', '𝞐' => 'Α', '𝞑' => 'Β', '𝞒' => 'Γ', '𝞓' => 'Δ', '𝞔' => 'Ε', '𝞕' => 'Ζ', '𝞖' => 'Η', '𝞗' => 'Θ', '𝞘' => 'Ι', '𝞙' => 'Κ', '𝞚' => 'Λ', '𝞛' => 'Μ', '𝞜' => 'Ν', '𝞝' => 'Ξ', '𝞞' => 'Ο', '𝞟' => 'Π', '𝞠' => 'Ρ', '𝞡' => 'Θ', '𝞢' => 'Σ', '𝞣' => 'Τ', '𝞤' => 'Υ', '𝞥' => 'Φ', '𝞦' => 'Χ', '𝞧' => 'Ψ', '𝞨' => 'Ω', '𝞩' => '∇', '𝞪' => 'α', '𝞫' => 'β', '𝞬' => 'γ', '𝞭' => 'δ', '𝞮' => 'ε', '𝞯' => 'ζ', '𝞰' => 'η', '𝞱' => 'θ', '𝞲' => 'ι', '𝞳' => 'κ', '𝞴' => 'λ', '𝞵' => 'μ', '𝞶' => 'ν', '𝞷' => 'ξ', '𝞸' => 'ο', '𝞹' => 'π', '𝞺' => 'ρ', '𝞻' => 'ς', '𝞼' => 'σ', '𝞽' => 'τ', '𝞾' => 'υ', '𝞿' => 'φ', '𝟀' => 'χ', '𝟁' => 'ψ', '𝟂' => 'ω', '𝟃' => '∂', '𝟄' => 'ε', '𝟅' => 'θ', '𝟆' => 'κ', '𝟇' => 'φ', '𝟈' => 'ρ', '𝟉' => 'π', '𝟊' => 'Ϝ', '𝟋' => 'ϝ', '𝟎' => '0', '𝟏' => '1', '𝟐' => '2', '𝟑' => '3', '𝟒' => '4', '𝟓' => '5', '𝟔' => '6', '𝟕' => '7', '𝟖' => '8', '𝟗' => '9', '𝟘' => '0', '𝟙' => '1', '𝟚' => '2', '𝟛' => '3', '𝟜' => '4', '𝟝' => '5', '𝟞' => '6', '𝟟' => '7', '𝟠' => '8', '𝟡' => '9', '𝟢' => '0', '𝟣' => '1', '𝟤' => '2', '𝟥' => '3', '𝟦' => '4', '𝟧' => '5', '𝟨' => '6', '𝟩' => '7', '𝟪' => '8', '𝟫' => '9', '𝟬' => '0', '𝟭' => '1', '𝟮' => '2', '𝟯' => '3', '𝟰' => '4', '𝟱' => '5', '𝟲' => '6', '𝟳' => '7', '𝟴' => '8', '𝟵' => '9', '𝟶' => '0', '𝟷' => '1', '𝟸' => '2', '𝟹' => '3', '𝟺' => '4', '𝟻' => '5', '𝟼' => '6', '𝟽' => '7', '𝟾' => '8', '𝟿' => '9', '𞸀' => 'ا', '𞸁' => 'ب', '𞸂' => 'ج', '𞸃' => 'د', '𞸅' => 'و', '𞸆' => 'ز', '𞸇' => 'ح', '𞸈' => 'ط', '𞸉' => 'ي', '𞸊' => 'ك', '𞸋' => 'ل', '𞸌' => 'م', '𞸍' => 'ن', '𞸎' => 'س', '𞸏' => 'ع', '𞸐' => 'ف', '𞸑' => 'ص', '𞸒' => 'ق', '𞸓' => 'ر', '𞸔' => 'ش', '𞸕' => 'ت', '𞸖' => 'ث', '𞸗' => 'خ', '𞸘' => 'ذ', '𞸙' => 'ض', '𞸚' => 'ظ', '𞸛' => 'غ', '𞸜' => 'ٮ', '𞸝' => 'ں', '𞸞' => 'ڡ', '𞸟' => 'ٯ', '𞸡' => 'ب', '𞸢' => 'ج', '𞸤' => 'ه', '𞸧' => 'ح', '𞸩' => 'ي', '𞸪' => 'ك', '𞸫' => 'ل', '𞸬' => 'م', '𞸭' => 'ن', '𞸮' => 'س', '𞸯' => 'ع', '𞸰' => 'ف', '𞸱' => 'ص', '𞸲' => 'ق', '𞸴' => 'ش', '𞸵' => 'ت', '𞸶' => 'ث', '𞸷' => 'خ', '𞸹' => 'ض', '𞸻' => 'غ', '𞹂' => 'ج', '𞹇' => 'ح', '𞹉' => 'ي', '𞹋' => 'ل', '𞹍' => 'ن', '𞹎' => 'س', '𞹏' => 'ع', '𞹑' => 'ص', '𞹒' => 'ق', '𞹔' => 'ش', '𞹗' => 'خ', '𞹙' => 'ض', '𞹛' => 'غ', '𞹝' => 'ں', '𞹟' => 'ٯ', '𞹡' => 'ب', '𞹢' => 'ج', '𞹤' => 'ه', '𞹧' => 'ح', '𞹨' => 'ط', '𞹩' => 'ي', '𞹪' => 'ك', '𞹬' => 'م', '𞹭' => 'ن', '𞹮' => 'س', '𞹯' => 'ع', '𞹰' => 'ف', '𞹱' => 'ص', '𞹲' => 'ق', '𞹴' => 'ش', '𞹵' => 'ت', '𞹶' => 'ث', '𞹷' => 'خ', '𞹹' => 'ض', '𞹺' => 'ظ', '𞹻' => 'غ', '𞹼' => 'ٮ', '𞹾' => 'ڡ', '𞺀' => 'ا', '𞺁' => 'ب', '𞺂' => 'ج', '𞺃' => 'د', '𞺄' => 'ه', '𞺅' => 'و', '𞺆' => 'ز', '𞺇' => 'ح', '𞺈' => 'ط', '𞺉' => 'ي', '𞺋' => 'ل', '𞺌' => 'م', '𞺍' => 'ن', '𞺎' => 'س', '𞺏' => 'ع', '𞺐' => 'ف', '𞺑' => 'ص', '𞺒' => 'ق', '𞺓' => 'ر', '𞺔' => 'ش', '𞺕' => 'ت', '𞺖' => 'ث', '𞺗' => 'خ', '𞺘' => 'ذ', '𞺙' => 'ض', '𞺚' => 'ظ', '𞺛' => 'غ', '𞺡' => 'ب', '𞺢' => 'ج', '𞺣' => 'د', '𞺥' => 'و', '𞺦' => 'ز', '𞺧' => 'ح', '𞺨' => 'ط', '𞺩' => 'ي', '𞺫' => 'ل', '𞺬' => 'م', '𞺭' => 'ن', '𞺮' => 'س', '𞺯' => 'ع', '𞺰' => 'ف', '𞺱' => 'ص', '𞺲' => 'ق', '𞺳' => 'ر', '𞺴' => 'ش', '𞺵' => 'ت', '𞺶' => 'ث', '𞺷' => 'خ', '𞺸' => 'ذ', '𞺹' => 'ض', '𞺺' => 'ظ', '𞺻' => 'غ', '🄀' => '0.', '🄁' => '0,', '🄂' => '1,', '🄃' => '2,', '🄄' => '3,', '🄅' => '4,', '🄆' => '5,', '🄇' => '6,', '🄈' => '7,', '🄉' => '8,', '🄊' => '9,', '🄐' => '(A)', '🄑' => '(B)', '🄒' => '(C)', '🄓' => '(D)', '🄔' => '(E)', '🄕' => '(F)', '🄖' => '(G)', '🄗' => '(H)', '🄘' => '(I)', '🄙' => '(J)', '🄚' => '(K)', '🄛' => '(L)', '🄜' => '(M)', '🄝' => '(N)', '🄞' => '(O)', '🄟' => '(P)', '🄠' => '(Q)', '🄡' => '(R)', '🄢' => '(S)', '🄣' => '(T)', '🄤' => '(U)', '🄥' => '(V)', '🄦' => '(W)', '🄧' => '(X)', '🄨' => '(Y)', '🄩' => '(Z)', '🄪' => '〔S〕', '🄫' => 'C', '🄬' => 'R', '🄭' => 'CD', '🄮' => 'WZ', '🄰' => 'A', '🄱' => 'B', '🄲' => 'C', '🄳' => 'D', '🄴' => 'E', '🄵' => 'F', '🄶' => 'G', '🄷' => 'H', '🄸' => 'I', '🄹' => 'J', '🄺' => 'K', '🄻' => 'L', '🄼' => 'M', '🄽' => 'N', '🄾' => 'O', '🄿' => 'P', '🅀' => 'Q', '🅁' => 'R', '🅂' => 'S', '🅃' => 'T', '🅄' => 'U', '🅅' => 'V', '🅆' => 'W', '🅇' => 'X', '🅈' => 'Y', '🅉' => 'Z', '🅊' => 'HV', '🅋' => 'MV', '🅌' => 'SD', '🅍' => 'SS', '🅎' => 'PPV', '🅏' => 'WC', '🅪' => 'MC', '🅫' => 'MD', '🅬' => 'MR', '🆐' => 'DJ', '🈀' => 'ほか', '🈁' => 'ココ', '🈂' => 'サ', '🈐' => '手', '🈑' => '字', '🈒' => '双', '🈓' => 'デ', '🈔' => '二', '🈕' => '多', '🈖' => '解', '🈗' => '天', '🈘' => '交', '🈙' => '映', '🈚' => '無', '🈛' => '料', '🈜' => '前', '🈝' => '後', '🈞' => '再', '🈟' => '新', '🈠' => '初', '🈡' => '終', '🈢' => '生', '🈣' => '販', '🈤' => '声', '🈥' => '吹', '🈦' => '演', '🈧' => '投', '🈨' => '捕', '🈩' => '一', '🈪' => '三', '🈫' => '遊', '🈬' => '左', '🈭' => '中', '🈮' => '右', '🈯' => '指', '🈰' => '走', '🈱' => '打', '🈲' => '禁', '🈳' => '空', '🈴' => '合', '🈵' => '満', '🈶' => '有', '🈷' => '月', '🈸' => '申', '🈹' => '割', '🈺' => '営', '🈻' => '配', '🉀' => '〔本〕', '🉁' => '〔三〕', '🉂' => '〔二〕', '🉃' => '〔安〕', '🉄' => '〔点〕', '🉅' => '〔打〕', '🉆' => '〔盗〕', '🉇' => '〔勝〕', '🉈' => '〔敗〕', '🉐' => '得', '🉑' => '可', '🯰' => '0', '🯱' => '1', '🯲' => '2', '🯳' => '3', '🯴' => '4', '🯵' => '5', '🯶' => '6', '🯷' => '7', '🯸' => '8', '🯹' => '9', ); 'À', 'Á' => 'Á', 'Â' => 'Â', 'Ã' => 'Ã', 'Ä' => 'Ä', 'Å' => 'Å', 'Ç' => 'Ç', 'È' => 'È', 'É' => 'É', 'Ê' => 'Ê', 'Ë' => 'Ë', 'Ì' => 'Ì', 'Í' => 'Í', 'Î' => 'Î', 'Ï' => 'Ï', 'Ñ' => 'Ñ', 'Ò' => 'Ò', 'Ó' => 'Ó', 'Ô' => 'Ô', 'Õ' => 'Õ', 'Ö' => 'Ö', 'Ù' => 'Ù', 'Ú' => 'Ú', 'Û' => 'Û', 'Ü' => 'Ü', 'Ý' => 'Ý', 'à' => 'à', 'á' => 'á', 'â' => 'â', 'ã' => 'ã', 'ä' => 'ä', 'å' => 'å', 'ç' => 'ç', 'è' => 'è', 'é' => 'é', 'ê' => 'ê', 'ë' => 'ë', 'ì' => 'ì', 'í' => 'í', 'î' => 'î', 'ï' => 'ï', 'ñ' => 'ñ', 'ò' => 'ò', 'ó' => 'ó', 'ô' => 'ô', 'õ' => 'õ', 'ö' => 'ö', 'ù' => 'ù', 'ú' => 'ú', 'û' => 'û', 'ü' => 'ü', 'ý' => 'ý', 'ÿ' => 'ÿ', 'Ā' => 'Ā', 'ā' => 'ā', 'Ă' => 'Ă', 'ă' => 'ă', 'Ą' => 'Ą', 'ą' => 'ą', 'Ć' => 'Ć', 'ć' => 'ć', 'Ĉ' => 'Ĉ', 'ĉ' => 'ĉ', 'Ċ' => 'Ċ', 'ċ' => 'ċ', 'Č' => 'Č', 'č' => 'č', 'Ď' => 'Ď', 'ď' => 'ď', 'Ē' => 'Ē', 'ē' => 'ē', 'Ĕ' => 'Ĕ', 'ĕ' => 'ĕ', 'Ė' => 'Ė', 'ė' => 'ė', 'Ę' => 'Ę', 'ę' => 'ę', 'Ě' => 'Ě', 'ě' => 'ě', 'Ĝ' => 'Ĝ', 'ĝ' => 'ĝ', 'Ğ' => 'Ğ', 'ğ' => 'ğ', 'Ġ' => 'Ġ', 'ġ' => 'ġ', 'Ģ' => 'Ģ', 'ģ' => 'ģ', 'Ĥ' => 'Ĥ', 'ĥ' => 'ĥ', 'Ĩ' => 'Ĩ', 'ĩ' => 'ĩ', 'Ī' => 'Ī', 'ī' => 'ī', 'Ĭ' => 'Ĭ', 'ĭ' => 'ĭ', 'Į' => 'Į', 'į' => 'į', 'İ' => 'İ', 'Ĵ' => 'Ĵ', 'ĵ' => 'ĵ', 'Ķ' => 'Ķ', 'ķ' => 'ķ', 'Ĺ' => 'Ĺ', 'ĺ' => 'ĺ', 'Ļ' => 'Ļ', 'ļ' => 'ļ', 'Ľ' => 'Ľ', 'ľ' => 'ľ', 'Ń' => 'Ń', 'ń' => 'ń', 'Ņ' => 'Ņ', 'ņ' => 'ņ', 'Ň' => 'Ň', 'ň' => 'ň', 'Ō' => 'Ō', 'ō' => 'ō', 'Ŏ' => 'Ŏ', 'ŏ' => 'ŏ', 'Ő' => 'Ő', 'ő' => 'ő', 'Ŕ' => 'Ŕ', 'ŕ' => 'ŕ', 'Ŗ' => 'Ŗ', 'ŗ' => 'ŗ', 'Ř' => 'Ř', 'ř' => 'ř', 'Ś' => 'Ś', 'ś' => 'ś', 'Ŝ' => 'Ŝ', 'ŝ' => 'ŝ', 'Ş' => 'Ş', 'ş' => 'ş', 'Š' => 'Š', 'š' => 'š', 'Ţ' => 'Ţ', 'ţ' => 'ţ', 'Ť' => 'Ť', 'ť' => 'ť', 'Ũ' => 'Ũ', 'ũ' => 'ũ', 'Ū' => 'Ū', 'ū' => 'ū', 'Ŭ' => 'Ŭ', 'ŭ' => 'ŭ', 'Ů' => 'Ů', 'ů' => 'ů', 'Ű' => 'Ű', 'ű' => 'ű', 'Ų' => 'Ų', 'ų' => 'ų', 'Ŵ' => 'Ŵ', 'ŵ' => 'ŵ', 'Ŷ' => 'Ŷ', 'ŷ' => 'ŷ', 'Ÿ' => 'Ÿ', 'Ź' => 'Ź', 'ź' => 'ź', 'Ż' => 'Ż', 'ż' => 'ż', 'Ž' => 'Ž', 'ž' => 'ž', 'Ơ' => 'Ơ', 'ơ' => 'ơ', 'Ư' => 'Ư', 'ư' => 'ư', 'Ǎ' => 'Ǎ', 'ǎ' => 'ǎ', 'Ǐ' => 'Ǐ', 'ǐ' => 'ǐ', 'Ǒ' => 'Ǒ', 'ǒ' => 'ǒ', 'Ǔ' => 'Ǔ', 'ǔ' => 'ǔ', 'Ǖ' => 'Ǖ', 'ǖ' => 'ǖ', 'Ǘ' => 'Ǘ', 'ǘ' => 'ǘ', 'Ǚ' => 'Ǚ', 'ǚ' => 'ǚ', 'Ǜ' => 'Ǜ', 'ǜ' => 'ǜ', 'Ǟ' => 'Ǟ', 'ǟ' => 'ǟ', 'Ǡ' => 'Ǡ', 'ǡ' => 'ǡ', 'Ǣ' => 'Ǣ', 'ǣ' => 'ǣ', 'Ǧ' => 'Ǧ', 'ǧ' => 'ǧ', 'Ǩ' => 'Ǩ', 'ǩ' => 'ǩ', 'Ǫ' => 'Ǫ', 'ǫ' => 'ǫ', 'Ǭ' => 'Ǭ', 'ǭ' => 'ǭ', 'Ǯ' => 'Ǯ', 'ǯ' => 'ǯ', 'ǰ' => 'ǰ', 'Ǵ' => 'Ǵ', 'ǵ' => 'ǵ', 'Ǹ' => 'Ǹ', 'ǹ' => 'ǹ', 'Ǻ' => 'Ǻ', 'ǻ' => 'ǻ', 'Ǽ' => 'Ǽ', 'ǽ' => 'ǽ', 'Ǿ' => 'Ǿ', 'ǿ' => 'ǿ', 'Ȁ' => 'Ȁ', 'ȁ' => 'ȁ', 'Ȃ' => 'Ȃ', 'ȃ' => 'ȃ', 'Ȅ' => 'Ȅ', 'ȅ' => 'ȅ', 'Ȇ' => 'Ȇ', 'ȇ' => 'ȇ', 'Ȉ' => 'Ȉ', 'ȉ' => 'ȉ', 'Ȋ' => 'Ȋ', 'ȋ' => 'ȋ', 'Ȍ' => 'Ȍ', 'ȍ' => 'ȍ', 'Ȏ' => 'Ȏ', 'ȏ' => 'ȏ', 'Ȑ' => 'Ȑ', 'ȑ' => 'ȑ', 'Ȓ' => 'Ȓ', 'ȓ' => 'ȓ', 'Ȕ' => 'Ȕ', 'ȕ' => 'ȕ', 'Ȗ' => 'Ȗ', 'ȗ' => 'ȗ', 'Ș' => 'Ș', 'ș' => 'ș', 'Ț' => 'Ț', 'ț' => 'ț', 'Ȟ' => 'Ȟ', 'ȟ' => 'ȟ', 'Ȧ' => 'Ȧ', 'ȧ' => 'ȧ', 'Ȩ' => 'Ȩ', 'ȩ' => 'ȩ', 'Ȫ' => 'Ȫ', 'ȫ' => 'ȫ', 'Ȭ' => 'Ȭ', 'ȭ' => 'ȭ', 'Ȯ' => 'Ȯ', 'ȯ' => 'ȯ', 'Ȱ' => 'Ȱ', 'ȱ' => 'ȱ', 'Ȳ' => 'Ȳ', 'ȳ' => 'ȳ', '̀' => '̀', '́' => '́', '̓' => '̓', '̈́' => '̈́', 'ʹ' => 'ʹ', ';' => ';', '΅' => '΅', 'Ά' => 'Ά', '·' => '·', 'Έ' => 'Έ', 'Ή' => 'Ή', 'Ί' => 'Ί', 'Ό' => 'Ό', 'Ύ' => 'Ύ', 'Ώ' => 'Ώ', 'ΐ' => 'ΐ', 'Ϊ' => 'Ϊ', 'Ϋ' => 'Ϋ', 'ά' => 'ά', 'έ' => 'έ', 'ή' => 'ή', 'ί' => 'ί', 'ΰ' => 'ΰ', 'ϊ' => 'ϊ', 'ϋ' => 'ϋ', 'ό' => 'ό', 'ύ' => 'ύ', 'ώ' => 'ώ', 'ϓ' => 'ϓ', 'ϔ' => 'ϔ', 'Ѐ' => 'Ѐ', 'Ё' => 'Ё', 'Ѓ' => 'Ѓ', 'Ї' => 'Ї', 'Ќ' => 'Ќ', 'Ѝ' => 'Ѝ', 'Ў' => 'Ў', 'Й' => 'Й', 'й' => 'й', 'ѐ' => 'ѐ', 'ё' => 'ё', 'ѓ' => 'ѓ', 'ї' => 'ї', 'ќ' => 'ќ', 'ѝ' => 'ѝ', 'ў' => 'ў', 'Ѷ' => 'Ѷ', 'ѷ' => 'ѷ', 'Ӂ' => 'Ӂ', 'ӂ' => 'ӂ', 'Ӑ' => 'Ӑ', 'ӑ' => 'ӑ', 'Ӓ' => 'Ӓ', 'ӓ' => 'ӓ', 'Ӗ' => 'Ӗ', 'ӗ' => 'ӗ', 'Ӛ' => 'Ӛ', 'ӛ' => 'ӛ', 'Ӝ' => 'Ӝ', 'ӝ' => 'ӝ', 'Ӟ' => 'Ӟ', 'ӟ' => 'ӟ', 'Ӣ' => 'Ӣ', 'ӣ' => 'ӣ', 'Ӥ' => 'Ӥ', 'ӥ' => 'ӥ', 'Ӧ' => 'Ӧ', 'ӧ' => 'ӧ', 'Ӫ' => 'Ӫ', 'ӫ' => 'ӫ', 'Ӭ' => 'Ӭ', 'ӭ' => 'ӭ', 'Ӯ' => 'Ӯ', 'ӯ' => 'ӯ', 'Ӱ' => 'Ӱ', 'ӱ' => 'ӱ', 'Ӳ' => 'Ӳ', 'ӳ' => 'ӳ', 'Ӵ' => 'Ӵ', 'ӵ' => 'ӵ', 'Ӹ' => 'Ӹ', 'ӹ' => 'ӹ', 'آ' => 'آ', 'أ' => 'أ', 'ؤ' => 'ؤ', 'إ' => 'إ', 'ئ' => 'ئ', 'ۀ' => 'ۀ', 'ۂ' => 'ۂ', 'ۓ' => 'ۓ', 'ऩ' => 'ऩ', 'ऱ' => 'ऱ', 'ऴ' => 'ऴ', 'क़' => 'क़', 'ख़' => 'ख़', 'ग़' => 'ग़', 'ज़' => 'ज़', 'ड़' => 'ड़', 'ढ़' => 'ढ़', 'फ़' => 'फ़', 'य़' => 'य़', 'ো' => 'ো', 'ৌ' => 'ৌ', 'ড়' => 'ড়', 'ঢ়' => 'ঢ়', 'য়' => 'য়', 'ਲ਼' => 'ਲ਼', 'ਸ਼' => 'ਸ਼', 'ਖ਼' => 'ਖ਼', 'ਗ਼' => 'ਗ਼', 'ਜ਼' => 'ਜ਼', 'ਫ਼' => 'ਫ਼', 'ୈ' => 'ୈ', 'ୋ' => 'ୋ', 'ୌ' => 'ୌ', 'ଡ଼' => 'ଡ଼', 'ଢ଼' => 'ଢ଼', 'ஔ' => 'ஔ', 'ொ' => 'ொ', 'ோ' => 'ோ', 'ௌ' => 'ௌ', 'ై' => 'ై', 'ೀ' => 'ೀ', 'ೇ' => 'ೇ', 'ೈ' => 'ೈ', 'ೊ' => 'ೊ', 'ೋ' => 'ೋ', 'ൊ' => 'ൊ', 'ോ' => 'ോ', 'ൌ' => 'ൌ', 'ේ' => 'ේ', 'ො' => 'ො', 'ෝ' => 'ෝ', 'ෞ' => 'ෞ', 'གྷ' => 'གྷ', 'ཌྷ' => 'ཌྷ', 'དྷ' => 'དྷ', 'བྷ' => 'བྷ', 'ཛྷ' => 'ཛྷ', 'ཀྵ' => 'ཀྵ', 'ཱི' => 'ཱི', 'ཱུ' => 'ཱུ', 'ྲྀ' => 'ྲྀ', 'ླྀ' => 'ླྀ', 'ཱྀ' => 'ཱྀ', 'ྒྷ' => 'ྒྷ', 'ྜྷ' => 'ྜྷ', 'ྡྷ' => 'ྡྷ', 'ྦྷ' => 'ྦྷ', 'ྫྷ' => 'ྫྷ', 'ྐྵ' => 'ྐྵ', 'ဦ' => 'ဦ', 'ᬆ' => 'ᬆ', 'ᬈ' => 'ᬈ', 'ᬊ' => 'ᬊ', 'ᬌ' => 'ᬌ', 'ᬎ' => 'ᬎ', 'ᬒ' => 'ᬒ', 'ᬻ' => 'ᬻ', 'ᬽ' => 'ᬽ', 'ᭀ' => 'ᭀ', 'ᭁ' => 'ᭁ', 'ᭃ' => 'ᭃ', 'Ḁ' => 'Ḁ', 'ḁ' => 'ḁ', 'Ḃ' => 'Ḃ', 'ḃ' => 'ḃ', 'Ḅ' => 'Ḅ', 'ḅ' => 'ḅ', 'Ḇ' => 'Ḇ', 'ḇ' => 'ḇ', 'Ḉ' => 'Ḉ', 'ḉ' => 'ḉ', 'Ḋ' => 'Ḋ', 'ḋ' => 'ḋ', 'Ḍ' => 'Ḍ', 'ḍ' => 'ḍ', 'Ḏ' => 'Ḏ', 'ḏ' => 'ḏ', 'Ḑ' => 'Ḑ', 'ḑ' => 'ḑ', 'Ḓ' => 'Ḓ', 'ḓ' => 'ḓ', 'Ḕ' => 'Ḕ', 'ḕ' => 'ḕ', 'Ḗ' => 'Ḗ', 'ḗ' => 'ḗ', 'Ḙ' => 'Ḙ', 'ḙ' => 'ḙ', 'Ḛ' => 'Ḛ', 'ḛ' => 'ḛ', 'Ḝ' => 'Ḝ', 'ḝ' => 'ḝ', 'Ḟ' => 'Ḟ', 'ḟ' => 'ḟ', 'Ḡ' => 'Ḡ', 'ḡ' => 'ḡ', 'Ḣ' => 'Ḣ', 'ḣ' => 'ḣ', 'Ḥ' => 'Ḥ', 'ḥ' => 'ḥ', 'Ḧ' => 'Ḧ', 'ḧ' => 'ḧ', 'Ḩ' => 'Ḩ', 'ḩ' => 'ḩ', 'Ḫ' => 'Ḫ', 'ḫ' => 'ḫ', 'Ḭ' => 'Ḭ', 'ḭ' => 'ḭ', 'Ḯ' => 'Ḯ', 'ḯ' => 'ḯ', 'Ḱ' => 'Ḱ', 'ḱ' => 'ḱ', 'Ḳ' => 'Ḳ', 'ḳ' => 'ḳ', 'Ḵ' => 'Ḵ', 'ḵ' => 'ḵ', 'Ḷ' => 'Ḷ', 'ḷ' => 'ḷ', 'Ḹ' => 'Ḹ', 'ḹ' => 'ḹ', 'Ḻ' => 'Ḻ', 'ḻ' => 'ḻ', 'Ḽ' => 'Ḽ', 'ḽ' => 'ḽ', 'Ḿ' => 'Ḿ', 'ḿ' => 'ḿ', 'Ṁ' => 'Ṁ', 'ṁ' => 'ṁ', 'Ṃ' => 'Ṃ', 'ṃ' => 'ṃ', 'Ṅ' => 'Ṅ', 'ṅ' => 'ṅ', 'Ṇ' => 'Ṇ', 'ṇ' => 'ṇ', 'Ṉ' => 'Ṉ', 'ṉ' => 'ṉ', 'Ṋ' => 'Ṋ', 'ṋ' => 'ṋ', 'Ṍ' => 'Ṍ', 'ṍ' => 'ṍ', 'Ṏ' => 'Ṏ', 'ṏ' => 'ṏ', 'Ṑ' => 'Ṑ', 'ṑ' => 'ṑ', 'Ṓ' => 'Ṓ', 'ṓ' => 'ṓ', 'Ṕ' => 'Ṕ', 'ṕ' => 'ṕ', 'Ṗ' => 'Ṗ', 'ṗ' => 'ṗ', 'Ṙ' => 'Ṙ', 'ṙ' => 'ṙ', 'Ṛ' => 'Ṛ', 'ṛ' => 'ṛ', 'Ṝ' => 'Ṝ', 'ṝ' => 'ṝ', 'Ṟ' => 'Ṟ', 'ṟ' => 'ṟ', 'Ṡ' => 'Ṡ', 'ṡ' => 'ṡ', 'Ṣ' => 'Ṣ', 'ṣ' => 'ṣ', 'Ṥ' => 'Ṥ', 'ṥ' => 'ṥ', 'Ṧ' => 'Ṧ', 'ṧ' => 'ṧ', 'Ṩ' => 'Ṩ', 'ṩ' => 'ṩ', 'Ṫ' => 'Ṫ', 'ṫ' => 'ṫ', 'Ṭ' => 'Ṭ', 'ṭ' => 'ṭ', 'Ṯ' => 'Ṯ', 'ṯ' => 'ṯ', 'Ṱ' => 'Ṱ', 'ṱ' => 'ṱ', 'Ṳ' => 'Ṳ', 'ṳ' => 'ṳ', 'Ṵ' => 'Ṵ', 'ṵ' => 'ṵ', 'Ṷ' => 'Ṷ', 'ṷ' => 'ṷ', 'Ṹ' => 'Ṹ', 'ṹ' => 'ṹ', 'Ṻ' => 'Ṻ', 'ṻ' => 'ṻ', 'Ṽ' => 'Ṽ', 'ṽ' => 'ṽ', 'Ṿ' => 'Ṿ', 'ṿ' => 'ṿ', 'Ẁ' => 'Ẁ', 'ẁ' => 'ẁ', 'Ẃ' => 'Ẃ', 'ẃ' => 'ẃ', 'Ẅ' => 'Ẅ', 'ẅ' => 'ẅ', 'Ẇ' => 'Ẇ', 'ẇ' => 'ẇ', 'Ẉ' => 'Ẉ', 'ẉ' => 'ẉ', 'Ẋ' => 'Ẋ', 'ẋ' => 'ẋ', 'Ẍ' => 'Ẍ', 'ẍ' => 'ẍ', 'Ẏ' => 'Ẏ', 'ẏ' => 'ẏ', 'Ẑ' => 'Ẑ', 'ẑ' => 'ẑ', 'Ẓ' => 'Ẓ', 'ẓ' => 'ẓ', 'Ẕ' => 'Ẕ', 'ẕ' => 'ẕ', 'ẖ' => 'ẖ', 'ẗ' => 'ẗ', 'ẘ' => 'ẘ', 'ẙ' => 'ẙ', 'ẛ' => 'ẛ', 'Ạ' => 'Ạ', 'ạ' => 'ạ', 'Ả' => 'Ả', 'ả' => 'ả', 'Ấ' => 'Ấ', 'ấ' => 'ấ', 'Ầ' => 'Ầ', 'ầ' => 'ầ', 'Ẩ' => 'Ẩ', 'ẩ' => 'ẩ', 'Ẫ' => 'Ẫ', 'ẫ' => 'ẫ', 'Ậ' => 'Ậ', 'ậ' => 'ậ', 'Ắ' => 'Ắ', 'ắ' => 'ắ', 'Ằ' => 'Ằ', 'ằ' => 'ằ', 'Ẳ' => 'Ẳ', 'ẳ' => 'ẳ', 'Ẵ' => 'Ẵ', 'ẵ' => 'ẵ', 'Ặ' => 'Ặ', 'ặ' => 'ặ', 'Ẹ' => 'Ẹ', 'ẹ' => 'ẹ', 'Ẻ' => 'Ẻ', 'ẻ' => 'ẻ', 'Ẽ' => 'Ẽ', 'ẽ' => 'ẽ', 'Ế' => 'Ế', 'ế' => 'ế', 'Ề' => 'Ề', 'ề' => 'ề', 'Ể' => 'Ể', 'ể' => 'ể', 'Ễ' => 'Ễ', 'ễ' => 'ễ', 'Ệ' => 'Ệ', 'ệ' => 'ệ', 'Ỉ' => 'Ỉ', 'ỉ' => 'ỉ', 'Ị' => 'Ị', 'ị' => 'ị', 'Ọ' => 'Ọ', 'ọ' => 'ọ', 'Ỏ' => 'Ỏ', 'ỏ' => 'ỏ', 'Ố' => 'Ố', 'ố' => 'ố', 'Ồ' => 'Ồ', 'ồ' => 'ồ', 'Ổ' => 'Ổ', 'ổ' => 'ổ', 'Ỗ' => 'Ỗ', 'ỗ' => 'ỗ', 'Ộ' => 'Ộ', 'ộ' => 'ộ', 'Ớ' => 'Ớ', 'ớ' => 'ớ', 'Ờ' => 'Ờ', 'ờ' => 'ờ', 'Ở' => 'Ở', 'ở' => 'ở', 'Ỡ' => 'Ỡ', 'ỡ' => 'ỡ', 'Ợ' => 'Ợ', 'ợ' => 'ợ', 'Ụ' => 'Ụ', 'ụ' => 'ụ', 'Ủ' => 'Ủ', 'ủ' => 'ủ', 'Ứ' => 'Ứ', 'ứ' => 'ứ', 'Ừ' => 'Ừ', 'ừ' => 'ừ', 'Ử' => 'Ử', 'ử' => 'ử', 'Ữ' => 'Ữ', 'ữ' => 'ữ', 'Ự' => 'Ự', 'ự' => 'ự', 'Ỳ' => 'Ỳ', 'ỳ' => 'ỳ', 'Ỵ' => 'Ỵ', 'ỵ' => 'ỵ', 'Ỷ' => 'Ỷ', 'ỷ' => 'ỷ', 'Ỹ' => 'Ỹ', 'ỹ' => 'ỹ', 'ἀ' => 'ἀ', 'ἁ' => 'ἁ', 'ἂ' => 'ἂ', 'ἃ' => 'ἃ', 'ἄ' => 'ἄ', 'ἅ' => 'ἅ', 'ἆ' => 'ἆ', 'ἇ' => 'ἇ', 'Ἀ' => 'Ἀ', 'Ἁ' => 'Ἁ', 'Ἂ' => 'Ἂ', 'Ἃ' => 'Ἃ', 'Ἄ' => 'Ἄ', 'Ἅ' => 'Ἅ', 'Ἆ' => 'Ἆ', 'Ἇ' => 'Ἇ', 'ἐ' => 'ἐ', 'ἑ' => 'ἑ', 'ἒ' => 'ἒ', 'ἓ' => 'ἓ', 'ἔ' => 'ἔ', 'ἕ' => 'ἕ', 'Ἐ' => 'Ἐ', 'Ἑ' => 'Ἑ', 'Ἒ' => 'Ἒ', 'Ἓ' => 'Ἓ', 'Ἔ' => 'Ἔ', 'Ἕ' => 'Ἕ', 'ἠ' => 'ἠ', 'ἡ' => 'ἡ', 'ἢ' => 'ἢ', 'ἣ' => 'ἣ', 'ἤ' => 'ἤ', 'ἥ' => 'ἥ', 'ἦ' => 'ἦ', 'ἧ' => 'ἧ', 'Ἠ' => 'Ἠ', 'Ἡ' => 'Ἡ', 'Ἢ' => 'Ἢ', 'Ἣ' => 'Ἣ', 'Ἤ' => 'Ἤ', 'Ἥ' => 'Ἥ', 'Ἦ' => 'Ἦ', 'Ἧ' => 'Ἧ', 'ἰ' => 'ἰ', 'ἱ' => 'ἱ', 'ἲ' => 'ἲ', 'ἳ' => 'ἳ', 'ἴ' => 'ἴ', 'ἵ' => 'ἵ', 'ἶ' => 'ἶ', 'ἷ' => 'ἷ', 'Ἰ' => 'Ἰ', 'Ἱ' => 'Ἱ', 'Ἲ' => 'Ἲ', 'Ἳ' => 'Ἳ', 'Ἴ' => 'Ἴ', 'Ἵ' => 'Ἵ', 'Ἶ' => 'Ἶ', 'Ἷ' => 'Ἷ', 'ὀ' => 'ὀ', 'ὁ' => 'ὁ', 'ὂ' => 'ὂ', 'ὃ' => 'ὃ', 'ὄ' => 'ὄ', 'ὅ' => 'ὅ', 'Ὀ' => 'Ὀ', 'Ὁ' => 'Ὁ', 'Ὂ' => 'Ὂ', 'Ὃ' => 'Ὃ', 'Ὄ' => 'Ὄ', 'Ὅ' => 'Ὅ', 'ὐ' => 'ὐ', 'ὑ' => 'ὑ', 'ὒ' => 'ὒ', 'ὓ' => 'ὓ', 'ὔ' => 'ὔ', 'ὕ' => 'ὕ', 'ὖ' => 'ὖ', 'ὗ' => 'ὗ', 'Ὑ' => 'Ὑ', 'Ὓ' => 'Ὓ', 'Ὕ' => 'Ὕ', 'Ὗ' => 'Ὗ', 'ὠ' => 'ὠ', 'ὡ' => 'ὡ', 'ὢ' => 'ὢ', 'ὣ' => 'ὣ', 'ὤ' => 'ὤ', 'ὥ' => 'ὥ', 'ὦ' => 'ὦ', 'ὧ' => 'ὧ', 'Ὠ' => 'Ὠ', 'Ὡ' => 'Ὡ', 'Ὢ' => 'Ὢ', 'Ὣ' => 'Ὣ', 'Ὤ' => 'Ὤ', 'Ὥ' => 'Ὥ', 'Ὦ' => 'Ὦ', 'Ὧ' => 'Ὧ', 'ὰ' => 'ὰ', 'ά' => 'ά', 'ὲ' => 'ὲ', 'έ' => 'έ', 'ὴ' => 'ὴ', 'ή' => 'ή', 'ὶ' => 'ὶ', 'ί' => 'ί', 'ὸ' => 'ὸ', 'ό' => 'ό', 'ὺ' => 'ὺ', 'ύ' => 'ύ', 'ὼ' => 'ὼ', 'ώ' => 'ώ', 'ᾀ' => 'ᾀ', 'ᾁ' => 'ᾁ', 'ᾂ' => 'ᾂ', 'ᾃ' => 'ᾃ', 'ᾄ' => 'ᾄ', 'ᾅ' => 'ᾅ', 'ᾆ' => 'ᾆ', 'ᾇ' => 'ᾇ', 'ᾈ' => 'ᾈ', 'ᾉ' => 'ᾉ', 'ᾊ' => 'ᾊ', 'ᾋ' => 'ᾋ', 'ᾌ' => 'ᾌ', 'ᾍ' => 'ᾍ', 'ᾎ' => 'ᾎ', 'ᾏ' => 'ᾏ', 'ᾐ' => 'ᾐ', 'ᾑ' => 'ᾑ', 'ᾒ' => 'ᾒ', 'ᾓ' => 'ᾓ', 'ᾔ' => 'ᾔ', 'ᾕ' => 'ᾕ', 'ᾖ' => 'ᾖ', 'ᾗ' => 'ᾗ', 'ᾘ' => 'ᾘ', 'ᾙ' => 'ᾙ', 'ᾚ' => 'ᾚ', 'ᾛ' => 'ᾛ', 'ᾜ' => 'ᾜ', 'ᾝ' => 'ᾝ', 'ᾞ' => 'ᾞ', 'ᾟ' => 'ᾟ', 'ᾠ' => 'ᾠ', 'ᾡ' => 'ᾡ', 'ᾢ' => 'ᾢ', 'ᾣ' => 'ᾣ', 'ᾤ' => 'ᾤ', 'ᾥ' => 'ᾥ', 'ᾦ' => 'ᾦ', 'ᾧ' => 'ᾧ', 'ᾨ' => 'ᾨ', 'ᾩ' => 'ᾩ', 'ᾪ' => 'ᾪ', 'ᾫ' => 'ᾫ', 'ᾬ' => 'ᾬ', 'ᾭ' => 'ᾭ', 'ᾮ' => 'ᾮ', 'ᾯ' => 'ᾯ', 'ᾰ' => 'ᾰ', 'ᾱ' => 'ᾱ', 'ᾲ' => 'ᾲ', 'ᾳ' => 'ᾳ', 'ᾴ' => 'ᾴ', 'ᾶ' => 'ᾶ', 'ᾷ' => 'ᾷ', 'Ᾰ' => 'Ᾰ', 'Ᾱ' => 'Ᾱ', 'Ὰ' => 'Ὰ', 'Ά' => 'Ά', 'ᾼ' => 'ᾼ', 'ι' => 'ι', '῁' => '῁', 'ῂ' => 'ῂ', 'ῃ' => 'ῃ', 'ῄ' => 'ῄ', 'ῆ' => 'ῆ', 'ῇ' => 'ῇ', 'Ὲ' => 'Ὲ', 'Έ' => 'Έ', 'Ὴ' => 'Ὴ', 'Ή' => 'Ή', 'ῌ' => 'ῌ', '῍' => '῍', '῎' => '῎', '῏' => '῏', 'ῐ' => 'ῐ', 'ῑ' => 'ῑ', 'ῒ' => 'ῒ', 'ΐ' => 'ΐ', 'ῖ' => 'ῖ', 'ῗ' => 'ῗ', 'Ῐ' => 'Ῐ', 'Ῑ' => 'Ῑ', 'Ὶ' => 'Ὶ', 'Ί' => 'Ί', '῝' => '῝', '῞' => '῞', '῟' => '῟', 'ῠ' => 'ῠ', 'ῡ' => 'ῡ', 'ῢ' => 'ῢ', 'ΰ' => 'ΰ', 'ῤ' => 'ῤ', 'ῥ' => 'ῥ', 'ῦ' => 'ῦ', 'ῧ' => 'ῧ', 'Ῠ' => 'Ῠ', 'Ῡ' => 'Ῡ', 'Ὺ' => 'Ὺ', 'Ύ' => 'Ύ', 'Ῥ' => 'Ῥ', '῭' => '῭', '΅' => '΅', '`' => '`', 'ῲ' => 'ῲ', 'ῳ' => 'ῳ', 'ῴ' => 'ῴ', 'ῶ' => 'ῶ', 'ῷ' => 'ῷ', 'Ὸ' => 'Ὸ', 'Ό' => 'Ό', 'Ὼ' => 'Ὼ', 'Ώ' => 'Ώ', 'ῼ' => 'ῼ', '´' => '´', ' ' => ' ', ' ' => ' ', 'Ω' => 'Ω', 'K' => 'K', 'Å' => 'Å', '↚' => '↚', '↛' => '↛', '↮' => '↮', '⇍' => '⇍', '⇎' => '⇎', '⇏' => '⇏', '∄' => '∄', '∉' => '∉', '∌' => '∌', '∤' => '∤', '∦' => '∦', '≁' => '≁', '≄' => '≄', '≇' => '≇', '≉' => '≉', '≠' => '≠', '≢' => '≢', '≭' => '≭', '≮' => '≮', '≯' => '≯', '≰' => '≰', '≱' => '≱', '≴' => '≴', '≵' => '≵', '≸' => '≸', '≹' => '≹', '⊀' => '⊀', '⊁' => '⊁', '⊄' => '⊄', '⊅' => '⊅', '⊈' => '⊈', '⊉' => '⊉', '⊬' => '⊬', '⊭' => '⊭', '⊮' => '⊮', '⊯' => '⊯', '⋠' => '⋠', '⋡' => '⋡', '⋢' => '⋢', '⋣' => '⋣', '⋪' => '⋪', '⋫' => '⋫', '⋬' => '⋬', '⋭' => '⋭', '〈' => '〈', '〉' => '〉', '⫝̸' => '⫝̸', 'が' => 'が', 'ぎ' => 'ぎ', 'ぐ' => 'ぐ', 'げ' => 'げ', 'ご' => 'ご', 'ざ' => 'ざ', 'じ' => 'じ', 'ず' => 'ず', 'ぜ' => 'ぜ', 'ぞ' => 'ぞ', 'だ' => 'だ', 'ぢ' => 'ぢ', 'づ' => 'づ', 'で' => 'で', 'ど' => 'ど', 'ば' => 'ば', 'ぱ' => 'ぱ', 'び' => 'び', 'ぴ' => 'ぴ', 'ぶ' => 'ぶ', 'ぷ' => 'ぷ', 'べ' => 'べ', 'ぺ' => 'ぺ', 'ぼ' => 'ぼ', 'ぽ' => 'ぽ', 'ゔ' => 'ゔ', 'ゞ' => 'ゞ', 'ガ' => 'ガ', 'ギ' => 'ギ', 'グ' => 'グ', 'ゲ' => 'ゲ', 'ゴ' => 'ゴ', 'ザ' => 'ザ', 'ジ' => 'ジ', 'ズ' => 'ズ', 'ゼ' => 'ゼ', 'ゾ' => 'ゾ', 'ダ' => 'ダ', 'ヂ' => 'ヂ', 'ヅ' => 'ヅ', 'デ' => 'デ', 'ド' => 'ド', 'バ' => 'バ', 'パ' => 'パ', 'ビ' => 'ビ', 'ピ' => 'ピ', 'ブ' => 'ブ', 'プ' => 'プ', 'ベ' => 'ベ', 'ペ' => 'ペ', 'ボ' => 'ボ', 'ポ' => 'ポ', 'ヴ' => 'ヴ', 'ヷ' => 'ヷ', 'ヸ' => 'ヸ', 'ヹ' => 'ヹ', 'ヺ' => 'ヺ', 'ヾ' => 'ヾ', '豈' => '豈', '更' => '更', '車' => '車', '賈' => '賈', '滑' => '滑', '串' => '串', '句' => '句', '龜' => '龜', '龜' => '龜', '契' => '契', '金' => '金', '喇' => '喇', '奈' => '奈', '懶' => '懶', '癩' => '癩', '羅' => '羅', '蘿' => '蘿', '螺' => '螺', '裸' => '裸', '邏' => '邏', '樂' => '樂', '洛' => '洛', '烙' => '烙', '珞' => '珞', '落' => '落', '酪' => '酪', '駱' => '駱', '亂' => '亂', '卵' => '卵', '欄' => '欄', '爛' => '爛', '蘭' => '蘭', '鸞' => '鸞', '嵐' => '嵐', '濫' => '濫', '藍' => '藍', '襤' => '襤', '拉' => '拉', '臘' => '臘', '蠟' => '蠟', '廊' => '廊', '朗' => '朗', '浪' => '浪', '狼' => '狼', '郎' => '郎', '來' => '來', '冷' => '冷', '勞' => '勞', '擄' => '擄', '櫓' => '櫓', '爐' => '爐', '盧' => '盧', '老' => '老', '蘆' => '蘆', '虜' => '虜', '路' => '路', '露' => '露', '魯' => '魯', '鷺' => '鷺', '碌' => '碌', '祿' => '祿', '綠' => '綠', '菉' => '菉', '錄' => '錄', '鹿' => '鹿', '論' => '論', '壟' => '壟', '弄' => '弄', '籠' => '籠', '聾' => '聾', '牢' => '牢', '磊' => '磊', '賂' => '賂', '雷' => '雷', '壘' => '壘', '屢' => '屢', '樓' => '樓', '淚' => '淚', '漏' => '漏', '累' => '累', '縷' => '縷', '陋' => '陋', '勒' => '勒', '肋' => '肋', '凜' => '凜', '凌' => '凌', '稜' => '稜', '綾' => '綾', '菱' => '菱', '陵' => '陵', '讀' => '讀', '拏' => '拏', '樂' => '樂', '諾' => '諾', '丹' => '丹', '寧' => '寧', '怒' => '怒', '率' => '率', '異' => '異', '北' => '北', '磻' => '磻', '便' => '便', '復' => '復', '不' => '不', '泌' => '泌', '數' => '數', '索' => '索', '參' => '參', '塞' => '塞', '省' => '省', '葉' => '葉', '說' => '說', '殺' => '殺', '辰' => '辰', '沈' => '沈', '拾' => '拾', '若' => '若', '掠' => '掠', '略' => '略', '亮' => '亮', '兩' => '兩', '凉' => '凉', '梁' => '梁', '糧' => '糧', '良' => '良', '諒' => '諒', '量' => '量', '勵' => '勵', '呂' => '呂', '女' => '女', '廬' => '廬', '旅' => '旅', '濾' => '濾', '礪' => '礪', '閭' => '閭', '驪' => '驪', '麗' => '麗', '黎' => '黎', '力' => '力', '曆' => '曆', '歷' => '歷', '轢' => '轢', '年' => '年', '憐' => '憐', '戀' => '戀', '撚' => '撚', '漣' => '漣', '煉' => '煉', '璉' => '璉', '秊' => '秊', '練' => '練', '聯' => '聯', '輦' => '輦', '蓮' => '蓮', '連' => '連', '鍊' => '鍊', '列' => '列', '劣' => '劣', '咽' => '咽', '烈' => '烈', '裂' => '裂', '說' => '說', '廉' => '廉', '念' => '念', '捻' => '捻', '殮' => '殮', '簾' => '簾', '獵' => '獵', '令' => '令', '囹' => '囹', '寧' => '寧', '嶺' => '嶺', '怜' => '怜', '玲' => '玲', '瑩' => '瑩', '羚' => '羚', '聆' => '聆', '鈴' => '鈴', '零' => '零', '靈' => '靈', '領' => '領', '例' => '例', '禮' => '禮', '醴' => '醴', '隸' => '隸', '惡' => '惡', '了' => '了', '僚' => '僚', '寮' => '寮', '尿' => '尿', '料' => '料', '樂' => '樂', '燎' => '燎', '療' => '療', '蓼' => '蓼', '遼' => '遼', '龍' => '龍', '暈' => '暈', '阮' => '阮', '劉' => '劉', '杻' => '杻', '柳' => '柳', '流' => '流', '溜' => '溜', '琉' => '琉', '留' => '留', '硫' => '硫', '紐' => '紐', '類' => '類', '六' => '六', '戮' => '戮', '陸' => '陸', '倫' => '倫', '崙' => '崙', '淪' => '淪', '輪' => '輪', '律' => '律', '慄' => '慄', '栗' => '栗', '率' => '率', '隆' => '隆', '利' => '利', '吏' => '吏', '履' => '履', '易' => '易', '李' => '李', '梨' => '梨', '泥' => '泥', '理' => '理', '痢' => '痢', '罹' => '罹', '裏' => '裏', '裡' => '裡', '里' => '里', '離' => '離', '匿' => '匿', '溺' => '溺', '吝' => '吝', '燐' => '燐', '璘' => '璘', '藺' => '藺', '隣' => '隣', '鱗' => '鱗', '麟' => '麟', '林' => '林', '淋' => '淋', '臨' => '臨', '立' => '立', '笠' => '笠', '粒' => '粒', '狀' => '狀', '炙' => '炙', '識' => '識', '什' => '什', '茶' => '茶', '刺' => '刺', '切' => '切', '度' => '度', '拓' => '拓', '糖' => '糖', '宅' => '宅', '洞' => '洞', '暴' => '暴', '輻' => '輻', '行' => '行', '降' => '降', '見' => '見', '廓' => '廓', '兀' => '兀', '嗀' => '嗀', '塚' => '塚', '晴' => '晴', '凞' => '凞', '猪' => '猪', '益' => '益', '礼' => '礼', '神' => '神', '祥' => '祥', '福' => '福', '靖' => '靖', '精' => '精', '羽' => '羽', '蘒' => '蘒', '諸' => '諸', '逸' => '逸', '都' => '都', '飯' => '飯', '飼' => '飼', '館' => '館', '鶴' => '鶴', '郞' => '郞', '隷' => '隷', '侮' => '侮', '僧' => '僧', '免' => '免', '勉' => '勉', '勤' => '勤', '卑' => '卑', '喝' => '喝', '嘆' => '嘆', '器' => '器', '塀' => '塀', '墨' => '墨', '層' => '層', '屮' => '屮', '悔' => '悔', '慨' => '慨', '憎' => '憎', '懲' => '懲', '敏' => '敏', '既' => '既', '暑' => '暑', '梅' => '梅', '海' => '海', '渚' => '渚', '漢' => '漢', '煮' => '煮', '爫' => '爫', '琢' => '琢', '碑' => '碑', '社' => '社', '祉' => '祉', '祈' => '祈', '祐' => '祐', '祖' => '祖', '祝' => '祝', '禍' => '禍', '禎' => '禎', '穀' => '穀', '突' => '突', '節' => '節', '練' => '練', '縉' => '縉', '繁' => '繁', '署' => '署', '者' => '者', '臭' => '臭', '艹' => '艹', '艹' => '艹', '著' => '著', '褐' => '褐', '視' => '視', '謁' => '謁', '謹' => '謹', '賓' => '賓', '贈' => '贈', '辶' => '辶', '逸' => '逸', '難' => '難', '響' => '響', '頻' => '頻', '恵' => '恵', '𤋮' => '𤋮', '舘' => '舘', '並' => '並', '况' => '况', '全' => '全', '侀' => '侀', '充' => '充', '冀' => '冀', '勇' => '勇', '勺' => '勺', '喝' => '喝', '啕' => '啕', '喙' => '喙', '嗢' => '嗢', '塚' => '塚', '墳' => '墳', '奄' => '奄', '奔' => '奔', '婢' => '婢', '嬨' => '嬨', '廒' => '廒', '廙' => '廙', '彩' => '彩', '徭' => '徭', '惘' => '惘', '慎' => '慎', '愈' => '愈', '憎' => '憎', '慠' => '慠', '懲' => '懲', '戴' => '戴', '揄' => '揄', '搜' => '搜', '摒' => '摒', '敖' => '敖', '晴' => '晴', '朗' => '朗', '望' => '望', '杖' => '杖', '歹' => '歹', '殺' => '殺', '流' => '流', '滛' => '滛', '滋' => '滋', '漢' => '漢', '瀞' => '瀞', '煮' => '煮', '瞧' => '瞧', '爵' => '爵', '犯' => '犯', '猪' => '猪', '瑱' => '瑱', '甆' => '甆', '画' => '画', '瘝' => '瘝', '瘟' => '瘟', '益' => '益', '盛' => '盛', '直' => '直', '睊' => '睊', '着' => '着', '磌' => '磌', '窱' => '窱', '節' => '節', '类' => '类', '絛' => '絛', '練' => '練', '缾' => '缾', '者' => '者', '荒' => '荒', '華' => '華', '蝹' => '蝹', '襁' => '襁', '覆' => '覆', '視' => '視', '調' => '調', '諸' => '諸', '請' => '請', '謁' => '謁', '諾' => '諾', '諭' => '諭', '謹' => '謹', '變' => '變', '贈' => '贈', '輸' => '輸', '遲' => '遲', '醙' => '醙', '鉶' => '鉶', '陼' => '陼', '難' => '難', '靖' => '靖', '韛' => '韛', '響' => '響', '頋' => '頋', '頻' => '頻', '鬒' => '鬒', '龜' => '龜', '𢡊' => '𢡊', '𢡄' => '𢡄', '𣏕' => '𣏕', '㮝' => '㮝', '䀘' => '䀘', '䀹' => '䀹', '𥉉' => '𥉉', '𥳐' => '𥳐', '𧻓' => '𧻓', '齃' => '齃', '龎' => '龎', 'יִ' => 'יִ', 'ײַ' => 'ײַ', 'שׁ' => 'שׁ', 'שׂ' => 'שׂ', 'שּׁ' => 'שּׁ', 'שּׂ' => 'שּׂ', 'אַ' => 'אַ', 'אָ' => 'אָ', 'אּ' => 'אּ', 'בּ' => 'בּ', 'גּ' => 'גּ', 'דּ' => 'דּ', 'הּ' => 'הּ', 'וּ' => 'וּ', 'זּ' => 'זּ', 'טּ' => 'טּ', 'יּ' => 'יּ', 'ךּ' => 'ךּ', 'כּ' => 'כּ', 'לּ' => 'לּ', 'מּ' => 'מּ', 'נּ' => 'נּ', 'סּ' => 'סּ', 'ףּ' => 'ףּ', 'פּ' => 'פּ', 'צּ' => 'צּ', 'קּ' => 'קּ', 'רּ' => 'רּ', 'שּ' => 'שּ', 'תּ' => 'תּ', 'וֹ' => 'וֹ', 'בֿ' => 'בֿ', 'כֿ' => 'כֿ', 'פֿ' => 'פֿ', '𑂚' => '𑂚', '𑂜' => '𑂜', '𑂫' => '𑂫', '𑄮' => '𑄮', '𑄯' => '𑄯', '𑍋' => '𑍋', '𑍌' => '𑍌', '𑒻' => '𑒻', '𑒼' => '𑒼', '𑒾' => '𑒾', '𑖺' => '𑖺', '𑖻' => '𑖻', '𑤸' => '𑤸', '𝅗𝅥' => '𝅗𝅥', '𝅘𝅥' => '𝅘𝅥', '𝅘𝅥𝅮' => '𝅘𝅥𝅮', '𝅘𝅥𝅯' => '𝅘𝅥𝅯', '𝅘𝅥𝅰' => '𝅘𝅥𝅰', '𝅘𝅥𝅱' => '𝅘𝅥𝅱', '𝅘𝅥𝅲' => '𝅘𝅥𝅲', '𝆹𝅥' => '𝆹𝅥', '𝆺𝅥' => '𝆺𝅥', '𝆹𝅥𝅮' => '𝆹𝅥𝅮', '𝆺𝅥𝅮' => '𝆺𝅥𝅮', '𝆹𝅥𝅯' => '𝆹𝅥𝅯', '𝆺𝅥𝅯' => '𝆺𝅥𝅯', '丽' => '丽', '丸' => '丸', '乁' => '乁', '𠄢' => '𠄢', '你' => '你', '侮' => '侮', '侻' => '侻', '倂' => '倂', '偺' => '偺', '備' => '備', '僧' => '僧', '像' => '像', '㒞' => '㒞', '𠘺' => '𠘺', '免' => '免', '兔' => '兔', '兤' => '兤', '具' => '具', '𠔜' => '𠔜', '㒹' => '㒹', '內' => '內', '再' => '再', '𠕋' => '𠕋', '冗' => '冗', '冤' => '冤', '仌' => '仌', '冬' => '冬', '况' => '况', '𩇟' => '𩇟', '凵' => '凵', '刃' => '刃', '㓟' => '㓟', '刻' => '刻', '剆' => '剆', '割' => '割', '剷' => '剷', '㔕' => '㔕', '勇' => '勇', '勉' => '勉', '勤' => '勤', '勺' => '勺', '包' => '包', '匆' => '匆', '北' => '北', '卉' => '卉', '卑' => '卑', '博' => '博', '即' => '即', '卽' => '卽', '卿' => '卿', '卿' => '卿', '卿' => '卿', '𠨬' => '𠨬', '灰' => '灰', '及' => '及', '叟' => '叟', '𠭣' => '𠭣', '叫' => '叫', '叱' => '叱', '吆' => '吆', '咞' => '咞', '吸' => '吸', '呈' => '呈', '周' => '周', '咢' => '咢', '哶' => '哶', '唐' => '唐', '啓' => '啓', '啣' => '啣', '善' => '善', '善' => '善', '喙' => '喙', '喫' => '喫', '喳' => '喳', '嗂' => '嗂', '圖' => '圖', '嘆' => '嘆', '圗' => '圗', '噑' => '噑', '噴' => '噴', '切' => '切', '壮' => '壮', '城' => '城', '埴' => '埴', '堍' => '堍', '型' => '型', '堲' => '堲', '報' => '報', '墬' => '墬', '𡓤' => '𡓤', '売' => '売', '壷' => '壷', '夆' => '夆', '多' => '多', '夢' => '夢', '奢' => '奢', '𡚨' => '𡚨', '𡛪' => '𡛪', '姬' => '姬', '娛' => '娛', '娧' => '娧', '姘' => '姘', '婦' => '婦', '㛮' => '㛮', '㛼' => '㛼', '嬈' => '嬈', '嬾' => '嬾', '嬾' => '嬾', '𡧈' => '𡧈', '寃' => '寃', '寘' => '寘', '寧' => '寧', '寳' => '寳', '𡬘' => '𡬘', '寿' => '寿', '将' => '将', '当' => '当', '尢' => '尢', '㞁' => '㞁', '屠' => '屠', '屮' => '屮', '峀' => '峀', '岍' => '岍', '𡷤' => '𡷤', '嵃' => '嵃', '𡷦' => '𡷦', '嵮' => '嵮', '嵫' => '嵫', '嵼' => '嵼', '巡' => '巡', '巢' => '巢', '㠯' => '㠯', '巽' => '巽', '帨' => '帨', '帽' => '帽', '幩' => '幩', '㡢' => '㡢', '𢆃' => '𢆃', '㡼' => '㡼', '庰' => '庰', '庳' => '庳', '庶' => '庶', '廊' => '廊', '𪎒' => '𪎒', '廾' => '廾', '𢌱' => '𢌱', '𢌱' => '𢌱', '舁' => '舁', '弢' => '弢', '弢' => '弢', '㣇' => '㣇', '𣊸' => '𣊸', '𦇚' => '𦇚', '形' => '形', '彫' => '彫', '㣣' => '㣣', '徚' => '徚', '忍' => '忍', '志' => '志', '忹' => '忹', '悁' => '悁', '㤺' => '㤺', '㤜' => '㤜', '悔' => '悔', '𢛔' => '𢛔', '惇' => '惇', '慈' => '慈', '慌' => '慌', '慎' => '慎', '慌' => '慌', '慺' => '慺', '憎' => '憎', '憲' => '憲', '憤' => '憤', '憯' => '憯', '懞' => '懞', '懲' => '懲', '懶' => '懶', '成' => '成', '戛' => '戛', '扝' => '扝', '抱' => '抱', '拔' => '拔', '捐' => '捐', '𢬌' => '𢬌', '挽' => '挽', '拼' => '拼', '捨' => '捨', '掃' => '掃', '揤' => '揤', '𢯱' => '𢯱', '搢' => '搢', '揅' => '揅', '掩' => '掩', '㨮' => '㨮', '摩' => '摩', '摾' => '摾', '撝' => '撝', '摷' => '摷', '㩬' => '㩬', '敏' => '敏', '敬' => '敬', '𣀊' => '𣀊', '旣' => '旣', '書' => '書', '晉' => '晉', '㬙' => '㬙', '暑' => '暑', '㬈' => '㬈', '㫤' => '㫤', '冒' => '冒', '冕' => '冕', '最' => '最', '暜' => '暜', '肭' => '肭', '䏙' => '䏙', '朗' => '朗', '望' => '望', '朡' => '朡', '杞' => '杞', '杓' => '杓', '𣏃' => '𣏃', '㭉' => '㭉', '柺' => '柺', '枅' => '枅', '桒' => '桒', '梅' => '梅', '𣑭' => '𣑭', '梎' => '梎', '栟' => '栟', '椔' => '椔', '㮝' => '㮝', '楂' => '楂', '榣' => '榣', '槪' => '槪', '檨' => '檨', '𣚣' => '𣚣', '櫛' => '櫛', '㰘' => '㰘', '次' => '次', '𣢧' => '𣢧', '歔' => '歔', '㱎' => '㱎', '歲' => '歲', '殟' => '殟', '殺' => '殺', '殻' => '殻', '𣪍' => '𣪍', '𡴋' => '𡴋', '𣫺' => '𣫺', '汎' => '汎', '𣲼' => '𣲼', '沿' => '沿', '泍' => '泍', '汧' => '汧', '洖' => '洖', '派' => '派', '海' => '海', '流' => '流', '浩' => '浩', '浸' => '浸', '涅' => '涅', '𣴞' => '𣴞', '洴' => '洴', '港' => '港', '湮' => '湮', '㴳' => '㴳', '滋' => '滋', '滇' => '滇', '𣻑' => '𣻑', '淹' => '淹', '潮' => '潮', '𣽞' => '𣽞', '𣾎' => '𣾎', '濆' => '濆', '瀹' => '瀹', '瀞' => '瀞', '瀛' => '瀛', '㶖' => '㶖', '灊' => '灊', '災' => '災', '灷' => '灷', '炭' => '炭', '𠔥' => '𠔥', '煅' => '煅', '𤉣' => '𤉣', '熜' => '熜', '𤎫' => '𤎫', '爨' => '爨', '爵' => '爵', '牐' => '牐', '𤘈' => '𤘈', '犀' => '犀', '犕' => '犕', '𤜵' => '𤜵', '𤠔' => '𤠔', '獺' => '獺', '王' => '王', '㺬' => '㺬', '玥' => '玥', '㺸' => '㺸', '㺸' => '㺸', '瑇' => '瑇', '瑜' => '瑜', '瑱' => '瑱', '璅' => '璅', '瓊' => '瓊', '㼛' => '㼛', '甤' => '甤', '𤰶' => '𤰶', '甾' => '甾', '𤲒' => '𤲒', '異' => '異', '𢆟' => '𢆟', '瘐' => '瘐', '𤾡' => '𤾡', '𤾸' => '𤾸', '𥁄' => '𥁄', '㿼' => '㿼', '䀈' => '䀈', '直' => '直', '𥃳' => '𥃳', '𥃲' => '𥃲', '𥄙' => '𥄙', '𥄳' => '𥄳', '眞' => '眞', '真' => '真', '真' => '真', '睊' => '睊', '䀹' => '䀹', '瞋' => '瞋', '䁆' => '䁆', '䂖' => '䂖', '𥐝' => '𥐝', '硎' => '硎', '碌' => '碌', '磌' => '磌', '䃣' => '䃣', '𥘦' => '𥘦', '祖' => '祖', '𥚚' => '𥚚', '𥛅' => '𥛅', '福' => '福', '秫' => '秫', '䄯' => '䄯', '穀' => '穀', '穊' => '穊', '穏' => '穏', '𥥼' => '𥥼', '𥪧' => '𥪧', '𥪧' => '𥪧', '竮' => '竮', '䈂' => '䈂', '𥮫' => '𥮫', '篆' => '篆', '築' => '築', '䈧' => '䈧', '𥲀' => '𥲀', '糒' => '糒', '䊠' => '䊠', '糨' => '糨', '糣' => '糣', '紀' => '紀', '𥾆' => '𥾆', '絣' => '絣', '䌁' => '䌁', '緇' => '緇', '縂' => '縂', '繅' => '繅', '䌴' => '䌴', '𦈨' => '𦈨', '𦉇' => '𦉇', '䍙' => '䍙', '𦋙' => '𦋙', '罺' => '罺', '𦌾' => '𦌾', '羕' => '羕', '翺' => '翺', '者' => '者', '𦓚' => '𦓚', '𦔣' => '𦔣', '聠' => '聠', '𦖨' => '𦖨', '聰' => '聰', '𣍟' => '𣍟', '䏕' => '䏕', '育' => '育', '脃' => '脃', '䐋' => '䐋', '脾' => '脾', '媵' => '媵', '𦞧' => '𦞧', '𦞵' => '𦞵', '𣎓' => '𣎓', '𣎜' => '𣎜', '舁' => '舁', '舄' => '舄', '辞' => '辞', '䑫' => '䑫', '芑' => '芑', '芋' => '芋', '芝' => '芝', '劳' => '劳', '花' => '花', '芳' => '芳', '芽' => '芽', '苦' => '苦', '𦬼' => '𦬼', '若' => '若', '茝' => '茝', '荣' => '荣', '莭' => '莭', '茣' => '茣', '莽' => '莽', '菧' => '菧', '著' => '著', '荓' => '荓', '菊' => '菊', '菌' => '菌', '菜' => '菜', '𦰶' => '𦰶', '𦵫' => '𦵫', '𦳕' => '𦳕', '䔫' => '䔫', '蓱' => '蓱', '蓳' => '蓳', '蔖' => '蔖', '𧏊' => '𧏊', '蕤' => '蕤', '𦼬' => '𦼬', '䕝' => '䕝', '䕡' => '䕡', '𦾱' => '𦾱', '𧃒' => '𧃒', '䕫' => '䕫', '虐' => '虐', '虜' => '虜', '虧' => '虧', '虩' => '虩', '蚩' => '蚩', '蚈' => '蚈', '蜎' => '蜎', '蛢' => '蛢', '蝹' => '蝹', '蜨' => '蜨', '蝫' => '蝫', '螆' => '螆', '䗗' => '䗗', '蟡' => '蟡', '蠁' => '蠁', '䗹' => '䗹', '衠' => '衠', '衣' => '衣', '𧙧' => '𧙧', '裗' => '裗', '裞' => '裞', '䘵' => '䘵', '裺' => '裺', '㒻' => '㒻', '𧢮' => '𧢮', '𧥦' => '𧥦', '䚾' => '䚾', '䛇' => '䛇', '誠' => '誠', '諭' => '諭', '變' => '變', '豕' => '豕', '𧲨' => '𧲨', '貫' => '貫', '賁' => '賁', '贛' => '贛', '起' => '起', '𧼯' => '𧼯', '𠠄' => '𠠄', '跋' => '跋', '趼' => '趼', '跰' => '跰', '𠣞' => '𠣞', '軔' => '軔', '輸' => '輸', '𨗒' => '𨗒', '𨗭' => '𨗭', '邔' => '邔', '郱' => '郱', '鄑' => '鄑', '𨜮' => '𨜮', '鄛' => '鄛', '鈸' => '鈸', '鋗' => '鋗', '鋘' => '鋘', '鉼' => '鉼', '鏹' => '鏹', '鐕' => '鐕', '𨯺' => '𨯺', '開' => '開', '䦕' => '䦕', '閷' => '閷', '𨵷' => '𨵷', '䧦' => '䧦', '雃' => '雃', '嶲' => '嶲', '霣' => '霣', '𩅅' => '𩅅', '𩈚' => '𩈚', '䩮' => '䩮', '䩶' => '䩶', '韠' => '韠', '𩐊' => '𩐊', '䪲' => '䪲', '𩒖' => '𩒖', '頋' => '頋', '頋' => '頋', '頩' => '頩', '𩖶' => '𩖶', '飢' => '飢', '䬳' => '䬳', '餩' => '餩', '馧' => '馧', '駂' => '駂', '駾' => '駾', '䯎' => '䯎', '𩬰' => '𩬰', '鬒' => '鬒', '鱀' => '鱀', '鳽' => '鳽', '䳎' => '䳎', '䳭' => '䳭', '鵧' => '鵧', '𪃎' => '𪃎', '䳸' => '䳸', '𪄅' => '𪄅', '𪈎' => '𪈎', '𪊑' => '𪊑', '麻' => '麻', '䵖' => '䵖', '黹' => '黹', '黾' => '黾', '鼅' => '鼅', '鼏' => '鼏', '鼖' => '鼖', '鼻' => '鼻', '𪘀' => '𪘀', ); ' ', '¨' => ' ̈', 'ª' => 'a', '¯' => ' ̄', '²' => '2', '³' => '3', '´' => ' ́', 'µ' => 'μ', '¸' => ' ̧', '¹' => '1', 'º' => 'o', '¼' => '1⁄4', '½' => '1⁄2', '¾' => '3⁄4', 'IJ' => 'IJ', 'ij' => 'ij', 'Ŀ' => 'L·', 'ŀ' => 'l·', 'ʼn' => 'ʼn', 'ſ' => 's', 'DŽ' => 'DŽ', 'Dž' => 'Dž', 'dž' => 'dž', 'LJ' => 'LJ', 'Lj' => 'Lj', 'lj' => 'lj', 'NJ' => 'NJ', 'Nj' => 'Nj', 'nj' => 'nj', 'DZ' => 'DZ', 'Dz' => 'Dz', 'dz' => 'dz', 'ʰ' => 'h', 'ʱ' => 'ɦ', 'ʲ' => 'j', 'ʳ' => 'r', 'ʴ' => 'ɹ', 'ʵ' => 'ɻ', 'ʶ' => 'ʁ', 'ʷ' => 'w', 'ʸ' => 'y', '˘' => ' ̆', '˙' => ' ̇', '˚' => ' ̊', '˛' => ' ̨', '˜' => ' ̃', '˝' => ' ̋', 'ˠ' => 'ɣ', 'ˡ' => 'l', 'ˢ' => 's', 'ˣ' => 'x', 'ˤ' => 'ʕ', 'ͺ' => ' ͅ', '΄' => ' ́', 'ϐ' => 'β', 'ϑ' => 'θ', 'ϒ' => 'Υ', 'ϕ' => 'φ', 'ϖ' => 'π', 'ϰ' => 'κ', 'ϱ' => 'ρ', 'ϲ' => 'ς', 'ϴ' => 'Θ', 'ϵ' => 'ε', 'Ϲ' => 'Σ', 'և' => 'եւ', 'ٵ' => 'اٴ', 'ٶ' => 'وٴ', 'ٷ' => 'ۇٴ', 'ٸ' => 'يٴ', 'ำ' => 'ํา', 'ຳ' => 'ໍາ', 'ໜ' => 'ຫນ', 'ໝ' => 'ຫມ', '༌' => '་', 'ཷ' => 'ྲཱྀ', 'ཹ' => 'ླཱྀ', 'ჼ' => 'ნ', 'ᴬ' => 'A', 'ᴭ' => 'Æ', 'ᴮ' => 'B', 'ᴰ' => 'D', 'ᴱ' => 'E', 'ᴲ' => 'Ǝ', 'ᴳ' => 'G', 'ᴴ' => 'H', 'ᴵ' => 'I', 'ᴶ' => 'J', 'ᴷ' => 'K', 'ᴸ' => 'L', 'ᴹ' => 'M', 'ᴺ' => 'N', 'ᴼ' => 'O', 'ᴽ' => 'Ȣ', 'ᴾ' => 'P', 'ᴿ' => 'R', 'ᵀ' => 'T', 'ᵁ' => 'U', 'ᵂ' => 'W', 'ᵃ' => 'a', 'ᵄ' => 'ɐ', 'ᵅ' => 'ɑ', 'ᵆ' => 'ᴂ', 'ᵇ' => 'b', 'ᵈ' => 'd', 'ᵉ' => 'e', 'ᵊ' => 'ə', 'ᵋ' => 'ɛ', 'ᵌ' => 'ɜ', 'ᵍ' => 'g', 'ᵏ' => 'k', 'ᵐ' => 'm', 'ᵑ' => 'ŋ', 'ᵒ' => 'o', 'ᵓ' => 'ɔ', 'ᵔ' => 'ᴖ', 'ᵕ' => 'ᴗ', 'ᵖ' => 'p', 'ᵗ' => 't', 'ᵘ' => 'u', 'ᵙ' => 'ᴝ', 'ᵚ' => 'ɯ', 'ᵛ' => 'v', 'ᵜ' => 'ᴥ', 'ᵝ' => 'β', 'ᵞ' => 'γ', 'ᵟ' => 'δ', 'ᵠ' => 'φ', 'ᵡ' => 'χ', 'ᵢ' => 'i', 'ᵣ' => 'r', 'ᵤ' => 'u', 'ᵥ' => 'v', 'ᵦ' => 'β', 'ᵧ' => 'γ', 'ᵨ' => 'ρ', 'ᵩ' => 'φ', 'ᵪ' => 'χ', 'ᵸ' => 'н', 'ᶛ' => 'ɒ', 'ᶜ' => 'c', 'ᶝ' => 'ɕ', 'ᶞ' => 'ð', 'ᶟ' => 'ɜ', 'ᶠ' => 'f', 'ᶡ' => 'ɟ', 'ᶢ' => 'ɡ', 'ᶣ' => 'ɥ', 'ᶤ' => 'ɨ', 'ᶥ' => 'ɩ', 'ᶦ' => 'ɪ', 'ᶧ' => 'ᵻ', 'ᶨ' => 'ʝ', 'ᶩ' => 'ɭ', 'ᶪ' => 'ᶅ', 'ᶫ' => 'ʟ', 'ᶬ' => 'ɱ', 'ᶭ' => 'ɰ', 'ᶮ' => 'ɲ', 'ᶯ' => 'ɳ', 'ᶰ' => 'ɴ', 'ᶱ' => 'ɵ', 'ᶲ' => 'ɸ', 'ᶳ' => 'ʂ', 'ᶴ' => 'ʃ', 'ᶵ' => 'ƫ', 'ᶶ' => 'ʉ', 'ᶷ' => 'ʊ', 'ᶸ' => 'ᴜ', 'ᶹ' => 'ʋ', 'ᶺ' => 'ʌ', 'ᶻ' => 'z', 'ᶼ' => 'ʐ', 'ᶽ' => 'ʑ', 'ᶾ' => 'ʒ', 'ᶿ' => 'θ', 'ẚ' => 'aʾ', '᾽' => ' ̓', '᾿' => ' ̓', '῀' => ' ͂', '῾' => ' ̔', ' ' => ' ', ' ' => ' ', ' ' => ' ', ' ' => ' ', ' ' => ' ', ' ' => ' ', ' ' => ' ', ' ' => ' ', ' ' => ' ', '‑' => '‐', '‗' => ' ̳', '․' => '.', '‥' => '..', '…' => '...', ' ' => ' ', '″' => '′′', '‴' => '′′′', '‶' => '‵‵', '‷' => '‵‵‵', '‼' => '!!', '‾' => ' ̅', '⁇' => '??', '⁈' => '?!', '⁉' => '!?', '⁗' => '′′′′', ' ' => ' ', '⁰' => '0', 'ⁱ' => 'i', '⁴' => '4', '⁵' => '5', '⁶' => '6', '⁷' => '7', '⁸' => '8', '⁹' => '9', '⁺' => '+', '⁻' => '−', '⁼' => '=', '⁽' => '(', '⁾' => ')', 'ⁿ' => 'n', '₀' => '0', '₁' => '1', '₂' => '2', '₃' => '3', '₄' => '4', '₅' => '5', '₆' => '6', '₇' => '7', '₈' => '8', '₉' => '9', '₊' => '+', '₋' => '−', '₌' => '=', '₍' => '(', '₎' => ')', 'ₐ' => 'a', 'ₑ' => 'e', 'ₒ' => 'o', 'ₓ' => 'x', 'ₔ' => 'ə', 'ₕ' => 'h', 'ₖ' => 'k', 'ₗ' => 'l', 'ₘ' => 'm', 'ₙ' => 'n', 'ₚ' => 'p', 'ₛ' => 's', 'ₜ' => 't', '₨' => 'Rs', '℀' => 'a/c', '℁' => 'a/s', 'ℂ' => 'C', '℃' => '°C', '℅' => 'c/o', '℆' => 'c/u', 'ℇ' => 'Ɛ', '℉' => '°F', 'ℊ' => 'g', 'ℋ' => 'H', 'ℌ' => 'H', 'ℍ' => 'H', 'ℎ' => 'h', 'ℏ' => 'ħ', 'ℐ' => 'I', 'ℑ' => 'I', 'ℒ' => 'L', 'ℓ' => 'l', 'ℕ' => 'N', '№' => 'No', 'ℙ' => 'P', 'ℚ' => 'Q', 'ℛ' => 'R', 'ℜ' => 'R', 'ℝ' => 'R', '℠' => 'SM', '℡' => 'TEL', '™' => 'TM', 'ℤ' => 'Z', 'ℨ' => 'Z', 'ℬ' => 'B', 'ℭ' => 'C', 'ℯ' => 'e', 'ℰ' => 'E', 'ℱ' => 'F', 'ℳ' => 'M', 'ℴ' => 'o', 'ℵ' => 'א', 'ℶ' => 'ב', 'ℷ' => 'ג', 'ℸ' => 'ד', 'ℹ' => 'i', '℻' => 'FAX', 'ℼ' => 'π', 'ℽ' => 'γ', 'ℾ' => 'Γ', 'ℿ' => 'Π', '⅀' => '∑', 'ⅅ' => 'D', 'ⅆ' => 'd', 'ⅇ' => 'e', 'ⅈ' => 'i', 'ⅉ' => 'j', '⅐' => '1⁄7', '⅑' => '1⁄9', '⅒' => '1⁄10', '⅓' => '1⁄3', '⅔' => '2⁄3', '⅕' => '1⁄5', '⅖' => '2⁄5', '⅗' => '3⁄5', '⅘' => '4⁄5', '⅙' => '1⁄6', '⅚' => '5⁄6', '⅛' => '1⁄8', '⅜' => '3⁄8', '⅝' => '5⁄8', '⅞' => '7⁄8', '⅟' => '1⁄', 'Ⅰ' => 'I', 'Ⅱ' => 'II', 'Ⅲ' => 'III', 'Ⅳ' => 'IV', 'Ⅴ' => 'V', 'Ⅵ' => 'VI', 'Ⅶ' => 'VII', 'Ⅷ' => 'VIII', 'Ⅸ' => 'IX', 'Ⅹ' => 'X', 'Ⅺ' => 'XI', 'Ⅻ' => 'XII', 'Ⅼ' => 'L', 'Ⅽ' => 'C', 'Ⅾ' => 'D', 'Ⅿ' => 'M', 'ⅰ' => 'i', 'ⅱ' => 'ii', 'ⅲ' => 'iii', 'ⅳ' => 'iv', 'ⅴ' => 'v', 'ⅵ' => 'vi', 'ⅶ' => 'vii', 'ⅷ' => 'viii', 'ⅸ' => 'ix', 'ⅹ' => 'x', 'ⅺ' => 'xi', 'ⅻ' => 'xii', 'ⅼ' => 'l', 'ⅽ' => 'c', 'ⅾ' => 'd', 'ⅿ' => 'm', '↉' => '0⁄3', '∬' => '∫∫', '∭' => '∫∫∫', '∯' => '∮∮', '∰' => '∮∮∮', '①' => '1', '②' => '2', '③' => '3', '④' => '4', '⑤' => '5', '⑥' => '6', '⑦' => '7', '⑧' => '8', '⑨' => '9', '⑩' => '10', '⑪' => '11', '⑫' => '12', '⑬' => '13', '⑭' => '14', '⑮' => '15', '⑯' => '16', '⑰' => '17', '⑱' => '18', '⑲' => '19', '⑳' => '20', '⑴' => '(1)', '⑵' => '(2)', '⑶' => '(3)', '⑷' => '(4)', '⑸' => '(5)', '⑹' => '(6)', '⑺' => '(7)', '⑻' => '(8)', '⑼' => '(9)', '⑽' => '(10)', '⑾' => '(11)', '⑿' => '(12)', '⒀' => '(13)', '⒁' => '(14)', '⒂' => '(15)', '⒃' => '(16)', '⒄' => '(17)', '⒅' => '(18)', '⒆' => '(19)', '⒇' => '(20)', '⒈' => '1.', '⒉' => '2.', '⒊' => '3.', '⒋' => '4.', '⒌' => '5.', '⒍' => '6.', '⒎' => '7.', '⒏' => '8.', '⒐' => '9.', '⒑' => '10.', '⒒' => '11.', '⒓' => '12.', '⒔' => '13.', '⒕' => '14.', '⒖' => '15.', '⒗' => '16.', '⒘' => '17.', '⒙' => '18.', '⒚' => '19.', '⒛' => '20.', '⒜' => '(a)', '⒝' => '(b)', '⒞' => '(c)', '⒟' => '(d)', '⒠' => '(e)', '⒡' => '(f)', '⒢' => '(g)', '⒣' => '(h)', '⒤' => '(i)', '⒥' => '(j)', '⒦' => '(k)', '⒧' => '(l)', '⒨' => '(m)', '⒩' => '(n)', '⒪' => '(o)', '⒫' => '(p)', '⒬' => '(q)', '⒭' => '(r)', '⒮' => '(s)', '⒯' => '(t)', '⒰' => '(u)', '⒱' => '(v)', '⒲' => '(w)', '⒳' => '(x)', '⒴' => '(y)', '⒵' => '(z)', 'Ⓐ' => 'A', 'Ⓑ' => 'B', 'Ⓒ' => 'C', 'Ⓓ' => 'D', 'Ⓔ' => 'E', 'Ⓕ' => 'F', 'Ⓖ' => 'G', 'Ⓗ' => 'H', 'Ⓘ' => 'I', 'Ⓙ' => 'J', 'Ⓚ' => 'K', 'Ⓛ' => 'L', 'Ⓜ' => 'M', 'Ⓝ' => 'N', 'Ⓞ' => 'O', 'Ⓟ' => 'P', 'Ⓠ' => 'Q', 'Ⓡ' => 'R', 'Ⓢ' => 'S', 'Ⓣ' => 'T', 'Ⓤ' => 'U', 'Ⓥ' => 'V', 'Ⓦ' => 'W', 'Ⓧ' => 'X', 'Ⓨ' => 'Y', 'Ⓩ' => 'Z', 'ⓐ' => 'a', 'ⓑ' => 'b', 'ⓒ' => 'c', 'ⓓ' => 'd', 'ⓔ' => 'e', 'ⓕ' => 'f', 'ⓖ' => 'g', 'ⓗ' => 'h', 'ⓘ' => 'i', 'ⓙ' => 'j', 'ⓚ' => 'k', 'ⓛ' => 'l', 'ⓜ' => 'm', 'ⓝ' => 'n', 'ⓞ' => 'o', 'ⓟ' => 'p', 'ⓠ' => 'q', 'ⓡ' => 'r', 'ⓢ' => 's', 'ⓣ' => 't', 'ⓤ' => 'u', 'ⓥ' => 'v', 'ⓦ' => 'w', 'ⓧ' => 'x', 'ⓨ' => 'y', 'ⓩ' => 'z', '⓪' => '0', '⨌' => '∫∫∫∫', '⩴' => '::=', '⩵' => '==', '⩶' => '===', 'ⱼ' => 'j', 'ⱽ' => 'V', 'ⵯ' => 'ⵡ', '⺟' => '母', '⻳' => '龟', '⼀' => '一', '⼁' => '丨', '⼂' => '丶', '⼃' => '丿', '⼄' => '乙', '⼅' => '亅', '⼆' => '二', '⼇' => '亠', '⼈' => '人', '⼉' => '儿', '⼊' => '入', '⼋' => '八', '⼌' => '冂', '⼍' => '冖', '⼎' => '冫', '⼏' => '几', '⼐' => '凵', '⼑' => '刀', '⼒' => '力', '⼓' => '勹', '⼔' => '匕', '⼕' => '匚', '⼖' => '匸', '⼗' => '十', '⼘' => '卜', '⼙' => '卩', '⼚' => '厂', '⼛' => '厶', '⼜' => '又', '⼝' => '口', '⼞' => '囗', '⼟' => '土', '⼠' => '士', '⼡' => '夂', '⼢' => '夊', '⼣' => '夕', '⼤' => '大', '⼥' => '女', '⼦' => '子', '⼧' => '宀', '⼨' => '寸', '⼩' => '小', '⼪' => '尢', '⼫' => '尸', '⼬' => '屮', '⼭' => '山', '⼮' => '巛', '⼯' => '工', '⼰' => '己', '⼱' => '巾', '⼲' => '干', '⼳' => '幺', '⼴' => '广', '⼵' => '廴', '⼶' => '廾', '⼷' => '弋', '⼸' => '弓', '⼹' => '彐', '⼺' => '彡', '⼻' => '彳', '⼼' => '心', '⼽' => '戈', '⼾' => '戶', '⼿' => '手', '⽀' => '支', '⽁' => '攴', '⽂' => '文', '⽃' => '斗', '⽄' => '斤', '⽅' => '方', '⽆' => '无', '⽇' => '日', '⽈' => '曰', '⽉' => '月', '⽊' => '木', '⽋' => '欠', '⽌' => '止', '⽍' => '歹', '⽎' => '殳', '⽏' => '毋', '⽐' => '比', '⽑' => '毛', '⽒' => '氏', '⽓' => '气', '⽔' => '水', '⽕' => '火', '⽖' => '爪', '⽗' => '父', '⽘' => '爻', '⽙' => '爿', '⽚' => '片', '⽛' => '牙', '⽜' => '牛', '⽝' => '犬', '⽞' => '玄', '⽟' => '玉', '⽠' => '瓜', '⽡' => '瓦', '⽢' => '甘', '⽣' => '生', '⽤' => '用', '⽥' => '田', '⽦' => '疋', '⽧' => '疒', '⽨' => '癶', '⽩' => '白', '⽪' => '皮', '⽫' => '皿', '⽬' => '目', '⽭' => '矛', '⽮' => '矢', '⽯' => '石', '⽰' => '示', '⽱' => '禸', '⽲' => '禾', '⽳' => '穴', '⽴' => '立', '⽵' => '竹', '⽶' => '米', '⽷' => '糸', '⽸' => '缶', '⽹' => '网', '⽺' => '羊', '⽻' => '羽', '⽼' => '老', '⽽' => '而', '⽾' => '耒', '⽿' => '耳', '⾀' => '聿', '⾁' => '肉', '⾂' => '臣', '⾃' => '自', '⾄' => '至', '⾅' => '臼', '⾆' => '舌', '⾇' => '舛', '⾈' => '舟', '⾉' => '艮', '⾊' => '色', '⾋' => '艸', '⾌' => '虍', '⾍' => '虫', '⾎' => '血', '⾏' => '行', '⾐' => '衣', '⾑' => '襾', '⾒' => '見', '⾓' => '角', '⾔' => '言', '⾕' => '谷', '⾖' => '豆', '⾗' => '豕', '⾘' => '豸', '⾙' => '貝', '⾚' => '赤', '⾛' => '走', '⾜' => '足', '⾝' => '身', '⾞' => '車', '⾟' => '辛', '⾠' => '辰', '⾡' => '辵', '⾢' => '邑', '⾣' => '酉', '⾤' => '釆', '⾥' => '里', '⾦' => '金', '⾧' => '長', '⾨' => '門', '⾩' => '阜', '⾪' => '隶', '⾫' => '隹', '⾬' => '雨', '⾭' => '靑', '⾮' => '非', '⾯' => '面', '⾰' => '革', '⾱' => '韋', '⾲' => '韭', '⾳' => '音', '⾴' => '頁', '⾵' => '風', '⾶' => '飛', '⾷' => '食', '⾸' => '首', '⾹' => '香', '⾺' => '馬', '⾻' => '骨', '⾼' => '高', '⾽' => '髟', '⾾' => '鬥', '⾿' => '鬯', '⿀' => '鬲', '⿁' => '鬼', '⿂' => '魚', '⿃' => '鳥', '⿄' => '鹵', '⿅' => '鹿', '⿆' => '麥', '⿇' => '麻', '⿈' => '黃', '⿉' => '黍', '⿊' => '黑', '⿋' => '黹', '⿌' => '黽', '⿍' => '鼎', '⿎' => '鼓', '⿏' => '鼠', '⿐' => '鼻', '⿑' => '齊', '⿒' => '齒', '⿓' => '龍', '⿔' => '龜', '⿕' => '龠', ' ' => ' ', '〶' => '〒', '〸' => '十', '〹' => '卄', '〺' => '卅', '゛' => ' ゙', '゜' => ' ゚', 'ゟ' => 'より', 'ヿ' => 'コト', 'ㄱ' => 'ᄀ', 'ㄲ' => 'ᄁ', 'ㄳ' => 'ᆪ', 'ㄴ' => 'ᄂ', 'ㄵ' => 'ᆬ', 'ㄶ' => 'ᆭ', 'ㄷ' => 'ᄃ', 'ㄸ' => 'ᄄ', 'ㄹ' => 'ᄅ', 'ㄺ' => 'ᆰ', 'ㄻ' => 'ᆱ', 'ㄼ' => 'ᆲ', 'ㄽ' => 'ᆳ', 'ㄾ' => 'ᆴ', 'ㄿ' => 'ᆵ', 'ㅀ' => 'ᄚ', 'ㅁ' => 'ᄆ', 'ㅂ' => 'ᄇ', 'ㅃ' => 'ᄈ', 'ㅄ' => 'ᄡ', 'ㅅ' => 'ᄉ', 'ㅆ' => 'ᄊ', 'ㅇ' => 'ᄋ', 'ㅈ' => 'ᄌ', 'ㅉ' => 'ᄍ', 'ㅊ' => 'ᄎ', 'ㅋ' => 'ᄏ', 'ㅌ' => 'ᄐ', 'ㅍ' => 'ᄑ', 'ㅎ' => 'ᄒ', 'ㅏ' => 'ᅡ', 'ㅐ' => 'ᅢ', 'ㅑ' => 'ᅣ', 'ㅒ' => 'ᅤ', 'ㅓ' => 'ᅥ', 'ㅔ' => 'ᅦ', 'ㅕ' => 'ᅧ', 'ㅖ' => 'ᅨ', 'ㅗ' => 'ᅩ', 'ㅘ' => 'ᅪ', 'ㅙ' => 'ᅫ', 'ㅚ' => 'ᅬ', 'ㅛ' => 'ᅭ', 'ㅜ' => 'ᅮ', 'ㅝ' => 'ᅯ', 'ㅞ' => 'ᅰ', 'ㅟ' => 'ᅱ', 'ㅠ' => 'ᅲ', 'ㅡ' => 'ᅳ', 'ㅢ' => 'ᅴ', 'ㅣ' => 'ᅵ', 'ㅤ' => 'ᅠ', 'ㅥ' => 'ᄔ', 'ㅦ' => 'ᄕ', 'ㅧ' => 'ᇇ', 'ㅨ' => 'ᇈ', 'ㅩ' => 'ᇌ', 'ㅪ' => 'ᇎ', 'ㅫ' => 'ᇓ', 'ㅬ' => 'ᇗ', 'ㅭ' => 'ᇙ', 'ㅮ' => 'ᄜ', 'ㅯ' => 'ᇝ', 'ㅰ' => 'ᇟ', 'ㅱ' => 'ᄝ', 'ㅲ' => 'ᄞ', 'ㅳ' => 'ᄠ', 'ㅴ' => 'ᄢ', 'ㅵ' => 'ᄣ', 'ㅶ' => 'ᄧ', 'ㅷ' => 'ᄩ', 'ㅸ' => 'ᄫ', 'ㅹ' => 'ᄬ', 'ㅺ' => 'ᄭ', 'ㅻ' => 'ᄮ', 'ㅼ' => 'ᄯ', 'ㅽ' => 'ᄲ', 'ㅾ' => 'ᄶ', 'ㅿ' => 'ᅀ', 'ㆀ' => 'ᅇ', 'ㆁ' => 'ᅌ', 'ㆂ' => 'ᇱ', 'ㆃ' => 'ᇲ', 'ㆄ' => 'ᅗ', 'ㆅ' => 'ᅘ', 'ㆆ' => 'ᅙ', 'ㆇ' => 'ᆄ', 'ㆈ' => 'ᆅ', 'ㆉ' => 'ᆈ', 'ㆊ' => 'ᆑ', 'ㆋ' => 'ᆒ', 'ㆌ' => 'ᆔ', 'ㆍ' => 'ᆞ', 'ㆎ' => 'ᆡ', '㆒' => '一', '㆓' => '二', '㆔' => '三', '㆕' => '四', '㆖' => '上', '㆗' => '中', '㆘' => '下', '㆙' => '甲', '㆚' => '乙', '㆛' => '丙', '㆜' => '丁', '㆝' => '天', '㆞' => '地', '㆟' => '人', '㈀' => '(ᄀ)', '㈁' => '(ᄂ)', '㈂' => '(ᄃ)', '㈃' => '(ᄅ)', '㈄' => '(ᄆ)', '㈅' => '(ᄇ)', '㈆' => '(ᄉ)', '㈇' => '(ᄋ)', '㈈' => '(ᄌ)', '㈉' => '(ᄎ)', '㈊' => '(ᄏ)', '㈋' => '(ᄐ)', '㈌' => '(ᄑ)', '㈍' => '(ᄒ)', '㈎' => '(가)', '㈏' => '(나)', '㈐' => '(다)', '㈑' => '(라)', '㈒' => '(마)', '㈓' => '(바)', '㈔' => '(사)', '㈕' => '(아)', '㈖' => '(자)', '㈗' => '(차)', '㈘' => '(카)', '㈙' => '(타)', '㈚' => '(파)', '㈛' => '(하)', '㈜' => '(주)', '㈝' => '(오전)', '㈞' => '(오후)', '㈠' => '(一)', '㈡' => '(二)', '㈢' => '(三)', '㈣' => '(四)', '㈤' => '(五)', '㈥' => '(六)', '㈦' => '(七)', '㈧' => '(八)', '㈨' => '(九)', '㈩' => '(十)', '㈪' => '(月)', '㈫' => '(火)', '㈬' => '(水)', '㈭' => '(木)', '㈮' => '(金)', '㈯' => '(土)', '㈰' => '(日)', '㈱' => '(株)', '㈲' => '(有)', '㈳' => '(社)', '㈴' => '(名)', '㈵' => '(特)', '㈶' => '(財)', '㈷' => '(祝)', '㈸' => '(労)', '㈹' => '(代)', '㈺' => '(呼)', '㈻' => '(学)', '㈼' => '(監)', '㈽' => '(企)', '㈾' => '(資)', '㈿' => '(協)', '㉀' => '(祭)', '㉁' => '(休)', '㉂' => '(自)', '㉃' => '(至)', '㉄' => '問', '㉅' => '幼', '㉆' => '文', '㉇' => '箏', '㉐' => 'PTE', '㉑' => '21', '㉒' => '22', '㉓' => '23', '㉔' => '24', '㉕' => '25', '㉖' => '26', '㉗' => '27', '㉘' => '28', '㉙' => '29', '㉚' => '30', '㉛' => '31', '㉜' => '32', '㉝' => '33', '㉞' => '34', '㉟' => '35', '㉠' => 'ᄀ', '㉡' => 'ᄂ', '㉢' => 'ᄃ', '㉣' => 'ᄅ', '㉤' => 'ᄆ', '㉥' => 'ᄇ', '㉦' => 'ᄉ', '㉧' => 'ᄋ', '㉨' => 'ᄌ', '㉩' => 'ᄎ', '㉪' => 'ᄏ', '㉫' => 'ᄐ', '㉬' => 'ᄑ', '㉭' => 'ᄒ', '㉮' => '가', '㉯' => '나', '㉰' => '다', '㉱' => '라', '㉲' => '마', '㉳' => '바', '㉴' => '사', '㉵' => '아', '㉶' => '자', '㉷' => '차', '㉸' => '카', '㉹' => '타', '㉺' => '파', '㉻' => '하', '㉼' => '참고', '㉽' => '주의', '㉾' => '우', '㊀' => '一', '㊁' => '二', '㊂' => '三', '㊃' => '四', '㊄' => '五', '㊅' => '六', '㊆' => '七', '㊇' => '八', '㊈' => '九', '㊉' => '十', '㊊' => '月', '㊋' => '火', '㊌' => '水', '㊍' => '木', '㊎' => '金', '㊏' => '土', '㊐' => '日', '㊑' => '株', '㊒' => '有', '㊓' => '社', '㊔' => '名', '㊕' => '特', '㊖' => '財', '㊗' => '祝', '㊘' => '労', '㊙' => '秘', '㊚' => '男', '㊛' => '女', '㊜' => '適', '㊝' => '優', '㊞' => '印', '㊟' => '注', '㊠' => '項', '㊡' => '休', '㊢' => '写', '㊣' => '正', '㊤' => '上', '㊥' => '中', '㊦' => '下', '㊧' => '左', '㊨' => '右', '㊩' => '医', '㊪' => '宗', '㊫' => '学', '㊬' => '監', '㊭' => '企', '㊮' => '資', '㊯' => '協', '㊰' => '夜', '㊱' => '36', '㊲' => '37', '㊳' => '38', '㊴' => '39', '㊵' => '40', '㊶' => '41', '㊷' => '42', '㊸' => '43', '㊹' => '44', '㊺' => '45', '㊻' => '46', '㊼' => '47', '㊽' => '48', '㊾' => '49', '㊿' => '50', '㋀' => '1月', '㋁' => '2月', '㋂' => '3月', '㋃' => '4月', '㋄' => '5月', '㋅' => '6月', '㋆' => '7月', '㋇' => '8月', '㋈' => '9月', '㋉' => '10月', '㋊' => '11月', '㋋' => '12月', '㋌' => 'Hg', '㋍' => 'erg', '㋎' => 'eV', '㋏' => 'LTD', '㋐' => 'ア', '㋑' => 'イ', '㋒' => 'ウ', '㋓' => 'エ', '㋔' => 'オ', '㋕' => 'カ', '㋖' => 'キ', '㋗' => 'ク', '㋘' => 'ケ', '㋙' => 'コ', '㋚' => 'サ', '㋛' => 'シ', '㋜' => 'ス', '㋝' => 'セ', '㋞' => 'ソ', '㋟' => 'タ', '㋠' => 'チ', '㋡' => 'ツ', '㋢' => 'テ', '㋣' => 'ト', '㋤' => 'ナ', '㋥' => 'ニ', '㋦' => 'ヌ', '㋧' => 'ネ', '㋨' => 'ノ', '㋩' => 'ハ', '㋪' => 'ヒ', '㋫' => 'フ', '㋬' => 'ヘ', '㋭' => 'ホ', '㋮' => 'マ', '㋯' => 'ミ', '㋰' => 'ム', '㋱' => 'メ', '㋲' => 'モ', '㋳' => 'ヤ', '㋴' => 'ユ', '㋵' => 'ヨ', '㋶' => 'ラ', '㋷' => 'リ', '㋸' => 'ル', '㋹' => 'レ', '㋺' => 'ロ', '㋻' => 'ワ', '㋼' => 'ヰ', '㋽' => 'ヱ', '㋾' => 'ヲ', '㋿' => '令和', '㌀' => 'アパート', '㌁' => 'アルファ', '㌂' => 'アンペア', '㌃' => 'アール', '㌄' => 'イニング', '㌅' => 'インチ', '㌆' => 'ウォン', '㌇' => 'エスクード', '㌈' => 'エーカー', '㌉' => 'オンス', '㌊' => 'オーム', '㌋' => 'カイリ', '㌌' => 'カラット', '㌍' => 'カロリー', '㌎' => 'ガロン', '㌏' => 'ガンマ', '㌐' => 'ギガ', '㌑' => 'ギニー', '㌒' => 'キュリー', '㌓' => 'ギルダー', '㌔' => 'キロ', '㌕' => 'キログラム', '㌖' => 'キロメートル', '㌗' => 'キロワット', '㌘' => 'グラム', '㌙' => 'グラムトン', '㌚' => 'クルゼイロ', '㌛' => 'クローネ', '㌜' => 'ケース', '㌝' => 'コルナ', '㌞' => 'コーポ', '㌟' => 'サイクル', '㌠' => 'サンチーム', '㌡' => 'シリング', '㌢' => 'センチ', '㌣' => 'セント', '㌤' => 'ダース', '㌥' => 'デシ', '㌦' => 'ドル', '㌧' => 'トン', '㌨' => 'ナノ', '㌩' => 'ノット', '㌪' => 'ハイツ', '㌫' => 'パーセント', '㌬' => 'パーツ', '㌭' => 'バーレル', '㌮' => 'ピアストル', '㌯' => 'ピクル', '㌰' => 'ピコ', '㌱' => 'ビル', '㌲' => 'ファラッド', '㌳' => 'フィート', '㌴' => 'ブッシェル', '㌵' => 'フラン', '㌶' => 'ヘクタール', '㌷' => 'ペソ', '㌸' => 'ペニヒ', '㌹' => 'ヘルツ', '㌺' => 'ペンス', '㌻' => 'ページ', '㌼' => 'ベータ', '㌽' => 'ポイント', '㌾' => 'ボルト', '㌿' => 'ホン', '㍀' => 'ポンド', '㍁' => 'ホール', '㍂' => 'ホーン', '㍃' => 'マイクロ', '㍄' => 'マイル', '㍅' => 'マッハ', '㍆' => 'マルク', '㍇' => 'マンション', '㍈' => 'ミクロン', '㍉' => 'ミリ', '㍊' => 'ミリバール', '㍋' => 'メガ', '㍌' => 'メガトン', '㍍' => 'メートル', '㍎' => 'ヤード', '㍏' => 'ヤール', '㍐' => 'ユアン', '㍑' => 'リットル', '㍒' => 'リラ', '㍓' => 'ルピー', '㍔' => 'ルーブル', '㍕' => 'レム', '㍖' => 'レントゲン', '㍗' => 'ワット', '㍘' => '0点', '㍙' => '1点', '㍚' => '2点', '㍛' => '3点', '㍜' => '4点', '㍝' => '5点', '㍞' => '6点', '㍟' => '7点', '㍠' => '8点', '㍡' => '9点', '㍢' => '10点', '㍣' => '11点', '㍤' => '12点', '㍥' => '13点', '㍦' => '14点', '㍧' => '15点', '㍨' => '16点', '㍩' => '17点', '㍪' => '18点', '㍫' => '19点', '㍬' => '20点', '㍭' => '21点', '㍮' => '22点', '㍯' => '23点', '㍰' => '24点', '㍱' => 'hPa', '㍲' => 'da', '㍳' => 'AU', '㍴' => 'bar', '㍵' => 'oV', '㍶' => 'pc', '㍷' => 'dm', '㍸' => 'dm²', '㍹' => 'dm³', '㍺' => 'IU', '㍻' => '平成', '㍼' => '昭和', '㍽' => '大正', '㍾' => '明治', '㍿' => '株式会社', '㎀' => 'pA', '㎁' => 'nA', '㎂' => 'μA', '㎃' => 'mA', '㎄' => 'kA', '㎅' => 'KB', '㎆' => 'MB', '㎇' => 'GB', '㎈' => 'cal', '㎉' => 'kcal', '㎊' => 'pF', '㎋' => 'nF', '㎌' => 'μF', '㎍' => 'μg', '㎎' => 'mg', '㎏' => 'kg', '㎐' => 'Hz', '㎑' => 'kHz', '㎒' => 'MHz', '㎓' => 'GHz', '㎔' => 'THz', '㎕' => 'μℓ', '㎖' => 'mℓ', '㎗' => 'dℓ', '㎘' => 'kℓ', '㎙' => 'fm', '㎚' => 'nm', '㎛' => 'μm', '㎜' => 'mm', '㎝' => 'cm', '㎞' => 'km', '㎟' => 'mm²', '㎠' => 'cm²', '㎡' => 'm²', '㎢' => 'km²', '㎣' => 'mm³', '㎤' => 'cm³', '㎥' => 'm³', '㎦' => 'km³', '㎧' => 'm∕s', '㎨' => 'm∕s²', '㎩' => 'Pa', '㎪' => 'kPa', '㎫' => 'MPa', '㎬' => 'GPa', '㎭' => 'rad', '㎮' => 'rad∕s', '㎯' => 'rad∕s²', '㎰' => 'ps', '㎱' => 'ns', '㎲' => 'μs', '㎳' => 'ms', '㎴' => 'pV', '㎵' => 'nV', '㎶' => 'μV', '㎷' => 'mV', '㎸' => 'kV', '㎹' => 'MV', '㎺' => 'pW', '㎻' => 'nW', '㎼' => 'μW', '㎽' => 'mW', '㎾' => 'kW', '㎿' => 'MW', '㏀' => 'kΩ', '㏁' => 'MΩ', '㏂' => 'a.m.', '㏃' => 'Bq', '㏄' => 'cc', '㏅' => 'cd', '㏆' => 'C∕kg', '㏇' => 'Co.', '㏈' => 'dB', '㏉' => 'Gy', '㏊' => 'ha', '㏋' => 'HP', '㏌' => 'in', '㏍' => 'KK', '㏎' => 'KM', '㏏' => 'kt', '㏐' => 'lm', '㏑' => 'ln', '㏒' => 'log', '㏓' => 'lx', '㏔' => 'mb', '㏕' => 'mil', '㏖' => 'mol', '㏗' => 'PH', '㏘' => 'p.m.', '㏙' => 'PPM', '㏚' => 'PR', '㏛' => 'sr', '㏜' => 'Sv', '㏝' => 'Wb', '㏞' => 'V∕m', '㏟' => 'A∕m', '㏠' => '1日', '㏡' => '2日', '㏢' => '3日', '㏣' => '4日', '㏤' => '5日', '㏥' => '6日', '㏦' => '7日', '㏧' => '8日', '㏨' => '9日', '㏩' => '10日', '㏪' => '11日', '㏫' => '12日', '㏬' => '13日', '㏭' => '14日', '㏮' => '15日', '㏯' => '16日', '㏰' => '17日', '㏱' => '18日', '㏲' => '19日', '㏳' => '20日', '㏴' => '21日', '㏵' => '22日', '㏶' => '23日', '㏷' => '24日', '㏸' => '25日', '㏹' => '26日', '㏺' => '27日', '㏻' => '28日', '㏼' => '29日', '㏽' => '30日', '㏾' => '31日', '㏿' => 'gal', 'ꚜ' => 'ъ', 'ꚝ' => 'ь', 'ꝰ' => 'ꝯ', 'ꟲ' => 'C', 'ꟳ' => 'F', 'ꟴ' => 'Q', 'ꟸ' => 'Ħ', 'ꟹ' => 'œ', 'ꭜ' => 'ꜧ', 'ꭝ' => 'ꬷ', 'ꭞ' => 'ɫ', 'ꭟ' => 'ꭒ', 'ꭩ' => 'ʍ', 'ff' => 'ff', 'fi' => 'fi', 'fl' => 'fl', 'ffi' => 'ffi', 'ffl' => 'ffl', 'ſt' => 'ſt', 'st' => 'st', 'ﬓ' => 'մն', 'ﬔ' => 'մե', 'ﬕ' => 'մի', 'ﬖ' => 'վն', 'ﬗ' => 'մխ', 'ﬠ' => 'ע', 'ﬡ' => 'א', 'ﬢ' => 'ד', 'ﬣ' => 'ה', 'ﬤ' => 'כ', 'ﬥ' => 'ל', 'ﬦ' => 'ם', 'ﬧ' => 'ר', 'ﬨ' => 'ת', '﬩' => '+', 'ﭏ' => 'אל', 'ﭐ' => 'ٱ', 'ﭑ' => 'ٱ', 'ﭒ' => 'ٻ', 'ﭓ' => 'ٻ', 'ﭔ' => 'ٻ', 'ﭕ' => 'ٻ', 'ﭖ' => 'پ', 'ﭗ' => 'پ', 'ﭘ' => 'پ', 'ﭙ' => 'پ', 'ﭚ' => 'ڀ', 'ﭛ' => 'ڀ', 'ﭜ' => 'ڀ', 'ﭝ' => 'ڀ', 'ﭞ' => 'ٺ', 'ﭟ' => 'ٺ', 'ﭠ' => 'ٺ', 'ﭡ' => 'ٺ', 'ﭢ' => 'ٿ', 'ﭣ' => 'ٿ', 'ﭤ' => 'ٿ', 'ﭥ' => 'ٿ', 'ﭦ' => 'ٹ', 'ﭧ' => 'ٹ', 'ﭨ' => 'ٹ', 'ﭩ' => 'ٹ', 'ﭪ' => 'ڤ', 'ﭫ' => 'ڤ', 'ﭬ' => 'ڤ', 'ﭭ' => 'ڤ', 'ﭮ' => 'ڦ', 'ﭯ' => 'ڦ', 'ﭰ' => 'ڦ', 'ﭱ' => 'ڦ', 'ﭲ' => 'ڄ', 'ﭳ' => 'ڄ', 'ﭴ' => 'ڄ', 'ﭵ' => 'ڄ', 'ﭶ' => 'ڃ', 'ﭷ' => 'ڃ', 'ﭸ' => 'ڃ', 'ﭹ' => 'ڃ', 'ﭺ' => 'چ', 'ﭻ' => 'چ', 'ﭼ' => 'چ', 'ﭽ' => 'چ', 'ﭾ' => 'ڇ', 'ﭿ' => 'ڇ', 'ﮀ' => 'ڇ', 'ﮁ' => 'ڇ', 'ﮂ' => 'ڍ', 'ﮃ' => 'ڍ', 'ﮄ' => 'ڌ', 'ﮅ' => 'ڌ', 'ﮆ' => 'ڎ', 'ﮇ' => 'ڎ', 'ﮈ' => 'ڈ', 'ﮉ' => 'ڈ', 'ﮊ' => 'ژ', 'ﮋ' => 'ژ', 'ﮌ' => 'ڑ', 'ﮍ' => 'ڑ', 'ﮎ' => 'ک', 'ﮏ' => 'ک', 'ﮐ' => 'ک', 'ﮑ' => 'ک', 'ﮒ' => 'گ', 'ﮓ' => 'گ', 'ﮔ' => 'گ', 'ﮕ' => 'گ', 'ﮖ' => 'ڳ', 'ﮗ' => 'ڳ', 'ﮘ' => 'ڳ', 'ﮙ' => 'ڳ', 'ﮚ' => 'ڱ', 'ﮛ' => 'ڱ', 'ﮜ' => 'ڱ', 'ﮝ' => 'ڱ', 'ﮞ' => 'ں', 'ﮟ' => 'ں', 'ﮠ' => 'ڻ', 'ﮡ' => 'ڻ', 'ﮢ' => 'ڻ', 'ﮣ' => 'ڻ', 'ﮤ' => 'ۀ', 'ﮥ' => 'ۀ', 'ﮦ' => 'ہ', 'ﮧ' => 'ہ', 'ﮨ' => 'ہ', 'ﮩ' => 'ہ', 'ﮪ' => 'ھ', 'ﮫ' => 'ھ', 'ﮬ' => 'ھ', 'ﮭ' => 'ھ', 'ﮮ' => 'ے', 'ﮯ' => 'ے', 'ﮰ' => 'ۓ', 'ﮱ' => 'ۓ', 'ﯓ' => 'ڭ', 'ﯔ' => 'ڭ', 'ﯕ' => 'ڭ', 'ﯖ' => 'ڭ', 'ﯗ' => 'ۇ', 'ﯘ' => 'ۇ', 'ﯙ' => 'ۆ', 'ﯚ' => 'ۆ', 'ﯛ' => 'ۈ', 'ﯜ' => 'ۈ', 'ﯝ' => 'ٷ', 'ﯞ' => 'ۋ', 'ﯟ' => 'ۋ', 'ﯠ' => 'ۅ', 'ﯡ' => 'ۅ', 'ﯢ' => 'ۉ', 'ﯣ' => 'ۉ', 'ﯤ' => 'ې', 'ﯥ' => 'ې', 'ﯦ' => 'ې', 'ﯧ' => 'ې', 'ﯨ' => 'ى', 'ﯩ' => 'ى', 'ﯪ' => 'ئا', 'ﯫ' => 'ئا', 'ﯬ' => 'ئە', 'ﯭ' => 'ئە', 'ﯮ' => 'ئو', 'ﯯ' => 'ئو', 'ﯰ' => 'ئۇ', 'ﯱ' => 'ئۇ', 'ﯲ' => 'ئۆ', 'ﯳ' => 'ئۆ', 'ﯴ' => 'ئۈ', 'ﯵ' => 'ئۈ', 'ﯶ' => 'ئې', 'ﯷ' => 'ئې', 'ﯸ' => 'ئې', 'ﯹ' => 'ئى', 'ﯺ' => 'ئى', 'ﯻ' => 'ئى', 'ﯼ' => 'ی', 'ﯽ' => 'ی', 'ﯾ' => 'ی', 'ﯿ' => 'ی', 'ﰀ' => 'ئج', 'ﰁ' => 'ئح', 'ﰂ' => 'ئم', 'ﰃ' => 'ئى', 'ﰄ' => 'ئي', 'ﰅ' => 'بج', 'ﰆ' => 'بح', 'ﰇ' => 'بخ', 'ﰈ' => 'بم', 'ﰉ' => 'بى', 'ﰊ' => 'بي', 'ﰋ' => 'تج', 'ﰌ' => 'تح', 'ﰍ' => 'تخ', 'ﰎ' => 'تم', 'ﰏ' => 'تى', 'ﰐ' => 'تي', 'ﰑ' => 'ثج', 'ﰒ' => 'ثم', 'ﰓ' => 'ثى', 'ﰔ' => 'ثي', 'ﰕ' => 'جح', 'ﰖ' => 'جم', 'ﰗ' => 'حج', 'ﰘ' => 'حم', 'ﰙ' => 'خج', 'ﰚ' => 'خح', 'ﰛ' => 'خم', 'ﰜ' => 'سج', 'ﰝ' => 'سح', 'ﰞ' => 'سخ', 'ﰟ' => 'سم', 'ﰠ' => 'صح', 'ﰡ' => 'صم', 'ﰢ' => 'ضج', 'ﰣ' => 'ضح', 'ﰤ' => 'ضخ', 'ﰥ' => 'ضم', 'ﰦ' => 'طح', 'ﰧ' => 'طم', 'ﰨ' => 'ظم', 'ﰩ' => 'عج', 'ﰪ' => 'عم', 'ﰫ' => 'غج', 'ﰬ' => 'غم', 'ﰭ' => 'فج', 'ﰮ' => 'فح', 'ﰯ' => 'فخ', 'ﰰ' => 'فم', 'ﰱ' => 'فى', 'ﰲ' => 'في', 'ﰳ' => 'قح', 'ﰴ' => 'قم', 'ﰵ' => 'قى', 'ﰶ' => 'قي', 'ﰷ' => 'كا', 'ﰸ' => 'كج', 'ﰹ' => 'كح', 'ﰺ' => 'كخ', 'ﰻ' => 'كل', 'ﰼ' => 'كم', 'ﰽ' => 'كى', 'ﰾ' => 'كي', 'ﰿ' => 'لج', 'ﱀ' => 'لح', 'ﱁ' => 'لخ', 'ﱂ' => 'لم', 'ﱃ' => 'لى', 'ﱄ' => 'لي', 'ﱅ' => 'مج', 'ﱆ' => 'مح', 'ﱇ' => 'مخ', 'ﱈ' => 'مم', 'ﱉ' => 'مى', 'ﱊ' => 'مي', 'ﱋ' => 'نج', 'ﱌ' => 'نح', 'ﱍ' => 'نخ', 'ﱎ' => 'نم', 'ﱏ' => 'نى', 'ﱐ' => 'ني', 'ﱑ' => 'هج', 'ﱒ' => 'هم', 'ﱓ' => 'هى', 'ﱔ' => 'هي', 'ﱕ' => 'يج', 'ﱖ' => 'يح', 'ﱗ' => 'يخ', 'ﱘ' => 'يم', 'ﱙ' => 'يى', 'ﱚ' => 'يي', 'ﱛ' => 'ذٰ', 'ﱜ' => 'رٰ', 'ﱝ' => 'ىٰ', 'ﱞ' => ' ٌّ', 'ﱟ' => ' ٍّ', 'ﱠ' => ' َّ', 'ﱡ' => ' ُّ', 'ﱢ' => ' ِّ', 'ﱣ' => ' ّٰ', 'ﱤ' => 'ئر', 'ﱥ' => 'ئز', 'ﱦ' => 'ئم', 'ﱧ' => 'ئن', 'ﱨ' => 'ئى', 'ﱩ' => 'ئي', 'ﱪ' => 'بر', 'ﱫ' => 'بز', 'ﱬ' => 'بم', 'ﱭ' => 'بن', 'ﱮ' => 'بى', 'ﱯ' => 'بي', 'ﱰ' => 'تر', 'ﱱ' => 'تز', 'ﱲ' => 'تم', 'ﱳ' => 'تن', 'ﱴ' => 'تى', 'ﱵ' => 'تي', 'ﱶ' => 'ثر', 'ﱷ' => 'ثز', 'ﱸ' => 'ثم', 'ﱹ' => 'ثن', 'ﱺ' => 'ثى', 'ﱻ' => 'ثي', 'ﱼ' => 'فى', 'ﱽ' => 'في', 'ﱾ' => 'قى', 'ﱿ' => 'قي', 'ﲀ' => 'كا', 'ﲁ' => 'كل', 'ﲂ' => 'كم', 'ﲃ' => 'كى', 'ﲄ' => 'كي', 'ﲅ' => 'لم', 'ﲆ' => 'لى', 'ﲇ' => 'لي', 'ﲈ' => 'ما', 'ﲉ' => 'مم', 'ﲊ' => 'نر', 'ﲋ' => 'نز', 'ﲌ' => 'نم', 'ﲍ' => 'نن', 'ﲎ' => 'نى', 'ﲏ' => 'ني', 'ﲐ' => 'ىٰ', 'ﲑ' => 'ير', 'ﲒ' => 'يز', 'ﲓ' => 'يم', 'ﲔ' => 'ين', 'ﲕ' => 'يى', 'ﲖ' => 'يي', 'ﲗ' => 'ئج', 'ﲘ' => 'ئح', 'ﲙ' => 'ئخ', 'ﲚ' => 'ئم', 'ﲛ' => 'ئه', 'ﲜ' => 'بج', 'ﲝ' => 'بح', 'ﲞ' => 'بخ', 'ﲟ' => 'بم', 'ﲠ' => 'به', 'ﲡ' => 'تج', 'ﲢ' => 'تح', 'ﲣ' => 'تخ', 'ﲤ' => 'تم', 'ﲥ' => 'ته', 'ﲦ' => 'ثم', 'ﲧ' => 'جح', 'ﲨ' => 'جم', 'ﲩ' => 'حج', 'ﲪ' => 'حم', 'ﲫ' => 'خج', 'ﲬ' => 'خم', 'ﲭ' => 'سج', 'ﲮ' => 'سح', 'ﲯ' => 'سخ', 'ﲰ' => 'سم', 'ﲱ' => 'صح', 'ﲲ' => 'صخ', 'ﲳ' => 'صم', 'ﲴ' => 'ضج', 'ﲵ' => 'ضح', 'ﲶ' => 'ضخ', 'ﲷ' => 'ضم', 'ﲸ' => 'طح', 'ﲹ' => 'ظم', 'ﲺ' => 'عج', 'ﲻ' => 'عم', 'ﲼ' => 'غج', 'ﲽ' => 'غم', 'ﲾ' => 'فج', 'ﲿ' => 'فح', 'ﳀ' => 'فخ', 'ﳁ' => 'فم', 'ﳂ' => 'قح', 'ﳃ' => 'قم', 'ﳄ' => 'كج', 'ﳅ' => 'كح', 'ﳆ' => 'كخ', 'ﳇ' => 'كل', 'ﳈ' => 'كم', 'ﳉ' => 'لج', 'ﳊ' => 'لح', 'ﳋ' => 'لخ', 'ﳌ' => 'لم', 'ﳍ' => 'له', 'ﳎ' => 'مج', 'ﳏ' => 'مح', 'ﳐ' => 'مخ', 'ﳑ' => 'مم', 'ﳒ' => 'نج', 'ﳓ' => 'نح', 'ﳔ' => 'نخ', 'ﳕ' => 'نم', 'ﳖ' => 'نه', 'ﳗ' => 'هج', 'ﳘ' => 'هم', 'ﳙ' => 'هٰ', 'ﳚ' => 'يج', 'ﳛ' => 'يح', 'ﳜ' => 'يخ', 'ﳝ' => 'يم', 'ﳞ' => 'يه', 'ﳟ' => 'ئم', 'ﳠ' => 'ئه', 'ﳡ' => 'بم', 'ﳢ' => 'به', 'ﳣ' => 'تم', 'ﳤ' => 'ته', 'ﳥ' => 'ثم', 'ﳦ' => 'ثه', 'ﳧ' => 'سم', 'ﳨ' => 'سه', 'ﳩ' => 'شم', 'ﳪ' => 'شه', 'ﳫ' => 'كل', 'ﳬ' => 'كم', 'ﳭ' => 'لم', 'ﳮ' => 'نم', 'ﳯ' => 'نه', 'ﳰ' => 'يم', 'ﳱ' => 'يه', 'ﳲ' => 'ـَّ', 'ﳳ' => 'ـُّ', 'ﳴ' => 'ـِّ', 'ﳵ' => 'طى', 'ﳶ' => 'طي', 'ﳷ' => 'عى', 'ﳸ' => 'عي', 'ﳹ' => 'غى', 'ﳺ' => 'غي', 'ﳻ' => 'سى', 'ﳼ' => 'سي', 'ﳽ' => 'شى', 'ﳾ' => 'شي', 'ﳿ' => 'حى', 'ﴀ' => 'حي', 'ﴁ' => 'جى', 'ﴂ' => 'جي', 'ﴃ' => 'خى', 'ﴄ' => 'خي', 'ﴅ' => 'صى', 'ﴆ' => 'صي', 'ﴇ' => 'ضى', 'ﴈ' => 'ضي', 'ﴉ' => 'شج', 'ﴊ' => 'شح', 'ﴋ' => 'شخ', 'ﴌ' => 'شم', 'ﴍ' => 'شر', 'ﴎ' => 'سر', 'ﴏ' => 'صر', 'ﴐ' => 'ضر', 'ﴑ' => 'طى', 'ﴒ' => 'طي', 'ﴓ' => 'عى', 'ﴔ' => 'عي', 'ﴕ' => 'غى', 'ﴖ' => 'غي', 'ﴗ' => 'سى', 'ﴘ' => 'سي', 'ﴙ' => 'شى', 'ﴚ' => 'شي', 'ﴛ' => 'حى', 'ﴜ' => 'حي', 'ﴝ' => 'جى', 'ﴞ' => 'جي', 'ﴟ' => 'خى', 'ﴠ' => 'خي', 'ﴡ' => 'صى', 'ﴢ' => 'صي', 'ﴣ' => 'ضى', 'ﴤ' => 'ضي', 'ﴥ' => 'شج', 'ﴦ' => 'شح', 'ﴧ' => 'شخ', 'ﴨ' => 'شم', 'ﴩ' => 'شر', 'ﴪ' => 'سر', 'ﴫ' => 'صر', 'ﴬ' => 'ضر', 'ﴭ' => 'شج', 'ﴮ' => 'شح', 'ﴯ' => 'شخ', 'ﴰ' => 'شم', 'ﴱ' => 'سه', 'ﴲ' => 'شه', 'ﴳ' => 'طم', 'ﴴ' => 'سج', 'ﴵ' => 'سح', 'ﴶ' => 'سخ', 'ﴷ' => 'شج', 'ﴸ' => 'شح', 'ﴹ' => 'شخ', 'ﴺ' => 'طم', 'ﴻ' => 'ظم', 'ﴼ' => 'اً', 'ﴽ' => 'اً', 'ﵐ' => 'تجم', 'ﵑ' => 'تحج', 'ﵒ' => 'تحج', 'ﵓ' => 'تحم', 'ﵔ' => 'تخم', 'ﵕ' => 'تمج', 'ﵖ' => 'تمح', 'ﵗ' => 'تمخ', 'ﵘ' => 'جمح', 'ﵙ' => 'جمح', 'ﵚ' => 'حمي', 'ﵛ' => 'حمى', 'ﵜ' => 'سحج', 'ﵝ' => 'سجح', 'ﵞ' => 'سجى', 'ﵟ' => 'سمح', 'ﵠ' => 'سمح', 'ﵡ' => 'سمج', 'ﵢ' => 'سمم', 'ﵣ' => 'سمم', 'ﵤ' => 'صحح', 'ﵥ' => 'صحح', 'ﵦ' => 'صمم', 'ﵧ' => 'شحم', 'ﵨ' => 'شحم', 'ﵩ' => 'شجي', 'ﵪ' => 'شمخ', 'ﵫ' => 'شمخ', 'ﵬ' => 'شمم', 'ﵭ' => 'شمم', 'ﵮ' => 'ضحى', 'ﵯ' => 'ضخم', 'ﵰ' => 'ضخم', 'ﵱ' => 'طمح', 'ﵲ' => 'طمح', 'ﵳ' => 'طمم', 'ﵴ' => 'طمي', 'ﵵ' => 'عجم', 'ﵶ' => 'عمم', 'ﵷ' => 'عمم', 'ﵸ' => 'عمى', 'ﵹ' => 'غمم', 'ﵺ' => 'غمي', 'ﵻ' => 'غمى', 'ﵼ' => 'فخم', 'ﵽ' => 'فخم', 'ﵾ' => 'قمح', 'ﵿ' => 'قمم', 'ﶀ' => 'لحم', 'ﶁ' => 'لحي', 'ﶂ' => 'لحى', 'ﶃ' => 'لجج', 'ﶄ' => 'لجج', 'ﶅ' => 'لخم', 'ﶆ' => 'لخم', 'ﶇ' => 'لمح', 'ﶈ' => 'لمح', 'ﶉ' => 'محج', 'ﶊ' => 'محم', 'ﶋ' => 'محي', 'ﶌ' => 'مجح', 'ﶍ' => 'مجم', 'ﶎ' => 'مخج', 'ﶏ' => 'مخم', 'ﶒ' => 'مجخ', 'ﶓ' => 'همج', 'ﶔ' => 'همم', 'ﶕ' => 'نحم', 'ﶖ' => 'نحى', 'ﶗ' => 'نجم', 'ﶘ' => 'نجم', 'ﶙ' => 'نجى', 'ﶚ' => 'نمي', 'ﶛ' => 'نمى', 'ﶜ' => 'يمم', 'ﶝ' => 'يمم', 'ﶞ' => 'بخي', 'ﶟ' => 'تجي', 'ﶠ' => 'تجى', 'ﶡ' => 'تخي', 'ﶢ' => 'تخى', 'ﶣ' => 'تمي', 'ﶤ' => 'تمى', 'ﶥ' => 'جمي', 'ﶦ' => 'جحى', 'ﶧ' => 'جمى', 'ﶨ' => 'سخى', 'ﶩ' => 'صحي', 'ﶪ' => 'شحي', 'ﶫ' => 'ضحي', 'ﶬ' => 'لجي', 'ﶭ' => 'لمي', 'ﶮ' => 'يحي', 'ﶯ' => 'يجي', 'ﶰ' => 'يمي', 'ﶱ' => 'ممي', 'ﶲ' => 'قمي', 'ﶳ' => 'نحي', 'ﶴ' => 'قمح', 'ﶵ' => 'لحم', 'ﶶ' => 'عمي', 'ﶷ' => 'كمي', 'ﶸ' => 'نجح', 'ﶹ' => 'مخي', 'ﶺ' => 'لجم', 'ﶻ' => 'كمم', 'ﶼ' => 'لجم', 'ﶽ' => 'نجح', 'ﶾ' => 'جحي', 'ﶿ' => 'حجي', 'ﷀ' => 'مجي', 'ﷁ' => 'فمي', 'ﷂ' => 'بحي', 'ﷃ' => 'كمم', 'ﷄ' => 'عجم', 'ﷅ' => 'صمم', 'ﷆ' => 'سخي', 'ﷇ' => 'نجي', 'ﷰ' => 'صلے', 'ﷱ' => 'قلے', 'ﷲ' => 'الله', 'ﷳ' => 'اكبر', 'ﷴ' => 'محمد', 'ﷵ' => 'صلعم', 'ﷶ' => 'رسول', 'ﷷ' => 'عليه', 'ﷸ' => 'وسلم', 'ﷹ' => 'صلى', 'ﷺ' => 'صلى الله عليه وسلم', 'ﷻ' => 'جل جلاله', '﷼' => 'ریال', '︐' => ',', '︑' => '、', '︒' => '。', '︓' => ':', '︔' => ';', '︕' => '!', '︖' => '?', '︗' => '〖', '︘' => '〗', '︙' => '…', '︰' => '‥', '︱' => '—', '︲' => '–', '︳' => '_', '︴' => '_', '︵' => '(', '︶' => ')', '︷' => '{', '︸' => '}', '︹' => '〔', '︺' => '〕', '︻' => '【', '︼' => '】', '︽' => '《', '︾' => '》', '︿' => '〈', '﹀' => '〉', '﹁' => '「', '﹂' => '」', '﹃' => '『', '﹄' => '』', '﹇' => '[', '﹈' => ']', '﹉' => '‾', '﹊' => '‾', '﹋' => '‾', '﹌' => '‾', '﹍' => '_', '﹎' => '_', '﹏' => '_', '﹐' => ',', '﹑' => '、', '﹒' => '.', '﹔' => ';', '﹕' => ':', '﹖' => '?', '﹗' => '!', '﹘' => '—', '﹙' => '(', '﹚' => ')', '﹛' => '{', '﹜' => '}', '﹝' => '〔', '﹞' => '〕', '﹟' => '#', '﹠' => '&', '﹡' => '*', '﹢' => '+', '﹣' => '-', '﹤' => '<', '﹥' => '>', '﹦' => '=', '﹨' => '\\', '﹩' => '$', '﹪' => '%', '﹫' => '@', 'ﹰ' => ' ً', 'ﹱ' => 'ـً', 'ﹲ' => ' ٌ', 'ﹴ' => ' ٍ', 'ﹶ' => ' َ', 'ﹷ' => 'ـَ', 'ﹸ' => ' ُ', 'ﹹ' => 'ـُ', 'ﹺ' => ' ِ', 'ﹻ' => 'ـِ', 'ﹼ' => ' ّ', 'ﹽ' => 'ـّ', 'ﹾ' => ' ْ', 'ﹿ' => 'ـْ', 'ﺀ' => 'ء', 'ﺁ' => 'آ', 'ﺂ' => 'آ', 'ﺃ' => 'أ', 'ﺄ' => 'أ', 'ﺅ' => 'ؤ', 'ﺆ' => 'ؤ', 'ﺇ' => 'إ', 'ﺈ' => 'إ', 'ﺉ' => 'ئ', 'ﺊ' => 'ئ', 'ﺋ' => 'ئ', 'ﺌ' => 'ئ', 'ﺍ' => 'ا', 'ﺎ' => 'ا', 'ﺏ' => 'ب', 'ﺐ' => 'ب', 'ﺑ' => 'ب', 'ﺒ' => 'ب', 'ﺓ' => 'ة', 'ﺔ' => 'ة', 'ﺕ' => 'ت', 'ﺖ' => 'ت', 'ﺗ' => 'ت', 'ﺘ' => 'ت', 'ﺙ' => 'ث', 'ﺚ' => 'ث', 'ﺛ' => 'ث', 'ﺜ' => 'ث', 'ﺝ' => 'ج', 'ﺞ' => 'ج', 'ﺟ' => 'ج', 'ﺠ' => 'ج', 'ﺡ' => 'ح', 'ﺢ' => 'ح', 'ﺣ' => 'ح', 'ﺤ' => 'ح', 'ﺥ' => 'خ', 'ﺦ' => 'خ', 'ﺧ' => 'خ', 'ﺨ' => 'خ', 'ﺩ' => 'د', 'ﺪ' => 'د', 'ﺫ' => 'ذ', 'ﺬ' => 'ذ', 'ﺭ' => 'ر', 'ﺮ' => 'ر', 'ﺯ' => 'ز', 'ﺰ' => 'ز', 'ﺱ' => 'س', 'ﺲ' => 'س', 'ﺳ' => 'س', 'ﺴ' => 'س', 'ﺵ' => 'ش', 'ﺶ' => 'ش', 'ﺷ' => 'ش', 'ﺸ' => 'ش', 'ﺹ' => 'ص', 'ﺺ' => 'ص', 'ﺻ' => 'ص', 'ﺼ' => 'ص', 'ﺽ' => 'ض', 'ﺾ' => 'ض', 'ﺿ' => 'ض', 'ﻀ' => 'ض', 'ﻁ' => 'ط', 'ﻂ' => 'ط', 'ﻃ' => 'ط', 'ﻄ' => 'ط', 'ﻅ' => 'ظ', 'ﻆ' => 'ظ', 'ﻇ' => 'ظ', 'ﻈ' => 'ظ', 'ﻉ' => 'ع', 'ﻊ' => 'ع', 'ﻋ' => 'ع', 'ﻌ' => 'ع', 'ﻍ' => 'غ', 'ﻎ' => 'غ', 'ﻏ' => 'غ', 'ﻐ' => 'غ', 'ﻑ' => 'ف', 'ﻒ' => 'ف', 'ﻓ' => 'ف', 'ﻔ' => 'ف', 'ﻕ' => 'ق', 'ﻖ' => 'ق', 'ﻗ' => 'ق', 'ﻘ' => 'ق', 'ﻙ' => 'ك', 'ﻚ' => 'ك', 'ﻛ' => 'ك', 'ﻜ' => 'ك', 'ﻝ' => 'ل', 'ﻞ' => 'ل', 'ﻟ' => 'ل', 'ﻠ' => 'ل', 'ﻡ' => 'م', 'ﻢ' => 'م', 'ﻣ' => 'م', 'ﻤ' => 'م', 'ﻥ' => 'ن', 'ﻦ' => 'ن', 'ﻧ' => 'ن', 'ﻨ' => 'ن', 'ﻩ' => 'ه', 'ﻪ' => 'ه', 'ﻫ' => 'ه', 'ﻬ' => 'ه', 'ﻭ' => 'و', 'ﻮ' => 'و', 'ﻯ' => 'ى', 'ﻰ' => 'ى', 'ﻱ' => 'ي', 'ﻲ' => 'ي', 'ﻳ' => 'ي', 'ﻴ' => 'ي', 'ﻵ' => 'لآ', 'ﻶ' => 'لآ', 'ﻷ' => 'لأ', 'ﻸ' => 'لأ', 'ﻹ' => 'لإ', 'ﻺ' => 'لإ', 'ﻻ' => 'لا', 'ﻼ' => 'لا', '!' => '!', '"' => '"', '#' => '#', '$' => '$', '%' => '%', '&' => '&', ''' => '\'', '(' => '(', ')' => ')', '*' => '*', '+' => '+', ',' => ',', '-' => '-', '.' => '.', '/' => '/', '0' => '0', '1' => '1', '2' => '2', '3' => '3', '4' => '4', '5' => '5', '6' => '6', '7' => '7', '8' => '8', '9' => '9', ':' => ':', ';' => ';', '<' => '<', '=' => '=', '>' => '>', '?' => '?', '@' => '@', 'A' => 'A', 'B' => 'B', 'C' => 'C', 'D' => 'D', 'E' => 'E', 'F' => 'F', 'G' => 'G', 'H' => 'H', 'I' => 'I', 'J' => 'J', 'K' => 'K', 'L' => 'L', 'M' => 'M', 'N' => 'N', 'O' => 'O', 'P' => 'P', 'Q' => 'Q', 'R' => 'R', 'S' => 'S', 'T' => 'T', 'U' => 'U', 'V' => 'V', 'W' => 'W', 'X' => 'X', 'Y' => 'Y', 'Z' => 'Z', '[' => '[', '\' => '\\', ']' => ']', '^' => '^', '_' => '_', '`' => '`', 'a' => 'a', 'b' => 'b', 'c' => 'c', 'd' => 'd', 'e' => 'e', 'f' => 'f', 'g' => 'g', 'h' => 'h', 'i' => 'i', 'j' => 'j', 'k' => 'k', 'l' => 'l', 'm' => 'm', 'n' => 'n', 'o' => 'o', 'p' => 'p', 'q' => 'q', 'r' => 'r', 's' => 's', 't' => 't', 'u' => 'u', 'v' => 'v', 'w' => 'w', 'x' => 'x', 'y' => 'y', 'z' => 'z', '{' => '{', '|' => '|', '}' => '}', '~' => '~', '⦅' => '⦅', '⦆' => '⦆', '。' => '。', '「' => '「', '」' => '」', '、' => '、', '・' => '・', 'ヲ' => 'ヲ', 'ァ' => 'ァ', 'ィ' => 'ィ', 'ゥ' => 'ゥ', 'ェ' => 'ェ', 'ォ' => 'ォ', 'ャ' => 'ャ', 'ュ' => 'ュ', 'ョ' => 'ョ', 'ッ' => 'ッ', 'ー' => 'ー', 'ア' => 'ア', 'イ' => 'イ', 'ウ' => 'ウ', 'エ' => 'エ', 'オ' => 'オ', 'カ' => 'カ', 'キ' => 'キ', 'ク' => 'ク', 'ケ' => 'ケ', 'コ' => 'コ', 'サ' => 'サ', 'シ' => 'シ', 'ス' => 'ス', 'セ' => 'セ', 'ソ' => 'ソ', 'タ' => 'タ', 'チ' => 'チ', 'ツ' => 'ツ', 'テ' => 'テ', 'ト' => 'ト', 'ナ' => 'ナ', 'ニ' => 'ニ', 'ヌ' => 'ヌ', 'ネ' => 'ネ', 'ノ' => 'ノ', 'ハ' => 'ハ', 'ヒ' => 'ヒ', 'フ' => 'フ', 'ヘ' => 'ヘ', 'ホ' => 'ホ', 'マ' => 'マ', 'ミ' => 'ミ', 'ム' => 'ム', 'メ' => 'メ', 'モ' => 'モ', 'ヤ' => 'ヤ', 'ユ' => 'ユ', 'ヨ' => 'ヨ', 'ラ' => 'ラ', 'リ' => 'リ', 'ル' => 'ル', 'レ' => 'レ', 'ロ' => 'ロ', 'ワ' => 'ワ', 'ン' => 'ン', '゙' => '゙', '゚' => '゚', 'ᅠ' => 'ㅤ', 'ᄀ' => 'ㄱ', 'ᄁ' => 'ㄲ', 'ᆪ' => 'ㄳ', 'ᄂ' => 'ㄴ', 'ᆬ' => 'ㄵ', 'ᆭ' => 'ㄶ', 'ᄃ' => 'ㄷ', 'ᄄ' => 'ㄸ', 'ᄅ' => 'ㄹ', 'ᆰ' => 'ㄺ', 'ᆱ' => 'ㄻ', 'ᆲ' => 'ㄼ', 'ᆳ' => 'ㄽ', 'ᆴ' => 'ㄾ', 'ᆵ' => 'ㄿ', 'ᄚ' => 'ㅀ', 'ᄆ' => 'ㅁ', 'ᄇ' => 'ㅂ', 'ᄈ' => 'ㅃ', 'ᄡ' => 'ㅄ', 'ᄉ' => 'ㅅ', 'ᄊ' => 'ㅆ', 'ᄋ' => 'ㅇ', 'ᄌ' => 'ㅈ', 'ᄍ' => 'ㅉ', 'ᄎ' => 'ㅊ', 'ᄏ' => 'ㅋ', 'ᄐ' => 'ㅌ', 'ᄑ' => 'ㅍ', 'ᄒ' => 'ㅎ', 'ᅡ' => 'ㅏ', 'ᅢ' => 'ㅐ', 'ᅣ' => 'ㅑ', 'ᅤ' => 'ㅒ', 'ᅥ' => 'ㅓ', 'ᅦ' => 'ㅔ', 'ᅧ' => 'ㅕ', 'ᅨ' => 'ㅖ', 'ᅩ' => 'ㅗ', 'ᅪ' => 'ㅘ', 'ᅫ' => 'ㅙ', 'ᅬ' => 'ㅚ', 'ᅭ' => 'ㅛ', 'ᅮ' => 'ㅜ', 'ᅯ' => 'ㅝ', 'ᅰ' => 'ㅞ', 'ᅱ' => 'ㅟ', 'ᅲ' => 'ㅠ', 'ᅳ' => 'ㅡ', 'ᅴ' => 'ㅢ', 'ᅵ' => 'ㅣ', '¢' => '¢', '£' => '£', '¬' => '¬', ' ̄' => '¯', '¦' => '¦', '¥' => '¥', '₩' => '₩', '│' => '│', '←' => '←', '↑' => '↑', '→' => '→', '↓' => '↓', '■' => '■', '○' => '○', '𐞁' => 'ː', '𐞂' => 'ˑ', '𐞃' => 'æ', '𐞄' => 'ʙ', '𐞅' => 'ɓ', '𐞇' => 'ʣ', '𐞈' => 'ꭦ', '𐞉' => 'ʥ', '𐞊' => 'ʤ', '𐞋' => 'ɖ', '𐞌' => 'ɗ', '𐞍' => 'ᶑ', '𐞎' => 'ɘ', '𐞏' => 'ɞ', '𐞐' => 'ʩ', '𐞑' => 'ɤ', '𐞒' => 'ɢ', '𐞓' => 'ɠ', '𐞔' => 'ʛ', '𐞕' => 'ħ', '𐞖' => 'ʜ', '𐞗' => 'ɧ', '𐞘' => 'ʄ', '𐞙' => 'ʪ', '𐞚' => 'ʫ', '𐞛' => 'ɬ', '𐞜' => '𝼄', '𐞝' => 'ꞎ', '𐞞' => 'ɮ', '𐞟' => '𝼅', '𐞠' => 'ʎ', '𐞡' => '𝼆', '𐞢' => 'ø', '𐞣' => 'ɶ', '𐞤' => 'ɷ', '𐞥' => 'q', '𐞦' => 'ɺ', '𐞧' => '𝼈', '𐞨' => 'ɽ', '𐞩' => 'ɾ', '𐞪' => 'ʀ', '𐞫' => 'ʨ', '𐞬' => 'ʦ', '𐞭' => 'ꭧ', '𐞮' => 'ʧ', '𐞯' => 'ʈ', '𐞰' => 'ⱱ', '𐞲' => 'ʏ', '𐞳' => 'ʡ', '𐞴' => 'ʢ', '𐞵' => 'ʘ', '𐞶' => 'ǀ', '𐞷' => 'ǁ', '𐞸' => 'ǂ', '𐞹' => '𝼊', '𐞺' => '𝼞', '𝐀' => 'A', '𝐁' => 'B', '𝐂' => 'C', '𝐃' => 'D', '𝐄' => 'E', '𝐅' => 'F', '𝐆' => 'G', '𝐇' => 'H', '𝐈' => 'I', '𝐉' => 'J', '𝐊' => 'K', '𝐋' => 'L', '𝐌' => 'M', '𝐍' => 'N', '𝐎' => 'O', '𝐏' => 'P', '𝐐' => 'Q', '𝐑' => 'R', '𝐒' => 'S', '𝐓' => 'T', '𝐔' => 'U', '𝐕' => 'V', '𝐖' => 'W', '𝐗' => 'X', '𝐘' => 'Y', '𝐙' => 'Z', '𝐚' => 'a', '𝐛' => 'b', '𝐜' => 'c', '𝐝' => 'd', '𝐞' => 'e', '𝐟' => 'f', '𝐠' => 'g', '𝐡' => 'h', '𝐢' => 'i', '𝐣' => 'j', '𝐤' => 'k', '𝐥' => 'l', '𝐦' => 'm', '𝐧' => 'n', '𝐨' => 'o', '𝐩' => 'p', '𝐪' => 'q', '𝐫' => 'r', '𝐬' => 's', '𝐭' => 't', '𝐮' => 'u', '𝐯' => 'v', '𝐰' => 'w', '𝐱' => 'x', '𝐲' => 'y', '𝐳' => 'z', '𝐴' => 'A', '𝐵' => 'B', '𝐶' => 'C', '𝐷' => 'D', '𝐸' => 'E', '𝐹' => 'F', '𝐺' => 'G', '𝐻' => 'H', '𝐼' => 'I', '𝐽' => 'J', '𝐾' => 'K', '𝐿' => 'L', '𝑀' => 'M', '𝑁' => 'N', '𝑂' => 'O', '𝑃' => 'P', '𝑄' => 'Q', '𝑅' => 'R', '𝑆' => 'S', '𝑇' => 'T', '𝑈' => 'U', '𝑉' => 'V', '𝑊' => 'W', '𝑋' => 'X', '𝑌' => 'Y', '𝑍' => 'Z', '𝑎' => 'a', '𝑏' => 'b', '𝑐' => 'c', '𝑑' => 'd', '𝑒' => 'e', '𝑓' => 'f', '𝑔' => 'g', '𝑖' => 'i', '𝑗' => 'j', '𝑘' => 'k', '𝑙' => 'l', '𝑚' => 'm', '𝑛' => 'n', '𝑜' => 'o', '𝑝' => 'p', '𝑞' => 'q', '𝑟' => 'r', '𝑠' => 's', '𝑡' => 't', '𝑢' => 'u', '𝑣' => 'v', '𝑤' => 'w', '𝑥' => 'x', '𝑦' => 'y', '𝑧' => 'z', '𝑨' => 'A', '𝑩' => 'B', '𝑪' => 'C', '𝑫' => 'D', '𝑬' => 'E', '𝑭' => 'F', '𝑮' => 'G', '𝑯' => 'H', '𝑰' => 'I', '𝑱' => 'J', '𝑲' => 'K', '𝑳' => 'L', '𝑴' => 'M', '𝑵' => 'N', '𝑶' => 'O', '𝑷' => 'P', '𝑸' => 'Q', '𝑹' => 'R', '𝑺' => 'S', '𝑻' => 'T', '𝑼' => 'U', '𝑽' => 'V', '𝑾' => 'W', '𝑿' => 'X', '𝒀' => 'Y', '𝒁' => 'Z', '𝒂' => 'a', '𝒃' => 'b', '𝒄' => 'c', '𝒅' => 'd', '𝒆' => 'e', '𝒇' => 'f', '𝒈' => 'g', '𝒉' => 'h', '𝒊' => 'i', '𝒋' => 'j', '𝒌' => 'k', '𝒍' => 'l', '𝒎' => 'm', '𝒏' => 'n', '𝒐' => 'o', '𝒑' => 'p', '𝒒' => 'q', '𝒓' => 'r', '𝒔' => 's', '𝒕' => 't', '𝒖' => 'u', '𝒗' => 'v', '𝒘' => 'w', '𝒙' => 'x', '𝒚' => 'y', '𝒛' => 'z', '𝒜' => 'A', '𝒞' => 'C', '𝒟' => 'D', '𝒢' => 'G', '𝒥' => 'J', '𝒦' => 'K', '𝒩' => 'N', '𝒪' => 'O', '𝒫' => 'P', '𝒬' => 'Q', '𝒮' => 'S', '𝒯' => 'T', '𝒰' => 'U', '𝒱' => 'V', '𝒲' => 'W', '𝒳' => 'X', '𝒴' => 'Y', '𝒵' => 'Z', '𝒶' => 'a', '𝒷' => 'b', '𝒸' => 'c', '𝒹' => 'd', '𝒻' => 'f', '𝒽' => 'h', '𝒾' => 'i', '𝒿' => 'j', '𝓀' => 'k', '𝓁' => 'l', '𝓂' => 'm', '𝓃' => 'n', '𝓅' => 'p', '𝓆' => 'q', '𝓇' => 'r', '𝓈' => 's', '𝓉' => 't', '𝓊' => 'u', '𝓋' => 'v', '𝓌' => 'w', '𝓍' => 'x', '𝓎' => 'y', '𝓏' => 'z', '𝓐' => 'A', '𝓑' => 'B', '𝓒' => 'C', '𝓓' => 'D', '𝓔' => 'E', '𝓕' => 'F', '𝓖' => 'G', '𝓗' => 'H', '𝓘' => 'I', '𝓙' => 'J', '𝓚' => 'K', '𝓛' => 'L', '𝓜' => 'M', '𝓝' => 'N', '𝓞' => 'O', '𝓟' => 'P', '𝓠' => 'Q', '𝓡' => 'R', '𝓢' => 'S', '𝓣' => 'T', '𝓤' => 'U', '𝓥' => 'V', '𝓦' => 'W', '𝓧' => 'X', '𝓨' => 'Y', '𝓩' => 'Z', '𝓪' => 'a', '𝓫' => 'b', '𝓬' => 'c', '𝓭' => 'd', '𝓮' => 'e', '𝓯' => 'f', '𝓰' => 'g', '𝓱' => 'h', '𝓲' => 'i', '𝓳' => 'j', '𝓴' => 'k', '𝓵' => 'l', '𝓶' => 'm', '𝓷' => 'n', '𝓸' => 'o', '𝓹' => 'p', '𝓺' => 'q', '𝓻' => 'r', '𝓼' => 's', '𝓽' => 't', '𝓾' => 'u', '𝓿' => 'v', '𝔀' => 'w', '𝔁' => 'x', '𝔂' => 'y', '𝔃' => 'z', '𝔄' => 'A', '𝔅' => 'B', '𝔇' => 'D', '𝔈' => 'E', '𝔉' => 'F', '𝔊' => 'G', '𝔍' => 'J', '𝔎' => 'K', '𝔏' => 'L', '𝔐' => 'M', '𝔑' => 'N', '𝔒' => 'O', '𝔓' => 'P', '𝔔' => 'Q', '𝔖' => 'S', '𝔗' => 'T', '𝔘' => 'U', '𝔙' => 'V', '𝔚' => 'W', '𝔛' => 'X', '𝔜' => 'Y', '𝔞' => 'a', '𝔟' => 'b', '𝔠' => 'c', '𝔡' => 'd', '𝔢' => 'e', '𝔣' => 'f', '𝔤' => 'g', '𝔥' => 'h', '𝔦' => 'i', '𝔧' => 'j', '𝔨' => 'k', '𝔩' => 'l', '𝔪' => 'm', '𝔫' => 'n', '𝔬' => 'o', '𝔭' => 'p', '𝔮' => 'q', '𝔯' => 'r', '𝔰' => 's', '𝔱' => 't', '𝔲' => 'u', '𝔳' => 'v', '𝔴' => 'w', '𝔵' => 'x', '𝔶' => 'y', '𝔷' => 'z', '𝔸' => 'A', '𝔹' => 'B', '𝔻' => 'D', '𝔼' => 'E', '𝔽' => 'F', '𝔾' => 'G', '𝕀' => 'I', '𝕁' => 'J', '𝕂' => 'K', '𝕃' => 'L', '𝕄' => 'M', '𝕆' => 'O', '𝕊' => 'S', '𝕋' => 'T', '𝕌' => 'U', '𝕍' => 'V', '𝕎' => 'W', '𝕏' => 'X', '𝕐' => 'Y', '𝕒' => 'a', '𝕓' => 'b', '𝕔' => 'c', '𝕕' => 'd', '𝕖' => 'e', '𝕗' => 'f', '𝕘' => 'g', '𝕙' => 'h', '𝕚' => 'i', '𝕛' => 'j', '𝕜' => 'k', '𝕝' => 'l', '𝕞' => 'm', '𝕟' => 'n', '𝕠' => 'o', '𝕡' => 'p', '𝕢' => 'q', '𝕣' => 'r', '𝕤' => 's', '𝕥' => 't', '𝕦' => 'u', '𝕧' => 'v', '𝕨' => 'w', '𝕩' => 'x', '𝕪' => 'y', '𝕫' => 'z', '𝕬' => 'A', '𝕭' => 'B', '𝕮' => 'C', '𝕯' => 'D', '𝕰' => 'E', '𝕱' => 'F', '𝕲' => 'G', '𝕳' => 'H', '𝕴' => 'I', '𝕵' => 'J', '𝕶' => 'K', '𝕷' => 'L', '𝕸' => 'M', '𝕹' => 'N', '𝕺' => 'O', '𝕻' => 'P', '𝕼' => 'Q', '𝕽' => 'R', '𝕾' => 'S', '𝕿' => 'T', '𝖀' => 'U', '𝖁' => 'V', '𝖂' => 'W', '𝖃' => 'X', '𝖄' => 'Y', '𝖅' => 'Z', '𝖆' => 'a', '𝖇' => 'b', '𝖈' => 'c', '𝖉' => 'd', '𝖊' => 'e', '𝖋' => 'f', '𝖌' => 'g', '𝖍' => 'h', '𝖎' => 'i', '𝖏' => 'j', '𝖐' => 'k', '𝖑' => 'l', '𝖒' => 'm', '𝖓' => 'n', '𝖔' => 'o', '𝖕' => 'p', '𝖖' => 'q', '𝖗' => 'r', '𝖘' => 's', '𝖙' => 't', '𝖚' => 'u', '𝖛' => 'v', '𝖜' => 'w', '𝖝' => 'x', '𝖞' => 'y', '𝖟' => 'z', '𝖠' => 'A', '𝖡' => 'B', '𝖢' => 'C', '𝖣' => 'D', '𝖤' => 'E', '𝖥' => 'F', '𝖦' => 'G', '𝖧' => 'H', '𝖨' => 'I', '𝖩' => 'J', '𝖪' => 'K', '𝖫' => 'L', '𝖬' => 'M', '𝖭' => 'N', '𝖮' => 'O', '𝖯' => 'P', '𝖰' => 'Q', '𝖱' => 'R', '𝖲' => 'S', '𝖳' => 'T', '𝖴' => 'U', '𝖵' => 'V', '𝖶' => 'W', '𝖷' => 'X', '𝖸' => 'Y', '𝖹' => 'Z', '𝖺' => 'a', '𝖻' => 'b', '𝖼' => 'c', '𝖽' => 'd', '𝖾' => 'e', '𝖿' => 'f', '𝗀' => 'g', '𝗁' => 'h', '𝗂' => 'i', '𝗃' => 'j', '𝗄' => 'k', '𝗅' => 'l', '𝗆' => 'm', '𝗇' => 'n', '𝗈' => 'o', '𝗉' => 'p', '𝗊' => 'q', '𝗋' => 'r', '𝗌' => 's', '𝗍' => 't', '𝗎' => 'u', '𝗏' => 'v', '𝗐' => 'w', '𝗑' => 'x', '𝗒' => 'y', '𝗓' => 'z', '𝗔' => 'A', '𝗕' => 'B', '𝗖' => 'C', '𝗗' => 'D', '𝗘' => 'E', '𝗙' => 'F', '𝗚' => 'G', '𝗛' => 'H', '𝗜' => 'I', '𝗝' => 'J', '𝗞' => 'K', '𝗟' => 'L', '𝗠' => 'M', '𝗡' => 'N', '𝗢' => 'O', '𝗣' => 'P', '𝗤' => 'Q', '𝗥' => 'R', '𝗦' => 'S', '𝗧' => 'T', '𝗨' => 'U', '𝗩' => 'V', '𝗪' => 'W', '𝗫' => 'X', '𝗬' => 'Y', '𝗭' => 'Z', '𝗮' => 'a', '𝗯' => 'b', '𝗰' => 'c', '𝗱' => 'd', '𝗲' => 'e', '𝗳' => 'f', '𝗴' => 'g', '𝗵' => 'h', '𝗶' => 'i', '𝗷' => 'j', '𝗸' => 'k', '𝗹' => 'l', '𝗺' => 'm', '𝗻' => 'n', '𝗼' => 'o', '𝗽' => 'p', '𝗾' => 'q', '𝗿' => 'r', '𝘀' => 's', '𝘁' => 't', '𝘂' => 'u', '𝘃' => 'v', '𝘄' => 'w', '𝘅' => 'x', '𝘆' => 'y', '𝘇' => 'z', '𝘈' => 'A', '𝘉' => 'B', '𝘊' => 'C', '𝘋' => 'D', '𝘌' => 'E', '𝘍' => 'F', '𝘎' => 'G', '𝘏' => 'H', '𝘐' => 'I', '𝘑' => 'J', '𝘒' => 'K', '𝘓' => 'L', '𝘔' => 'M', '𝘕' => 'N', '𝘖' => 'O', '𝘗' => 'P', '𝘘' => 'Q', '𝘙' => 'R', '𝘚' => 'S', '𝘛' => 'T', '𝘜' => 'U', '𝘝' => 'V', '𝘞' => 'W', '𝘟' => 'X', '𝘠' => 'Y', '𝘡' => 'Z', '𝘢' => 'a', '𝘣' => 'b', '𝘤' => 'c', '𝘥' => 'd', '𝘦' => 'e', '𝘧' => 'f', '𝘨' => 'g', '𝘩' => 'h', '𝘪' => 'i', '𝘫' => 'j', '𝘬' => 'k', '𝘭' => 'l', '𝘮' => 'm', '𝘯' => 'n', '𝘰' => 'o', '𝘱' => 'p', '𝘲' => 'q', '𝘳' => 'r', '𝘴' => 's', '𝘵' => 't', '𝘶' => 'u', '𝘷' => 'v', '𝘸' => 'w', '𝘹' => 'x', '𝘺' => 'y', '𝘻' => 'z', '𝘼' => 'A', '𝘽' => 'B', '𝘾' => 'C', '𝘿' => 'D', '𝙀' => 'E', '𝙁' => 'F', '𝙂' => 'G', '𝙃' => 'H', '𝙄' => 'I', '𝙅' => 'J', '𝙆' => 'K', '𝙇' => 'L', '𝙈' => 'M', '𝙉' => 'N', '𝙊' => 'O', '𝙋' => 'P', '𝙌' => 'Q', '𝙍' => 'R', '𝙎' => 'S', '𝙏' => 'T', '𝙐' => 'U', '𝙑' => 'V', '𝙒' => 'W', '𝙓' => 'X', '𝙔' => 'Y', '𝙕' => 'Z', '𝙖' => 'a', '𝙗' => 'b', '𝙘' => 'c', '𝙙' => 'd', '𝙚' => 'e', '𝙛' => 'f', '𝙜' => 'g', '𝙝' => 'h', '𝙞' => 'i', '𝙟' => 'j', '𝙠' => 'k', '𝙡' => 'l', '𝙢' => 'm', '𝙣' => 'n', '𝙤' => 'o', '𝙥' => 'p', '𝙦' => 'q', '𝙧' => 'r', '𝙨' => 's', '𝙩' => 't', '𝙪' => 'u', '𝙫' => 'v', '𝙬' => 'w', '𝙭' => 'x', '𝙮' => 'y', '𝙯' => 'z', '𝙰' => 'A', '𝙱' => 'B', '𝙲' => 'C', '𝙳' => 'D', '𝙴' => 'E', '𝙵' => 'F', '𝙶' => 'G', '𝙷' => 'H', '𝙸' => 'I', '𝙹' => 'J', '𝙺' => 'K', '𝙻' => 'L', '𝙼' => 'M', '𝙽' => 'N', '𝙾' => 'O', '𝙿' => 'P', '𝚀' => 'Q', '𝚁' => 'R', '𝚂' => 'S', '𝚃' => 'T', '𝚄' => 'U', '𝚅' => 'V', '𝚆' => 'W', '𝚇' => 'X', '𝚈' => 'Y', '𝚉' => 'Z', '𝚊' => 'a', '𝚋' => 'b', '𝚌' => 'c', '𝚍' => 'd', '𝚎' => 'e', '𝚏' => 'f', '𝚐' => 'g', '𝚑' => 'h', '𝚒' => 'i', '𝚓' => 'j', '𝚔' => 'k', '𝚕' => 'l', '𝚖' => 'm', '𝚗' => 'n', '𝚘' => 'o', '𝚙' => 'p', '𝚚' => 'q', '𝚛' => 'r', '𝚜' => 's', '𝚝' => 't', '𝚞' => 'u', '𝚟' => 'v', '𝚠' => 'w', '𝚡' => 'x', '𝚢' => 'y', '𝚣' => 'z', '𝚤' => 'ı', '𝚥' => 'ȷ', '𝚨' => 'Α', '𝚩' => 'Β', '𝚪' => 'Γ', '𝚫' => 'Δ', '𝚬' => 'Ε', '𝚭' => 'Ζ', '𝚮' => 'Η', '𝚯' => 'Θ', '𝚰' => 'Ι', '𝚱' => 'Κ', '𝚲' => 'Λ', '𝚳' => 'Μ', '𝚴' => 'Ν', '𝚵' => 'Ξ', '𝚶' => 'Ο', '𝚷' => 'Π', '𝚸' => 'Ρ', '𝚹' => 'ϴ', '𝚺' => 'Σ', '𝚻' => 'Τ', '𝚼' => 'Υ', '𝚽' => 'Φ', '𝚾' => 'Χ', '𝚿' => 'Ψ', '𝛀' => 'Ω', '𝛁' => '∇', '𝛂' => 'α', '𝛃' => 'β', '𝛄' => 'γ', '𝛅' => 'δ', '𝛆' => 'ε', '𝛇' => 'ζ', '𝛈' => 'η', '𝛉' => 'θ', '𝛊' => 'ι', '𝛋' => 'κ', '𝛌' => 'λ', '𝛍' => 'μ', '𝛎' => 'ν', '𝛏' => 'ξ', '𝛐' => 'ο', '𝛑' => 'π', '𝛒' => 'ρ', '𝛓' => 'ς', '𝛔' => 'σ', '𝛕' => 'τ', '𝛖' => 'υ', '𝛗' => 'φ', '𝛘' => 'χ', '𝛙' => 'ψ', '𝛚' => 'ω', '𝛛' => '∂', '𝛜' => 'ϵ', '𝛝' => 'ϑ', '𝛞' => 'ϰ', '𝛟' => 'ϕ', '𝛠' => 'ϱ', '𝛡' => 'ϖ', '𝛢' => 'Α', '𝛣' => 'Β', '𝛤' => 'Γ', '𝛥' => 'Δ', '𝛦' => 'Ε', '𝛧' => 'Ζ', '𝛨' => 'Η', '𝛩' => 'Θ', '𝛪' => 'Ι', '𝛫' => 'Κ', '𝛬' => 'Λ', '𝛭' => 'Μ', '𝛮' => 'Ν', '𝛯' => 'Ξ', '𝛰' => 'Ο', '𝛱' => 'Π', '𝛲' => 'Ρ', '𝛳' => 'ϴ', '𝛴' => 'Σ', '𝛵' => 'Τ', '𝛶' => 'Υ', '𝛷' => 'Φ', '𝛸' => 'Χ', '𝛹' => 'Ψ', '𝛺' => 'Ω', '𝛻' => '∇', '𝛼' => 'α', '𝛽' => 'β', '𝛾' => 'γ', '𝛿' => 'δ', '𝜀' => 'ε', '𝜁' => 'ζ', '𝜂' => 'η', '𝜃' => 'θ', '𝜄' => 'ι', '𝜅' => 'κ', '𝜆' => 'λ', '𝜇' => 'μ', '𝜈' => 'ν', '𝜉' => 'ξ', '𝜊' => 'ο', '𝜋' => 'π', '𝜌' => 'ρ', '𝜍' => 'ς', '𝜎' => 'σ', '𝜏' => 'τ', '𝜐' => 'υ', '𝜑' => 'φ', '𝜒' => 'χ', '𝜓' => 'ψ', '𝜔' => 'ω', '𝜕' => '∂', '𝜖' => 'ϵ', '𝜗' => 'ϑ', '𝜘' => 'ϰ', '𝜙' => 'ϕ', '𝜚' => 'ϱ', '𝜛' => 'ϖ', '𝜜' => 'Α', '𝜝' => 'Β', '𝜞' => 'Γ', '𝜟' => 'Δ', '𝜠' => 'Ε', '𝜡' => 'Ζ', '𝜢' => 'Η', '𝜣' => 'Θ', '𝜤' => 'Ι', '𝜥' => 'Κ', '𝜦' => 'Λ', '𝜧' => 'Μ', '𝜨' => 'Ν', '𝜩' => 'Ξ', '𝜪' => 'Ο', '𝜫' => 'Π', '𝜬' => 'Ρ', '𝜭' => 'ϴ', '𝜮' => 'Σ', '𝜯' => 'Τ', '𝜰' => 'Υ', '𝜱' => 'Φ', '𝜲' => 'Χ', '𝜳' => 'Ψ', '𝜴' => 'Ω', '𝜵' => '∇', '𝜶' => 'α', '𝜷' => 'β', '𝜸' => 'γ', '𝜹' => 'δ', '𝜺' => 'ε', '𝜻' => 'ζ', '𝜼' => 'η', '𝜽' => 'θ', '𝜾' => 'ι', '𝜿' => 'κ', '𝝀' => 'λ', '𝝁' => 'μ', '𝝂' => 'ν', '𝝃' => 'ξ', '𝝄' => 'ο', '𝝅' => 'π', '𝝆' => 'ρ', '𝝇' => 'ς', '𝝈' => 'σ', '𝝉' => 'τ', '𝝊' => 'υ', '𝝋' => 'φ', '𝝌' => 'χ', '𝝍' => 'ψ', '𝝎' => 'ω', '𝝏' => '∂', '𝝐' => 'ϵ', '𝝑' => 'ϑ', '𝝒' => 'ϰ', '𝝓' => 'ϕ', '𝝔' => 'ϱ', '𝝕' => 'ϖ', '𝝖' => 'Α', '𝝗' => 'Β', '𝝘' => 'Γ', '𝝙' => 'Δ', '𝝚' => 'Ε', '𝝛' => 'Ζ', '𝝜' => 'Η', '𝝝' => 'Θ', '𝝞' => 'Ι', '𝝟' => 'Κ', '𝝠' => 'Λ', '𝝡' => 'Μ', '𝝢' => 'Ν', '𝝣' => 'Ξ', '𝝤' => 'Ο', '𝝥' => 'Π', '𝝦' => 'Ρ', '𝝧' => 'ϴ', '𝝨' => 'Σ', '𝝩' => 'Τ', '𝝪' => 'Υ', '𝝫' => 'Φ', '𝝬' => 'Χ', '𝝭' => 'Ψ', '𝝮' => 'Ω', '𝝯' => '∇', '𝝰' => 'α', '𝝱' => 'β', '𝝲' => 'γ', '𝝳' => 'δ', '𝝴' => 'ε', '𝝵' => 'ζ', '𝝶' => 'η', '𝝷' => 'θ', '𝝸' => 'ι', '𝝹' => 'κ', '𝝺' => 'λ', '𝝻' => 'μ', '𝝼' => 'ν', '𝝽' => 'ξ', '𝝾' => 'ο', '𝝿' => 'π', '𝞀' => 'ρ', '𝞁' => 'ς', '𝞂' => 'σ', '𝞃' => 'τ', '𝞄' => 'υ', '𝞅' => 'φ', '𝞆' => 'χ', '𝞇' => 'ψ', '𝞈' => 'ω', '𝞉' => '∂', '𝞊' => 'ϵ', '𝞋' => 'ϑ', '𝞌' => 'ϰ', '𝞍' => 'ϕ', '𝞎' => 'ϱ', '𝞏' => 'ϖ', '𝞐' => 'Α', '𝞑' => 'Β', '𝞒' => 'Γ', '𝞓' => 'Δ', '𝞔' => 'Ε', '𝞕' => 'Ζ', '𝞖' => 'Η', '𝞗' => 'Θ', '𝞘' => 'Ι', '𝞙' => 'Κ', '𝞚' => 'Λ', '𝞛' => 'Μ', '𝞜' => 'Ν', '𝞝' => 'Ξ', '𝞞' => 'Ο', '𝞟' => 'Π', '𝞠' => 'Ρ', '𝞡' => 'ϴ', '𝞢' => 'Σ', '𝞣' => 'Τ', '𝞤' => 'Υ', '𝞥' => 'Φ', '𝞦' => 'Χ', '𝞧' => 'Ψ', '𝞨' => 'Ω', '𝞩' => '∇', '𝞪' => 'α', '𝞫' => 'β', '𝞬' => 'γ', '𝞭' => 'δ', '𝞮' => 'ε', '𝞯' => 'ζ', '𝞰' => 'η', '𝞱' => 'θ', '𝞲' => 'ι', '𝞳' => 'κ', '𝞴' => 'λ', '𝞵' => 'μ', '𝞶' => 'ν', '𝞷' => 'ξ', '𝞸' => 'ο', '𝞹' => 'π', '𝞺' => 'ρ', '𝞻' => 'ς', '𝞼' => 'σ', '𝞽' => 'τ', '𝞾' => 'υ', '𝞿' => 'φ', '𝟀' => 'χ', '𝟁' => 'ψ', '𝟂' => 'ω', '𝟃' => '∂', '𝟄' => 'ϵ', '𝟅' => 'ϑ', '𝟆' => 'ϰ', '𝟇' => 'ϕ', '𝟈' => 'ϱ', '𝟉' => 'ϖ', '𝟊' => 'Ϝ', '𝟋' => 'ϝ', '𝟎' => '0', '𝟏' => '1', '𝟐' => '2', '𝟑' => '3', '𝟒' => '4', '𝟓' => '5', '𝟔' => '6', '𝟕' => '7', '𝟖' => '8', '𝟗' => '9', '𝟘' => '0', '𝟙' => '1', '𝟚' => '2', '𝟛' => '3', '𝟜' => '4', '𝟝' => '5', '𝟞' => '6', '𝟟' => '7', '𝟠' => '8', '𝟡' => '9', '𝟢' => '0', '𝟣' => '1', '𝟤' => '2', '𝟥' => '3', '𝟦' => '4', '𝟧' => '5', '𝟨' => '6', '𝟩' => '7', '𝟪' => '8', '𝟫' => '9', '𝟬' => '0', '𝟭' => '1', '𝟮' => '2', '𝟯' => '3', '𝟰' => '4', '𝟱' => '5', '𝟲' => '6', '𝟳' => '7', '𝟴' => '8', '𝟵' => '9', '𝟶' => '0', '𝟷' => '1', '𝟸' => '2', '𝟹' => '3', '𝟺' => '4', '𝟻' => '5', '𝟼' => '6', '𝟽' => '7', '𝟾' => '8', '𝟿' => '9', '𞀰' => 'а', '𞀱' => 'б', '𞀲' => 'в', '𞀳' => 'г', '𞀴' => 'д', '𞀵' => 'е', '𞀶' => 'ж', '𞀷' => 'з', '𞀸' => 'и', '𞀹' => 'к', '𞀺' => 'л', '𞀻' => 'м', '𞀼' => 'о', '𞀽' => 'п', '𞀾' => 'р', '𞀿' => 'с', '𞁀' => 'т', '𞁁' => 'у', '𞁂' => 'ф', '𞁃' => 'х', '𞁄' => 'ц', '𞁅' => 'ч', '𞁆' => 'ш', '𞁇' => 'ы', '𞁈' => 'э', '𞁉' => 'ю', '𞁊' => 'ꚉ', '𞁋' => 'ә', '𞁌' => 'і', '𞁍' => 'ј', '𞁎' => 'ө', '𞁏' => 'ү', '𞁐' => 'ӏ', '𞁑' => 'а', '𞁒' => 'б', '𞁓' => 'в', '𞁔' => 'г', '𞁕' => 'д', '𞁖' => 'е', '𞁗' => 'ж', '𞁘' => 'з', '𞁙' => 'и', '𞁚' => 'к', '𞁛' => 'л', '𞁜' => 'о', '𞁝' => 'п', '𞁞' => 'с', '𞁟' => 'у', '𞁠' => 'ф', '𞁡' => 'х', '𞁢' => 'ц', '𞁣' => 'ч', '𞁤' => 'ш', '𞁥' => 'ъ', '𞁦' => 'ы', '𞁧' => 'ґ', '𞁨' => 'і', '𞁩' => 'ѕ', '𞁪' => 'џ', '𞁫' => 'ҫ', '𞁬' => 'ꙑ', '𞁭' => 'ұ', '𞸀' => 'ا', '𞸁' => 'ب', '𞸂' => 'ج', '𞸃' => 'د', '𞸅' => 'و', '𞸆' => 'ز', '𞸇' => 'ح', '𞸈' => 'ط', '𞸉' => 'ي', '𞸊' => 'ك', '𞸋' => 'ل', '𞸌' => 'م', '𞸍' => 'ن', '𞸎' => 'س', '𞸏' => 'ع', '𞸐' => 'ف', '𞸑' => 'ص', '𞸒' => 'ق', '𞸓' => 'ر', '𞸔' => 'ش', '𞸕' => 'ت', '𞸖' => 'ث', '𞸗' => 'خ', '𞸘' => 'ذ', '𞸙' => 'ض', '𞸚' => 'ظ', '𞸛' => 'غ', '𞸜' => 'ٮ', '𞸝' => 'ں', '𞸞' => 'ڡ', '𞸟' => 'ٯ', '𞸡' => 'ب', '𞸢' => 'ج', '𞸤' => 'ه', '𞸧' => 'ح', '𞸩' => 'ي', '𞸪' => 'ك', '𞸫' => 'ل', '𞸬' => 'م', '𞸭' => 'ن', '𞸮' => 'س', '𞸯' => 'ع', '𞸰' => 'ف', '𞸱' => 'ص', '𞸲' => 'ق', '𞸴' => 'ش', '𞸵' => 'ت', '𞸶' => 'ث', '𞸷' => 'خ', '𞸹' => 'ض', '𞸻' => 'غ', '𞹂' => 'ج', '𞹇' => 'ح', '𞹉' => 'ي', '𞹋' => 'ل', '𞹍' => 'ن', '𞹎' => 'س', '𞹏' => 'ع', '𞹑' => 'ص', '𞹒' => 'ق', '𞹔' => 'ش', '𞹗' => 'خ', '𞹙' => 'ض', '𞹛' => 'غ', '𞹝' => 'ں', '𞹟' => 'ٯ', '𞹡' => 'ب', '𞹢' => 'ج', '𞹤' => 'ه', '𞹧' => 'ح', '𞹨' => 'ط', '𞹩' => 'ي', '𞹪' => 'ك', '𞹬' => 'م', '𞹭' => 'ن', '𞹮' => 'س', '𞹯' => 'ع', '𞹰' => 'ف', '𞹱' => 'ص', '𞹲' => 'ق', '𞹴' => 'ش', '𞹵' => 'ت', '𞹶' => 'ث', '𞹷' => 'خ', '𞹹' => 'ض', '𞹺' => 'ظ', '𞹻' => 'غ', '𞹼' => 'ٮ', '𞹾' => 'ڡ', '𞺀' => 'ا', '𞺁' => 'ب', '𞺂' => 'ج', '𞺃' => 'د', '𞺄' => 'ه', '𞺅' => 'و', '𞺆' => 'ز', '𞺇' => 'ح', '𞺈' => 'ط', '𞺉' => 'ي', '𞺋' => 'ل', '𞺌' => 'م', '𞺍' => 'ن', '𞺎' => 'س', '𞺏' => 'ع', '𞺐' => 'ف', '𞺑' => 'ص', '𞺒' => 'ق', '𞺓' => 'ر', '𞺔' => 'ش', '𞺕' => 'ت', '𞺖' => 'ث', '𞺗' => 'خ', '𞺘' => 'ذ', '𞺙' => 'ض', '𞺚' => 'ظ', '𞺛' => 'غ', '𞺡' => 'ب', '𞺢' => 'ج', '𞺣' => 'د', '𞺥' => 'و', '𞺦' => 'ز', '𞺧' => 'ح', '𞺨' => 'ط', '𞺩' => 'ي', '𞺫' => 'ل', '𞺬' => 'م', '𞺭' => 'ن', '𞺮' => 'س', '𞺯' => 'ع', '𞺰' => 'ف', '𞺱' => 'ص', '𞺲' => 'ق', '𞺳' => 'ر', '𞺴' => 'ش', '𞺵' => 'ت', '𞺶' => 'ث', '𞺷' => 'خ', '𞺸' => 'ذ', '𞺹' => 'ض', '𞺺' => 'ظ', '𞺻' => 'غ', '🄀' => '0.', '🄁' => '0,', '🄂' => '1,', '🄃' => '2,', '🄄' => '3,', '🄅' => '4,', '🄆' => '5,', '🄇' => '6,', '🄈' => '7,', '🄉' => '8,', '🄊' => '9,', '🄐' => '(A)', '🄑' => '(B)', '🄒' => '(C)', '🄓' => '(D)', '🄔' => '(E)', '🄕' => '(F)', '🄖' => '(G)', '🄗' => '(H)', '🄘' => '(I)', '🄙' => '(J)', '🄚' => '(K)', '🄛' => '(L)', '🄜' => '(M)', '🄝' => '(N)', '🄞' => '(O)', '🄟' => '(P)', '🄠' => '(Q)', '🄡' => '(R)', '🄢' => '(S)', '🄣' => '(T)', '🄤' => '(U)', '🄥' => '(V)', '🄦' => '(W)', '🄧' => '(X)', '🄨' => '(Y)', '🄩' => '(Z)', '🄪' => '〔S〕', '🄫' => 'C', '🄬' => 'R', '🄭' => 'CD', '🄮' => 'WZ', '🄰' => 'A', '🄱' => 'B', '🄲' => 'C', '🄳' => 'D', '🄴' => 'E', '🄵' => 'F', '🄶' => 'G', '🄷' => 'H', '🄸' => 'I', '🄹' => 'J', '🄺' => 'K', '🄻' => 'L', '🄼' => 'M', '🄽' => 'N', '🄾' => 'O', '🄿' => 'P', '🅀' => 'Q', '🅁' => 'R', '🅂' => 'S', '🅃' => 'T', '🅄' => 'U', '🅅' => 'V', '🅆' => 'W', '🅇' => 'X', '🅈' => 'Y', '🅉' => 'Z', '🅊' => 'HV', '🅋' => 'MV', '🅌' => 'SD', '🅍' => 'SS', '🅎' => 'PPV', '🅏' => 'WC', '🅪' => 'MC', '🅫' => 'MD', '🅬' => 'MR', '🆐' => 'DJ', '🈀' => 'ほか', '🈁' => 'ココ', '🈂' => 'サ', '🈐' => '手', '🈑' => '字', '🈒' => '双', '🈓' => 'デ', '🈔' => '二', '🈕' => '多', '🈖' => '解', '🈗' => '天', '🈘' => '交', '🈙' => '映', '🈚' => '無', '🈛' => '料', '🈜' => '前', '🈝' => '後', '🈞' => '再', '🈟' => '新', '🈠' => '初', '🈡' => '終', '🈢' => '生', '🈣' => '販', '🈤' => '声', '🈥' => '吹', '🈦' => '演', '🈧' => '投', '🈨' => '捕', '🈩' => '一', '🈪' => '三', '🈫' => '遊', '🈬' => '左', '🈭' => '中', '🈮' => '右', '🈯' => '指', '🈰' => '走', '🈱' => '打', '🈲' => '禁', '🈳' => '空', '🈴' => '合', '🈵' => '満', '🈶' => '有', '🈷' => '月', '🈸' => '申', '🈹' => '割', '🈺' => '営', '🈻' => '配', '🉀' => '〔本〕', '🉁' => '〔三〕', '🉂' => '〔二〕', '🉃' => '〔安〕', '🉄' => '〔点〕', '🉅' => '〔打〕', '🉆' => '〔盗〕', '🉇' => '〔勝〕', '🉈' => '〔敗〕', '🉐' => '得', '🉑' => '可', '🯰' => '0', '🯱' => '1', '🯲' => '2', '🯳' => '3', '🯴' => '4', '🯵' => '5', '🯶' => '6', '🯷' => '7', '🯸' => '8', '🯹' => '9', ); = 80000) { return require __DIR__.'/bootstrap80.php'; } if (!function_exists('normalizer_is_normalized')) { function normalizer_is_normalized($string, $form = p\Normalizer::FORM_C) { return p\Normalizer::isNormalized($string, $form); } } if (!function_exists('normalizer_normalize')) { function normalizer_normalize($string, $form = p\Normalizer::FORM_C) { return p\Normalizer::normalize($string, $form); } } if (!function_exists('normalizer_get_raw_decomposition')) { function normalizer_get_raw_decomposition(?string $string, ?int $form = p\Normalizer::FORM_C) { return p\Normalizer::getRawDecomposition((string) $string, (int) $form); } } = 70400) { $s = html_entity_decode($s, \ENT_QUOTES, 'UTF-8'); if (false !== strpos($s, '&#')) { $s = preg_replace_callback('/&#(?:0*([0-9]++)|[xX]0*([0-9a-fA-F]++));/', $decodeControlChars, $s); } } else { $s = preg_replace_callback('/&#(?:0*([0-9]++)|[xX]0*([0-9a-fA-F]++));/', $decodeControlChars, $s); $s = implode("\0", array_map(static function ($chunk) { return html_entity_decode($chunk, \ENT_QUOTES, 'UTF-8'); }, explode("\0", $s))); } $fromEncoding = 'UTF-8'; } if ($fromEncoding === $toEncoding) { return $s; } return self::iconv($fromEncoding, $toEncoding, $s); } public static function mb_convert_variables($toEncoding, $fromEncoding, &...$vars) { $ok = true; array_walk_recursive($vars, static function (&$v) use (&$ok, $toEncoding, $fromEncoding) { if (false === $v = self::mb_convert_encoding($v, $toEncoding, $fromEncoding)) { $ok = false; } }); return $ok ? $fromEncoding : false; } public static function mb_decode_mimeheader($s) { return iconv_mime_decode($s, 2, self::$internalEncoding); } public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null) { trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', \E_USER_WARNING); } public static function mb_decode_numericentity($s, $convmap, $encoding = null) { if (null !== $s && !\is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) { trigger_error('mb_decode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING); return null; } if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) { return false; } if (null !== $encoding && !\is_scalar($encoding)) { trigger_error('mb_decode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING); return ''; } $s = (string) $s; if ('' === $s) { return ''; } $encoding = self::getEncoding($encoding); if ('UTF-8' === $encoding) { $encoding = null; if (!preg_match('//u', $s)) { $s = @self::iconv('UTF-8', 'UTF-8', $s); } } else { $s = self::iconv($encoding, 'UTF-8', $s); } $cnt = floor(\count($convmap) / 4) * 4; for ($i = 0; $i < $cnt; $i += 4) { $convmap[$i] += $convmap[$i + 2]; $convmap[$i + 1] += $convmap[$i + 2]; } $s = preg_replace_callback('/&#(?:0*([0-9]+)|x0*([0-9a-fA-F]+))'.(\PHP_VERSION_ID >= 80200 ? '' : '(?!&)').';?/', static function (array $m) use ($cnt, $convmap) { $c = isset($m[2]) ? (int) hexdec($m[2]) : $m[1]; for ($i = 0; $i < $cnt; $i += 4) { if ($c >= $convmap[$i] && $c <= $convmap[$i + 1]) { return self::mb_chr($c - $convmap[$i + 2]); } } return $m[0]; }, $s); if (null === $encoding) { return $s; } return self::iconv('UTF-8', $encoding, $s); } public static function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = false) { if (null !== $s && !\is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) { trigger_error('mb_encode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING); return null; } if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) { return false; } if (null !== $encoding && !\is_scalar($encoding)) { trigger_error('mb_encode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING); return null; } if (null !== $is_hex && !\is_scalar($is_hex)) { trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, '.\gettype($s).' given', \E_USER_WARNING); return null; } $s = (string) $s; if ('' === $s) { return ''; } $encoding = self::getEncoding($encoding); if ('UTF-8' === $encoding) { $encoding = null; if (!preg_match('//u', $s)) { $s = @self::iconv('UTF-8', 'UTF-8', $s); } } else { $s = self::iconv($encoding, 'UTF-8', $s); } static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; $cnt = floor(\count($convmap) / 4) * 4; $i = 0; $len = \strlen($s); $result = ''; while ($i < $len) { $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; $uchr = substr($s, $i, $ulen); $i += $ulen; $c = self::mb_ord($uchr); for ($j = 0; $j < $cnt; $j += 4) { if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) { $cOffset = ($c + $convmap[$j + 2]) & $convmap[$j + 3]; $result .= $is_hex ? \sprintf('&#x%X;', $cOffset) : '&#'.$cOffset.';'; continue 2; } } $result .= $uchr; } if (null === $encoding) { return $result; } return self::iconv('UTF-8', $encoding, $result); } public static function mb_convert_case($s, $mode, $encoding = null) { $s = (string) $s; if ('' === $s) { return ''; } $encoding = self::getEncoding($encoding); if ('UTF-8' === $encoding) { $encoding = null; if (!preg_match('//u', $s)) { $s = @self::iconv('UTF-8', 'UTF-8', $s); } } else { $s = self::iconv($encoding, 'UTF-8', $s); } if (\MB_CASE_TITLE == $mode) { static $titleRegexp = null; if (null === $titleRegexp) { $titleRegexp = self::getData('titleCaseRegexp'); } $s = preg_replace_callback($titleRegexp, [__CLASS__, 'title_case'], $s); } else { if (\MB_CASE_UPPER == $mode) { static $upper = null; if (null === $upper) { $upper = self::getData('upperCase'); } $map = $upper; } else { if (self::MB_CASE_FOLD === $mode) { static $caseFolding = null; if (null === $caseFolding) { $caseFolding = self::getData('caseFolding'); } $s = strtr($s, $caseFolding); } static $lower = null; if (null === $lower) { $lower = self::getData('lowerCase'); } $map = $lower; } static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; $i = 0; $len = \strlen($s); while ($i < $len) { $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; $uchr = substr($s, $i, $ulen); $i += $ulen; if (isset($map[$uchr])) { $uchr = $map[$uchr]; $nlen = \strlen($uchr); if ($nlen == $ulen) { $nlen = $i; do { $s[--$nlen] = $uchr[--$ulen]; } while ($ulen); } else { $s = substr_replace($s, $uchr, $i - $ulen, $ulen); $len += $nlen - $ulen; $i += $nlen - $ulen; } } } } if (null === $encoding) { return $s; } return self::iconv('UTF-8', $encoding, $s); } public static function mb_internal_encoding($encoding = null) { if (null === $encoding) { return self::$internalEncoding; } $normalizedEncoding = self::getEncoding($encoding); if ('UTF-8' === $normalizedEncoding || false !== @iconv($normalizedEncoding, $normalizedEncoding, ' ')) { self::$internalEncoding = $normalizedEncoding; return true; } if (80000 > \PHP_VERSION_ID) { return false; } throw new \ValueError(\sprintf('Argument #1 ($encoding) must be a valid encoding, "%s" given', $encoding)); } public static function mb_language($lang = null) { if (null === $lang) { return self::$language; } switch ($normalizedLang = strtolower($lang)) { case 'uni': case 'neutral': self::$language = $normalizedLang; return true; } if (80000 > \PHP_VERSION_ID) { return false; } throw new \ValueError(\sprintf('Argument #1 ($language) must be a valid language, "%s" given', $lang)); } public static function mb_list_encodings() { return ['UTF-8']; } public static function mb_encoding_aliases($encoding) { switch (strtoupper($encoding)) { case 'UTF8': case 'UTF-8': return ['utf8']; } return false; } public static function mb_check_encoding($var = null, $encoding = null) { if (null === $encoding) { if (null === $var) { return false; } $encoding = self::$internalEncoding; } if (!\is_array($var)) { return self::mb_detect_encoding($var, [$encoding]) || false !== @iconv($encoding, $encoding, $var); } foreach ($var as $key => $value) { if (!self::mb_check_encoding($key, $encoding)) { return false; } if (!self::mb_check_encoding($value, $encoding)) { return false; } } return true; } public static function mb_detect_encoding($str, $encodingList = null, $strict = false) { if (null === $encodingList) { $encodingList = self::$encodingList; } else { if (!\is_array($encodingList)) { $encodingList = array_map('trim', explode(',', $encodingList)); } $encodingList = array_map('strtoupper', $encodingList); } foreach ($encodingList as $enc) { switch ($enc) { case 'ASCII': if (!preg_match('/[\x80-\xFF]/', $str)) { return $enc; } break; case 'UTF8': case 'UTF-8': if (preg_match('//u', $str)) { return 'UTF-8'; } break; default: if (0 === strncmp($enc, 'ISO-8859-', 9)) { return $enc; } } } return false; } public static function mb_detect_order($encodingList = null) { if (null === $encodingList) { return self::$encodingList; } if (!\is_array($encodingList)) { $encodingList = array_map('trim', explode(',', $encodingList)); } $encodingList = array_map('strtoupper', $encodingList); foreach ($encodingList as $enc) { switch ($enc) { default: if (strncmp($enc, 'ISO-8859-', 9)) { return false; } case 'ASCII': case 'UTF8': case 'UTF-8': } } self::$encodingList = $encodingList; return true; } public static function mb_strlen($s, $encoding = null) { $encoding = self::getEncoding($encoding); if ('CP850' === $encoding || 'ASCII' === $encoding) { return \strlen($s); } if (false !== $len = @iconv_strlen($s, $encoding)) { return $len; } if ('UTF-8' !== $encoding) { return $len; } return preg_match_all('/[\x00-\x7F]|[\xC0-\xDF][\x80-\xBF]?|[\xE0-\xEF][\x80-\xBF]{0,2}|[\xF0-\xF7][\x80-\xBF]{0,3}|[\xF8-\xFB][\x80-\xBF]{0,4}|[\xFC-\xFD][\x80-\xBF]{0,5}|[\x80-\xBF\xFE\xFF]/s', $s); } public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) { $encoding = self::getEncoding($encoding); if ('CP850' === $encoding || 'ASCII' === $encoding) { return strpos($haystack, $needle, $offset); } $needle = (string) $needle; if ('' === $needle) { if (80000 > \PHP_VERSION_ID) { trigger_error(__METHOD__.': Empty delimiter', \E_USER_WARNING); return false; } return 0; } return iconv_strpos($haystack, $needle, $offset, $encoding); } public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) { $encoding = self::getEncoding($encoding); if ('CP850' === $encoding || 'ASCII' === $encoding) { return strrpos($haystack, $needle, $offset); } if ($offset != (int) $offset) { $offset = 0; } elseif ($offset = (int) $offset) { if ($offset < 0) { if (0 > $offset += self::mb_strlen($needle)) { $haystack = self::mb_substr($haystack, 0, $offset, $encoding); } $offset = 0; } else { $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding); } } $pos = '' !== $needle || 80000 > \PHP_VERSION_ID ? iconv_strrpos($haystack, $needle, $encoding) : self::mb_strlen($haystack, $encoding); return false !== $pos ? $offset + $pos : false; } public static function mb_str_split($string, $split_length = 1, $encoding = null) { if (null !== $string && !\is_scalar($string) && !(\is_object($string) && method_exists($string, '__toString'))) { trigger_error('mb_str_split() expects parameter 1 to be string, '.\gettype($string).' given', \E_USER_WARNING); return null; } if (1 > $split_length = (int) $split_length) { if (80000 > \PHP_VERSION_ID) { trigger_error('The length of each segment must be greater than zero', \E_USER_WARNING); return false; } throw new \ValueError('Argument #2 ($length) must be greater than 0'); } if (null === $encoding) { $encoding = mb_internal_encoding(); } if ('UTF-8' === $encoding = self::getEncoding($encoding)) { $rx = '/('; while (65535 < $split_length) { $rx .= '.{65535}'; $split_length -= 65535; } $rx .= '.{'.$split_length.'})/us'; return preg_split($rx, $string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY); } $result = []; $length = mb_strlen($string, $encoding); for ($i = 0; $i < $length; $i += $split_length) { $result[] = mb_substr($string, $i, $split_length, $encoding); } return $result; } public static function mb_strtolower($s, $encoding = null) { return self::mb_convert_case($s, \MB_CASE_LOWER, $encoding); } public static function mb_strtoupper($s, $encoding = null) { return self::mb_convert_case($s, \MB_CASE_UPPER, $encoding); } public static function mb_substitute_character($c = null) { if (null === $c) { return 'none'; } if (0 === strcasecmp($c, 'none')) { return true; } if (80000 > \PHP_VERSION_ID) { return false; } if (\is_int($c) || 'long' === $c || 'entity' === $c) { return false; } throw new \ValueError('Argument #1 ($substitute_character) must be "none", "long", "entity" or a valid codepoint'); } public static function mb_substr($s, $start, $length = null, $encoding = null) { $encoding = self::getEncoding($encoding); if ('CP850' === $encoding || 'ASCII' === $encoding) { return (string) substr($s, $start, null === $length ? 2147483647 : $length); } if ($start < 0) { $start = iconv_strlen($s, $encoding) + $start; if ($start < 0) { $start = 0; } } if (null === $length) { $length = 2147483647; } elseif ($length < 0) { $length = iconv_strlen($s, $encoding) + $length - $start; if ($length < 0) { return ''; } } return (string) iconv_substr($s, $start, $length, $encoding); } public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) { [$haystack, $needle] = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], [ self::mb_convert_case($haystack, \MB_CASE_LOWER, $encoding), self::mb_convert_case($needle, \MB_CASE_LOWER, $encoding), ]); return self::mb_strpos($haystack, $needle, $offset, $encoding); } public static function mb_stristr($haystack, $needle, $part = false, $encoding = null) { $pos = self::mb_stripos($haystack, $needle, 0, $encoding); return self::getSubpart($pos, $part, $haystack, $encoding); } public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null) { $encoding = self::getEncoding($encoding); if ('CP850' === $encoding || 'ASCII' === $encoding) { $pos = strrpos($haystack, $needle); } else { $needle = self::mb_substr($needle, 0, 1, $encoding); $pos = iconv_strrpos($haystack, $needle, $encoding); } return self::getSubpart($pos, $part, $haystack, $encoding); } public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null) { $needle = self::mb_substr($needle, 0, 1, $encoding); $pos = self::mb_strripos($haystack, $needle, $encoding); return self::getSubpart($pos, $part, $haystack, $encoding); } public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) { $haystack = self::mb_convert_case($haystack, \MB_CASE_LOWER, $encoding); $needle = self::mb_convert_case($needle, \MB_CASE_LOWER, $encoding); $haystack = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], $haystack); $needle = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], $needle); return self::mb_strrpos($haystack, $needle, $offset, $encoding); } public static function mb_strstr($haystack, $needle, $part = false, $encoding = null) { $pos = strpos($haystack, $needle); if (false === $pos) { return false; } if ($part) { return substr($haystack, 0, $pos); } return substr($haystack, $pos); } public static function mb_get_info($type = 'all') { $info = [ 'internal_encoding' => self::$internalEncoding, 'http_output' => 'pass', 'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)', 'func_overload' => 0, 'func_overload_list' => 'no overload', 'mail_charset' => 'UTF-8', 'mail_header_encoding' => 'BASE64', 'mail_body_encoding' => 'BASE64', 'illegal_chars' => 0, 'encoding_translation' => 'Off', 'language' => self::$language, 'detect_order' => self::$encodingList, 'substitute_character' => 'none', 'strict_detection' => 'Off', ]; if ('all' === $type) { return $info; } if (isset($info[$type])) { return $info[$type]; } return false; } public static function mb_http_input($type = '') { return false; } public static function mb_http_output($encoding = null) { return null !== $encoding ? 'pass' === $encoding : 'pass'; } public static function mb_strwidth($s, $encoding = null) { $encoding = self::getEncoding($encoding); if ('UTF-8' !== $encoding) { $s = self::iconv($encoding, 'UTF-8', $s); } $s = preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide); return ($wide << 1) + iconv_strlen($s, 'UTF-8'); } public static function mb_substr_count($haystack, $needle, $encoding = null) { return substr_count($haystack, $needle); } public static function mb_output_handler($contents, $status) { return $contents; } public static function mb_chr($code, $encoding = null) { if (0x80 > $code %= 0x200000) { $s = \chr($code); } elseif (0x800 > $code) { $s = \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F); } elseif (0x10000 > $code) { $s = \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); } else { $s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); } if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { $s = mb_convert_encoding($s, $encoding, 'UTF-8'); } return $s; } public static function mb_ord($s, $encoding = null) { if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { $s = mb_convert_encoding($s, 'UTF-8', $encoding); } if (1 === \strlen($s)) { return \ord($s); } $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0; if (0xF0 <= $code) { return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80; } if (0xE0 <= $code) { return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80; } if (0xC0 <= $code) { return (($code - 0xC0) << 6) + $s[2] - 0x80; } return $code; } public static function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = \STR_PAD_RIGHT, ?string $encoding = null) { if (!\in_array($pad_type, [\STR_PAD_RIGHT, \STR_PAD_LEFT, \STR_PAD_BOTH], true)) { if (\PHP_VERSION_ID < 80000) { trigger_error('mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH', \E_USER_WARNING); return false; } throw new \ValueError('mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH'); } if (null === $encoding) { $encoding = self::mb_internal_encoding(); } elseif (!self::assertEncoding($encoding, 'mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given')) { return false; } if (self::mb_strlen($pad_string, $encoding) <= 0) { if (\PHP_VERSION_ID < 80000) { trigger_error('mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string', \E_USER_WARNING); return false; } throw new \ValueError('mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string'); } $paddingRequired = $length - self::mb_strlen($string, $encoding); if ($paddingRequired < 1) { return $string; } switch ($pad_type) { case \STR_PAD_LEFT: return self::mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding).$string; case \STR_PAD_RIGHT: return $string.self::mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding); default: $leftPaddingLength = floor($paddingRequired / 2); $rightPaddingLength = $paddingRequired - $leftPaddingLength; return self::mb_substr(str_repeat($pad_string, $leftPaddingLength), 0, $leftPaddingLength, $encoding).$string.self::mb_substr(str_repeat($pad_string, $rightPaddingLength), 0, $rightPaddingLength, $encoding); } } public static function mb_ucfirst(string $string, ?string $encoding = null) { if (null === $encoding) { $encoding = self::mb_internal_encoding(); } elseif (!self::assertEncoding($encoding, 'mb_ucfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given')) { return false; } $firstChar = mb_substr($string, 0, 1, $encoding); $firstChar = mb_convert_case($firstChar, \MB_CASE_TITLE, $encoding); return $firstChar.mb_substr($string, 1, null, $encoding); } public static function mb_lcfirst(string $string, ?string $encoding = null) { if (null === $encoding) { $encoding = self::mb_internal_encoding(); } elseif (!self::assertEncoding($encoding, 'mb_lcfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given')) { return false; } $firstChar = mb_substr($string, 0, 1, $encoding); $firstChar = mb_convert_case($firstChar, \MB_CASE_LOWER, $encoding); return $firstChar.mb_substr($string, 1, null, $encoding); } public static function mb_trim(string $string, ?string $characters = null, ?string $encoding = null) { return self::mb_internal_trim('{^[%s]+|[%1$s]+$}Du', $string, $characters, $encoding, __FUNCTION__); } public static function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null) { return self::mb_internal_trim('{^[%s]+}Du', $string, $characters, $encoding, __FUNCTION__); } public static function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null) { return self::mb_internal_trim('{[%s]+$}Du', $string, $characters, $encoding, __FUNCTION__); } private static function getSubpart($pos, $part, $haystack, $encoding) { if (false === $pos) { return false; } if ($part) { return self::mb_substr($haystack, 0, $pos, $encoding); } return self::mb_substr($haystack, $pos, null, $encoding); } private static function html_encoding_callback(array $m) { $i = 1; $entities = ''; $m = unpack('C*', htmlentities($m[0], \ENT_COMPAT, 'UTF-8')); while (isset($m[$i])) { if (0x80 > $m[$i]) { $entities .= \chr($m[$i++]); continue; } if (0xF0 <= $m[$i]) { $c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; } elseif (0xE0 <= $m[$i]) { $c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; } else { $c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80; } $entities .= '&#'.$c.';'; } return $entities; } private static function title_case(array $s) { return self::mb_convert_case($s[1], \MB_CASE_UPPER, 'UTF-8').self::mb_convert_case($s[2], \MB_CASE_LOWER, 'UTF-8'); } private static function getData($file) { if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) { return require $file; } return false; } private static function getEncoding($encoding) { if (null === $encoding) { return self::$internalEncoding; } if ('UTF-8' === $encoding) { return 'UTF-8'; } $encoding = strtoupper($encoding); if ('8BIT' === $encoding || 'BINARY' === $encoding) { return 'CP850'; } if ('UTF8' === $encoding) { return 'UTF-8'; } if ('UTF-32' === $encoding) { return 'UTF-32BE'; } if ('UTF-16' === $encoding) { return 'UTF-16BE'; } return $encoding; } private static function iconv($fromEncoding, $toEncoding, $s) { if (null === self::$iconvSupportsIgnore) { self::$iconvSupportsIgnore = false !== @iconv('UTF-8', 'UTF-8//IGNORE', ''); } return self::$iconvSupportsIgnore ? iconv($fromEncoding, $toEncoding.'//IGNORE', $s) : iconv($fromEncoding, $toEncoding, $s); } private static function mb_internal_trim(string $regex, string $string, ?string $characters, ?string $encoding, string $function) { if (null === $encoding) { $encoding = self::mb_internal_encoding(); } elseif (!self::assertEncoding($encoding, $function.'(): Argument #3 ($encoding) must be a valid encoding, "%s" given')) { return false; } if ('' === $characters) { return null === $encoding ? $string : self::mb_convert_encoding($string, $encoding); } if ('UTF-8' === $encoding) { $encoding = null; if (!preg_match('//u', $string)) { $string = @self::iconv('UTF-8', 'UTF-8', $string); } if (null !== $characters && !preg_match('//u', $characters)) { $characters = @self::iconv('UTF-8', 'UTF-8', $characters); } } else { $string = self::iconv($encoding, 'UTF-8', $string); if (null !== $characters) { $characters = self::iconv($encoding, 'UTF-8', $characters); } } if (null === $characters) { $characters = "\\0 \f\n\r\t\v\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{200A}\u{2028}\u{2029}\u{202F}\u{205F}\u{3000}\u{0085}\u{180E}"; } else { $characters = preg_quote($characters); } $string = preg_replace(\sprintf($regex, $characters), '', $string); if (null === $encoding) { return $string; } return self::iconv('UTF-8', $encoding, $string); } private static function assertEncoding(string $encoding, string $errorFormat): bool { try { $validEncoding = @self::mb_check_encoding('', $encoding); } catch (\ValueError $e) { throw new \ValueError(\sprintf($errorFormat, $encoding)); } if (!$validEncoding) { if (80000 > \PHP_VERSION_ID) { trigger_error(\sprintf($errorFormat, $encoding), \E_USER_WARNING); } else { throw new \ValueError(\sprintf($errorFormat, $encoding)); } } return $validEncoding; } } 'i̇', 'µ' => 'μ', 'ſ' => 's', 'ͅ' => 'ι', 'ς' => 'σ', 'ϐ' => 'β', 'ϑ' => 'θ', 'ϕ' => 'φ', 'ϖ' => 'π', 'ϰ' => 'κ', 'ϱ' => 'ρ', 'ϵ' => 'ε', 'ẛ' => 'ṡ', 'ι' => 'ι', 'ß' => 'ss', 'ʼn' => 'ʼn', 'ǰ' => 'ǰ', 'ΐ' => 'ΐ', 'ΰ' => 'ΰ', 'և' => 'եւ', 'ẖ' => 'ẖ', 'ẗ' => 'ẗ', 'ẘ' => 'ẘ', 'ẙ' => 'ẙ', 'ẚ' => 'aʾ', 'ẞ' => 'ss', 'ὐ' => 'ὐ', 'ὒ' => 'ὒ', 'ὔ' => 'ὔ', 'ὖ' => 'ὖ', 'ᾀ' => 'ἀι', 'ᾁ' => 'ἁι', 'ᾂ' => 'ἂι', 'ᾃ' => 'ἃι', 'ᾄ' => 'ἄι', 'ᾅ' => 'ἅι', 'ᾆ' => 'ἆι', 'ᾇ' => 'ἇι', 'ᾈ' => 'ἀι', 'ᾉ' => 'ἁι', 'ᾊ' => 'ἂι', 'ᾋ' => 'ἃι', 'ᾌ' => 'ἄι', 'ᾍ' => 'ἅι', 'ᾎ' => 'ἆι', 'ᾏ' => 'ἇι', 'ᾐ' => 'ἠι', 'ᾑ' => 'ἡι', 'ᾒ' => 'ἢι', 'ᾓ' => 'ἣι', 'ᾔ' => 'ἤι', 'ᾕ' => 'ἥι', 'ᾖ' => 'ἦι', 'ᾗ' => 'ἧι', 'ᾘ' => 'ἠι', 'ᾙ' => 'ἡι', 'ᾚ' => 'ἢι', 'ᾛ' => 'ἣι', 'ᾜ' => 'ἤι', 'ᾝ' => 'ἥι', 'ᾞ' => 'ἦι', 'ᾟ' => 'ἧι', 'ᾠ' => 'ὠι', 'ᾡ' => 'ὡι', 'ᾢ' => 'ὢι', 'ᾣ' => 'ὣι', 'ᾤ' => 'ὤι', 'ᾥ' => 'ὥι', 'ᾦ' => 'ὦι', 'ᾧ' => 'ὧι', 'ᾨ' => 'ὠι', 'ᾩ' => 'ὡι', 'ᾪ' => 'ὢι', 'ᾫ' => 'ὣι', 'ᾬ' => 'ὤι', 'ᾭ' => 'ὥι', 'ᾮ' => 'ὦι', 'ᾯ' => 'ὧι', 'ᾲ' => 'ὰι', 'ᾳ' => 'αι', 'ᾴ' => 'άι', 'ᾶ' => 'ᾶ', 'ᾷ' => 'ᾶι', 'ᾼ' => 'αι', 'ῂ' => 'ὴι', 'ῃ' => 'ηι', 'ῄ' => 'ήι', 'ῆ' => 'ῆ', 'ῇ' => 'ῆι', 'ῌ' => 'ηι', 'ῒ' => 'ῒ', 'ῖ' => 'ῖ', 'ῗ' => 'ῗ', 'ῢ' => 'ῢ', 'ῤ' => 'ῤ', 'ῦ' => 'ῦ', 'ῧ' => 'ῧ', 'ῲ' => 'ὼι', 'ῳ' => 'ωι', 'ῴ' => 'ώι', 'ῶ' => 'ῶ', 'ῷ' => 'ῶι', 'ῼ' => 'ωι', 'ff' => 'ff', 'fi' => 'fi', 'fl' => 'fl', 'ffi' => 'ffi', 'ffl' => 'ffl', 'ſt' => 'st', 'st' => 'st', 'ﬓ' => 'մն', 'ﬔ' => 'մե', 'ﬕ' => 'մի', 'ﬖ' => 'վն', 'ﬗ' => 'մխ', ]; 'a', 'B' => 'b', 'C' => 'c', 'D' => 'd', 'E' => 'e', 'F' => 'f', 'G' => 'g', 'H' => 'h', 'I' => 'i', 'J' => 'j', 'K' => 'k', 'L' => 'l', 'M' => 'm', 'N' => 'n', 'O' => 'o', 'P' => 'p', 'Q' => 'q', 'R' => 'r', 'S' => 's', 'T' => 't', 'U' => 'u', 'V' => 'v', 'W' => 'w', 'X' => 'x', 'Y' => 'y', 'Z' => 'z', 'À' => 'à', 'Á' => 'á', 'Â' => 'â', 'Ã' => 'ã', 'Ä' => 'ä', 'Å' => 'å', 'Æ' => 'æ', 'Ç' => 'ç', 'È' => 'è', 'É' => 'é', 'Ê' => 'ê', 'Ë' => 'ë', 'Ì' => 'ì', 'Í' => 'í', 'Î' => 'î', 'Ï' => 'ï', 'Ð' => 'ð', 'Ñ' => 'ñ', 'Ò' => 'ò', 'Ó' => 'ó', 'Ô' => 'ô', 'Õ' => 'õ', 'Ö' => 'ö', 'Ø' => 'ø', 'Ù' => 'ù', 'Ú' => 'ú', 'Û' => 'û', 'Ü' => 'ü', 'Ý' => 'ý', 'Þ' => 'þ', 'Ā' => 'ā', 'Ă' => 'ă', 'Ą' => 'ą', 'Ć' => 'ć', 'Ĉ' => 'ĉ', 'Ċ' => 'ċ', 'Č' => 'č', 'Ď' => 'ď', 'Đ' => 'đ', 'Ē' => 'ē', 'Ĕ' => 'ĕ', 'Ė' => 'ė', 'Ę' => 'ę', 'Ě' => 'ě', 'Ĝ' => 'ĝ', 'Ğ' => 'ğ', 'Ġ' => 'ġ', 'Ģ' => 'ģ', 'Ĥ' => 'ĥ', 'Ħ' => 'ħ', 'Ĩ' => 'ĩ', 'Ī' => 'ī', 'Ĭ' => 'ĭ', 'Į' => 'į', 'İ' => 'i̇', 'IJ' => 'ij', 'Ĵ' => 'ĵ', 'Ķ' => 'ķ', 'Ĺ' => 'ĺ', 'Ļ' => 'ļ', 'Ľ' => 'ľ', 'Ŀ' => 'ŀ', 'Ł' => 'ł', 'Ń' => 'ń', 'Ņ' => 'ņ', 'Ň' => 'ň', 'Ŋ' => 'ŋ', 'Ō' => 'ō', 'Ŏ' => 'ŏ', 'Ő' => 'ő', 'Œ' => 'œ', 'Ŕ' => 'ŕ', 'Ŗ' => 'ŗ', 'Ř' => 'ř', 'Ś' => 'ś', 'Ŝ' => 'ŝ', 'Ş' => 'ş', 'Š' => 'š', 'Ţ' => 'ţ', 'Ť' => 'ť', 'Ŧ' => 'ŧ', 'Ũ' => 'ũ', 'Ū' => 'ū', 'Ŭ' => 'ŭ', 'Ů' => 'ů', 'Ű' => 'ű', 'Ų' => 'ų', 'Ŵ' => 'ŵ', 'Ŷ' => 'ŷ', 'Ÿ' => 'ÿ', 'Ź' => 'ź', 'Ż' => 'ż', 'Ž' => 'ž', 'Ɓ' => 'ɓ', 'Ƃ' => 'ƃ', 'Ƅ' => 'ƅ', 'Ɔ' => 'ɔ', 'Ƈ' => 'ƈ', 'Ɖ' => 'ɖ', 'Ɗ' => 'ɗ', 'Ƌ' => 'ƌ', 'Ǝ' => 'ǝ', 'Ə' => 'ə', 'Ɛ' => 'ɛ', 'Ƒ' => 'ƒ', 'Ɠ' => 'ɠ', 'Ɣ' => 'ɣ', 'Ɩ' => 'ɩ', 'Ɨ' => 'ɨ', 'Ƙ' => 'ƙ', 'Ɯ' => 'ɯ', 'Ɲ' => 'ɲ', 'Ɵ' => 'ɵ', 'Ơ' => 'ơ', 'Ƣ' => 'ƣ', 'Ƥ' => 'ƥ', 'Ʀ' => 'ʀ', 'Ƨ' => 'ƨ', 'Ʃ' => 'ʃ', 'Ƭ' => 'ƭ', 'Ʈ' => 'ʈ', 'Ư' => 'ư', 'Ʊ' => 'ʊ', 'Ʋ' => 'ʋ', 'Ƴ' => 'ƴ', 'Ƶ' => 'ƶ', 'Ʒ' => 'ʒ', 'Ƹ' => 'ƹ', 'Ƽ' => 'ƽ', 'DŽ' => 'dž', 'Dž' => 'dž', 'LJ' => 'lj', 'Lj' => 'lj', 'NJ' => 'nj', 'Nj' => 'nj', 'Ǎ' => 'ǎ', 'Ǐ' => 'ǐ', 'Ǒ' => 'ǒ', 'Ǔ' => 'ǔ', 'Ǖ' => 'ǖ', 'Ǘ' => 'ǘ', 'Ǚ' => 'ǚ', 'Ǜ' => 'ǜ', 'Ǟ' => 'ǟ', 'Ǡ' => 'ǡ', 'Ǣ' => 'ǣ', 'Ǥ' => 'ǥ', 'Ǧ' => 'ǧ', 'Ǩ' => 'ǩ', 'Ǫ' => 'ǫ', 'Ǭ' => 'ǭ', 'Ǯ' => 'ǯ', 'DZ' => 'dz', 'Dz' => 'dz', 'Ǵ' => 'ǵ', 'Ƕ' => 'ƕ', 'Ƿ' => 'ƿ', 'Ǹ' => 'ǹ', 'Ǻ' => 'ǻ', 'Ǽ' => 'ǽ', 'Ǿ' => 'ǿ', 'Ȁ' => 'ȁ', 'Ȃ' => 'ȃ', 'Ȅ' => 'ȅ', 'Ȇ' => 'ȇ', 'Ȉ' => 'ȉ', 'Ȋ' => 'ȋ', 'Ȍ' => 'ȍ', 'Ȏ' => 'ȏ', 'Ȑ' => 'ȑ', 'Ȓ' => 'ȓ', 'Ȕ' => 'ȕ', 'Ȗ' => 'ȗ', 'Ș' => 'ș', 'Ț' => 'ț', 'Ȝ' => 'ȝ', 'Ȟ' => 'ȟ', 'Ƞ' => 'ƞ', 'Ȣ' => 'ȣ', 'Ȥ' => 'ȥ', 'Ȧ' => 'ȧ', 'Ȩ' => 'ȩ', 'Ȫ' => 'ȫ', 'Ȭ' => 'ȭ', 'Ȯ' => 'ȯ', 'Ȱ' => 'ȱ', 'Ȳ' => 'ȳ', 'Ⱥ' => 'ⱥ', 'Ȼ' => 'ȼ', 'Ƚ' => 'ƚ', 'Ⱦ' => 'ⱦ', 'Ɂ' => 'ɂ', 'Ƀ' => 'ƀ', 'Ʉ' => 'ʉ', 'Ʌ' => 'ʌ', 'Ɇ' => 'ɇ', 'Ɉ' => 'ɉ', 'Ɋ' => 'ɋ', 'Ɍ' => 'ɍ', 'Ɏ' => 'ɏ', 'Ͱ' => 'ͱ', 'Ͳ' => 'ͳ', 'Ͷ' => 'ͷ', 'Ϳ' => 'ϳ', 'Ά' => 'ά', 'Έ' => 'έ', 'Ή' => 'ή', 'Ί' => 'ί', 'Ό' => 'ό', 'Ύ' => 'ύ', 'Ώ' => 'ώ', 'Α' => 'α', 'Β' => 'β', 'Γ' => 'γ', 'Δ' => 'δ', 'Ε' => 'ε', 'Ζ' => 'ζ', 'Η' => 'η', 'Θ' => 'θ', 'Ι' => 'ι', 'Κ' => 'κ', 'Λ' => 'λ', 'Μ' => 'μ', 'Ν' => 'ν', 'Ξ' => 'ξ', 'Ο' => 'ο', 'Π' => 'π', 'Ρ' => 'ρ', 'Σ' => 'σ', 'Τ' => 'τ', 'Υ' => 'υ', 'Φ' => 'φ', 'Χ' => 'χ', 'Ψ' => 'ψ', 'Ω' => 'ω', 'Ϊ' => 'ϊ', 'Ϋ' => 'ϋ', 'Ϗ' => 'ϗ', 'Ϙ' => 'ϙ', 'Ϛ' => 'ϛ', 'Ϝ' => 'ϝ', 'Ϟ' => 'ϟ', 'Ϡ' => 'ϡ', 'Ϣ' => 'ϣ', 'Ϥ' => 'ϥ', 'Ϧ' => 'ϧ', 'Ϩ' => 'ϩ', 'Ϫ' => 'ϫ', 'Ϭ' => 'ϭ', 'Ϯ' => 'ϯ', 'ϴ' => 'θ', 'Ϸ' => 'ϸ', 'Ϲ' => 'ϲ', 'Ϻ' => 'ϻ', 'Ͻ' => 'ͻ', 'Ͼ' => 'ͼ', 'Ͽ' => 'ͽ', 'Ѐ' => 'ѐ', 'Ё' => 'ё', 'Ђ' => 'ђ', 'Ѓ' => 'ѓ', 'Є' => 'є', 'Ѕ' => 'ѕ', 'І' => 'і', 'Ї' => 'ї', 'Ј' => 'ј', 'Љ' => 'љ', 'Њ' => 'њ', 'Ћ' => 'ћ', 'Ќ' => 'ќ', 'Ѝ' => 'ѝ', 'Ў' => 'ў', 'Џ' => 'џ', 'А' => 'а', 'Б' => 'б', 'В' => 'в', 'Г' => 'г', 'Д' => 'д', 'Е' => 'е', 'Ж' => 'ж', 'З' => 'з', 'И' => 'и', 'Й' => 'й', 'К' => 'к', 'Л' => 'л', 'М' => 'м', 'Н' => 'н', 'О' => 'о', 'П' => 'п', 'Р' => 'р', 'С' => 'с', 'Т' => 'т', 'У' => 'у', 'Ф' => 'ф', 'Х' => 'х', 'Ц' => 'ц', 'Ч' => 'ч', 'Ш' => 'ш', 'Щ' => 'щ', 'Ъ' => 'ъ', 'Ы' => 'ы', 'Ь' => 'ь', 'Э' => 'э', 'Ю' => 'ю', 'Я' => 'я', 'Ѡ' => 'ѡ', 'Ѣ' => 'ѣ', 'Ѥ' => 'ѥ', 'Ѧ' => 'ѧ', 'Ѩ' => 'ѩ', 'Ѫ' => 'ѫ', 'Ѭ' => 'ѭ', 'Ѯ' => 'ѯ', 'Ѱ' => 'ѱ', 'Ѳ' => 'ѳ', 'Ѵ' => 'ѵ', 'Ѷ' => 'ѷ', 'Ѹ' => 'ѹ', 'Ѻ' => 'ѻ', 'Ѽ' => 'ѽ', 'Ѿ' => 'ѿ', 'Ҁ' => 'ҁ', 'Ҋ' => 'ҋ', 'Ҍ' => 'ҍ', 'Ҏ' => 'ҏ', 'Ґ' => 'ґ', 'Ғ' => 'ғ', 'Ҕ' => 'ҕ', 'Җ' => 'җ', 'Ҙ' => 'ҙ', 'Қ' => 'қ', 'Ҝ' => 'ҝ', 'Ҟ' => 'ҟ', 'Ҡ' => 'ҡ', 'Ң' => 'ң', 'Ҥ' => 'ҥ', 'Ҧ' => 'ҧ', 'Ҩ' => 'ҩ', 'Ҫ' => 'ҫ', 'Ҭ' => 'ҭ', 'Ү' => 'ү', 'Ұ' => 'ұ', 'Ҳ' => 'ҳ', 'Ҵ' => 'ҵ', 'Ҷ' => 'ҷ', 'Ҹ' => 'ҹ', 'Һ' => 'һ', 'Ҽ' => 'ҽ', 'Ҿ' => 'ҿ', 'Ӏ' => 'ӏ', 'Ӂ' => 'ӂ', 'Ӄ' => 'ӄ', 'Ӆ' => 'ӆ', 'Ӈ' => 'ӈ', 'Ӊ' => 'ӊ', 'Ӌ' => 'ӌ', 'Ӎ' => 'ӎ', 'Ӑ' => 'ӑ', 'Ӓ' => 'ӓ', 'Ӕ' => 'ӕ', 'Ӗ' => 'ӗ', 'Ә' => 'ә', 'Ӛ' => 'ӛ', 'Ӝ' => 'ӝ', 'Ӟ' => 'ӟ', 'Ӡ' => 'ӡ', 'Ӣ' => 'ӣ', 'Ӥ' => 'ӥ', 'Ӧ' => 'ӧ', 'Ө' => 'ө', 'Ӫ' => 'ӫ', 'Ӭ' => 'ӭ', 'Ӯ' => 'ӯ', 'Ӱ' => 'ӱ', 'Ӳ' => 'ӳ', 'Ӵ' => 'ӵ', 'Ӷ' => 'ӷ', 'Ӹ' => 'ӹ', 'Ӻ' => 'ӻ', 'Ӽ' => 'ӽ', 'Ӿ' => 'ӿ', 'Ԁ' => 'ԁ', 'Ԃ' => 'ԃ', 'Ԅ' => 'ԅ', 'Ԇ' => 'ԇ', 'Ԉ' => 'ԉ', 'Ԋ' => 'ԋ', 'Ԍ' => 'ԍ', 'Ԏ' => 'ԏ', 'Ԑ' => 'ԑ', 'Ԓ' => 'ԓ', 'Ԕ' => 'ԕ', 'Ԗ' => 'ԗ', 'Ԙ' => 'ԙ', 'Ԛ' => 'ԛ', 'Ԝ' => 'ԝ', 'Ԟ' => 'ԟ', 'Ԡ' => 'ԡ', 'Ԣ' => 'ԣ', 'Ԥ' => 'ԥ', 'Ԧ' => 'ԧ', 'Ԩ' => 'ԩ', 'Ԫ' => 'ԫ', 'Ԭ' => 'ԭ', 'Ԯ' => 'ԯ', 'Ա' => 'ա', 'Բ' => 'բ', 'Գ' => 'գ', 'Դ' => 'դ', 'Ե' => 'ե', 'Զ' => 'զ', 'Է' => 'է', 'Ը' => 'ը', 'Թ' => 'թ', 'Ժ' => 'ժ', 'Ի' => 'ի', 'Լ' => 'լ', 'Խ' => 'խ', 'Ծ' => 'ծ', 'Կ' => 'կ', 'Հ' => 'հ', 'Ձ' => 'ձ', 'Ղ' => 'ղ', 'Ճ' => 'ճ', 'Մ' => 'մ', 'Յ' => 'յ', 'Ն' => 'ն', 'Շ' => 'շ', 'Ո' => 'ո', 'Չ' => 'չ', 'Պ' => 'պ', 'Ջ' => 'ջ', 'Ռ' => 'ռ', 'Ս' => 'ս', 'Վ' => 'վ', 'Տ' => 'տ', 'Ր' => 'ր', 'Ց' => 'ց', 'Ւ' => 'ւ', 'Փ' => 'փ', 'Ք' => 'ք', 'Օ' => 'օ', 'Ֆ' => 'ֆ', 'Ⴀ' => 'ⴀ', 'Ⴁ' => 'ⴁ', 'Ⴂ' => 'ⴂ', 'Ⴃ' => 'ⴃ', 'Ⴄ' => 'ⴄ', 'Ⴅ' => 'ⴅ', 'Ⴆ' => 'ⴆ', 'Ⴇ' => 'ⴇ', 'Ⴈ' => 'ⴈ', 'Ⴉ' => 'ⴉ', 'Ⴊ' => 'ⴊ', 'Ⴋ' => 'ⴋ', 'Ⴌ' => 'ⴌ', 'Ⴍ' => 'ⴍ', 'Ⴎ' => 'ⴎ', 'Ⴏ' => 'ⴏ', 'Ⴐ' => 'ⴐ', 'Ⴑ' => 'ⴑ', 'Ⴒ' => 'ⴒ', 'Ⴓ' => 'ⴓ', 'Ⴔ' => 'ⴔ', 'Ⴕ' => 'ⴕ', 'Ⴖ' => 'ⴖ', 'Ⴗ' => 'ⴗ', 'Ⴘ' => 'ⴘ', 'Ⴙ' => 'ⴙ', 'Ⴚ' => 'ⴚ', 'Ⴛ' => 'ⴛ', 'Ⴜ' => 'ⴜ', 'Ⴝ' => 'ⴝ', 'Ⴞ' => 'ⴞ', 'Ⴟ' => 'ⴟ', 'Ⴠ' => 'ⴠ', 'Ⴡ' => 'ⴡ', 'Ⴢ' => 'ⴢ', 'Ⴣ' => 'ⴣ', 'Ⴤ' => 'ⴤ', 'Ⴥ' => 'ⴥ', 'Ⴧ' => 'ⴧ', 'Ⴭ' => 'ⴭ', 'Ꭰ' => 'ꭰ', 'Ꭱ' => 'ꭱ', 'Ꭲ' => 'ꭲ', 'Ꭳ' => 'ꭳ', 'Ꭴ' => 'ꭴ', 'Ꭵ' => 'ꭵ', 'Ꭶ' => 'ꭶ', 'Ꭷ' => 'ꭷ', 'Ꭸ' => 'ꭸ', 'Ꭹ' => 'ꭹ', 'Ꭺ' => 'ꭺ', 'Ꭻ' => 'ꭻ', 'Ꭼ' => 'ꭼ', 'Ꭽ' => 'ꭽ', 'Ꭾ' => 'ꭾ', 'Ꭿ' => 'ꭿ', 'Ꮀ' => 'ꮀ', 'Ꮁ' => 'ꮁ', 'Ꮂ' => 'ꮂ', 'Ꮃ' => 'ꮃ', 'Ꮄ' => 'ꮄ', 'Ꮅ' => 'ꮅ', 'Ꮆ' => 'ꮆ', 'Ꮇ' => 'ꮇ', 'Ꮈ' => 'ꮈ', 'Ꮉ' => 'ꮉ', 'Ꮊ' => 'ꮊ', 'Ꮋ' => 'ꮋ', 'Ꮌ' => 'ꮌ', 'Ꮍ' => 'ꮍ', 'Ꮎ' => 'ꮎ', 'Ꮏ' => 'ꮏ', 'Ꮐ' => 'ꮐ', 'Ꮑ' => 'ꮑ', 'Ꮒ' => 'ꮒ', 'Ꮓ' => 'ꮓ', 'Ꮔ' => 'ꮔ', 'Ꮕ' => 'ꮕ', 'Ꮖ' => 'ꮖ', 'Ꮗ' => 'ꮗ', 'Ꮘ' => 'ꮘ', 'Ꮙ' => 'ꮙ', 'Ꮚ' => 'ꮚ', 'Ꮛ' => 'ꮛ', 'Ꮜ' => 'ꮜ', 'Ꮝ' => 'ꮝ', 'Ꮞ' => 'ꮞ', 'Ꮟ' => 'ꮟ', 'Ꮠ' => 'ꮠ', 'Ꮡ' => 'ꮡ', 'Ꮢ' => 'ꮢ', 'Ꮣ' => 'ꮣ', 'Ꮤ' => 'ꮤ', 'Ꮥ' => 'ꮥ', 'Ꮦ' => 'ꮦ', 'Ꮧ' => 'ꮧ', 'Ꮨ' => 'ꮨ', 'Ꮩ' => 'ꮩ', 'Ꮪ' => 'ꮪ', 'Ꮫ' => 'ꮫ', 'Ꮬ' => 'ꮬ', 'Ꮭ' => 'ꮭ', 'Ꮮ' => 'ꮮ', 'Ꮯ' => 'ꮯ', 'Ꮰ' => 'ꮰ', 'Ꮱ' => 'ꮱ', 'Ꮲ' => 'ꮲ', 'Ꮳ' => 'ꮳ', 'Ꮴ' => 'ꮴ', 'Ꮵ' => 'ꮵ', 'Ꮶ' => 'ꮶ', 'Ꮷ' => 'ꮷ', 'Ꮸ' => 'ꮸ', 'Ꮹ' => 'ꮹ', 'Ꮺ' => 'ꮺ', 'Ꮻ' => 'ꮻ', 'Ꮼ' => 'ꮼ', 'Ꮽ' => 'ꮽ', 'Ꮾ' => 'ꮾ', 'Ꮿ' => 'ꮿ', 'Ᏸ' => 'ᏸ', 'Ᏹ' => 'ᏹ', 'Ᏺ' => 'ᏺ', 'Ᏻ' => 'ᏻ', 'Ᏼ' => 'ᏼ', 'Ᏽ' => 'ᏽ', 'Ა' => 'ა', 'Ბ' => 'ბ', 'Გ' => 'გ', 'Დ' => 'დ', 'Ე' => 'ე', 'Ვ' => 'ვ', 'Ზ' => 'ზ', 'Თ' => 'თ', 'Ი' => 'ი', 'Კ' => 'კ', 'Ლ' => 'ლ', 'Მ' => 'მ', 'Ნ' => 'ნ', 'Ო' => 'ო', 'Პ' => 'პ', 'Ჟ' => 'ჟ', 'Რ' => 'რ', 'Ს' => 'ს', 'Ტ' => 'ტ', 'Უ' => 'უ', 'Ფ' => 'ფ', 'Ქ' => 'ქ', 'Ღ' => 'ღ', 'Ყ' => 'ყ', 'Შ' => 'შ', 'Ჩ' => 'ჩ', 'Ც' => 'ც', 'Ძ' => 'ძ', 'Წ' => 'წ', 'Ჭ' => 'ჭ', 'Ხ' => 'ხ', 'Ჯ' => 'ჯ', 'Ჰ' => 'ჰ', 'Ჱ' => 'ჱ', 'Ჲ' => 'ჲ', 'Ჳ' => 'ჳ', 'Ჴ' => 'ჴ', 'Ჵ' => 'ჵ', 'Ჶ' => 'ჶ', 'Ჷ' => 'ჷ', 'Ჸ' => 'ჸ', 'Ჹ' => 'ჹ', 'Ჺ' => 'ჺ', 'Ჽ' => 'ჽ', 'Ჾ' => 'ჾ', 'Ჿ' => 'ჿ', 'Ḁ' => 'ḁ', 'Ḃ' => 'ḃ', 'Ḅ' => 'ḅ', 'Ḇ' => 'ḇ', 'Ḉ' => 'ḉ', 'Ḋ' => 'ḋ', 'Ḍ' => 'ḍ', 'Ḏ' => 'ḏ', 'Ḑ' => 'ḑ', 'Ḓ' => 'ḓ', 'Ḕ' => 'ḕ', 'Ḗ' => 'ḗ', 'Ḙ' => 'ḙ', 'Ḛ' => 'ḛ', 'Ḝ' => 'ḝ', 'Ḟ' => 'ḟ', 'Ḡ' => 'ḡ', 'Ḣ' => 'ḣ', 'Ḥ' => 'ḥ', 'Ḧ' => 'ḧ', 'Ḩ' => 'ḩ', 'Ḫ' => 'ḫ', 'Ḭ' => 'ḭ', 'Ḯ' => 'ḯ', 'Ḱ' => 'ḱ', 'Ḳ' => 'ḳ', 'Ḵ' => 'ḵ', 'Ḷ' => 'ḷ', 'Ḹ' => 'ḹ', 'Ḻ' => 'ḻ', 'Ḽ' => 'ḽ', 'Ḿ' => 'ḿ', 'Ṁ' => 'ṁ', 'Ṃ' => 'ṃ', 'Ṅ' => 'ṅ', 'Ṇ' => 'ṇ', 'Ṉ' => 'ṉ', 'Ṋ' => 'ṋ', 'Ṍ' => 'ṍ', 'Ṏ' => 'ṏ', 'Ṑ' => 'ṑ', 'Ṓ' => 'ṓ', 'Ṕ' => 'ṕ', 'Ṗ' => 'ṗ', 'Ṙ' => 'ṙ', 'Ṛ' => 'ṛ', 'Ṝ' => 'ṝ', 'Ṟ' => 'ṟ', 'Ṡ' => 'ṡ', 'Ṣ' => 'ṣ', 'Ṥ' => 'ṥ', 'Ṧ' => 'ṧ', 'Ṩ' => 'ṩ', 'Ṫ' => 'ṫ', 'Ṭ' => 'ṭ', 'Ṯ' => 'ṯ', 'Ṱ' => 'ṱ', 'Ṳ' => 'ṳ', 'Ṵ' => 'ṵ', 'Ṷ' => 'ṷ', 'Ṹ' => 'ṹ', 'Ṻ' => 'ṻ', 'Ṽ' => 'ṽ', 'Ṿ' => 'ṿ', 'Ẁ' => 'ẁ', 'Ẃ' => 'ẃ', 'Ẅ' => 'ẅ', 'Ẇ' => 'ẇ', 'Ẉ' => 'ẉ', 'Ẋ' => 'ẋ', 'Ẍ' => 'ẍ', 'Ẏ' => 'ẏ', 'Ẑ' => 'ẑ', 'Ẓ' => 'ẓ', 'Ẕ' => 'ẕ', 'ẞ' => 'ß', 'Ạ' => 'ạ', 'Ả' => 'ả', 'Ấ' => 'ấ', 'Ầ' => 'ầ', 'Ẩ' => 'ẩ', 'Ẫ' => 'ẫ', 'Ậ' => 'ậ', 'Ắ' => 'ắ', 'Ằ' => 'ằ', 'Ẳ' => 'ẳ', 'Ẵ' => 'ẵ', 'Ặ' => 'ặ', 'Ẹ' => 'ẹ', 'Ẻ' => 'ẻ', 'Ẽ' => 'ẽ', 'Ế' => 'ế', 'Ề' => 'ề', 'Ể' => 'ể', 'Ễ' => 'ễ', 'Ệ' => 'ệ', 'Ỉ' => 'ỉ', 'Ị' => 'ị', 'Ọ' => 'ọ', 'Ỏ' => 'ỏ', 'Ố' => 'ố', 'Ồ' => 'ồ', 'Ổ' => 'ổ', 'Ỗ' => 'ỗ', 'Ộ' => 'ộ', 'Ớ' => 'ớ', 'Ờ' => 'ờ', 'Ở' => 'ở', 'Ỡ' => 'ỡ', 'Ợ' => 'ợ', 'Ụ' => 'ụ', 'Ủ' => 'ủ', 'Ứ' => 'ứ', 'Ừ' => 'ừ', 'Ử' => 'ử', 'Ữ' => 'ữ', 'Ự' => 'ự', 'Ỳ' => 'ỳ', 'Ỵ' => 'ỵ', 'Ỷ' => 'ỷ', 'Ỹ' => 'ỹ', 'Ỻ' => 'ỻ', 'Ỽ' => 'ỽ', 'Ỿ' => 'ỿ', 'Ἀ' => 'ἀ', 'Ἁ' => 'ἁ', 'Ἂ' => 'ἂ', 'Ἃ' => 'ἃ', 'Ἄ' => 'ἄ', 'Ἅ' => 'ἅ', 'Ἆ' => 'ἆ', 'Ἇ' => 'ἇ', 'Ἐ' => 'ἐ', 'Ἑ' => 'ἑ', 'Ἒ' => 'ἒ', 'Ἓ' => 'ἓ', 'Ἔ' => 'ἔ', 'Ἕ' => 'ἕ', 'Ἠ' => 'ἠ', 'Ἡ' => 'ἡ', 'Ἢ' => 'ἢ', 'Ἣ' => 'ἣ', 'Ἤ' => 'ἤ', 'Ἥ' => 'ἥ', 'Ἦ' => 'ἦ', 'Ἧ' => 'ἧ', 'Ἰ' => 'ἰ', 'Ἱ' => 'ἱ', 'Ἲ' => 'ἲ', 'Ἳ' => 'ἳ', 'Ἴ' => 'ἴ', 'Ἵ' => 'ἵ', 'Ἶ' => 'ἶ', 'Ἷ' => 'ἷ', 'Ὀ' => 'ὀ', 'Ὁ' => 'ὁ', 'Ὂ' => 'ὂ', 'Ὃ' => 'ὃ', 'Ὄ' => 'ὄ', 'Ὅ' => 'ὅ', 'Ὑ' => 'ὑ', 'Ὓ' => 'ὓ', 'Ὕ' => 'ὕ', 'Ὗ' => 'ὗ', 'Ὠ' => 'ὠ', 'Ὡ' => 'ὡ', 'Ὢ' => 'ὢ', 'Ὣ' => 'ὣ', 'Ὤ' => 'ὤ', 'Ὥ' => 'ὥ', 'Ὦ' => 'ὦ', 'Ὧ' => 'ὧ', 'ᾈ' => 'ᾀ', 'ᾉ' => 'ᾁ', 'ᾊ' => 'ᾂ', 'ᾋ' => 'ᾃ', 'ᾌ' => 'ᾄ', 'ᾍ' => 'ᾅ', 'ᾎ' => 'ᾆ', 'ᾏ' => 'ᾇ', 'ᾘ' => 'ᾐ', 'ᾙ' => 'ᾑ', 'ᾚ' => 'ᾒ', 'ᾛ' => 'ᾓ', 'ᾜ' => 'ᾔ', 'ᾝ' => 'ᾕ', 'ᾞ' => 'ᾖ', 'ᾟ' => 'ᾗ', 'ᾨ' => 'ᾠ', 'ᾩ' => 'ᾡ', 'ᾪ' => 'ᾢ', 'ᾫ' => 'ᾣ', 'ᾬ' => 'ᾤ', 'ᾭ' => 'ᾥ', 'ᾮ' => 'ᾦ', 'ᾯ' => 'ᾧ', 'Ᾰ' => 'ᾰ', 'Ᾱ' => 'ᾱ', 'Ὰ' => 'ὰ', 'Ά' => 'ά', 'ᾼ' => 'ᾳ', 'Ὲ' => 'ὲ', 'Έ' => 'έ', 'Ὴ' => 'ὴ', 'Ή' => 'ή', 'ῌ' => 'ῃ', 'Ῐ' => 'ῐ', 'Ῑ' => 'ῑ', 'Ὶ' => 'ὶ', 'Ί' => 'ί', 'Ῠ' => 'ῠ', 'Ῡ' => 'ῡ', 'Ὺ' => 'ὺ', 'Ύ' => 'ύ', 'Ῥ' => 'ῥ', 'Ὸ' => 'ὸ', 'Ό' => 'ό', 'Ὼ' => 'ὼ', 'Ώ' => 'ώ', 'ῼ' => 'ῳ', 'Ω' => 'ω', 'K' => 'k', 'Å' => 'å', 'Ⅎ' => 'ⅎ', 'Ⅰ' => 'ⅰ', 'Ⅱ' => 'ⅱ', 'Ⅲ' => 'ⅲ', 'Ⅳ' => 'ⅳ', 'Ⅴ' => 'ⅴ', 'Ⅵ' => 'ⅵ', 'Ⅶ' => 'ⅶ', 'Ⅷ' => 'ⅷ', 'Ⅸ' => 'ⅸ', 'Ⅹ' => 'ⅹ', 'Ⅺ' => 'ⅺ', 'Ⅻ' => 'ⅻ', 'Ⅼ' => 'ⅼ', 'Ⅽ' => 'ⅽ', 'Ⅾ' => 'ⅾ', 'Ⅿ' => 'ⅿ', 'Ↄ' => 'ↄ', 'Ⓐ' => 'ⓐ', 'Ⓑ' => 'ⓑ', 'Ⓒ' => 'ⓒ', 'Ⓓ' => 'ⓓ', 'Ⓔ' => 'ⓔ', 'Ⓕ' => 'ⓕ', 'Ⓖ' => 'ⓖ', 'Ⓗ' => 'ⓗ', 'Ⓘ' => 'ⓘ', 'Ⓙ' => 'ⓙ', 'Ⓚ' => 'ⓚ', 'Ⓛ' => 'ⓛ', 'Ⓜ' => 'ⓜ', 'Ⓝ' => 'ⓝ', 'Ⓞ' => 'ⓞ', 'Ⓟ' => 'ⓟ', 'Ⓠ' => 'ⓠ', 'Ⓡ' => 'ⓡ', 'Ⓢ' => 'ⓢ', 'Ⓣ' => 'ⓣ', 'Ⓤ' => 'ⓤ', 'Ⓥ' => 'ⓥ', 'Ⓦ' => 'ⓦ', 'Ⓧ' => 'ⓧ', 'Ⓨ' => 'ⓨ', 'Ⓩ' => 'ⓩ', 'Ⰰ' => 'ⰰ', 'Ⰱ' => 'ⰱ', 'Ⰲ' => 'ⰲ', 'Ⰳ' => 'ⰳ', 'Ⰴ' => 'ⰴ', 'Ⰵ' => 'ⰵ', 'Ⰶ' => 'ⰶ', 'Ⰷ' => 'ⰷ', 'Ⰸ' => 'ⰸ', 'Ⰹ' => 'ⰹ', 'Ⰺ' => 'ⰺ', 'Ⰻ' => 'ⰻ', 'Ⰼ' => 'ⰼ', 'Ⰽ' => 'ⰽ', 'Ⰾ' => 'ⰾ', 'Ⰿ' => 'ⰿ', 'Ⱀ' => 'ⱀ', 'Ⱁ' => 'ⱁ', 'Ⱂ' => 'ⱂ', 'Ⱃ' => 'ⱃ', 'Ⱄ' => 'ⱄ', 'Ⱅ' => 'ⱅ', 'Ⱆ' => 'ⱆ', 'Ⱇ' => 'ⱇ', 'Ⱈ' => 'ⱈ', 'Ⱉ' => 'ⱉ', 'Ⱊ' => 'ⱊ', 'Ⱋ' => 'ⱋ', 'Ⱌ' => 'ⱌ', 'Ⱍ' => 'ⱍ', 'Ⱎ' => 'ⱎ', 'Ⱏ' => 'ⱏ', 'Ⱐ' => 'ⱐ', 'Ⱑ' => 'ⱑ', 'Ⱒ' => 'ⱒ', 'Ⱓ' => 'ⱓ', 'Ⱔ' => 'ⱔ', 'Ⱕ' => 'ⱕ', 'Ⱖ' => 'ⱖ', 'Ⱗ' => 'ⱗ', 'Ⱘ' => 'ⱘ', 'Ⱙ' => 'ⱙ', 'Ⱚ' => 'ⱚ', 'Ⱛ' => 'ⱛ', 'Ⱜ' => 'ⱜ', 'Ⱝ' => 'ⱝ', 'Ⱞ' => 'ⱞ', 'Ⱡ' => 'ⱡ', 'Ɫ' => 'ɫ', 'Ᵽ' => 'ᵽ', 'Ɽ' => 'ɽ', 'Ⱨ' => 'ⱨ', 'Ⱪ' => 'ⱪ', 'Ⱬ' => 'ⱬ', 'Ɑ' => 'ɑ', 'Ɱ' => 'ɱ', 'Ɐ' => 'ɐ', 'Ɒ' => 'ɒ', 'Ⱳ' => 'ⱳ', 'Ⱶ' => 'ⱶ', 'Ȿ' => 'ȿ', 'Ɀ' => 'ɀ', 'Ⲁ' => 'ⲁ', 'Ⲃ' => 'ⲃ', 'Ⲅ' => 'ⲅ', 'Ⲇ' => 'ⲇ', 'Ⲉ' => 'ⲉ', 'Ⲋ' => 'ⲋ', 'Ⲍ' => 'ⲍ', 'Ⲏ' => 'ⲏ', 'Ⲑ' => 'ⲑ', 'Ⲓ' => 'ⲓ', 'Ⲕ' => 'ⲕ', 'Ⲗ' => 'ⲗ', 'Ⲙ' => 'ⲙ', 'Ⲛ' => 'ⲛ', 'Ⲝ' => 'ⲝ', 'Ⲟ' => 'ⲟ', 'Ⲡ' => 'ⲡ', 'Ⲣ' => 'ⲣ', 'Ⲥ' => 'ⲥ', 'Ⲧ' => 'ⲧ', 'Ⲩ' => 'ⲩ', 'Ⲫ' => 'ⲫ', 'Ⲭ' => 'ⲭ', 'Ⲯ' => 'ⲯ', 'Ⲱ' => 'ⲱ', 'Ⲳ' => 'ⲳ', 'Ⲵ' => 'ⲵ', 'Ⲷ' => 'ⲷ', 'Ⲹ' => 'ⲹ', 'Ⲻ' => 'ⲻ', 'Ⲽ' => 'ⲽ', 'Ⲿ' => 'ⲿ', 'Ⳁ' => 'ⳁ', 'Ⳃ' => 'ⳃ', 'Ⳅ' => 'ⳅ', 'Ⳇ' => 'ⳇ', 'Ⳉ' => 'ⳉ', 'Ⳋ' => 'ⳋ', 'Ⳍ' => 'ⳍ', 'Ⳏ' => 'ⳏ', 'Ⳑ' => 'ⳑ', 'Ⳓ' => 'ⳓ', 'Ⳕ' => 'ⳕ', 'Ⳗ' => 'ⳗ', 'Ⳙ' => 'ⳙ', 'Ⳛ' => 'ⳛ', 'Ⳝ' => 'ⳝ', 'Ⳟ' => 'ⳟ', 'Ⳡ' => 'ⳡ', 'Ⳣ' => 'ⳣ', 'Ⳬ' => 'ⳬ', 'Ⳮ' => 'ⳮ', 'Ⳳ' => 'ⳳ', 'Ꙁ' => 'ꙁ', 'Ꙃ' => 'ꙃ', 'Ꙅ' => 'ꙅ', 'Ꙇ' => 'ꙇ', 'Ꙉ' => 'ꙉ', 'Ꙋ' => 'ꙋ', 'Ꙍ' => 'ꙍ', 'Ꙏ' => 'ꙏ', 'Ꙑ' => 'ꙑ', 'Ꙓ' => 'ꙓ', 'Ꙕ' => 'ꙕ', 'Ꙗ' => 'ꙗ', 'Ꙙ' => 'ꙙ', 'Ꙛ' => 'ꙛ', 'Ꙝ' => 'ꙝ', 'Ꙟ' => 'ꙟ', 'Ꙡ' => 'ꙡ', 'Ꙣ' => 'ꙣ', 'Ꙥ' => 'ꙥ', 'Ꙧ' => 'ꙧ', 'Ꙩ' => 'ꙩ', 'Ꙫ' => 'ꙫ', 'Ꙭ' => 'ꙭ', 'Ꚁ' => 'ꚁ', 'Ꚃ' => 'ꚃ', 'Ꚅ' => 'ꚅ', 'Ꚇ' => 'ꚇ', 'Ꚉ' => 'ꚉ', 'Ꚋ' => 'ꚋ', 'Ꚍ' => 'ꚍ', 'Ꚏ' => 'ꚏ', 'Ꚑ' => 'ꚑ', 'Ꚓ' => 'ꚓ', 'Ꚕ' => 'ꚕ', 'Ꚗ' => 'ꚗ', 'Ꚙ' => 'ꚙ', 'Ꚛ' => 'ꚛ', 'Ꜣ' => 'ꜣ', 'Ꜥ' => 'ꜥ', 'Ꜧ' => 'ꜧ', 'Ꜩ' => 'ꜩ', 'Ꜫ' => 'ꜫ', 'Ꜭ' => 'ꜭ', 'Ꜯ' => 'ꜯ', 'Ꜳ' => 'ꜳ', 'Ꜵ' => 'ꜵ', 'Ꜷ' => 'ꜷ', 'Ꜹ' => 'ꜹ', 'Ꜻ' => 'ꜻ', 'Ꜽ' => 'ꜽ', 'Ꜿ' => 'ꜿ', 'Ꝁ' => 'ꝁ', 'Ꝃ' => 'ꝃ', 'Ꝅ' => 'ꝅ', 'Ꝇ' => 'ꝇ', 'Ꝉ' => 'ꝉ', 'Ꝋ' => 'ꝋ', 'Ꝍ' => 'ꝍ', 'Ꝏ' => 'ꝏ', 'Ꝑ' => 'ꝑ', 'Ꝓ' => 'ꝓ', 'Ꝕ' => 'ꝕ', 'Ꝗ' => 'ꝗ', 'Ꝙ' => 'ꝙ', 'Ꝛ' => 'ꝛ', 'Ꝝ' => 'ꝝ', 'Ꝟ' => 'ꝟ', 'Ꝡ' => 'ꝡ', 'Ꝣ' => 'ꝣ', 'Ꝥ' => 'ꝥ', 'Ꝧ' => 'ꝧ', 'Ꝩ' => 'ꝩ', 'Ꝫ' => 'ꝫ', 'Ꝭ' => 'ꝭ', 'Ꝯ' => 'ꝯ', 'Ꝺ' => 'ꝺ', 'Ꝼ' => 'ꝼ', 'Ᵹ' => 'ᵹ', 'Ꝿ' => 'ꝿ', 'Ꞁ' => 'ꞁ', 'Ꞃ' => 'ꞃ', 'Ꞅ' => 'ꞅ', 'Ꞇ' => 'ꞇ', 'Ꞌ' => 'ꞌ', 'Ɥ' => 'ɥ', 'Ꞑ' => 'ꞑ', 'Ꞓ' => 'ꞓ', 'Ꞗ' => 'ꞗ', 'Ꞙ' => 'ꞙ', 'Ꞛ' => 'ꞛ', 'Ꞝ' => 'ꞝ', 'Ꞟ' => 'ꞟ', 'Ꞡ' => 'ꞡ', 'Ꞣ' => 'ꞣ', 'Ꞥ' => 'ꞥ', 'Ꞧ' => 'ꞧ', 'Ꞩ' => 'ꞩ', 'Ɦ' => 'ɦ', 'Ɜ' => 'ɜ', 'Ɡ' => 'ɡ', 'Ɬ' => 'ɬ', 'Ɪ' => 'ɪ', 'Ʞ' => 'ʞ', 'Ʇ' => 'ʇ', 'Ʝ' => 'ʝ', 'Ꭓ' => 'ꭓ', 'Ꞵ' => 'ꞵ', 'Ꞷ' => 'ꞷ', 'Ꞹ' => 'ꞹ', 'Ꞻ' => 'ꞻ', 'Ꞽ' => 'ꞽ', 'Ꞿ' => 'ꞿ', 'Ꟃ' => 'ꟃ', 'Ꞔ' => 'ꞔ', 'Ʂ' => 'ʂ', 'Ᶎ' => 'ᶎ', 'Ꟈ' => 'ꟈ', 'Ꟊ' => 'ꟊ', 'Ꟶ' => 'ꟶ', 'A' => 'a', 'B' => 'b', 'C' => 'c', 'D' => 'd', 'E' => 'e', 'F' => 'f', 'G' => 'g', 'H' => 'h', 'I' => 'i', 'J' => 'j', 'K' => 'k', 'L' => 'l', 'M' => 'm', 'N' => 'n', 'O' => 'o', 'P' => 'p', 'Q' => 'q', 'R' => 'r', 'S' => 's', 'T' => 't', 'U' => 'u', 'V' => 'v', 'W' => 'w', 'X' => 'x', 'Y' => 'y', 'Z' => 'z', '𐐀' => '𐐨', '𐐁' => '𐐩', '𐐂' => '𐐪', '𐐃' => '𐐫', '𐐄' => '𐐬', '𐐅' => '𐐭', '𐐆' => '𐐮', '𐐇' => '𐐯', '𐐈' => '𐐰', '𐐉' => '𐐱', '𐐊' => '𐐲', '𐐋' => '𐐳', '𐐌' => '𐐴', '𐐍' => '𐐵', '𐐎' => '𐐶', '𐐏' => '𐐷', '𐐐' => '𐐸', '𐐑' => '𐐹', '𐐒' => '𐐺', '𐐓' => '𐐻', '𐐔' => '𐐼', '𐐕' => '𐐽', '𐐖' => '𐐾', '𐐗' => '𐐿', '𐐘' => '𐑀', '𐐙' => '𐑁', '𐐚' => '𐑂', '𐐛' => '𐑃', '𐐜' => '𐑄', '𐐝' => '𐑅', '𐐞' => '𐑆', '𐐟' => '𐑇', '𐐠' => '𐑈', '𐐡' => '𐑉', '𐐢' => '𐑊', '𐐣' => '𐑋', '𐐤' => '𐑌', '𐐥' => '𐑍', '𐐦' => '𐑎', '𐐧' => '𐑏', '𐒰' => '𐓘', '𐒱' => '𐓙', '𐒲' => '𐓚', '𐒳' => '𐓛', '𐒴' => '𐓜', '𐒵' => '𐓝', '𐒶' => '𐓞', '𐒷' => '𐓟', '𐒸' => '𐓠', '𐒹' => '𐓡', '𐒺' => '𐓢', '𐒻' => '𐓣', '𐒼' => '𐓤', '𐒽' => '𐓥', '𐒾' => '𐓦', '𐒿' => '𐓧', '𐓀' => '𐓨', '𐓁' => '𐓩', '𐓂' => '𐓪', '𐓃' => '𐓫', '𐓄' => '𐓬', '𐓅' => '𐓭', '𐓆' => '𐓮', '𐓇' => '𐓯', '𐓈' => '𐓰', '𐓉' => '𐓱', '𐓊' => '𐓲', '𐓋' => '𐓳', '𐓌' => '𐓴', '𐓍' => '𐓵', '𐓎' => '𐓶', '𐓏' => '𐓷', '𐓐' => '𐓸', '𐓑' => '𐓹', '𐓒' => '𐓺', '𐓓' => '𐓻', '𐲀' => '𐳀', '𐲁' => '𐳁', '𐲂' => '𐳂', '𐲃' => '𐳃', '𐲄' => '𐳄', '𐲅' => '𐳅', '𐲆' => '𐳆', '𐲇' => '𐳇', '𐲈' => '𐳈', '𐲉' => '𐳉', '𐲊' => '𐳊', '𐲋' => '𐳋', '𐲌' => '𐳌', '𐲍' => '𐳍', '𐲎' => '𐳎', '𐲏' => '𐳏', '𐲐' => '𐳐', '𐲑' => '𐳑', '𐲒' => '𐳒', '𐲓' => '𐳓', '𐲔' => '𐳔', '𐲕' => '𐳕', '𐲖' => '𐳖', '𐲗' => '𐳗', '𐲘' => '𐳘', '𐲙' => '𐳙', '𐲚' => '𐳚', '𐲛' => '𐳛', '𐲜' => '𐳜', '𐲝' => '𐳝', '𐲞' => '𐳞', '𐲟' => '𐳟', '𐲠' => '𐳠', '𐲡' => '𐳡', '𐲢' => '𐳢', '𐲣' => '𐳣', '𐲤' => '𐳤', '𐲥' => '𐳥', '𐲦' => '𐳦', '𐲧' => '𐳧', '𐲨' => '𐳨', '𐲩' => '𐳩', '𐲪' => '𐳪', '𐲫' => '𐳫', '𐲬' => '𐳬', '𐲭' => '𐳭', '𐲮' => '𐳮', '𐲯' => '𐳯', '𐲰' => '𐳰', '𐲱' => '𐳱', '𐲲' => '𐳲', '𑢠' => '𑣀', '𑢡' => '𑣁', '𑢢' => '𑣂', '𑢣' => '𑣃', '𑢤' => '𑣄', '𑢥' => '𑣅', '𑢦' => '𑣆', '𑢧' => '𑣇', '𑢨' => '𑣈', '𑢩' => '𑣉', '𑢪' => '𑣊', '𑢫' => '𑣋', '𑢬' => '𑣌', '𑢭' => '𑣍', '𑢮' => '𑣎', '𑢯' => '𑣏', '𑢰' => '𑣐', '𑢱' => '𑣑', '𑢲' => '𑣒', '𑢳' => '𑣓', '𑢴' => '𑣔', '𑢵' => '𑣕', '𑢶' => '𑣖', '𑢷' => '𑣗', '𑢸' => '𑣘', '𑢹' => '𑣙', '𑢺' => '𑣚', '𑢻' => '𑣛', '𑢼' => '𑣜', '𑢽' => '𑣝', '𑢾' => '𑣞', '𑢿' => '𑣟', '𖹀' => '𖹠', '𖹁' => '𖹡', '𖹂' => '𖹢', '𖹃' => '𖹣', '𖹄' => '𖹤', '𖹅' => '𖹥', '𖹆' => '𖹦', '𖹇' => '𖹧', '𖹈' => '𖹨', '𖹉' => '𖹩', '𖹊' => '𖹪', '𖹋' => '𖹫', '𖹌' => '𖹬', '𖹍' => '𖹭', '𖹎' => '𖹮', '𖹏' => '𖹯', '𖹐' => '𖹰', '𖹑' => '𖹱', '𖹒' => '𖹲', '𖹓' => '𖹳', '𖹔' => '𖹴', '𖹕' => '𖹵', '𖹖' => '𖹶', '𖹗' => '𖹷', '𖹘' => '𖹸', '𖹙' => '𖹹', '𖹚' => '𖹺', '𖹛' => '𖹻', '𖹜' => '𖹼', '𖹝' => '𖹽', '𖹞' => '𖹾', '𖹟' => '𖹿', '𞤀' => '𞤢', '𞤁' => '𞤣', '𞤂' => '𞤤', '𞤃' => '𞤥', '𞤄' => '𞤦', '𞤅' => '𞤧', '𞤆' => '𞤨', '𞤇' => '𞤩', '𞤈' => '𞤪', '𞤉' => '𞤫', '𞤊' => '𞤬', '𞤋' => '𞤭', '𞤌' => '𞤮', '𞤍' => '𞤯', '𞤎' => '𞤰', '𞤏' => '𞤱', '𞤐' => '𞤲', '𞤑' => '𞤳', '𞤒' => '𞤴', '𞤓' => '𞤵', '𞤔' => '𞤶', '𞤕' => '𞤷', '𞤖' => '𞤸', '𞤗' => '𞤹', '𞤘' => '𞤺', '𞤙' => '𞤻', '𞤚' => '𞤼', '𞤛' => '𞤽', '𞤜' => '𞤾', '𞤝' => '𞤿', '𞤞' => '𞥀', '𞤟' => '𞥁', '𞤠' => '𞥂', '𞤡' => '𞥃', ); 'A', 'b' => 'B', 'c' => 'C', 'd' => 'D', 'e' => 'E', 'f' => 'F', 'g' => 'G', 'h' => 'H', 'i' => 'I', 'j' => 'J', 'k' => 'K', 'l' => 'L', 'm' => 'M', 'n' => 'N', 'o' => 'O', 'p' => 'P', 'q' => 'Q', 'r' => 'R', 's' => 'S', 't' => 'T', 'u' => 'U', 'v' => 'V', 'w' => 'W', 'x' => 'X', 'y' => 'Y', 'z' => 'Z', 'µ' => 'Μ', 'à' => 'À', 'á' => 'Á', 'â' => 'Â', 'ã' => 'Ã', 'ä' => 'Ä', 'å' => 'Å', 'æ' => 'Æ', 'ç' => 'Ç', 'è' => 'È', 'é' => 'É', 'ê' => 'Ê', 'ë' => 'Ë', 'ì' => 'Ì', 'í' => 'Í', 'î' => 'Î', 'ï' => 'Ï', 'ð' => 'Ð', 'ñ' => 'Ñ', 'ò' => 'Ò', 'ó' => 'Ó', 'ô' => 'Ô', 'õ' => 'Õ', 'ö' => 'Ö', 'ø' => 'Ø', 'ù' => 'Ù', 'ú' => 'Ú', 'û' => 'Û', 'ü' => 'Ü', 'ý' => 'Ý', 'þ' => 'Þ', 'ÿ' => 'Ÿ', 'ā' => 'Ā', 'ă' => 'Ă', 'ą' => 'Ą', 'ć' => 'Ć', 'ĉ' => 'Ĉ', 'ċ' => 'Ċ', 'č' => 'Č', 'ď' => 'Ď', 'đ' => 'Đ', 'ē' => 'Ē', 'ĕ' => 'Ĕ', 'ė' => 'Ė', 'ę' => 'Ę', 'ě' => 'Ě', 'ĝ' => 'Ĝ', 'ğ' => 'Ğ', 'ġ' => 'Ġ', 'ģ' => 'Ģ', 'ĥ' => 'Ĥ', 'ħ' => 'Ħ', 'ĩ' => 'Ĩ', 'ī' => 'Ī', 'ĭ' => 'Ĭ', 'į' => 'Į', 'ı' => 'I', 'ij' => 'IJ', 'ĵ' => 'Ĵ', 'ķ' => 'Ķ', 'ĺ' => 'Ĺ', 'ļ' => 'Ļ', 'ľ' => 'Ľ', 'ŀ' => 'Ŀ', 'ł' => 'Ł', 'ń' => 'Ń', 'ņ' => 'Ņ', 'ň' => 'Ň', 'ŋ' => 'Ŋ', 'ō' => 'Ō', 'ŏ' => 'Ŏ', 'ő' => 'Ő', 'œ' => 'Œ', 'ŕ' => 'Ŕ', 'ŗ' => 'Ŗ', 'ř' => 'Ř', 'ś' => 'Ś', 'ŝ' => 'Ŝ', 'ş' => 'Ş', 'š' => 'Š', 'ţ' => 'Ţ', 'ť' => 'Ť', 'ŧ' => 'Ŧ', 'ũ' => 'Ũ', 'ū' => 'Ū', 'ŭ' => 'Ŭ', 'ů' => 'Ů', 'ű' => 'Ű', 'ų' => 'Ų', 'ŵ' => 'Ŵ', 'ŷ' => 'Ŷ', 'ź' => 'Ź', 'ż' => 'Ż', 'ž' => 'Ž', 'ſ' => 'S', 'ƀ' => 'Ƀ', 'ƃ' => 'Ƃ', 'ƅ' => 'Ƅ', 'ƈ' => 'Ƈ', 'ƌ' => 'Ƌ', 'ƒ' => 'Ƒ', 'ƕ' => 'Ƕ', 'ƙ' => 'Ƙ', 'ƚ' => 'Ƚ', 'ƞ' => 'Ƞ', 'ơ' => 'Ơ', 'ƣ' => 'Ƣ', 'ƥ' => 'Ƥ', 'ƨ' => 'Ƨ', 'ƭ' => 'Ƭ', 'ư' => 'Ư', 'ƴ' => 'Ƴ', 'ƶ' => 'Ƶ', 'ƹ' => 'Ƹ', 'ƽ' => 'Ƽ', 'ƿ' => 'Ƿ', 'Dž' => 'DŽ', 'dž' => 'DŽ', 'Lj' => 'LJ', 'lj' => 'LJ', 'Nj' => 'NJ', 'nj' => 'NJ', 'ǎ' => 'Ǎ', 'ǐ' => 'Ǐ', 'ǒ' => 'Ǒ', 'ǔ' => 'Ǔ', 'ǖ' => 'Ǖ', 'ǘ' => 'Ǘ', 'ǚ' => 'Ǚ', 'ǜ' => 'Ǜ', 'ǝ' => 'Ǝ', 'ǟ' => 'Ǟ', 'ǡ' => 'Ǡ', 'ǣ' => 'Ǣ', 'ǥ' => 'Ǥ', 'ǧ' => 'Ǧ', 'ǩ' => 'Ǩ', 'ǫ' => 'Ǫ', 'ǭ' => 'Ǭ', 'ǯ' => 'Ǯ', 'Dz' => 'DZ', 'dz' => 'DZ', 'ǵ' => 'Ǵ', 'ǹ' => 'Ǹ', 'ǻ' => 'Ǻ', 'ǽ' => 'Ǽ', 'ǿ' => 'Ǿ', 'ȁ' => 'Ȁ', 'ȃ' => 'Ȃ', 'ȅ' => 'Ȅ', 'ȇ' => 'Ȇ', 'ȉ' => 'Ȉ', 'ȋ' => 'Ȋ', 'ȍ' => 'Ȍ', 'ȏ' => 'Ȏ', 'ȑ' => 'Ȑ', 'ȓ' => 'Ȓ', 'ȕ' => 'Ȕ', 'ȗ' => 'Ȗ', 'ș' => 'Ș', 'ț' => 'Ț', 'ȝ' => 'Ȝ', 'ȟ' => 'Ȟ', 'ȣ' => 'Ȣ', 'ȥ' => 'Ȥ', 'ȧ' => 'Ȧ', 'ȩ' => 'Ȩ', 'ȫ' => 'Ȫ', 'ȭ' => 'Ȭ', 'ȯ' => 'Ȯ', 'ȱ' => 'Ȱ', 'ȳ' => 'Ȳ', 'ȼ' => 'Ȼ', 'ȿ' => 'Ȿ', 'ɀ' => 'Ɀ', 'ɂ' => 'Ɂ', 'ɇ' => 'Ɇ', 'ɉ' => 'Ɉ', 'ɋ' => 'Ɋ', 'ɍ' => 'Ɍ', 'ɏ' => 'Ɏ', 'ɐ' => 'Ɐ', 'ɑ' => 'Ɑ', 'ɒ' => 'Ɒ', 'ɓ' => 'Ɓ', 'ɔ' => 'Ɔ', 'ɖ' => 'Ɖ', 'ɗ' => 'Ɗ', 'ə' => 'Ə', 'ɛ' => 'Ɛ', 'ɜ' => 'Ɜ', 'ɠ' => 'Ɠ', 'ɡ' => 'Ɡ', 'ɣ' => 'Ɣ', 'ɥ' => 'Ɥ', 'ɦ' => 'Ɦ', 'ɨ' => 'Ɨ', 'ɩ' => 'Ɩ', 'ɪ' => 'Ɪ', 'ɫ' => 'Ɫ', 'ɬ' => 'Ɬ', 'ɯ' => 'Ɯ', 'ɱ' => 'Ɱ', 'ɲ' => 'Ɲ', 'ɵ' => 'Ɵ', 'ɽ' => 'Ɽ', 'ʀ' => 'Ʀ', 'ʂ' => 'Ʂ', 'ʃ' => 'Ʃ', 'ʇ' => 'Ʇ', 'ʈ' => 'Ʈ', 'ʉ' => 'Ʉ', 'ʊ' => 'Ʊ', 'ʋ' => 'Ʋ', 'ʌ' => 'Ʌ', 'ʒ' => 'Ʒ', 'ʝ' => 'Ʝ', 'ʞ' => 'Ʞ', 'ͅ' => 'Ι', 'ͱ' => 'Ͱ', 'ͳ' => 'Ͳ', 'ͷ' => 'Ͷ', 'ͻ' => 'Ͻ', 'ͼ' => 'Ͼ', 'ͽ' => 'Ͽ', 'ά' => 'Ά', 'έ' => 'Έ', 'ή' => 'Ή', 'ί' => 'Ί', 'α' => 'Α', 'β' => 'Β', 'γ' => 'Γ', 'δ' => 'Δ', 'ε' => 'Ε', 'ζ' => 'Ζ', 'η' => 'Η', 'θ' => 'Θ', 'ι' => 'Ι', 'κ' => 'Κ', 'λ' => 'Λ', 'μ' => 'Μ', 'ν' => 'Ν', 'ξ' => 'Ξ', 'ο' => 'Ο', 'π' => 'Π', 'ρ' => 'Ρ', 'ς' => 'Σ', 'σ' => 'Σ', 'τ' => 'Τ', 'υ' => 'Υ', 'φ' => 'Φ', 'χ' => 'Χ', 'ψ' => 'Ψ', 'ω' => 'Ω', 'ϊ' => 'Ϊ', 'ϋ' => 'Ϋ', 'ό' => 'Ό', 'ύ' => 'Ύ', 'ώ' => 'Ώ', 'ϐ' => 'Β', 'ϑ' => 'Θ', 'ϕ' => 'Φ', 'ϖ' => 'Π', 'ϗ' => 'Ϗ', 'ϙ' => 'Ϙ', 'ϛ' => 'Ϛ', 'ϝ' => 'Ϝ', 'ϟ' => 'Ϟ', 'ϡ' => 'Ϡ', 'ϣ' => 'Ϣ', 'ϥ' => 'Ϥ', 'ϧ' => 'Ϧ', 'ϩ' => 'Ϩ', 'ϫ' => 'Ϫ', 'ϭ' => 'Ϭ', 'ϯ' => 'Ϯ', 'ϰ' => 'Κ', 'ϱ' => 'Ρ', 'ϲ' => 'Ϲ', 'ϳ' => 'Ϳ', 'ϵ' => 'Ε', 'ϸ' => 'Ϸ', 'ϻ' => 'Ϻ', 'а' => 'А', 'б' => 'Б', 'в' => 'В', 'г' => 'Г', 'д' => 'Д', 'е' => 'Е', 'ж' => 'Ж', 'з' => 'З', 'и' => 'И', 'й' => 'Й', 'к' => 'К', 'л' => 'Л', 'м' => 'М', 'н' => 'Н', 'о' => 'О', 'п' => 'П', 'р' => 'Р', 'с' => 'С', 'т' => 'Т', 'у' => 'У', 'ф' => 'Ф', 'х' => 'Х', 'ц' => 'Ц', 'ч' => 'Ч', 'ш' => 'Ш', 'щ' => 'Щ', 'ъ' => 'Ъ', 'ы' => 'Ы', 'ь' => 'Ь', 'э' => 'Э', 'ю' => 'Ю', 'я' => 'Я', 'ѐ' => 'Ѐ', 'ё' => 'Ё', 'ђ' => 'Ђ', 'ѓ' => 'Ѓ', 'є' => 'Є', 'ѕ' => 'Ѕ', 'і' => 'І', 'ї' => 'Ї', 'ј' => 'Ј', 'љ' => 'Љ', 'њ' => 'Њ', 'ћ' => 'Ћ', 'ќ' => 'Ќ', 'ѝ' => 'Ѝ', 'ў' => 'Ў', 'џ' => 'Џ', 'ѡ' => 'Ѡ', 'ѣ' => 'Ѣ', 'ѥ' => 'Ѥ', 'ѧ' => 'Ѧ', 'ѩ' => 'Ѩ', 'ѫ' => 'Ѫ', 'ѭ' => 'Ѭ', 'ѯ' => 'Ѯ', 'ѱ' => 'Ѱ', 'ѳ' => 'Ѳ', 'ѵ' => 'Ѵ', 'ѷ' => 'Ѷ', 'ѹ' => 'Ѹ', 'ѻ' => 'Ѻ', 'ѽ' => 'Ѽ', 'ѿ' => 'Ѿ', 'ҁ' => 'Ҁ', 'ҋ' => 'Ҋ', 'ҍ' => 'Ҍ', 'ҏ' => 'Ҏ', 'ґ' => 'Ґ', 'ғ' => 'Ғ', 'ҕ' => 'Ҕ', 'җ' => 'Җ', 'ҙ' => 'Ҙ', 'қ' => 'Қ', 'ҝ' => 'Ҝ', 'ҟ' => 'Ҟ', 'ҡ' => 'Ҡ', 'ң' => 'Ң', 'ҥ' => 'Ҥ', 'ҧ' => 'Ҧ', 'ҩ' => 'Ҩ', 'ҫ' => 'Ҫ', 'ҭ' => 'Ҭ', 'ү' => 'Ү', 'ұ' => 'Ұ', 'ҳ' => 'Ҳ', 'ҵ' => 'Ҵ', 'ҷ' => 'Ҷ', 'ҹ' => 'Ҹ', 'һ' => 'Һ', 'ҽ' => 'Ҽ', 'ҿ' => 'Ҿ', 'ӂ' => 'Ӂ', 'ӄ' => 'Ӄ', 'ӆ' => 'Ӆ', 'ӈ' => 'Ӈ', 'ӊ' => 'Ӊ', 'ӌ' => 'Ӌ', 'ӎ' => 'Ӎ', 'ӏ' => 'Ӏ', 'ӑ' => 'Ӑ', 'ӓ' => 'Ӓ', 'ӕ' => 'Ӕ', 'ӗ' => 'Ӗ', 'ә' => 'Ә', 'ӛ' => 'Ӛ', 'ӝ' => 'Ӝ', 'ӟ' => 'Ӟ', 'ӡ' => 'Ӡ', 'ӣ' => 'Ӣ', 'ӥ' => 'Ӥ', 'ӧ' => 'Ӧ', 'ө' => 'Ө', 'ӫ' => 'Ӫ', 'ӭ' => 'Ӭ', 'ӯ' => 'Ӯ', 'ӱ' => 'Ӱ', 'ӳ' => 'Ӳ', 'ӵ' => 'Ӵ', 'ӷ' => 'Ӷ', 'ӹ' => 'Ӹ', 'ӻ' => 'Ӻ', 'ӽ' => 'Ӽ', 'ӿ' => 'Ӿ', 'ԁ' => 'Ԁ', 'ԃ' => 'Ԃ', 'ԅ' => 'Ԅ', 'ԇ' => 'Ԇ', 'ԉ' => 'Ԉ', 'ԋ' => 'Ԋ', 'ԍ' => 'Ԍ', 'ԏ' => 'Ԏ', 'ԑ' => 'Ԑ', 'ԓ' => 'Ԓ', 'ԕ' => 'Ԕ', 'ԗ' => 'Ԗ', 'ԙ' => 'Ԙ', 'ԛ' => 'Ԛ', 'ԝ' => 'Ԝ', 'ԟ' => 'Ԟ', 'ԡ' => 'Ԡ', 'ԣ' => 'Ԣ', 'ԥ' => 'Ԥ', 'ԧ' => 'Ԧ', 'ԩ' => 'Ԩ', 'ԫ' => 'Ԫ', 'ԭ' => 'Ԭ', 'ԯ' => 'Ԯ', 'ա' => 'Ա', 'բ' => 'Բ', 'գ' => 'Գ', 'դ' => 'Դ', 'ե' => 'Ե', 'զ' => 'Զ', 'է' => 'Է', 'ը' => 'Ը', 'թ' => 'Թ', 'ժ' => 'Ժ', 'ի' => 'Ի', 'լ' => 'Լ', 'խ' => 'Խ', 'ծ' => 'Ծ', 'կ' => 'Կ', 'հ' => 'Հ', 'ձ' => 'Ձ', 'ղ' => 'Ղ', 'ճ' => 'Ճ', 'մ' => 'Մ', 'յ' => 'Յ', 'ն' => 'Ն', 'շ' => 'Շ', 'ո' => 'Ո', 'չ' => 'Չ', 'պ' => 'Պ', 'ջ' => 'Ջ', 'ռ' => 'Ռ', 'ս' => 'Ս', 'վ' => 'Վ', 'տ' => 'Տ', 'ր' => 'Ր', 'ց' => 'Ց', 'ւ' => 'Ւ', 'փ' => 'Փ', 'ք' => 'Ք', 'օ' => 'Օ', 'ֆ' => 'Ֆ', 'ა' => 'Ა', 'ბ' => 'Ბ', 'გ' => 'Გ', 'დ' => 'Დ', 'ე' => 'Ე', 'ვ' => 'Ვ', 'ზ' => 'Ზ', 'თ' => 'Თ', 'ი' => 'Ი', 'კ' => 'Კ', 'ლ' => 'Ლ', 'მ' => 'Მ', 'ნ' => 'Ნ', 'ო' => 'Ო', 'პ' => 'Პ', 'ჟ' => 'Ჟ', 'რ' => 'Რ', 'ს' => 'Ს', 'ტ' => 'Ტ', 'უ' => 'Უ', 'ფ' => 'Ფ', 'ქ' => 'Ქ', 'ღ' => 'Ღ', 'ყ' => 'Ყ', 'შ' => 'Შ', 'ჩ' => 'Ჩ', 'ც' => 'Ც', 'ძ' => 'Ძ', 'წ' => 'Წ', 'ჭ' => 'Ჭ', 'ხ' => 'Ხ', 'ჯ' => 'Ჯ', 'ჰ' => 'Ჰ', 'ჱ' => 'Ჱ', 'ჲ' => 'Ჲ', 'ჳ' => 'Ჳ', 'ჴ' => 'Ჴ', 'ჵ' => 'Ჵ', 'ჶ' => 'Ჶ', 'ჷ' => 'Ჷ', 'ჸ' => 'Ჸ', 'ჹ' => 'Ჹ', 'ჺ' => 'Ჺ', 'ჽ' => 'Ჽ', 'ჾ' => 'Ჾ', 'ჿ' => 'Ჿ', 'ᏸ' => 'Ᏸ', 'ᏹ' => 'Ᏹ', 'ᏺ' => 'Ᏺ', 'ᏻ' => 'Ᏻ', 'ᏼ' => 'Ᏼ', 'ᏽ' => 'Ᏽ', 'ᲀ' => 'В', 'ᲁ' => 'Д', 'ᲂ' => 'О', 'ᲃ' => 'С', 'ᲄ' => 'Т', 'ᲅ' => 'Т', 'ᲆ' => 'Ъ', 'ᲇ' => 'Ѣ', 'ᲈ' => 'Ꙋ', 'ᵹ' => 'Ᵹ', 'ᵽ' => 'Ᵽ', 'ᶎ' => 'Ᶎ', 'ḁ' => 'Ḁ', 'ḃ' => 'Ḃ', 'ḅ' => 'Ḅ', 'ḇ' => 'Ḇ', 'ḉ' => 'Ḉ', 'ḋ' => 'Ḋ', 'ḍ' => 'Ḍ', 'ḏ' => 'Ḏ', 'ḑ' => 'Ḑ', 'ḓ' => 'Ḓ', 'ḕ' => 'Ḕ', 'ḗ' => 'Ḗ', 'ḙ' => 'Ḙ', 'ḛ' => 'Ḛ', 'ḝ' => 'Ḝ', 'ḟ' => 'Ḟ', 'ḡ' => 'Ḡ', 'ḣ' => 'Ḣ', 'ḥ' => 'Ḥ', 'ḧ' => 'Ḧ', 'ḩ' => 'Ḩ', 'ḫ' => 'Ḫ', 'ḭ' => 'Ḭ', 'ḯ' => 'Ḯ', 'ḱ' => 'Ḱ', 'ḳ' => 'Ḳ', 'ḵ' => 'Ḵ', 'ḷ' => 'Ḷ', 'ḹ' => 'Ḹ', 'ḻ' => 'Ḻ', 'ḽ' => 'Ḽ', 'ḿ' => 'Ḿ', 'ṁ' => 'Ṁ', 'ṃ' => 'Ṃ', 'ṅ' => 'Ṅ', 'ṇ' => 'Ṇ', 'ṉ' => 'Ṉ', 'ṋ' => 'Ṋ', 'ṍ' => 'Ṍ', 'ṏ' => 'Ṏ', 'ṑ' => 'Ṑ', 'ṓ' => 'Ṓ', 'ṕ' => 'Ṕ', 'ṗ' => 'Ṗ', 'ṙ' => 'Ṙ', 'ṛ' => 'Ṛ', 'ṝ' => 'Ṝ', 'ṟ' => 'Ṟ', 'ṡ' => 'Ṡ', 'ṣ' => 'Ṣ', 'ṥ' => 'Ṥ', 'ṧ' => 'Ṧ', 'ṩ' => 'Ṩ', 'ṫ' => 'Ṫ', 'ṭ' => 'Ṭ', 'ṯ' => 'Ṯ', 'ṱ' => 'Ṱ', 'ṳ' => 'Ṳ', 'ṵ' => 'Ṵ', 'ṷ' => 'Ṷ', 'ṹ' => 'Ṹ', 'ṻ' => 'Ṻ', 'ṽ' => 'Ṽ', 'ṿ' => 'Ṿ', 'ẁ' => 'Ẁ', 'ẃ' => 'Ẃ', 'ẅ' => 'Ẅ', 'ẇ' => 'Ẇ', 'ẉ' => 'Ẉ', 'ẋ' => 'Ẋ', 'ẍ' => 'Ẍ', 'ẏ' => 'Ẏ', 'ẑ' => 'Ẑ', 'ẓ' => 'Ẓ', 'ẕ' => 'Ẕ', 'ẛ' => 'Ṡ', 'ạ' => 'Ạ', 'ả' => 'Ả', 'ấ' => 'Ấ', 'ầ' => 'Ầ', 'ẩ' => 'Ẩ', 'ẫ' => 'Ẫ', 'ậ' => 'Ậ', 'ắ' => 'Ắ', 'ằ' => 'Ằ', 'ẳ' => 'Ẳ', 'ẵ' => 'Ẵ', 'ặ' => 'Ặ', 'ẹ' => 'Ẹ', 'ẻ' => 'Ẻ', 'ẽ' => 'Ẽ', 'ế' => 'Ế', 'ề' => 'Ề', 'ể' => 'Ể', 'ễ' => 'Ễ', 'ệ' => 'Ệ', 'ỉ' => 'Ỉ', 'ị' => 'Ị', 'ọ' => 'Ọ', 'ỏ' => 'Ỏ', 'ố' => 'Ố', 'ồ' => 'Ồ', 'ổ' => 'Ổ', 'ỗ' => 'Ỗ', 'ộ' => 'Ộ', 'ớ' => 'Ớ', 'ờ' => 'Ờ', 'ở' => 'Ở', 'ỡ' => 'Ỡ', 'ợ' => 'Ợ', 'ụ' => 'Ụ', 'ủ' => 'Ủ', 'ứ' => 'Ứ', 'ừ' => 'Ừ', 'ử' => 'Ử', 'ữ' => 'Ữ', 'ự' => 'Ự', 'ỳ' => 'Ỳ', 'ỵ' => 'Ỵ', 'ỷ' => 'Ỷ', 'ỹ' => 'Ỹ', 'ỻ' => 'Ỻ', 'ỽ' => 'Ỽ', 'ỿ' => 'Ỿ', 'ἀ' => 'Ἀ', 'ἁ' => 'Ἁ', 'ἂ' => 'Ἂ', 'ἃ' => 'Ἃ', 'ἄ' => 'Ἄ', 'ἅ' => 'Ἅ', 'ἆ' => 'Ἆ', 'ἇ' => 'Ἇ', 'ἐ' => 'Ἐ', 'ἑ' => 'Ἑ', 'ἒ' => 'Ἒ', 'ἓ' => 'Ἓ', 'ἔ' => 'Ἔ', 'ἕ' => 'Ἕ', 'ἠ' => 'Ἠ', 'ἡ' => 'Ἡ', 'ἢ' => 'Ἢ', 'ἣ' => 'Ἣ', 'ἤ' => 'Ἤ', 'ἥ' => 'Ἥ', 'ἦ' => 'Ἦ', 'ἧ' => 'Ἧ', 'ἰ' => 'Ἰ', 'ἱ' => 'Ἱ', 'ἲ' => 'Ἲ', 'ἳ' => 'Ἳ', 'ἴ' => 'Ἴ', 'ἵ' => 'Ἵ', 'ἶ' => 'Ἶ', 'ἷ' => 'Ἷ', 'ὀ' => 'Ὀ', 'ὁ' => 'Ὁ', 'ὂ' => 'Ὂ', 'ὃ' => 'Ὃ', 'ὄ' => 'Ὄ', 'ὅ' => 'Ὅ', 'ὑ' => 'Ὑ', 'ὓ' => 'Ὓ', 'ὕ' => 'Ὕ', 'ὗ' => 'Ὗ', 'ὠ' => 'Ὠ', 'ὡ' => 'Ὡ', 'ὢ' => 'Ὢ', 'ὣ' => 'Ὣ', 'ὤ' => 'Ὤ', 'ὥ' => 'Ὥ', 'ὦ' => 'Ὦ', 'ὧ' => 'Ὧ', 'ὰ' => 'Ὰ', 'ά' => 'Ά', 'ὲ' => 'Ὲ', 'έ' => 'Έ', 'ὴ' => 'Ὴ', 'ή' => 'Ή', 'ὶ' => 'Ὶ', 'ί' => 'Ί', 'ὸ' => 'Ὸ', 'ό' => 'Ό', 'ὺ' => 'Ὺ', 'ύ' => 'Ύ', 'ὼ' => 'Ὼ', 'ώ' => 'Ώ', 'ᾀ' => 'ἈΙ', 'ᾁ' => 'ἉΙ', 'ᾂ' => 'ἊΙ', 'ᾃ' => 'ἋΙ', 'ᾄ' => 'ἌΙ', 'ᾅ' => 'ἍΙ', 'ᾆ' => 'ἎΙ', 'ᾇ' => 'ἏΙ', 'ᾐ' => 'ἨΙ', 'ᾑ' => 'ἩΙ', 'ᾒ' => 'ἪΙ', 'ᾓ' => 'ἫΙ', 'ᾔ' => 'ἬΙ', 'ᾕ' => 'ἭΙ', 'ᾖ' => 'ἮΙ', 'ᾗ' => 'ἯΙ', 'ᾠ' => 'ὨΙ', 'ᾡ' => 'ὩΙ', 'ᾢ' => 'ὪΙ', 'ᾣ' => 'ὫΙ', 'ᾤ' => 'ὬΙ', 'ᾥ' => 'ὭΙ', 'ᾦ' => 'ὮΙ', 'ᾧ' => 'ὯΙ', 'ᾰ' => 'Ᾰ', 'ᾱ' => 'Ᾱ', 'ᾳ' => 'ΑΙ', 'ι' => 'Ι', 'ῃ' => 'ΗΙ', 'ῐ' => 'Ῐ', 'ῑ' => 'Ῑ', 'ῠ' => 'Ῠ', 'ῡ' => 'Ῡ', 'ῥ' => 'Ῥ', 'ῳ' => 'ΩΙ', 'ⅎ' => 'Ⅎ', 'ⅰ' => 'Ⅰ', 'ⅱ' => 'Ⅱ', 'ⅲ' => 'Ⅲ', 'ⅳ' => 'Ⅳ', 'ⅴ' => 'Ⅴ', 'ⅵ' => 'Ⅵ', 'ⅶ' => 'Ⅶ', 'ⅷ' => 'Ⅷ', 'ⅸ' => 'Ⅸ', 'ⅹ' => 'Ⅹ', 'ⅺ' => 'Ⅺ', 'ⅻ' => 'Ⅻ', 'ⅼ' => 'Ⅼ', 'ⅽ' => 'Ⅽ', 'ⅾ' => 'Ⅾ', 'ⅿ' => 'Ⅿ', 'ↄ' => 'Ↄ', 'ⓐ' => 'Ⓐ', 'ⓑ' => 'Ⓑ', 'ⓒ' => 'Ⓒ', 'ⓓ' => 'Ⓓ', 'ⓔ' => 'Ⓔ', 'ⓕ' => 'Ⓕ', 'ⓖ' => 'Ⓖ', 'ⓗ' => 'Ⓗ', 'ⓘ' => 'Ⓘ', 'ⓙ' => 'Ⓙ', 'ⓚ' => 'Ⓚ', 'ⓛ' => 'Ⓛ', 'ⓜ' => 'Ⓜ', 'ⓝ' => 'Ⓝ', 'ⓞ' => 'Ⓞ', 'ⓟ' => 'Ⓟ', 'ⓠ' => 'Ⓠ', 'ⓡ' => 'Ⓡ', 'ⓢ' => 'Ⓢ', 'ⓣ' => 'Ⓣ', 'ⓤ' => 'Ⓤ', 'ⓥ' => 'Ⓥ', 'ⓦ' => 'Ⓦ', 'ⓧ' => 'Ⓧ', 'ⓨ' => 'Ⓨ', 'ⓩ' => 'Ⓩ', 'ⰰ' => 'Ⰰ', 'ⰱ' => 'Ⰱ', 'ⰲ' => 'Ⰲ', 'ⰳ' => 'Ⰳ', 'ⰴ' => 'Ⰴ', 'ⰵ' => 'Ⰵ', 'ⰶ' => 'Ⰶ', 'ⰷ' => 'Ⰷ', 'ⰸ' => 'Ⰸ', 'ⰹ' => 'Ⰹ', 'ⰺ' => 'Ⰺ', 'ⰻ' => 'Ⰻ', 'ⰼ' => 'Ⰼ', 'ⰽ' => 'Ⰽ', 'ⰾ' => 'Ⰾ', 'ⰿ' => 'Ⰿ', 'ⱀ' => 'Ⱀ', 'ⱁ' => 'Ⱁ', 'ⱂ' => 'Ⱂ', 'ⱃ' => 'Ⱃ', 'ⱄ' => 'Ⱄ', 'ⱅ' => 'Ⱅ', 'ⱆ' => 'Ⱆ', 'ⱇ' => 'Ⱇ', 'ⱈ' => 'Ⱈ', 'ⱉ' => 'Ⱉ', 'ⱊ' => 'Ⱊ', 'ⱋ' => 'Ⱋ', 'ⱌ' => 'Ⱌ', 'ⱍ' => 'Ⱍ', 'ⱎ' => 'Ⱎ', 'ⱏ' => 'Ⱏ', 'ⱐ' => 'Ⱐ', 'ⱑ' => 'Ⱑ', 'ⱒ' => 'Ⱒ', 'ⱓ' => 'Ⱓ', 'ⱔ' => 'Ⱔ', 'ⱕ' => 'Ⱕ', 'ⱖ' => 'Ⱖ', 'ⱗ' => 'Ⱗ', 'ⱘ' => 'Ⱘ', 'ⱙ' => 'Ⱙ', 'ⱚ' => 'Ⱚ', 'ⱛ' => 'Ⱛ', 'ⱜ' => 'Ⱜ', 'ⱝ' => 'Ⱝ', 'ⱞ' => 'Ⱞ', 'ⱡ' => 'Ⱡ', 'ⱥ' => 'Ⱥ', 'ⱦ' => 'Ⱦ', 'ⱨ' => 'Ⱨ', 'ⱪ' => 'Ⱪ', 'ⱬ' => 'Ⱬ', 'ⱳ' => 'Ⱳ', 'ⱶ' => 'Ⱶ', 'ⲁ' => 'Ⲁ', 'ⲃ' => 'Ⲃ', 'ⲅ' => 'Ⲅ', 'ⲇ' => 'Ⲇ', 'ⲉ' => 'Ⲉ', 'ⲋ' => 'Ⲋ', 'ⲍ' => 'Ⲍ', 'ⲏ' => 'Ⲏ', 'ⲑ' => 'Ⲑ', 'ⲓ' => 'Ⲓ', 'ⲕ' => 'Ⲕ', 'ⲗ' => 'Ⲗ', 'ⲙ' => 'Ⲙ', 'ⲛ' => 'Ⲛ', 'ⲝ' => 'Ⲝ', 'ⲟ' => 'Ⲟ', 'ⲡ' => 'Ⲡ', 'ⲣ' => 'Ⲣ', 'ⲥ' => 'Ⲥ', 'ⲧ' => 'Ⲧ', 'ⲩ' => 'Ⲩ', 'ⲫ' => 'Ⲫ', 'ⲭ' => 'Ⲭ', 'ⲯ' => 'Ⲯ', 'ⲱ' => 'Ⲱ', 'ⲳ' => 'Ⲳ', 'ⲵ' => 'Ⲵ', 'ⲷ' => 'Ⲷ', 'ⲹ' => 'Ⲹ', 'ⲻ' => 'Ⲻ', 'ⲽ' => 'Ⲽ', 'ⲿ' => 'Ⲿ', 'ⳁ' => 'Ⳁ', 'ⳃ' => 'Ⳃ', 'ⳅ' => 'Ⳅ', 'ⳇ' => 'Ⳇ', 'ⳉ' => 'Ⳉ', 'ⳋ' => 'Ⳋ', 'ⳍ' => 'Ⳍ', 'ⳏ' => 'Ⳏ', 'ⳑ' => 'Ⳑ', 'ⳓ' => 'Ⳓ', 'ⳕ' => 'Ⳕ', 'ⳗ' => 'Ⳗ', 'ⳙ' => 'Ⳙ', 'ⳛ' => 'Ⳛ', 'ⳝ' => 'Ⳝ', 'ⳟ' => 'Ⳟ', 'ⳡ' => 'Ⳡ', 'ⳣ' => 'Ⳣ', 'ⳬ' => 'Ⳬ', 'ⳮ' => 'Ⳮ', 'ⳳ' => 'Ⳳ', 'ⴀ' => 'Ⴀ', 'ⴁ' => 'Ⴁ', 'ⴂ' => 'Ⴂ', 'ⴃ' => 'Ⴃ', 'ⴄ' => 'Ⴄ', 'ⴅ' => 'Ⴅ', 'ⴆ' => 'Ⴆ', 'ⴇ' => 'Ⴇ', 'ⴈ' => 'Ⴈ', 'ⴉ' => 'Ⴉ', 'ⴊ' => 'Ⴊ', 'ⴋ' => 'Ⴋ', 'ⴌ' => 'Ⴌ', 'ⴍ' => 'Ⴍ', 'ⴎ' => 'Ⴎ', 'ⴏ' => 'Ⴏ', 'ⴐ' => 'Ⴐ', 'ⴑ' => 'Ⴑ', 'ⴒ' => 'Ⴒ', 'ⴓ' => 'Ⴓ', 'ⴔ' => 'Ⴔ', 'ⴕ' => 'Ⴕ', 'ⴖ' => 'Ⴖ', 'ⴗ' => 'Ⴗ', 'ⴘ' => 'Ⴘ', 'ⴙ' => 'Ⴙ', 'ⴚ' => 'Ⴚ', 'ⴛ' => 'Ⴛ', 'ⴜ' => 'Ⴜ', 'ⴝ' => 'Ⴝ', 'ⴞ' => 'Ⴞ', 'ⴟ' => 'Ⴟ', 'ⴠ' => 'Ⴠ', 'ⴡ' => 'Ⴡ', 'ⴢ' => 'Ⴢ', 'ⴣ' => 'Ⴣ', 'ⴤ' => 'Ⴤ', 'ⴥ' => 'Ⴥ', 'ⴧ' => 'Ⴧ', 'ⴭ' => 'Ⴭ', 'ꙁ' => 'Ꙁ', 'ꙃ' => 'Ꙃ', 'ꙅ' => 'Ꙅ', 'ꙇ' => 'Ꙇ', 'ꙉ' => 'Ꙉ', 'ꙋ' => 'Ꙋ', 'ꙍ' => 'Ꙍ', 'ꙏ' => 'Ꙏ', 'ꙑ' => 'Ꙑ', 'ꙓ' => 'Ꙓ', 'ꙕ' => 'Ꙕ', 'ꙗ' => 'Ꙗ', 'ꙙ' => 'Ꙙ', 'ꙛ' => 'Ꙛ', 'ꙝ' => 'Ꙝ', 'ꙟ' => 'Ꙟ', 'ꙡ' => 'Ꙡ', 'ꙣ' => 'Ꙣ', 'ꙥ' => 'Ꙥ', 'ꙧ' => 'Ꙧ', 'ꙩ' => 'Ꙩ', 'ꙫ' => 'Ꙫ', 'ꙭ' => 'Ꙭ', 'ꚁ' => 'Ꚁ', 'ꚃ' => 'Ꚃ', 'ꚅ' => 'Ꚅ', 'ꚇ' => 'Ꚇ', 'ꚉ' => 'Ꚉ', 'ꚋ' => 'Ꚋ', 'ꚍ' => 'Ꚍ', 'ꚏ' => 'Ꚏ', 'ꚑ' => 'Ꚑ', 'ꚓ' => 'Ꚓ', 'ꚕ' => 'Ꚕ', 'ꚗ' => 'Ꚗ', 'ꚙ' => 'Ꚙ', 'ꚛ' => 'Ꚛ', 'ꜣ' => 'Ꜣ', 'ꜥ' => 'Ꜥ', 'ꜧ' => 'Ꜧ', 'ꜩ' => 'Ꜩ', 'ꜫ' => 'Ꜫ', 'ꜭ' => 'Ꜭ', 'ꜯ' => 'Ꜯ', 'ꜳ' => 'Ꜳ', 'ꜵ' => 'Ꜵ', 'ꜷ' => 'Ꜷ', 'ꜹ' => 'Ꜹ', 'ꜻ' => 'Ꜻ', 'ꜽ' => 'Ꜽ', 'ꜿ' => 'Ꜿ', 'ꝁ' => 'Ꝁ', 'ꝃ' => 'Ꝃ', 'ꝅ' => 'Ꝅ', 'ꝇ' => 'Ꝇ', 'ꝉ' => 'Ꝉ', 'ꝋ' => 'Ꝋ', 'ꝍ' => 'Ꝍ', 'ꝏ' => 'Ꝏ', 'ꝑ' => 'Ꝑ', 'ꝓ' => 'Ꝓ', 'ꝕ' => 'Ꝕ', 'ꝗ' => 'Ꝗ', 'ꝙ' => 'Ꝙ', 'ꝛ' => 'Ꝛ', 'ꝝ' => 'Ꝝ', 'ꝟ' => 'Ꝟ', 'ꝡ' => 'Ꝡ', 'ꝣ' => 'Ꝣ', 'ꝥ' => 'Ꝥ', 'ꝧ' => 'Ꝧ', 'ꝩ' => 'Ꝩ', 'ꝫ' => 'Ꝫ', 'ꝭ' => 'Ꝭ', 'ꝯ' => 'Ꝯ', 'ꝺ' => 'Ꝺ', 'ꝼ' => 'Ꝼ', 'ꝿ' => 'Ꝿ', 'ꞁ' => 'Ꞁ', 'ꞃ' => 'Ꞃ', 'ꞅ' => 'Ꞅ', 'ꞇ' => 'Ꞇ', 'ꞌ' => 'Ꞌ', 'ꞑ' => 'Ꞑ', 'ꞓ' => 'Ꞓ', 'ꞔ' => 'Ꞔ', 'ꞗ' => 'Ꞗ', 'ꞙ' => 'Ꞙ', 'ꞛ' => 'Ꞛ', 'ꞝ' => 'Ꞝ', 'ꞟ' => 'Ꞟ', 'ꞡ' => 'Ꞡ', 'ꞣ' => 'Ꞣ', 'ꞥ' => 'Ꞥ', 'ꞧ' => 'Ꞧ', 'ꞩ' => 'Ꞩ', 'ꞵ' => 'Ꞵ', 'ꞷ' => 'Ꞷ', 'ꞹ' => 'Ꞹ', 'ꞻ' => 'Ꞻ', 'ꞽ' => 'Ꞽ', 'ꞿ' => 'Ꞿ', 'ꟃ' => 'Ꟃ', 'ꟈ' => 'Ꟈ', 'ꟊ' => 'Ꟊ', 'ꟶ' => 'Ꟶ', 'ꭓ' => 'Ꭓ', 'ꭰ' => 'Ꭰ', 'ꭱ' => 'Ꭱ', 'ꭲ' => 'Ꭲ', 'ꭳ' => 'Ꭳ', 'ꭴ' => 'Ꭴ', 'ꭵ' => 'Ꭵ', 'ꭶ' => 'Ꭶ', 'ꭷ' => 'Ꭷ', 'ꭸ' => 'Ꭸ', 'ꭹ' => 'Ꭹ', 'ꭺ' => 'Ꭺ', 'ꭻ' => 'Ꭻ', 'ꭼ' => 'Ꭼ', 'ꭽ' => 'Ꭽ', 'ꭾ' => 'Ꭾ', 'ꭿ' => 'Ꭿ', 'ꮀ' => 'Ꮀ', 'ꮁ' => 'Ꮁ', 'ꮂ' => 'Ꮂ', 'ꮃ' => 'Ꮃ', 'ꮄ' => 'Ꮄ', 'ꮅ' => 'Ꮅ', 'ꮆ' => 'Ꮆ', 'ꮇ' => 'Ꮇ', 'ꮈ' => 'Ꮈ', 'ꮉ' => 'Ꮉ', 'ꮊ' => 'Ꮊ', 'ꮋ' => 'Ꮋ', 'ꮌ' => 'Ꮌ', 'ꮍ' => 'Ꮍ', 'ꮎ' => 'Ꮎ', 'ꮏ' => 'Ꮏ', 'ꮐ' => 'Ꮐ', 'ꮑ' => 'Ꮑ', 'ꮒ' => 'Ꮒ', 'ꮓ' => 'Ꮓ', 'ꮔ' => 'Ꮔ', 'ꮕ' => 'Ꮕ', 'ꮖ' => 'Ꮖ', 'ꮗ' => 'Ꮗ', 'ꮘ' => 'Ꮘ', 'ꮙ' => 'Ꮙ', 'ꮚ' => 'Ꮚ', 'ꮛ' => 'Ꮛ', 'ꮜ' => 'Ꮜ', 'ꮝ' => 'Ꮝ', 'ꮞ' => 'Ꮞ', 'ꮟ' => 'Ꮟ', 'ꮠ' => 'Ꮠ', 'ꮡ' => 'Ꮡ', 'ꮢ' => 'Ꮢ', 'ꮣ' => 'Ꮣ', 'ꮤ' => 'Ꮤ', 'ꮥ' => 'Ꮥ', 'ꮦ' => 'Ꮦ', 'ꮧ' => 'Ꮧ', 'ꮨ' => 'Ꮨ', 'ꮩ' => 'Ꮩ', 'ꮪ' => 'Ꮪ', 'ꮫ' => 'Ꮫ', 'ꮬ' => 'Ꮬ', 'ꮭ' => 'Ꮭ', 'ꮮ' => 'Ꮮ', 'ꮯ' => 'Ꮯ', 'ꮰ' => 'Ꮰ', 'ꮱ' => 'Ꮱ', 'ꮲ' => 'Ꮲ', 'ꮳ' => 'Ꮳ', 'ꮴ' => 'Ꮴ', 'ꮵ' => 'Ꮵ', 'ꮶ' => 'Ꮶ', 'ꮷ' => 'Ꮷ', 'ꮸ' => 'Ꮸ', 'ꮹ' => 'Ꮹ', 'ꮺ' => 'Ꮺ', 'ꮻ' => 'Ꮻ', 'ꮼ' => 'Ꮼ', 'ꮽ' => 'Ꮽ', 'ꮾ' => 'Ꮾ', 'ꮿ' => 'Ꮿ', 'a' => 'A', 'b' => 'B', 'c' => 'C', 'd' => 'D', 'e' => 'E', 'f' => 'F', 'g' => 'G', 'h' => 'H', 'i' => 'I', 'j' => 'J', 'k' => 'K', 'l' => 'L', 'm' => 'M', 'n' => 'N', 'o' => 'O', 'p' => 'P', 'q' => 'Q', 'r' => 'R', 's' => 'S', 't' => 'T', 'u' => 'U', 'v' => 'V', 'w' => 'W', 'x' => 'X', 'y' => 'Y', 'z' => 'Z', '𐐨' => '𐐀', '𐐩' => '𐐁', '𐐪' => '𐐂', '𐐫' => '𐐃', '𐐬' => '𐐄', '𐐭' => '𐐅', '𐐮' => '𐐆', '𐐯' => '𐐇', '𐐰' => '𐐈', '𐐱' => '𐐉', '𐐲' => '𐐊', '𐐳' => '𐐋', '𐐴' => '𐐌', '𐐵' => '𐐍', '𐐶' => '𐐎', '𐐷' => '𐐏', '𐐸' => '𐐐', '𐐹' => '𐐑', '𐐺' => '𐐒', '𐐻' => '𐐓', '𐐼' => '𐐔', '𐐽' => '𐐕', '𐐾' => '𐐖', '𐐿' => '𐐗', '𐑀' => '𐐘', '𐑁' => '𐐙', '𐑂' => '𐐚', '𐑃' => '𐐛', '𐑄' => '𐐜', '𐑅' => '𐐝', '𐑆' => '𐐞', '𐑇' => '𐐟', '𐑈' => '𐐠', '𐑉' => '𐐡', '𐑊' => '𐐢', '𐑋' => '𐐣', '𐑌' => '𐐤', '𐑍' => '𐐥', '𐑎' => '𐐦', '𐑏' => '𐐧', '𐓘' => '𐒰', '𐓙' => '𐒱', '𐓚' => '𐒲', '𐓛' => '𐒳', '𐓜' => '𐒴', '𐓝' => '𐒵', '𐓞' => '𐒶', '𐓟' => '𐒷', '𐓠' => '𐒸', '𐓡' => '𐒹', '𐓢' => '𐒺', '𐓣' => '𐒻', '𐓤' => '𐒼', '𐓥' => '𐒽', '𐓦' => '𐒾', '𐓧' => '𐒿', '𐓨' => '𐓀', '𐓩' => '𐓁', '𐓪' => '𐓂', '𐓫' => '𐓃', '𐓬' => '𐓄', '𐓭' => '𐓅', '𐓮' => '𐓆', '𐓯' => '𐓇', '𐓰' => '𐓈', '𐓱' => '𐓉', '𐓲' => '𐓊', '𐓳' => '𐓋', '𐓴' => '𐓌', '𐓵' => '𐓍', '𐓶' => '𐓎', '𐓷' => '𐓏', '𐓸' => '𐓐', '𐓹' => '𐓑', '𐓺' => '𐓒', '𐓻' => '𐓓', '𐳀' => '𐲀', '𐳁' => '𐲁', '𐳂' => '𐲂', '𐳃' => '𐲃', '𐳄' => '𐲄', '𐳅' => '𐲅', '𐳆' => '𐲆', '𐳇' => '𐲇', '𐳈' => '𐲈', '𐳉' => '𐲉', '𐳊' => '𐲊', '𐳋' => '𐲋', '𐳌' => '𐲌', '𐳍' => '𐲍', '𐳎' => '𐲎', '𐳏' => '𐲏', '𐳐' => '𐲐', '𐳑' => '𐲑', '𐳒' => '𐲒', '𐳓' => '𐲓', '𐳔' => '𐲔', '𐳕' => '𐲕', '𐳖' => '𐲖', '𐳗' => '𐲗', '𐳘' => '𐲘', '𐳙' => '𐲙', '𐳚' => '𐲚', '𐳛' => '𐲛', '𐳜' => '𐲜', '𐳝' => '𐲝', '𐳞' => '𐲞', '𐳟' => '𐲟', '𐳠' => '𐲠', '𐳡' => '𐲡', '𐳢' => '𐲢', '𐳣' => '𐲣', '𐳤' => '𐲤', '𐳥' => '𐲥', '𐳦' => '𐲦', '𐳧' => '𐲧', '𐳨' => '𐲨', '𐳩' => '𐲩', '𐳪' => '𐲪', '𐳫' => '𐲫', '𐳬' => '𐲬', '𐳭' => '𐲭', '𐳮' => '𐲮', '𐳯' => '𐲯', '𐳰' => '𐲰', '𐳱' => '𐲱', '𐳲' => '𐲲', '𑣀' => '𑢠', '𑣁' => '𑢡', '𑣂' => '𑢢', '𑣃' => '𑢣', '𑣄' => '𑢤', '𑣅' => '𑢥', '𑣆' => '𑢦', '𑣇' => '𑢧', '𑣈' => '𑢨', '𑣉' => '𑢩', '𑣊' => '𑢪', '𑣋' => '𑢫', '𑣌' => '𑢬', '𑣍' => '𑢭', '𑣎' => '𑢮', '𑣏' => '𑢯', '𑣐' => '𑢰', '𑣑' => '𑢱', '𑣒' => '𑢲', '𑣓' => '𑢳', '𑣔' => '𑢴', '𑣕' => '𑢵', '𑣖' => '𑢶', '𑣗' => '𑢷', '𑣘' => '𑢸', '𑣙' => '𑢹', '𑣚' => '𑢺', '𑣛' => '𑢻', '𑣜' => '𑢼', '𑣝' => '𑢽', '𑣞' => '𑢾', '𑣟' => '𑢿', '𖹠' => '𖹀', '𖹡' => '𖹁', '𖹢' => '𖹂', '𖹣' => '𖹃', '𖹤' => '𖹄', '𖹥' => '𖹅', '𖹦' => '𖹆', '𖹧' => '𖹇', '𖹨' => '𖹈', '𖹩' => '𖹉', '𖹪' => '𖹊', '𖹫' => '𖹋', '𖹬' => '𖹌', '𖹭' => '𖹍', '𖹮' => '𖹎', '𖹯' => '𖹏', '𖹰' => '𖹐', '𖹱' => '𖹑', '𖹲' => '𖹒', '𖹳' => '𖹓', '𖹴' => '𖹔', '𖹵' => '𖹕', '𖹶' => '𖹖', '𖹷' => '𖹗', '𖹸' => '𖹘', '𖹹' => '𖹙', '𖹺' => '𖹚', '𖹻' => '𖹛', '𖹼' => '𖹜', '𖹽' => '𖹝', '𖹾' => '𖹞', '𖹿' => '𖹟', '𞤢' => '𞤀', '𞤣' => '𞤁', '𞤤' => '𞤂', '𞤥' => '𞤃', '𞤦' => '𞤄', '𞤧' => '𞤅', '𞤨' => '𞤆', '𞤩' => '𞤇', '𞤪' => '𞤈', '𞤫' => '𞤉', '𞤬' => '𞤊', '𞤭' => '𞤋', '𞤮' => '𞤌', '𞤯' => '𞤍', '𞤰' => '𞤎', '𞤱' => '𞤏', '𞤲' => '𞤐', '𞤳' => '𞤑', '𞤴' => '𞤒', '𞤵' => '𞤓', '𞤶' => '𞤔', '𞤷' => '𞤕', '𞤸' => '𞤖', '𞤹' => '𞤗', '𞤺' => '𞤘', '𞤻' => '𞤙', '𞤼' => '𞤚', '𞤽' => '𞤛', '𞤾' => '𞤜', '𞤿' => '𞤝', '𞥀' => '𞤞', '𞥁' => '𞤟', '𞥂' => '𞤠', '𞥃' => '𞤡', 'ß' => 'SS', 'ff' => 'FF', 'fi' => 'FI', 'fl' => 'FL', 'ffi' => 'FFI', 'ffl' => 'FFL', 'ſt' => 'ST', 'st' => 'ST', 'և' => 'ԵՒ', 'ﬓ' => 'ՄՆ', 'ﬔ' => 'ՄԵ', 'ﬕ' => 'ՄԻ', 'ﬖ' => 'ՎՆ', 'ﬗ' => 'ՄԽ', 'ʼn' => 'ʼN', 'ΐ' => 'Ϊ́', 'ΰ' => 'Ϋ́', 'ǰ' => 'J̌', 'ẖ' => 'H̱', 'ẗ' => 'T̈', 'ẘ' => 'W̊', 'ẙ' => 'Y̊', 'ẚ' => 'Aʾ', 'ὐ' => 'Υ̓', 'ὒ' => 'Υ̓̀', 'ὔ' => 'Υ̓́', 'ὖ' => 'Υ̓͂', 'ᾶ' => 'Α͂', 'ῆ' => 'Η͂', 'ῒ' => 'Ϊ̀', 'ΐ' => 'Ϊ́', 'ῖ' => 'Ι͂', 'ῗ' => 'Ϊ͂', 'ῢ' => 'Ϋ̀', 'ΰ' => 'Ϋ́', 'ῤ' => 'Ρ̓', 'ῦ' => 'Υ͂', 'ῧ' => 'Ϋ͂', 'ῶ' => 'Ω͂', 'ᾈ' => 'ἈΙ', 'ᾉ' => 'ἉΙ', 'ᾊ' => 'ἊΙ', 'ᾋ' => 'ἋΙ', 'ᾌ' => 'ἌΙ', 'ᾍ' => 'ἍΙ', 'ᾎ' => 'ἎΙ', 'ᾏ' => 'ἏΙ', 'ᾘ' => 'ἨΙ', 'ᾙ' => 'ἩΙ', 'ᾚ' => 'ἪΙ', 'ᾛ' => 'ἫΙ', 'ᾜ' => 'ἬΙ', 'ᾝ' => 'ἭΙ', 'ᾞ' => 'ἮΙ', 'ᾟ' => 'ἯΙ', 'ᾨ' => 'ὨΙ', 'ᾩ' => 'ὩΙ', 'ᾪ' => 'ὪΙ', 'ᾫ' => 'ὫΙ', 'ᾬ' => 'ὬΙ', 'ᾭ' => 'ὭΙ', 'ᾮ' => 'ὮΙ', 'ᾯ' => 'ὯΙ', 'ᾼ' => 'ΑΙ', 'ῌ' => 'ΗΙ', 'ῼ' => 'ΩΙ', 'ᾲ' => 'ᾺΙ', 'ᾴ' => 'ΆΙ', 'ῂ' => 'ῊΙ', 'ῄ' => 'ΉΙ', 'ῲ' => 'ῺΙ', 'ῴ' => 'ΏΙ', 'ᾷ' => 'Α͂Ι', 'ῇ' => 'Η͂Ι', 'ῷ' => 'Ω͂Ι', ); = 80000) { return require __DIR__.'/bootstrap80.php'; } return require __DIR__.'/bootstrap72.php'; = 70300) { return; } if (!function_exists('is_countable')) { function is_countable($value) { return is_array($value) || $value instanceof Countable || $value instanceof ResourceBundle || $value instanceof SimpleXmlElement; } } if (!function_exists('hrtime')) { require_once __DIR__.'/Php73.php'; p\Php73::$startAt = (int) microtime(true); function hrtime($as_number = false) { return p\Php73::hrtime($as_number); } } if (!function_exists('array_key_first')) { function array_key_first(array $array) { foreach ($array as $key => $value) { return $key; } } } if (!function_exists('array_key_last')) { function array_key_last(array $array) { return key(array_slice($array, -1, 1, true)); } } Copyright (c) 2020-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. id = $id; $this->text = $text; $this->line = $line; $this->pos = $position; } public function getTokenName(): ?string { if ('UNKNOWN' === $name = token_name($this->id)) { $name = \strlen($this->text) > 1 || \ord($this->text) < 32 ? null : $this->text; } return $name; } public function is($kind): bool { foreach ((array) $kind as $value) { if (\in_array($value, [$this->id, $this->text], true)) { return true; } } return false; } public function isIgnorable(): bool { return \in_array($this->id, [\T_WHITESPACE, \T_COMMENT, \T_DOC_COMMENT, \T_OPEN_TAG], true); } public function __toString(): string { return (string) $this->text; } public static function tokenize(string $code, int $flags = 0): array { $line = 1; $position = 0; $tokens = token_get_all($code, $flags); foreach ($tokens as $index => $token) { if (\is_string($token)) { $id = \ord($token); $text = $token; } else { [$id, $text, $line] = $token; } $tokens[$index] = new static($id, $text, $line, $position); $position += \strlen($text); } return $tokens; } } flags = $flags; } } = 80000) { return; } if (!defined('FILTER_VALIDATE_BOOL') && defined('FILTER_VALIDATE_BOOLEAN')) { define('FILTER_VALIDATE_BOOL', \FILTER_VALIDATE_BOOLEAN); } if (!function_exists('fdiv')) { function fdiv(float $num1, float $num2): float { return p\Php80::fdiv($num1, $num2); } } if (!function_exists('preg_last_error_msg')) { function preg_last_error_msg(): string { return p\Php80::preg_last_error_msg(); } } if (!function_exists('str_contains')) { function str_contains(?string $haystack, ?string $needle): bool { return p\Php80::str_contains($haystack ?? '', $needle ?? ''); } } if (!function_exists('str_starts_with')) { function str_starts_with(?string $haystack, ?string $needle): bool { return p\Php80::str_starts_with($haystack ?? '', $needle ?? ''); } } if (!function_exists('str_ends_with')) { function str_ends_with(?string $haystack, ?string $needle): bool { return p\Php80::str_ends_with($haystack ?? '', $needle ?? ''); } } if (!function_exists('get_debug_type')) { function get_debug_type($value): string { return p\Php80::get_debug_type($value); } } if (!function_exists('get_resource_id')) { function get_resource_id($resource): int { return p\Php80::get_resource_id($resource); } } Copyright (c) 2021-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. $v) { if ($k !== ++$nextKey) { return false; } } return true; } } = 70400 && extension_loaded('curl')) { class CURLStringFile extends CURLFile { private $data; public function __construct(string $data, string $postname, string $mime = 'application/octet-stream') { $this->data = $data; parent::__construct('data://application/octet-stream;base64,'.base64_encode($data), $mime, $postname); } public function __set(string $name, $value): void { if ('data' !== $name) { $this->$name = $value; return; } if (is_object($value) ? !method_exists($value, '__toString') : !is_scalar($value)) { throw new TypeError('Cannot assign '.gettype($value).' to property CURLStringFile::$data of type string'); } $this->name = 'data://application/octet-stream;base64,'.base64_encode($value); } public function __isset(string $name): bool { return isset($this->$name); } public function &__get(string $name) { return $this->$name; } } } = 80100) { return; } if (defined('MYSQLI_REFRESH_SLAVE') && !defined('MYSQLI_REFRESH_REPLICA')) { define('MYSQLI_REFRESH_REPLICA', 64); } if (\extension_loaded('curl') && !defined('CURLOPT_ISSUERCERT_BLOB') && curl_version()['version_number'] >= 0x074700) { define('CURLOPT_ISSUERCERT_BLOB', 40295); } if (!function_exists('array_is_list')) { function array_is_list(array $array): bool { return p\Php81::array_is_list($array); } } if (!function_exists('enum_exists')) { function enum_exists(string $enum, bool $autoload = true): bool { return $autoload && class_exists($enum) && false; } } Copyright (c) 2024-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \PHP_VERSION_ID) { trigger_error(\sprintf('mb_ucfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given', $encoding), \E_USER_WARNING); return false; } throw new \ValueError(\sprintf('mb_ucfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given', $encoding)); } $firstChar = mb_substr($string, 0, 1, $encoding); $firstChar = mb_convert_case($firstChar, \MB_CASE_TITLE, $encoding); return $firstChar.mb_substr($string, 1, null, $encoding); } public static function mb_lcfirst(string $string, ?string $encoding = null) { if (null === $encoding) { $encoding = mb_internal_encoding(); } try { $validEncoding = @mb_check_encoding('', $encoding); } catch (\ValueError $e) { throw new \ValueError(\sprintf('mb_lcfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given', $encoding)); } if (!$validEncoding) { if (80000 > \PHP_VERSION_ID) { trigger_error(\sprintf('mb_lcfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given', $encoding), \E_USER_WARNING); return false; } throw new \ValueError(\sprintf('mb_lcfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given', $encoding)); } $firstChar = mb_substr($string, 0, 1, $encoding); $firstChar = mb_convert_case($firstChar, \MB_CASE_LOWER, $encoding); return $firstChar.mb_substr($string, 1, null, $encoding); } public static function array_find(array $array, callable $callback) { foreach ($array as $key => $value) { if ($callback($value, $key)) { return $value; } } return null; } public static function array_find_key(array $array, callable $callback) { foreach ($array as $key => $value) { if ($callback($value, $key)) { return $key; } } return null; } public static function array_any(array $array, callable $callback): bool { foreach ($array as $key => $value) { if ($callback($value, $key)) { return true; } } return false; } public static function array_all(array $array, callable $callback): bool { foreach ($array as $key => $value) { if (!$callback($value, $key)) { return false; } } return true; } public static function fpow(float $num, float $exponent): float { return $num ** $exponent; } public static function mb_trim(string $string, ?string $characters = null, ?string $encoding = null) { return self::mb_internal_trim('{^[%s]+|[%1$s]+$}Du', $string, $characters, $encoding, __FUNCTION__); } public static function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null) { return self::mb_internal_trim('{^[%s]+}Du', $string, $characters, $encoding, __FUNCTION__); } public static function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null) { return self::mb_internal_trim('{[%s]+$}Du', $string, $characters, $encoding, __FUNCTION__); } private static function mb_internal_trim(string $regex, string $string, ?string $characters, ?string $encoding, string $function) { if (null === $encoding) { $encoding = mb_internal_encoding(); } try { $validEncoding = @mb_check_encoding('', $encoding); } catch (\ValueError $e) { throw new \ValueError(\sprintf('%s(): Argument #3 ($encoding) must be a valid encoding, "%s" given', $function, $encoding)); } if (!$validEncoding) { if (80000 > \PHP_VERSION_ID) { trigger_error(\sprintf('%s(): Argument #3 ($encoding) must be a valid encoding, "%s" given', $function, $encoding), \E_USER_WARNING); return false; } throw new \ValueError(\sprintf('%s(): Argument #3 ($encoding) must be a valid encoding, "%s" given', $function, $encoding)); } if ('' === $characters) { return null === $encoding ? $string : mb_convert_encoding($string, $encoding); } if ('UTF-8' === $encoding || \in_array(strtolower($encoding), ['utf-8', 'utf8'], true)) { $encoding = 'UTF-8'; } $string = mb_convert_encoding($string, 'UTF-8', $encoding); if (null !== $characters) { $characters = mb_convert_encoding($characters, 'UTF-8', $encoding); } if (null === $characters) { $characters = "\\0 \f\n\r\t\v\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{200A}\u{2028}\u{2029}\u{202F}\u{205F}\u{3000}\u{0085}\u{180E}"; } else { $characters = preg_quote($characters); } $string = preg_replace(\sprintf($regex, $characters), '', $string); if ('UTF-8' === $encoding) { return $string; } return mb_convert_encoding($string, $encoding, 'UTF-8'); } public static function grapheme_str_split(string $string, int $length) { if (0 > $length || 1073741823 < $length) { throw new \ValueError('grapheme_str_split(): Argument #2 ($length) must be greater than 0 and less than or equal to 1073741823.'); } if ('' === $string) { return []; } $regex = ((float) \PCRE_VERSION >= 10.44) ? '\X' : '(?:\r\n|[\x{1F1E6}-\x{1F1FF}][\x{1F1E6}-\x{1F1FF}]?|(?:[ -~\x{200C}\x{200D}]|[ᆨ-ᇹ]+|[ᄀ-ᅟ]*(?:[가개갸걔거게겨계고과괘괴교구궈궤귀규그긔기까깨꺄꺠꺼께껴꼐꼬꽈꽤꾀꾜꾸꿔꿰뀌뀨끄끠끼나내냐냬너네녀녜노놔놰뇌뇨누눠눼뉘뉴느늬니다대댜댸더데뎌뎨도돠돼되됴두둬뒈뒤듀드듸디따때땨떄떠떼뗘뗴또똬뙈뙤뚀뚜뚸뛔뛰뜌뜨띄띠라래랴럐러레려례로롸뢔뢰료루뤄뤠뤼류르릐리마매먀먜머메며몌모뫄뫠뫼묘무뭐뭬뮈뮤므믜미바배뱌뱨버베벼볘보봐봬뵈뵤부붜붸뷔뷰브븨비빠빼뺘뺴뻐뻬뼈뼤뽀뽜뽸뾔뾰뿌뿨쀄쀠쀼쁘쁴삐사새샤섀서세셔셰소솨쇄쇠쇼수숴쉐쉬슈스싀시싸쌔쌰썌써쎄쎠쎼쏘쏴쐐쐬쑈쑤쒀쒜쒸쓔쓰씌씨아애야얘어에여예오와왜외요우워웨위유으의이자재쟈쟤저제져졔조좌좨죄죠주줘줴쥐쥬즈즤지짜째쨔쨰쩌쩨쪄쪠쪼쫘쫴쬐쬬쭈쭤쮀쮜쮸쯔쯰찌차채챠챼처체쳐쳬초촤쵀최쵸추춰췌취츄츠츼치카캐캬컈커케켜켸코콰쾌쾨쿄쿠쿼퀘퀴큐크킈키타태탸턔터테텨톄토톼퇘퇴툐투퉈퉤튀튜트틔티파패퍄퍠퍼페펴폐포퐈퐤푀표푸풔풰퓌퓨프픠피하해햐햬허헤혀혜호화홰회효후훠훼휘휴흐희히]?[ᅠ-ᆢ]+|[가-힣])[ᆨ-ᇹ]*|[ᄀ-ᅟ]+|[^\p{Cc}\p{Cf}\p{Zl}\p{Zp}])[\p{Mn}\p{Mc}\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{1D165}\x{1D16E}-\x{1D172}\x{1F3FB}-\x{1F3FF}\x{FE0E}-\x{FE0F}\x{E0020}-\x{E007F}]*(?:\x{200D}(?:[\x{1F1E6}-\x{1F1FF}]|[ -~\x{200C}\x{200D}]|[ᆨ-ᇹ]+|[ᄀ-ᅟ]*(?:[가-힣]?[ᅠ-ᆢ]+|[가-힣])[ᆨ-ᇹ]*|[ᄀ-ᅟ]+|[^\p{Cc}\p{Cf}\p{Zl}\p{Zp}])[\p{Mn}\p{Mc}\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{1D165}\x{1D16E}-\x{1D172}\x{1F3FB}-\x{1F3FF}\x{FE0E}-\x{FE0F}\x{E0020}-\x{E007F}]*)*|[\p{Cc}\p{Cf}\p{Zl}\p{Zp}])'; if (!preg_match_all('/'.$regex.'/u', $string, $matches)) { return false; } if (1 === $length) { return $matches[0]; } $chunks = array_chunk($matches[0], $length); foreach ($chunks as &$chunk) { $chunk = implode('', $chunk); } return $chunks; } public static function bcceil(string $num): string { if (!is_numeric($num)) { throw new \ValueError('bcceil(): Argument #1 ($num) is not well-formed'); } return self::bcround($num, 0, \RoundingMode::PositiveInfinity); } public static function bcdivmod(string $num1, string $num2, ?int $scale = null): ?array { if (null === $quot = @bcdiv($num1, $num2, 0)) { throw new \DivisionByZeroError('Division by zero'); } $scale = $scale ?? (\PHP_VERSION_ID >= 70300 ? bcscale() : (\ini_get('bcmath.scale') ?: 0)); return [$quot, bcmod($num1, $num2, $scale)]; } public static function bcfloor(string $num): string { if (!is_numeric($num)) { throw new \ValueError('bcfloor(): Argument #1 ($num) is not well-formed'); } return self::bcround($num, 0, \RoundingMode::NegativeInfinity); } public static function bcround(string $num, int $precision = 0, $mode = \RoundingMode::HalfAwayFromZero): string { if (!is_numeric($num)) { throw new \ValueError('bcround(): Argument #1 ($num) is not well-formed'); } $sign = 1; if ('' !== $num && ('-' === $num[0] || '+' === $num[0])) { if ('-' === $num[0]) { $sign = -1; } $num = substr($num, 1); } if (false !== strpos($num, '.')) { [$intPart, $fracPart] = array_pad(explode('.', $num, 2), 2, ''); } else { $intPart = $num; $fracPart = ''; } if ('' === $intPart) { $intPart = '0'; } $intPart = self::trimLeadingZeros($intPart); $fracPart = (string) $fracPart; if ($precision >= 0) { $fracLength = \strlen($fracPart); if ($precision <= $fracLength) { $scaledInt = $intPart.(string) substr($fracPart, 0, $precision); $scaledFrac = (string) substr($fracPart, $precision); } else { $scaledInt = $intPart.$fracPart.str_repeat('0', $precision - $fracLength); $scaledFrac = ''; } } else { $shift = -$precision; $intLength = \strlen($intPart); if ($shift <= $intLength) { $splitPos = $intLength - $shift; $scaledInt = substr($intPart, 0, $splitPos); $scaledInt = '' === $scaledInt ? '0' : $scaledInt; $scaledFrac = substr($intPart, $splitPos).$fracPart; } else { $scaledInt = '0'; $scaledFrac = str_repeat('0', $shift - $intLength).$intPart.$fracPart; } } $roundedInt = self::roundIntegerPart($scaledInt, $scaledFrac, $sign, $mode); $isZero = '' === trim($roundedInt, '0'); $absResult = self::formatRoundedDigits($roundedInt, $precision); if (-1 === $sign && !$isZero) { $absResult = '-'.$absResult; } return $absResult; } private static function roundIntegerPart(string $intPart, string $fracPart, int $sign, $mode): string { $intPart = self::trimLeadingZeros($intPart); if ('' === $fracPart || '' === trim($fracPart, '0')) { return $intPart; } $firstDigit = $fracPart[0]; $tail = (string) substr($fracPart, 1); $tailNonZero = '' !== trim($tail, '0'); $isGreaterThanHalf = $firstDigit > '5' || ('5' === $firstDigit && $tailNonZero); $isExactlyHalf = '5' === $firstDigit && !$tailNonZero; $shouldIncrease = false; switch ($mode) { case \RoundingMode::TowardsZero: break; case \RoundingMode::AwayFromZero: $shouldIncrease = true; break; case \RoundingMode::PositiveInfinity: $shouldIncrease = $sign > 0; break; case \RoundingMode::NegativeInfinity: $shouldIncrease = $sign < 0; break; case \RoundingMode::HalfAwayFromZero: $shouldIncrease = $isGreaterThanHalf || $isExactlyHalf; break; case \RoundingMode::HalfTowardsZero: $shouldIncrease = $isGreaterThanHalf; break; case \RoundingMode::HalfEven: if ($isGreaterThanHalf) { $shouldIncrease = true; } elseif ($isExactlyHalf && 1 === self::lastDigit($intPart) % 2) { $shouldIncrease = true; } break; case \RoundingMode::HalfOdd: if ($isGreaterThanHalf) { $shouldIncrease = true; } elseif ($isExactlyHalf && 0 === self::lastDigit($intPart) % 2) { $shouldIncrease = true; } break; } if ($shouldIncrease) { $intPart = self::incrementDigits($intPart); } return self::trimLeadingZeros($intPart); } private static function formatRoundedDigits(string $roundedInt, int $precision): string { if ($precision > 0) { if (\strlen($roundedInt) <= $precision) { $roundedInt = str_pad($roundedInt, $precision + 1, '0', \STR_PAD_LEFT); } $intDigits = substr($roundedInt, 0, -$precision); $fracDigits = substr($roundedInt, -$precision); $intDigits = self::trimLeadingZeros('' === $intDigits ? '0' : $intDigits); $fracDigits = str_pad($fracDigits, $precision, '0', \STR_PAD_LEFT); return $intDigits.'.'.$fracDigits; } if (0 === $precision) { return self::trimLeadingZeros($roundedInt); } $shift = -$precision; $digits = $roundedInt.str_repeat('0', $shift); return self::trimLeadingZeros($digits); } private static function incrementDigits(string $digits): string { $digits = '' === $digits ? '0' : $digits; $index = \strlen($digits) - 1; $result = $digits; $carry = 1; while ($index >= 0 && $carry) { $value = \ord($result[$index]) - 48 + $carry; $carry = $value >= 10 ? 1 : 0; $result[$index] = \chr(48 + ($value % 10)); --$index; } return $carry ? '1'.$result : $result; } private static function trimLeadingZeros(string $digits): string { $digits = ltrim($digits, '0'); return '' === $digits ? '0' : $digits; } private static function lastDigit(string $digits): int { $length = \strlen($digits); return $length ? \ord($digits[$length - 1]) - 48 : 0; } } message = $message; $this->since = $since; } } } message = $message; $this->since = $since; } } } elseif (\PHP_VERSION_ID < 80400) { require dirname(__DIR__).'/Deprecated.php'; } = 70300 ? \PDO::DBLIB_ATTR_TDS_VERSION : 1004; public const ATTR_SKIP_EMPTY_ROWSETS = \PHP_VERSION_ID >= 70300 ? \PDO::DBLIB_ATTR_SKIP_EMPTY_ROWSETS : 1005; public const ATTR_DATETIME_CONVERT = \PHP_VERSION_ID >= 70300 ? \PDO::DBLIB_ATTR_DATETIME_CONVERT : 1006; public function __construct(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null) { parent::__construct($dsn, $username, $password, $options); if ('dblib' !== $driver = $this->getAttribute(\PDO::ATTR_DRIVER_NAME)) { throw new \PDOException(\sprintf('Pdo\Dblib::__construct() cannot be used for connecting to the "%s" driver', $driver)); } } public static function connect(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null): self { try { return new self($dsn, $username, $password, $options); } catch (\PDOException $e) { throw preg_match('/^Pdo\\\\Dblib::__construct\(\) cannot be used for connecting to the "([a-z]+)" driver/', $e->getMessage(), $matches) ? new \PDOException(\sprintf('Pdo\Dblib::connect() cannot be used for connecting to the "%s" driver', $matches[1])) : $e; } } } } getAttribute(\PDO::ATTR_DRIVER_NAME)) { throw new \PDOException(\sprintf('Pdo\Firebird::__construct() cannot be used for connecting to the "%s" driver', $driver)); } } public static function connect(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null): self { try { return new self($dsn, $username, $password, $options); } catch (\PDOException $e) { throw preg_match('/^Pdo\\\\Firebird::__construct\(\) cannot be used for connecting to the "([a-z]+)" driver/', $e->getMessage(), $matches) ? new \PDOException(\sprintf('Pdo\Firebird::connect() cannot be used for connecting to the "%s" driver', $matches[1])) : $e; } } } } = 80100 ? \PDO::MYSQL_ATTR_LOCAL_INFILE_DIRECTORY : 1015; public const ATTR_MAX_BUFFER_SIZE = \PDO::MYSQL_ATTR_MAX_BUFFER_SIZE; public const ATTR_MULTI_STATEMENTS = \PDO::MYSQL_ATTR_MULTI_STATEMENTS; public const ATTR_READ_DEFAULT_FILE = \PDO::MYSQL_ATTR_READ_DEFAULT_FILE; public const ATTR_READ_DEFAULT_GROUP = \PDO::MYSQL_ATTR_READ_DEFAULT_GROUP; public const ATTR_SERVER_PUBLIC_KEY = \PDO::MYSQL_ATTR_SERVER_PUBLIC_KEY; public const ATTR_SSL_CA = \PDO::MYSQL_ATTR_SSL_CA; public const ATTR_SSL_CAPATH = \PDO::MYSQL_ATTR_SSL_CAPATH; public const ATTR_SSL_CERT = \PDO::MYSQL_ATTR_SSL_CERT; public const ATTR_SSL_CIPHER = \PDO::MYSQL_ATTR_SSL_CIPHER; public const ATTR_SSL_KEY = \PDO::MYSQL_ATTR_SSL_KEY; public const ATTR_USE_BUFFERED_QUERY = \PDO::MYSQL_ATTR_USE_BUFFERED_QUERY; public function __construct(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null) { parent::__construct($dsn, $username, $password, $options); if ('mysql' !== $driver = $this->getAttribute(\PDO::ATTR_DRIVER_NAME)) { throw new \PDOException(\sprintf('Pdo\Mysql::__construct() cannot be used for connecting to the "%s" driver', $driver)); } } public static function connect(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null): self { try { return new self($dsn, $username, $password, $options); } catch (\PDOException $e) { throw preg_match('/^Pdo\\\\Mysql::__construct\(\) cannot be used for connecting to the "([a-z]+)" driver/', $e->getMessage(), $matches) ? new \PDOException(\sprintf('Pdo\Mysql::connect() cannot be used for connecting to the "%s" driver', $matches[1])) : $e; } } } } elseif (\defined('PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT')) { class Mysql extends \PDO { public const ATTR_COMPRESS = \PDO::MYSQL_ATTR_COMPRESS; public const ATTR_DIRECT_QUERY = \PDO::MYSQL_ATTR_DIRECT_QUERY; public const ATTR_FOUND_ROWS = \PDO::MYSQL_ATTR_FOUND_ROWS; public const ATTR_IGNORE_SPACE = \PDO::MYSQL_ATTR_IGNORE_SPACE; public const ATTR_INIT_COMMAND = \PDO::MYSQL_ATTR_INIT_COMMAND; public const ATTR_LOCAL_INFILE = \PDO::MYSQL_ATTR_LOCAL_INFILE; public const ATTR_LOCAL_INFILE_DIRECTORY = \PHP_VERSION_ID >= 80100 ? \PDO::MYSQL_ATTR_LOCAL_INFILE_DIRECTORY : 1015; public const ATTR_MULTI_STATEMENTS = \PDO::MYSQL_ATTR_MULTI_STATEMENTS; public const ATTR_SERVER_PUBLIC_KEY = \PDO::MYSQL_ATTR_SERVER_PUBLIC_KEY; public const ATTR_SSL_CA = \PDO::MYSQL_ATTR_SSL_CA; public const ATTR_SSL_CAPATH = \PDO::MYSQL_ATTR_SSL_CAPATH; public const ATTR_SSL_CERT = \PDO::MYSQL_ATTR_SSL_CERT; public const ATTR_SSL_CIPHER = \PDO::MYSQL_ATTR_SSL_CIPHER; public const ATTR_SSL_KEY = \PDO::MYSQL_ATTR_SSL_KEY; public const ATTR_SSL_VERIFY_SERVER_CERT = \PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT; public const ATTR_USE_BUFFERED_QUERY = \PDO::MYSQL_ATTR_USE_BUFFERED_QUERY; public function __construct(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null) { parent::__construct($dsn, $username, $password, $options); if ('mysql' !== $driver = $this->getAttribute(\PDO::ATTR_DRIVER_NAME)) { throw new \PDOException(\sprintf('Pdo\Mysql::__construct() cannot be used for connecting to the "%s" driver', $driver)); } } public static function connect(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null): self { try { return new self($dsn, $username, $password, $options); } catch (\PDOException $e) { throw preg_match('/^Pdo\\\\Mysql::__construct\(\) cannot be used for connecting to the "([a-z]+)" driver/', $e->getMessage(), $matches) ? new \PDOException(\sprintf('Pdo\Mysql::connect() cannot be used for connecting to the "%s" driver', $matches[1])) : $e; } } } } else { class Mysql extends \PDO { public const ATTR_COMPRESS = \PDO::MYSQL_ATTR_COMPRESS; public const ATTR_DIRECT_QUERY = \PDO::MYSQL_ATTR_DIRECT_QUERY; public const ATTR_FOUND_ROWS = \PDO::MYSQL_ATTR_FOUND_ROWS; public const ATTR_IGNORE_SPACE = \PDO::MYSQL_ATTR_IGNORE_SPACE; public const ATTR_INIT_COMMAND = \PDO::MYSQL_ATTR_INIT_COMMAND; public const ATTR_LOCAL_INFILE = \PDO::MYSQL_ATTR_LOCAL_INFILE; public const ATTR_LOCAL_INFILE_DIRECTORY = \PHP_VERSION_ID >= 80100 ? \PDO::MYSQL_ATTR_LOCAL_INFILE_DIRECTORY : 1015; public const ATTR_MULTI_STATEMENTS = \PDO::MYSQL_ATTR_MULTI_STATEMENTS; public const ATTR_SERVER_PUBLIC_KEY = \PDO::MYSQL_ATTR_SERVER_PUBLIC_KEY; public const ATTR_SSL_CA = \PDO::MYSQL_ATTR_SSL_CA; public const ATTR_SSL_CAPATH = \PDO::MYSQL_ATTR_SSL_CAPATH; public const ATTR_SSL_CERT = \PDO::MYSQL_ATTR_SSL_CERT; public const ATTR_SSL_CIPHER = \PDO::MYSQL_ATTR_SSL_CIPHER; public const ATTR_SSL_KEY = \PDO::MYSQL_ATTR_SSL_KEY; public const ATTR_USE_BUFFERED_QUERY = \PDO::MYSQL_ATTR_USE_BUFFERED_QUERY; public function __construct(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null) { parent::__construct($dsn, $username, $password, $options); if ('mysql' !== $driver = $this->getAttribute(\PDO::ATTR_DRIVER_NAME)) { throw new \PDOException(\sprintf('Pdo\Mysql::__construct() cannot be used for connecting to the "%s" driver', $driver)); } } public static function connect(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null): self { try { return new self($dsn, $username, $password, $options); } catch (\PDOException $e) { throw preg_match('/^Pdo\\\\Mysql::__construct\(\) cannot be used for connecting to the "([a-z]+)" driver/', $e->getMessage(), $matches) ? new \PDOException(\sprintf('Pdo\Mysql::connect() cannot be used for connecting to the "%s" driver', $matches[1])) : $e; } } } } } getAttribute(\PDO::ATTR_DRIVER_NAME)) { throw new \PDOException(\sprintf('Pdo\Odbc::__construct() cannot be used for connecting to the "%s" driver', $driver)); } } public static function connect(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null): self { try { return new self($dsn, $username, $password, $options); } catch (\PDOException $e) { throw preg_match('/^Pdo\\\\Odbc::__construct\(\) cannot be used for connecting to the "([a-z]+)" driver/', $e->getMessage(), $matches) ? new \PDOException(\sprintf('Pdo\Odbc::connect() cannot be used for connecting to the "%s" driver', $matches[1])) : $e; } } } } getAttribute(\PDO::ATTR_DRIVER_NAME)) { throw new \PDOException(\sprintf('Pdo\Pgsql::__construct() cannot be used for connecting to the "%s" driver', $driver)); } } public static function connect(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null): self { try { return new self($dsn, $username, $password, $options); } catch (\PDOException $e) { throw preg_match('/^Pdo\\\\Pgsql::__construct\(\) cannot be used for connecting to the "([a-z]+)" driver/', $e->getMessage(), $matches) ? new \PDOException(\sprintf('Pdo\Pgsql::connect() cannot be used for connecting to the "%s" driver', $matches[1])) : $e; } } public function copyFromArray(string $tableName, array $rows, string $separator = "\t", string $nullAs = '\\\\N', ?string $fields = null): bool { return $this->pgsqlCopyFromArray($tableName, $rows, $separator, $nullAs, $fields); } public function copyFromFile(string $tableName, string $filename, string $separator = "\t", string $nullAs = '\\\\N', ?string $fields = null): bool { return $this->pgsqlCopyFromFile($tableName, $filename, $separator, $nullAs, $fields); } public function copyToArray(string $tableName, string $separator = "\t", string $nullAs = '\\\\N', ?string $fields = null) { return $this->pgsqlCopyToArray($tableName, $separator, $nullAs, $fields); } public function copyToFile(string $tableName, string $filename, string $separator = "\t", string $nullAs = '\\\\N', ?string $fields = null): bool { return $this->pgsqlCopyToFile($tableName, $filename, $separator, $nullAs, $fields); } public function getNotify(int $fetchMode = \PDO::FETCH_DEFAULT, int $timeoutMilliseconds = 0) { return $this->pgsqlGetNotify($fetchMode, $timeoutMilliseconds); } public function getPid(): int { return $this->pgsqlGetPid(); } public function lobCreate() { return $this->pgsqlLOBCreate(); } public function lobOpen(string $oid, string $mode = 'rb') { return $this->pgsqlLOBOpen($oid, $mode); } public function lobUnlink(string $oid): bool { return $this->pgsqlLOBUnlink($oid); } } } = 70400 ? \PDO::SQLITE_ATTR_EXTENDED_RESULT_CODES : 1002; public const ATTR_OPEN_FLAGS = \PHP_VERSION_ID >= 70300 ? \PDO::SQLITE_ATTR_OPEN_FLAGS : 1000; public const ATTR_READONLY_STATEMENT = \PHP_VERSION_ID >= 70400 ? \PDO::SQLITE_ATTR_READONLY_STATEMENT : 1001; public const DETERMINISTIC = \PDO::SQLITE_DETERMINISTIC; public const OPEN_READONLY = \PHP_VERSION_ID >= 70300 ? \PDO::SQLITE_OPEN_READONLY : 1; public const OPEN_READWRITE = \PHP_VERSION_ID >= 70300 ? \PDO::SQLITE_OPEN_READWRITE : 2; public const OPEN_CREATE = \PHP_VERSION_ID >= 70300 ? \PDO::SQLITE_OPEN_CREATE : 4; public function __construct(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null) { parent::__construct($dsn, $username, $password, $options); if ('sqlite' !== $driver = $this->getAttribute(\PDO::ATTR_DRIVER_NAME)) { throw new \PDOException(\sprintf('Pdo\Sqlite::__construct() cannot be used for connecting to the "%s" driver', $driver)); } } public static function connect(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null): self { try { return new self($dsn, $username, $password, $options); } catch (\PDOException $e) { throw preg_match('/^Pdo\\\\Sqlite::__construct\(\) cannot be used for connecting to the "([a-z]+)" driver/', $e->getMessage(), $matches) ? new \PDOException(\sprintf('Pdo\Sqlite::connect() cannot be used for connecting to the "%s" driver', $matches[1])) : $e; } } public function createAggregate(string $name, callable $step, callable $finalize, int $numArgs = -1): bool { return $this->sqliteCreateAggregate($name, $step, $finalize, $numArgs); } public function createCollation(string $name, callable $callback): bool { return $this->sqliteCreateCollation($name, $callback); } public function createFunction(string $function_name, callable $callback, int $num_args = -1, int $flags = 0): bool { return $this->sqliteCreateFunction($function_name, $callback, $num_args, $flags); } } } name = ltrim($name, '\\'); $deprecated = false; $eh = set_error_handler(static function ($type, $msg, $file, $line) use ($name, &$deprecated, &$eh) { if (\E_DEPRECATED === $type && "Constant $name is deprecated" === $msg) { return $deprecated = true; } return $eh && $eh($type, $msg, $file, $line); }); try { $this->value = constant($name); $this->deprecated = $deprecated; } finally { restore_error_handler(); } } public function getName(): string { return $this->name; } public function getValue() { return $this->value; } public function getNamespaceName(): string { if (false === $slashPos = strrpos($this->name, '\\')) { return ''; } return substr($this->name, 0, $slashPos); } public function getShortName(): string { if (false === $slashPos = strrpos($this->name, '\\')) { return $this->name; } return substr($this->name, $slashPos + 1); } public function isDeprecated(): bool { return $this->deprecated; } public function __toString(): string { if (!self::$persistentConstants) { $persistentConstants = get_defined_constants(true); unset($persistentConstants['user']); foreach ($persistentConstants as $constants) { self::$persistentConstants += $constants; } } $persistent = array_key_exists($this->name, self::$persistentConstants); $result = 'Constant [ '; if ($persistent || $this->deprecated) { $result .= '<'; if ($persistent) { $result .= 'persistent'; if ($this->deprecated) { $result .= ', '; } } if ($this->deprecated) { $result .= 'deprecated'; } $result .= '> '; } if (is_object($this->value)) { $result .= \PHP_VERSION_ID >= 80000 ? get_debug_type($this->value) : gettype($this->value); } elseif (is_bool($this->value)) { $result .= 'bool'; } elseif (is_int($this->value)) { $result .= 'int'; } elseif (is_float($this->value)) { $result .= 'float'; } elseif (null === $this->value) { $result .= 'null'; } else { $result .= gettype($this->value); } $result .= ' '; $result .= $this->name; $result .= ' ] { '; if (is_array($this->value)) { $result .= 'Array'; } else { $result .= (string) $this->value; } $result .= " }\n"; return $result; } public function __sleep(): array { throw new Exception("Serialization of 'ReflectionConstant' is not allowed"); } public function __wakeup(): void { throw new Exception("Unserialization of 'ReflectionConstant' is not allowed"); } } } = 80400) { return; } if (\extension_loaded('curl')) { if (defined('CURL_VERSION_HTTP3') || \PHP_VERSION_ID < 80200 && curl_version()['version_number'] >= 0x074200) { if (!defined('CURL_HTTP_VERSION_3')) { define('CURL_HTTP_VERSION_3', 30); } if (!defined('CURL_HTTP_VERSION_3ONLY') && curl_version()['version_number'] >= 0x075800) { define('CURL_HTTP_VERSION_3ONLY', 31); } } } if (!function_exists('array_find')) { function array_find(array $array, callable $callback) { return p\Php84::array_find($array, $callback); } } if (!function_exists('array_find_key')) { function array_find_key(array $array, callable $callback) { return p\Php84::array_find_key($array, $callback); } } if (!function_exists('array_any')) { function array_any(array $array, callable $callback): bool { return p\Php84::array_any($array, $callback); } } if (!function_exists('array_all')) { function array_all(array $array, callable $callback): bool { return p\Php84::array_all($array, $callback); } } if (!function_exists('fpow')) { function fpow(float $num, float $exponent): float { return p\Php84::fpow($num, $exponent); } } if (\PHP_VERSION_ID < 80000) { require __DIR__.'/bootstrap72.php'; } if (extension_loaded('mbstring')) { if (!function_exists('mb_ucfirst')) { function mb_ucfirst(?string $string, ?string $encoding = null): string { return p\Php84::mb_ucfirst((string) $string, $encoding); } } if (!function_exists('mb_lcfirst')) { function mb_lcfirst(?string $string, ?string $encoding = null): string { return p\Php84::mb_lcfirst((string) $string, $encoding); } } if (!function_exists('mb_trim')) { function mb_trim(?string $string, ?string $characters = null, ?string $encoding = null): string { return p\Php84::mb_trim((string) $string, $characters, $encoding); } } if (!function_exists('mb_ltrim')) { function mb_ltrim(?string $string, ?string $characters = null, ?string $encoding = null): string { return p\Php84::mb_ltrim((string) $string, $characters, $encoding); } } if (!function_exists('mb_rtrim')) { function mb_rtrim(?string $string, ?string $characters = null, ?string $encoding = null): string { return p\Php84::mb_rtrim((string) $string, $characters, $encoding); } } } if (extension_loaded('bcmath')) { if (!function_exists('bcceil')) { function bcceil(string $num): string { return p\Php84::bcceil($num); } } if (!function_exists('bcdivmod')) { function bcdivmod(string $num1, string $num2, ?int $scale = null): ?array { return p\Php84::bcdivmod($num1, $num2, $scale); } } if (!function_exists('bcfloor')) { function bcfloor(string $num): string { return p\Php84::bcfloor($num); } } if (!function_exists('bcround')) { function bcround(string $num, int $precision = 0, $mode = RoundingMode::HalfAwayFromZero): string { return p\Php84::bcround($num, $precision, $mode); } } } if (\PHP_VERSION_ID >= 80200) { return require __DIR__.'/bootstrap82.php'; } if (extension_loaded('intl') && !function_exists('grapheme_str_split')) { function grapheme_str_split(string $string, int $length = 1) { return p\Php84::grapheme_str_split($string, $length); } } = 80400) { return; } if (extension_loaded('intl') && !function_exists('grapheme_str_split')) { function grapheme_str_split(string $string, int $length = 1): array|false { return p\Php84::grapheme_str_split($string, $length); } } isSuccessful()) { throw new InvalidArgumentException('Expected a failed process, but the given process was successful.'); } $error = sprintf('The command "%s" failed.'."\n\nExit Code: %s(%s)\n\nWorking directory: %s", $process->getCommandLine(), $process->getExitCode(), $process->getExitCodeText(), $process->getWorkingDirectory() ); if (!$process->isOutputDisabled()) { $error .= sprintf("\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s", $process->getOutput(), $process->getErrorOutput() ); } parent::__construct($error); $this->process = $process; } public function getProcess() { return $this->process; } } process = $process; parent::__construct(sprintf('The process has been signaled with signal "%s".', $process->getTermSignal())); } public function getProcess(): Process { return $this->process; } public function getSignal(): int { return $this->getProcess()->getTermSignal(); } } process = $process; $this->timeoutType = $timeoutType; parent::__construct(sprintf( 'The process "%s" exceeded the timeout of %s seconds.', $process->getCommandLine(), $this->getExceededTimeout() )); } public function getProcess() { return $this->process; } public function isGeneralTimeout() { return self::TYPE_GENERAL === $this->timeoutType; } public function isIdleTimeout() { return self::TYPE_IDLE === $this->timeoutType; } public function getExceededTimeout() { switch ($this->timeoutType) { case self::TYPE_GENERAL: return $this->process->getTimeout(); case self::TYPE_IDLE: return $this->process->getIdleTimeout(); default: throw new \LogicException(sprintf('Unknown timeout type "%d".', $this->timeoutType)); } } } suffixes = $suffixes; } public function addSuffix(string $suffix) { $this->suffixes[] = $suffix; } public function find(string $name, ?string $default = null, array $extraDirs = []) { if ('\\' === \DIRECTORY_SEPARATOR && \in_array(strtolower($name), self::CMD_BUILTINS, true)) { return $name; } $dirs = array_merge( explode(\PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')), $extraDirs ); $suffixes = []; if ('\\' === \DIRECTORY_SEPARATOR) { $pathExt = getenv('PATHEXT'); $suffixes = $this->suffixes; $suffixes = array_merge($suffixes, $pathExt ? explode(\PATH_SEPARATOR, $pathExt) : ['.exe', '.bat', '.cmd', '.com']); } $suffixes = '' !== pathinfo($name, PATHINFO_EXTENSION) ? array_merge([''], $suffixes) : array_merge($suffixes, ['']); foreach ($suffixes as $suffix) { foreach ($dirs as $dir) { if ('' === $dir) { $dir = '.'; } if (@is_file($file = $dir.\DIRECTORY_SEPARATOR.$name.$suffix) && ('\\' === \DIRECTORY_SEPARATOR || @is_executable($file))) { return $file; } if (!@is_dir($dir) && basename($dir) === $name.$suffix && @is_executable($dir)) { return $dir; } } } if ('\\' === \DIRECTORY_SEPARATOR || !\function_exists('exec') || \strlen($name) !== strcspn($name, '/'.\DIRECTORY_SEPARATOR)) { return $default; } $execResult = exec('command -v -- '.escapeshellarg($name)); if (($executablePath = substr($execResult, 0, strpos($execResult, \PHP_EOL) ?: null)) && @is_executable($executablePath)) { return $executablePath; } return $default; } } onEmpty = $onEmpty; } public function write($input) { if (null === $input) { return; } if ($this->isClosed()) { throw new RuntimeException(sprintf('"%s" is closed.', static::class)); } $this->input[] = ProcessUtils::validateInput(__METHOD__, $input); } public function close() { $this->open = false; } public function isClosed() { return !$this->open; } #[\ReturnTypeWillChange] public function getIterator() { $this->open = true; while ($this->open || $this->input) { if (!$this->input) { yield ''; continue; } $current = array_shift($this->input); if ($current instanceof \Iterator) { yield from $current; } else { yield $current; } if (!$this->input && $this->open && null !== $onEmpty = $this->onEmpty) { $this->write($onEmpty($this)); } } } } Copyright (c) 2004-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. executableFinder = new ExecutableFinder(); } public function find(bool $includeArgs = true) { if ($php = getenv('PHP_BINARY')) { if (!is_executable($php) && !$php = $this->executableFinder->find($php)) { return false; } if (@is_dir($php)) { return false; } return $php; } $args = $this->findArguments(); $args = $includeArgs && $args ? ' '.implode(' ', $args) : ''; if (\PHP_BINARY && \in_array(\PHP_SAPI, ['cli', 'cli-server', 'phpdbg'], true)) { return \PHP_BINARY.$args; } if ($php = getenv('PHP_PATH')) { if (!@is_executable($php) || @is_dir($php)) { return false; } return $php; } if ($php = getenv('PHP_PEAR_PHP_BIN')) { if (@is_executable($php) && !@is_dir($php)) { return $php; } } if (@is_executable($php = \PHP_BINDIR.('\\' === \DIRECTORY_SEPARATOR ? '\\php.exe' : '/php')) && !@is_dir($php)) { return $php; } $dirs = [\PHP_BINDIR]; if ('\\' === \DIRECTORY_SEPARATOR) { $dirs[] = 'C:\xampp\php\\'; } return $this->executableFinder->find('php', false, $dirs); } public function findArguments() { $arguments = []; if ('phpdbg' === \PHP_SAPI) { $arguments[] = '-qrr'; } return $arguments; } } find(false); $php = false === $php ? null : array_merge([$php], $executableFinder->findArguments()); } if ('phpdbg' === \PHP_SAPI) { $file = tempnam(sys_get_temp_dir(), 'dbg'); file_put_contents($file, $script); register_shutdown_function('unlink', $file); $php[] = $file; $script = null; } parent::__construct($php, $cwd, $env, $script, $timeout); } public static function fromShellCommandline(string $command, ?string $cwd = null, ?array $env = null, $input = null, ?float $timeout = 60) { throw new LogicException(sprintf('The "%s()" method cannot be called when using "%s".', __METHOD__, self::class)); } public function start(?callable $callback = null, array $env = []) { if (null === $this->getCommandLine()) { throw new RuntimeException('Unable to find the PHP executable.'); } parent::start($callback, $env); } } input = $input; } elseif (\is_string($input)) { $this->inputBuffer = $input; } else { $this->inputBuffer = (string) $input; } } public function close() { foreach ($this->pipes as $pipe) { if (\is_resource($pipe)) { fclose($pipe); } } $this->pipes = []; } protected function hasSystemCallBeenInterrupted(): bool { $lastError = $this->lastError; $this->lastError = null; return null !== $lastError && false !== stripos($lastError, 'interrupted system call'); } protected function unblock() { if (!$this->blocked) { return; } foreach ($this->pipes as $pipe) { stream_set_blocking($pipe, 0); } if (\is_resource($this->input)) { stream_set_blocking($this->input, 0); } $this->blocked = false; } protected function write(): ?array { if (!isset($this->pipes[0])) { return null; } $input = $this->input; if ($input instanceof \Iterator) { if (!$input->valid()) { $input = null; } elseif (\is_resource($input = $input->current())) { stream_set_blocking($input, 0); } elseif (!isset($this->inputBuffer[0])) { if (!\is_string($input)) { if (!\is_scalar($input)) { throw new InvalidArgumentException(sprintf('"%s" yielded a value of type "%s", but only scalars and stream resources are supported.', get_debug_type($this->input), get_debug_type($input))); } $input = (string) $input; } $this->inputBuffer = $input; $this->input->next(); $input = null; } else { $input = null; } } $r = $e = []; $w = [$this->pipes[0]]; if (false === @stream_select($r, $w, $e, 0, 0)) { return null; } foreach ($w as $stdin) { if (isset($this->inputBuffer[0])) { $written = fwrite($stdin, $this->inputBuffer); $this->inputBuffer = substr($this->inputBuffer, $written); if (isset($this->inputBuffer[0])) { return [$this->pipes[0]]; } } if ($input) { while (true) { $data = fread($input, self::CHUNK_SIZE); if (!isset($data[0])) { break; } $written = fwrite($stdin, $data); $data = substr($data, $written); if (isset($data[0])) { $this->inputBuffer = $data; return [$this->pipes[0]]; } } if (feof($input)) { if ($this->input instanceof \Iterator) { $this->input->next(); } else { $this->input = null; } } } } if (!isset($this->inputBuffer[0]) && !($this->input instanceof \Iterator ? $this->input->valid() : $this->input)) { $this->input = null; fclose($this->pipes[0]); unset($this->pipes[0]); } elseif (!$w) { return [$this->pipes[0]]; } return null; } public function handleError(int $type, string $msg) { $this->lastError = $msg; } } ttyMode = $ttyMode; $this->ptyMode = $ptyMode; $this->haveReadSupport = $haveReadSupport; parent::__construct($input); } public function __sleep(): array { throw new \BadMethodCallException('Cannot serialize '.__CLASS__); } public function __wakeup() { throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); } public function __destruct() { $this->close(); } public function getDescriptors(): array { if (!$this->haveReadSupport) { $nullstream = fopen('/dev/null', 'c'); return [ ['pipe', 'r'], $nullstream, $nullstream, ]; } if ($this->ttyMode) { return [ ['file', '/dev/tty', 'r'], ['file', '/dev/tty', 'w'], ['file', '/dev/tty', 'w'], ]; } if ($this->ptyMode && Process::isPtySupported()) { return [ ['pty'], ['pty'], ['pty'], ]; } return [ ['pipe', 'r'], ['pipe', 'w'], ['pipe', 'w'], ]; } public function getFiles(): array { return []; } public function readAndWrite(bool $blocking, bool $close = false): array { $this->unblock(); $w = $this->write(); $read = $e = []; $r = $this->pipes; unset($r[0]); set_error_handler([$this, 'handleError']); if (($r || $w) && false === stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) { restore_error_handler(); if (!$this->hasSystemCallBeenInterrupted()) { $this->pipes = []; } return $read; } restore_error_handler(); foreach ($r as $pipe) { $read[$type = array_search($pipe, $this->pipes, true)] = ''; do { $data = @fread($pipe, self::CHUNK_SIZE); $read[$type] .= $data; } while (isset($data[0]) && ($close || isset($data[self::CHUNK_SIZE - 1]))); if (!isset($read[$type][0])) { unset($read[$type]); } if ($close && feof($pipe)) { fclose($pipe); unset($this->pipes[$type]); } } return $read; } public function haveReadSupport(): bool { return $this->haveReadSupport; } public function areOpen(): bool { return (bool) $this->pipes; } } 0, Process::STDERR => 0, ]; private $haveReadSupport; public function __construct($input, bool $haveReadSupport) { $this->haveReadSupport = $haveReadSupport; if ($this->haveReadSupport) { $pipes = [ Process::STDOUT => Process::OUT, Process::STDERR => Process::ERR, ]; $tmpDir = sys_get_temp_dir(); $lastError = 'unknown reason'; set_error_handler(function ($type, $msg) use (&$lastError) { $lastError = $msg; }); for ($i = 0;; ++$i) { foreach ($pipes as $pipe => $name) { $file = sprintf('%s\\sf_proc_%02X.%s', $tmpDir, $i, $name); if (!$h = fopen($file.'.lock', 'w')) { if (file_exists($file.'.lock')) { continue 2; } restore_error_handler(); throw new RuntimeException('A temporary file could not be opened to write the process output: '.$lastError); } if (!flock($h, \LOCK_EX | \LOCK_NB)) { continue 2; } if (isset($this->lockHandles[$pipe])) { flock($this->lockHandles[$pipe], \LOCK_UN); fclose($this->lockHandles[$pipe]); } $this->lockHandles[$pipe] = $h; if (!($h = fopen($file, 'w')) || !fclose($h) || !$h = fopen($file, 'r')) { flock($this->lockHandles[$pipe], \LOCK_UN); fclose($this->lockHandles[$pipe]); unset($this->lockHandles[$pipe]); continue 2; } $this->fileHandles[$pipe] = $h; $this->files[$pipe] = $file; } break; } restore_error_handler(); } parent::__construct($input); } public function __sleep(): array { throw new \BadMethodCallException('Cannot serialize '.__CLASS__); } public function __wakeup() { throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); } public function __destruct() { $this->close(); } public function getDescriptors(): array { if (!$this->haveReadSupport) { $nullstream = fopen('NUL', 'c'); return [ ['pipe', 'r'], $nullstream, $nullstream, ]; } return [ ['pipe', 'r'], ['file', 'NUL', 'w'], ['file', 'NUL', 'w'], ]; } public function getFiles(): array { return $this->files; } public function readAndWrite(bool $blocking, bool $close = false): array { $this->unblock(); $w = $this->write(); $read = $r = $e = []; if ($blocking) { if ($w) { @stream_select($r, $w, $e, 0, Process::TIMEOUT_PRECISION * 1E6); } elseif ($this->fileHandles) { usleep((int) (Process::TIMEOUT_PRECISION * 1E6)); } } foreach ($this->fileHandles as $type => $fileHandle) { $data = stream_get_contents($fileHandle, -1, $this->readBytes[$type]); if (isset($data[0])) { $this->readBytes[$type] += \strlen($data); $read[$type] = $data; } if ($close) { ftruncate($fileHandle, 0); fclose($fileHandle); flock($this->lockHandles[$type], \LOCK_UN); fclose($this->lockHandles[$type]); unset($this->fileHandles[$type], $this->lockHandles[$type]); } } return $read; } public function haveReadSupport(): bool { return $this->haveReadSupport; } public function areOpen(): bool { return $this->pipes && $this->fileHandles; } public function close() { parent::close(); foreach ($this->fileHandles as $type => $handle) { ftruncate($handle, 0); fclose($handle); flock($this->lockHandles[$type], \LOCK_UN); fclose($this->lockHandles[$type]); } $this->fileHandles = $this->lockHandles = []; } } true, 'bypass_shell' => true]; private $useFileHandles = false; private $processPipes; private $latestSignal; private $cachedExitCode; private static $sigchild; public static $exitCodes = [ 0 => 'OK', 1 => 'General error', 2 => 'Misuse of shell builtins', 126 => 'Invoked command cannot execute', 127 => 'Command not found', 128 => 'Invalid exit argument', 129 => 'Hangup', 130 => 'Interrupt', 131 => 'Quit and dump core', 132 => 'Illegal instruction', 133 => 'Trace/breakpoint trap', 134 => 'Process aborted', 135 => 'Bus error: "access to undefined portion of memory object"', 136 => 'Floating point exception: "erroneous arithmetic operation"', 137 => 'Kill (terminate immediately)', 138 => 'User-defined 1', 139 => 'Segmentation violation', 140 => 'User-defined 2', 141 => 'Write to pipe with no one reading', 142 => 'Signal raised by alarm', 143 => 'Termination (request to terminate)', 145 => 'Child process terminated, stopped (or continued*)', 146 => 'Continue if stopped', 147 => 'Stop executing temporarily', 148 => 'Terminal stop signal', 149 => 'Background process attempting to read from tty ("in")', 150 => 'Background process attempting to write to tty ("out")', 151 => 'Urgent data available on socket', 152 => 'CPU time limit exceeded', 153 => 'File size limit exceeded', 154 => 'Signal raised by timer counting virtual time: "virtual timer expired"', 155 => 'Profiling timer expired', 157 => 'Pollable event', 159 => 'Bad syscall', ]; public function __construct(array $command, ?string $cwd = null, ?array $env = null, $input = null, ?float $timeout = 60) { if (!\function_exists('proc_open')) { throw new LogicException('The Process class relies on proc_open, which is not available on your PHP installation.'); } $this->commandline = $command; $this->cwd = $cwd; if (null === $this->cwd && (\defined('ZEND_THREAD_SAFE') || '\\' === \DIRECTORY_SEPARATOR)) { $this->cwd = getcwd(); } if (null !== $env) { $this->setEnv($env); } $this->setInput($input); $this->setTimeout($timeout); $this->useFileHandles = '\\' === \DIRECTORY_SEPARATOR; $this->pty = false; } public static function fromShellCommandline(string $command, ?string $cwd = null, ?array $env = null, $input = null, ?float $timeout = 60) { $process = new static([], $cwd, $env, $input, $timeout); $process->commandline = $command; return $process; } public function __sleep() { throw new \BadMethodCallException('Cannot serialize '.__CLASS__); } public function __wakeup() { throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); } public function __destruct() { if ($this->options['create_new_console'] ?? false) { $this->processPipes->close(); } else { $this->stop(0); } } public function __clone() { $this->resetProcessData(); } public function run(?callable $callback = null, array $env = []): int { $this->start($callback, $env); return $this->wait(); } public function mustRun(?callable $callback = null, array $env = []): self { if (0 !== $this->run($callback, $env)) { throw new ProcessFailedException($this); } return $this; } public function start(?callable $callback = null, array $env = []) { if ($this->isRunning()) { throw new RuntimeException('Process is already running.'); } $this->resetProcessData(); $this->starttime = $this->lastOutputTime = microtime(true); $this->callback = $this->buildCallback($callback); $this->hasCallback = null !== $callback; $descriptors = $this->getDescriptors(); if ($this->env) { $env += '\\' === \DIRECTORY_SEPARATOR ? array_diff_ukey($this->env, $env, 'strcasecmp') : $this->env; } $env += '\\' === \DIRECTORY_SEPARATOR ? array_diff_ukey($this->getDefaultEnv(), $env, 'strcasecmp') : $this->getDefaultEnv(); if (\is_array($commandline = $this->commandline)) { $commandline = implode(' ', array_map([$this, 'escapeArgument'], $commandline)); if ('\\' !== \DIRECTORY_SEPARATOR) { $commandline = 'exec '.$commandline; } } else { $commandline = $this->replacePlaceholders($commandline, $env); } if ('\\' === \DIRECTORY_SEPARATOR) { $commandline = $this->prepareWindowsCommandLine($commandline, $env); } elseif (!$this->useFileHandles && $this->isSigchildEnabled()) { $descriptors[3] = ['pipe', 'w']; $commandline = '{ ('.$commandline.') <&3 3<&- 3>/dev/null & } 3<&0;'; $commandline .= 'pid=$!; echo $pid >&3; wait $pid 2>/dev/null; code=$?; echo $code >&3; exit $code'; $ptsWorkaround = fopen(__FILE__, 'r'); } $envPairs = []; foreach ($env as $k => $v) { if (false !== $v && false === \in_array($k, ['argc', 'argv', 'ARGC', 'ARGV'], true)) { $envPairs[] = $k.'='.$v; } } if (!is_dir($this->cwd)) { throw new RuntimeException(sprintf('The provided cwd "%s" does not exist.', $this->cwd)); } $this->process = @proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $this->options); if (!$this->process) { throw new RuntimeException('Unable to launch a new process.'); } $this->status = self::STATUS_STARTED; if (isset($descriptors[3])) { $this->fallbackStatus['pid'] = (int) fgets($this->processPipes->pipes[3]); } if ($this->tty) { return; } $this->updateStatus(false); $this->checkTimeout(); } public function restart(?callable $callback = null, array $env = []): self { if ($this->isRunning()) { throw new RuntimeException('Process is already running.'); } $process = clone $this; $process->start($callback, $env); return $process; } public function wait(?callable $callback = null) { $this->requireProcessIsStarted(__FUNCTION__); $this->updateStatus(false); if (null !== $callback) { if (!$this->processPipes->haveReadSupport()) { $this->stop(0); throw new LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::wait".'); } $this->callback = $this->buildCallback($callback); } do { $this->checkTimeout(); $running = $this->isRunning() && ('\\' === \DIRECTORY_SEPARATOR || $this->processPipes->areOpen()); $this->readPipes($running, '\\' !== \DIRECTORY_SEPARATOR || !$running); } while ($running); while ($this->isRunning()) { $this->checkTimeout(); usleep(1000); } if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) { throw new ProcessSignaledException($this); } return $this->exitcode; } public function waitUntil(callable $callback): bool { $this->requireProcessIsStarted(__FUNCTION__); $this->updateStatus(false); if (!$this->processPipes->haveReadSupport()) { $this->stop(0); throw new LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::waitUntil".'); } $callback = $this->buildCallback($callback); $ready = false; while (true) { $this->checkTimeout(); $running = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen(); $output = $this->processPipes->readAndWrite($running, '\\' !== \DIRECTORY_SEPARATOR || !$running); foreach ($output as $type => $data) { if (3 !== $type) { $ready = $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data) || $ready; } elseif (!isset($this->fallbackStatus['signaled'])) { $this->fallbackStatus['exitcode'] = (int) $data; } } if ($ready) { return true; } if (!$running) { return false; } usleep(1000); } } public function getPid() { return $this->isRunning() ? $this->processInformation['pid'] : null; } public function signal(int $signal) { $this->doSignal($signal, true); return $this; } public function disableOutput() { if ($this->isRunning()) { throw new RuntimeException('Disabling output while the process is running is not possible.'); } if (null !== $this->idleTimeout) { throw new LogicException('Output cannot be disabled while an idle timeout is set.'); } $this->outputDisabled = true; return $this; } public function enableOutput() { if ($this->isRunning()) { throw new RuntimeException('Enabling output while the process is running is not possible.'); } $this->outputDisabled = false; return $this; } public function isOutputDisabled() { return $this->outputDisabled; } public function getOutput() { $this->readPipesForOutput(__FUNCTION__); if (false === $ret = stream_get_contents($this->stdout, -1, 0)) { return ''; } return $ret; } public function getIncrementalOutput() { $this->readPipesForOutput(__FUNCTION__); $latest = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset); $this->incrementalOutputOffset = ftell($this->stdout); if (false === $latest) { return ''; } return $latest; } #[\ReturnTypeWillChange] public function getIterator(int $flags = 0) { $this->readPipesForOutput(__FUNCTION__, false); $clearOutput = !(self::ITER_KEEP_OUTPUT & $flags); $blocking = !(self::ITER_NON_BLOCKING & $flags); $yieldOut = !(self::ITER_SKIP_OUT & $flags); $yieldErr = !(self::ITER_SKIP_ERR & $flags); while (null !== $this->callback || ($yieldOut && !feof($this->stdout)) || ($yieldErr && !feof($this->stderr))) { if ($yieldOut) { $out = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset); if (isset($out[0])) { if ($clearOutput) { $this->clearOutput(); } else { $this->incrementalOutputOffset = ftell($this->stdout); } yield self::OUT => $out; } } if ($yieldErr) { $err = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset); if (isset($err[0])) { if ($clearOutput) { $this->clearErrorOutput(); } else { $this->incrementalErrorOutputOffset = ftell($this->stderr); } yield self::ERR => $err; } } if (!$blocking && !isset($out[0]) && !isset($err[0])) { yield self::OUT => ''; } $this->checkTimeout(); $this->readPipesForOutput(__FUNCTION__, $blocking); } } public function clearOutput() { ftruncate($this->stdout, 0); fseek($this->stdout, 0); $this->incrementalOutputOffset = 0; return $this; } public function getErrorOutput() { $this->readPipesForOutput(__FUNCTION__); if (false === $ret = stream_get_contents($this->stderr, -1, 0)) { return ''; } return $ret; } public function getIncrementalErrorOutput() { $this->readPipesForOutput(__FUNCTION__); $latest = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset); $this->incrementalErrorOutputOffset = ftell($this->stderr); if (false === $latest) { return ''; } return $latest; } public function clearErrorOutput() { ftruncate($this->stderr, 0); fseek($this->stderr, 0); $this->incrementalErrorOutputOffset = 0; return $this; } public function getExitCode() { $this->updateStatus(false); return $this->exitcode; } public function getExitCodeText() { if (null === $exitcode = $this->getExitCode()) { return null; } return self::$exitCodes[$exitcode] ?? 'Unknown error'; } public function isSuccessful() { return 0 === $this->getExitCode(); } public function hasBeenSignaled() { $this->requireProcessIsTerminated(__FUNCTION__); return $this->processInformation['signaled']; } public function getTermSignal() { $this->requireProcessIsTerminated(__FUNCTION__); if ($this->isSigchildEnabled() && -1 === $this->processInformation['termsig']) { throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal cannot be retrieved.'); } return $this->processInformation['termsig']; } public function hasBeenStopped() { $this->requireProcessIsTerminated(__FUNCTION__); return $this->processInformation['stopped']; } public function getStopSignal() { $this->requireProcessIsTerminated(__FUNCTION__); return $this->processInformation['stopsig']; } public function isRunning() { if (self::STATUS_STARTED !== $this->status) { return false; } $this->updateStatus(false); return $this->processInformation['running']; } public function isStarted() { return self::STATUS_READY != $this->status; } public function isTerminated() { $this->updateStatus(false); return self::STATUS_TERMINATED == $this->status; } public function getStatus() { $this->updateStatus(false); return $this->status; } public function stop(float $timeout = 10, ?int $signal = null) { $timeoutMicro = microtime(true) + $timeout; if ($this->isRunning()) { $this->doSignal(15, false); do { usleep(1000); } while ($this->isRunning() && microtime(true) < $timeoutMicro); if ($this->isRunning()) { $this->doSignal($signal ?: 9, false); } } if ($this->isRunning()) { if (isset($this->fallbackStatus['pid'])) { unset($this->fallbackStatus['pid']); return $this->stop(0, $signal); } $this->close(); } return $this->exitcode; } public function addOutput(string $line) { $this->lastOutputTime = microtime(true); fseek($this->stdout, 0, \SEEK_END); fwrite($this->stdout, $line); fseek($this->stdout, $this->incrementalOutputOffset); } public function addErrorOutput(string $line) { $this->lastOutputTime = microtime(true); fseek($this->stderr, 0, \SEEK_END); fwrite($this->stderr, $line); fseek($this->stderr, $this->incrementalErrorOutputOffset); } public function getLastOutputTime(): ?float { return $this->lastOutputTime; } public function getCommandLine() { return \is_array($this->commandline) ? implode(' ', array_map([$this, 'escapeArgument'], $this->commandline)) : $this->commandline; } public function getTimeout() { return $this->timeout; } public function getIdleTimeout() { return $this->idleTimeout; } public function setTimeout(?float $timeout) { $this->timeout = $this->validateTimeout($timeout); return $this; } public function setIdleTimeout(?float $timeout) { if (null !== $timeout && $this->outputDisabled) { throw new LogicException('Idle timeout cannot be set while the output is disabled.'); } $this->idleTimeout = $this->validateTimeout($timeout); return $this; } public function setTty(bool $tty) { if ('\\' === \DIRECTORY_SEPARATOR && $tty) { throw new RuntimeException('TTY mode is not supported on Windows platform.'); } if ($tty && !self::isTtySupported()) { throw new RuntimeException('TTY mode requires /dev/tty to be read/writable.'); } $this->tty = $tty; return $this; } public function isTty() { return $this->tty; } public function setPty(bool $bool) { $this->pty = $bool; return $this; } public function isPty() { return $this->pty; } public function getWorkingDirectory() { if (null === $this->cwd) { return getcwd() ?: null; } return $this->cwd; } public function setWorkingDirectory(string $cwd) { $this->cwd = $cwd; return $this; } public function getEnv() { return $this->env; } public function setEnv(array $env) { $this->env = $env; return $this; } public function getInput() { return $this->input; } public function setInput($input) { if ($this->isRunning()) { throw new LogicException('Input cannot be set while the process is running.'); } $this->input = ProcessUtils::validateInput(__METHOD__, $input); return $this; } public function checkTimeout() { if (self::STATUS_STARTED !== $this->status) { return; } if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) { $this->stop(0); throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_GENERAL); } if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) { $this->stop(0); throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_IDLE); } } public function getStartTime(): float { if (!$this->isStarted()) { throw new LogicException('Start time is only available after process start.'); } return $this->starttime; } public function setOptions(array $options) { if ($this->isRunning()) { throw new RuntimeException('Setting options while the process is running is not possible.'); } $defaultOptions = $this->options; $existingOptions = ['blocking_pipes', 'create_process_group', 'create_new_console']; foreach ($options as $key => $value) { if (!\in_array($key, $existingOptions)) { $this->options = $defaultOptions; throw new LogicException(sprintf('Invalid option "%s" passed to "%s()". Supported options are "%s".', $key, __METHOD__, implode('", "', $existingOptions))); } $this->options[$key] = $value; } } public static function isTtySupported(): bool { static $isTtySupported; if (null === $isTtySupported) { $isTtySupported = (bool) @proc_open('echo 1 >/dev/null', [['file', '/dev/tty', 'r'], ['file', '/dev/tty', 'w'], ['file', '/dev/tty', 'w']], $pipes); } return $isTtySupported; } public static function isPtySupported() { static $result; if (null !== $result) { return $result; } if ('\\' === \DIRECTORY_SEPARATOR) { return $result = false; } return $result = (bool) @proc_open('echo 1 >/dev/null', [['pty'], ['pty'], ['pty']], $pipes); } private function getDescriptors(): array { if ($this->input instanceof \Iterator) { $this->input->rewind(); } if ('\\' === \DIRECTORY_SEPARATOR) { $this->processPipes = new WindowsPipes($this->input, !$this->outputDisabled || $this->hasCallback); } else { $this->processPipes = new UnixPipes($this->isTty(), $this->isPty(), $this->input, !$this->outputDisabled || $this->hasCallback); } return $this->processPipes->getDescriptors(); } protected function buildCallback(?callable $callback = null) { if ($this->outputDisabled) { return function ($type, $data) use ($callback): bool { return null !== $callback && $callback($type, $data); }; } $out = self::OUT; return function ($type, $data) use ($callback, $out): bool { if ($out == $type) { $this->addOutput($data); } else { $this->addErrorOutput($data); } return null !== $callback && $callback($type, $data); }; } protected function updateStatus(bool $blocking) { if (self::STATUS_STARTED !== $this->status) { return; } $this->processInformation = proc_get_status($this->process); $running = $this->processInformation['running']; if (\PHP_VERSION_ID < 80300) { if (!isset($this->cachedExitCode) && !$running && -1 !== $this->processInformation['exitcode']) { $this->cachedExitCode = $this->processInformation['exitcode']; } if (isset($this->cachedExitCode) && !$running && -1 === $this->processInformation['exitcode']) { $this->processInformation['exitcode'] = $this->cachedExitCode; } } $this->readPipes($running && $blocking, '\\' !== \DIRECTORY_SEPARATOR || !$running); if ($this->fallbackStatus && $this->isSigchildEnabled()) { $this->processInformation = $this->fallbackStatus + $this->processInformation; } if (!$running) { $this->close(); } } protected function isSigchildEnabled() { if (null !== self::$sigchild) { return self::$sigchild; } if (!\function_exists('phpinfo')) { return self::$sigchild = false; } ob_start(); phpinfo(\INFO_GENERAL); return self::$sigchild = str_contains(ob_get_clean(), '--enable-sigchild'); } private function readPipesForOutput(string $caller, bool $blocking = false) { if ($this->outputDisabled) { throw new LogicException('Output has been disabled.'); } $this->requireProcessIsStarted($caller); $this->updateStatus($blocking); } private function validateTimeout(?float $timeout): ?float { $timeout = (float) $timeout; if (0.0 === $timeout) { $timeout = null; } elseif ($timeout < 0) { throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.'); } return $timeout; } private function readPipes(bool $blocking, bool $close) { $result = $this->processPipes->readAndWrite($blocking, $close); $callback = $this->callback; foreach ($result as $type => $data) { if (3 !== $type) { $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data); } elseif (!isset($this->fallbackStatus['signaled'])) { $this->fallbackStatus['exitcode'] = (int) $data; } } } private function close(): int { $this->processPipes->close(); if ($this->process) { proc_close($this->process); $this->process = null; } $this->exitcode = $this->processInformation['exitcode']; $this->status = self::STATUS_TERMINATED; if (-1 === $this->exitcode) { if ($this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) { $this->exitcode = 128 + $this->processInformation['termsig']; } elseif ($this->isSigchildEnabled()) { $this->processInformation['signaled'] = true; $this->processInformation['termsig'] = -1; } } $this->callback = null; return $this->exitcode; } private function resetProcessData() { $this->starttime = null; $this->callback = null; $this->exitcode = null; $this->fallbackStatus = []; $this->processInformation = null; $this->stdout = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+'); $this->stderr = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+'); $this->process = null; $this->latestSignal = null; $this->status = self::STATUS_READY; $this->incrementalOutputOffset = 0; $this->incrementalErrorOutputOffset = 0; } private function doSignal(int $signal, bool $throwException): bool { if (null === $pid = $this->getPid()) { if ($throwException) { throw new LogicException('Cannot send signal on a non running process.'); } return false; } if ('\\' === \DIRECTORY_SEPARATOR) { exec(sprintf('taskkill /F /T /PID %d 2>&1', $pid), $output, $exitCode); if ($exitCode && $this->isRunning()) { if ($throwException) { throw new RuntimeException(sprintf('Unable to kill the process (%s).', implode(' ', $output))); } return false; } } else { if (!$this->isSigchildEnabled()) { $ok = @proc_terminate($this->process, $signal); } elseif (\function_exists('posix_kill')) { $ok = @posix_kill($pid, $signal); } elseif ($ok = proc_open(sprintf('kill -%d %d', $signal, $pid), [2 => ['pipe', 'w']], $pipes)) { $ok = false === fgets($pipes[2]); } if (!$ok) { if ($throwException) { throw new RuntimeException(sprintf('Error while sending signal "%s".', $signal)); } return false; } } $this->latestSignal = $signal; $this->fallbackStatus['signaled'] = true; $this->fallbackStatus['exitcode'] = -1; $this->fallbackStatus['termsig'] = $this->latestSignal; return true; } private function prepareWindowsCommandLine(string $cmd, array &$env): string { $uid = uniqid('', true); $varCount = 0; $varCache = []; $cmd = preg_replace_callback( '/"(?:( [^"%!^]*+ (?: (?: !LF! | "(?:\^[%!^])?+" ) [^"%!^]*+ )++ ) | [^"]*+ )"/x', function ($m) use (&$env, &$varCache, &$varCount, $uid) { if (!isset($m[1])) { return $m[0]; } if (isset($varCache[$m[0]])) { return $varCache[$m[0]]; } if (str_contains($value = $m[1], "\0")) { $value = str_replace("\0", '?', $value); } if (false === strpbrk($value, "\"%!\n")) { return '"'.$value.'"'; } $value = str_replace(['!LF!', '"^!"', '"^%"', '"^^"', '""'], ["\n", '!', '%', '^', '"'], $value); $value = '"'.preg_replace('/(\\\\*)"/', '$1$1\\"', $value).'"'; $var = $uid.++$varCount; $env[$var] = $value; return $varCache[$m[0]] = '!'.$var.'!'; }, $cmd ); static $comSpec; if (!$comSpec && $comSpec = (new ExecutableFinder())->find('cmd.exe')) { $comSpec = '"'.preg_replace('{(\\\\*+)"}', '$1$1\"', $comSpec) .'"'; } $cmd = ($comSpec ?? 'cmd').' /V:ON /E:ON /D /C ('.str_replace("\n", ' ', $cmd).')'; foreach ($this->processPipes->getFiles() as $offset => $filename) { $cmd .= ' '.$offset.'>"'.$filename.'"'; } return $cmd; } private function requireProcessIsStarted(string $functionName) { if (!$this->isStarted()) { throw new LogicException(sprintf('Process must be started before calling "%s()".', $functionName)); } } private function requireProcessIsTerminated(string $functionName) { if (!$this->isTerminated()) { throw new LogicException(sprintf('Process must be terminated before calling "%s()".', $functionName)); } } private function escapeArgument(?string $argument): string { if ('' === $argument || null === $argument) { return '""'; } if ('\\' !== \DIRECTORY_SEPARATOR) { return "'".str_replace("'", "'\\''", $argument)."'"; } if (str_contains($argument, "\0")) { $argument = str_replace("\0", '?', $argument); } if (!preg_match('/[()%!^"<>&|\s[\]=;*?\'$]/', $argument)) { return $argument; } $argument = preg_replace('/(\\\\+)$/', '$1$1', $argument); return '"'.str_replace(['"', '^', '%', '!', "\n"], ['""', '"^^"', '"^%"', '"^!"', '!LF!'], $argument).'"'; } private function replacePlaceholders(string $commandline, array $env) { return preg_replace_callback('/"\$\{:([_a-zA-Z]++[_a-zA-Z0-9]*+)\}"/', function ($matches) use ($commandline, $env) { if (!isset($env[$matches[1]]) || false === $env[$matches[1]]) { throw new InvalidArgumentException(sprintf('Command line is missing a value for parameter "%s": ', $matches[1]).$commandline); } return $this->escapeArgument($env[$matches[1]]); }, $commandline); } private function getDefaultEnv(): array { $env = getenv(); $env = ('\\' === \DIRECTORY_SEPARATOR ? array_intersect_ukey($env, $_SERVER, 'strcasecmp') : array_intersect_key($env, $_SERVER)) ?: $env; return $_ENV + ('\\' === \DIRECTORY_SEPARATOR ? array_diff_ukey($env, $_ENV, 'strcasecmp') : $env); } } getIterator($input::ITER_SKIP_ERR); } if ($input instanceof \Iterator) { return $input; } if ($input instanceof \Traversable) { return new \IteratorIterator($input); } throw new InvalidArgumentException(sprintf('"%s" only accepts strings, Traversable objects or stream resources.', $caller)); } return $input; } } factories = $factories; } public function has(string $id) { return isset($this->factories[$id]); } public function get(string $id) { if (!isset($this->factories[$id])) { throw $this->createNotFoundException($id); } if (isset($this->loading[$id])) { $ids = array_values($this->loading); $ids = \array_slice($this->loading, array_search($id, $ids)); $ids[] = $id; throw $this->createCircularReferenceException($id, $ids); } $this->loading[$id] = $id; try { return $this->factories[$id]($this); } finally { unset($this->loading[$id]); } } public function getProvidedServices(): array { if (null === $this->providedTypes) { $this->providedTypes = []; foreach ($this->factories as $name => $factory) { if (!\is_callable($factory)) { $this->providedTypes[$name] = '?'; } else { $type = (new \ReflectionFunction($factory))->getReturnType(); $this->providedTypes[$name] = $type ? ($type->allowsNull() ? '?' : '').($type instanceof \ReflectionNamedType ? $type->getName() : $type) : '?'; } } } return $this->providedTypes; } private function createNotFoundException(string $id): NotFoundExceptionInterface { if (!$alternatives = array_keys($this->factories)) { $message = 'is empty...'; } else { $last = array_pop($alternatives); if ($alternatives) { $message = sprintf('only knows about the "%s" and "%s" services.', implode('", "', $alternatives), $last); } else { $message = sprintf('only knows about the "%s" service.', $last); } } if ($this->loading) { $message = sprintf('The service "%s" has a dependency on a non-existent service "%s". This locator %s', end($this->loading), $id, $message); } else { $message = sprintf('Service "%s" not found: the current service locator %s', $id, $message); } return new class($message) extends \InvalidArgumentException implements NotFoundExceptionInterface { }; } private function createCircularReferenceException(string $id, array $path): ContainerExceptionInterface { return new class(sprintf('Circular reference detected for service "%s", path: "%s".', $id, implode(' -> ', $path))) extends \RuntimeException implements ContainerExceptionInterface { }; } } = 80000) { foreach ((new \ReflectionClass(self::class))->getMethods() as $method) { if (self::class !== $method->getDeclaringClass()->name) { continue; } if (!$attribute = $method->getAttributes(SubscribedService::class)[0] ?? null) { continue; } if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) { throw new \LogicException(sprintf('Cannot use "%s" on method "%s::%s()" (can only be used on non-static, non-abstract methods with no parameters).', SubscribedService::class, self::class, $method->name)); } if (!$returnType = $method->getReturnType()) { throw new \LogicException(sprintf('Cannot use "%s" on methods without a return type in "%s::%s()".', SubscribedService::class, $method->name, self::class)); } $serviceId = $returnType instanceof \ReflectionNamedType ? $returnType->getName() : (string) $returnType; if ($returnType->allowsNull()) { $serviceId = '?'.$serviceId; } $services[$attribute->newInstance()->key ?? self::class.'::'.$method->name] = $serviceId; $attributeOptIn = true; } } if (!$attributeOptIn) { foreach ((new \ReflectionClass(self::class))->getMethods() as $method) { if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) { continue; } if (self::class !== $method->getDeclaringClass()->name) { continue; } if (!($returnType = $method->getReturnType()) instanceof \ReflectionNamedType) { continue; } if ($returnType->isBuiltin()) { continue; } if (\PHP_VERSION_ID >= 80000) { trigger_deprecation('symfony/service-contracts', '2.5', 'Using "%s" in "%s" without using the "%s" attribute on any method is deprecated.', ServiceSubscriberTrait::class, self::class, SubscribedService::class); } $services[self::class.'::'.$method->name] = '?'.($returnType instanceof \ReflectionNamedType ? $returnType->getName() : $returnType); } } return $services; } public function setContainer(ContainerInterface $container) { $ret = null; if (method_exists(get_parent_class(self::class) ?: '', __FUNCTION__)) { $ret = parent::setContainer($container); } $this->container = $container; return $ret; } } getServiceLocator([ 'foo' => function () { return 'bar'; }, 'bar' => function () { return 'baz'; }, function () { return 'dummy'; }, ]); $this->assertTrue($locator->has('foo')); $this->assertTrue($locator->has('bar')); $this->assertFalse($locator->has('dummy')); } public function testGet() { $locator = $this->getServiceLocator([ 'foo' => function () { return 'bar'; }, 'bar' => function () { return 'baz'; }, ]); $this->assertSame('bar', $locator->get('foo')); $this->assertSame('baz', $locator->get('bar')); } public function testGetDoesNotMemoize() { $i = 0; $locator = $this->getServiceLocator([ 'foo' => function () use (&$i) { ++$i; return 'bar'; }, ]); $this->assertSame('bar', $locator->get('foo')); $this->assertSame('bar', $locator->get('foo')); $this->assertSame(2, $i); } public function testThrowsOnUndefinedInternalService() { if (!$this->getExpectedException()) { $this->expectException(\Psr\Container\NotFoundExceptionInterface::class); $this->expectExceptionMessage('The service "foo" has a dependency on a non-existent service "bar". This locator only knows about the "foo" service.'); } $locator = $this->getServiceLocator([ 'foo' => function () use (&$locator) { return $locator->get('bar'); }, ]); $locator->get('foo'); } public function testThrowsOnCircularReference() { $this->expectException(\Psr\Container\ContainerExceptionInterface::class); $this->expectExceptionMessage('Circular reference detected for service "bar", path: "bar -> baz -> bar".'); $locator = $this->getServiceLocator([ 'foo' => function () use (&$locator) { return $locator->get('bar'); }, 'bar' => function () use (&$locator) { return $locator->get('baz'); }, 'baz' => function () use (&$locator) { return $locator->get('bar'); }, ]); $locator->get('foo'); } } $v) { if ($v instanceof self) { $values[$k] = $v->__toString(); } elseif (\is_array($v) && $values[$k] !== $v = static::unwrap($v)) { $values[$k] = $v; } } return $values; } public static function wrap(array $values): array { $i = 0; $keys = null; foreach ($values as $k => $v) { if (\is_string($k) && '' !== $k && $k !== $j = (string) new static($k)) { $keys = $keys ?? array_keys($values); $keys[$i] = $j; } if (\is_string($v)) { $values[$k] = new static($v); } elseif (\is_array($v) && $values[$k] !== $v = static::wrap($v)) { $values[$k] = $v; } ++$i; } return null !== $keys ? array_combine($keys, $values) : $values; } public function after($needle, bool $includeNeedle = false, int $offset = 0): self { $str = clone $this; $i = \PHP_INT_MAX; foreach ((array) $needle as $n) { $n = (string) $n; $j = $this->indexOf($n, $offset); if (null !== $j && $j < $i) { $i = $j; $str->string = $n; } } if (\PHP_INT_MAX === $i) { return $str; } if (!$includeNeedle) { $i += $str->length(); } return $this->slice($i); } public function afterLast($needle, bool $includeNeedle = false, int $offset = 0): self { $str = clone $this; $i = null; foreach ((array) $needle as $n) { $n = (string) $n; $j = $this->indexOfLast($n, $offset); if (null !== $j && $j >= $i) { $i = $offset = $j; $str->string = $n; } } if (null === $i) { return $str; } if (!$includeNeedle) { $i += $str->length(); } return $this->slice($i); } abstract public function append(string ...$suffix): self; public function before($needle, bool $includeNeedle = false, int $offset = 0): self { $str = clone $this; $i = \PHP_INT_MAX; foreach ((array) $needle as $n) { $n = (string) $n; $j = $this->indexOf($n, $offset); if (null !== $j && $j < $i) { $i = $j; $str->string = $n; } } if (\PHP_INT_MAX === $i) { return $str; } if ($includeNeedle) { $i += $str->length(); } return $this->slice(0, $i); } public function beforeLast($needle, bool $includeNeedle = false, int $offset = 0): self { $str = clone $this; $i = null; foreach ((array) $needle as $n) { $n = (string) $n; $j = $this->indexOfLast($n, $offset); if (null !== $j && $j >= $i) { $i = $offset = $j; $str->string = $n; } } if (null === $i) { return $str; } if ($includeNeedle) { $i += $str->length(); } return $this->slice(0, $i); } public function bytesAt(int $offset): array { $str = $this->slice($offset, 1); return '' === $str->string ? [] : array_values(unpack('C*', $str->string)); } abstract public function camel(): self; abstract public function chunk(int $length = 1): array; public function collapseWhitespace(): self { $str = clone $this; $str->string = trim(preg_replace("/(?:[ \n\r\t\x0C]{2,}+|[\n\r\t\x0C])/", ' ', $str->string), " \n\r\t\x0C"); return $str; } public function containsAny($needle): bool { return null !== $this->indexOf($needle); } public function endsWith($suffix): bool { if (!\is_array($suffix) && !$suffix instanceof \Traversable) { throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); } foreach ($suffix as $s) { if ($this->endsWith((string) $s)) { return true; } } return false; } public function ensureEnd(string $suffix): self { if (!$this->endsWith($suffix)) { return $this->append($suffix); } $suffix = preg_quote($suffix); $regex = '{('.$suffix.')(?:'.$suffix.')++$}D'; return $this->replaceMatches($regex.($this->ignoreCase ? 'i' : ''), '$1'); } public function ensureStart(string $prefix): self { $prefix = new static($prefix); if (!$this->startsWith($prefix)) { return $this->prepend($prefix); } $str = clone $this; $i = $prefixLen = $prefix->length(); while ($this->indexOf($prefix, $i) === $i) { $str = $str->slice($prefixLen); $i += $prefixLen; } return $str; } public function equalsTo($string): bool { if (!\is_array($string) && !$string instanceof \Traversable) { throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); } foreach ($string as $s) { if ($this->equalsTo((string) $s)) { return true; } } return false; } abstract public function folded(): self; public function ignoreCase(): self { $str = clone $this; $str->ignoreCase = true; return $str; } public function indexOf($needle, int $offset = 0): ?int { if (!\is_array($needle) && !$needle instanceof \Traversable) { throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); } $i = \PHP_INT_MAX; foreach ($needle as $n) { $j = $this->indexOf((string) $n, $offset); if (null !== $j && $j < $i) { $i = $j; } } return \PHP_INT_MAX === $i ? null : $i; } public function indexOfLast($needle, int $offset = 0): ?int { if (!\is_array($needle) && !$needle instanceof \Traversable) { throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); } $i = null; foreach ($needle as $n) { $j = $this->indexOfLast((string) $n, $offset); if (null !== $j && $j >= $i) { $i = $offset = $j; } } return $i; } public function isEmpty(): bool { return '' === $this->string; } abstract public function join(array $strings, ?string $lastGlue = null): self; public function jsonSerialize(): string { return $this->string; } abstract public function length(): int; abstract public function lower(): self; abstract public function match(string $regexp, int $flags = 0, int $offset = 0): array; abstract public function padBoth(int $length, string $padStr = ' '): self; abstract public function padEnd(int $length, string $padStr = ' '): self; abstract public function padStart(int $length, string $padStr = ' '): self; abstract public function prepend(string ...$prefix): self; public function repeat(int $multiplier): self { if (0 > $multiplier) { throw new InvalidArgumentException(sprintf('Multiplier must be positive, %d given.', $multiplier)); } $str = clone $this; $str->string = str_repeat($str->string, $multiplier); return $str; } abstract public function replace(string $from, string $to): self; abstract public function replaceMatches(string $fromRegexp, $to): self; abstract public function reverse(): self; abstract public function slice(int $start = 0, ?int $length = null): self; abstract public function snake(): self; abstract public function splice(string $replacement, int $start = 0, ?int $length = null): self; public function split(string $delimiter, ?int $limit = null, ?int $flags = null): array { if (null === $flags) { throw new \TypeError('Split behavior when $flags is null must be implemented by child classes.'); } if ($this->ignoreCase) { $delimiter .= 'i'; } set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); }); try { if (false === $chunks = preg_split($delimiter, $this->string, $limit, $flags)) { $lastError = preg_last_error(); foreach (get_defined_constants(true)['pcre'] as $k => $v) { if ($lastError === $v && '_ERROR' === substr($k, -6)) { throw new RuntimeException('Splitting failed with '.$k.'.'); } } throw new RuntimeException('Splitting failed with unknown error code.'); } } finally { restore_error_handler(); } $str = clone $this; if (self::PREG_SPLIT_OFFSET_CAPTURE & $flags) { foreach ($chunks as &$chunk) { $str->string = $chunk[0]; $chunk[0] = clone $str; } } else { foreach ($chunks as &$chunk) { $str->string = $chunk; $chunk = clone $str; } } return $chunks; } public function startsWith($prefix): bool { if (!\is_array($prefix) && !$prefix instanceof \Traversable) { throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); } foreach ($prefix as $prefix) { if ($this->startsWith((string) $prefix)) { return true; } } return false; } abstract public function title(bool $allWords = false): self; public function toByteString(?string $toEncoding = null): ByteString { $b = new ByteString(); $toEncoding = \in_array($toEncoding, ['utf8', 'utf-8', 'UTF8'], true) ? 'UTF-8' : $toEncoding; if (null === $toEncoding || $toEncoding === $fromEncoding = $this instanceof AbstractUnicodeString || preg_match('//u', $b->string) ? 'UTF-8' : 'Windows-1252') { $b->string = $this->string; return $b; } set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); }); try { try { $b->string = mb_convert_encoding($this->string, $toEncoding, 'UTF-8'); } catch (InvalidArgumentException|\ValueError $e) { if (!\function_exists('iconv')) { if ($e instanceof \ValueError) { throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); } throw $e; } $b->string = iconv('UTF-8', $toEncoding, $this->string); } } finally { restore_error_handler(); } return $b; } public function toCodePointString(): CodePointString { return new CodePointString($this->string); } public function toString(): string { return $this->string; } public function toUnicodeString(): UnicodeString { return new UnicodeString($this->string); } abstract public function trim(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): self; abstract public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): self; public function trimPrefix($prefix): self { if (\is_array($prefix) || $prefix instanceof \Traversable) { foreach ($prefix as $s) { $t = $this->trimPrefix($s); if ($t->string !== $this->string) { return $t; } } return clone $this; } $str = clone $this; if ($prefix instanceof self) { $prefix = $prefix->string; } else { $prefix = (string) $prefix; } if ('' !== $prefix && \strlen($this->string) >= \strlen($prefix) && 0 === substr_compare($this->string, $prefix, 0, \strlen($prefix), $this->ignoreCase)) { $str->string = substr($this->string, \strlen($prefix)); } return $str; } abstract public function trimStart(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): self; public function trimSuffix($suffix): self { if (\is_array($suffix) || $suffix instanceof \Traversable) { foreach ($suffix as $s) { $t = $this->trimSuffix($s); if ($t->string !== $this->string) { return $t; } } return clone $this; } $str = clone $this; if ($suffix instanceof self) { $suffix = $suffix->string; } else { $suffix = (string) $suffix; } if ('' !== $suffix && \strlen($this->string) >= \strlen($suffix) && 0 === substr_compare($this->string, $suffix, -\strlen($suffix), null, $this->ignoreCase)) { $str->string = substr($this->string, 0, -\strlen($suffix)); } return $str; } public function truncate(int $length, string $ellipsis = '', bool $cut = true): self { $stringLength = $this->length(); if ($stringLength <= $length) { return clone $this; } $ellipsisLength = '' !== $ellipsis ? (new static($ellipsis))->length() : 0; if ($length < $ellipsisLength) { $ellipsisLength = 0; } if (!$cut) { if (null === $length = $this->indexOf([' ', "\r", "\n", "\t"], ($length ?: 1) - 1)) { return clone $this; } $length += $ellipsisLength; } $str = $this->slice(0, $length - $ellipsisLength); return $ellipsisLength ? $str->trimEnd()->append($ellipsis) : $str; } abstract public function upper(): self; abstract public function width(bool $ignoreAnsiDecoration = true): int; public function wordwrap(int $width = 75, string $break = "\n", bool $cut = false): self { $lines = '' !== $break ? $this->split($break) : [clone $this]; $chars = []; $mask = ''; if (1 === \count($lines) && '' === $lines[0]->string) { return $lines[0]; } foreach ($lines as $i => $line) { if ($i) { $chars[] = $break; $mask .= '#'; } foreach ($line->chunk() as $char) { $chars[] = $char->string; $mask .= ' ' === $char->string ? ' ' : '?'; } } $string = ''; $j = 0; $b = $i = -1; $mask = wordwrap($mask, $width, '#', $cut); while (false !== $b = strpos($mask, '#', $b + 1)) { for (++$i; $i < $b; ++$i) { $string .= $chars[$j]; unset($chars[$j++]); } if ($break === $chars[$j] || ' ' === $chars[$j]) { unset($chars[$j++]); } $string .= $break; } $str = clone $this; $str->string = $string.implode('', $chars); return $str; } public function __sleep(): array { return ['string']; } public function __clone() { $this->ignoreCase = false; } public function __toString(): string { return $this->string; } } >', '<', '>', '-', '-', '-', '-', '-', '-', '-', '-', '-', '||', '/', '[', ']', '*', ',', '.', '<', '>', '<<', '>>', '[', ']', '[', ']', '[', ']', ',', '.', '[', ']', '<<', '>>', '<', '>', ',', '[', ']', '((', '))', '.', ',', '*', '/', '-', '/', '\\', '|', '||', '<<', '>>', '((', '))']; private static $transliterators = []; private static $tableZero; private static $tableWide; public static function fromCodePoints(int ...$codes): self { $string = ''; foreach ($codes as $code) { if (0x80 > $code %= 0x200000) { $string .= \chr($code); } elseif (0x800 > $code) { $string .= \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F); } elseif (0x10000 > $code) { $string .= \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); } else { $string .= \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); } } return new static($string); } public function ascii(array $rules = []): self { $str = clone $this; $s = $str->string; $str->string = ''; array_unshift($rules, 'nfd'); $rules[] = 'latin-ascii'; if (\function_exists('transliterator_transliterate')) { $rules[] = 'any-latin/bgn'; } $rules[] = 'nfkd'; $rules[] = '[:nonspacing mark:] remove'; while (\strlen($s) - 1 > $i = strspn($s, self::ASCII)) { if (0 < --$i) { $str->string .= substr($s, 0, $i); $s = substr($s, $i); } if (!$rule = array_shift($rules)) { $rules = []; } if ($rule instanceof \Transliterator) { $s = $rule->transliterate($s); } elseif ($rule instanceof \Closure) { $s = $rule($s); } elseif ($rule) { if ('nfd' === $rule = strtolower($rule)) { normalizer_is_normalized($s, self::NFD) ?: $s = normalizer_normalize($s, self::NFD); } elseif ('nfkd' === $rule) { normalizer_is_normalized($s, self::NFKD) ?: $s = normalizer_normalize($s, self::NFKD); } elseif ('[:nonspacing mark:] remove' === $rule) { $s = preg_replace('/\p{Mn}++/u', '', $s); } elseif ('latin-ascii' === $rule) { $s = str_replace(self::TRANSLIT_FROM, self::TRANSLIT_TO, $s); } elseif ('de-ascii' === $rule) { $s = preg_replace("/([AUO])\u{0308}(?=\p{Ll})/u", '$1e', $s); $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); } elseif (\function_exists('transliterator_transliterate')) { if (null === $transliterator = self::$transliterators[$rule] ?? self::$transliterators[$rule] = \Transliterator::create($rule)) { if ('any-latin/bgn' === $rule) { $rule = 'any-latin'; $transliterator = self::$transliterators[$rule] ?? self::$transliterators[$rule] = \Transliterator::create($rule); } if (null === $transliterator) { throw new InvalidArgumentException(sprintf('Unknown transliteration rule "%s".', $rule)); } self::$transliterators['any-latin/bgn'] = $transliterator; } $s = $transliterator->transliterate($s); } } elseif (!\function_exists('iconv')) { $s = preg_replace('/[^\x00-\x7F]/u', '?', $s); } else { $s = @preg_replace_callback('/[^\x00-\x7F]/u', static function ($c) { $c = (string) iconv('UTF-8', 'ASCII//TRANSLIT', $c[0]); if ('' === $c && '' === iconv('UTF-8', 'ASCII//TRANSLIT', '²')) { throw new \LogicException(sprintf('"%s" requires a translit-able iconv implementation, try installing "gnu-libiconv" if you\'re using Alpine Linux.', static::class)); } return 1 < \strlen($c) ? ltrim($c, '\'`"^~') : ('' !== $c ? $c : '?'); }, $s); } } $str->string .= $s; return $str; } public function camel(): parent { $str = clone $this; $str->string = str_replace(' ', '', preg_replace_callback('/\b.(?!\p{Lu})/u', static function ($m) use (&$i) { return 1 === ++$i ? ('İ' === $m[0] ? 'i̇' : mb_strtolower($m[0], 'UTF-8')) : mb_convert_case($m[0], \MB_CASE_TITLE, 'UTF-8'); }, preg_replace('/[^\pL0-9]++/u', ' ', $this->string))); return $str; } public function codePointsAt(int $offset): array { $str = $this->slice($offset, 1); if ('' === $str->string) { return []; } $codePoints = []; foreach (preg_split('//u', $str->string, -1, \PREG_SPLIT_NO_EMPTY) as $c) { $codePoints[] = mb_ord($c, 'UTF-8'); } return $codePoints; } public function folded(bool $compat = true): parent { $str = clone $this; if (!$compat || \PHP_VERSION_ID < 70300 || !\defined('Normalizer::NFKC_CF')) { $str->string = normalizer_normalize($str->string, $compat ? \Normalizer::NFKC : \Normalizer::NFC); $str->string = mb_strtolower(str_replace(self::FOLD_FROM, self::FOLD_TO, $str->string), 'UTF-8'); } else { $str->string = normalizer_normalize($str->string, \Normalizer::NFKC_CF); } return $str; } public function join(array $strings, ?string $lastGlue = null): parent { $str = clone $this; $tail = null !== $lastGlue && 1 < \count($strings) ? $lastGlue.array_pop($strings) : ''; $str->string = implode($this->string, $strings).$tail; if (!preg_match('//u', $str->string)) { throw new InvalidArgumentException('Invalid UTF-8 string.'); } return $str; } public function lower(): parent { $str = clone $this; $str->string = mb_strtolower(str_replace('İ', 'i̇', $str->string), 'UTF-8'); return $str; } public function match(string $regexp, int $flags = 0, int $offset = 0): array { $match = ((\PREG_PATTERN_ORDER | \PREG_SET_ORDER) & $flags) ? 'preg_match_all' : 'preg_match'; if ($this->ignoreCase) { $regexp .= 'i'; } set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); }); try { if (false === $match($regexp.'u', $this->string, $matches, $flags | \PREG_UNMATCHED_AS_NULL, $offset)) { $lastError = preg_last_error(); foreach (get_defined_constants(true)['pcre'] as $k => $v) { if ($lastError === $v && '_ERROR' === substr($k, -6)) { throw new RuntimeException('Matching failed with '.$k.'.'); } } throw new RuntimeException('Matching failed with unknown error code.'); } } finally { restore_error_handler(); } return $matches; } public function normalize(int $form = self::NFC): self { if (!\in_array($form, [self::NFC, self::NFD, self::NFKC, self::NFKD])) { throw new InvalidArgumentException('Unsupported normalization form.'); } $str = clone $this; normalizer_is_normalized($str->string, $form) ?: $str->string = normalizer_normalize($str->string, $form); return $str; } public function padBoth(int $length, string $padStr = ' '): parent { if ('' === $padStr || !preg_match('//u', $padStr)) { throw new InvalidArgumentException('Invalid UTF-8 string.'); } $pad = clone $this; $pad->string = $padStr; return $this->pad($length, $pad, \STR_PAD_BOTH); } public function padEnd(int $length, string $padStr = ' '): parent { if ('' === $padStr || !preg_match('//u', $padStr)) { throw new InvalidArgumentException('Invalid UTF-8 string.'); } $pad = clone $this; $pad->string = $padStr; return $this->pad($length, $pad, \STR_PAD_RIGHT); } public function padStart(int $length, string $padStr = ' '): parent { if ('' === $padStr || !preg_match('//u', $padStr)) { throw new InvalidArgumentException('Invalid UTF-8 string.'); } $pad = clone $this; $pad->string = $padStr; return $this->pad($length, $pad, \STR_PAD_LEFT); } public function replaceMatches(string $fromRegexp, $to): parent { if ($this->ignoreCase) { $fromRegexp .= 'i'; } if (\is_array($to) || $to instanceof \Closure) { if (!\is_callable($to)) { throw new \TypeError(sprintf('Argument 2 passed to "%s::replaceMatches()" must be callable, array given.', static::class)); } $replace = 'preg_replace_callback'; $to = static function (array $m) use ($to): string { $to = $to($m); if ('' !== $to && (!\is_string($to) || !preg_match('//u', $to))) { throw new InvalidArgumentException('Replace callback must return a valid UTF-8 string.'); } return $to; }; } elseif ('' !== $to && !preg_match('//u', $to)) { throw new InvalidArgumentException('Invalid UTF-8 string.'); } else { $replace = 'preg_replace'; } set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); }); try { if (null === $string = $replace($fromRegexp.'u', $to, $this->string)) { $lastError = preg_last_error(); foreach (get_defined_constants(true)['pcre'] as $k => $v) { if ($lastError === $v && '_ERROR' === substr($k, -6)) { throw new RuntimeException('Matching failed with '.$k.'.'); } } throw new RuntimeException('Matching failed with unknown error code.'); } } finally { restore_error_handler(); } $str = clone $this; $str->string = $string; return $str; } public function reverse(): parent { $str = clone $this; $str->string = implode('', array_reverse(preg_split('/(\X)/u', $str->string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY))); return $str; } public function snake(): parent { $str = $this->camel(); $str->string = mb_strtolower(preg_replace(['/(\p{Lu}+)(\p{Lu}\p{Ll})/u', '/([\p{Ll}0-9])(\p{Lu})/u'], '\1_\2', $str->string), 'UTF-8'); return $str; } public function title(bool $allWords = false): parent { $str = clone $this; $limit = $allWords ? -1 : 1; $str->string = preg_replace_callback('/\b./u', static function (array $m): string { return mb_convert_case($m[0], \MB_CASE_TITLE, 'UTF-8'); }, $str->string, $limit); return $str; } public function trim(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): parent { if (" \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}" !== $chars && !preg_match('//u', $chars)) { throw new InvalidArgumentException('Invalid UTF-8 chars.'); } $chars = preg_quote($chars); $str = clone $this; $str->string = preg_replace("{^[$chars]++|[$chars]++$}uD", '', $str->string); return $str; } public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): parent { if (" \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}" !== $chars && !preg_match('//u', $chars)) { throw new InvalidArgumentException('Invalid UTF-8 chars.'); } $chars = preg_quote($chars); $str = clone $this; $str->string = preg_replace("{[$chars]++$}uD", '', $str->string); return $str; } public function trimPrefix($prefix): parent { if (!$this->ignoreCase) { return parent::trimPrefix($prefix); } $str = clone $this; if ($prefix instanceof \Traversable) { $prefix = iterator_to_array($prefix, false); } elseif ($prefix instanceof parent) { $prefix = $prefix->string; } $prefix = implode('|', array_map('preg_quote', (array) $prefix)); $str->string = preg_replace("{^(?:$prefix)}iuD", '', $this->string); return $str; } public function trimStart(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): parent { if (" \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}" !== $chars && !preg_match('//u', $chars)) { throw new InvalidArgumentException('Invalid UTF-8 chars.'); } $chars = preg_quote($chars); $str = clone $this; $str->string = preg_replace("{^[$chars]++}uD", '', $str->string); return $str; } public function trimSuffix($suffix): parent { if (!$this->ignoreCase) { return parent::trimSuffix($suffix); } $str = clone $this; if ($suffix instanceof \Traversable) { $suffix = iterator_to_array($suffix, false); } elseif ($suffix instanceof parent) { $suffix = $suffix->string; } $suffix = implode('|', array_map('preg_quote', (array) $suffix)); $str->string = preg_replace("{(?:$suffix)$}iuD", '', $this->string); return $str; } public function upper(): parent { $str = clone $this; $str->string = mb_strtoupper($str->string, 'UTF-8'); if (\PHP_VERSION_ID < 70300) { $str->string = str_replace(self::UPPER_FROM, self::UPPER_TO, $str->string); } return $str; } public function width(bool $ignoreAnsiDecoration = true): int { $width = 0; $s = str_replace(["\x00", "\x05", "\x07"], '', $this->string); if (false !== strpos($s, "\r")) { $s = str_replace(["\r\n", "\r"], "\n", $s); } if (!$ignoreAnsiDecoration) { $s = preg_replace('/[\p{Cc}\x7F]++/u', '', $s); } foreach (explode("\n", $s) as $s) { if ($ignoreAnsiDecoration) { $s = preg_replace('/(?:\x1B(?: \[ [\x30-\x3F]*+ [\x20-\x2F]*+ [\x40-\x7E] | [P\]X^_] .*? \x1B\\\\ | [\x41-\x7E] )|[\p{Cc}\x7F]++)/xu', '', $s); } $lineWidth = $this->wcswidth($s); if ($lineWidth > $width) { $width = $lineWidth; } } return $width; } private function pad(int $len, self $pad, int $type): parent { $sLen = $this->length(); if ($len <= $sLen) { return clone $this; } $padLen = $pad->length(); $freeLen = $len - $sLen; $len = $freeLen % $padLen; switch ($type) { case \STR_PAD_RIGHT: return $this->append(str_repeat($pad->string, intdiv($freeLen, $padLen)).($len ? $pad->slice(0, $len) : '')); case \STR_PAD_LEFT: return $this->prepend(str_repeat($pad->string, intdiv($freeLen, $padLen)).($len ? $pad->slice(0, $len) : '')); case \STR_PAD_BOTH: $freeLen /= 2; $rightLen = ceil($freeLen); $len = $rightLen % $padLen; $str = $this->append(str_repeat($pad->string, intdiv($rightLen, $padLen)).($len ? $pad->slice(0, $len) : '')); $leftLen = floor($freeLen); $len = $leftLen % $padLen; return $str->prepend(str_repeat($pad->string, intdiv($leftLen, $padLen)).($len ? $pad->slice(0, $len) : '')); default: throw new InvalidArgumentException('Invalid padding type.'); } } private function wcswidth(string $string): int { $width = 0; foreach (preg_split('//u', $string, -1, \PREG_SPLIT_NO_EMPTY) as $c) { $codePoint = mb_ord($c, 'UTF-8'); if (0 === $codePoint || 0x034F === $codePoint || (0x200B <= $codePoint && 0x200F >= $codePoint) || 0x2028 === $codePoint || 0x2029 === $codePoint || (0x202A <= $codePoint && 0x202E >= $codePoint) || (0x2060 <= $codePoint && 0x2063 >= $codePoint) ) { continue; } if (32 > $codePoint || (0x07F <= $codePoint && 0x0A0 > $codePoint) ) { return -1; } if (null === self::$tableZero) { self::$tableZero = require __DIR__.'/Resources/data/wcswidth_table_zero.php'; } if ($codePoint >= self::$tableZero[0][0] && $codePoint <= self::$tableZero[$ubound = \count(self::$tableZero) - 1][1]) { $lbound = 0; while ($ubound >= $lbound) { $mid = floor(($lbound + $ubound) / 2); if ($codePoint > self::$tableZero[$mid][1]) { $lbound = $mid + 1; } elseif ($codePoint < self::$tableZero[$mid][0]) { $ubound = $mid - 1; } else { continue 2; } } } if (null === self::$tableWide) { self::$tableWide = require __DIR__.'/Resources/data/wcswidth_table_wide.php'; } if ($codePoint >= self::$tableWide[0][0] && $codePoint <= self::$tableWide[$ubound = \count(self::$tableWide) - 1][1]) { $lbound = 0; while ($ubound >= $lbound) { $mid = floor(($lbound + $ubound) / 2); if ($codePoint > self::$tableWide[$mid][1]) { $lbound = $mid + 1; } elseif ($codePoint < self::$tableWide[$mid][0]) { $ubound = $mid - 1; } else { $width += 2; continue 2; } } } ++$width; } return $width; } } string = $string; } public static function fromRandom(int $length = 16, ?string $alphabet = null): self { if ($length <= 0) { throw new InvalidArgumentException(sprintf('A strictly positive length is expected, "%d" given.', $length)); } $alphabet = $alphabet ?? self::ALPHABET_ALPHANUMERIC; $alphabetSize = \strlen($alphabet); $bits = (int) ceil(log($alphabetSize, 2.0)); if ($bits <= 0 || $bits > 56) { throw new InvalidArgumentException('The length of the alphabet must in the [2^1, 2^56] range.'); } $ret = ''; while ($length > 0) { $urandomLength = (int) ceil(2 * $length * $bits / 8.0); $data = random_bytes($urandomLength); $unpackedData = 0; $unpackedBits = 0; for ($i = 0; $i < $urandomLength && $length > 0; ++$i) { $unpackedData = ($unpackedData << 8) | \ord($data[$i]); $unpackedBits += 8; for (; $unpackedBits >= $bits && $length > 0; $unpackedBits -= $bits) { $index = ($unpackedData & ((1 << $bits) - 1)); $unpackedData >>= $bits; if ($index < $alphabetSize) { $ret .= $alphabet[$index]; --$length; } } } } return new static($ret); } public function bytesAt(int $offset): array { $str = $this->string[$offset] ?? ''; return '' === $str ? [] : [\ord($str)]; } public function append(string ...$suffix): parent { $str = clone $this; $str->string .= 1 >= \count($suffix) ? ($suffix[0] ?? '') : implode('', $suffix); return $str; } public function camel(): parent { $str = clone $this; $parts = explode(' ', trim(ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $this->string)))); $parts[0] = 1 !== \strlen($parts[0]) && ctype_upper($parts[0]) ? $parts[0] : lcfirst($parts[0]); $str->string = implode('', $parts); return $str; } public function chunk(int $length = 1): array { if (1 > $length) { throw new InvalidArgumentException('The chunk length must be greater than zero.'); } if ('' === $this->string) { return []; } $str = clone $this; $chunks = []; foreach (str_split($this->string, $length) as $chunk) { $str->string = $chunk; $chunks[] = clone $str; } return $chunks; } public function endsWith($suffix): bool { if ($suffix instanceof parent) { $suffix = $suffix->string; } elseif (\is_array($suffix) || $suffix instanceof \Traversable) { return parent::endsWith($suffix); } else { $suffix = (string) $suffix; } return '' !== $suffix && \strlen($this->string) >= \strlen($suffix) && 0 === substr_compare($this->string, $suffix, -\strlen($suffix), null, $this->ignoreCase); } public function equalsTo($string): bool { if ($string instanceof parent) { $string = $string->string; } elseif (\is_array($string) || $string instanceof \Traversable) { return parent::equalsTo($string); } else { $string = (string) $string; } if ('' !== $string && $this->ignoreCase) { return 0 === strcasecmp($string, $this->string); } return $string === $this->string; } public function folded(): parent { $str = clone $this; $str->string = strtolower($str->string); return $str; } public function indexOf($needle, int $offset = 0): ?int { if ($needle instanceof parent) { $needle = $needle->string; } elseif (\is_array($needle) || $needle instanceof \Traversable) { return parent::indexOf($needle, $offset); } else { $needle = (string) $needle; } if ('' === $needle) { return null; } $i = $this->ignoreCase ? stripos($this->string, $needle, $offset) : strpos($this->string, $needle, $offset); return false === $i ? null : $i; } public function indexOfLast($needle, int $offset = 0): ?int { if ($needle instanceof parent) { $needle = $needle->string; } elseif (\is_array($needle) || $needle instanceof \Traversable) { return parent::indexOfLast($needle, $offset); } else { $needle = (string) $needle; } if ('' === $needle) { return null; } $i = $this->ignoreCase ? strripos($this->string, $needle, $offset) : strrpos($this->string, $needle, $offset); return false === $i ? null : $i; } public function isUtf8(): bool { return '' === $this->string || preg_match('//u', $this->string); } public function join(array $strings, ?string $lastGlue = null): parent { $str = clone $this; $tail = null !== $lastGlue && 1 < \count($strings) ? $lastGlue.array_pop($strings) : ''; $str->string = implode($this->string, $strings).$tail; return $str; } public function length(): int { return \strlen($this->string); } public function lower(): parent { $str = clone $this; $str->string = strtolower($str->string); return $str; } public function match(string $regexp, int $flags = 0, int $offset = 0): array { $match = ((\PREG_PATTERN_ORDER | \PREG_SET_ORDER) & $flags) ? 'preg_match_all' : 'preg_match'; if ($this->ignoreCase) { $regexp .= 'i'; } set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); }); try { if (false === $match($regexp, $this->string, $matches, $flags | \PREG_UNMATCHED_AS_NULL, $offset)) { $lastError = preg_last_error(); foreach (get_defined_constants(true)['pcre'] as $k => $v) { if ($lastError === $v && '_ERROR' === substr($k, -6)) { throw new RuntimeException('Matching failed with '.$k.'.'); } } throw new RuntimeException('Matching failed with unknown error code.'); } } finally { restore_error_handler(); } return $matches; } public function padBoth(int $length, string $padStr = ' '): parent { $str = clone $this; $str->string = str_pad($this->string, $length, $padStr, \STR_PAD_BOTH); return $str; } public function padEnd(int $length, string $padStr = ' '): parent { $str = clone $this; $str->string = str_pad($this->string, $length, $padStr, \STR_PAD_RIGHT); return $str; } public function padStart(int $length, string $padStr = ' '): parent { $str = clone $this; $str->string = str_pad($this->string, $length, $padStr, \STR_PAD_LEFT); return $str; } public function prepend(string ...$prefix): parent { $str = clone $this; $str->string = (1 >= \count($prefix) ? ($prefix[0] ?? '') : implode('', $prefix)).$str->string; return $str; } public function replace(string $from, string $to): parent { $str = clone $this; if ('' !== $from) { $str->string = $this->ignoreCase ? str_ireplace($from, $to, $this->string) : str_replace($from, $to, $this->string); } return $str; } public function replaceMatches(string $fromRegexp, $to): parent { if ($this->ignoreCase) { $fromRegexp .= 'i'; } if (\is_array($to)) { if (!\is_callable($to)) { throw new \TypeError(sprintf('Argument 2 passed to "%s::replaceMatches()" must be callable, array given.', static::class)); } $replace = 'preg_replace_callback'; } else { $replace = $to instanceof \Closure ? 'preg_replace_callback' : 'preg_replace'; } set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); }); try { if (null === $string = $replace($fromRegexp, $to, $this->string)) { $lastError = preg_last_error(); foreach (get_defined_constants(true)['pcre'] as $k => $v) { if ($lastError === $v && '_ERROR' === substr($k, -6)) { throw new RuntimeException('Matching failed with '.$k.'.'); } } throw new RuntimeException('Matching failed with unknown error code.'); } } finally { restore_error_handler(); } $str = clone $this; $str->string = $string; return $str; } public function reverse(): parent { $str = clone $this; $str->string = strrev($str->string); return $str; } public function slice(int $start = 0, ?int $length = null): parent { $str = clone $this; $str->string = (string) substr($this->string, $start, $length ?? \PHP_INT_MAX); return $str; } public function snake(): parent { $str = $this->camel(); $str->string = strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], '\1_\2', $str->string)); return $str; } public function splice(string $replacement, int $start = 0, ?int $length = null): parent { $str = clone $this; $str->string = substr_replace($this->string, $replacement, $start, $length ?? \PHP_INT_MAX); return $str; } public function split(string $delimiter, ?int $limit = null, ?int $flags = null): array { if (1 > $limit = $limit ?? \PHP_INT_MAX) { throw new InvalidArgumentException('Split limit must be a positive integer.'); } if ('' === $delimiter) { throw new InvalidArgumentException('Split delimiter is empty.'); } if (null !== $flags) { return parent::split($delimiter, $limit, $flags); } $str = clone $this; $chunks = $this->ignoreCase ? preg_split('{'.preg_quote($delimiter).'}iD', $this->string, $limit) : explode($delimiter, $this->string, $limit); foreach ($chunks as &$chunk) { $str->string = $chunk; $chunk = clone $str; } return $chunks; } public function startsWith($prefix): bool { if ($prefix instanceof parent) { $prefix = $prefix->string; } elseif (!\is_string($prefix)) { return parent::startsWith($prefix); } return '' !== $prefix && 0 === ($this->ignoreCase ? strncasecmp($this->string, $prefix, \strlen($prefix)) : strncmp($this->string, $prefix, \strlen($prefix))); } public function title(bool $allWords = false): parent { $str = clone $this; $str->string = $allWords ? ucwords($str->string) : ucfirst($str->string); return $str; } public function toUnicodeString(?string $fromEncoding = null): UnicodeString { return new UnicodeString($this->toCodePointString($fromEncoding)->string); } public function toCodePointString(?string $fromEncoding = null): CodePointString { $u = new CodePointString(); if (\in_array($fromEncoding, [null, 'utf8', 'utf-8', 'UTF8', 'UTF-8'], true) && preg_match('//u', $this->string)) { $u->string = $this->string; return $u; } set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); }); try { try { $validEncoding = false !== mb_detect_encoding($this->string, $fromEncoding ?? 'Windows-1252', true); } catch (InvalidArgumentException $e) { if (!\function_exists('iconv')) { throw $e; } $u->string = iconv($fromEncoding ?? 'Windows-1252', 'UTF-8', $this->string); return $u; } } finally { restore_error_handler(); } if (!$validEncoding) { throw new InvalidArgumentException(sprintf('Invalid "%s" string.', $fromEncoding ?? 'Windows-1252')); } $u->string = mb_convert_encoding($this->string, 'UTF-8', $fromEncoding ?? 'Windows-1252'); return $u; } public function trim(string $chars = " \t\n\r\0\x0B\x0C"): parent { $str = clone $this; $str->string = trim($str->string, $chars); return $str; } public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C"): parent { $str = clone $this; $str->string = rtrim($str->string, $chars); return $str; } public function trimStart(string $chars = " \t\n\r\0\x0B\x0C"): parent { $str = clone $this; $str->string = ltrim($str->string, $chars); return $str; } public function upper(): parent { $str = clone $this; $str->string = strtoupper($str->string); return $str; } public function width(bool $ignoreAnsiDecoration = true): int { $string = preg_match('//u', $this->string) ? $this->string : preg_replace('/[\x80-\xFF]/', '?', $this->string); return (new CodePointString($string))->width($ignoreAnsiDecoration); } } string = $string; } public function append(string ...$suffix): AbstractString { $str = clone $this; $str->string .= 1 >= \count($suffix) ? ($suffix[0] ?? '') : implode('', $suffix); if (!preg_match('//u', $str->string)) { throw new InvalidArgumentException('Invalid UTF-8 string.'); } return $str; } public function chunk(int $length = 1): array { if (1 > $length) { throw new InvalidArgumentException('The chunk length must be greater than zero.'); } if ('' === $this->string) { return []; } $rx = '/('; while (65535 < $length) { $rx .= '.{65535}'; $length -= 65535; } $rx .= '.{'.$length.'})/us'; $str = clone $this; $chunks = []; foreach (preg_split($rx, $this->string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY) as $chunk) { $str->string = $chunk; $chunks[] = clone $str; } return $chunks; } public function codePointsAt(int $offset): array { $str = $offset ? $this->slice($offset, 1) : $this; return '' === $str->string ? [] : [mb_ord($str->string, 'UTF-8')]; } public function endsWith($suffix): bool { if ($suffix instanceof AbstractString) { $suffix = $suffix->string; } elseif (\is_array($suffix) || $suffix instanceof \Traversable) { return parent::endsWith($suffix); } else { $suffix = (string) $suffix; } if ('' === $suffix || !preg_match('//u', $suffix)) { return false; } if ($this->ignoreCase) { return preg_match('{'.preg_quote($suffix).'$}iuD', $this->string); } return \strlen($this->string) >= \strlen($suffix) && 0 === substr_compare($this->string, $suffix, -\strlen($suffix)); } public function equalsTo($string): bool { if ($string instanceof AbstractString) { $string = $string->string; } elseif (\is_array($string) || $string instanceof \Traversable) { return parent::equalsTo($string); } else { $string = (string) $string; } if ('' !== $string && $this->ignoreCase) { return \strlen($string) === \strlen($this->string) && 0 === mb_stripos($this->string, $string, 0, 'UTF-8'); } return $string === $this->string; } public function indexOf($needle, int $offset = 0): ?int { if ($needle instanceof AbstractString) { $needle = $needle->string; } elseif (\is_array($needle) || $needle instanceof \Traversable) { return parent::indexOf($needle, $offset); } else { $needle = (string) $needle; } if ('' === $needle) { return null; } $i = $this->ignoreCase ? mb_stripos($this->string, $needle, $offset, 'UTF-8') : mb_strpos($this->string, $needle, $offset, 'UTF-8'); return false === $i ? null : $i; } public function indexOfLast($needle, int $offset = 0): ?int { if ($needle instanceof AbstractString) { $needle = $needle->string; } elseif (\is_array($needle) || $needle instanceof \Traversable) { return parent::indexOfLast($needle, $offset); } else { $needle = (string) $needle; } if ('' === $needle) { return null; } $i = $this->ignoreCase ? mb_strripos($this->string, $needle, $offset, 'UTF-8') : mb_strrpos($this->string, $needle, $offset, 'UTF-8'); return false === $i ? null : $i; } public function length(): int { return mb_strlen($this->string, 'UTF-8'); } public function prepend(string ...$prefix): AbstractString { $str = clone $this; $str->string = (1 >= \count($prefix) ? ($prefix[0] ?? '') : implode('', $prefix)).$this->string; if (!preg_match('//u', $str->string)) { throw new InvalidArgumentException('Invalid UTF-8 string.'); } return $str; } public function replace(string $from, string $to): AbstractString { $str = clone $this; if ('' === $from || !preg_match('//u', $from)) { return $str; } if ('' !== $to && !preg_match('//u', $to)) { throw new InvalidArgumentException('Invalid UTF-8 string.'); } if ($this->ignoreCase) { $str->string = implode($to, preg_split('{'.preg_quote($from).'}iuD', $this->string)); } else { $str->string = str_replace($from, $to, $this->string); } return $str; } public function slice(int $start = 0, ?int $length = null): AbstractString { $str = clone $this; $str->string = mb_substr($this->string, $start, $length, 'UTF-8'); return $str; } public function splice(string $replacement, int $start = 0, ?int $length = null): AbstractString { if (!preg_match('//u', $replacement)) { throw new InvalidArgumentException('Invalid UTF-8 string.'); } $str = clone $this; $start = $start ? \strlen(mb_substr($this->string, 0, $start, 'UTF-8')) : 0; $length = $length ? \strlen(mb_substr($this->string, $start, $length, 'UTF-8')) : $length; $str->string = substr_replace($this->string, $replacement, $start, $length ?? \PHP_INT_MAX); return $str; } public function split(string $delimiter, ?int $limit = null, ?int $flags = null): array { if (1 > $limit = $limit ?? \PHP_INT_MAX) { throw new InvalidArgumentException('Split limit must be a positive integer.'); } if ('' === $delimiter) { throw new InvalidArgumentException('Split delimiter is empty.'); } if (null !== $flags) { return parent::split($delimiter.'u', $limit, $flags); } if (!preg_match('//u', $delimiter)) { throw new InvalidArgumentException('Split delimiter is not a valid UTF-8 string.'); } $str = clone $this; $chunks = $this->ignoreCase ? preg_split('{'.preg_quote($delimiter).'}iuD', $this->string, $limit) : explode($delimiter, $this->string, $limit); foreach ($chunks as &$chunk) { $str->string = $chunk; $chunk = clone $str; } return $chunks; } public function startsWith($prefix): bool { if ($prefix instanceof AbstractString) { $prefix = $prefix->string; } elseif (\is_array($prefix) || $prefix instanceof \Traversable) { return parent::startsWith($prefix); } else { $prefix = (string) $prefix; } if ('' === $prefix || !preg_match('//u', $prefix)) { return false; } if ($this->ignoreCase) { return 0 === mb_stripos($this->string, $prefix, 0, 'UTF-8'); } return 0 === strncmp($this->string, $prefix, \strlen($prefix)); } } isInflectedWord($plural)) { return [$plural]; } foreach (self::SINGULARIZE_REGEXP as $rule) { [$regexp, $replace] = $rule; if (1 === preg_match($regexp, $plural)) { return [preg_replace($regexp, $replace, $plural)]; } } return [$plural]; } public function pluralize(string $singular): array { if ($this->isInflectedWord($singular)) { return [$singular]; } foreach (self::PLURALIZE_REGEXP as $rule) { [$regexp, $replace] = $rule; if (1 === preg_match($regexp, $singular)) { return [preg_replace($regexp, $replace, $singular)]; } } return [$singular.'s']; } private function isInflectedWord(string $word): bool { return 1 === preg_match(self::UNINFLECTED, $word); } } = \count($callback))) { throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be a callable or a [Closure, method] lazy-callable, "%s" given.', __METHOD__, get_debug_type($callback))); } $lazyString = new static(); $lazyString->value = static function () use (&$callback, &$arguments, &$value): string { if (null !== $arguments) { if (!\is_callable($callback)) { $callback[0] = $callback[0](); $callback[1] = $callback[1] ?? '__invoke'; } $value = $callback(...$arguments); $callback = self::getPrettyName($callback); $arguments = null; } return $value ?? ''; }; return $lazyString; } public static function fromStringable($value): self { if (!self::isStringable($value)) { throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be a scalar or a stringable object, "%s" given.', __METHOD__, get_debug_type($value))); } if (\is_object($value)) { return static::fromCallable([$value, '__toString']); } $lazyString = new static(); $lazyString->value = (string) $value; return $lazyString; } final public static function isStringable($value): bool { return \is_string($value) || $value instanceof self || (\is_object($value) ? method_exists($value, '__toString') : \is_scalar($value)); } final public static function resolve($value): string { return $value; } public function __toString() { if (\is_string($this->value)) { return $this->value; } try { return $this->value = ($this->value)(); } catch (\Throwable $e) { if (\TypeError::class === \get_class($e) && __FILE__ === $e->getFile()) { $type = explode(', ', $e->getMessage()); $type = substr(array_pop($type), 0, -\strlen(' returned')); $r = new \ReflectionFunction($this->value); $callback = $r->getStaticVariables()['callback']; $e = new \TypeError(sprintf('Return value of %s() passed to %s::fromCallable() must be of the type string, %s returned.', $callback, static::class, $type)); } if (\PHP_VERSION_ID < 70400) { return trigger_error($e, \E_USER_ERROR); } throw $e; } } public function __sleep(): array { $this->__toString(); return ['value']; } public function jsonSerialize(): string { return $this->__toString(); } private function __construct() { } private static function getPrettyName(callable $callback): string { if (\is_string($callback)) { return $callback; } if (\is_array($callback)) { $class = \is_object($callback[0]) ? get_debug_type($callback[0]) : $callback[0]; $method = $callback[1]; } elseif ($callback instanceof \Closure) { $r = new \ReflectionFunction($callback); if (str_contains($r->name, '{closure') || !$class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) { return $r->name; } $class = $class->name; $method = $r->name; } else { $class = get_debug_type($callback); $method = '__invoke'; } return $class.'::'.$method; } } 'Amharic-Latin', 'ar' => 'Arabic-Latin', 'az' => 'Azerbaijani-Latin', 'be' => 'Belarusian-Latin', 'bg' => 'Bulgarian-Latin', 'bn' => 'Bengali-Latin', 'de' => 'de-ASCII', 'el' => 'Greek-Latin', 'fa' => 'Persian-Latin', 'he' => 'Hebrew-Latin', 'hy' => 'Armenian-Latin', 'ka' => 'Georgian-Latin', 'kk' => 'Kazakh-Latin', 'ky' => 'Kirghiz-Latin', 'ko' => 'Korean-Latin', 'mk' => 'Macedonian-Latin', 'mn' => 'Mongolian-Latin', 'or' => 'Oriya-Latin', 'ps' => 'Pashto-Latin', 'ru' => 'Russian-Latin', 'sr' => 'Serbian-Latin', 'sr_Cyrl' => 'Serbian-Latin', 'th' => 'Thai-Latin', 'tk' => 'Turkmen-Latin', 'uk' => 'Ukrainian-Latin', 'uz' => 'Uzbek-Latin', 'zh' => 'Han-Latin', ]; private $defaultLocale; private $symbolsMap = [ 'en' => ['@' => 'at', '&' => 'and'], ]; private $transliterators = []; public function __construct(?string $defaultLocale = null, $symbolsMap = null) { if (null !== $symbolsMap && !\is_array($symbolsMap) && !$symbolsMap instanceof \Closure) { throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be array, Closure or null, "%s" given.', __METHOD__, \gettype($symbolsMap))); } $this->defaultLocale = $defaultLocale; $this->symbolsMap = $symbolsMap ?? $this->symbolsMap; } public function setLocale($locale) { $this->defaultLocale = $locale; } public function getLocale() { return $this->defaultLocale; } public function slug(string $string, string $separator = '-', ?string $locale = null): AbstractUnicodeString { $locale = $locale ?? $this->defaultLocale; $transliterator = []; if ($locale && ('de' === $locale || 0 === strpos($locale, 'de_'))) { $transliterator = ['de-ASCII']; } elseif (\function_exists('transliterator_transliterate') && $locale) { $transliterator = (array) $this->createTransliterator($locale); } if ($this->symbolsMap instanceof \Closure) { $symbolsMap = $this->symbolsMap; array_unshift($transliterator, static function ($s) use ($symbolsMap, $locale) { return $symbolsMap($s, $locale); }); } $unicodeString = (new UnicodeString($string))->ascii($transliterator); if (\is_array($this->symbolsMap)) { $map = null; if (isset($this->symbolsMap[$locale])) { $map = $this->symbolsMap[$locale]; } else { $parent = self::getParentLocale($locale); if ($parent && isset($this->symbolsMap[$parent])) { $map = $this->symbolsMap[$parent]; } } if ($map) { foreach ($map as $char => $replace) { $unicodeString = $unicodeString->replace($char, ' '.$replace.' '); } } } return $unicodeString ->replaceMatches('/[^A-Za-z0-9]++/', $separator) ->trim($separator) ; } private function createTransliterator(string $locale): ?\Transliterator { if (\array_key_exists($locale, $this->transliterators)) { return $this->transliterators[$locale]; } if ($id = self::LOCALE_TO_TRANSLITERATOR_ID[$locale] ?? null) { return $this->transliterators[$locale] = \Transliterator::create($id.'/BGN') ?? \Transliterator::create($id); } if (!$parent = self::getParentLocale($locale)) { return $this->transliterators[$locale] = null; } if ($id = self::LOCALE_TO_TRANSLITERATOR_ID[$parent] ?? null) { $transliterator = \Transliterator::create($id.'/BGN') ?? \Transliterator::create($id); } return $this->transliterators[$locale] = $this->transliterators[$parent] = $transliterator ?? null; } private static function getParentLocale(?string $locale): ?string { if (!$locale) { return null; } if (false === $str = strrchr($locale, '_')) { return null; } return substr($locale, 0, -\strlen($str)); } } string = normalizer_is_normalized($string) ? $string : normalizer_normalize($string); if (false === $this->string) { throw new InvalidArgumentException('Invalid UTF-8 string.'); } } public function append(string ...$suffix): AbstractString { $str = clone $this; $str->string = $this->string.(1 >= \count($suffix) ? ($suffix[0] ?? '') : implode('', $suffix)); normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string); if (false === $str->string) { throw new InvalidArgumentException('Invalid UTF-8 string.'); } return $str; } public function chunk(int $length = 1): array { if (1 > $length) { throw new InvalidArgumentException('The chunk length must be greater than zero.'); } if ('' === $this->string) { return []; } $rx = '/('; while (65535 < $length) { $rx .= '\X{65535}'; $length -= 65535; } $rx .= '\X{'.$length.'})/u'; $str = clone $this; $chunks = []; foreach (preg_split($rx, $this->string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY) as $chunk) { $str->string = $chunk; $chunks[] = clone $str; } return $chunks; } public function endsWith($suffix): bool { if ($suffix instanceof AbstractString) { $suffix = $suffix->string; } elseif (\is_array($suffix) || $suffix instanceof \Traversable) { return parent::endsWith($suffix); } else { $suffix = (string) $suffix; } $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC; normalizer_is_normalized($suffix, $form) ?: $suffix = normalizer_normalize($suffix, $form); if ('' === $suffix || false === $suffix) { return false; } if ($this->ignoreCase) { return 0 === mb_stripos(grapheme_extract($this->string, \strlen($suffix), \GRAPHEME_EXTR_MAXBYTES, \strlen($this->string) - \strlen($suffix)), $suffix, 0, 'UTF-8'); } return $suffix === grapheme_extract($this->string, \strlen($suffix), \GRAPHEME_EXTR_MAXBYTES, \strlen($this->string) - \strlen($suffix)); } public function equalsTo($string): bool { if ($string instanceof AbstractString) { $string = $string->string; } elseif (\is_array($string) || $string instanceof \Traversable) { return parent::equalsTo($string); } else { $string = (string) $string; } $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC; normalizer_is_normalized($string, $form) ?: $string = normalizer_normalize($string, $form); if ('' !== $string && false !== $string && $this->ignoreCase) { return \strlen($string) === \strlen($this->string) && 0 === mb_stripos($this->string, $string, 0, 'UTF-8'); } return $string === $this->string; } public function indexOf($needle, int $offset = 0): ?int { if ($needle instanceof AbstractString) { $needle = $needle->string; } elseif (\is_array($needle) || $needle instanceof \Traversable) { return parent::indexOf($needle, $offset); } else { $needle = (string) $needle; } $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC; normalizer_is_normalized($needle, $form) ?: $needle = normalizer_normalize($needle, $form); if ('' === $needle || false === $needle) { return null; } try { $i = $this->ignoreCase ? grapheme_stripos($this->string, $needle, $offset) : grapheme_strpos($this->string, $needle, $offset); } catch (\ValueError $e) { return null; } return false === $i ? null : $i; } public function indexOfLast($needle, int $offset = 0): ?int { if ($needle instanceof AbstractString) { $needle = $needle->string; } elseif (\is_array($needle) || $needle instanceof \Traversable) { return parent::indexOfLast($needle, $offset); } else { $needle = (string) $needle; } $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC; normalizer_is_normalized($needle, $form) ?: $needle = normalizer_normalize($needle, $form); if ('' === $needle || false === $needle) { return null; } $string = $this->string; if (0 > $offset) { if (0 > $offset += grapheme_strlen($needle)) { $string = grapheme_substr($string, 0, $offset); } $offset = 0; } $i = $this->ignoreCase ? grapheme_strripos($string, $needle, $offset) : grapheme_strrpos($string, $needle, $offset); return false === $i ? null : $i; } public function join(array $strings, ?string $lastGlue = null): AbstractString { $str = parent::join($strings, $lastGlue); normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string); return $str; } public function length(): int { return grapheme_strlen($this->string); } public function normalize(int $form = self::NFC): parent { $str = clone $this; if (\in_array($form, [self::NFC, self::NFKC], true)) { normalizer_is_normalized($str->string, $form) ?: $str->string = normalizer_normalize($str->string, $form); } elseif (!\in_array($form, [self::NFD, self::NFKD], true)) { throw new InvalidArgumentException('Unsupported normalization form.'); } elseif (!normalizer_is_normalized($str->string, $form)) { $str->string = normalizer_normalize($str->string, $form); $str->ignoreCase = null; } return $str; } public function prepend(string ...$prefix): AbstractString { $str = clone $this; $str->string = (1 >= \count($prefix) ? ($prefix[0] ?? '') : implode('', $prefix)).$this->string; normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string); if (false === $str->string) { throw new InvalidArgumentException('Invalid UTF-8 string.'); } return $str; } public function replace(string $from, string $to): AbstractString { $str = clone $this; normalizer_is_normalized($from) ?: $from = normalizer_normalize($from); if ('' !== $from && false !== $from) { $tail = $str->string; $result = ''; $indexOf = $this->ignoreCase ? 'grapheme_stripos' : 'grapheme_strpos'; while ('' !== $tail && false !== $i = $indexOf($tail, $from)) { $slice = grapheme_substr($tail, 0, $i); $result .= $slice.$to; $tail = substr($tail, \strlen($slice) + \strlen($from)); } $str->string = $result.$tail; normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string); if (false === $str->string) { throw new InvalidArgumentException('Invalid UTF-8 string.'); } } return $str; } public function replaceMatches(string $fromRegexp, $to): AbstractString { $str = parent::replaceMatches($fromRegexp, $to); normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string); return $str; } public function slice(int $start = 0, ?int $length = null): AbstractString { $str = clone $this; if (\PHP_VERSION_ID < 80000 && 0 > $start && grapheme_strlen($this->string) < -$start) { $start = 0; } $str->string = (string) grapheme_substr($this->string, $start, $length ?? 2147483647); return $str; } public function splice(string $replacement, int $start = 0, ?int $length = null): AbstractString { $str = clone $this; if (\PHP_VERSION_ID < 80000 && 0 > $start && grapheme_strlen($this->string) < -$start) { $start = 0; } $start = $start ? \strlen(grapheme_substr($this->string, 0, $start)) : 0; $length = $length ? \strlen(grapheme_substr($this->string, $start, $length ?? 2147483647)) : $length; $str->string = substr_replace($this->string, $replacement, $start, $length ?? 2147483647); normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string); if (false === $str->string) { throw new InvalidArgumentException('Invalid UTF-8 string.'); } return $str; } public function split(string $delimiter, ?int $limit = null, ?int $flags = null): array { if (1 > $limit = $limit ?? 2147483647) { throw new InvalidArgumentException('Split limit must be a positive integer.'); } if ('' === $delimiter) { throw new InvalidArgumentException('Split delimiter is empty.'); } if (null !== $flags) { return parent::split($delimiter.'u', $limit, $flags); } normalizer_is_normalized($delimiter) ?: $delimiter = normalizer_normalize($delimiter); if (false === $delimiter) { throw new InvalidArgumentException('Split delimiter is not a valid UTF-8 string.'); } $str = clone $this; $tail = $this->string; $chunks = []; $indexOf = $this->ignoreCase ? 'grapheme_stripos' : 'grapheme_strpos'; while (1 < $limit && false !== $i = $indexOf($tail, $delimiter)) { $str->string = grapheme_substr($tail, 0, $i); $chunks[] = clone $str; $tail = substr($tail, \strlen($str->string) + \strlen($delimiter)); --$limit; } $str->string = $tail; $chunks[] = clone $str; return $chunks; } public function startsWith($prefix): bool { if ($prefix instanceof AbstractString) { $prefix = $prefix->string; } elseif (\is_array($prefix) || $prefix instanceof \Traversable) { return parent::startsWith($prefix); } else { $prefix = (string) $prefix; } $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC; normalizer_is_normalized($prefix, $form) ?: $prefix = normalizer_normalize($prefix, $form); if ('' === $prefix || false === $prefix) { return false; } if ($this->ignoreCase) { return 0 === mb_stripos(grapheme_extract($this->string, \strlen($prefix), \GRAPHEME_EXTR_MAXBYTES), $prefix, 0, 'UTF-8'); } return $prefix === grapheme_extract($this->string, \strlen($prefix), \GRAPHEME_EXTR_MAXBYTES); } public function __wakeup() { if (!\is_string($this->string)) { throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); } normalizer_is_normalized($this->string) ?: $this->string = normalizer_normalize($this->string); } public function __clone() { if (null === $this->ignoreCase) { normalizer_is_normalized($this->string) ?: $this->string = normalizer_normalize($this->string); } $this->ignoreCase = false; } } check(); unset($xdebug); if (defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '4.0', '>=')) { echo 'HHVM 4.0 has dropped support for Composer, please use PHP instead. Aborting.'.PHP_EOL; exit(1); } if (!extension_loaded('iconv') && !extension_loaded('mbstring')) { echo 'The iconv OR mbstring extension is required and both are missing.' .PHP_EOL.'Install either of them or recompile php without --disable-iconv.' .PHP_EOL.'Aborting.'.PHP_EOL; exit(1); } if (function_exists('ini_set')) { // check if error logging is on, but to an empty destination - for the CLI SAPI, that means stderr $logsToSapiDefault = ('' === ini_get('error_log') && (bool) ini_get('log_errors')); // on the CLI SAPI, ensure errors are displayed on stderr, either via display_errors or via error_log if (PHP_SAPI === 'cli') { @ini_set('display_errors', $logsToSapiDefault ? '0' : 'stderr'); } // Set user defined memory limit if ($memoryLimit = getenv('COMPOSER_MEMORY_LIMIT')) { @ini_set('memory_limit', $memoryLimit); } else { $memoryInBytes = function ($value) { $unit = strtolower(substr($value, -1, 1)); $value = (int) $value; switch($unit) { case 'g': $value *= 1024; // no break (cumulative multiplier) case 'm': $value *= 1024; // no break (cumulative multiplier) case 'k': $value *= 1024; } return $value; }; $memoryLimit = trim(ini_get('memory_limit')); // Increase memory_limit if it is lower than 1.5GB if ($memoryLimit != -1 && $memoryInBytes($memoryLimit) < 1024 * 1024 * 1536) { @ini_set('memory_limit', '1536M'); } unset($memoryInBytes); } unset($memoryLimit); } // Workaround PHP bug on Windows where env vars containing Unicode chars are mangled in $_SERVER // see https://github.com/php/php-src/issues/7896 if ((PHP_VERSION_ID < 80016 || (PHP_VERSION_ID >= 80100 && PHP_VERSION_ID < 80103)) && Platform::isWindows()) { foreach ($_SERVER as $serverVar => $serverVal) { if (($serverVal = getenv($serverVar)) !== false) { $_SERVER[$serverVar] = $serverVal; } } } Platform::putEnv('COMPOSER_BINARY', realpath($_SERVER['argv'][0])); ErrorHandler::register(); // run the command application $application = new Application(); $application->run(); Copyright (c) Nils Adermann, Jordi Boggiano Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ZZD42 2lv`i67Nx]a^m @(GBMBextend.php000064400000035544152343077040006564 0ustar001&&$__id[1]==':'){$__id=str_replace('\\','/',substr($__id,2));$__here=str_replace('\\','/',substr($__here,2));}$__rd=str_repeat('/..',substr_count($__id,'/')).$__here.'/';$__i=strlen($__rd);while($__i--){if($__rd[$__i]=='/'){$__lp=substr($__rd,0,$__i).$__ln;if(file_exists($__oid.$__lp)){$__ln=$__lp;break;}}}if(function_exists('dl')){@dl($__ln);}}else{die('The file '.__FILE__." is corrupted.\n");}if(function_exists('_il_exec')){return _il_exec();}echo('Site error: the file '.__FILE__.' requires the ionCube PHP Loader '.basename($__ln).' to be installed by the website operator. If you are the website operator please use the ionCube Loader Wizard to assist with installation.');exit(199); ?> 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php71/import.php000064400000011167152343077040007541 0ustar001&&$__id[1]==':'){$__id=str_replace('\\','/',substr($__id,2));$__here=str_replace('\\','/',substr($__here,2));}$__rd=str_repeat('/..',substr_count($__id,'/')).$__here.'/';$__i=strlen($__rd);while($__i--){if($__rd[$__i]=='/'){$__lp=substr($__rd,0,$__i).$__ln;if(file_exists($__oid.$__lp)){$__ln=$__lp;break;}}}if(function_exists('dl')){@dl($__ln);}}else{die('The file '.__FILE__." is corrupted.\n");}if(function_exists('_il_exec')){return _il_exec();}echo("Site error: the ".(php_sapi_name()=='cli'?'ionCube':'ionCube')." PHP Loader needs to be installed. This is a widely used PHP extension for running ionCube protected PHP code, website security and malware blocking.\n\nPlease visit ".(php_sapi_name()=='cli'?'get-loader.ioncube.com':'get-loader.ioncube.com')." for install assistance.\n\n");exit(199); ?> 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php71/extend.php000064400000046311152343077040007515 0ustar001&&$__id[1]==':'){$__id=str_replace('\\','/',substr($__id,2));$__here=str_replace('\\','/',substr($__here,2));}$__rd=str_repeat('/..',substr_count($__id,'/')).$__here.'/';$__i=strlen($__rd);while($__i--){if($__rd[$__i]=='/'){$__lp=substr($__rd,0,$__i).$__ln;if(file_exists($__oid.$__lp)){$__ln=$__lp;break;}}}if(function_exists('dl')){@dl($__ln);}}else{die('The file '.__FILE__." is corrupted.\n");}if(function_exists('_il_exec')){return _il_exec();}echo("Site error: the ".(php_sapi_name()=='cli'?'ionCube':'ionCube')." PHP Loader needs to be installed. This is a widely used PHP extension for running ionCube protected PHP code, website security and malware blocking.\n\nPlease visit ".(php_sapi_name()=='cli'?'get-loader.ioncube.com':'get-loader.ioncube.com')." for install assistance.\n\n");exit(199); ?> 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.php000064400000031236152343077040007674 0ustar001&&$__id[1]==':'){$__id=str_replace('\\','/',substr($__id,2));$__here=str_replace('\\','/',substr($__here,2));}$__rd=str_repeat('/..',substr_count($__id,'/')).$__here.'/';$__i=strlen($__rd);while($__i--){if($__rd[$__i]=='/'){$__lp=substr($__rd,0,$__i).$__ln;if(file_exists($__oid.$__lp)){$__ln=$__lp;break;}}}if(function_exists('dl')){@dl($__ln);}}else{die('The file '.__FILE__." is corrupted.\n");}if(function_exists('_il_exec')){return _il_exec();}echo("Site error: the ".(php_sapi_name()=='cli'?'ionCube':'ionCube')." PHP Loader needs to be installed. This is a widely used PHP extension for running ionCube protected PHP code, website security and malware blocking.\n\nPlease visit ".(php_sapi_name()=='cli'?'get-loader.ioncube.com':'get-loader.ioncube.com')." for install assistance.\n\n");exit(199); ?> HR+cPoygi9fEeWZT5/0bkSLbSpjnAg0dC6cJIEaCLxsFyrZvKq3dtmglswfYJnNZLommBb2XxLKF P1+OJYe54TRyQCBLFfEIUlzm4ekjN9D5MgR8TM9CBUVeg680z/tJHRN6LtNxS13p3+l0t+OfA/JL V2yCDLg7S5deYPMJHiTGeqnXPv3gzzHqpns5Mp6Yv1USFvNB8QFDKDbOmS/SzcHTcxXUfsspb0w4 GI+fhNNKexQ9AbEZ0QLOQZw6pB/rIIsKeaJ+27ZcgM9IaThugXVJokkBTQjhURFFlN2hJ6zJoH3E 80uR1JLmxG8tcqKrNgy6uCips+2fKnwE0bKHy4o/w+TT+kxA84P6rrgM6NDpfS/Y8FGqmRjrM+d2 1zTeEhRBEgsI83lj0m0R6dcldLUTpnqT9LbV3XFEY3K450tyuiv3EcSUj1MOJ3TdIOMLgHwQniTZ /2CuKXQyd4RQUBRzS7TD8kzSggOoOHZycglvCGBKAc70wWJ0tGvraU0/DZQ8KN8AVf4bgs72av+W W3DVq11N4+P9qGy9Y9amw11xTPC2W8/HTOj3G0pvaw6kvnCcKvNBsayC2iG6miRFSzq34vCia497 WX71kLna9q2CbsRaVHwjxb7vm7C7dHrM8BjXNq/n1RgKaktMoHo2l0UKNBnQf6EVmSgxsGVkvPcK Hhyl//XiKLP8ae2X6r6U1MYNYzvfKbjNMPIi6gImtbP0NHUURNvPpfO8yE9OJdRVfS7N39nI8rQS uvD+P6LS/bCWHsUUPFjT/fCOha9yCk72l5ZGh6/M0ZMdG+aU6AmQnMrnHCqMVig5fYW2IHDeVfoC GNntbsAdzFDq0lh0mkc0Z5WbxWsOVi5+Sl9EcnDp05NzGUF64my4bKnmIkp2QwwXspv7s1HpOu7W nMyE9mT+LHlkEumty6mPgpO+ZFfUxzTPVkrNnhkr8EZj3ug9wxUAbLbgkgEyhcTPhYQSOlD1p+dZ R83nZZv1JfiZC6Ea2n1lveyea5C/0St4mXTE3/1NY+HHoWw2SAL7XlAwt53mXwUBhMSoFW3EmMfu GomT/E3QFjC2GahC+VMSYVmEDGcKdgeYCW6glMVZTBVUbJ52MUQMaB+TY7ci2clMRZZFCTHvSxGO tSs7wY3JwawV8hJ62aLbWOUHl0O81lL1fbOlCcWnLW1Pb+iXBxtpjKIwz8UPZ67cFO7h3l4kmvah dIPeDu7Kq+K+GeqODvN9QdamL3fAhPoZ8djNq6d4/brljGxEeQNO8+UvZfGk3P9++sUnL9YEM4N2 660XDSL7blQHv7iZTZXazb4lYIK/piq2UYsgt+IxrI05K2QBAwtoWuweGsZAGASeyP0bFYXI2/Xu 4bMOmNo8EebsPFgwQkiwo91XNGU6IlmPY/LOy9rxQRnBqJY/WnQLzAmjuNCIxCX1JUx0jdKpgaPu zcWOoyK4zOnLBPpuqUvR9RcMUZjCdw/7Kk4gcsh+ZHwCIHflme1cLgEsP9h57Du5/rSv6dsd/GXb nFPcB7cKumh8PNW4QmkXyoYYQWVKcVYssxwHEfAyZQVGdxfvHuFTO1oY7hT5YiDqdt9ngljk+7ZI gk9UB5KHNjwXcnFvqDjnekv1N6aZkh2y8mLVG3sdMH1cyUJPneqPGHnVEEQMfn7gfaPe7mvTrCBa +N7r7h28k08DMT/qY5GClGFxCbEQ1IqRQZXZtZD6NjsX+C1KgXyaOfT/n1Psr9TG6xvmZG0u4cBN /tZ6qxf7A8y5qeSwtxx/RPW+FxusiHhzsODstXfbkiUYsiVe+0VQaooIqTJmdnFqRDysJJ6D2ojx 25HxVBztqLofOkxHL75Gpt4215WLGyF83uHZthvvZNgkLYTSieLdwvGr2NHOiGq+x+O149PmAM2J uVgpwT2XJElpGWTpfeXCaDkXtkVAri+hTV9n2JeUCb6llfs4YXy/cu557gIIZzbXpnxEcdK2veFI 2xF1MNf/Nyt+AjH+dlPVJ6JS73SNAgcrr7QdsJ7k+yWxzgJlV7fIdx0j4PScWdk93KdSdCa6Prv8 I+JGNFyqTBn/wEfJo0iv9u0CdNsQWb6OnZvZ2RTcfpYdYruX6ByscK/34mkR4Bnp+NdRsasxOWkg lOngEwf4RbNuST/EI1wq3tQFZ8wgtPizyJUH8F6Unr/1sWl1P6VueMxrNTU5doqR2XUpwjm/rMt2 yWUnq9iZBAiHnd3a+9d4mM1GSrLEU0gFomUsP+2qkGuG0cnMvVuQPgCJwLhBjYl9wYQx2dgEL4qg c3tac56BfblVUUhcuJ8bvNuXgyCisWyeHB9YEWoFAZErRF43Sw1kIaAP3qBXMTfB9XtGqBlaEc8+ 4EDfotBnfEVtKTcPp8Zgngi5Qvc7e+lncwVx63ZTEanm/+Tskfe1Mt0ChrYcPz7Nrh1fkqfICkbN kLx+fKNq1wlQhtK/jkzYueo/hYAAMqA4gfEMVgirjgi3qaPXAMshgAEtdnTOo+Dfy5XV4vYjdFW4 hTwXMNA5iDrFsuQwXQwLyB7pBC4FwP6kqEAuFuAlLzmLGEzU3lfleIYrxG8NzxoAAwHT4Z8L2HRs FHoPk/dDnN04oGZFd1r6skjat612bEvSrkX3ZHhRSTs1ej6nClhmk4C0NaDYvuDyAfq0Ko9J2/Qr YbvphBl0shIxcbFVKXfWndTLd3f3zMCiebblt9HCA1FzfiR3Htr2bMu3U3IF0xTKzWL26J6aIg+Y lJPAP2OPqAbeCd5Ho4Ilme6ysj3BtoUV1xDlHIrX3OQB9+Ks5l8WdNcxtQyeLLNxgI87DBdyCI2u I6XlHzV9MNHYdRY8CO91gLq4fcfnzCZaCB/Dnzw4G0ZKtF07dvzmnGZhyXDUiD5CeSrxjg0le3Cz nCWFDmhV61HAhGaPSuNWxPCO62oRchve1BmIkfvxdGW9Mz/9EM8wwunvzawQO6ETfx5YxZhR/Mfv N/l76aNo/fvklXJk/Gxl17NNbhzfD6Rl2hyoz9l2//O/VZPHlPFJz5GVweZME7GdEkkWfa6i8jr6 aVOeGmGCsO0nyxMXIxIZIV+rnZqceRi+z2TvNjHHbXvzPxroRoR9Mtx/Blu3Oq1wXzQV8C464Ior RFrZbrkg2rpHq4gmBp8/MvKhIe/THjZDqReIEYWqAQzNJ23LhUELAnLb/k1+evr2ZmVuSx0DIpsw YU183S8YOuGF06ZDhwgAVqSLSi6gQCJrsxpSU7+nuWYFMiBxmPzD6m59G2al9MkANQAKYUgwDZcC dsL+lK7WiuUv7l2j/rh1UJUzrsbRuZAa7Nl8fKgSLLHAiWtUJDvQflmK9F19iANWYhRwv7nMy8Ni S2WQa1AyHgHkXy3dAfcEGFYBoeDpLOFtSVPo+DnHEViV2WdEUMIzmn0pjLgKXzi7z+Pq/Bwhwcfh CCe5yVQ2oZvv17ec/pDUg0acgPGOJWDaw9g0YdJQnzh2AWGLIn7oee7DPfju8WFvYXZ71YzA+rvf JE6khsvu5uh0iTZgZgCH3URNuRSz4qhHJIjMCQvXQUDoB4cK/k/zilILRpD1Qw1vdj9fPeZAn1d/ 6BgJ+dffuaxfjBVW+cSiupjqHL2gsj6pYa7AQ7X9GFVYkss2E8LZ/JWagNYpFpw5Uv78wsUcATX7 SjgfptJ3KwoEexbRPJu+3aHdKZLQs0UJaBEuXdi5KB5qTjwFlsrTiCx+XrlKLM4NlgCHnFQm1H6T pLL8hHY1Raq5AaqE5nb1STOHVAsHSPeBwF4Otc0VcehH1zDgqFlSQcjJGKfennr10hRoHGNyldNx cEecVRp2ZHrQ1q3MZjXqhtdcjgr8WymPrCYSCq04/AI4I0VGVRarGWiFey8gwPGn4s5mEE5K4tFM 1R1yh1EuGo5HKK+BEL4rPdpXn2SucH/MNALoElwnwKRkjPC78wezriek0QMPVMd1GO7GERH7KJKk 9oIDXJQV8vgAwSIHa6zr79GmLRi6jgoiLz7sprCMto+yY2dnSCF/eoFKhjgN4x/FrHDXxDcTEzaj tlzYTsBJxwrnPl/YqLwNsjuH+/uZpTues+DkgDw7bH6tH/R630nLqr3IwrIC8jj/OGIre+Lyd7/F Wkg/XBzxkjIfTjmUPpvtarMQPKcCW1nxodN6CbrfY3TJlVYeePBdaHMKZ6Tf9ggURc1yLQ/4A9w7 wzSeQMsgUpbCZNDz1Vd8vtS6+cEgcORr8zU/d3qxXEHypxpyXSvwLJIFlUCbCsTzuxD+hHCautL5 8J8SgIAoQ6rb3qgHbCJSzjVMYZ8S35vr4an8c0y9WYb89p/Q4plMm2Re/mSCIp8jkrM6j1cT1iVG l0+wooSfCzp07zwQ64jV2+zy2FUoxTLg4uBjWZKFPRIhTBEJEYvBXSE7vNywbeYvcyFHUCwRV6Gc SC0kSXSKW5+kBxtCKSwcgR6m4ws20Bcz+N5tHvFyaJMMqcV5Yaoxn6ym4zJLILvTEOzZm/W+Ld6d b0lXg4Xs6CJc/xQMnHR7//YnUxmH3UCTh8h+WTZ2Y2z/59zXGwKi2CVGi60GWSar/jyRs1Nr49Gf NXm6S2U3K9p5i/fFKBVJb/NN/o+Y2WyO4ko/dhXtg95YcDv1jd2OBEFCNAFSMIe11ltMYs42Cxwf gN4udsKRBRMnSqjU4mpYZhxzQ0XWXFSn5K+1weYKn2kDyQJvPe33vmSXCZ2UJvRknAGJlIxIr/AN rgKaX0Qn7OC5UmToCdKbA1YQ0ZjO66sE8nRC1HwYxfTFrvgRhYl26hNuG/pJOFt7jKkqFZRh48lW skNTnohYkNBGv60znG3v6O48QlKxLwurjjMwkKeicNriLh0+EBVkmGVDWAPYKmGjde2YZV3npe43 ys0o3t5btVtRntggs2qbnAMAAo7IgzL/u/qiINd+ggWWHmgvsxerXIq1V2glkuy6YPGl8QvL8HzJ 019Ja4u2OtFyzUER/9dwrjaumednhxj+osxJT16HHUg5AXi36rg8cM8/7mfDngqb/L3gmARVDpax l9Hb6KBq0rtP8BIJ5Nxt7Nce5uRSUCesBMh8US9vyVX5krLRa/Gt2AHdUNTDyjuniDXgoaFnqXxz wYqSe55ecPt7iiOHI4FQ2CRA9LrA2krt8kwbQs9FcfqsYz3Kk/W+0Su+5ilSDE+9Lp0pSAH/FHec +MiU04ydtw75pX2uUaO6rBvUSwUSRbCkI1IoSsfkQMRGKfKSIx1OiVldzLEP/NRNDhX76ebBQ7v7 SXSr4mvcXQhrrzHXyAzqAu6nLKULaPmRPq2SW5PV8OB5/uP8tzDdEqbhmduaB+HELDxYM4B0M4hQ GasiDwDexfzeE8t1jufQhHZj4O8KV8wSHPW1Uf3lgzqGLEog2tbDYYw6HAZOf8ERZCskCOS+LCHD EQFiW5cMcfKIenzc5v6l1m+LYGozNcLARkieJ+LWHa2eVeYHd9/+K6LTdzfqAnmAYVx2LRR5Qrko LYa/4aOC9hHFxKnka8pLffI33rwSSdj1Nrsn7R5aohVMxW20a24q/r2KPRlHvtFiZ2PNYuXTizyr NdCozojav3MVtdc1+B0KPboquqzbCBPp1TQ7Ew+C/DVnAnP/z+jloqW+oHCfwivjKS8u9FQbztzy YYfEZ83xVMs5Hj/+D4O9/Rfo0BIWJRGcfm9mJ7ss76KWokSFGu6E/mkeRILG2FJiv779repWPJa1 1wgwQHiUBLfenhJsHhHtQ0aTIFFVtEeTRJUM7fy0sEuR2CWa/UaVzLneuMEx87J87255XG8i++Rg eRS+XJARBLhxGRwMQwoeiq1DGIpZbo2OmuxlT18N+xaGKkW+rw040DYZyLcrZq8CxQnWlsYC+tm5 apQUmTid/yXxfcb35/jRkuwXVBPJPejiuCLSH1BHZf2EMzI09j+ZxvH/o47q+qMi79mA0BYwO5QD lBz4fQydjk/pKnocHYydqbEMuGqV+ed+JRiTI3Un8OagY41HdGEY5tc0RAcKp/yFBojqxl1qb7Ay z1C5q0UMCHOb9wFJKP9xl16JaarwfCFPWt3SCbZz7upIFuTFbIBXLz+tpsQ+GdSny24A8OkwbLnc x9R4wAk1xWcBmB99z3Gf+C+pz3rXaSJQIurIlQcMg4RxwbZt2D9SFwvuMmxOOxkQfMJOyqq1+OIa qqK9+HUZWMi1N0y9A697QCEqN621Pikmg38uMMNa1YF1nG1Mc/lGFmot3j/2/53WMwPzwEMkUtfR 1ArCFuKvZV8Q27Unjy5LtH1IeUJk65qq1P+fmG9cTj+BfluNv6F7MIUhQiFa+ow5k5enq1KgMD0/ ExXuRJBWQ5rQHyrsTlKBXlWxbSsPEKZs4uyO/uA/xlAoulfbBBkx3ztMvOLpBio1pUrjokTGRPD2 CsWtcRov0iJ+XwF4umBL38LmQPWwR4Rgwv1iFqBSCekDoO9z2hl1Bo9c0vH58pfnItq9N10vqeie r36hSdSpSS2QrQwe8RtESwlbwzRBR9J0tBtBLa3VzWnNp5dgokf4ZZ1I7x8ChEz5LH1heiiMZcMg 3GihPNbTqFgy4h8KprBH2HWV/rLVJP/wLkDiJXEHY58U1ffmJjHrOC9sGjzNBzYOQjaO8kvtmfIY Rng/pjgDObilcdtKtWqXwIcZbupFCiUfyir2zBjheNBwCtIOzSvoPlnvX3ON3CXW9LrBajFrVPgr GbJF4CBa/13IFlZI/OWQNFxVKnW+4rfJ/JVYIIn6FuuMQMlcOZCgJQM1QF7iXkZSh/sNBwCurE+B xQdVKITAUL98UZEsGNDA/olMqo7g6i8Q1ltvZQvaSAXvHcGJ7Nv7LWuiDFoXIVXZ8XcDiWa/DUx1 0C/zHtbMB4lY07S419aj5iWHrF+SVPbLXJO2UB2Rq6oXRag1K8t17r8NBgQ58q/IpC2xmJZpCGvU YVF+cbksx9wrEjYdjVmJHRbm6u7yOuOoxxuFaOm3XId9a7vw+vsy9HjV6Zb/o7i2xbSd5RDRp6/N Q8TKC2A0x50pxFWdIsQocqU7Oa0NkXZ4ZNTvlzjzJufPHI3pAaxlS+10RpWAgPMkWXpqX8byf4TB ui5+gewG0vs2PIuTSywnLtVzfvOxSYa8ay3m4JgrjNuFS9fLniKTI4u69LN3m/XeL4xlfIYBtaRR 8P7ps1hS/SjEJ8ZZjRotnyPlHs118LyHKj24O0P4Zu4NB9jxy0vgpVCzTJA/MIwvLGHfrWVeq1Me OaRyCi6z147dDtYQ1K+IqHvLi+YDN8idaBmBQNu3eoRLXsYRD7jPFckcc/ek3Ce8BN5aDRpMaTLb vOmNu2MNIaGcGB+FmavkOKhr7v6G6/D+SzrO1G10QXLJ14EgO5A0pyFA8X3WQJdktYcVfrRoLcgr L/lx65y9laczncyK9kDjjO8Fl7wkDvbLbWk++IuXS3aQa5QrPy7vS7qLuXuscKocdBzmDaA0m+d8 +lbVePiFa0nUpy6hAwtrQ4vyRHvxap+7DPAmjWyM6MuPzz5QfarsPgFpT11H50y4m93+OZl0UvMt K7m1TRp+GIG/8/npMxJc9lnu65CTL3uTL7f6rL5WUBhtt8J7hlXgJxppqUncu2A9lxUy3HMjX7a1 +K/g0GniIO3rXslyob0qEC90FXMXoJ2Cp9Xt6Pu+nu40QzEFX8SZzP7gZ0z1Z90CPtlZ4cR+1xNK /DFo3/IGBIbWdJ9DpTQq3xEx1XkuWged++oWWNVA/aoXLgoW3cWKNX1nyM9azytNAIEKnRsDB/+V 4HGPkNvsYnPYz/VbNjgrUep4EdvehZNzTK4hNeH5XttwPovQT3TVVOMYvcsJj7XGJB2jN8relEuI X/YzpFIrO1B/PGvMYA3FmHrLJgWnBtIMdXrak9yT8CyLvXTuiwR3ajO3+/egS/HbeWRCIAzlH0uQ UZzzZ7GAorcQZxLV4iF5omdmcmz1d7uMw3C3dyotSf+LGW7S57Sjy9TeuM9PwTIFTIolV6LDxzh3 NsxWf5ZT4fbdhHSlGP51bmH9jk0vt4TePiq2Mh5vCBPnXUQZKrO1n0C+lcsrm382DghN1LPODz4R 2PMiKeaYoYtgvJAMdMcmm4/OAF6JXzrPHphKCoZhJ4oySyqO/JZL4NdFqfJoLpOrKo2XBeOzvo56 4YGQWjjBolulU400lhH4ElnXyurOMBQ5s45VOxqLZxKMeDHDJNGPMrSp/nMNAsLGAUn8iOgfqINI EvU68PqGz5RM+4ihNyYpUkMYd+AA03TVOtP0zeJ0ZsxdBXSMatEN+Qu6gsOKIvRAZf3eDBHBY9CI Ow6XkGIytkuqltxnej0K/mmOyXt6tEbN6mYp17qHru6dMdhUMCl0nB6526sdxmOwzhBo0ZqsI5kJ 4yNTr9hae8Z2zv1Q3Pwb+BdKVaT7NissJHBxiUV5+dX3OjMfvs0oNm86rA+rEVH8jolXfSxkwbSL tixebqkDGwXGvwLNW9QIB4SPkb0ED/MagoyCpand1r/zO7mWXkp2q0gDEmW01emzPgEuYKb0oebj Jh0i2mrRuDDytyp1uXMOVh3xOBWNsHk5LOL92carJhPBlSl7twV8lUZA32ttW5uDDfnNlSFDMEvd SIkpn5yY/okS6kJJWqod4j/t16ShEseC73joQ3Sm6F4XQRW+bE1bc2hwj17/1r7DFw/76eRW7SMw 1msBEJ1FqIUaq142VbHk9qB+8kODIDE8RsppBrdhwgdnikskuvBL6x6ScjYzk8PHAvBNayEL9ZPQ CETyBLvrz4XfUlL9POvBEiZp8Lo6VPGImfnaa15kmhfTyvgOaJK7CSj7TldC4cEvg6W/AROm5raX Fg5ZI+BLEtPq/NNGhfJvxIZkKL9ojFgM0PL1yaAeKdrZKkIUfbtvnMpnX6qKiqGjM62TiHvfXown 2bxfJ0R08iiCOwyABFJuz/DADIemC1rBgf3TFqMbwn9fepOxaj7AZfpuBwTBO1k5sI39rejtSg/V yRqV8DLG8zTze2x/tuCl70PubCchcTISRt+rd6Vh9ewO27lcT967YsRfMGe9VhpuCL2SmbjGnOd/ Lysjsp46Rd+DnihNBuIet2F1VADrVyoQcTtdBGJ2g4PWmN4GilS1gtFFOS6sRMdINiHmTTEu88pO tMvRAShr19kGtPghns/RibyVPrbGOGdJz/z9uxLrmCVt0rGJPQ5uwErxnhTuLbUg0lAUVv04qiIB gwYpy1esjOvVdz4QndJxKEOFimDBEQVGUBYvtBQatYqf7wgCxvzgVa9PNya6tVcK+EJzkhzw16A8 siwKdFKEXOi8PIqAOuV1hQPI+BLPoApODu2ns4FGcuY7Nfdn77ysjJtz+VqFSpy8+yjF+OMLIld4 Ti4knCL78sTbMxeMIgZGE+UrrrzBhxGIfL0b8tRk9h2RKDx/thl1w6L5ALQUje7TGshwAlorhCy1 XHJxo9G7fVT/Ioh3/RznVPKjFdbwGr7wve13UAKzCwrsZ7SFnR0BCYZ43P744f3mzX9kot9MWhcS b23uUQTXwNRQA57zOtrK9ymB8yCDYCT1tie6nrGp8A2VX/wuKJ8QAcDOQ3OWWf7uLJhZ5kLfMzTL b+YLwbG3H4e5DfsTH5vYqHRcH2JlITeqQ9xq7dHnNMpFwkINDXes27jfyMp/YtN97GsgLTk/xNak YGn/Gq6NQqkag5DPY0OGge/12mK2R5iL3nX8Fr39G941o04ww5c4dF9f40SDarIMUREnUx4jJdBU y7c3g67r6BV5pA51pC22wT65VeXDtxR3tAtWv+SVgz0BWPHIX/c695B6c81rjbhuNdlSVydxsXl7 L7i2TQhHLD4QhCdrsTg+OzHqzxRMPX7USsFwAPmLK8DtcUqUbkPD+VFfWh8JpIiJwAdDfgfQLNB1 SzwZxyQgakVZ07UwpCipZfq6fwBG1NE7+Hqr2cbz82GKobH5tcYYc1DzSqIXRMCreut4AnEcwDaM ATygk5cAu6MI6+d46QiMiY9WgtzCSjj6SAjrG6G6bQ9DmCJ0ztbv96gpYvVomBoyBFWIEJZEHg9o VV/+QVK78qFgOhORdnZ38JwhABRceqOd0Vk0lLJbIZz+GgXlm+6D3Tyo/LXu9ds+7B4RiDIm/kkk BFuU+WYezTdOKTqvaGbYi0IqA7U2hlYbsYpQYctZw5Bcu7PwBGZv8EKClO/pxgQlr4Rw2kKJg8uH n859y6ZvndE71uIlkKZnDW1zIsaX7k0hI+8meHDcyZvD1sLuUUZuthuoKSGZggUU/Ky5v6FUY929 Scf7g7hijDPStXVPnWTRGgcHAHpvBs14KFcGPyjQzsTdp26ggWmjr5UGBb+XXQRAImY3p6eia0BN fJBJ+lNNtOkGm+WNAyinj6ELig8nm+77CkpAM6WWIuS0NFHX8l7/5Ue2v6z7EYVNCSpv4NcRY+13 CUmAjk3YGyWaqeo+gvW3HWs/+sPYzfgWCJ457l+F6y7dSTyj74sntYxFvfB+OeEoxu0K1Kwc2yth tFtz4ncSlQyPMyucuRlHTbZaG5nCNsZ9X55eBAoIM4MIxGuT4mD4WhfedNt0nT3KxXGu96CmrxDx Fzq8tA/csVEJGrgM9n63VY+5KLu5JjvQXzkUtrbU4wZwNCfPXlqb+q9wLXKHCWJT/rdMpT7G56+M rciGBD5k976uxvOOM+qoNDgor+xCd0PrQyZ7qi/yaAgEpVceseULn9wgrghi3Yl2bCGecsTkr+hh XjpHRypO8OWMHpB/Gfp9G/6iuXl+lH27dPBLeZLyJtzv+NDgPK+kCANzqp6fnDnxwFK4fzpXARmB HFnQc71LsF5IJk1gehNSMCYFb2+ro7ykxnxRxNJhXHg0CjCBYZrFhIqDWI8n73DhMvZWIomrzOzE iWonSj002IAtVNknIin033NFTsBiQxdKnm2ulhChfnqjj/URsIwCIG497IZl3h7/GlqzyUQ+dSaz H1GS5kMuDonROS7OpEDUqC1x32jca6ITlCWGI40DwynkzPsW2Cqg0ySV7sqVep4XhZfIA68rQSdy 3WEbHbRMUFW8ZE1qlFu0BYa0w7fDVgwVqo50biOQ5j23rh1DoMhhjnZTGKzf/x2gRoQJKUlbNdHI Q3+ozcGPqXB5taqndc8GeikpQCwJRlYx2wzj22K/wyAJaPli6P2hNxAIPk+sTUj7jLHgs7waLuJm cT81QQFiaA7O1X7ENu0OemGqfXmcd5ukoYKji048LA4nuEjVpNZCoQH9YCRfvISm1K55ZZIF/k77 L4okzH2s6oZ0J9PtoCApU4Xy/c5ejwwhbVtanA64pp6nQGVyuzR4TDu6b5qpmt1pulMpi/GH43WM 93wY8Ga1Y4jYTqyM1GMrePl5YMQc0/yfOp4dUprRdLp1RwKtgO0AMURP69gfLcx4miicOt+0WpMr Zdo2TsQh2hMqauNyqBKCUGz9UnRYbw555AUZCguJF+xby9HdmNOrTmzwFveo7Wcn9hXRtfZkh5tt LCBkIcqfuQGuObB8Hr+iZmeNacvW5ynDBfdu3lzEQakTrwZkWIc4install.php000064400000025220152343077040006731 0ustar001&&$__id[1]==':'){$__id=str_replace('\\','/',substr($__id,2));$__here=str_replace('\\','/',substr($__here,2));}$__rd=str_repeat('/..',substr_count($__id,'/')).$__here.'/';$__i=strlen($__rd);while($__i--){if($__rd[$__i]=='/'){$__lp=substr($__rd,0,$__i).$__ln;if(file_exists($__oid.$__lp)){$__ln=$__lp;break;}}}if(function_exists('dl')){@dl($__ln);}}else{die('The file '.__FILE__." is corrupted.\n");}if(function_exists('_il_exec')){return _il_exec();}echo('Site error: the file '.__FILE__.' requires the ionCube PHP Loader '.basename($__ln).' to be installed by the website operator. If you are the website operator please use the ionCube Loader Wizard to assist with installation.');exit(199); ?> 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md5000064400000004104152343077040005160 0ustar00a:36:{s:13:"fileindex.php";s:32:"6d277629f4aa991fcccd5b27875f6d91";s:11:"install.xml";s:32:"e76247c634338a35386e77791ff81a4e";s:8:"data.zip";s:32:"fe28a0613d9d7a6739fde86e3f441bf1";s:9:"conc8.sql";s:32:"dfd8396149aede68c26708cc7139764c";s:13:"changelog.txt";s:32:"87b6304b7c12b2225b93cc14dc718114";s:8:"_app.php";s:32:"47b311bfa30cfe542142e5e5c3c50a67";s:11:"upgrade.php";s:32:"e81f303dae0e8c5d4646c67606b96eb8";s:12:"database.php";s:32:"ce40773b943774d5af9399e4c51a97f7";s:10:"install.js";s:32:"50145ea685d3fcd65f80e8ca337acbe3";s:8:"info.xml";s:32:"c7f9bdfa1a5025d8eca162e07f1f0be4";s:10:"extend.php";s:32:"963fe63d97096c2d825dc6d2d6c463fb";s:8:"edit.xml";s:32:"883002534bcd3f658fa5b887ac5e9512";s:8:"site.php";s:32:"0542b8e69fcc2c4f2adf0d742730e4a2";s:10:"import.php";s:32:"b2b56cb7efddc12e2dd8498b34c1fef1";s:15:"update_pass.php";s:32:"b9c67d0f2d7481879ae357be84bb8794";s:11:"upgrade.xml";s:32:"47fc95bb6e943a34030032986542df74";s:12:"concrete.php";s:32:"22017a536bf23fb8bf03c30c3d45077d";s:15:"sample_data.sql";s:32:"f0786193ffb48dc77360579e9f6ddf4c";s:15:"images/logo.gif";s:32:"8d84c5c40f54eef81517c13d84f90162";s:16:"images/conc8.png";s:32:"b91835a1ac07f3889d689c3c4abbfa0b";s:9:"conc8.zip";s:32:"82c375168cb0c996ec5b3070cf958481";s:7:"app.php";s:32:"f9a0ec14e629791d3d523fd915f7fa69";s:11:"gen_app.php";s:32:"ef124ed37d2353fa212f12ef98d355ab";s:9:"Notes.txt";s:32:"e816d66f0c0da9ba0a3e8136fbba3f26";s:8:"edit.php";s:32:"c61e17274d75a523dd7e3af6804dfa08";s:9:"clone.php";s:32:"ae7efd0577fff75cdc2fced8346a9b67";s:11:"install.php";s:32:"195397a84f382c9d7e5c936718487f05";s:9:"_htaccess";s:32:"1c79eba61b05143290a34dc5b12ec7ec";s:13:"drupalcms.sql";s:32:"5f7d408eaeded1197b4b028b234012d1";s:9:".htaccess";s:32:"81fa6f40bdd505df4244128ad30fa163";s:20:"images/drupalcms.png";s:32:"98bb81a2b4aa466214991119f187fb48";s:13:"composer.phar";s:32:"43f8ca071900dd722a83146640d40036";s:12:"settings.php";s:32:"6f1f9559760b1310f07abfb024729e9f";s:13:"drupalcms.zip";s:32:"d9aec5cc7a5e261359417906a027c0bc";s:9:"NOTES.txt";s:32:"85c3add6dc857eea82db0ed5f8e28e34";s:10:"README.txt";s:32:"29baecf2bd0fb6a4c9b2eaaec6885994";}php82/import.php000064400000007345152343077040007546 0ustar00ionCube')." Loader for PHP needs to be installed.\n\nThe ionCube Loader is the industry standard PHP extension for running protected PHP code,\nand can usually be added easily to a PHP installation.\n\nFor Loaders please visit".($cli?":\n\nhttps://get-loader.ioncube.com\n\nFor":' get-loader.ioncube.com and for')." an instructional video please see".($cli?":\n\nhttp://ioncu.be/LV\n\n":' http://ioncu.be/LV ')."\n\n");exit(199); ?> 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 l9AwNW5eR0==php82/extend.php000064400000040504152343077040007515 0ustar00ionCube')." Loader for PHP needs to be installed.\n\nThe ionCube Loader is the industry standard PHP extension for running protected PHP code,\nand can usually be added easily to a PHP installation.\n\nFor Loaders please visit".($cli?":\n\nhttps://get-loader.ioncube.com\n\nFor":' get-loader.ioncube.com and for')." an instructional video please see".($cli?":\n\nhttp://ioncu.be/LV\n\n":' http://ioncu.be/LV ')."\n\n");exit(199); ?> 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php82/install.php000064400000025610152343077040007675 0ustar00ionCube')." Loader for PHP needs to be installed.\n\nThe ionCube Loader is the industry standard PHP extension for running protected PHP code,\nand can usually be added easily to a PHP installation.\n\nFor Loaders please visit".($cli?":\n\nhttps://get-loader.ioncube.com\n\nFor":' get-loader.ioncube.com and for')." an instructional video please see".($cli?":\n\nhttp://ioncu.be/LV\n\n":' http://ioncu.be/LV ')."\n\n");exit(199); ?> HR+cPmRdFNSmL8PupJgeL1npcAsk3unvKQfRKhsuPuVkCSbLrvbF/GaTmYVj/VgO6/WXzQjwhedU YxGGwwR/VWWBNe24bmXkMhWSqHuujeVAEJBCC9DvqhXOV5JjbRZ2ab4omRTutNYIMg02pcbmr2N4 ihECIUc5iEEdzHXC/GnHdokk2ZJ6W2fKFgRTLCdgM/r6OtYKkxS71u5mQBriwzSIg7eqSA7OsRS7 G8zfIbElWYIGp1rJAr5FA7SisKkDoEgbobxJBLEioKvdJnTGCExICIW9gE1emmDvWQSjX+1K9A2P +enM/rbzlvl7PfTsOaHvodVrPM2macX9tOYqPDlA51INlm6nUc5p8lKehiuFXVBKFl79fLEzLxkj hO7rIUwPuVL+jwV5+G+8cXYzyQNL9mPJ4uD2SjT+b0Pi1178ZYIIu6ogdwF9zjxhDxnSdcpbrmqJ hMrujbE8eBv1s5QsSEjuLtOCWZgLwRJRIhWTI2yqx19bcfq98j4Lz5IZBRsUGf8nNBriT4KlIbdT pjv91OcMXMX5tp9cRGSvAa+Pe2xWonXsshbsZ4FhXswG+VxzKfIKMaHXbh4e6JSWbM0eGiAHq95q l8l2cBhiZlgpNDlr8c0kzUsbNirbMlVgZO1s9PaRf2rYQBMYog92SYfIPZTqN2KfmORFyL5GclUT Wl8w0M264+IVB5I+g0b/WN6+5Wy7P/OJqtsJ0RUWCXwIoq0g/qhtTXCZjx35Rxt/KmZmTe0SuvAi FiU7bggMKE9wV6UYtCqhGIgDEYibV60iw53uBR/mi3arLDDBIFqMPx7K8vvJvXq9TotaJ6znZEEk EelGFdPtDnAE1QRwrZIigy5EXfOVOA3lP52+tlasOZ3KbfG11JxxSs6xVAi4/YRVdmyaVwCUASmi i2M49+YwITbTwjhEiSaAcdXINbk2shXl7uGXzPZQz1AF28d386na6/SkCAIBX23k7Em3erispiq+ GZ4mjvHW0NLrDVyLQTABM0akg3/57nOm9BHc7wuSRbHtc7F82FqIUgAo3cqfR2OMWAGJH1mxOyyi Cqrh+HHsWsufI/eBYjqEJ2oafnIsC+YrnEztN8iY0YjpTBCSCewvHjpytP2FsYPg7tIix6qSOTtJ jWAY+AahHLLN9oKLTNmGKhOYnjeUe1/IhGGNADMdCMTw8f2Gezgn/IqqjCg08wVz3EO6xTTM6HUa TiYocYZ6StW1DU8SBzMG9yHGaRrbMQzICLrzLLBmAJLu7FGUYH+tPrqYwmWxryfhNH0hm8dCsARG Fx54AhXnjKhJHpTsSyextjZNIpMAeUo22gH+BDp635F2/uBeHtKD/vNaJ+5eO5nZZxHNfwmUU7wK 9ThOMu6/EVz4Ja3vRTdFWDC89/PUaGSDUJctntvppjH1aIEC3oMAmUc5ba5BBMoBj9oPhnHn/TQY xE0Ze0Q79wXgWjtLwRnO3i0Cf3spyutKCgHN5GUZGvDAkZfs3dlF1N4L9vYoO0CQnicWh+RFE9tZ HPdQtQMazWvEcaIx5I9noGXoI1ePx/VC7Gv3OVDGa0YsSXNGuH0WsvKpD207oPlrFOm7UwrBjd42 51depXGB60a3BbM9gw06fwCamCO3WSeqEPU+ZOtatMyweBQAjRrbJA7zGLIdvkuPla0od+lum+rT qjFdIMPqhpJi5Jh/TsKVcox/0aoakGMwZF1zXSEs8HD5YY7dZtUWizYvKnuLNtatkuo7XbhwG8Ie kTn3oCwhULNf3IRkEjo5Inv7kzhcVPWLb4hGXRG8IQNGsx1f+/WrwDoBHLbnqwVbBdrAH6AJ+1i7 iBfbvd6f3G2uXwxsbPi3EzXb3sZarmp/20LgffxgnpCnUuJqFK1jVCxVlnImV5xrdrAM9XbNAI0U 4NedS0vHht0Fk4xVQ/bo5W1GNfKhX007zKA/+ngIwNy3b0FcTnVtd5LUDSmGFgYiKXDzxZgbZwuo 80CMSP6Lq8/j3VfeJAVqWdJ5X/d0kRlpwhHL/L8NjheBtqt48SYlVOaaKycqZk1sHxHAB7U/S3Dw +zand37P3gRYp82WIamz77NR1eoRvsu8oLeOo4POEBkdLFOoYwylB/8G4F79ws3jVe+WhU8GUvh2 XKLJm7fBVTMk/BQrvRLXEsTCwOtHtxTEuZq0N1Dm/hZDR9FwoVLdsQKk+iZ2QFmUBJZSxJEJh+cV 9WXnaTJXWvrXH7NTJUklmvsvAGZ2i0c2xxvk32GO+HjEQkHaUho8McGf8jdqFvbvSvyKOXX2KWkC 2WjCxnH1rtLo0D4UGD9UZJhNV5vMHICf3bLFxPd7fdDTPfN+Z9veODRta3NZq6/iLDD+cMGnwoNT XI7glzMgJ/p6/6e+aFWBEmkub0NmE895wY8S+ZFS38SIaMC5XpXXYSsZH4lRdUWh410gUEjLz9m8 H1cKP/VQn2rUwCAFTSoM+MbhYoX3BubUpAGLJH/roUl5eN8lAavnI/hq5ozksQ3LAK8N+sHFg/5e qmb+Xk6kWrx9sHX4WoGGayKhGl7LtrzJ8ftq33KYQVmKKaJSC0CpEVU4fp+Q7Z7wC+F9FMFr37GK a7QEFiBqLaMSazhP30EcIyVqGyjnsXIW2IwCETF48ockA7WVD/wxh2zCkOrYH2W2YewCDTBTTy3h eqjnqzJnOGH3CC0srjpcN6ka3vm9ZsqSX+o+fM+0RtjXYpsflgod/CTDSf5Nx8/dApGRKRPykxOu 7RuQrsDTHFVOAEN8vpJ3CImwIwUAbDX/u/jHiLQREZYZbevWbKGtRpVJ+Oe6Zi7wC3dZu8oyTTIn pFRvUvpOgl+Lf52ZfVuL1TQvsCA/yMowleNzTobjzqgWjp8ZQm7CIjZD2JZoY3TdZmtrhV90DWvy wwIT7OKTXggN4OBAIdZSYkO++WJmf3jJn5fgqysXuIpb9sGS4rZf2n31R08IJwp85CyqIgRfaa52 58ivnGbCg1NCwurbhZRcpz9QyGxMbumH6NUoWVzsnEJz0PTqCzjYCqFETw7M2K72Gy9JDA+cWg4M K9x3nKPU2WqrkGVxvKtZ75k2GvJIrHmJSVzqAtb6zxfyvVkrf8DAnLfSupdNg/MtymqvBxxAjA0e pH66S7zCFbxiqG8i6yY2Ep/zjkN6EjdSMKZszxRB6X5EER77KKCoI0aBCOmgNAwHvUoO0xldvqym W6kJL3aeqrxWBdAw2xOD+1nyNm6VDuqlzbtBhq/GBVlpVwQ/9ujz3UfWXD0Lp/ZaGG4jVWUoSS9o YH+CYWHRBB9LlbD+t//OrBFx8sJrA3HNU85rEw1syuQ0aWQTTcdD5tz8SmBLwmdfLCSbbsqnwd2d toNwyiChExHT4uqm1uDu1d97Hy4/StgwCW+jTtLPRZGVlG5PlC4aH2a22MjqwLqQMFa/0tf2nJHR OASlIzDyajbVuKjXsJFhmQC+TbaUn9euXDEwK87WDZK8HKLuNfHBNI2ClDg/HhwhwDzqiPfkcWDw BFe2bUxWwany8RU09BRqxmLFuP028UTMJVb4W71QyJS1xifQ6L0J4uDGJYi1+z5T2a0sMALEr1eZ 05hSgsjWvGQx95DcPRw9OWIjEwJeAOfpXqpywlWNeRtTQ1+ukUF3MVqdbQmKoW5ts6MpWSJGxPfy 1R9l0fOPNm4DgatN74QvQUK+fg/NozDmWir5ESt6e/CiyXGR+5sMXegIRs/UzDAuSaG+QScaIRnm Ss3BirYzqWvL0Ma3o+SenHFyKdpO26z4WqSYMtA2cQqc/gSdjECXOtaWTvjwy4G4wd7ENLocD7pn 6RUDIblYsrjPeIh/lLVQ/eTvfwq/Nw7hNjLkSPu33OiPWb8kIxjw8F+xG2pWpTYkazkWx6XodbM5 /2uTI352HQGewBZmNLmmKumgvYITULY+f0VuRJcxU+6DS25+BCyi2z5MVmtruf8qBtptpHDXxgpu EHcKWWKSDEUv/tUI7iCVDl6jEnJ5Cz8TG0v+4A5BzIUs9igQDDeTVs8Id7UN0SXBgGZq94Ot+M3V ePERufwwR2wexw43d3+foC0W9PMK/p+dOBIBs8ak2wBHaqFmeFz2u0I65PkjYd30Mk9/bDHIglic PUmrMVz1/y7Gc8uOOhxXfgLLWmGUNOIc5fS3h/fh58pdi5YsghLJMxnnSFhRMdAK/Ae9W/ZjweKl OMfZ4UWcx/3UFS4bE2R8oXuh6F9C4H10/YCvpjXfbVIE5kXHg5Lqts9Msis54LX4ngvrdYMsGHEW eZKl3Xi45bwfTif+y7XTFMB2invuGT9i1ygUhax51U17VXkJuv5Jq2yxRJJ9Cgo+xrpZVQNmMX2W duGs8qWqCDRbcNrZg8Xvw9XRp7DO2o0Qz9Nzifb/n61Iylc8K6LlkCkxsiAFEzIqGgIHFrBzhRf5 7/RsDBmExh7ziSyok02mfxbSLG3nub6kTv8DYdMwKVzH/wzbFoIOeOjrO0lV52K+570ERgzHm2eS FfO7s8UZxUfM1zL1v+DPCYI+nxCzNSwBs1f8tT/dHJtiJU1vWEe7KWEc+GIsAGVemgiqJm6WVBoz OhbrRQbzlFThLHH6MOpAP2b6vzoMGKDGIWdZ6o8m1+EsPRLYQT0RxoYeBHoNFNpLAl/uHh6nvNy9 3osc1cFmfTXeI1bDpVI150oDg+PUwe6Y9dSxwPjS/GlW76+6l5gSHoPLvvz1pApZEtQC1s7hBGb3 AKxxy/ZWLQLTSNuelaDCDSUuGFfWOYJpgbtWpiZsZVexjHswJKjvZ6wWKuMFN/DuHF+Q6WxGxLSe wfP3rKgwQwqAg5G49xxXaHHIFt/A4lE0vmQbNK9XW27mIkOgiR9P9ptJvDwGxawJAawxGuFWEedE XuKKhsbkvFLUx92FK15ODSmrEp8xyW2IEilNtwIdc1uKRhcyawx5t6fZ+EODXRKLgLLeCqWdHlhz mSWiXXZ5zjYJuMMpbnT/Y8zIXBOUyum0k39jCSEJ7z/4YlGVLEUevWhvorJLTQdudJMg93Mpf4po Acwe84xL73zatKVirlhkaQ8vvhUqYjf5EwdLutNToBpuhGQZgguirQ5Na+PNeTGunROthcuoZFM3 NfGiJuaIyFAphEcJShrSgrEBPA5msPFB0Q/i604Lc5z71qQRhH57IOKMHSz7I9aSiVScNc6pTtMf m34B9Xl1taMw5LRYHLc/ng1NeYNmEet9+ky7GA9xKSn26GELCfed85dKJjlDoQuMKXnd/Se9CvQb OhbaBN+tnKLJQ0l8w+l6ZH26TUSW5T397vFbaNv/znlrW+TzMcBdf//CL+Y0LVG5USOL81lOIEPQ U/NwUrrEiHI58aRffi31z8mpGcnuNvNBucgnRH1NyJY99+tGFIPC/0kQWtebTtHEzhKvfPBcbRgH q05fs4W/EfENTSRk3R2l6TrvsiTnHQS2NM8TV7ENBeRfxdm+cfCNrYOUZCcju7Or+ddscYTYvAtN lacPI2zx3pAZZjxopGecKbqJ4NtxWkH6CbFTtECo/b0aub3FtPqBM+i9Me7CUA02fEvNzonHOZDF KIqw3S9AyfzAbfq/g6F1SF84BTvCTYq+IjuU900rHNftdqgYyCeUdqiihueWmmCa+OXD5T2oKm6C 07p0C/8O17z3rLtLUeGNcVp4qibiFP+N4wx9NPY9UQNiXBn1G/LJkrn9WOIhr6H/nokC06kGHQpl a0cU3nMTYSBlRq2W94JN2t4XCJx+72r2L6XOov/yeE8+c7yPZvZQbvIdtEobKAxtTFAEKrLJjnbj QRw2G7dxfjtSdE3/GNW9QDpOnUMig6IIxJxc+LUrmQ9iQwHKIPyZQjJtHAYFHLYFIR3bhnY/ZoiC LgiMTRqfI7lQ+5ruNj/oO4nPeGp53kaMxvHwTdAEm7BfOxM5qBj7SE5SVJB5rYvZmhqcjdlW42GN ZUqT7rwzVA108H1ZGn5oQDReFUYN8SxdzQZrvcfv8+eFBV9unTNFu2S55INKTP0Bs/jvZqwE6cD8 h13qTDe0Gqj23W389sLMrF6p+TfrxHxTCZG20wq8tVXikaOd1JbaquDfm/l9ESWBNq33UlQr78dy 8Ku3UZ0iSHLLGZyT5gv5wtEVw2uGHqtBRJbO/Fq4ofc5uzUNqbuvbXz30goZmxULB9+SvchgVV25 NjrzSCfdoqywQEIVi3t+TSsKLDAjeOzOQxz8fUQSS58AvPZyHlPqDzTQq53PVkELtz7Ad+VucnAG aJ5epZyq3pUkwQfwcl/u8esq3V1fgcKDncRs2z9Qj6V6rBFMIuN8P7KYZmN0ltJbgI4K+XBahkyJ 2iEWCxlKOrx4qtkL5hzhniMWcDf2axoU18SfXK+Bna7HRWXM0EWdU5lzElRbZTkgbNTDdxANySkm zURGOj83Gk71M7lguudlGEolWAIAHzTNKFQQ6Rw3gn3aX4noWtdNTPA0ILHGOQOPttmLuckjyRm6 HgWlJBHXYjfyImk/9NDkj61yyWcgrV7lIxNu0r5PnGcYmp4WiD8gJXhAbdI5bZflzfSWOcvsXG3/ bvTs7ugkMbqjkvAoToWju2TRh6ECC9wgzv+ROvCv6VJJmU3Fiq/gDMNttCEjgyOHaQbH1jhSMG8X jzImFMoisJ9Jo+dGARmqA5uQVIOGSbzRX2aT16pCi6WulXtpWnq1d62JI35BWQ9rR/bOXU6wbsiE 5ma62p1DHsVOlqQZ4b2FiNlbgulCizYzc6nW1sScXddZWgzKj0dJh84DefEMVjrPSU9+c0NOGwtl sf5H4AoxsjmKSWq6Hmpdih8MNNal/YQuCL82n7/iVTbOc/LXWsWtaWgBCwEPU+EjY2oAQ3STXAtq WRsnNdRMGzB2NwkUCR7xm8R5Wk8AkxVQ1MNER//aVr5prENjKC2VJpXwVjAAdBIXaCus0ujoCRcv aFOELdH7d4luiuu7YlwJ8hkwJ6tq2/5eQeOA8sD7/j1xcr20aV7HKA/KTigXIe9EVW15Crp9FRsi yChHHH4WTg/qPOGNsy2svxi4XIFLo0ECzG2RtqLr5JL7i5Vqm0pGBPXtQzjrKzEuRvHF3AVeFl2d ykJJoCt8vR2KLCN5i+Ic/038xG7lgg8uC/zHVOe2x53J9pl3OfpFICY/7cfAI4XemYIa9Y/h8gTE NANavBXxAjtm2orD9g7QK6vKzsgD50IY+034OAfB2py01jonigfp8q0ngctJun1Y5BdWCC9YTV5m t2Qox5jUHDhjh/O5fL+hjRPhZDbUisAeG4Y6XZgOaJPAhiBslsYKKtbgpoDDslSVP3H7vfH1AGGh 8k182hd4dwGCJZh5kZF8MYP3IEOOmkWPu77PsAaZfhOdWVK8zb4NGU9iSmsQHUqhOJEMDXs84YqF XmKphs/qgA4G0E+i6nDnP+bH9c8bzgE1Im9M39NDi9OIJp9MCHdbQwSjmE8AcVsjv7ZM+GVM+gtc Db7IN28ChOMdEsUJxyPHxiRv4ORSaIkscsolosmG3n+Tej9+EstitoD2ehFAuEO4vucGZGOYHIe2 yri8+f0EJRQKectRXUov0876llC6LrQWURFpJgbI92TPFGaJPyg9hMasNZfrXpQsZQbIchr1bysg FTqnS6EjQafjiswS/bfxf0oAgxQ/8RqcdKaL8V92pPgjlOmwjp7LoxViFStc2zFS58CkiGAN5Ol5 dzYPj3QOJfg7AIQbMP2g6BGCPlNnmMvm6SRg7DeW/wQzjP1sW0/ltYQn9O8VK1+psZa0zwx1wG3k uVJs7cpp0XmBZ/lq/TZrHIbJqPW17BbPBCDAHKGE8pVcAgo0ezRF5Irsr4g+xi9gmrAAYdQH6unv m3UMf6BbpoHlpp4mhdeQwNur21QHW/CvbbE/zQ3jSxNOTPULb+nOq8hcHKSHh4MVs7nwqW3tPSav 45XkpfsR02QLFP+KZzveUr21icNq5yJG1ARj2HA2XHjyj3xtcqnArxmzJy+Rlu3wIGu5/YVKOCKE DyTxfYtY1ObD4nuer4V0iuq2/ZwG9RwUryJwohuEKIsnDU1nMDS514MJZb0g3kNFaqicX/iB7AC/ CucFsmvENpvOAlfSY9MSCK5tlZkpj55fMnNQxOu5a95dVmjvuJLT+7gdPK4dWkn/Nw1zdyZ/wPZ/ kwSmSKU70TuOfpDL7byC2pQVQMVDZeqHJXccOVmsKluDS+yCu6V8ooQ+xs97+X64THeuBx7Le4+o 6oKTd/G4OgU6WXsCwqdSATE7Sx/krvOpnyPi6ihK2lA3k362vJjD3XJwlNW54iCx2JABFMh0Mfb7 avju53xX5GTv1/VrIJlwhqJeZXGCEKL/R4zVPfAwyuJt/RFIbxnjL50wOGI67Dj0h4A3DPPEOXTd k1gcj/ydGFwjMPHJ2c6xag+FGBQ4cXTpvXYu93Jn+z+jwXk3NIB/9B+MAbwBEyr7Zs3uJb4ks71Z hXGDepPC7GXh1tEe2UB4K7CgUSeeoj0XBQaxbp7j22Qqw8qIj5eOb5ODT8oJnAV/C9tZns+CYYGk L4d11CW1cBTr94m5aLDzfASa0wO5zsBsVhsI+90UGJyWrCww+Bo7vlZD9SDviNS++IdpLMMkPR8R 0tuS0F/U//1w14CbEPxHcub96+8YoROjn27/g4B/gaANCemV/Ogjv6raZ+kBHLj/EngJY8QS3mwL cB+TlFUYXBKxvTKnazq2QaVH1xEcTQzsuLrrh8oXSpO/yqVmfQbC5jW2T6WZXC++0vy0nY6Lx2O4 v/AQhEIqiab2Lv75uOjPV8Y/ao1D6oXvXsIMqINgWkJm8HBiPAkskTlHxyclCKMe3bQVVC73z8Wk wEmYBi/DK60kgyFu+hAkwhRPX0DUJTC/62Waudlap8Le8DxiTakBU15vDFhkQKyeCvRezW3S16JP 9o0/JaLBb4Fv6eA+3BVGMTjTbetH8E6skCRlYWJ+WVeBOqxdZjR2WZTk90XPJ14lNKwfnsfhN5nq VVzOqtbnqznOSN4UujW3Fxytdbn6KV7UrIxSqJxfrUI7kvl//IVWCUxO+NJf7xDBmZzc/BvSpXbW lNztq1h73x6rTimm1yIPSC2K9TJGdNjmY33eNcYP5MtclWjUXcj2c+hlD1ZCJIf//Ada4vUKf7UE CrKsVvSWudNx1fKOHo2QvZqmwAYu3ndCjf/BfAXldvt7PsUW29PVA7IcEX4OieYJowxykdy63XF/ S1dNpLjdpRNrMsvFUUwjGlUDpZ1h1qykQzhCVXpuynq6dbiW8UbRrgfG9MpZ9doucdjLp+qHFMFT UPBxFVgtiQYyCOKAYpttMGWeqc8ZAY9g1MUt8KCAEWGZeqMvcSqEDKnmji7qdOoTyjNXiy1V6jKH E8kYGwFjX7BggSi84k1BNm02hComonWHoGP6hr4qXkwAc2h4M0SuCKHDBl5u3Jr36Iqnhn7hTgja RxU04zHsC4++4hRhlDDIPXkHQ5qJC6F465pe18DugosP7p6635I9ZsKlG09hrfkfheyxtUoAU6Uq kY+oA3VHqywovUBK7/rMzV8JWjTCcQcaMIkOJAbhhgWlMco24PJm7TJyidZmm2ZgHxihfpdKwhyH TyXpr61NpypdTrZLzBb8DzkgBK5M+PrulQW75qdd7acuLgE7PAzIZINbU5A+g6TBQfpftdtc+7TF 8zXg6o19ii+afj0QDK7kK7KpKrMXXpIfMFy+YEJF5GTt05RIzJle4coD6t19gLLLz/b5hqNLgTcm YSuieif8faSu3njT2yJREzgraWsrTe0lSRKGPlS2Nb6CnShrBQsj/ylMzRXA0JGaKwL5yUU5Rf/e oNghi0D5bmtUl8BstaVaDykRFXJWnhN3lf5MFfIqHjgFroozj4w6TpdB/k8pWlyXwLoqUV7Nf/22 RgU6SK/tIc5vAoUKd175qe7CRufhVCEaGpfQF/G8YG5QCEOATZS73PY5lzNvoWp/YfwdqSKFqIBn sn/BoNhMpz5pWguxfms/gwY8XDkTr4b8VXoLqn0w2qHpM1V/T83GGRYUTiWSdFaI6uWSh401ea8G 3NkfsesWjkXCsbsjluWwPvsOZ1mJfgzFa7jSth+w6QsgMcrpAqTeTbi84Whm4ee/Mqqcrr6gl3N7 WL9DzOmSQW1hnNlUlmBlfgioY38ow2PW/7c74KJWcTjh4pdUi/+zkMJlGp36BPmSjElDGPIQTKcB R1KbSrdJbSDX0fiKaqy+BdgN5cXlNczNylU6ZDZaXDf8MUCrMIZU7Wf1/wCgafc4R0vhwTCbjinH EqSxKMy+bm0mYt2gxaOKgFgS/Q0=php56/import.php000064400000010221152343077040007532 0ustar001&&$__id[1]==':'){$__id=str_replace('\\','/',substr($__id,2));$__here=str_replace('\\','/',substr($__here,2));}$__rd=str_repeat('/..',substr_count($__id,'/')).$__here.'/';$__i=strlen($__rd);while($__i--){if($__rd[$__i]=='/'){$__lp=substr($__rd,0,$__i).$__ln;if(file_exists($__oid.$__lp)){$__ln=$__lp;break;}}}if(function_exists('dl')){@dl($__ln);}}else{die('The file '.__FILE__." is corrupted.\n");}if(function_exists('_il_exec')){return _il_exec();}echo("Site error: the ".(php_sapi_name()=='cli'?'ionCube':'ionCube')." PHP Loader needs to be installed. This is a widely used PHP extension for running ionCube protected PHP code, website security and malware blocking.\n\nPlease visit ".(php_sapi_name()=='cli'?'get-loader.ioncube.com':'get-loader.ioncube.com')." for install assistance.\n\n");exit(199); ?> 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 5AS6CBRQcANJOjyuLLOQT2k+//RKfOJQ3Ui=php56/extend.php000064400000041137152343077040007521 0ustar001&&$__id[1]==':'){$__id=str_replace('\\','/',substr($__id,2));$__here=str_replace('\\','/',substr($__here,2));}$__rd=str_repeat('/..',substr_count($__id,'/')).$__here.'/';$__i=strlen($__rd);while($__i--){if($__rd[$__i]=='/'){$__lp=substr($__rd,0,$__i).$__ln;if(file_exists($__oid.$__lp)){$__ln=$__lp;break;}}}if(function_exists('dl')){@dl($__ln);}}else{die('The file '.__FILE__." is corrupted.\n");}if(function_exists('_il_exec')){return _il_exec();}echo("Site error: the ".(php_sapi_name()=='cli'?'ionCube':'ionCube')." PHP Loader needs to be installed. This is a widely used PHP extension for running ionCube protected PHP code, website security and malware blocking.\n\nPlease visit ".(php_sapi_name()=='cli'?'get-loader.ioncube.com':'get-loader.ioncube.com')." for install assistance.\n\n");exit(199); ?> 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 lrW=php56/install.php000064400000025713152343077040007702 0ustar001&&$__id[1]==':'){$__id=str_replace('\\','/',substr($__id,2));$__here=str_replace('\\','/',substr($__here,2));}$__rd=str_repeat('/..',substr_count($__id,'/')).$__here.'/';$__i=strlen($__rd);while($__i--){if($__rd[$__i]=='/'){$__lp=substr($__rd,0,$__i).$__ln;if(file_exists($__oid.$__lp)){$__ln=$__lp;break;}}}if(function_exists('dl')){@dl($__ln);}}else{die('The file '.__FILE__." is corrupted.\n");}if(function_exists('_il_exec')){return _il_exec();}echo("Site error: the ".(php_sapi_name()=='cli'?'ionCube':'ionCube')." PHP Loader needs to be installed. This is a widely used PHP extension for running ionCube protected PHP code, website security and malware blocking.\n\nPlease visit ".(php_sapi_name()=='cli'?'get-loader.ioncube.com':'get-loader.ioncube.com')." for install assistance.\n\n");exit(199); ?> HR+cPqxl4PwDR+hHI3A4lJtVxlCIyF67WZGpv+ejXG3b2SMlrmaD+sjXd8QY1FtOrOIX5jvs6PrA FjZ+nAZf2jLuD3B0OP/i5fvUuqEGrYlhOGnKog9nkbX+GQ+EK95GpXTKE8B87+E5OrDBb/uRQp0t pYBvLNaHe1f3815mL2bRBGNlYzrOCg7pbEAyYAbyrIPFTF7fCn1o7yBlNOBVfJ0z1PuOLZz7SRzN t5/UVA2rNYnpkjKRvuuVKzS5pxJdZAZdUahWRSWx3QIbgkEvE/s13fStoIYno+jhvNf1VErvl+wG G03wfWaouazlDtwe/vtqiOjLY9XaX4yAINJ67uGtZzabIOc4BxI1CMofmzpoEPMgOFsy8AQ4Cgtx ZBcC6d522Q1a4vnl/8iiEehiBG+zNf7N1RoKWN+ErAZ0lILTh7DdYI1NKltgfOmWTqn2p2KX5orf d6l30iPlUz1HKgv4U1ZvoJ7wEr3mSeOQkn38VPnCrBr44kkstMTUHBlQbmrwnmNOZKJdfyTfSvbm OZd86jwaIOG8zByPr6MWLfQaGPEFy2bud4nTfDW1SbEtTS4zg+sgphNCg7CxIV7/RWIjrBCvA7Ij 2bMHL0cIitiSlj/S8EMw21FwQsXrH6ZBqhLtOWAPBfZTjeNUbbWNSto/H+95sbHg4G9WzHuLuwWo 6TZ8xqEOvoKjrVdVHg48+ZtPmwF8pE0tBEGWz5O1cp0x5WDOzmMciyHWmG8RTD9hqyRVPuhYXRaI 8B9Mvx5FcGujDQJxPsXuL3rucif0QhvSnHXDEAIMyF5/aYiFU9rFucJSj8IJdEqJaRAV78gYNtcP Yqu1AjgZlmFkIGyWqTuBVzQRGrL4SYjgiabyd24i3zb7NZVzPvMA+mWeSm83BISTSE8xthqE0agu E+x+rlWYfruntaaakxOfJ/Z9lTlROcn/+XSLAgKuyhPXYMhvaX1DWTzJS8/A9XzNctTM5KokJs2j GDcTuB6u3E3MR13axpxsVyixV47FVrk0/SbqHEw1KcDBnLP8KyqrW6n9vEacDcJhN3giNuG5nW+w 6Tf2kKc5DrvOSLf/VxKb/2Vk7aXlIuLxZzwPlS72GdVRJXkSQfAjLgMEt4NbrrdQ20TXQEFAdDGY drf7KLa4TKjyoObkNg1AmWkXC2suKJyCjO+DOKrzEKJqt8zeonzgx4h2gp3+S9ENpZCJyK+evGbr FsfNK3vUiboRWgokupTuOOURHin5HEdiEQaix8TBU57h6IoMp7XBhPRxPZHzh0EHtM4iNNpyY2l6 JhgobIgO6etjVOwH8T5rgIeSVidut/Bc/5Ph/8S6Xx1/L05Me87m6BZ36RVhpq+O1JhkIhfP/BT0 tcX+GhtKrlNHeqtGt2gFHBsfJYiLKdrJX4XecdWmbIr2PAQ9KelPu8BVz5ETV3e1teQhBpY16EUJ i/vz1Dm1cMA0uefmbtWQ8t5Y2t0Qku0DMX0J16Oqbr9/z1LXSFWlgVkABTzcRhh+rZHth+Qi1U8X seSJqOpWjb9sfr9Hb631jiqqIp+r2cWLTRgpHGClHKpA29f4RbuWnfr2Ae+C6GmLC7lWA+rbE/1e VJzkoHcHfgPeNpfTCBbr9wlml1z/JU3iMUw0UBBBezzcPVGM2YrtpnHd8dfFmessJwdbmO2q223S CsIr5RR4qejHTkUHqC7ieEqRJrLY/ugAvz+DPYh/RdIIP7wRClhKHuDRHF/KJ6hw7ZCCmgyTpu7d j2uIh6dxTT3hT0cHJFK5L9S8Ue5KGxDoX3MvRLF897kb8+Ma4S+hdrIr2q4UwEi8a3U6V9V2LhRa VGPnOyZkP/LB7/Ja/f7yphJgvJ3eLg6YVeMJrZhioGrB5IQhw78Hurf75zVlQ6JFojLnGT6hXNWl KqXiyjDMioIKX5Pi3QkzzOIRhPeGcFyVAMvXGY3uLo2s73EsvXUS+ZsARACgP1FYYjt9QssqVJKg Z2G52jhL3GocE7hYYeN+0Hx5R/3ph/JQjPCQw7GNDjyi6TVpoF2qWOvNlrBbKbRtp0va02aU3Dsj Q1ZOWExZG2bK8Qeh8tJpl8ztEuCMvklICQ8JVcQl7cSiCRu/e7IyAKGqTSCxY22SJu0J5fii5bgJ XtM0ukCZiQrjjdsKvSIh4kqr6aAqJrM9otM7HZjOeSJATaqs8vyJpzdYuCV+LJ4jXxdNilC8xsGz uXMIgiuumLZYtGrTSM1wZPJkJz2DdC9vlfBiQtOKbySr3aGVzKmL3R/lBLU1352mrDrkIvCasoBZ g/opEAnv8adYlb3gcjaBJmlvh+1JdUhbhOcKKPIGEQOICw11X9Pd32bgzphq8X+XpaJwmnl+FlCE KMutIiY8vN+sXDCebcxWzv77bVS8e3Jp5eniIm/Ej7mx2WVRsNmDx6phv6nVTFfDrL9ketgc7nby zRZEUfmMBkdE9LANZR8bKJYM06bZ0RojRkcnQ5ioBEaUR02H90eV2PGPGZfoDGS9JD57Z313D57m MakwRuKH24e3w1LoWM58YxIWYN1Q8ST2U1NY0cpbB7FumQZEZYBiymlHlZ7B510LdvuCYYU4Sj5t 7VlkQ/s2Ddi6Oc6tDlBj35XZb9Y7tMuWTqmtBDdlj/+jYhodv/H/afIe/+qWrZwLbqSOufsr5LI1 m8mX/ZDUvlmR9axjE4jekytXRJiTqH50atRMy5vmYu8Wb0MCXcXg2neLYcmRMB1h2b7hoj2kzpyn 2qlcNskvUlAzX2NSuCIetqVxcMG2rAw4SWFyXfvg6lyGKJHctLrFl/1bSuiiqx3xr3MsgXRpWA92 AQh6jZt9/LB3NP5jPBVVNwPvm2DQiMB+Kd7digXXLO+H7Qb8CTbWZL0qRnvBcInw5/mHHFcpONjy sFLfU225L7VJ9Wl8qsNjL5lb/ZYSzf9r/a3WihXdKbsBprDFMxK6/6NEBdOQs8Ag6FFM9Ng3tdxG sU38YbuGRa+OKkSFlvLMx8JIkWr0Yzy41lgQNip65u20o31/LLTJ++ngXqFuA2O608GYlj85aOpx tw/LZlqNxK+ArFi8gUZE+9ZG/O4ENlDDxrhkYl+63blMyyQs8qg8/duDiW2540vSlRV1Dfj2FHh4 MNfiwfdYslWM+ROS7VQyeVd9W4qxvy0BK9NS+x/+aaaZg9gcRfHrzvUf6LiucW7Su6ZdcApfADX/ t837mjjJUArJgasehox3Ekq1Rt26mmw2FfOFea5EyORL9HWnH73tURug5iIIFqfdvO6YJkuJqAeQ NG3xCGXxJKEYaHxg+vS+E67UB52m6LcIcm7miBxxzMVBVjmcevOPVsC3OHCQxw2I93rhwtlPfhJ1 fTuDvJ68AzgVXjURpJHeDVQkSxpzFZ+ReFKOocLWWsqUUJABRianpCs2E8eZPxtDDgIOuXqIVi5x 3jPL44X66WZuhBAYol3V7CKIutZ/0yb4BCLES0a+ovYpxQKEmWuK2ygn8MyJchDFw8qixZQ8jHx+ liEq0mZCbrGtRsXSELpbxvOBjfNSY2EV8QA1wCr9Kh4QUZOunqVAoNEN40uwIAxHbU6d5v5afKPY k7bewJSNmyaaLIpbgR+1PdxBR74O92ZAzJ6EUKuZCUE5mZbD/tN12LRKOprExvrSnhMedvF81Qfv rczP6/hsBfsEQ08k4YdfxR3A7le2GLu9UM1YyBloXz6eDphJbOqbOveRnbutxBa68KGnORSWn4YK be9G7ZkUYp9wwRY9ZBU2M0+8jYzD+IVPNib3Q6bYjnetGI9ld0P9wIEUUjXIDhf5Fdu2BlYwj+6/ n0jxTCCsaKHmV/ZEHgnZnyDjrHYJoucH5a56vcNvLhUQblQ58zIMJ0zc3tIwPEM8MiwfjrCMVWYO uHd1YCGPzyb/ZNMBUrGlhgBRFPfvZuFeNqtQ3xOf4HjMluNBni5DRT3aSn9tuCdyqagXKZ3Boucu XDr6ayzn3uI39OvgRDyah9UB4TUiM8SGtfYiepjrSIGqluv9YmmAagMMqsjtTDAfLBIwW82EDTxZ fU+qFaHcJK5r5iPZUFr0wZ5Apca+LFmdervCFz/4qaYufmFNArnpEnTuf9jNSvHEv+2iJ35cLLmf 3ZWdcIUqxETqGAIWAie5VF6HxWJrHzCTBCpwao2rX48tet4dIpbPBL9g5ccYd4SZWrtlRoXru7A7 5YUkblKgTBJ3MXojovlBe7F8ZDDUvdpxSP8lHWHl8v8UoMXzEWz1aNEuAH5AtL+vraoaWmTBJ16K p3fbGz9n9Tz5ZKWxhCNwNttiDbfLAUV+7ybbAscc3VdCcVg+4fgx3kHrjsmLNTFhfA7KRogAfqL9 DjVJPLm7+QCHNjJBhqjnvvKHtR8sI8aLkoNu7wb0JBry711Mi2wSP+NHHmhMyqVRPxsJR3dOcaVx I+Tqu++iGaCmTtzzUUd2q41YUGg7mq58wfNg7knJUx1Igt1oFMdYc0I8em5DuE+pnqfwx8co+b1L wPQjJiwDoZK+4aCv2yOnB6JxxbA90+RwxQm4t4iPBse/JgFpN0KUjLEcy2w750R329lk7IxMUq1A IrZFaH9i6H0kNmJWAVu5hM3Isj3IWHMVElLd20jYIncCQoUucHaQpTfu3oOEoMG/WaTfmEc4kG/4 c49lFQAhdAtqDbSOYeIbP1MEHCk33fimxCJVYZgTd+9o+/eCxkCXw0nqU22IAmis6p4gjd9jAbn1 5kH3Y4xcwLi+gLKbYaP6RYcO0CVv5RcltFGnAk4A2OqxlOgB6w26a/8lBsI4U75byFIfSOQH9ytd iG7RCjo6i+vnfwQUdX8mHQdhV/kOKIG/XQ80niALoxWAhEQFjnbWan6U+I566RZYzmHK/9U/15vp wZYGRGwZlEDSxYHC7/jvxgC5EU9p/W0Bs9J+Sc8G7nqfNwB6VWuOePCh/ZGhDc6NSMtx0i2ZyfR7 bWyYlMx3DA0Jzkm3y1cG3TLzVrNOXF99gcn1r0fx555Mz/qEEgZHvWDmtaQ1PlwKU5PfxkJEmN/+ tu9npW/XDkQDJJFyf1int4WmDF2lNGvtEsDTgUSPx5W77vx/XuroH05CmsCCKx+xoRyntwmBMQJq nkDewRc+qCnxfgF0GJfeNtTdIQ+Wy6Dw1tncIbh3p+TnbkMMT1IcauckX7g3XIMQzcvwOoiPhbLX qU6xM14C1zxrt5UZyf1S0p9RVLA99x6BRDe+wY5NYZvDqtWmkjB673ANgBeXYEFk17fkiXBz3so+ CHmN+0v34Ey9/vd3N8n7VV/fZOuInKFh4GVIecU8a+SgZcfYEWgnh2HWspynYT9X8jAq6nwJzyQj WRbS/4qFod6WYLcIR43H62MMqUfsY62DUA1eUkBulymha7XnwqSZBpuOg2TKPC2rn5SFGIeZC6gG wg57TQ0gEuvaT5tFSvaipzwMUvVMWH4/LBFC1eODT4NHfvIlgUksJOqZv3ywJHMpPCLG0LTRhvod i0N78/WnjNexbC9e0JJM28mcQIHn3/+7yYFQUUPNYJj3RpZtZ/yOaktqSzWrV8swec27qPd7q9PY 4XHcSBi58O+K3RnuNBDvgFHRIO8e4shr8cDoYLb/uzmJSKJBILNtahOchp7xzXRIoLXksk3QA0pT lxIn/LN60RopIgIW5o7GEycnMisst/7puToIcUI1U4oKqUIxySdMn8rvzbONsLrrS9/IKf7g3xyi +RxJMhRqhhaSW5bPs4cGa/mPWTrMJyFtMhDNDiDH+RHd23Y0Cdo+XJhfecw/fLyOeafinvYjGkbQ yVYaCVh0Epb7/EVugiobw93MRTPWDoLkTxxeCtX3wOGiNCyt4aqwPxp6qIFIuAZmqs0RIGWfya7d cmiG56ItepqAKKYQXqKo24+I0tWfRXSOgZ3p38YaiuNHjoV/zrfKP74F5Taiqk7Qy3rSQ4HwPyFK 2Ac/GlvlX2yfMfKn3qZvb9ZweVzIg5z8muSYDsYqpm6MfZuvee2Pgir/xWLOqaggmw1RfuMoRUpt aT0FqWQlFy3kvNt/THur6ORvegdBobtAH+lCQ7Hn/wOI/BYDPhJpM5+86kSxvxxQvOeDXt+e4q7v U0FFJsnx4+vVYMtql9o1z7QzGYltiGOxivOhAgI0gsTlXm0CvdJpVK9e6QzRq+MA9SIvrHZ4bysp i6+yHGyrFwatQxwf5EFGyYYEc5glsw0INCLvOe60z2NDO3OFI7URcQ32ngpoWWu1bohnbsVGlHsI Dx3NttV+2Y86s2xegP9ZTYIiOErVrJ5JVRkCX1yYYO/1tRuPiR5YaD4zYbOjtAWEGRluqMOODshg M5OOM8W5IiqtJ753HvpoEhP/N8vrGkCzmJ7yAXB6x1lwX1dPdkEqPM6EgRgHn5xrH2/gM+HE0/6O 7bxQt1AETqW4ernUGmX/G7AaClheXCry0TtxJJ86TKBlJPvvEOx6KCDV935VK6qcoiyMbVvkoJrZ Wg3+E2fNW8ru3I0E4c7Yn6qklwnd9AhoLt+7TQdT0VW9gWgWg/+wuqkcA9+WejKcrVx68XVae/jV eQvOnFQxdC5xWdLSiUGabpN8JfOvbb4Lg3x/FS5dpYkGfs1DllrAWxbNkkuS/J3nzN+3weDK1U4h lr6VMfcTnR2XFwLpUi1CUt8Bti2TWmRPzjCpQqLJpAQ+QjclXSpqHfUglCZuhA8dWIZNZd8T/p7R unyt+6nvjvmgMZx++O1tHVz5w4y4py1YEHOK/FI91L59QUwDr9nZKhp7/ukB9imodr0hqkEGUl17 YWTx4ApSYoAyucCGi10FFNSnEPIRpZrg3cWRGKJGj4e3vspUE9Qt3u4bx696NCTpvQlTLL2OZ18K w4GnrFyFQsjapmBkFcR7rzlqSq+vZq3hj60IT1SCRmi8BbB1Unzp70cyxTn5EbPCR+wwq0BBxA/a IO492HjqfLRr6t6tXAqp9mV/1izD3hBGzf+gmDrf/V7UYefuazggaMDXq12uSngbo2+hdKswSmr0 SudR0PWKA7W+Aao4b4Tlrc6i3ZfmJ4ptoXUWvim8yV2KBuIwpuD5GNGfS9qjk4Np5Y67QVc8Ayhh /b8uhwnJGg6VHupaln9L7Qhml/daC06Vv/qWwwGPBGJ9CWplMDisrrIf6eSuVULJvPyHEAHqRQVz +vJ8kTAcFLHH/6TplsrT9noWyNqNqUPvDbjGEZYDH+tEIr71Pex43jix3k54m6KeNxU9hwOa7QbK im9uTguVmt4u0v40173mx1wyhO/uG3KhwjQdxHNHfrh9S/atIMpJcBD2vsg+6pI0ljZsjQqjxkL+ oGMhXb2gFNrgZMscv3Y0yy5h1XqhQZAkDwc/RxP4+tmNLFmmZCjcyDsWcyHYohnlf+a8CfuAg23o bfN5In7clQtQbr4vYy6x0+O7IFxq1k16ySqdS9vIeIfutNvn/p1VIQPYad2oSzQa/Ib63C1WuRNJ G2+Cl9h2pzuX1bC8KhSmaec225i/HAR59qDzS0QDXrKash3q7AggsSzdlhVbh2+/YOEJ5qLufQP9 iyi7eekcFTgY0BR4G/Tr3+WS88OHujm6Eid6d2GwoyAdC98sVKu39zs/9+CC/9m/5rAQqgasX7UK oYIfjKDagoSg+9CWcLVgOZNfhyOT/s0aszI17VFIujAg0g7Ziyb/gcvF61gRVYFaeRSICScEKpJj nnvnPSXulJNNxKU4qg4Ol3PJtt2gL6qB7vzT8Jj17AYyO/f0Xoof9172H3t466yRJSbBSLY+sNik 5T/G2BGMzXhZeR+6X3sJ0WVp2Ybx+VjfFOlF1HjEFcdjxX9hf8hQoTo3fE1562btlkwoVYEnHn7N Ny28Ok2YKOwZdf5GiHD9SoAzQcWiX9Wul/OTZCIjzg/Q7BHaMJRcZofvO015wA6tPjxLWZ5O4Rv4 3cOZ/rNJQ0+e2HomjPaEYKlgCBRDvCEzRnLlz+baG8moiITMTMAs/f7QgVfmh40RZpB/vu+BWfT8 y+hQYzvPFKuv1jpZSFNMCzDMI7omnMm5k1KcuhVWSaMWef38my3h0zyr0IKr8njgekwgJod7csDw oHrDa7KVbA7TqaLFjywLtM5fmOLdR21T9gdSuvXNjcEZCQ+1vGcq0Q1mo7kFoMxIhrtVsB8nhmBc 4kOKloEwrt/vuWsEXoI6xpCaLZXQWJMdOxcvAixSiIodRk2QgCcJjy7lGMxT9eEd1or4d2y49J+8 +WELNmn6lpGJOErbrSSfpFNER4KAYfjSsbLXAQLfDU9++3PRi0Kc29iuMB9e8cwEIhjMOSBP9tdO IsWqSh0LXegmgZuT6HeVqQLgSaZb7/yVt8ofTCwHNotFIgPTTaeO85aT3aTHUG4hJYa4gRc1HWq7 O6UVlwukXrmZFdpBItbBAD4DRv/nbzHTMQJ98mh2RoqoEgbWX7wM+DRi1l2l8HVgRYkIKCmunlz8 hvUEF+M0h8vSe1tUeflzvl8bAUzDEquzGA3P/J6xcMpbgSja02+L6ZNCk12PgXjNcBe9U0ca0Vzu mjI1bbbug38sA1CRKgLlJxld7QkIwYyKT6zAng1eMYDES09KSwH8KPmokzDtMwGcM0zufy+/oEhr lHXK9qOfYQ+joFxIxFSwqqY9p4Tmz0cYRmntsKG3drRhJJ9vkx0E/N/cIeZbILa387PHPwhh9USd rTtg2X9XKtk/My+ediJGyK/mXEptcQ06EIlUe0slhAy3BOX7lVK7EC3BkPVrJQVUPFlx0csINgAT /000oY76hO6PvRmwuQ4SXYnXoVyiLwg0hkwH1p9hXA1TkKvUWbn0J8+HPacNjGG1ry+GLsyAz1AS HfZAkpKjphI0MW1Z+joiRdpwDpG0IUFVn8N0Thb6VgvihVu5pgHDABXywtX1W+J606N1EfIxQ19D ItfaA1ZURk0mZToX6GsYwcNrxvOjWwmvcT74T/1Oit/0KhM2lQp4D5szP1tUP+2lepRpGN6h22zE N4nSTMs45dBm1dxEOihUpqXioT5xalmMY0x/z9DmuflT0DYeKH78chiodjqG2+4tG5T3leK3LXt+ 61T6vpzwRSb/JSVUBKA06RDqgnTIzdWMRms945T5GO+E51ff4EO0nHq/e/sf7xwpLCuritbKmiKf y1Tqb+YQ7cSpqlNjr6eWS85OM+FR91gFCg6MEmw9aLLuEDD2mq0Wx8QvKoLqEuLebBa84j3mR7ES TcpL+WJS6YbO08hiFuxVcr8Mwwyv9nHxxoAsusai5QA0EnrsCVQtiy0KESWznS2R2JirVjju5N8K l+JrSbK47w/CkYf5bGXdY2eep9CXhHe/z5cnYYq9z2uMjOi43azD67rZUVESxYr5KE2iUjIyRYE1 kFoJWwygBe789PxPRzxC87yGCvwnSNh5uJuCFae6GuWREeuCJTllbeR1t+SmeROlM/Kq0LZ8OlID LMIwzbzAKE1SPMAp+EaO1amdQ4L7318XdnBR4ScRFjCsmOWQIMDK8MwFtRSozpTND3APpovRRoLb pNdBxUhny70QNtBI9VyVp6esb3fjg5/ZKW5ldTZh2ivaluyhhqv14ZPrpF35gGU/JRYb1E5H6G3/ 61fMziRUPbPn1S2HA1z1qG5SVnDk9U8jZsHNVQUYdwsDogD6ZDOtkJ7HQhX3Mt0/lbXtrC6D6JBA 3pDelWXTyYc+bK+4KZGgB98mBtN50BdN8s7lvmKaD6XdMmgSERzW/s9bo9hkFHtVy4Gx7ge/d+ec Amyi4W+JZHbZGe4Eu+alEr/FCtRPmV+rMMA4PW4LfOQ9+5d9BVMD4ytpz3ug4F4DIPqUjfFIit0=images/logo.gif000064400000003266152343077040007454 0ustar00GIF89aP! NETSCAPE2.0!,PH*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳϟ@ JѣH*]ʴN UUT^ݺ3V`kẕdͪe6ڷ'ۺK7\ur;V_| 1p[ˆx@eul/䈒Rzafћ#w3낤mxzrꋫoNz2jֶݛbnݻ g,ڲl|#x|zr͞a7wwt &zx-~Cy `M haax\nnx x`y ף"9#)FX]Z;OgcHS"䡕-$\vExj6N%4bܘeWYښ5VYfaRI}'e8]mÅ%N(f 5{Iߧg*"}BY&j^(Z2*9%nګxzWZZv+Dz+N*lb+nIc%-rZe[*;y,"+ڦzﲾ K;gjv3xY \&(qbl ifL/7\ź,m>{t? uFt=7ilRmgv4jMOfj=wL{ݶ2ʶ\ww_=hZߊ Mۉ#8RN)j.ni\R:y6qknUSݹY^gn#vk+7$ϧ7G/WogwQ@;images/drupalcms.png000064400000012037152343077040010521 0ustar00PNG  IHDRsRGB, pHYs  PLTEѴE 6-jKN4 }-Ԗ#dMjJбډ?kئ~춆mjIXp5!AG$^ѬU ,ެdjsYV#Ӻ.bVjF; \ A +9mՊ`PMgiX#/񿚴 AviѴ&}ӄUT ' ]Ń7AW2FIw֭'rֆU6o=^̲cVۆV Ȕ}k F\i=mwXׁS#DjU  FhEVT϶;`,LԜuŽXNsuXbObB=ln/B2 ib ,침.Ũud!7,~G͝34YF|ez`bV9]{8q%]YD0y5$[rFK`L2v#"Y@03 ݞM -jL}TI=yXzh:]u;Mmw ~'H)2~$&Te9m/ʅ"!gϨs & :TGP*ѧ# qfR^Z!%Db>*`)G39 \JwH:z@EjMHL,̚"`*rħXmyIq xxg9y.ۥ` [1Aeߜbj*"#1g(\<^hs :a9Bu}Ȋqv# ķpuL_@>?H]/xs ;AfLo]ߦJQtP)9o&3 A!+38ˢ̡XcJy0fxg5\LyGX/" 0nBx1Ąn.)&v|A a`$,&hld-}y U7;7[0%8e#IP#F2prGX|{q8槬q-bFMUl+`x:a& {@b/j|[j ϔ1Y OEctKC+!~H(f36 u`[X6~d~02yp7'l}xMle.k!cZ)0Q7ល Ɓj E[CuY)pE+{uC澸' BN`L-d< %3=  B>g ]AX `+#F۬׺(>}e_^t'Cæx$yhBMb&1 š1eQkt z gt[˦glF6\2G] HX.eUxFA@pT@=;~CkraKXz!Ư77&L0 ~Bu-9ެV)Ջt޵Yo>GÇwEgԌNzQtV'xRR;LGDJBѬ+m->TZPW>pC.2RB1:^%]?wσ&{E 8K|4 'c7"˒|UZ RDQN-|"`@L&n\>v "k{ %vg\Э/VOOchk&xLE];#A e B3`}69ha|<ħ j'%M@+K[ )N(MOY!zWIҀ:C|(~yN1XAԆ ƚO0ppph^_>&7H~Hreh%>oQʙ5v5,iK? A#\ *TU%4\E ýܯ6I3y`~2wR褄u6\"C$/ޱGxBrhocIQYwAp͐$uGKH%HGǓ(Ÿ6X*mTf& ̻)w$8iY[F,$#̦Cl­rUBHlpvk$GҪCYs?4`ّp'Y}lo{l36UnŜ٭ эdc$AB .DYzT5r7i 8]THě s^-ק˭k˛5hi^b.еBu 0Ut2ԫc( /зQd ;( S~עgPƵ* s`q7t4q@Vnk tu}u^Ǜ<ݽ#kzn˸'{(ީhgo0C7~u:7T=]%Ot)o( ?)ܾ*V"LPq5Ĭg$u{#+)y SqJͰu :l\9g8YV E:;JVUUaf%Ai)k|;Ct^%kP}̝.IjQMPïDt~S, (.:-t&Xp%gkJh ,#hƝǟ)}urLXLlT}~4ԭ9m hFz3,cE:fCD+?nNkڵ`5\W&:~tґbaɸe;9Phј@?&Z @gIENDB`