├── .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$level>");
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 |
--------------------------------------------------------------------------------