commit 40616f83463dd7e53d7e1a3f946f7c0c40f2b6ce Author: david-fairbanks42 Date: Thu Feb 11 11:33:02 2021 -0500 Initial commit Like to add configuration to not backup a volume mounted to a particular location. diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..a1e497a --- /dev/null +++ b/.env.example @@ -0,0 +1,16 @@ +# Rename this file to .env and change the values as required +ENABLE=true +# The count is the old snapshots +# A value of 4 will keep 5 snapshots on AWS +# Latest and the last four +MAX_SNAPSHOT_COUNT=4 +# JSON encoded key value pair of tags to assign to the snapshot +# The Name tag is automatically carried over from the instance +TAGS='{"Project":"Backup"}' +AWS_KEY= +AWS_SECRET= +AWS_REGION= +AWS_VERSION=latest + +# Optional overrides (limited effects) +MACHINE_TYPE=ec2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..356d6ac --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.env +vendor + +.idea diff --git a/README.md b/README.md new file mode 100644 index 0000000..87492cc --- /dev/null +++ b/README.md @@ -0,0 +1,92 @@ +# Automatic EC2 Volume Back-Up (Snapshot) + +Stand-alone script designed to run via cron or systemd to back-up EC2 volumes on a regular schedule. + +All volumes associated with the EC2 instance are backed up. + +## Usage +After the required configuration settings are set up in the `.env` file, simply executing `php backup.php` will +perform the backup. The script has additional options such as `--no-prune` to prevent the script from removing old +snapshots. If the configuration setting `ENABLE` is set to `false` the script will no perform any actions. To override +the `ENABLE` setting, `--force` can be used. The script will also respond to `--help` to outline the usage. + +## Setup +Copy the repository (via `git clone` or upload files) to your server. My recommendation is to install in your home +directory (ec2-user or ubuntu). + +1. From this directory, run `composer install` +2. Copy (or rename/move) the `.env.example` file to the same directory +3. Add the required AWS credentials (see below) to the `.env` file +4. Modify the `TAGS` value in `.env` + +You will want to set up this script to run on a regular schedule. For instance, I run this every day. Examples using +Crontab and SystemD are outlined below. + +### Requires IAM permission +It is **STRONGLY** recommended to create an IAM use specifically for programmatic use. The script requires +AWS credentials to be stored in a hidden file. + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "VisualEditor0", + "Effect": "Allow", + "Action": [ + "ec2:DescribeVolumeStatus", + "ec2:DeleteSnapshot", + "ec2:DescribeTags", + "ec2:DescribeSnapshotAttribute", + "ec2:DescribeVolumes", + "ec2:CreateSnapshot", + "ec2:CreateTags", + "ec2:DescribeSnapshots", + "ec2:DescribeVolumeAttribute" + ], + "Resource": "*" + } + ] +} +``` + +### Schedule Execution via Cron +The following example will run the backup script every day at 3am UTC (server time) and send script outputs +to `backup.log` in the home directory. + +Be sure to change the path to the script and your log file. + +`0 3 * * * /usr/bin/env php /home/ubuntu/backup/backup.php >> /home/ubuntu/backup.log 2>&1` + +### Schedule Execution via SystemD +The following example will run the backup script every day at 3am UTC (server time). Script outputs are handled +directly by SystemD and are available through `journalctl`. + +Create the two following files (requires root permission). Be sure to change the path to the script. + +/etc/systemd/system/ec2-backup.service +```unit file (systemd) +[Unit] +Description=Create and rotate EBS snapshots on AWS for EC2 instances + +[Service] +Type=oneshot +ExecStart=/usr/bin/env php /home/ubuntu/backup/backup.php +User=ubuntu +``` + +/etc/systemd/system/ec2-backup.timer +```unit file (systemd) +[Unit] +Description=Run ec2-backup.service every day + +[Timer] +OnCalendar=03:00 +AccuracySec=1h + +[Install] +WantedBy=timers.target +``` + +To activate the schedule, execute the command (noting the sudo use) +`sudo systemctl start ec2-backup.timer` diff --git a/app/Dates.php b/app/Dates.php new file mode 100644 index 0000000..9f85929 --- /dev/null +++ b/app/Dates.php @@ -0,0 +1,50 @@ + + * @package Fairbanks\Kizarian\Utilities + * @version 2.0 + */ +class Dates { + + /** + * @param mixed $date + * @param null|Carbon $default + * + * @return Carbon + */ + public static function makeCarbon($date, Carbon $default=null) + { + if(is_object($date) && $date instanceof Carbon) { + return $date; + } elseif(is_object($date) && $date instanceof \DateTime) { + return Carbon::instance($date); + } elseif(is_object($date)) { + return Carbon::parse($date->date, $date->timezone); + } elseif(is_array($date)) { + return Carbon::parse($date['date'], $date['timezone']); + } elseif(is_numeric($date)) { + return Carbon::createFromTimestamp($date); + } elseif(is_string($date)) { + return Carbon::parse($date); + } + + if($default !== null) + return $default; + + return Carbon::now(); + } +} diff --git a/app/Ec2Backup.php b/app/Ec2Backup.php new file mode 100644 index 0000000..243dc89 --- /dev/null +++ b/app/Ec2Backup.php @@ -0,0 +1,269 @@ + + * @version 2.0 + */ +class Ec2Backup +{ + /** + * @var Ec2Client + */ + private $ec2Client; + + /** + * @var boolean + */ + private $enable = true; + + /** + * Number of completed backups to keep + * This is the number BEFORE a backup is started + * @var int + */ + private $maxBackupCount = 4; + + public function __construct(Ec2Client $ec2Client) { + $this->ec2Client = $ec2Client; + + $this->enable = boolean(config('ENABLE', true)); + + $this->maxBackupCount = (int)config('MAX_SNAPSHOT_COUNT', 4); + if($this->maxBackupCount <= 0) + $this->maxBackupCount = 4; + } + + public function create(array $options=[]) + { + $options = array_merge(['noPrune' => false, 'force' => false], $options); + + if($this->enable == false && $options['force'] != true) { + app_echo('EC2 Backup is disabled in environment'); + return; + } + + /* + * is this an ec2 instance + * get volume ID + * get list of snapshots + * prune old snapshots + * create new snapshot + */ + + try { + $machine = MachineDetails::getDetails(); + } catch(\Exception $e) { + app_echo("Error getting machine details: {$e->getMessage()}"); + return; + } + + if($machine['type'] != 'ec2') { + app_echo('Instance type is wrong to do an EC2 backup', $machine); + return; + } + + $volumes = $this->getVolumes($machine['instanceId']); + $tags = $this->getTags($machine['instanceId']); + + foreach($volumes as $volume) { + if($options['noPrune'] == true) { + $pruneWording = '(pruning disabled)'; + } else { + $pruneCount = $this->pruneBackups($volume['volumeId']); + $pruneWording = "and pruned {$pruneCount} old snapshots"; + } + + $name = (isset($tags['Name'])) ? $tags['Name'] : $machine['instanceId']; + if(count($volumes) > 1) + $name .= " ({$volume['device']})"; + + $r = $this->backup(['volumeId' => $volume['volumeId'], 'name' => $name]); + if($r == true) { + app_echo("Successfully started snapshot for {$volume['volumeId']} {$pruneWording}"); + } else { + app_echo("Error starting snapshot for {$volume['volumeId']} {$pruneWording}"); + } + } + } + + public function getVolumes($instanceId) + { + try { + $result = $this->ec2Client->describeVolumes( + [ + 'DryRun' => false, + 'Filters' => [ + [ + 'Name' => 'attachment.instance-id', + 'Values' => [$instanceId] + ] + ] + ] + ); + } catch(\Exception $e) { + app_echo('Error getting volumes: ' . $e->getMessage()); + return []; + } + + $volumes = []; + foreach($result['Volumes'] as $volume) { + $volumes[$volume['VolumeId']] = [ + 'device' => $volume['Attachments'][0]['Device'], + 'volumeId' => $volume['VolumeId'] + ]; + } + + return $volumes; + } + + public function getTags($instanceId) + { + try { + $result = $this->ec2Client->describeTags( + [ + 'DryRun' => false, + 'Filters' => [ + [ + 'Name' => 'resource-id', + 'Values' => [$instanceId] + ] + ] + ] + ); + } catch(\Exception $e) { + app_echo('Error getting instance tags: ' . $e->getMessage()); + return []; + } + + $tags = []; + foreach($result['Tags'] as $tag) { + $tags[$tag['Key']] = $tag['Value']; + } + + return $tags; + } + + public function getBackups($volumeId) + { + try { + $result = $this->ec2Client->describeSnapshots( + [ + 'DryRun' => false, + 'Filters' => [ + [ + 'Name' => 'volume-id', + 'Values' => [$volumeId], + ] + ] + ] + ); + } catch(\Exception $e) { + app_echo('Error getting current snapshots: ' . $e->getMessage()); + return []; + } + + $snapshots = []; + foreach($result['Snapshots'] as $snapshot) { + if($snapshot['State'] != 'completed') + continue; + + $snapshots[$snapshot['SnapshotId']] = [ + 'started' => Dates::makeCarbon($snapshot['StartTime']), + 'description' => $snapshot['Description'], + 'snapshotId' => $snapshot['SnapshotId'] + ]; + } + + uasort($snapshots, function($a, $b) { + if($a['started'] == $b['started']) { + return 0; + } else { + return $a['started'] > $b['started'] ? 1 : 0; + } + }); + + return $snapshots; + } + + public function pruneBackups($volumeId) + { + $backups = $this->getBackups($volumeId); + + if(count($backups) <= $this->maxBackupCount) + return 0; + + $prune = array_slice($backups, 0, count($backups) - $this->maxBackupCount); + if(empty($prune)) + return 0; + + $count = 0; + foreach($prune as $snapshotId => $snapshot) { + try { + $this->ec2Client->deleteSnapshot( + [ + 'DryRun' => false, + 'SnapshotId' => $snapshotId + ] + ); + } catch(\Exception $e) { + app_echo('Error pruning snapshot: ' . $e->getMessage()); + continue; + } + + $count++; + } + + return $count; + } + + public function backup(array $params) + { + if(!isset($params['volumeId'])) { + app_echo('Volume ID is not set in backup parameters'); + return false; + } + + $name = (isset($params['name'])) ? $params['name'] : $params['volumeId']; + + $tags = [['Key' => 'Name', 'Value' => $name]]; + $snapTags = json_decode(config('TAGS', '[]'), true); + if(is_array($snapTags) && !empty($snapTags)) { + foreach($snapTags as $key => $value) { + $tags[] = ['Key' => $key, 'Value' => $value]; + } + } + + try { + $this->ec2Client->createSnapshot( + [ + 'Description' => sprintf('%s Backup %s', $name, date('Y-m-d')), + 'DryRun' => false, + 'TagSpecifications' => [ + [ + 'ResourceType' => 'snapshot', + 'Tags' => $tags + ] + ], + 'VolumeId' => $params['volumeId'] + ] + ); + } catch(\Exception $e) { + app_echo($e->getMessage()); + return false; + } + + return true; + } +} diff --git a/app/MachineDetails.php b/app/MachineDetails.php new file mode 100644 index 0000000..b373ac5 --- /dev/null +++ b/app/MachineDetails.php @@ -0,0 +1,271 @@ + + * @version 2.0 + */ +class MachineDetails +{ + /** + * @var MachineDetails + */ + protected static $instance; + + /** + * @var array + */ + protected static $details = [ + 'type' => null, + 'id' => null, + 'machineId' => null, + 'instanceId' => null, + 'region' => null, + 'publicIp' => null, + 'privateIp' => null, + ]; + + /** + * PHP config setting for default_socket_timeout to reset to after doing file_get_contents() + * @var int + */ + protected $socketTimeout = null; + + /** + * @return MachineDetails + */ + public static function getInstance() + { + if (!isset(static::$instance)) { + static::$instance = new static; + } + + return static::$instance; + } + + /** + * @return array + */ + public static function getDetails() + { + if(self::$instance === null) + self::$instance = self::getInstance(); + + return self::$details; + } + + /** + * @return string|null + */ + public static function id() + { + if(self::$instance === null) + self::$instance = self::getInstance(); + + return self::$details['id']; + } + + /** + * @return string|null + */ + public static function machineId() + { + if(self::$instance === null) + self::$instance = self::getInstance(); + + return self::$details['machineId']; + } + + /** + * @return string + */ + public static function fullMachineId() + { + if(self::$instance === null) + self::$instance = self::getInstance(); + + $id = [ + config('MACHINE_TYPE'), + self::$details['region'], + self::$details['machineId'] + ]; + + return implode('-', array_unique(array_filter($id))); + } + + /** + * @return string|null + */ + public static function region() + { + if(self::$instance === null) + self::$instance = self::getInstance(); + + return self::$details['region']; + } + + /** + * @return string|null + */ + public static function publicIp() + { + if(self::$instance === null) + self::$instance = self::getInstance(); + + if(self::$details['publicIp'] === null) { + switch(self::$details['type']) { + case 'ec2' : + self::$details['publicIp'] = self::getEc2PublicIp(); + break; + default : + self::$details['publicIp'] = self::getLocalPublicIp(); + } + } + + return self::$details['privateIp']; + } + + /** + * @return string|null + */ + public static function privateIp() + { + if(self::$instance === null) + self::$instance = self::getInstance(); + + return self::$details['privateIp']; + } + + /* Private methods */ + + private static function getLocalPublicIp() + { + return '127.0.0.1'; + } + + private static function getEc2PublicIp() + { + if(self::$instance === null) + self::$instance = self::getInstance(); + + self::$instance->shortenTimeout(); + $ip = @file_get_contents('http://169.254.169.254/latest/meta-data/public-ipv4'); + self::$instance->resetTimeout(); + + return $ip; + } + + private function __construct() { + $type = config('MACHINE_TYPE'); + if($type === null || empty($type)) { + $type = $this->determineMachineType(); + } + + switch($type) { + case 'ec2' : + self::$details['type'] = 'ec2'; + self::$details = array_merge(self::$details, $this->getEc2Data()); + break; + default : + self::$details['type'] = 'local'; + self::$details = array_merge(self::$details, $this->getLocalData()); + } + } + + /** + * Make clone magic method private, so nobody can clone instance. + */ + private function __clone() {} + + private function determineMachineType() + { + $this->shortenTimeout(); + $hostname = @file_get_contents('http://169.254.169.254/latest/meta-data/hostname'); + $this->resetTimeout(); + + if(strpos($hostname, 'ec2') !== false) { + return 'ec2'; + } else { + return 'local'; + } + } + + private function getLocalData() + { + return [ + 'id' => 'dev', + 'machineId' => 'dev', + 'region' => 'local', + 'publicIp' => '127.0.0.1', + 'privateIp' => '127.0.0.1', + ]; + } + + private function getEc2Data() + { + /* + * curl "http://169.254.169.254/latest/dynamic/instance-identity/document" + * { + * "privateIp" : "172.31.59.103", + * "devpayProductCodes" : null, + * "availabilityZone" : "us-east-1b", + * "version" : "2010-08-31", + * "instanceId" : "i-0a1233...", + * "billingProducts" : null, + * "pendingTime" : "2017-05-12T15:21:57Z", + * "instanceType" : "t2.micro", + * "accountId" : "393...", + * "architecture" : "x86_64", + * "kernelId" : null, + * "ramdiskId" : null, + * "imageId" : "ami-9a...", + * "region" : "us-east-1" + * } + */ + + $this->shortenTimeout(); + $json = @file_get_contents('http://169.254.169.254/latest/dynamic/instance-identity/document'); + $this->resetTimeout(); + + $data = json_decode($json, true); + + if(!is_array($data) || empty($data)) { + throw new \Exception('Invalid machine details from http://169.254.169.254. Assumption is this is not an EC2 instance.'); + } + + if(isset($data['instanceId'])) + $data['machineId'] = substr($data['instanceId'], 2); + + $out = []; + foreach(self::$details as $key => $v) { + $out[$key] = (isset($data[$key])) ? $data[$key] : $v; + } + + return $out; + } + + private function shortenTimeout() + { + if($this->socketTimeout === null) { + $this->socketTimeout = ini_get('default_socket_timeout'); + } + + ini_set('default_socket_timeout', 2); + } + + private function resetTimeout() + { + if($this->socketTimeout !== null) { + ini_set('default_socket_timeout', $this->socketTimeout); + } + } +} diff --git a/app/functions.php b/app/functions.php new file mode 100644 index 0000000..d6e493c --- /dev/null +++ b/app/functions.php @@ -0,0 +1,160 @@ + $key) { + $out[$key] = (isset($values[$index])) ? $values[$index] : $default; + } + + return $out; + } +} + +if(!function_exists('array_limit_keys')) { + /** + * Filter input array to only have the keys provided + * + * Only ensures first level of supplied array + * + * @param array $keys + * @param array $input + * + * @return array + */ + function array_limit_keys(array $keys = [], array $input = []) + { + if(empty($keys)) { + return []; + } + + //return array_filter($input, function($item) use ($keys) { + // return in_array($item, $keys); + //}, ARRAY_FILTER_USE_KEY); + + $out = []; + foreach($keys as $key) { + if(array_key_exists($key, $input)) { + $out[$key] = $input[$key]; + } + } + + return $out; + } +} + +if(!function_exists('boolean')) { + /** + * Convert value to boolean + * + * Extends boolval() to look at more words as being TRUE. + * Strings and numbers default to FALSE. + * Notable differences from boolval() docs: + * +----------+-----------+-----------+ + * | val | boolval() | boolean() | + * +----------+-----------+-----------+ + * | 0 | false | false | + * | 42 | true | false | + * | 0 | false | false | + * | 4.2 | true | false | + * | 1 | true | true | + * | NULL | false | false | + * | "" | false | false | + * | "string" | true | false | + * | "0" | false | false | + * | "1" | true | true | + * | "yes" | true | true | + * | "no" | true | false | + * | "y" | true | true | + * | "n" | true | false | + * | "true" | true | true | + * | "false" | true | false | + * | [1,2] | true | true | + * | [] | false | false | + * | stdClass | true | true | + * +----------+-----------+-----------+ + * + * @param boolean|int|string|null $var + * + * @return boolean + */ + function boolean($var) + { + if(is_bool($var)) { + return ($var == true); + } + + if(is_string($var)) { + $var = strtolower($var); + + switch($var) { + case 'true' : + case 'on' : + case 'yes' : + case 'y' : + case '1' : + return true; + default : + return false; + } + } + + if(is_numeric($var)) { + return ($var == 1); + } + + return boolval($var); + } +} + +if(!function_exists('config')) { + /** + * @param string $key + * @param scalar $default + * @return scalar + */ + function config($key, $default = null) + { + if(array_key_exists($key, $_ENV)) { + return $_ENV[$key]; + } else { + return $default; + } + } +} + +if(!function_exists('app_echo')) { + /** + * @param string $message + * @param array $context + */ + function app_echo($message, $context = []) + { + if(is_array($context) && !empty($context)) { + $message .= ' ' . json_encode($context); + } + + echo trim($message) . "\n"; + } +} diff --git a/backup.php b/backup.php new file mode 100644 index 0000000..f85bce9 --- /dev/null +++ b/backup.php @@ -0,0 +1,44 @@ + isset($arguments['f']) || isset($arguments['force']), + 'noPrune' => isset($arguments['p']) || isset($arguments['no-prune']) +]; + +require_once(__DIR__ . '/vendor/autoload.php'); + +$dotenv = Dotenv\Dotenv::createImmutable(__DIR__); +$dotenv->load(); +$dotenv->required('AWS_KEY')->notEmpty(); +$dotenv->required('AWS_SECRET')->notEmpty(); +$dotenv->required('AWS_REGION')->notEmpty(); +$dotenv->ifPresent('MAX_SNAPSHOT_COUNT')->isInteger(); +$dotenv->ifPresent('ENABLE')->isBoolean(); + +$ec2Client = new \Aws\Ec2\Ec2Client([ + 'credentials' => [ + 'key' => config('AWS_KEY'), + 'secret' => config('AWS_SECRET') + ], + 'region' => config('AWS_REGION'), + 'version' => config('AWS_VERSION', 'latest') +]); +$ec2Backup = new \App\Ec2Backup($ec2Client); + +$ec2Backup->create(); diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..fc5aa21 --- /dev/null +++ b/composer.json @@ -0,0 +1,21 @@ +{ + "require": { + "ext-json": "*", + "aws/aws-sdk-php": "^3.173", + "nesbot/carbon": "^2.45", + "vlucas/phpdotenv": "^5.3" + }, + "config": { + "optimize-autoloader": true, + "preferred-install": "dist", + "sort-packages": true + }, + "autoload": { + "psr-4": { + "App\\": "app/" + }, + "files": [ + "app/functions.php" + ] + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..ac00cc7 --- /dev/null +++ b/composer.lock @@ -0,0 +1,1272 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "aa8a1da73c8884def1f89adb90f357c4", + "packages": [ + { + "name": "aws/aws-sdk-php", + "version": "3.173.6", + "source": { + "type": "git", + "url": "https://github.com/aws/aws-sdk-php.git", + "reference": "439a161e46895cd1f7b93b4c0295f95d6a17385e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/439a161e46895cd1f7b93b4c0295f95d6a17385e", + "reference": "439a161e46895cd1f7b93b4c0295f95d6a17385e", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-pcre": "*", + "ext-simplexml": "*", + "guzzlehttp/guzzle": "^5.3.3|^6.2.1|^7.0", + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.4.1", + "mtdowling/jmespath.php": "^2.5", + "php": ">=5.5" + }, + "require-dev": { + "andrewsville/php-token-reflection": "^1.4", + "aws/aws-php-sns-message-validator": "~1.0", + "behat/behat": "~3.0", + "doctrine/cache": "~1.4", + "ext-dom": "*", + "ext-openssl": "*", + "ext-pcntl": "*", + "ext-sockets": "*", + "nette/neon": "^2.3", + "paragonie/random_compat": ">= 2", + "phpunit/phpunit": "^4.8.35|^5.4.3", + "psr/cache": "^1.0", + "psr/simple-cache": "^1.0", + "sebastian/comparator": "^1.2.3" + }, + "suggest": { + "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", + "doctrine/cache": "To use the DoctrineCacheAdapter", + "ext-curl": "To send requests using cURL", + "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages", + "ext-sockets": "To use client-side monitoring" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Aws\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Amazon Web Services", + "homepage": "http://aws.amazon.com" + } + ], + "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project", + "homepage": "http://aws.amazon.com/sdkforphp", + "keywords": [ + "amazon", + "aws", + "cloud", + "dynamodb", + "ec2", + "glacier", + "s3", + "sdk" + ], + "support": { + "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", + "issues": "https://github.com/aws/aws-sdk-php/issues", + "source": "https://github.com/aws/aws-sdk-php/tree/3.173.6" + }, + "time": "2021-02-09T19:14:22+00:00" + }, + { + "name": "graham-campbell/result-type", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/GrahamCampbell/Result-Type.git", + "reference": "7e279d2cd5d7fbb156ce46daada972355cea27bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/7e279d2cd5d7fbb156ce46daada972355cea27bb", + "reference": "7e279d2cd5d7fbb156ce46daada972355cea27bb", + "shasum": "" + }, + "require": { + "php": "^7.0|^8.0", + "phpoption/phpoption": "^1.7.3" + }, + "require-dev": { + "phpunit/phpunit": "^6.5|^7.5|^8.5|^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "GrahamCampbell\\ResultType\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "graham@alt-three.com" + } + ], + "description": "An Implementation Of The Result Type", + "keywords": [ + "Graham Campbell", + "GrahamCampbell", + "Result Type", + "Result-Type", + "result" + ], + "support": { + "issues": "https://github.com/GrahamCampbell/Result-Type/issues", + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.0.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", + "type": "tidelift" + } + ], + "time": "2020-04-13T13:17:36+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.2.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "0aa74dfb41ae110835923ef10a9d803a22d50e79" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/0aa74dfb41ae110835923ef10a9d803a22d50e79", + "reference": "0aa74dfb41ae110835923ef10a9d803a22d50e79", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.4", + "guzzlehttp/psr7": "^1.7", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "php-http/client-integration-tests": "^3.0", + "phpunit/phpunit": "^8.5.5 || ^9.3.5", + "psr/log": "^1.1" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.1-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.2.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://github.com/alexeyshockov", + "type": "github" + }, + { + "url": "https://github.com/gmponos", + "type": "github" + } + ], + "time": "2020-10-10T11:47:56+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "60d379c243457e073cff02bc323a2a86cb355631" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/60d379c243457e073cff02bc323a2a86cb355631", + "reference": "60d379c243457e073cff02bc323a2a86cb355631", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.4 || ^5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/1.4.0" + }, + "time": "2020-09-30T07:37:28+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.7.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "53330f47520498c0ae1f61f7e2c90f55690c06a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/53330f47520498c0ae1f61f7e2c90f55690c06a3", + "reference": "53330f47520498c0ae1f61f7e2c90f55690c06a3", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0", + "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-zlib": "*", + "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/1.7.0" + }, + "time": "2020-09-30T07:37:11+00:00" + }, + { + "name": "mtdowling/jmespath.php", + "version": "2.6.0", + "source": { + "type": "git", + "url": "https://github.com/jmespath/jmespath.php.git", + "reference": "42dae2cbd13154083ca6d70099692fef8ca84bfb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/42dae2cbd13154083ca6d70099692fef8ca84bfb", + "reference": "42dae2cbd13154083ca6d70099692fef8ca84bfb", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0 || ^8.0", + "symfony/polyfill-mbstring": "^1.17" + }, + "require-dev": { + "composer/xdebug-handler": "^1.4", + "phpunit/phpunit": "^4.8.36 || ^7.5.15" + }, + "bin": [ + "bin/jp.php" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6-dev" + } + }, + "autoload": { + "psr-4": { + "JmesPath\\": "src/" + }, + "files": [ + "src/JmesPath.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Declaratively specify how to extract elements from a JSON document", + "keywords": [ + "json", + "jsonpath" + ], + "support": { + "issues": "https://github.com/jmespath/jmespath.php/issues", + "source": "https://github.com/jmespath/jmespath.php/tree/2.6.0" + }, + "time": "2020-07-31T21:01:56+00:00" + }, + { + "name": "nesbot/carbon", + "version": "2.45.0", + "source": { + "type": "git", + "url": "https://github.com/briannesbitt/Carbon.git", + "reference": "e2ba3174ce869da1713c38340dbb36572dfacd5a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/e2ba3174ce869da1713c38340dbb36572dfacd5a", + "reference": "e2ba3174ce869da1713c38340dbb36572dfacd5a", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.1.8 || ^8.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/translation": "^3.4 || ^4.0 || ^5.0" + }, + "require-dev": { + "doctrine/orm": "^2.7", + "friendsofphp/php-cs-fixer": "^2.14 || ^3.0", + "kylekatarnls/multi-tester": "^2.0", + "phpmd/phpmd": "^2.9", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.54", + "phpunit/phpunit": "^7.5.20 || ^8.5.14", + "squizlabs/php_codesniffer": "^3.4" + }, + "bin": [ + "bin/carbon" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev", + "dev-3.x": "3.x-dev" + }, + "laravel": { + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "psr-4": { + "Carbon\\": "src/Carbon/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "http://nesbot.com" + }, + { + "name": "kylekatarnls", + "homepage": "http://github.com/kylekatarnls" + } + ], + "description": "An API extension for DateTime that supports 281 different languages.", + "homepage": "http://carbon.nesbot.com", + "keywords": [ + "date", + "datetime", + "time" + ], + "support": { + "issues": "https://github.com/briannesbitt/Carbon/issues", + "source": "https://github.com/briannesbitt/Carbon" + }, + "funding": [ + { + "url": "https://opencollective.com/Carbon", + "type": "open_collective" + }, + { + "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon", + "type": "tidelift" + } + ], + "time": "2021-02-07T21:35:59+00:00" + }, + { + "name": "phpoption/phpoption", + "version": "1.7.5", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "994ecccd8f3283ecf5ac33254543eb0ac946d525" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/994ecccd8f3283ecf5ac33254543eb0ac946d525", + "reference": "994ecccd8f3283ecf5ac33254543eb0ac946d525", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "phpunit/phpunit": "^4.8.35 || ^5.7.27 || ^6.5.6 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7-dev" + } + }, + "autoload": { + "psr-4": { + "PhpOption\\": "src/PhpOption/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Graham Campbell", + "email": "graham@alt-three.com" + } + ], + "description": "Option Type for PHP", + "keywords": [ + "language", + "option", + "php", + "type" + ], + "support": { + "issues": "https://github.com/schmittjoh/php-option/issues", + "source": "https://github.com/schmittjoh/php-option/tree/1.7.5" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" + } + ], + "time": "2020-07-20T17:29:33+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client/tree/master" + }, + "time": "2020-06-29T06:28:15+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.22.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "c6c942b1ac76c82448322025e084cadc56048b4e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/c6c942b1ac76c82448322025e084cadc56048b4e", + "reference": "c6c942b1ac76c82448322025e084cadc56048b4e", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "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.22.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-07T16:49:33+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.22.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "f377a3dd1fde44d37b9831d68dc8dea3ffd28e13" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/f377a3dd1fde44d37b9831d68dc8dea3ffd28e13", + "reference": "f377a3dd1fde44d37b9831d68dc8dea3ffd28e13", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.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": "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.22.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-07T16:49:33+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.22.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dc3063ba22c2a1fd2f45ed856374d79114998f91", + "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "files": [ + "bootstrap.php" + ], + "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.22.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-07T16:49:33+00:00" + }, + { + "name": "symfony/translation", + "version": "v5.2.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "c021864d4354ee55160ddcfd31dc477a1bc77949" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/c021864d4354ee55160ddcfd31dc477a1bc77949", + "reference": "c021864d4354ee55160ddcfd31dc477a1bc77949", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "^1.15", + "symfony/translation-contracts": "^2.3" + }, + "conflict": { + "symfony/config": "<4.4", + "symfony/dependency-injection": "<5.0", + "symfony/http-kernel": "<5.0", + "symfony/twig-bundle": "<5.0", + "symfony/yaml": "<4.4" + }, + "provide": { + "symfony/translation-implementation": "2.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^4.4|^5.0", + "symfony/console": "^4.4|^5.0", + "symfony/dependency-injection": "^5.0", + "symfony/finder": "^4.4|^5.0", + "symfony/http-kernel": "^5.0", + "symfony/intl": "^4.4|^5.0", + "symfony/service-contracts": "^1.1.2|^2", + "symfony/yaml": "^4.4|^5.0" + }, + "suggest": { + "psr/log-implementation": "To use logging capability in translator", + "symfony/config": "", + "symfony/yaml": "" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "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 tools to internationalize your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/translation/tree/v5.2.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-27T10:15:41+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v2.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "e2eaa60b558f26a4b0354e1bbb25636efaaad105" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/e2eaa60b558f26a4b0354e1bbb25636efaaad105", + "reference": "e2eaa60b558f26a4b0354e1bbb25636efaaad105", + "shasum": "" + }, + "require": { + "php": ">=7.2.5" + }, + "suggest": { + "symfony/translation-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + } + }, + "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 translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v2.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-28T13:05:58+00:00" + }, + { + "name": "vlucas/phpdotenv", + "version": "v5.3.0", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "b3eac5c7ac896e52deab4a99068e3f4ab12d9e56" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/b3eac5c7ac896e52deab4a99068e3f4ab12d9e56", + "reference": "b3eac5c7ac896e52deab4a99068e3f4ab12d9e56", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "graham-campbell/result-type": "^1.0.1", + "php": "^7.1.3 || ^8.0", + "phpoption/phpoption": "^1.7.4", + "symfony/polyfill-ctype": "^1.17", + "symfony/polyfill-mbstring": "^1.17", + "symfony/polyfill-php80": "^1.17" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "ext-filter": "*", + "phpunit/phpunit": "^7.5.20 || ^8.5.14 || ^9.5.1" + }, + "suggest": { + "ext-filter": "Required to use the boolean validator." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.3-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "graham@alt-three.com", + "homepage": "https://gjcampbell.co.uk/" + }, + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "https://vancelucas.com/" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "issues": "https://github.com/vlucas/phpdotenv/issues", + "source": "https://github.com/vlucas/phpdotenv/tree/v5.3.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], + "time": "2021-01-20T15:23:13+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.0.0" +}