├── .gitignore ├── src └── Pop │ ├── EntityDefault │ ├── Email.yml │ ├── FinancialType.yml │ ├── GroupContact.yml │ ├── Participant.yml │ ├── Phone.yml │ ├── SavedSearch.yml │ ├── EntityBatch.yml │ ├── ContributionRecur.yml │ ├── Relationship.yml │ ├── MessageTemplate.yml │ ├── ContributionSoft.yml │ ├── FinancialTrxn.yml │ ├── Note.yml │ ├── EntityTag.yml │ ├── Group.yml │ ├── MembershipType.yml │ ├── RelationshipType.yml │ ├── Activity.yml │ ├── Address.yml │ ├── ContactType.yml │ ├── Event.yml │ ├── Contribution.yml │ ├── Household.yml │ ├── Organization.yml │ ├── Contact.yml │ ├── CustomGroup.yml │ ├── Individual.yml │ ├── default.yml │ ├── Pledge.yml │ └── Grant.yml │ ├── OptionStore.php │ ├── Populator.php │ ├── EntityStore.php │ ├── Connection.php │ └── Pop.php ├── examples ├── basic-250.yml ├── basic-1k.yml ├── basic-10k.yml └── basic-100k.yml ├── tests └── Pop │ └── PopTest.php ├── composer.json ├── bin └── pop ├── README.md └── composer.lock /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | /extern 3 | /vendor 4 | -------------------------------------------------------------------------------- /src/Pop/EntityDefault/Email.yml: -------------------------------------------------------------------------------- 1 | fields: 2 | email: f.safeEmail 3 | -------------------------------------------------------------------------------- /src/Pop/EntityDefault/FinancialType.yml: -------------------------------------------------------------------------------- 1 | fields: 2 | enabled: 1 3 | -------------------------------------------------------------------------------- /src/Pop/EntityDefault/GroupContact.yml: -------------------------------------------------------------------------------- 1 | populators: 2 | - groupId 3 | -------------------------------------------------------------------------------- /src/Pop/EntityDefault/Participant.yml: -------------------------------------------------------------------------------- 1 | populators: 2 | - eventId 3 | -------------------------------------------------------------------------------- /src/Pop/EntityDefault/Phone.yml: -------------------------------------------------------------------------------- 1 | fields: 2 | phone: f.phoneNumber 3 | -------------------------------------------------------------------------------- /src/Pop/EntityDefault/SavedSearch.yml: -------------------------------------------------------------------------------- 1 | fields: 2 | form_values: 1 3 | -------------------------------------------------------------------------------- /src/Pop/EntityDefault/EntityBatch.yml: -------------------------------------------------------------------------------- 1 | fields: 2 | entity_id: r.contact 3 | -------------------------------------------------------------------------------- /src/Pop/EntityDefault/ContributionRecur.yml: -------------------------------------------------------------------------------- 1 | fields: 2 | frequency_interval: 1 3 | -------------------------------------------------------------------------------- /src/Pop/EntityDefault/Relationship.yml: -------------------------------------------------------------------------------- 1 | populators: 2 | - relationshipFields 3 | -------------------------------------------------------------------------------- /src/Pop/EntityDefault/MessageTemplate.yml: -------------------------------------------------------------------------------- 1 | fields: 2 | msg_title: f.sentence 3 | 4 | -------------------------------------------------------------------------------- /src/Pop/EntityDefault/ContributionSoft.yml: -------------------------------------------------------------------------------- 1 | fields: 2 | amount: f.randomFloat,2,1,100 3 | -------------------------------------------------------------------------------- /src/Pop/EntityDefault/FinancialTrxn.yml: -------------------------------------------------------------------------------- 1 | fields: 2 | total_amount: f.randomFloat,2,1,100 3 | -------------------------------------------------------------------------------- /src/Pop/EntityDefault/Note.yml: -------------------------------------------------------------------------------- 1 | fields: 2 | entity_id: r.contact 3 | note: f.paragraph 4 | -------------------------------------------------------------------------------- /src/Pop/EntityDefault/EntityTag.yml: -------------------------------------------------------------------------------- 1 | fields: 2 | entity_id: r.Contact 3 | tag_id: r.Tag 4 | -------------------------------------------------------------------------------- /src/Pop/EntityDefault/Group.yml: -------------------------------------------------------------------------------- 1 | fields: 2 | title: f.sentence,4 3 | group_type: choose 4 | -------------------------------------------------------------------------------- /src/Pop/EntityDefault/MembershipType.yml: -------------------------------------------------------------------------------- 1 | fields: 2 | duration_unit: year 3 | duration_interval: 1 4 | -------------------------------------------------------------------------------- /src/Pop/EntityDefault/RelationshipType.yml: -------------------------------------------------------------------------------- 1 | fields: 2 | name_a_b: F.words,2,1 3 | name_b_a: F.words,2,1 4 | -------------------------------------------------------------------------------- /src/Pop/EntityDefault/Activity.yml: -------------------------------------------------------------------------------- 1 | fields: 2 | source_contact_id: r.Contact 3 | activity_type_id: r.ActivityType 4 | -------------------------------------------------------------------------------- /src/Pop/EntityDefault/Address.yml: -------------------------------------------------------------------------------- 1 | fields: 2 | street_address: f.streetName 3 | city: f.city 4 | postal_code: f.postcode 5 | -------------------------------------------------------------------------------- /src/Pop/EntityDefault/ContactType.yml: -------------------------------------------------------------------------------- 1 | fields: 2 | parent_id: 3 | Individual: 1 4 | Household: 1 5 | Organization: 1 6 | -------------------------------------------------------------------------------- /src/Pop/EntityDefault/Event.yml: -------------------------------------------------------------------------------- 1 | fields: 2 | start_date: f.date,Y-m-d H:i:s 3 | title: f.sentence,2 4 | event_type_id: choose 5 | -------------------------------------------------------------------------------- /src/Pop/EntityDefault/Contribution.yml: -------------------------------------------------------------------------------- 1 | fields: 2 | total_amount: f.randomFloat,2,1,100 3 | receive_date: f.dateTimeBetween,-15 years,now 4 | -------------------------------------------------------------------------------- /src/Pop/EntityDefault/Household.yml: -------------------------------------------------------------------------------- 1 | fields: 2 | household_name: f.lastName 3 | children: 4 | - Address: 0-2 5 | - Email: 0-1 6 | - Phone: 0-2 7 | -------------------------------------------------------------------------------- /src/Pop/EntityDefault/Organization.yml: -------------------------------------------------------------------------------- 1 | fields: 2 | organization_name: f.company 3 | children: 4 | - Address: 0-1 5 | - Email: 0-3 6 | - Phone: 0-2 7 | -------------------------------------------------------------------------------- /src/Pop/EntityDefault/Contact.yml: -------------------------------------------------------------------------------- 1 | fields: 2 | first_name: f.firstName 3 | last_name: f.lastName 4 | household_name: f.lastName 5 | organization_name: f.company 6 | -------------------------------------------------------------------------------- /src/Pop/EntityDefault/CustomGroup.yml: -------------------------------------------------------------------------------- 1 | fields: 2 | extends: 3 | Contact: 1 4 | Individual: 1 5 | Household: 1 6 | Organization: 1 7 | Activity: 1 8 | -------------------------------------------------------------------------------- /src/Pop/EntityDefault/Individual.yml: -------------------------------------------------------------------------------- 1 | fields: 2 | first_name: f.firstName 3 | last_name: f.lastName 4 | children: 5 | - Address: 0-2 6 | - Email: 0-3 7 | - Phone: 0-2 8 | -------------------------------------------------------------------------------- /src/Pop/EntityDefault/default.yml: -------------------------------------------------------------------------------- 1 | fields: 2 | title: F.words,3,1 3 | name: F.words,3,1 4 | label: F.words,3,1 5 | description: f.paragraph,1 6 | source: pop 7 | domain_id: 1 8 | -------------------------------------------------------------------------------- /src/Pop/EntityDefault/Pledge.yml: -------------------------------------------------------------------------------- 1 | fields: 2 | installments: 3 | 3: 4 4 | 5: 2 5 | 6: 2 6 | 10: 4 7 | 12: 5 8 | 24: 1 9 | start_date: f.dateTimeBetween,-5 years,1 year 10 | amount: f.randomFloat,2,1,100 11 | -------------------------------------------------------------------------------- /examples/basic-250.yml: -------------------------------------------------------------------------------- 1 | ## Create 100 individuals and enough groups/events that most 2 | ## individuals will land in 1-3 groups/events. 3 | 4 | - Organization: 25 5 | - Individual: 250 6 | - Group: 5 7 | - GroupContact: 500 8 | - Event: 5 9 | - Participant: 500 10 | -------------------------------------------------------------------------------- /src/Pop/EntityDefault/Grant.yml: -------------------------------------------------------------------------------- 1 | fields: 2 | status_id: choose 3 | amount_total: f.randomFloat,0,500,5000 4 | grant_report_received: 5 | 1: 3 6 | 0: 1 7 | application_received_date: 8 | 'f.dateTimeBetween,-3 years,-1 years': 1 9 | '': 1 10 | -------------------------------------------------------------------------------- /examples/basic-1k.yml: -------------------------------------------------------------------------------- 1 | ## Create 1,000 individuals and enough groups/events that most 2 | ## individuals will land in 1-3 groups/events. 3 | 4 | - Organization: 100 5 | - Individual: 1000 6 | - Group: 10 7 | - GroupContact: 2000 8 | - Event: 10 9 | - Participant: 2000 10 | -------------------------------------------------------------------------------- /examples/basic-10k.yml: -------------------------------------------------------------------------------- 1 | ## Create 10,000 individuals and enough groups/events that most 2 | ## individuals will land in 1-3 groups/events. 3 | 4 | - Organization: 1000 5 | - Individual: 10000 6 | - Group: 50 7 | - GroupContact: 20000 8 | - Event: 50 9 | - Participant: 2000 10 | -------------------------------------------------------------------------------- /examples/basic-100k.yml: -------------------------------------------------------------------------------- 1 | ## Create 100,000 individuals and enough groups/events that most 2 | ## individuals will land in 1-3 groups/events. 3 | 4 | - Organization: 10000 5 | - Individual: 100000 6 | - Group: 500 7 | - GroupContact: 200000 8 | - Event: 500 9 | - Participant: 20000 10 | -------------------------------------------------------------------------------- /tests/Pop/PopTest.php: -------------------------------------------------------------------------------- 1 | store[$entity][$field])){ 14 | $options = Connection::api4($entity, 'getoptions', [ 15 | 'sequential' => 1, 16 | 'field' => $field, 17 | ]); 18 | $this->store[$entity][$field] = $options['values']; 19 | } 20 | return $this->store[$entity][$field][array_rand($this->store[$entity][$field])]['key']; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Pop/Populator.php: -------------------------------------------------------------------------------- 1 | entityStore = $entityStore; 9 | $this->optionStore = $optionStore; 10 | } 11 | 12 | function relationshipFields($entity, &$fields){ 13 | 14 | // if no relationship type has been specified, get one 15 | if(!isset($fields['relationship_type_id'])){ 16 | $relationshipType = $this->entityStore->getRandom('RelationshipType'); 17 | }else{ 18 | $relationshipType = $this->entityStore->getSpecific('RelationshipType', NULL, $fields['relationship_type_id']); 19 | } 20 | $fields['relationship_type_id']=$relationshipType['id']; 21 | $fields['contact_id_a'] = $this->entityStore->getRandom('Contact', array('return' => 'id', 'contact_type' => $relationshipType['contact_type_a']))['id']; 22 | $fields['contact_id_b'] = $this->entityStore->getRandom('Contact', array('return' => 'id', 'contact_type' => $relationshipType['contact_type_b']))['id']; 23 | } 24 | 25 | function eventId($entity, &$fields){ 26 | 27 | // if no relationship type has been specified, get one 28 | if(!isset($fields['event_id'])){ 29 | $fields['event_id'] = $this->entityStore->getRandom('Event', array('return' => 'id', 'is_template' => false))['id']; 30 | } 31 | } 32 | 33 | function groupId($entity, &$fields) { 34 | if(!isset($fields['group_id'])) { 35 | $fields['group_id'] = $this->entityStore->getRandom('Group', array('return' => 'id'))['id']; 36 | } 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "outlandishideas/civipop", 3 | "description": "A library for populating a CiviCRM site with fake data", 4 | "type": "library", 5 | "require": { 6 | "fzaninotto/faker": "@stable", 7 | "twig/twig": "@stable", 8 | "symfony/yaml": "@stable", 9 | "civicrm/composer-downloads-plugin": "^4.0", 10 | "clippy/std": "~0.4.3", 11 | "clippy/container": "~1.2", 12 | "symfony/filesystem": "~6.4" 13 | }, 14 | "license": "MIT", 15 | "authors": [{ 16 | "name": "Michael McAndrew", 17 | "email": "michaelmcandrew@thirdsectordesign.org" 18 | }], 19 | "autoload": { 20 | "psr-4": { 21 | "Civi\\Pop\\": "src/Pop", 22 | "Civi\\Pipe\\": "extern/Civi/Pipe" 23 | } 24 | }, 25 | "config": { 26 | "platform": { 27 | "php": "8.1.0" 28 | }, 29 | "allow-plugins": { 30 | "civicrm/composer-downloads-plugin": true 31 | } 32 | }, 33 | "extra": { 34 | "downloads": { 35 | "BasicPipeClient": { 36 | "version": "5.61.0", 37 | "url": "https://raw.githubusercontent.com/civicrm/civicrm-core/{$version}/Civi/Pipe/BasicPipeClient.php", 38 | "path": "extern/Civi/Pipe/BasicPipeClient.php" 39 | }, 40 | "JsonRpcMethodException": { 41 | "version": "5.61.0", 42 | "url": "https://raw.githubusercontent.com/civicrm/civicrm-core/{$version}/Civi/Pipe/JsonRpcMethodException.php", 43 | "path": "extern/Civi/Pipe/JsonRpcMethodException.php" 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Pop/EntityStore.php: -------------------------------------------------------------------------------- 1 | array('is_template' => false)); 13 | 14 | function getRandom($entity, $filter = []){ 15 | 16 | $key = $this->getKey($entity, $filter); 17 | return $this->store[$key][$this->getRandomId($entity, $filter)]; 18 | } 19 | 20 | function getSpecific($entity, $filter = [], $id){ 21 | $key = $this->getKey($entity, $filter); 22 | if(!isset($this->store[$key])){ 23 | $this->init($entity, $filter); 24 | } 25 | return $this->store[$key][$id]; 26 | } 27 | 28 | function getRandomId($entity, $filter = []){ 29 | $key = $this->getKey($entity, $filter); 30 | if(!isset($this->store[$key])){ 31 | $this->init($entity, $filter); 32 | } 33 | return array_rand($this->store[$key]); 34 | } 35 | 36 | function getKey(&$entity, &$filter){ 37 | if(in_array($entity, array('Individual', 'Household', 'Organization'))){ 38 | $filter['contact_type'] = $entity; 39 | $entity = 'Contact'; 40 | } 41 | $key = $entity; 42 | if(isset($filter)){ 43 | ksort($filter); 44 | $key .= json_encode($filter); 45 | } 46 | return $key; 47 | } 48 | 49 | function init($entity, $filter){ 50 | if(isset($this->entityParams[$entity])){ 51 | $filter = array_merge($filter, $this->entityParams[$entity]); 52 | } 53 | $params = array_merge($filter, ['checkPermissions' => false, 'options' => ['limit' => 100 * 1000]]); 54 | $result = Connection::api4($entity, 'get', $params); 55 | $this->store[$this->getKey($entity, $filter)] = $result['values']; 56 | } 57 | 58 | function add($entity, $id){ 59 | $this->store[$entity] = $id; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Pop/Connection.php: -------------------------------------------------------------------------------- 1 | FALSE, 'bufferSize' => $bufferSize]); 48 | } 49 | 50 | /** 51 | * Send an APIv3 request. 52 | */ 53 | public static function api3(string $entity, string $action, array $params = []): array { 54 | if (--static::$sessionLimit < 0) { 55 | static::connect(...static::$connectOptions); 56 | } 57 | return static::$pipe->call('api3', [$entity, $action, $params]); 58 | } 59 | 60 | /** 61 | * Send an APIv3 request. 62 | */ 63 | public static function api4(string $entity, string $action, array $params = []): array { 64 | if (--static::$sessionLimit < 0) { 65 | static::connect(...static::$connectOptions); 66 | } 67 | return static::$pipe->call('api4', [$entity, $action, $params]); 68 | } 69 | 70 | public static function options(array $options): void { 71 | if (--static::$sessionLimit < 0) { 72 | static::connect(...static::$connectOptions); 73 | } 74 | static::$pipe->call('options', $options); 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /bin/pop: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | register(plugins()); 15 | 16 | $c['app']->main('file', function(string $file, SymfonyStyle $io, OutputInterface $output) { 17 | 18 | $startTime = time(); 19 | 20 | try { 21 | // determine where cv lives first 22 | $cv = getCvExecutable(); 23 | 24 | // check that cv works (using Pipe command: https://github.com/civicrm/cv/blob/master/src/Command/PipeCommand.php) 25 | \Civi\Pop\Connection::connect("php -d memory_limit=-1 $cv pipe vjt"); 26 | } 27 | catch (\Throwable $t) { 28 | $io->error([ 29 | 'Failed to locate CiviCRM. Suggestions:', 30 | ' - Ensure that cv is installed', 31 | ' - Run this command from inside the CiviCRM web root', 32 | " - Set an explicit CiviCRM location:\n export CIVICRM_BOOT=\"Auto://var/www/example.org\"", 33 | ]); 34 | // return 1; 35 | throw $t; 36 | } 37 | 38 | $pop = new Pop($output); 39 | $pop->setInteractive(TRUE); 40 | 41 | $fs = new Filesystem(); 42 | if ($fs->isAbsolutePath($file)){ 43 | $absFile = $file; 44 | } 45 | else { 46 | $searchPath = [$_SERVER['PWD'], dirname(__DIR__) . '/examples']; 47 | foreach ($searchPath as $searchDir) { 48 | $absFile = $searchDir . DIRECTORY_SEPARATOR . $file; 49 | if (file_exists($absFile)) { 50 | break; 51 | } 52 | } 53 | } 54 | 55 | if (file_exists($absFile)) { 56 | $pop->process($absFile); 57 | } 58 | else { 59 | throw new \RuntimeException("Failed to read file: $file"); 60 | } 61 | 62 | $output->write(Yaml::dump($pop->getSummary()), OutputInterface::OUTPUT_RAW); 63 | 64 | $endTime = time(); 65 | $output->write(sprintf("Completed in %dm %ds\n", ($endTime - $startTime) / 60, ($endTime - $startTime) % 60)); 66 | 67 | }); 68 | 69 | function getCvExecutable() 70 | { 71 | $output = null; 72 | $retval = null; 73 | 74 | // 'which' is called 'where' on windows 75 | $which = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' ? 'where' : 'which'; 76 | exec($which . ' cv', $output, $retval); 77 | if ($retval !== 0 || empty($output[0])) { 78 | throw new \RuntimeException('cv command not located'); 79 | } 80 | return $output[0]; 81 | } 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pop 2 | 3 | A library for populating a CiviCRM site with fake data. 4 | 5 | ## Requirements 6 | 7 | CiviCRM with console access and [cv](https://github.com/civicrm/cv) 8 | 9 | ## Install 10 | 11 | ``` 12 | git clone https://github.com/michaelmcandrew/pop ~/src/pop 13 | cd pop 14 | composer install 15 | export PATH=$PWD/bin 16 | ``` 17 | 18 | ## Usage 19 | 20 | ``` 21 | cd /var/www/example.org 22 | pop basic-10k.yml 23 | ``` 24 | 25 | Note the usage of a YAML file (`basic-10k.yml`). This can be a built-in example (`basic-250.yml`, `basic-1k.yml`, `basic-10k.yml`, `basic-100k.yml`) 26 | or a file that you define for yourself. 27 | 28 | ## Syntax 29 | 30 | Pop files are a series of instructions written in yaml. Each instruction creates 31 | one or more entities. Some examples will help explain the syntax. 32 | 33 | You can use Pop to create 500 Individuals: 34 | 35 | ```yaml 36 | - Individual: 500 37 | ``` 38 | 39 | Many instructions can be combined into a single file and will be carried out one after the other: 40 | 41 | ```yaml 42 | - Organization: 500 43 | - Individual: 5000 44 | - Event: 80 45 | - MembershipType: 5 46 | - Membership: 500 47 | ``` 48 | 49 | Pop uses realistic default fields when creating entities and creates realistic 'child entities' when appropriate. 50 | 51 | The defaults for each entity are defined in the [EntityDefault](src/Pop/EntityDefault) directory. The defaults for an Individual is as follows: 52 | 53 | ```yaml 54 | fields: 55 | first_name: f.firstName # a realistic first name 56 | last_name: f.lastName # a realistic first name 57 | children: 58 | - Address: 0-2 # creates up to 2 postal addresses 59 | - Email: 0-3 # creates up to 3 email addresses 60 | - Phone: 0-2 # creates up to 2 phone numbers 61 | ``` 62 | 63 | ### Fields 64 | 65 | Instead of using the defaults, you can specify the fields you want to create as follows: 66 | 67 | ```yaml 68 | - Individual: 500 69 | fields: 70 | job_title: Fundraiser 71 | ``` 72 | 73 | ### Children 74 | 75 | You can also define child entities. The following example creates 500 donors and creates between 10 and 100 donations. 76 | 77 | ```yaml 78 | - Individual: 500 79 | children: 80 | - Contribution: 10-100 81 | ``` 82 | 83 | Note that the count of entities can be specified as a range with lower and upper limits as in the Contribution example above. 84 | 85 | ### Choosing fields 86 | 87 | Sometimes it is useful to supply a list of values for a field and have Pop choose one for you each time an entity is created: 88 | 89 | ```yaml 90 | - Individual: 500 91 | fields: 92 | job_title: 93 | CEO: 1 # ~ 25 CEOs 94 | Membership manager: 3 # ~ 75 membership managers 95 | Event co-ordinator: 6 # ~ 150 event co-ordinators 96 | Fundraiser: 10 # ~ 250 fundraisers 97 | ``` 98 | 99 | Higher value fields are more likely to be picked. 100 | 101 | ### Using Faker 102 | 103 | You can also ask Pop to use the [Faker library](https://github.com/fzaninotto/Faker) to generate field values. For example, you can use the Faker jobTitle method as follows: 104 | 105 | ```yaml 106 | - Individual: 500 107 | fields: 108 | job_title: f.jobTitle 109 | ``` 110 | 111 | The syntax for invoking a faker method is 'f.' followed by the method name, 112 | followed by any parameters, seperated by commas. Note that a capital F will 113 | cause the first letter of the field to be capitalised. Below are some more examples of faker methods: 114 | 115 | ```yaml 116 | first_name: f.firstName # a random first name 117 | total_amount: f.randomFloat,2,1000,2000 # an amount between 1000 and 2000 118 | receive_date: f.dateTimeBetween,-5 years,now # a date in the last 5 years 119 | event_title: F.words,3,1 # three words 120 | description: f.paragraph,1 # a paragraph 121 | ``` 122 | 123 | See https://github.com/fzaninotto/Faker for a full list of methods. 124 | 125 | ### Random entities 126 | 127 | Sometimes it is useful to pick a random entity as the field value of another entity. This happens automatically for required fields (for example a Event and a Contact are randomly selected each time a Participant is created). Sometimes it is useful to request a random Entity tag, which you can do as follows: 128 | 129 | ```yaml 130 | - EntityTag: 1 131 | fields: 132 | entity_table: civicrm_activity 133 | tag_id: r.Activity 134 | ``` 135 | 136 | ### Random options 137 | 138 | Required options are populated automatically. Sometimes, you may want to request that pop chooses a random option from those that are available. To do this, specify choose (only valid for fields that can be passed to [Entity].getoptions API). 139 | 140 | ```yaml 141 | - Event: 1 142 | fields: 143 | participant_listing_id: choose 144 | ``` 145 | 146 | # Supported entities 147 | 148 | The most common CiviCRM entities are tested to ensure they work with Pop. Other entities may or may not work. The entityProvider function in the [Pop test suite](https://github.com/michaelmcandrew/cv/blob/pop/tests/Command/PopCommandTest.php)) tests has the most up to date list of tested entities. 149 | 150 | # Hopes and dreams 151 | 152 | * Support more entities. 153 | * Use twig as templating language for fields (in the same way that ansible uses Jinja) 154 | * Add command to create a single set of entities (easier than reading a pop file), e.g. `civipop -e Individual -c 300` 155 | * read from stdin 156 | * allow fields to reference each other. So that, for example, email can be set as {$first_name}.{$last_name}@{current_employer}.org 157 | * Asign all created entities to a batch each time pop is run so that they can be easily found and deleted in future. 158 | -------------------------------------------------------------------------------- /src/Pop/Pop.php: -------------------------------------------------------------------------------- 1 | output = $output; 30 | 31 | // Initialise faker 32 | $this->faker = Faker\Factory::create(); 33 | 34 | // Initialise entity store 35 | $this->entityStore = new EntityStore(); 36 | 37 | // Initialise option store 38 | $this->optionStore = new OptionStore(); 39 | 40 | // Initialise entity 41 | // 42 | $this->populator = new Populator($this->entityStore, $this->optionStore); 43 | 44 | // Get available entities (pretending that Individuals, Organisations and 45 | // Households are also entities) 46 | 47 | $availableEntities = Connection::api4('Entity', 'get', ['checkPermissions' => false, 'select' => ['name']]); 48 | $availableEntities = array_column($availableEntities, 'name'); 49 | $this->availableEntities = array_merge( 50 | $availableEntities, 51 | array('Individual', 'Household', 'Organization') 52 | ); 53 | 54 | // Define where to find Pop yml files 55 | $this->entityDefaultDir = __DIR__.DIRECTORY_SEPARATOR.'EntityDefault'.DIRECTORY_SEPARATOR; 56 | 57 | $this->defaultDefinition = Yaml::parseFile("{$this->entityDefaultDir}default.yml"); 58 | 59 | } 60 | 61 | function process($file){ 62 | 63 | $this->log("Adding entities...", 'comment'); 64 | 65 | // load instructions from files 66 | $this->load($file); 67 | 68 | // process each instruction 69 | foreach ($this->instructions as $instruction){ 70 | 71 | // create a definition from each instruction 72 | $definition = $this->translate($instruction); 73 | 74 | // backfill definition with entity defaults 75 | $definition = $this->backfill($definition); 76 | 77 | // create entities based on the definition 78 | $this->createEntities($definition); 79 | } 80 | 81 | // summarise the process 82 | $this->summarize(); 83 | } 84 | 85 | function log($message, $level = null){ 86 | if($this->isInteractive()){ 87 | if(isset($level)){ 88 | $this->output->writeln("<$level>$message"); 89 | }else{ 90 | $this->output->writeln("$message"); 91 | } 92 | } 93 | } 94 | 95 | function load($file){ 96 | 97 | // load the yaml file 98 | $this->instructions = Yaml::parseFile($file); 99 | if(!$this->instructions){ 100 | $this->log("Error: could not open yaml file: ($file)", 'error'); 101 | exit(1); 102 | } 103 | } 104 | 105 | /** 106 | * Parses an instructVion, returning a definition when valid and 107 | * exiting with error messages when not valid. 108 | * @param $instruction 109 | * @return $definition 110 | */ 111 | function translate($instruction){ 112 | 113 | // clone the $instruction for debugging purposes 114 | $original = $instruction; 115 | 116 | // move valid parts of the instruction to the definition 117 | $parts = array('fields', 'children', 'populators'); 118 | 119 | foreach($parts as $part){ 120 | if(isset($instruction[$part])){ 121 | $definition[$part]=$instruction[$part]; 122 | unset($instruction[$part]); 123 | }else{ 124 | $definition[$part]=array(); 125 | } 126 | } 127 | 128 | // at this point, the instruction should be a one 129 | // element array of form array(Entity => count). 130 | // Commplain if this is not the case. 131 | if(count($instruction) != 1){ 132 | $this->log("Error: badly formatted instruction", 'error'); 133 | $this->log(Yaml::dump($original), 'error'); 134 | exit(1); 135 | } 136 | 137 | // define the entity and check that it is available 138 | $definition['entity'] = key($instruction); 139 | if(!in_array($definition['entity'], $this->availableEntities)){ 140 | $this->log("Error: could not find entity: {$definition['entity']} (yaml below)", 'error'); 141 | $this->log(Yaml::dump($original)); 142 | exit(1); 143 | } 144 | 145 | // check that the count is valid, i.e. an integer or a range specified by 146 | // two integers seperated by a dash or NULL (which we interpret as 1) 147 | $definition['count'] = current($instruction); 148 | if($definition['count']===NULL){ 149 | $definition['count'] = 1; 150 | } 151 | if(!preg_match('/^\d+(\-\d+)?$/', $definition['count'])){ 152 | $this->log("Error: invalid value for count: {$definition['count']}", 'error'); 153 | $this->log(yaml_emit($original), 'error'); 154 | exit(1); 155 | } 156 | return $definition; 157 | } 158 | 159 | function backfill($definition) { 160 | 161 | try { 162 | // get defaults for this entity, if they exist 163 | $entityDefault = Yaml::parseFile("{$this->entityDefaultDir}{$definition['entity']}.yml"); 164 | } catch (\Exception $err) { 165 | $entityDefault = []; 166 | } 167 | 168 | // backfill with default fields for this entity 169 | if(isset($entityDefault['fields'])){ 170 | $definition['fields'] = array_replace_recursive($entityDefault['fields'], $definition['fields']); 171 | } 172 | 173 | // backfill with global default fields 174 | if(isset($this->defaultDefinition['fields'])){ 175 | $definition['fields'] = array_replace_recursive($this->defaultDefinition['fields'], $definition['fields']); 176 | } 177 | 178 | // only allow fields available for this api 179 | foreach($this->getAvailableFields($definition['entity']) as $field){ 180 | $availableFields[$field['name']] = null; 181 | } 182 | $definition['fields'] = array_intersect_key($definition['fields'], $availableFields); 183 | 184 | // add any children defined in the default entity 185 | if(isset($entityDefault['children'])){ 186 | $definition['children'] = array_merge($definition['children'], $entityDefault['children']); 187 | } 188 | 189 | // add any populators defined in the default entity 190 | if(isset($entityDefault['populators'])){ 191 | $definition['populators'] = array_merge($definition['populators'], $entityDefault['populators']); 192 | } 193 | 194 | return $definition; 195 | } 196 | 197 | function getAvailableFields($entity){ 198 | if(!isset($this->availableFields[$entity])){ 199 | $fields = Connection::api4($entity, 'getFields', ['checkPermissions' => false, 'action'=> 'create']); 200 | $this->availableFields[$entity] = $fields; 201 | } 202 | return $this->availableFields[$entity]; 203 | } 204 | 205 | function getRequiredFields($entity){ 206 | if(!isset($this->requiredFields[$entity])){ 207 | $this->requiredFields[$entity] = []; 208 | foreach($this->getAvailableFields($entity) as $availableField){ 209 | if(!empty($availableField['required'])) { 210 | $this->requiredFields[$entity][$availableField['name']] = $availableField; 211 | } 212 | } 213 | } 214 | return $this->requiredFields[$entity]; 215 | } 216 | 217 | // create a set of entities from a definition 218 | function createEntities($definition, $parent = null){ 219 | 220 | // if count is a range, decide how many entities to create 221 | if(strpos($definition['count'], '-')){ 222 | $definition['count'] = explode('-', $definition['count']); 223 | $definition['count'] = rand(min($definition['count']) , max($definition['count'])); 224 | } 225 | 226 | // if this is an individual, household or organisation, convert it to a 227 | // contact with an appropriatly defined contact_type 228 | if(in_array($definition['entity'], array('Individual', 'Household', 'Organization'))){ 229 | $definition['fields']['contact_type'] = $definition['entity']; 230 | $definition['entity'] = 'Contact'; 231 | } 232 | 233 | // if this is a child of another entity, populate the parent id 234 | if($parent){ 235 | $definition['fields'][strtolower("{$parent['entity']}_id")]=$parent['id']; 236 | } 237 | 238 | 239 | // create each entity 240 | $count = 0; 241 | while($count < $definition['count']){ 242 | 243 | $createdEntity = $this->populate($definition['entity'], $definition['fields'], $definition['populators']); 244 | // create children if necessary 245 | if(isset($definition['children'])){; 246 | foreach($definition['children'] as $childInstruction){ 247 | $childDefinition = $this->translate($childInstruction); 248 | $childDefinition = $this->backfill($childDefinition); 249 | $this->createEntities($childDefinition, $createdEntity); 250 | } 251 | } 252 | $count++; 253 | } 254 | } 255 | 256 | function populate($entity, $fields, $populators){ 257 | 258 | if(count($populators)){ 259 | foreach($populators as $populator){ 260 | if(method_exists($this->populator, $populator)){ 261 | $this->populator->$populator($entity, $fields); 262 | }else{ 263 | $this->log("Could not find method '{$populator}'", 'error'); 264 | exit; 265 | }; 266 | } 267 | } 268 | // go through fields, making substitutions where necessary 269 | foreach($fields as $name => &$value){ 270 | 271 | // if value is an array, select one at (weighted) random 272 | if(is_array($value)){ 273 | $value = $this->weightedRandomSelect($value); 274 | } 275 | 276 | // if we are using a modifier, run the appropriate function 277 | $this->modify($name, $value, $entity); 278 | } 279 | 280 | // add any required fields using sensible defaults 281 | foreach($this->getRequiredFields($entity) as $requiredFieldName => $requiredFieldDef){ 282 | if(!isset($fields[$requiredFieldName])){ 283 | if(isset($requiredFieldDef['fk_entity'])){ 284 | $fields[$requiredFieldName] = $this->entityStore->getRandomId($requiredFieldDef['fk_entity']); 285 | }elseif(isset($requiredFieldDef['pseudoconstant'])){ 286 | $fields[$requiredFieldName] = $this->optionStore->getRandomId($entity, $requiredFieldDef['name']); 287 | } 288 | } 289 | } 290 | try{ 291 | // echo 'creating ' . $entity . ': ' . json_encode($fields); 292 | $result = Connection::api4($entity, 'create', ['checkPermissions' => false, 'values' => $fields]); 293 | }catch(\Exception $e){ 294 | $this->recordFailure($entity, $fields, $e->getMessage()); 295 | return; 296 | } 297 | 298 | if(!$result['is_error']){ 299 | $this->recordSuccess($entity, $result['id']); 300 | //add to the random entity register so they can be selected in future 301 | $this->entityStore->add($entity, $result['id']); 302 | return array('entity' => $entity, 'id' => $result['id']); 303 | }else{ 304 | $this->recordFailure($entity, $fields, $result['error_message']); 305 | } 306 | } 307 | 308 | function modify($field, &$value, $entity){ 309 | 310 | // TODO refactor this function so that it checks for 311 | 312 | // check for keywords 313 | 314 | // check for modifier prefixes 315 | 316 | if(strpos($value,"r.")===0){ 317 | $value = $this->entityStore->getRandomId(substr($value,2)); 318 | }elseif($value == "choose"){ 319 | $value = $this->optionStore->getRandomId($entity, $field); 320 | }elseif(stripos($value,"f.")===0){ 321 | $value = $this->getFake($value); 322 | } 323 | } 324 | 325 | function weightedRandomSelect($array){ 326 | 327 | $total = 0; 328 | foreach($array as $choice => $value){ 329 | if($value==NULL){ 330 | $value=1; 331 | } 332 | $choices[$choice] = $total += $value; 333 | } 334 | $selection = rand()/getrandmax()*$total; 335 | foreach($choices as $choice => $value){ 336 | if($selection < $value){ 337 | return $choice; 338 | } 339 | } 340 | } 341 | 342 | function getFake($field){ 343 | $function = explode(',', substr($field,2)); 344 | $output = call_user_func_array(array($this->faker, array_shift($function)), $function); 345 | if ($output instanceof \DateTime) { 346 | $output = $output->format('Y-m-d H:i:s'); 347 | } 348 | 349 | if($field[0]=='F'){ 350 | $output=ucfirst($output); 351 | } 352 | return $output; 353 | } 354 | 355 | function recordSuccess($entity, $id){ 356 | $x = 0; 357 | while ($x < count($this->summary)){ 358 | if($this->isInteractive()){ 359 | echo "\033[1A"; 360 | } 361 | $x++; 362 | } 363 | if(isset($this->summary[$entity])){ 364 | $this->summary[$entity]['count']++; 365 | $this->summary[$entity]['last_id']=$id; 366 | }else{ 367 | $this->summary[$entity]['count']=1; 368 | $this->summary[$entity]['first_id']=$id; 369 | $this->summary[$entity]['last_id']=$id; 370 | } 371 | ksort($this->summary); 372 | foreach($this->summary as $entity => $stats){ 373 | $this->log("\033[K{$entity}: {$stats['count']}"); 374 | } 375 | } 376 | 377 | function recordFailure($entity, $fields, $message){ 378 | $this->summary[$entity]['errors'][]=array('fields' => $fields, 'message' => $message); 379 | $this->log("Could not create '{$entity}' [{$message}] (fields below)", 'error'); 380 | $this->log(print_r($fields, true)); 381 | } 382 | 383 | function summarize(){ 384 | $x = 0; 385 | while ($x < count($this->summary)){ 386 | if($this->isInteractive()){ 387 | echo "\033[1A"; 388 | } 389 | $x++; 390 | } 391 | // print_r($this->summary); 392 | foreach($this->summary as $entity => $stats){ 393 | if(!empty($stats['first_id']) && $stats['first_id']==$stats['last_id']){ 394 | $this->log("\033[K{$entity}: {$stats['count']} ({$stats['first_id']})"); 395 | }elseif($stats['first_id'] < $stats['last_id']){ 396 | $this->log("\033[K{$entity}: {$stats['count']} ({$stats['first_id']} to {$stats['last_id']})"); 397 | }else{ 398 | $this->log("\033[K{$entity}: {$stats['count']} (unknown ids)"); 399 | } 400 | } 401 | } 402 | 403 | function getSummary(){ 404 | return $this->summary; 405 | } 406 | 407 | function setInteractive($interactive){ 408 | $this->interactive = (bool) $interactive; 409 | } 410 | 411 | function isInteractive(){ 412 | return $this->interactive; 413 | } 414 | } 415 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "e3b5dfef88e360655d9c4c481433b778", 8 | "packages": [ 9 | { 10 | "name": "civicrm/composer-downloads-plugin", 11 | "version": "v4.0.0", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/civicrm/composer-downloads-plugin.git", 15 | "reference": "7af08ca4a78607ac2bad3215c05a10390da9c673" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/civicrm/composer-downloads-plugin/zipball/7af08ca4a78607ac2bad3215c05a10390da9c673", 20 | "reference": "7af08ca4a78607ac2bad3215c05a10390da9c673", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "civicrm/gitignore": "~1.2.0", 25 | "composer-plugin-api": "^2.0", 26 | "php": ">=7.2" 27 | }, 28 | "require-dev": { 29 | "composer/composer": "~2.0", 30 | "friendsofphp/php-cs-fixer": "^2.3", 31 | "phpunit/phpunit": "^8.5 || ^9.5", 32 | "totten/process-helper": "^1.0.1" 33 | }, 34 | "type": "composer-plugin", 35 | "extra": { 36 | "class": "LastCall\\DownloadsPlugin\\Plugin" 37 | }, 38 | "autoload": { 39 | "psr-4": { 40 | "LastCall\\DownloadsPlugin\\": "src/" 41 | } 42 | }, 43 | "notification-url": "https://packagist.org/downloads/", 44 | "license": [ 45 | "MIT" 46 | ], 47 | "authors": [ 48 | { 49 | "name": "Rob Bayliss", 50 | "email": "rob@lastcallmedia.com" 51 | }, 52 | { 53 | "name": "Tim Otten", 54 | "email": "totten@civicrm.org" 55 | } 56 | ], 57 | "description": "Composer plugin for downloading additional files within any composer package.", 58 | "support": { 59 | "source": "https://github.com/civicrm/composer-downloads-plugin/tree/v4.0.0" 60 | }, 61 | "time": "2024-04-09T20:34:36+00:00" 62 | }, 63 | { 64 | "name": "civicrm/gitignore", 65 | "version": "1.2.0", 66 | "source": { 67 | "type": "git", 68 | "url": "https://github.com/civicrm/PHPGitIgnore.git", 69 | "reference": "7491782ee89c4e14bbcc9b30bebb90389054cb2e" 70 | }, 71 | "dist": { 72 | "type": "zip", 73 | "url": "https://api.github.com/repos/civicrm/PHPGitIgnore/zipball/7491782ee89c4e14bbcc9b30bebb90389054cb2e", 74 | "reference": "7491782ee89c4e14bbcc9b30bebb90389054cb2e", 75 | "shasum": "" 76 | }, 77 | "require": { 78 | "php": ">=5.6" 79 | }, 80 | "replace": { 81 | "togos/gitignore": "1.*" 82 | }, 83 | "require-dev": { 84 | "togos/simpler-test": "1.1.1" 85 | }, 86 | "type": "library", 87 | "autoload": { 88 | "psr-0": { 89 | "TOGoS_GitIgnore_": "src/main/php/" 90 | } 91 | }, 92 | "notification-url": "https://packagist.org/downloads/", 93 | "license": [ 94 | "MIT" 95 | ], 96 | "description": "Parser for .gitignore (and sparse-checkout, and anything else using the same format) files", 97 | "support": { 98 | "source": "https://github.com/civicrm/PHPGitIgnore/tree/1.2.0" 99 | }, 100 | "time": "2024-04-09T07:07:33+00:00" 101 | }, 102 | { 103 | "name": "clippy/container", 104 | "version": "v1.4.2", 105 | "source": { 106 | "type": "git", 107 | "url": "https://github.com/clippy-php/container.git", 108 | "reference": "a5d2cc17f7192465997ea6651a39fac508c77e80" 109 | }, 110 | "dist": { 111 | "type": "zip", 112 | "url": "https://api.github.com/repos/clippy-php/container/zipball/a5d2cc17f7192465997ea6651a39fac508c77e80", 113 | "reference": "a5d2cc17f7192465997ea6651a39fac508c77e80", 114 | "shasum": "" 115 | }, 116 | "require": { 117 | "php": ">=7.2", 118 | "php-di/invoker": "~2.0", 119 | "pimple/pimple": "~3.0", 120 | "psr/container": "~1.1||~2.0" 121 | }, 122 | "type": "library", 123 | "autoload": { 124 | "psr-4": { 125 | "Clippy\\": "src/Clippy/", 126 | "Pimple\\": "src/Pimple/" 127 | } 128 | }, 129 | "notification-url": "https://packagist.org/downloads/", 130 | "license": [ 131 | "MIT" 132 | ], 133 | "authors": [ 134 | { 135 | "name": "Tim Otten", 136 | "email": "totten@civicrm.org" 137 | } 138 | ], 139 | "description": "Dependency-injection container inspired by Pimple and PHP-DI Invoker", 140 | "support": { 141 | "issues": "https://github.com/clippy-php/container/issues", 142 | "source": "https://github.com/clippy-php/container/tree/v1.4.2" 143 | }, 144 | "time": "2022-12-08T04:23:05+00:00" 145 | }, 146 | { 147 | "name": "clippy/std", 148 | "version": "v0.4.7", 149 | "source": { 150 | "type": "git", 151 | "url": "https://github.com/clippy-php/std.git", 152 | "reference": "ba39a44a1cf44ba12a71e9535f51c6a0055abe47" 153 | }, 154 | "dist": { 155 | "type": "zip", 156 | "url": "https://api.github.com/repos/clippy-php/std/zipball/ba39a44a1cf44ba12a71e9535f51c6a0055abe47", 157 | "reference": "ba39a44a1cf44ba12a71e9535f51c6a0055abe47", 158 | "shasum": "" 159 | }, 160 | "require": { 161 | "clippy/container": "~1.3.1||~1.4.0", 162 | "guzzlehttp/guzzle": "~6.0", 163 | "lesser-evil/shell-verbosity-is-evil": "^1.0", 164 | "mnapoli/silly": "~1.7", 165 | "php": ">=7.2", 166 | "symfony/process": "~2.8|~3.0|~4.0" 167 | }, 168 | "type": "library", 169 | "autoload": { 170 | "files": [ 171 | "funcs.php", 172 | "plugins.php" 173 | ], 174 | "psr-4": { 175 | "Clippy\\": "src/" 176 | } 177 | }, 178 | "notification-url": "https://packagist.org/downloads/", 179 | "license": [ 180 | "MIT" 181 | ], 182 | "authors": [ 183 | { 184 | "name": "Tim Otten", 185 | "email": "totten@civicrm.org" 186 | } 187 | ], 188 | "description": "TODO", 189 | "support": { 190 | "issues": "https://github.com/clippy-php/std/issues", 191 | "source": "https://github.com/clippy-php/std/tree/v0.4.7" 192 | }, 193 | "time": "2024-09-18T23:54:53+00:00" 194 | }, 195 | { 196 | "name": "fzaninotto/faker", 197 | "version": "v1.5.0", 198 | "source": { 199 | "type": "git", 200 | "url": "https://github.com/fzaninotto/Faker.git", 201 | "reference": "d0190b156bcca848d401fb80f31f504f37141c8d" 202 | }, 203 | "dist": { 204 | "type": "zip", 205 | "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/d0190b156bcca848d401fb80f31f504f37141c8d", 206 | "reference": "d0190b156bcca848d401fb80f31f504f37141c8d", 207 | "shasum": "" 208 | }, 209 | "require": { 210 | "php": ">=5.3.3" 211 | }, 212 | "require-dev": { 213 | "phpunit/phpunit": "~4.0", 214 | "squizlabs/php_codesniffer": "~1.5" 215 | }, 216 | "suggest": { 217 | "ext-intl": "*" 218 | }, 219 | "type": "library", 220 | "extra": { 221 | "branch-alias": { 222 | "dev-master": "1.5.x-dev" 223 | } 224 | }, 225 | "autoload": { 226 | "psr-4": { 227 | "Faker\\": "src/Faker/" 228 | } 229 | }, 230 | "notification-url": "https://packagist.org/downloads/", 231 | "license": [ 232 | "MIT" 233 | ], 234 | "authors": [ 235 | { 236 | "name": "François Zaninotto" 237 | } 238 | ], 239 | "description": "Faker is a PHP library that generates fake data for you.", 240 | "keywords": [ 241 | "data", 242 | "faker", 243 | "fixtures" 244 | ], 245 | "support": { 246 | "issues": "https://github.com/fzaninotto/Faker/issues", 247 | "source": "https://github.com/fzaninotto/Faker/tree/master" 248 | }, 249 | "abandoned": true, 250 | "time": "2015-05-29T06:29:14+00:00" 251 | }, 252 | { 253 | "name": "guzzlehttp/guzzle", 254 | "version": "6.5.8", 255 | "source": { 256 | "type": "git", 257 | "url": "https://github.com/guzzle/guzzle.git", 258 | "reference": "a52f0440530b54fa079ce76e8c5d196a42cad981" 259 | }, 260 | "dist": { 261 | "type": "zip", 262 | "url": "https://api.github.com/repos/guzzle/guzzle/zipball/a52f0440530b54fa079ce76e8c5d196a42cad981", 263 | "reference": "a52f0440530b54fa079ce76e8c5d196a42cad981", 264 | "shasum": "" 265 | }, 266 | "require": { 267 | "ext-json": "*", 268 | "guzzlehttp/promises": "^1.0", 269 | "guzzlehttp/psr7": "^1.9", 270 | "php": ">=5.5", 271 | "symfony/polyfill-intl-idn": "^1.17" 272 | }, 273 | "require-dev": { 274 | "ext-curl": "*", 275 | "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", 276 | "psr/log": "^1.1" 277 | }, 278 | "suggest": { 279 | "psr/log": "Required for using the Log middleware" 280 | }, 281 | "type": "library", 282 | "extra": { 283 | "branch-alias": { 284 | "dev-master": "6.5-dev" 285 | } 286 | }, 287 | "autoload": { 288 | "files": [ 289 | "src/functions_include.php" 290 | ], 291 | "psr-4": { 292 | "GuzzleHttp\\": "src/" 293 | } 294 | }, 295 | "notification-url": "https://packagist.org/downloads/", 296 | "license": [ 297 | "MIT" 298 | ], 299 | "authors": [ 300 | { 301 | "name": "Graham Campbell", 302 | "email": "hello@gjcampbell.co.uk", 303 | "homepage": "https://github.com/GrahamCampbell" 304 | }, 305 | { 306 | "name": "Michael Dowling", 307 | "email": "mtdowling@gmail.com", 308 | "homepage": "https://github.com/mtdowling" 309 | }, 310 | { 311 | "name": "Jeremy Lindblom", 312 | "email": "jeremeamia@gmail.com", 313 | "homepage": "https://github.com/jeremeamia" 314 | }, 315 | { 316 | "name": "George Mponos", 317 | "email": "gmponos@gmail.com", 318 | "homepage": "https://github.com/gmponos" 319 | }, 320 | { 321 | "name": "Tobias Nyholm", 322 | "email": "tobias.nyholm@gmail.com", 323 | "homepage": "https://github.com/Nyholm" 324 | }, 325 | { 326 | "name": "Márk Sági-Kazár", 327 | "email": "mark.sagikazar@gmail.com", 328 | "homepage": "https://github.com/sagikazarmark" 329 | }, 330 | { 331 | "name": "Tobias Schultze", 332 | "email": "webmaster@tubo-world.de", 333 | "homepage": "https://github.com/Tobion" 334 | } 335 | ], 336 | "description": "Guzzle is a PHP HTTP client library", 337 | "homepage": "http://guzzlephp.org/", 338 | "keywords": [ 339 | "client", 340 | "curl", 341 | "framework", 342 | "http", 343 | "http client", 344 | "rest", 345 | "web service" 346 | ], 347 | "support": { 348 | "issues": "https://github.com/guzzle/guzzle/issues", 349 | "source": "https://github.com/guzzle/guzzle/tree/6.5.8" 350 | }, 351 | "funding": [ 352 | { 353 | "url": "https://github.com/GrahamCampbell", 354 | "type": "github" 355 | }, 356 | { 357 | "url": "https://github.com/Nyholm", 358 | "type": "github" 359 | }, 360 | { 361 | "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", 362 | "type": "tidelift" 363 | } 364 | ], 365 | "time": "2022-06-20T22:16:07+00:00" 366 | }, 367 | { 368 | "name": "guzzlehttp/promises", 369 | "version": "1.5.3", 370 | "source": { 371 | "type": "git", 372 | "url": "https://github.com/guzzle/promises.git", 373 | "reference": "67ab6e18aaa14d753cc148911d273f6e6cb6721e" 374 | }, 375 | "dist": { 376 | "type": "zip", 377 | "url": "https://api.github.com/repos/guzzle/promises/zipball/67ab6e18aaa14d753cc148911d273f6e6cb6721e", 378 | "reference": "67ab6e18aaa14d753cc148911d273f6e6cb6721e", 379 | "shasum": "" 380 | }, 381 | "require": { 382 | "php": ">=5.5" 383 | }, 384 | "require-dev": { 385 | "symfony/phpunit-bridge": "^4.4 || ^5.1" 386 | }, 387 | "type": "library", 388 | "autoload": { 389 | "files": [ 390 | "src/functions_include.php" 391 | ], 392 | "psr-4": { 393 | "GuzzleHttp\\Promise\\": "src/" 394 | } 395 | }, 396 | "notification-url": "https://packagist.org/downloads/", 397 | "license": [ 398 | "MIT" 399 | ], 400 | "authors": [ 401 | { 402 | "name": "Graham Campbell", 403 | "email": "hello@gjcampbell.co.uk", 404 | "homepage": "https://github.com/GrahamCampbell" 405 | }, 406 | { 407 | "name": "Michael Dowling", 408 | "email": "mtdowling@gmail.com", 409 | "homepage": "https://github.com/mtdowling" 410 | }, 411 | { 412 | "name": "Tobias Nyholm", 413 | "email": "tobias.nyholm@gmail.com", 414 | "homepage": "https://github.com/Nyholm" 415 | }, 416 | { 417 | "name": "Tobias Schultze", 418 | "email": "webmaster@tubo-world.de", 419 | "homepage": "https://github.com/Tobion" 420 | } 421 | ], 422 | "description": "Guzzle promises library", 423 | "keywords": [ 424 | "promise" 425 | ], 426 | "support": { 427 | "issues": "https://github.com/guzzle/promises/issues", 428 | "source": "https://github.com/guzzle/promises/tree/1.5.3" 429 | }, 430 | "funding": [ 431 | { 432 | "url": "https://github.com/GrahamCampbell", 433 | "type": "github" 434 | }, 435 | { 436 | "url": "https://github.com/Nyholm", 437 | "type": "github" 438 | }, 439 | { 440 | "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", 441 | "type": "tidelift" 442 | } 443 | ], 444 | "time": "2023-05-21T12:31:43+00:00" 445 | }, 446 | { 447 | "name": "guzzlehttp/psr7", 448 | "version": "1.9.1", 449 | "source": { 450 | "type": "git", 451 | "url": "https://github.com/guzzle/psr7.git", 452 | "reference": "e4490cabc77465aaee90b20cfc9a770f8c04be6b" 453 | }, 454 | "dist": { 455 | "type": "zip", 456 | "url": "https://api.github.com/repos/guzzle/psr7/zipball/e4490cabc77465aaee90b20cfc9a770f8c04be6b", 457 | "reference": "e4490cabc77465aaee90b20cfc9a770f8c04be6b", 458 | "shasum": "" 459 | }, 460 | "require": { 461 | "php": ">=5.4.0", 462 | "psr/http-message": "~1.0", 463 | "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" 464 | }, 465 | "provide": { 466 | "psr/http-message-implementation": "1.0" 467 | }, 468 | "require-dev": { 469 | "ext-zlib": "*", 470 | "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10" 471 | }, 472 | "suggest": { 473 | "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" 474 | }, 475 | "type": "library", 476 | "autoload": { 477 | "files": [ 478 | "src/functions_include.php" 479 | ], 480 | "psr-4": { 481 | "GuzzleHttp\\Psr7\\": "src/" 482 | } 483 | }, 484 | "notification-url": "https://packagist.org/downloads/", 485 | "license": [ 486 | "MIT" 487 | ], 488 | "authors": [ 489 | { 490 | "name": "Graham Campbell", 491 | "email": "hello@gjcampbell.co.uk", 492 | "homepage": "https://github.com/GrahamCampbell" 493 | }, 494 | { 495 | "name": "Michael Dowling", 496 | "email": "mtdowling@gmail.com", 497 | "homepage": "https://github.com/mtdowling" 498 | }, 499 | { 500 | "name": "George Mponos", 501 | "email": "gmponos@gmail.com", 502 | "homepage": "https://github.com/gmponos" 503 | }, 504 | { 505 | "name": "Tobias Nyholm", 506 | "email": "tobias.nyholm@gmail.com", 507 | "homepage": "https://github.com/Nyholm" 508 | }, 509 | { 510 | "name": "Márk Sági-Kazár", 511 | "email": "mark.sagikazar@gmail.com", 512 | "homepage": "https://github.com/sagikazarmark" 513 | }, 514 | { 515 | "name": "Tobias Schultze", 516 | "email": "webmaster@tubo-world.de", 517 | "homepage": "https://github.com/Tobion" 518 | } 519 | ], 520 | "description": "PSR-7 message implementation that also provides common utility methods", 521 | "keywords": [ 522 | "http", 523 | "message", 524 | "psr-7", 525 | "request", 526 | "response", 527 | "stream", 528 | "uri", 529 | "url" 530 | ], 531 | "support": { 532 | "issues": "https://github.com/guzzle/psr7/issues", 533 | "source": "https://github.com/guzzle/psr7/tree/1.9.1" 534 | }, 535 | "funding": [ 536 | { 537 | "url": "https://github.com/GrahamCampbell", 538 | "type": "github" 539 | }, 540 | { 541 | "url": "https://github.com/Nyholm", 542 | "type": "github" 543 | }, 544 | { 545 | "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", 546 | "type": "tidelift" 547 | } 548 | ], 549 | "time": "2023-04-17T16:00:37+00:00" 550 | }, 551 | { 552 | "name": "lesser-evil/shell-verbosity-is-evil", 553 | "version": "v1.0.0", 554 | "source": { 555 | "type": "git", 556 | "url": "https://github.com/totten/shell-verbosity-is-evil.git", 557 | "reference": "4195bce7e3adaeda6d4747a1f3ec3c39b124f169" 558 | }, 559 | "dist": { 560 | "type": "zip", 561 | "url": "https://api.github.com/repos/totten/shell-verbosity-is-evil/zipball/4195bce7e3adaeda6d4747a1f3ec3c39b124f169", 562 | "reference": "4195bce7e3adaeda6d4747a1f3ec3c39b124f169", 563 | "shasum": "" 564 | }, 565 | "type": "library", 566 | "autoload": { 567 | "psr-4": { 568 | "LesserEvil\\": "src/" 569 | } 570 | }, 571 | "notification-url": "https://packagist.org/downloads/", 572 | "license": [ 573 | "MIT" 574 | ], 575 | "authors": [ 576 | { 577 | "name": "Tim Otten", 578 | "email": "totten@civicrm.org" 579 | } 580 | ], 581 | "support": { 582 | "issues": "https://github.com/totten/shell-verbosity-is-evil/issues", 583 | "source": "https://github.com/totten/shell-verbosity-is-evil/tree/v1.0.0" 584 | }, 585 | "time": "2022-11-02T23:02:43+00:00" 586 | }, 587 | { 588 | "name": "mnapoli/silly", 589 | "version": "1.9.0", 590 | "source": { 591 | "type": "git", 592 | "url": "https://github.com/mnapoli/silly.git", 593 | "reference": "fdb69ac9335b4c83f70baf433eae91e691901f1c" 594 | }, 595 | "dist": { 596 | "type": "zip", 597 | "url": "https://api.github.com/repos/mnapoli/silly/zipball/fdb69ac9335b4c83f70baf433eae91e691901f1c", 598 | "reference": "fdb69ac9335b4c83f70baf433eae91e691901f1c", 599 | "shasum": "" 600 | }, 601 | "require": { 602 | "php": ">=7.4", 603 | "php-di/invoker": "~2.0", 604 | "psr/container": "^1.0|^2.0", 605 | "symfony/console": "~3.0|~4.0|~5.0|~6.0|~7.0" 606 | }, 607 | "require-dev": { 608 | "friendsofphp/php-cs-fixer": "^2.12", 609 | "mnapoli/phpunit-easymock": "~1.0", 610 | "phpunit/phpunit": "^6.4|^7|^8|^9|^10" 611 | }, 612 | "type": "library", 613 | "autoload": { 614 | "psr-4": { 615 | "Silly\\": "src/" 616 | } 617 | }, 618 | "notification-url": "https://packagist.org/downloads/", 619 | "license": [ 620 | "MIT" 621 | ], 622 | "description": "Silly CLI micro-framework based on Symfony Console", 623 | "keywords": [ 624 | "PSR-11", 625 | "cli", 626 | "console", 627 | "framework", 628 | "micro-framework", 629 | "silly" 630 | ], 631 | "support": { 632 | "issues": "https://github.com/mnapoli/silly/issues", 633 | "source": "https://github.com/mnapoli/silly/tree/1.9.0" 634 | }, 635 | "funding": [ 636 | { 637 | "url": "https://github.com/mnapoli", 638 | "type": "github" 639 | }, 640 | { 641 | "url": "https://tidelift.com/funding/github/packagist/mnapoli/silly", 642 | "type": "tidelift" 643 | } 644 | ], 645 | "time": "2024-01-18T15:47:39+00:00" 646 | }, 647 | { 648 | "name": "php-di/invoker", 649 | "version": "2.3.4", 650 | "source": { 651 | "type": "git", 652 | "url": "https://github.com/PHP-DI/Invoker.git", 653 | "reference": "33234b32dafa8eb69202f950a1fc92055ed76a86" 654 | }, 655 | "dist": { 656 | "type": "zip", 657 | "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/33234b32dafa8eb69202f950a1fc92055ed76a86", 658 | "reference": "33234b32dafa8eb69202f950a1fc92055ed76a86", 659 | "shasum": "" 660 | }, 661 | "require": { 662 | "php": ">=7.3", 663 | "psr/container": "^1.0|^2.0" 664 | }, 665 | "require-dev": { 666 | "athletic/athletic": "~0.1.8", 667 | "mnapoli/hard-mode": "~0.3.0", 668 | "phpunit/phpunit": "^9.0" 669 | }, 670 | "type": "library", 671 | "autoload": { 672 | "psr-4": { 673 | "Invoker\\": "src/" 674 | } 675 | }, 676 | "notification-url": "https://packagist.org/downloads/", 677 | "license": [ 678 | "MIT" 679 | ], 680 | "description": "Generic and extensible callable invoker", 681 | "homepage": "https://github.com/PHP-DI/Invoker", 682 | "keywords": [ 683 | "callable", 684 | "dependency", 685 | "dependency-injection", 686 | "injection", 687 | "invoke", 688 | "invoker" 689 | ], 690 | "support": { 691 | "issues": "https://github.com/PHP-DI/Invoker/issues", 692 | "source": "https://github.com/PHP-DI/Invoker/tree/2.3.4" 693 | }, 694 | "funding": [ 695 | { 696 | "url": "https://github.com/mnapoli", 697 | "type": "github" 698 | } 699 | ], 700 | "time": "2023-09-08T09:24:21+00:00" 701 | }, 702 | { 703 | "name": "pimple/pimple", 704 | "version": "v3.5.0", 705 | "source": { 706 | "type": "git", 707 | "url": "https://github.com/silexphp/Pimple.git", 708 | "reference": "a94b3a4db7fb774b3d78dad2315ddc07629e1bed" 709 | }, 710 | "dist": { 711 | "type": "zip", 712 | "url": "https://api.github.com/repos/silexphp/Pimple/zipball/a94b3a4db7fb774b3d78dad2315ddc07629e1bed", 713 | "reference": "a94b3a4db7fb774b3d78dad2315ddc07629e1bed", 714 | "shasum": "" 715 | }, 716 | "require": { 717 | "php": ">=7.2.5", 718 | "psr/container": "^1.1 || ^2.0" 719 | }, 720 | "require-dev": { 721 | "symfony/phpunit-bridge": "^5.4@dev" 722 | }, 723 | "type": "library", 724 | "extra": { 725 | "branch-alias": { 726 | "dev-master": "3.4.x-dev" 727 | } 728 | }, 729 | "autoload": { 730 | "psr-0": { 731 | "Pimple": "src/" 732 | } 733 | }, 734 | "notification-url": "https://packagist.org/downloads/", 735 | "license": [ 736 | "MIT" 737 | ], 738 | "authors": [ 739 | { 740 | "name": "Fabien Potencier", 741 | "email": "fabien@symfony.com" 742 | } 743 | ], 744 | "description": "Pimple, a simple Dependency Injection Container", 745 | "homepage": "https://pimple.symfony.com", 746 | "keywords": [ 747 | "container", 748 | "dependency injection" 749 | ], 750 | "support": { 751 | "source": "https://github.com/silexphp/Pimple/tree/v3.5.0" 752 | }, 753 | "time": "2021-10-28T11:13:42+00:00" 754 | }, 755 | { 756 | "name": "psr/container", 757 | "version": "2.0.2", 758 | "source": { 759 | "type": "git", 760 | "url": "https://github.com/php-fig/container.git", 761 | "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" 762 | }, 763 | "dist": { 764 | "type": "zip", 765 | "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", 766 | "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", 767 | "shasum": "" 768 | }, 769 | "require": { 770 | "php": ">=7.4.0" 771 | }, 772 | "type": "library", 773 | "extra": { 774 | "branch-alias": { 775 | "dev-master": "2.0.x-dev" 776 | } 777 | }, 778 | "autoload": { 779 | "psr-4": { 780 | "Psr\\Container\\": "src/" 781 | } 782 | }, 783 | "notification-url": "https://packagist.org/downloads/", 784 | "license": [ 785 | "MIT" 786 | ], 787 | "authors": [ 788 | { 789 | "name": "PHP-FIG", 790 | "homepage": "https://www.php-fig.org/" 791 | } 792 | ], 793 | "description": "Common Container Interface (PHP FIG PSR-11)", 794 | "homepage": "https://github.com/php-fig/container", 795 | "keywords": [ 796 | "PSR-11", 797 | "container", 798 | "container-interface", 799 | "container-interop", 800 | "psr" 801 | ], 802 | "support": { 803 | "issues": "https://github.com/php-fig/container/issues", 804 | "source": "https://github.com/php-fig/container/tree/2.0.2" 805 | }, 806 | "time": "2021-11-05T16:47:00+00:00" 807 | }, 808 | { 809 | "name": "psr/http-message", 810 | "version": "1.1", 811 | "source": { 812 | "type": "git", 813 | "url": "https://github.com/php-fig/http-message.git", 814 | "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba" 815 | }, 816 | "dist": { 817 | "type": "zip", 818 | "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba", 819 | "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba", 820 | "shasum": "" 821 | }, 822 | "require": { 823 | "php": "^7.2 || ^8.0" 824 | }, 825 | "type": "library", 826 | "extra": { 827 | "branch-alias": { 828 | "dev-master": "1.1.x-dev" 829 | } 830 | }, 831 | "autoload": { 832 | "psr-4": { 833 | "Psr\\Http\\Message\\": "src/" 834 | } 835 | }, 836 | "notification-url": "https://packagist.org/downloads/", 837 | "license": [ 838 | "MIT" 839 | ], 840 | "authors": [ 841 | { 842 | "name": "PHP-FIG", 843 | "homepage": "http://www.php-fig.org/" 844 | } 845 | ], 846 | "description": "Common interface for HTTP messages", 847 | "homepage": "https://github.com/php-fig/http-message", 848 | "keywords": [ 849 | "http", 850 | "http-message", 851 | "psr", 852 | "psr-7", 853 | "request", 854 | "response" 855 | ], 856 | "support": { 857 | "source": "https://github.com/php-fig/http-message/tree/1.1" 858 | }, 859 | "time": "2023-04-04T09:50:52+00:00" 860 | }, 861 | { 862 | "name": "ralouphie/getallheaders", 863 | "version": "3.0.3", 864 | "source": { 865 | "type": "git", 866 | "url": "https://github.com/ralouphie/getallheaders.git", 867 | "reference": "120b605dfeb996808c31b6477290a714d356e822" 868 | }, 869 | "dist": { 870 | "type": "zip", 871 | "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", 872 | "reference": "120b605dfeb996808c31b6477290a714d356e822", 873 | "shasum": "" 874 | }, 875 | "require": { 876 | "php": ">=5.6" 877 | }, 878 | "require-dev": { 879 | "php-coveralls/php-coveralls": "^2.1", 880 | "phpunit/phpunit": "^5 || ^6.5" 881 | }, 882 | "type": "library", 883 | "autoload": { 884 | "files": [ 885 | "src/getallheaders.php" 886 | ] 887 | }, 888 | "notification-url": "https://packagist.org/downloads/", 889 | "license": [ 890 | "MIT" 891 | ], 892 | "authors": [ 893 | { 894 | "name": "Ralph Khattar", 895 | "email": "ralph.khattar@gmail.com" 896 | } 897 | ], 898 | "description": "A polyfill for getallheaders.", 899 | "support": { 900 | "issues": "https://github.com/ralouphie/getallheaders/issues", 901 | "source": "https://github.com/ralouphie/getallheaders/tree/develop" 902 | }, 903 | "time": "2019-03-08T08:55:37+00:00" 904 | }, 905 | { 906 | "name": "symfony/console", 907 | "version": "v5.4.44", 908 | "source": { 909 | "type": "git", 910 | "url": "https://github.com/symfony/console.git", 911 | "reference": "5b5a0aa66e3296e303e22490f90f521551835a83" 912 | }, 913 | "dist": { 914 | "type": "zip", 915 | "url": "https://api.github.com/repos/symfony/console/zipball/5b5a0aa66e3296e303e22490f90f521551835a83", 916 | "reference": "5b5a0aa66e3296e303e22490f90f521551835a83", 917 | "shasum": "" 918 | }, 919 | "require": { 920 | "php": ">=7.2.5", 921 | "symfony/deprecation-contracts": "^2.1|^3", 922 | "symfony/polyfill-mbstring": "~1.0", 923 | "symfony/polyfill-php73": "^1.9", 924 | "symfony/polyfill-php80": "^1.16", 925 | "symfony/service-contracts": "^1.1|^2|^3", 926 | "symfony/string": "^5.1|^6.0" 927 | }, 928 | "conflict": { 929 | "psr/log": ">=3", 930 | "symfony/dependency-injection": "<4.4", 931 | "symfony/dotenv": "<5.1", 932 | "symfony/event-dispatcher": "<4.4", 933 | "symfony/lock": "<4.4", 934 | "symfony/process": "<4.4" 935 | }, 936 | "provide": { 937 | "psr/log-implementation": "1.0|2.0" 938 | }, 939 | "require-dev": { 940 | "psr/log": "^1|^2", 941 | "symfony/config": "^4.4|^5.0|^6.0", 942 | "symfony/dependency-injection": "^4.4|^5.0|^6.0", 943 | "symfony/event-dispatcher": "^4.4|^5.0|^6.0", 944 | "symfony/lock": "^4.4|^5.0|^6.0", 945 | "symfony/process": "^4.4|^5.0|^6.0", 946 | "symfony/var-dumper": "^4.4|^5.0|^6.0" 947 | }, 948 | "suggest": { 949 | "psr/log": "For using the console logger", 950 | "symfony/event-dispatcher": "", 951 | "symfony/lock": "", 952 | "symfony/process": "" 953 | }, 954 | "type": "library", 955 | "autoload": { 956 | "psr-4": { 957 | "Symfony\\Component\\Console\\": "" 958 | }, 959 | "exclude-from-classmap": [ 960 | "/Tests/" 961 | ] 962 | }, 963 | "notification-url": "https://packagist.org/downloads/", 964 | "license": [ 965 | "MIT" 966 | ], 967 | "authors": [ 968 | { 969 | "name": "Fabien Potencier", 970 | "email": "fabien@symfony.com" 971 | }, 972 | { 973 | "name": "Symfony Community", 974 | "homepage": "https://symfony.com/contributors" 975 | } 976 | ], 977 | "description": "Eases the creation of beautiful and testable command line interfaces", 978 | "homepage": "https://symfony.com", 979 | "keywords": [ 980 | "cli", 981 | "command-line", 982 | "console", 983 | "terminal" 984 | ], 985 | "support": { 986 | "source": "https://github.com/symfony/console/tree/v5.4.44" 987 | }, 988 | "funding": [ 989 | { 990 | "url": "https://symfony.com/sponsor", 991 | "type": "custom" 992 | }, 993 | { 994 | "url": "https://github.com/fabpot", 995 | "type": "github" 996 | }, 997 | { 998 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 999 | "type": "tidelift" 1000 | } 1001 | ], 1002 | "time": "2024-09-20T07:56:40+00:00" 1003 | }, 1004 | { 1005 | "name": "symfony/deprecation-contracts", 1006 | "version": "v3.5.0", 1007 | "source": { 1008 | "type": "git", 1009 | "url": "https://github.com/symfony/deprecation-contracts.git", 1010 | "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" 1011 | }, 1012 | "dist": { 1013 | "type": "zip", 1014 | "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", 1015 | "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", 1016 | "shasum": "" 1017 | }, 1018 | "require": { 1019 | "php": ">=8.1" 1020 | }, 1021 | "type": "library", 1022 | "extra": { 1023 | "branch-alias": { 1024 | "dev-main": "3.5-dev" 1025 | }, 1026 | "thanks": { 1027 | "name": "symfony/contracts", 1028 | "url": "https://github.com/symfony/contracts" 1029 | } 1030 | }, 1031 | "autoload": { 1032 | "files": [ 1033 | "function.php" 1034 | ] 1035 | }, 1036 | "notification-url": "https://packagist.org/downloads/", 1037 | "license": [ 1038 | "MIT" 1039 | ], 1040 | "authors": [ 1041 | { 1042 | "name": "Nicolas Grekas", 1043 | "email": "p@tchwork.com" 1044 | }, 1045 | { 1046 | "name": "Symfony Community", 1047 | "homepage": "https://symfony.com/contributors" 1048 | } 1049 | ], 1050 | "description": "A generic function and convention to trigger deprecation notices", 1051 | "homepage": "https://symfony.com", 1052 | "support": { 1053 | "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" 1054 | }, 1055 | "funding": [ 1056 | { 1057 | "url": "https://symfony.com/sponsor", 1058 | "type": "custom" 1059 | }, 1060 | { 1061 | "url": "https://github.com/fabpot", 1062 | "type": "github" 1063 | }, 1064 | { 1065 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1066 | "type": "tidelift" 1067 | } 1068 | ], 1069 | "time": "2024-04-18T09:32:20+00:00" 1070 | }, 1071 | { 1072 | "name": "symfony/filesystem", 1073 | "version": "v6.4.12", 1074 | "source": { 1075 | "type": "git", 1076 | "url": "https://github.com/symfony/filesystem.git", 1077 | "reference": "f810e3cbdf7fdc35983968523d09f349fa9ada12" 1078 | }, 1079 | "dist": { 1080 | "type": "zip", 1081 | "url": "https://api.github.com/repos/symfony/filesystem/zipball/f810e3cbdf7fdc35983968523d09f349fa9ada12", 1082 | "reference": "f810e3cbdf7fdc35983968523d09f349fa9ada12", 1083 | "shasum": "" 1084 | }, 1085 | "require": { 1086 | "php": ">=8.1", 1087 | "symfony/polyfill-ctype": "~1.8", 1088 | "symfony/polyfill-mbstring": "~1.8" 1089 | }, 1090 | "require-dev": { 1091 | "symfony/process": "^5.4|^6.4|^7.0" 1092 | }, 1093 | "type": "library", 1094 | "autoload": { 1095 | "psr-4": { 1096 | "Symfony\\Component\\Filesystem\\": "" 1097 | }, 1098 | "exclude-from-classmap": [ 1099 | "/Tests/" 1100 | ] 1101 | }, 1102 | "notification-url": "https://packagist.org/downloads/", 1103 | "license": [ 1104 | "MIT" 1105 | ], 1106 | "authors": [ 1107 | { 1108 | "name": "Fabien Potencier", 1109 | "email": "fabien@symfony.com" 1110 | }, 1111 | { 1112 | "name": "Symfony Community", 1113 | "homepage": "https://symfony.com/contributors" 1114 | } 1115 | ], 1116 | "description": "Provides basic utilities for the filesystem", 1117 | "homepage": "https://symfony.com", 1118 | "support": { 1119 | "source": "https://github.com/symfony/filesystem/tree/v6.4.12" 1120 | }, 1121 | "funding": [ 1122 | { 1123 | "url": "https://symfony.com/sponsor", 1124 | "type": "custom" 1125 | }, 1126 | { 1127 | "url": "https://github.com/fabpot", 1128 | "type": "github" 1129 | }, 1130 | { 1131 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1132 | "type": "tidelift" 1133 | } 1134 | ], 1135 | "time": "2024-09-16T16:01:33+00:00" 1136 | }, 1137 | { 1138 | "name": "symfony/polyfill-ctype", 1139 | "version": "v1.31.0", 1140 | "source": { 1141 | "type": "git", 1142 | "url": "https://github.com/symfony/polyfill-ctype.git", 1143 | "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" 1144 | }, 1145 | "dist": { 1146 | "type": "zip", 1147 | "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", 1148 | "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", 1149 | "shasum": "" 1150 | }, 1151 | "require": { 1152 | "php": ">=7.2" 1153 | }, 1154 | "provide": { 1155 | "ext-ctype": "*" 1156 | }, 1157 | "suggest": { 1158 | "ext-ctype": "For best performance" 1159 | }, 1160 | "type": "library", 1161 | "extra": { 1162 | "thanks": { 1163 | "name": "symfony/polyfill", 1164 | "url": "https://github.com/symfony/polyfill" 1165 | } 1166 | }, 1167 | "autoload": { 1168 | "files": [ 1169 | "bootstrap.php" 1170 | ], 1171 | "psr-4": { 1172 | "Symfony\\Polyfill\\Ctype\\": "" 1173 | } 1174 | }, 1175 | "notification-url": "https://packagist.org/downloads/", 1176 | "license": [ 1177 | "MIT" 1178 | ], 1179 | "authors": [ 1180 | { 1181 | "name": "Gert de Pagter", 1182 | "email": "BackEndTea@gmail.com" 1183 | }, 1184 | { 1185 | "name": "Symfony Community", 1186 | "homepage": "https://symfony.com/contributors" 1187 | } 1188 | ], 1189 | "description": "Symfony polyfill for ctype functions", 1190 | "homepage": "https://symfony.com", 1191 | "keywords": [ 1192 | "compatibility", 1193 | "ctype", 1194 | "polyfill", 1195 | "portable" 1196 | ], 1197 | "support": { 1198 | "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" 1199 | }, 1200 | "funding": [ 1201 | { 1202 | "url": "https://symfony.com/sponsor", 1203 | "type": "custom" 1204 | }, 1205 | { 1206 | "url": "https://github.com/fabpot", 1207 | "type": "github" 1208 | }, 1209 | { 1210 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1211 | "type": "tidelift" 1212 | } 1213 | ], 1214 | "time": "2024-09-09T11:45:10+00:00" 1215 | }, 1216 | { 1217 | "name": "symfony/polyfill-intl-grapheme", 1218 | "version": "v1.31.0", 1219 | "source": { 1220 | "type": "git", 1221 | "url": "https://github.com/symfony/polyfill-intl-grapheme.git", 1222 | "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" 1223 | }, 1224 | "dist": { 1225 | "type": "zip", 1226 | "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", 1227 | "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", 1228 | "shasum": "" 1229 | }, 1230 | "require": { 1231 | "php": ">=7.2" 1232 | }, 1233 | "suggest": { 1234 | "ext-intl": "For best performance" 1235 | }, 1236 | "type": "library", 1237 | "extra": { 1238 | "thanks": { 1239 | "name": "symfony/polyfill", 1240 | "url": "https://github.com/symfony/polyfill" 1241 | } 1242 | }, 1243 | "autoload": { 1244 | "files": [ 1245 | "bootstrap.php" 1246 | ], 1247 | "psr-4": { 1248 | "Symfony\\Polyfill\\Intl\\Grapheme\\": "" 1249 | } 1250 | }, 1251 | "notification-url": "https://packagist.org/downloads/", 1252 | "license": [ 1253 | "MIT" 1254 | ], 1255 | "authors": [ 1256 | { 1257 | "name": "Nicolas Grekas", 1258 | "email": "p@tchwork.com" 1259 | }, 1260 | { 1261 | "name": "Symfony Community", 1262 | "homepage": "https://symfony.com/contributors" 1263 | } 1264 | ], 1265 | "description": "Symfony polyfill for intl's grapheme_* functions", 1266 | "homepage": "https://symfony.com", 1267 | "keywords": [ 1268 | "compatibility", 1269 | "grapheme", 1270 | "intl", 1271 | "polyfill", 1272 | "portable", 1273 | "shim" 1274 | ], 1275 | "support": { 1276 | "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" 1277 | }, 1278 | "funding": [ 1279 | { 1280 | "url": "https://symfony.com/sponsor", 1281 | "type": "custom" 1282 | }, 1283 | { 1284 | "url": "https://github.com/fabpot", 1285 | "type": "github" 1286 | }, 1287 | { 1288 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1289 | "type": "tidelift" 1290 | } 1291 | ], 1292 | "time": "2024-09-09T11:45:10+00:00" 1293 | }, 1294 | { 1295 | "name": "symfony/polyfill-intl-idn", 1296 | "version": "v1.31.0", 1297 | "source": { 1298 | "type": "git", 1299 | "url": "https://github.com/symfony/polyfill-intl-idn.git", 1300 | "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773" 1301 | }, 1302 | "dist": { 1303 | "type": "zip", 1304 | "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/c36586dcf89a12315939e00ec9b4474adcb1d773", 1305 | "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773", 1306 | "shasum": "" 1307 | }, 1308 | "require": { 1309 | "php": ">=7.2", 1310 | "symfony/polyfill-intl-normalizer": "^1.10" 1311 | }, 1312 | "suggest": { 1313 | "ext-intl": "For best performance" 1314 | }, 1315 | "type": "library", 1316 | "extra": { 1317 | "thanks": { 1318 | "name": "symfony/polyfill", 1319 | "url": "https://github.com/symfony/polyfill" 1320 | } 1321 | }, 1322 | "autoload": { 1323 | "files": [ 1324 | "bootstrap.php" 1325 | ], 1326 | "psr-4": { 1327 | "Symfony\\Polyfill\\Intl\\Idn\\": "" 1328 | } 1329 | }, 1330 | "notification-url": "https://packagist.org/downloads/", 1331 | "license": [ 1332 | "MIT" 1333 | ], 1334 | "authors": [ 1335 | { 1336 | "name": "Laurent Bassin", 1337 | "email": "laurent@bassin.info" 1338 | }, 1339 | { 1340 | "name": "Trevor Rowbotham", 1341 | "email": "trevor.rowbotham@pm.me" 1342 | }, 1343 | { 1344 | "name": "Symfony Community", 1345 | "homepage": "https://symfony.com/contributors" 1346 | } 1347 | ], 1348 | "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", 1349 | "homepage": "https://symfony.com", 1350 | "keywords": [ 1351 | "compatibility", 1352 | "idn", 1353 | "intl", 1354 | "polyfill", 1355 | "portable", 1356 | "shim" 1357 | ], 1358 | "support": { 1359 | "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.31.0" 1360 | }, 1361 | "funding": [ 1362 | { 1363 | "url": "https://symfony.com/sponsor", 1364 | "type": "custom" 1365 | }, 1366 | { 1367 | "url": "https://github.com/fabpot", 1368 | "type": "github" 1369 | }, 1370 | { 1371 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1372 | "type": "tidelift" 1373 | } 1374 | ], 1375 | "time": "2024-09-09T11:45:10+00:00" 1376 | }, 1377 | { 1378 | "name": "symfony/polyfill-intl-normalizer", 1379 | "version": "v1.31.0", 1380 | "source": { 1381 | "type": "git", 1382 | "url": "https://github.com/symfony/polyfill-intl-normalizer.git", 1383 | "reference": "3833d7255cc303546435cb650316bff708a1c75c" 1384 | }, 1385 | "dist": { 1386 | "type": "zip", 1387 | "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", 1388 | "reference": "3833d7255cc303546435cb650316bff708a1c75c", 1389 | "shasum": "" 1390 | }, 1391 | "require": { 1392 | "php": ">=7.2" 1393 | }, 1394 | "suggest": { 1395 | "ext-intl": "For best performance" 1396 | }, 1397 | "type": "library", 1398 | "extra": { 1399 | "thanks": { 1400 | "name": "symfony/polyfill", 1401 | "url": "https://github.com/symfony/polyfill" 1402 | } 1403 | }, 1404 | "autoload": { 1405 | "files": [ 1406 | "bootstrap.php" 1407 | ], 1408 | "psr-4": { 1409 | "Symfony\\Polyfill\\Intl\\Normalizer\\": "" 1410 | }, 1411 | "classmap": [ 1412 | "Resources/stubs" 1413 | ] 1414 | }, 1415 | "notification-url": "https://packagist.org/downloads/", 1416 | "license": [ 1417 | "MIT" 1418 | ], 1419 | "authors": [ 1420 | { 1421 | "name": "Nicolas Grekas", 1422 | "email": "p@tchwork.com" 1423 | }, 1424 | { 1425 | "name": "Symfony Community", 1426 | "homepage": "https://symfony.com/contributors" 1427 | } 1428 | ], 1429 | "description": "Symfony polyfill for intl's Normalizer class and related functions", 1430 | "homepage": "https://symfony.com", 1431 | "keywords": [ 1432 | "compatibility", 1433 | "intl", 1434 | "normalizer", 1435 | "polyfill", 1436 | "portable", 1437 | "shim" 1438 | ], 1439 | "support": { 1440 | "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" 1441 | }, 1442 | "funding": [ 1443 | { 1444 | "url": "https://symfony.com/sponsor", 1445 | "type": "custom" 1446 | }, 1447 | { 1448 | "url": "https://github.com/fabpot", 1449 | "type": "github" 1450 | }, 1451 | { 1452 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1453 | "type": "tidelift" 1454 | } 1455 | ], 1456 | "time": "2024-09-09T11:45:10+00:00" 1457 | }, 1458 | { 1459 | "name": "symfony/polyfill-mbstring", 1460 | "version": "v1.31.0", 1461 | "source": { 1462 | "type": "git", 1463 | "url": "https://github.com/symfony/polyfill-mbstring.git", 1464 | "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" 1465 | }, 1466 | "dist": { 1467 | "type": "zip", 1468 | "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", 1469 | "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", 1470 | "shasum": "" 1471 | }, 1472 | "require": { 1473 | "php": ">=7.2" 1474 | }, 1475 | "provide": { 1476 | "ext-mbstring": "*" 1477 | }, 1478 | "suggest": { 1479 | "ext-mbstring": "For best performance" 1480 | }, 1481 | "type": "library", 1482 | "extra": { 1483 | "thanks": { 1484 | "name": "symfony/polyfill", 1485 | "url": "https://github.com/symfony/polyfill" 1486 | } 1487 | }, 1488 | "autoload": { 1489 | "files": [ 1490 | "bootstrap.php" 1491 | ], 1492 | "psr-4": { 1493 | "Symfony\\Polyfill\\Mbstring\\": "" 1494 | } 1495 | }, 1496 | "notification-url": "https://packagist.org/downloads/", 1497 | "license": [ 1498 | "MIT" 1499 | ], 1500 | "authors": [ 1501 | { 1502 | "name": "Nicolas Grekas", 1503 | "email": "p@tchwork.com" 1504 | }, 1505 | { 1506 | "name": "Symfony Community", 1507 | "homepage": "https://symfony.com/contributors" 1508 | } 1509 | ], 1510 | "description": "Symfony polyfill for the Mbstring extension", 1511 | "homepage": "https://symfony.com", 1512 | "keywords": [ 1513 | "compatibility", 1514 | "mbstring", 1515 | "polyfill", 1516 | "portable", 1517 | "shim" 1518 | ], 1519 | "support": { 1520 | "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" 1521 | }, 1522 | "funding": [ 1523 | { 1524 | "url": "https://symfony.com/sponsor", 1525 | "type": "custom" 1526 | }, 1527 | { 1528 | "url": "https://github.com/fabpot", 1529 | "type": "github" 1530 | }, 1531 | { 1532 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1533 | "type": "tidelift" 1534 | } 1535 | ], 1536 | "time": "2024-09-09T11:45:10+00:00" 1537 | }, 1538 | { 1539 | "name": "symfony/polyfill-php73", 1540 | "version": "v1.31.0", 1541 | "source": { 1542 | "type": "git", 1543 | "url": "https://github.com/symfony/polyfill-php73.git", 1544 | "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb" 1545 | }, 1546 | "dist": { 1547 | "type": "zip", 1548 | "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", 1549 | "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", 1550 | "shasum": "" 1551 | }, 1552 | "require": { 1553 | "php": ">=7.2" 1554 | }, 1555 | "type": "library", 1556 | "extra": { 1557 | "thanks": { 1558 | "name": "symfony/polyfill", 1559 | "url": "https://github.com/symfony/polyfill" 1560 | } 1561 | }, 1562 | "autoload": { 1563 | "files": [ 1564 | "bootstrap.php" 1565 | ], 1566 | "psr-4": { 1567 | "Symfony\\Polyfill\\Php73\\": "" 1568 | }, 1569 | "classmap": [ 1570 | "Resources/stubs" 1571 | ] 1572 | }, 1573 | "notification-url": "https://packagist.org/downloads/", 1574 | "license": [ 1575 | "MIT" 1576 | ], 1577 | "authors": [ 1578 | { 1579 | "name": "Nicolas Grekas", 1580 | "email": "p@tchwork.com" 1581 | }, 1582 | { 1583 | "name": "Symfony Community", 1584 | "homepage": "https://symfony.com/contributors" 1585 | } 1586 | ], 1587 | "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", 1588 | "homepage": "https://symfony.com", 1589 | "keywords": [ 1590 | "compatibility", 1591 | "polyfill", 1592 | "portable", 1593 | "shim" 1594 | ], 1595 | "support": { 1596 | "source": "https://github.com/symfony/polyfill-php73/tree/v1.31.0" 1597 | }, 1598 | "funding": [ 1599 | { 1600 | "url": "https://symfony.com/sponsor", 1601 | "type": "custom" 1602 | }, 1603 | { 1604 | "url": "https://github.com/fabpot", 1605 | "type": "github" 1606 | }, 1607 | { 1608 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1609 | "type": "tidelift" 1610 | } 1611 | ], 1612 | "time": "2024-09-09T11:45:10+00:00" 1613 | }, 1614 | { 1615 | "name": "symfony/polyfill-php80", 1616 | "version": "v1.31.0", 1617 | "source": { 1618 | "type": "git", 1619 | "url": "https://github.com/symfony/polyfill-php80.git", 1620 | "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" 1621 | }, 1622 | "dist": { 1623 | "type": "zip", 1624 | "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", 1625 | "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", 1626 | "shasum": "" 1627 | }, 1628 | "require": { 1629 | "php": ">=7.2" 1630 | }, 1631 | "type": "library", 1632 | "extra": { 1633 | "thanks": { 1634 | "name": "symfony/polyfill", 1635 | "url": "https://github.com/symfony/polyfill" 1636 | } 1637 | }, 1638 | "autoload": { 1639 | "files": [ 1640 | "bootstrap.php" 1641 | ], 1642 | "psr-4": { 1643 | "Symfony\\Polyfill\\Php80\\": "" 1644 | }, 1645 | "classmap": [ 1646 | "Resources/stubs" 1647 | ] 1648 | }, 1649 | "notification-url": "https://packagist.org/downloads/", 1650 | "license": [ 1651 | "MIT" 1652 | ], 1653 | "authors": [ 1654 | { 1655 | "name": "Ion Bazan", 1656 | "email": "ion.bazan@gmail.com" 1657 | }, 1658 | { 1659 | "name": "Nicolas Grekas", 1660 | "email": "p@tchwork.com" 1661 | }, 1662 | { 1663 | "name": "Symfony Community", 1664 | "homepage": "https://symfony.com/contributors" 1665 | } 1666 | ], 1667 | "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", 1668 | "homepage": "https://symfony.com", 1669 | "keywords": [ 1670 | "compatibility", 1671 | "polyfill", 1672 | "portable", 1673 | "shim" 1674 | ], 1675 | "support": { 1676 | "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" 1677 | }, 1678 | "funding": [ 1679 | { 1680 | "url": "https://symfony.com/sponsor", 1681 | "type": "custom" 1682 | }, 1683 | { 1684 | "url": "https://github.com/fabpot", 1685 | "type": "github" 1686 | }, 1687 | { 1688 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1689 | "type": "tidelift" 1690 | } 1691 | ], 1692 | "time": "2024-09-09T11:45:10+00:00" 1693 | }, 1694 | { 1695 | "name": "symfony/polyfill-php81", 1696 | "version": "v1.31.0", 1697 | "source": { 1698 | "type": "git", 1699 | "url": "https://github.com/symfony/polyfill-php81.git", 1700 | "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" 1701 | }, 1702 | "dist": { 1703 | "type": "zip", 1704 | "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", 1705 | "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", 1706 | "shasum": "" 1707 | }, 1708 | "require": { 1709 | "php": ">=7.2" 1710 | }, 1711 | "type": "library", 1712 | "extra": { 1713 | "thanks": { 1714 | "name": "symfony/polyfill", 1715 | "url": "https://github.com/symfony/polyfill" 1716 | } 1717 | }, 1718 | "autoload": { 1719 | "files": [ 1720 | "bootstrap.php" 1721 | ], 1722 | "psr-4": { 1723 | "Symfony\\Polyfill\\Php81\\": "" 1724 | }, 1725 | "classmap": [ 1726 | "Resources/stubs" 1727 | ] 1728 | }, 1729 | "notification-url": "https://packagist.org/downloads/", 1730 | "license": [ 1731 | "MIT" 1732 | ], 1733 | "authors": [ 1734 | { 1735 | "name": "Nicolas Grekas", 1736 | "email": "p@tchwork.com" 1737 | }, 1738 | { 1739 | "name": "Symfony Community", 1740 | "homepage": "https://symfony.com/contributors" 1741 | } 1742 | ], 1743 | "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", 1744 | "homepage": "https://symfony.com", 1745 | "keywords": [ 1746 | "compatibility", 1747 | "polyfill", 1748 | "portable", 1749 | "shim" 1750 | ], 1751 | "support": { 1752 | "source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0" 1753 | }, 1754 | "funding": [ 1755 | { 1756 | "url": "https://symfony.com/sponsor", 1757 | "type": "custom" 1758 | }, 1759 | { 1760 | "url": "https://github.com/fabpot", 1761 | "type": "github" 1762 | }, 1763 | { 1764 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1765 | "type": "tidelift" 1766 | } 1767 | ], 1768 | "time": "2024-09-09T11:45:10+00:00" 1769 | }, 1770 | { 1771 | "name": "symfony/process", 1772 | "version": "v4.4.44", 1773 | "source": { 1774 | "type": "git", 1775 | "url": "https://github.com/symfony/process.git", 1776 | "reference": "5cee9cdc4f7805e2699d9fd66991a0e6df8252a2" 1777 | }, 1778 | "dist": { 1779 | "type": "zip", 1780 | "url": "https://api.github.com/repos/symfony/process/zipball/5cee9cdc4f7805e2699d9fd66991a0e6df8252a2", 1781 | "reference": "5cee9cdc4f7805e2699d9fd66991a0e6df8252a2", 1782 | "shasum": "" 1783 | }, 1784 | "require": { 1785 | "php": ">=7.1.3", 1786 | "symfony/polyfill-php80": "^1.16" 1787 | }, 1788 | "type": "library", 1789 | "autoload": { 1790 | "psr-4": { 1791 | "Symfony\\Component\\Process\\": "" 1792 | }, 1793 | "exclude-from-classmap": [ 1794 | "/Tests/" 1795 | ] 1796 | }, 1797 | "notification-url": "https://packagist.org/downloads/", 1798 | "license": [ 1799 | "MIT" 1800 | ], 1801 | "authors": [ 1802 | { 1803 | "name": "Fabien Potencier", 1804 | "email": "fabien@symfony.com" 1805 | }, 1806 | { 1807 | "name": "Symfony Community", 1808 | "homepage": "https://symfony.com/contributors" 1809 | } 1810 | ], 1811 | "description": "Executes commands in sub-processes", 1812 | "homepage": "https://symfony.com", 1813 | "support": { 1814 | "source": "https://github.com/symfony/process/tree/v4.4.44" 1815 | }, 1816 | "funding": [ 1817 | { 1818 | "url": "https://symfony.com/sponsor", 1819 | "type": "custom" 1820 | }, 1821 | { 1822 | "url": "https://github.com/fabpot", 1823 | "type": "github" 1824 | }, 1825 | { 1826 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1827 | "type": "tidelift" 1828 | } 1829 | ], 1830 | "time": "2022-06-27T13:16:42+00:00" 1831 | }, 1832 | { 1833 | "name": "symfony/service-contracts", 1834 | "version": "v3.5.0", 1835 | "source": { 1836 | "type": "git", 1837 | "url": "https://github.com/symfony/service-contracts.git", 1838 | "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f" 1839 | }, 1840 | "dist": { 1841 | "type": "zip", 1842 | "url": "https://api.github.com/repos/symfony/service-contracts/zipball/bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", 1843 | "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", 1844 | "shasum": "" 1845 | }, 1846 | "require": { 1847 | "php": ">=8.1", 1848 | "psr/container": "^1.1|^2.0", 1849 | "symfony/deprecation-contracts": "^2.5|^3" 1850 | }, 1851 | "conflict": { 1852 | "ext-psr": "<1.1|>=2" 1853 | }, 1854 | "type": "library", 1855 | "extra": { 1856 | "branch-alias": { 1857 | "dev-main": "3.5-dev" 1858 | }, 1859 | "thanks": { 1860 | "name": "symfony/contracts", 1861 | "url": "https://github.com/symfony/contracts" 1862 | } 1863 | }, 1864 | "autoload": { 1865 | "psr-4": { 1866 | "Symfony\\Contracts\\Service\\": "" 1867 | }, 1868 | "exclude-from-classmap": [ 1869 | "/Test/" 1870 | ] 1871 | }, 1872 | "notification-url": "https://packagist.org/downloads/", 1873 | "license": [ 1874 | "MIT" 1875 | ], 1876 | "authors": [ 1877 | { 1878 | "name": "Nicolas Grekas", 1879 | "email": "p@tchwork.com" 1880 | }, 1881 | { 1882 | "name": "Symfony Community", 1883 | "homepage": "https://symfony.com/contributors" 1884 | } 1885 | ], 1886 | "description": "Generic abstractions related to writing services", 1887 | "homepage": "https://symfony.com", 1888 | "keywords": [ 1889 | "abstractions", 1890 | "contracts", 1891 | "decoupling", 1892 | "interfaces", 1893 | "interoperability", 1894 | "standards" 1895 | ], 1896 | "support": { 1897 | "source": "https://github.com/symfony/service-contracts/tree/v3.5.0" 1898 | }, 1899 | "funding": [ 1900 | { 1901 | "url": "https://symfony.com/sponsor", 1902 | "type": "custom" 1903 | }, 1904 | { 1905 | "url": "https://github.com/fabpot", 1906 | "type": "github" 1907 | }, 1908 | { 1909 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1910 | "type": "tidelift" 1911 | } 1912 | ], 1913 | "time": "2024-04-18T09:32:20+00:00" 1914 | }, 1915 | { 1916 | "name": "symfony/string", 1917 | "version": "v6.4.12", 1918 | "source": { 1919 | "type": "git", 1920 | "url": "https://github.com/symfony/string.git", 1921 | "reference": "f8a1ccebd0997e16112dfecfd74220b78e5b284b" 1922 | }, 1923 | "dist": { 1924 | "type": "zip", 1925 | "url": "https://api.github.com/repos/symfony/string/zipball/f8a1ccebd0997e16112dfecfd74220b78e5b284b", 1926 | "reference": "f8a1ccebd0997e16112dfecfd74220b78e5b284b", 1927 | "shasum": "" 1928 | }, 1929 | "require": { 1930 | "php": ">=8.1", 1931 | "symfony/polyfill-ctype": "~1.8", 1932 | "symfony/polyfill-intl-grapheme": "~1.0", 1933 | "symfony/polyfill-intl-normalizer": "~1.0", 1934 | "symfony/polyfill-mbstring": "~1.0" 1935 | }, 1936 | "conflict": { 1937 | "symfony/translation-contracts": "<2.5" 1938 | }, 1939 | "require-dev": { 1940 | "symfony/error-handler": "^5.4|^6.0|^7.0", 1941 | "symfony/http-client": "^5.4|^6.0|^7.0", 1942 | "symfony/intl": "^6.2|^7.0", 1943 | "symfony/translation-contracts": "^2.5|^3.0", 1944 | "symfony/var-exporter": "^5.4|^6.0|^7.0" 1945 | }, 1946 | "type": "library", 1947 | "autoload": { 1948 | "files": [ 1949 | "Resources/functions.php" 1950 | ], 1951 | "psr-4": { 1952 | "Symfony\\Component\\String\\": "" 1953 | }, 1954 | "exclude-from-classmap": [ 1955 | "/Tests/" 1956 | ] 1957 | }, 1958 | "notification-url": "https://packagist.org/downloads/", 1959 | "license": [ 1960 | "MIT" 1961 | ], 1962 | "authors": [ 1963 | { 1964 | "name": "Nicolas Grekas", 1965 | "email": "p@tchwork.com" 1966 | }, 1967 | { 1968 | "name": "Symfony Community", 1969 | "homepage": "https://symfony.com/contributors" 1970 | } 1971 | ], 1972 | "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", 1973 | "homepage": "https://symfony.com", 1974 | "keywords": [ 1975 | "grapheme", 1976 | "i18n", 1977 | "string", 1978 | "unicode", 1979 | "utf-8", 1980 | "utf8" 1981 | ], 1982 | "support": { 1983 | "source": "https://github.com/symfony/string/tree/v6.4.12" 1984 | }, 1985 | "funding": [ 1986 | { 1987 | "url": "https://symfony.com/sponsor", 1988 | "type": "custom" 1989 | }, 1990 | { 1991 | "url": "https://github.com/fabpot", 1992 | "type": "github" 1993 | }, 1994 | { 1995 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1996 | "type": "tidelift" 1997 | } 1998 | ], 1999 | "time": "2024-09-20T08:15:52+00:00" 2000 | }, 2001 | { 2002 | "name": "symfony/yaml", 2003 | "version": "v6.4.12", 2004 | "source": { 2005 | "type": "git", 2006 | "url": "https://github.com/symfony/yaml.git", 2007 | "reference": "762ee56b2649659380e0ef4d592d807bc17b7971" 2008 | }, 2009 | "dist": { 2010 | "type": "zip", 2011 | "url": "https://api.github.com/repos/symfony/yaml/zipball/762ee56b2649659380e0ef4d592d807bc17b7971", 2012 | "reference": "762ee56b2649659380e0ef4d592d807bc17b7971", 2013 | "shasum": "" 2014 | }, 2015 | "require": { 2016 | "php": ">=8.1", 2017 | "symfony/deprecation-contracts": "^2.5|^3", 2018 | "symfony/polyfill-ctype": "^1.8" 2019 | }, 2020 | "conflict": { 2021 | "symfony/console": "<5.4" 2022 | }, 2023 | "require-dev": { 2024 | "symfony/console": "^5.4|^6.0|^7.0" 2025 | }, 2026 | "bin": [ 2027 | "Resources/bin/yaml-lint" 2028 | ], 2029 | "type": "library", 2030 | "autoload": { 2031 | "psr-4": { 2032 | "Symfony\\Component\\Yaml\\": "" 2033 | }, 2034 | "exclude-from-classmap": [ 2035 | "/Tests/" 2036 | ] 2037 | }, 2038 | "notification-url": "https://packagist.org/downloads/", 2039 | "license": [ 2040 | "MIT" 2041 | ], 2042 | "authors": [ 2043 | { 2044 | "name": "Fabien Potencier", 2045 | "email": "fabien@symfony.com" 2046 | }, 2047 | { 2048 | "name": "Symfony Community", 2049 | "homepage": "https://symfony.com/contributors" 2050 | } 2051 | ], 2052 | "description": "Loads and dumps YAML files", 2053 | "homepage": "https://symfony.com", 2054 | "support": { 2055 | "source": "https://github.com/symfony/yaml/tree/v6.4.12" 2056 | }, 2057 | "funding": [ 2058 | { 2059 | "url": "https://symfony.com/sponsor", 2060 | "type": "custom" 2061 | }, 2062 | { 2063 | "url": "https://github.com/fabpot", 2064 | "type": "github" 2065 | }, 2066 | { 2067 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 2068 | "type": "tidelift" 2069 | } 2070 | ], 2071 | "time": "2024-09-17T12:47:12+00:00" 2072 | }, 2073 | { 2074 | "name": "twig/twig", 2075 | "version": "v3.14.0", 2076 | "source": { 2077 | "type": "git", 2078 | "url": "https://github.com/twigphp/Twig.git", 2079 | "reference": "126b2c97818dbff0cdf3fbfc881aedb3d40aae72" 2080 | }, 2081 | "dist": { 2082 | "type": "zip", 2083 | "url": "https://api.github.com/repos/twigphp/Twig/zipball/126b2c97818dbff0cdf3fbfc881aedb3d40aae72", 2084 | "reference": "126b2c97818dbff0cdf3fbfc881aedb3d40aae72", 2085 | "shasum": "" 2086 | }, 2087 | "require": { 2088 | "php": ">=8.0.2", 2089 | "symfony/deprecation-contracts": "^2.5|^3", 2090 | "symfony/polyfill-ctype": "^1.8", 2091 | "symfony/polyfill-mbstring": "^1.3", 2092 | "symfony/polyfill-php81": "^1.29" 2093 | }, 2094 | "require-dev": { 2095 | "psr/container": "^1.0|^2.0", 2096 | "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" 2097 | }, 2098 | "type": "library", 2099 | "autoload": { 2100 | "files": [ 2101 | "src/Resources/core.php", 2102 | "src/Resources/debug.php", 2103 | "src/Resources/escaper.php", 2104 | "src/Resources/string_loader.php" 2105 | ], 2106 | "psr-4": { 2107 | "Twig\\": "src/" 2108 | } 2109 | }, 2110 | "notification-url": "https://packagist.org/downloads/", 2111 | "license": [ 2112 | "BSD-3-Clause" 2113 | ], 2114 | "authors": [ 2115 | { 2116 | "name": "Fabien Potencier", 2117 | "email": "fabien@symfony.com", 2118 | "homepage": "http://fabien.potencier.org", 2119 | "role": "Lead Developer" 2120 | }, 2121 | { 2122 | "name": "Twig Team", 2123 | "role": "Contributors" 2124 | }, 2125 | { 2126 | "name": "Armin Ronacher", 2127 | "email": "armin.ronacher@active-4.com", 2128 | "role": "Project Founder" 2129 | } 2130 | ], 2131 | "description": "Twig, the flexible, fast, and secure template language for PHP", 2132 | "homepage": "https://twig.symfony.com", 2133 | "keywords": [ 2134 | "templating" 2135 | ], 2136 | "support": { 2137 | "issues": "https://github.com/twigphp/Twig/issues", 2138 | "source": "https://github.com/twigphp/Twig/tree/v3.14.0" 2139 | }, 2140 | "funding": [ 2141 | { 2142 | "url": "https://github.com/fabpot", 2143 | "type": "github" 2144 | }, 2145 | { 2146 | "url": "https://tidelift.com/funding/github/packagist/twig/twig", 2147 | "type": "tidelift" 2148 | } 2149 | ], 2150 | "time": "2024-09-09T17:55:12+00:00" 2151 | } 2152 | ], 2153 | "packages-dev": [], 2154 | "aliases": [], 2155 | "minimum-stability": "stable", 2156 | "stability-flags": { 2157 | "fzaninotto/faker": 0, 2158 | "twig/twig": 0, 2159 | "symfony/yaml": 0 2160 | }, 2161 | "prefer-stable": false, 2162 | "prefer-lowest": false, 2163 | "platform": [], 2164 | "platform-dev": [], 2165 | "platform-overrides": { 2166 | "php": "8.1.0" 2167 | }, 2168 | "plugin-api-version": "2.6.0" 2169 | } 2170 | --------------------------------------------------------------------------------