diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml new file mode 100644 index 0000000..e69de29 diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..4b39e40 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,7 @@ +parameters: + level: 0 + paths: + - src + treatPhpDocTypesAsCertain: false + scanDirectories: + - ../../ \ No newline at end of file diff --git a/src/Api/Admin.php b/src/Api/Admin.php new file mode 100644 index 0000000..27ef88f --- /dev/null +++ b/src/Api/Admin.php @@ -0,0 +1,1622 @@ +di['db']->find('service_proxmox_server'); + $servers_grouped = array(); + + // Iterate through each server + foreach ($servers as $server) { + // Find all virtual machines (VMs) on this server and calculate CPU cores and RAM usage + $vms = $this->di['db']->find('service_proxmox', 'server_id=:id', array(':id' => $server->id)); + $server_cpu_cores = 0; + $server_ram = 0; + + // Count the number of VMs + $vm_count = 0; + foreach ($vms as $vm) { + // Sum up the CPU cores and RAM of each VM + $server_cpu_cores += $vm->cpu_cores; + $server_ram += $vm->ram; + $vm_count++; + } + + // Calculate the percentage of RAM usage if the server's RAM is not zero + if ($server->ram != 0) { + $server_ram_percent = round($server_ram / $server->ram * 100, 0, PHP_ROUND_HALF_DOWN); + } else { + $server_ram_percent = 0; + } + + // Retrieve the overprovisioning factor from the extension's configuration and calculate overprovisioned CPU cores + $config = $this->di['mod_config']('Serviceproxmox'); + $overprovion_percent = $config['cpu_overprovisioning']; + $cpu_cores_overprovision = $server->cpu_cores + round($server->cpu_cores * $overprovion_percent / 100, 0, PHP_ROUND_HALF_DOWN); + + // Retrieve the RAM overprovisioning factor from the extension's configuration and calculate overprovisioned RAM + $ram_overprovion_percent = $config['ram_overprovisioning']; + $ram_overprovision = round($server->ram / 1024 / 1024 / 1024, 0, PHP_ROUND_HALF_DOWN) + round(round($server->ram / 1024 / 1024 / 1024, 0, PHP_ROUND_HALF_DOWN) * $ram_overprovion_percent / 100, 0, PHP_ROUND_HALF_DOWN); + + // Store the server's group information in the grouped servers array + $servers_grouped[$server['group']]['group'] = $server->group; + + // Prepare the grouped Server's array for the Template to render. + $servers_grouped[$server['group']]['servers'][$server['id']] = array( + 'id' => $server->id, + 'name' => $server->name, + 'group' => $server->group, + 'ipv4' => $server->ipv4, + 'hostname' => $server->hostname, + 'port' => $server->port, + 'vm_count' => $vm_count, + 'access' => $this->getService()->find_access($server), + 'cpu_cores' => $server->cpu_cores, + 'cpu_cores_allocated' => $server->cpu_cores_allocated, + 'cpu_cores_overprovision' => $cpu_cores_overprovision, + 'cpu_cores_provisioned' => $server_cpu_cores, + 'ram_provisioned' => $server_ram, + 'ram_overprovision' => $ram_overprovision, + 'ram_used' => round($server->ram_allocated / 1024 / 1024 / 1024, 0, PHP_ROUND_HALF_DOWN), // TODO: Make nicer? + 'ram' => round($server->ram / 1024 / 1024 / 1024, 0, PHP_ROUND_HALF_DOWN), // TODO: Make nicer? + 'ram_percent' => $server_ram_percent, + 'active' => $server->active, + ); + } + return $servers_grouped; + } + + /** + * Get list of storage + * + * @return array + */ + public function storage_get_list($data) + { + $storages = $this->di['db']->find('service_proxmox_storage'); + $storages_grouped = array(); + foreach ($storages as $storage) { + $server = $this->di['db']->getExistingModelById('service_proxmox_server', $storage->server_id, 'Server not found'); + switch ($storage->type) { + case 'local': + $storage->type = 'Local'; + break; + case 'nfs': + $storage->type = 'NFS'; + break; + case 'dir': + $storage->type = 'Directory'; + break; + case 'iscsi': + $storage->type = 'iSCSI'; + break; + case 'lvm': + $storage->type = 'LVM'; + break; + case 'lvmthin': + $storage->type = 'LVM thinpool'; + break; + case 'rbd': + $storage->type = 'Ceph'; + break; + case 'sheepdog': + $storage->type = 'Sheepdog'; + break; + case 'glusterfs': + $storage->type = 'GlusterFS'; + break; + case 'cephfs': + $storage->type = 'CephFS'; + break; + case 'zfs': + $storage->type = 'ZFS'; + break; + case 'zfspool': + $storage->type = 'ZFS Pool'; + break; + case 'iscsidirect': + $storage->type = 'iSCSI Direct'; + break; + case 'drbd': + $storage->type = 'DRBD'; + break; + case 'dev': + $storage->type = 'Device'; + break; + case 'pbs': + $storage->type = 'Proxmox Backup Server'; + break; + } + $storages_grouped[$storage['type']]['group'] = $storage->type; + // Map storage group types to better descriptions for display + // TODO: Add translations + + + $storages_grouped[$storage['type']]['storages'][$storage['id']] = array( + 'id' => $storage->id, + 'servername' => $server->name, + 'storageclass' => $storage->storageclass, + 'name' => $storage->storage, + 'content' => $storage->content, + 'type' => $storage->type, + 'active' => $storage->active, + 'size' => $storage->size, + 'used' => $storage->used, + 'free' => $storage->free, + 'percent_used' => ($storage->size == 0 || $storage->used == 0 || $storage->free == 0) ? 0 : round($storage->used / $storage->size * 100, 2), + ); + + } + return $storages_grouped; + } + + /** + * Get list of storageclasses + * + * @return array + */ + + public function storageclass_get_list($data) + { + $storageclasses = $this->di['db']->find('service_proxmox_storageclass'); + return $storageclasses; + } + + /** + * Get list of storage controllers + * + * @return array + */ + public function storage_controller_get_list($data) + { + // Return Array of storage controllers: + // lsi | lsi53c810 | virtio-scsi-pci | virtio-scsi-single | megasas | pvscsi + $storage_controllers = array( + 'lsi' => 'LSI', + 'lsi53c810' => 'LSI 53C810', + 'virtio-scsi-pci' => 'VirtIO SCSI PCI', + 'virtio-scsi-single' => 'VirtIO SCSI Single', + 'megasas' => 'MegaSAS', + 'pvscsi' => 'PVSCSI', + 'sata' => 'SATA', + 'ide' => 'IDE', + ); + return $storage_controllers; + } + + /** * + * Get list of Active Services + * + * @return array + */ + public function service_proxmox_get_list($data) + { + $services = $this->di['db']->find('service_proxmox'); + return $services; + } + + /** + * Create a new storageclass + * + * @return array + */ + public function storageclass_create($data) + { + $storageclass = $this->di['db']->dispense('service_proxmox_storageclass'); + $storageclass->storageclass = $data['storageClassName']; + $this->di['db']->store($storageclass); + return $storageclass; + } + + /** + * Retrieve a single storageclass + * + * @return array + */ + public function storageclass_get($data) + { + $storageclass = $this->di['db']->getExistingModelById('service_proxmox_storageclass', $data['id'], 'Storageclass not found'); + return $storageclass; + } + + /** + * Get list of server groups + * + * @return array + */ + public function server_groups() + { + $sql = "SELECT DISTINCT `group` FROM `service_proxmox_server` WHERE `active` = 1"; + $groups = $this->di['db']->getAll($sql); + return $groups; + } + + /** + * get list of servers in a group + * + */ + public function servers_in_group($data) + { + $sql = "SELECT * FROM `service_proxmox_server` WHERE `group` = '" . $data['group'] . "' AND `active` = 1"; + + $servers = $this->di['db']->getAll($sql); + + // remove password & api keys from results + foreach ($servers as $key => $server) { + $servers[$key]['root_password'] = ''; + $servers[$key]['tokenvalue'] = ''; + } + + return $servers; + } + + /** + * Get list of qemu templates for a server + * + */ + public function qemu_templates_on_server($data) + { + $sql = "SELECT * FROM `service_proxmox_qemu_template` WHERE `server_id` = '" . $data['server_id'] . "'"; + $templates = $this->di['db']->getAll($sql); + return $templates; + } + + + /** + * Get list of OS types + * + * @return array + */ + + public function os_get_list() + { + $os_types = array( + 'other' => 'Other', + 'wxp' => 'Windows XP', + 'w2k' => 'Windows 2000', + 'w2k3' => 'Windows 2003', + 'w2k8' => 'Windows 2008', + 'wvista' => 'Windows Vista', + 'win7' => 'Windows 7', + 'win8' => 'Windows 8', + 'win10' => 'Windows 10', + 'win11' => 'Windows 11', + 'l24' => 'Linux 2.4 Kernel', + 'l26' => 'Linux 2.6 Kernel', + 'solaris' => 'Solaris', + ); + return $os_types; + } + + /** + * Get list of BIOS types + * + * @return array + */ + public function bios_get_list() + { + $bios_types = array( + 'seabios' => 'SeaBIOS', + 'ovmf' => 'OVMF (UEFI)', + ); + return $bios_types; + } + + /** + * Get list of VNC types + * + * @return array + */ + public function lxc_appliance_get_list() + { + // get all service_proxmox_lxc_appliances + $lxc_appliance = $this->di['db']->find('service_proxmox_lxc_appliance'); + // sort alphabetically by headline + usort($lxc_appliance, function ($a, $b) { + return strcmp($a->headline, $b->headline); + }); + return $lxc_appliance; + } + + // Function to get list of lxc config templates + public function get_lxc_config_template() + { + $lxc_tmpl = $this->di['db']->find('service_proxmox_lxc_config_template'); + return $lxc_tmpl; + } + + // Function to enable qemu template + public function vm_config_template_enable($data) + { + error_log("vm_config_template_enable: " . $data['id']); + $vm_config_template = $this->di['db']->getExistingModelById('service_proxmox_vm_config_template', $data['id'], 'VM Config Template not found'); + $vm_config_template->state = 'active'; + $this->di['db']->store($vm_config_template); + return $vm_config_template; + } + + // Function to disable qemu template + public function vm_config_template_disable($data) + { + error_log("vm_config_template_disable: " . $data['id']); + $vm_config_template = $this->di['db']->getExistingModelById('service_proxmox_vm_config_template', $data['id'], 'VM Config Template not found'); + $vm_config_template->state = 'inactive'; + $this->di['db']->store($vm_config_template); + return $vm_config_template; + } + /* ################################################################################################### */ + /* ########################################## Servers ############################################## */ + /* ################################################################################################### */ + + /** + * Create new hosting server + * + * @param string $name - server name + * @param string $ipv4 - server ipv4 + * @param string $hostname - server hostname + * @param string $port - server port + * @param string $root_user - server root user + * @param string $root_password - server root password + * @param string $realm - server realm + * + * @return bool - server id + * + * @throws \Box_Exception + */ + public function server_create($data) + { + // enable api token & secret + $required = array( + 'name' => 'Server name is missing', + 'ipv4' => 'Server ipv4 is missing', + 'hostname' => 'Server hostname is missing', + 'port' => 'Server port is missing', + 'auth_type' => 'Authentication type is missing', + 'realm' => 'Proxmox user realm is missing', + ); + $this->di['validator']->checkRequiredParamsForArray($required, $data); + + // check if server already exists based on name, ipv4 or hostname + $server = $this->di['db']->findOne('service_proxmox_server', 'name=:name OR ipv4=:ipv4 OR hostname=:hostname', array(':name' => $data['name'], ':ipv4' => $data['ipv4'], ':hostname' => $data['hostname'])); + if ($server) { + throw new \Box_Exception('Server already exists'); + } + + $server = $this->di['db']->dispense('service_proxmox_server'); + $server->name = $data['name']; + $server->group = $data['group']; + $server->ipv4 = $data['ipv4']; + $server->ipv6 = $data['ipv6']; + $server->hostname = $data['hostname']; + $server->port = $data['port']; + $server->realm = $data['realm']; + $server->active = $data['active']; + $server->created_at = date('Y-m-d H:i:s'); + $server->updated_at = date('Y-m-d H:i:s'); + + $this->di['db']->store($server); + $this->di['logger']->info('Created Proxmox server %s', $server->id); + + // check if auth_type is username or token + if ($data['auth_type'] == 'username') { + $server->root_user = $data['root_user']; + $server->root_password = $data['root_password']; + $server->tokenname = ''; + $server->tokenvalue = ''; + $this->di['db']->store($server); + $this->getService()->test_connection($server); + } else { + $server->root_user = ''; + $server->root_password = ''; + $server->tokenname = $data['tokenname']; + $server->tokenvalue = $data['tokenvalue']; + $this->di['db']->store($server); + + } + + + // Validate server by testing connection + + + + return true; + } + + /** + * Get server details + * + * @param int $id - server id + * @return array + * + * @throws \Box_Exception + */ + public function server_get($data) + { + // Retrieve associated server + $server = $this->di['db']->findOne('service_proxmox_server', 'id=:id', array(':id' => $data['server_id'])); + if (!$server) { + throw new \Box_Exception('Server not found'); + } + + $output = array( + 'id' => $server->id, + 'name' => $server->name, + 'group' => $server->group, + 'ipv4' => $server->ipv4, + 'ipv6' => $server->ipv6, + 'hostname' => $server->hostname, + 'port' => $server->port, + 'realm' => $server->realm, + 'tokenname' => $server->tokenname, + 'tokenvalue' => str_repeat("*", 26), + 'root_user' => $server->root_user, + 'root_password' => $server->root_password, + 'admin_password' => $server->admin_password, + 'active' => $server->active, + ); + return $output; + } + + /** + * Update server configuration + * + * @param int $id - server id + * @param string $name - server name + * @param string $ipv4 - server ipv4 + * @param string $hostname - server hostname + * @param string $port - server port + * @param string $root_user - server root user + * @param string $realm - server realm + * + * @return bool + * @throws \Box_Exception + */ + public function server_update($data) + { + $required = array( + 'name' => 'Server name is missing', + 'root_user' => 'Root user is missing', + 'ipv4' => 'Server ipv4 is missing', + 'hostname' => 'Server hostname is missing', + 'port' => 'Server port is missing', + 'realm' => 'Proxmox user realm is missing', + ); + $this->di['validator']->checkRequiredParamsForArray($required, $data); + + $server = $this->di['db']->findOne('service_proxmox_server', 'id=:id', array(':id' => $data['server_id'])); + + $server->name = $data['name']; + $server->group = $data['group']; + $server->ipv4 = $data['ipv4']; + $server->ipv6 = $data['ipv6']; + $server->hostname = $data['hostname']; + $server->port = $data['port']; + $server->realm = $data['realm']; + $server->cpu_cores = $data['cpu_cores']; + $server->ram = $data['ram']; + $server->root_user = $data['root_user']; + $server->root_password = $data['root_password']; + $server->tokenname = $data['tokenname']; + $server->config = $data['config']; + $server->active = $data['active']; + $server->created_at = date('Y-m-d H:i:s'); + $server->updated_at = date('Y-m-d H:i:s'); + + $this->di['db']->store($server); + + $this->di['logger']->info('Update Proxmox server %s', $server->id); + + return true; + } + + /** + * Delete server + * + * @param int $id - server id + * @return bool + * @throws \Box_Exception + */ + public function server_delete($data) + { + $required = array( + 'id' => 'Server id is missing', + ); + $this->di['validator']->checkRequiredParamsForArray($required, $data); + + // check if there are services provisioned on this server + $vms = $this->di['db']->find('service_proxmox', 'server_id=:server_id', array(':server_id' => $data['id'])); + + // if there are vms provisioned on this server, throw an exception + if (!empty($vms)) { + throw new \Box_Exception('VMs are still provisioned on this server'); + } else { + // delete storages + $storages = $this->di['db']->find('service_proxmox_storage', 'server_id=:server_id', array(':server_id' => $data['id'])); + foreach ($storages as $storage) { + $this->di['db']->trash($storage); + } + + // delete server + $server = $this->di['db']->getExistingModelById('service_proxmox_server', $data['id'], 'Server not found'); + $this->di['db']->trash($server); + } + } + + /** + * Get server details from order id + * This is used to manage the service from the order. + * TODO: Remove this function and use server_get instead + * @param int $order_id + * @return array + */ + + public function server_get_from_order($data) + { + $required = array( + 'order_id' => 'Order id is missing', + ); + + $this->di['validator']->checkRequiredParamsForArray($required, $data); + + $service = $this->di['db']->findOne( + 'service_proxmox', + "order_id=:id", + array(':id' => $data['order_id']) + ); + + if (!$service) { + return null; + } + + $data = array('server_id' => $service['server_id']); + $output = $this->server_get($data); + return $output; + } + + + /** + * Receive Hardware Data from proxmox server + * + * @param int $server_id + * @return array + */ + public function get_hardware_data($server_id) + { + $server = $this->di['db']->getExistingModelById('service_proxmox_server', $server_id, 'Server not found'); + $service = $this->getService(); + $hardware_data = $service->getHardwareData($server); + $server->cpu_cores = $hardware_data['cpuinfo']['cores']; + $server->ram = $hardware_data['memory']['total']; + + $serverstorage = $service->getStorageData($server); + + foreach ($serverstorage as $key => $value) { + $sql = "SELECT * FROM `service_proxmox_storage` WHERE server_id = " . $server_id . " AND storage = '" . $value['storage'] . "'"; + $storage = $this->di['db']->getAll($sql); + + // if the storage exists, update it, otherwise create it + if (!empty($storage)) { + $storage = $this->di['db']->findOne('service_proxmox_storage', 'server_id=:server_id AND storage=:storage', array(':server_id' => $server_id, ':storage' => $value['storage'])); + } else { + $storage = $this->di['db']->dispense('service_proxmox_storage'); + } + + $storage->server_id = $server_id; + $storage->storage = $value['storage']; + $storage->type = $value['type']; + $storage->content = $value['content']; + + $storage->used = $value['used'] / 1000 / 1000 / 1000; + $storage->size = $value['total'] / 1000 / 1000 / 1000; + $storage->free = $value['avail'] / 1000 / 1000 / 1000; + + $storage->active = $value['active']; + $this->di['db']->store($storage); + } + $this->di['db']->store($server); + $allresources = $service->getAssignedResources($server); + // summarzie the fields cpus and maxmem for each vm and store it in the server table + $server->cpu_cores_allocated = 0; + $server->ram_allocated = 0; + foreach ($allresources as $key => $value) { + $server->cpu_cores_allocated += $value['cpus']; + $server->ram_allocated += $value['maxmem']; + } + $this->di['db']->store($server); + $qemu_templates = $service->getQemuTemplates($server); + error_log('qemu_templates: ' . print_r($qemu_templates, true)); + foreach ($qemu_templates as $key => $value) { + // check if $value['template'] exists, and if it's content is 1 + if (!empty($value['template'])) { + if ($value['template'] == 1) { + $sql = "SELECT * FROM `service_proxmox_qemu_template` WHERE server_id = " . $server_id . " AND vmid = " . $value['vmid']; + $template = $this->di['db']->getAll($sql); + + // if the template exists, update it, otherwise create it + if (!empty($template)) { + $template = $this->di['db']->findOne('service_proxmox_qemu_template', 'server_id=:server_id AND vmid=:vmid', array(':server_id' => $server_id, ':vmid' => $value['vmid'])); + } else { + $template = $this->di['db']->dispense('service_proxmox_qemu_template'); + } + $template->vmid = $value['vmid']; + $template->server_id = $server_id; + $template->name = $value['name']; + $template->created_at = date('Y-m-d H:i:s'); + $template->updated_at = date('Y-m-d H:i:s'); + + $stored = $this->di['db']->store($template); + error_log('template saved: ' . print_r($stored, true)); + } + } + } + + return $hardware_data; + } + + + /** + * Test connection to server + * + * @param int $id - server id + * + * @return bool + * @throws \Box_Exception + */ + public function server_test_connection($data) + { + + $required = array( + 'id' => 'Server id is missing', + ); + $this->di['validator']->checkRequiredParamsForArray($required, $data); + + $server = $this->di['db']->getExistingModelById('service_proxmox_server', $data['id'], 'Server not found'); + + if ($this->getService()->test_connection($server)) { + $this->get_hardware_data($data['id']); + return true; + } else { + return false; + } + } + + /** + * Prepare Server for PVE + * + * @param int $id - server id + * + * @return bool + * @throws \Box_Exception + */ + public function server_prepare_pve_setup($data) + { + $required = array( + 'id' => 'Server id is missing', + ); + $this->di['validator']->checkRequiredParamsForArray($required, $data); + + $server = $this->di['db']->getExistingModelById('service_proxmox_server', $data['id'], 'Server not found'); + $updatedserver = $this->getService()->prepare_pve_setup($server); + if ($updatedserver) { + $this->di['db']->store($updatedserver); + return true; + } else { + return false; + } + } + + /** + * Test access to server + * This method can be removed at a later date as it is primarily a debugging tool. + * @param int $id - server id + * + * @return bool + * @throws \Box_Exception + */ + public function test_access($data) + { + $required = array( + 'id' => 'Server id is missing', + ); + $this->di['validator']->checkRequiredParamsForArray($required, $data); + + $server = $this->di['db']->getExistingModelById('service_proxmox_server', $data['id'], 'Server not found'); + + if ($this->getService()->test_access($server)) { + return true; + } else { + return false; + } + } + + + /** + * Get all available templates from any proxmox server + * + * @return array + * @throws \Box_Exception + */ + public function pull_lxc_appliances() + { + $appliances = $this->getService()->getAvailableAppliances(); + + + foreach ($appliances as $appliance) { + // check if the appliance already exists + //$appliance = $this->di['db']->findOne('service_proxmox_lxc_appliance', 'sha512sum=:sha512sum', array(':sha512sum' => $appliance['sha512sum'])); + // if the appliance exists, update it, otherwise create it + + $template = $this->di['db']->dispense('service_proxmox_lxc_appliance'); + $template->headline = $appliance['headline']; + $template->package = $appliance['package']; + $template->section = $appliance['section']; + $template->type = $appliance['type']; + $template->source = $appliance['source']; + $template->headline = $appliance['headline']; + $template->location = $appliance['location']; + // if description is empty, use the headline + if (empty($appliance['description'])) { + $appliance['description'] = $appliance['headline']; + } + $template->description = $appliance['description']; + $template->template = $appliance['template']; + $template->os = $appliance['os']; + $template->infopage = $appliance['infopage']; + $template->version = $appliance['version']; + $template->sha512sum = $appliance['sha512sum']; + $template->architecture = $appliance['architecture']; + $this->di['db']->store($template); + } + return true; + } + + /* ################################################################################################### */ + /* ########################################## Storage ############################################## */ + /* ################################################################################################### */ + + + /** + * Get a storage + * + * @param int $id - storage id + * + * @return array + * @throws \Box_Exception + */ + public function storage_get($data) + { + // Retrieve associated storage + $storage = $this->di['db']->findOne('service_proxmox_storage', 'id=:id', array(':id' => $data['storage_id'])); + $server = $this->di['db']->getExistingModelById('service_proxmox_server', $storage->server_id, 'Server not found'); + $output = array( + 'id' => $storage->id, + 'name' => $storage->name, + 'server_id' => $storage->server_id, + 'storageclass' => $storage->storageclass, + 'storage' => $storage->storage, + 'content' => $storage->content, + 'type' => $storage->type, + 'active' => $storage->active, + 'size' => $storage->size, + 'used' => $storage->used, + 'free' => $storage->free, + 'percent_used' => ($storage->size == 0 || $storage->used == 0 || $storage->free == 0) ? 0 : round($storage->used / $storage->size * 100, 2), + // add list of storage classes + 'storageclasses' => $this->storageclass_get_list($data), + 'server_name' => $server->name, + ); + + return $output; + } + + + /** + * Update a storage with storageclasses + * TODO: Implement & Fix functionality + * @param int $id - server id + * + * @return array + * @throws \Box_Exception + */ + public function storage_update($data) + { + $required = array( + 'storageid' => 'Storage id is missing', + 'storageTypeTags' => 'Storage tags are missing', + ); + $this->di['validator']->checkRequiredParamsForArray($required, $data); + + // Retrieve associated storage + $storage = $this->di['db']->findOne('service_proxmox_storage', 'id=:id', array(':id' => $data['storageid'])); + + // $data['storageTypeTags']; contains an array of tags like this: Array([0] => ssd,hdd,sdf) + // This needs to be split up and stored as valid json in the storage table + error_log('storageTypeTags: ' . print_r($data['storageTypeTags'], true)); + // Assuming you have $data['storagetype'] populated + $storageType = $data['storagetype']; + $tagArray = []; + foreach ($data['storageTypeTags'] as $tag) { + $splitTags = explode(',', $tag); + $tagArray = array_merge($tagArray, $splitTags); // Flat array of tags + } + + // for every tag in the tagArray, save the id to the error_log + $jsonArray = []; + foreach ($tagArray as $tagId) { + // Fetch the tag details from DB + $tag_from_db = $this->di['db']->findOne('service_proxmox_tag', 'id=:id AND type=:type', [ + ':id' => $tagId, + ':type' => $storageType + ]); + + if ($tag_from_db) { + $jsonArray[] = [ + 'id' => $tag_from_db->id, + 'name' => $tag_from_db->name + ]; + } else { + error_log("No DB entry found for tagId: $tagId and type: $storageType"); + } + } + + $jsonString = json_encode($jsonArray); + $storage->storageclass = $jsonString; + $this->di['db']->store($storage); + return true; + } + + + /** + * Update Product + * + * @param array $data + * + * @return bool + * @throws \Box_Exception + */ + public function product_update($data) + { + $required = array( + 'id' => 'Product id is missing', + 'group' => 'Server group is missing', + 'filling' => 'Filling method is missing', + 'show_stock' => 'Stock display (Show Stock) is missing', + 'server' => 'Server is missing', + 'virt' => 'Virtualization type is missing', + + ); + + $this->di['validator']->checkRequiredParamsForArray($required, $data); + + // Check if virt is lxc or qemu, and if lxc, check if lxc-templ is set. if qemu, check if vm-templ is set. + if ($data['virt'] == 'lxc' && empty($data['lxc-templ'])) { + throw new \Box_Exception('LXC Template is missing'); + } elseif ($data['virt'] == 'qemu' && empty($data['vm-templ'])) { + throw new \Box_Exception('VM Template is missing'); + } + + // Retrieve associated product + $product = $this->di['db']->findOne('product', 'id=:id', array(':id' => $data['id'])); + + $config = array( + 'group' => $data['group'], + 'filling' => $data['filling'], + 'show_stock' => $data['show_stock'], + 'virt' => $data['virt'], + 'server' => $data['server'], + 'lxc-templ' => $data['lxc-templ'], + 'vm-templ' => $data['vm-templ'], + 'vmconftempl' => $data['vmconftempl'], + ); + + $product->config = json_encode($config); + $product->updated_at = date('Y-m-d H:i:s'); + $this->di['db']->store($product); + + $this->di['logger']->info('Update Proxmox product %s', $product->id); + return true; + } + + /* ################################################################################################### */ + /* #################################### Resource Management ######################################## */ + /* ################################################################################################### */ + + /** + * Get list of vm templates + * + * @return array + */ + public function service_get_vmtemplates() + { + $output = $this->getService()->get_vmtemplates(); + return $output; + } + + /** + * Get list of qemu templates + * + * @return array + */ + public function service_get_qemutemplates() + { + $output = $this->getService()->get_qemutemplates(); + return $output; + } + + /** + * Get list of lxc templates + * + * @return array + */ + public function service_get_lxctemplates() + { + $output = $this->getService()->get_lxctemplates(); + return $output; + } + + /** + * Get list of ip ranges + * + * @return array + */ + public function service_get_ip_ranges() + { + $output = $this->getService()->get_ip_ranges(); + return $output; + } + + /** + * Get list of ip adresses + * + */ + public function service_get_ip_adresses() + { + $output = $this->getService()->get_ip_adresses(); + return $output; + } + + /** + * Get list of vlans + * + * @return array + */ + public function service_get_vlans() + { + $output = $this->getService()->get_vlans(); + return $output; + } + + /** + * Get list of tags by type + * + * @return array + */ + public function service_get_tags($data) + { + $output = $this->getService()->get_tags($data); + return $output; + } + + public function service_add_tag($data) + { + $output = $this->getService()->save_tag($data); + return $output; + } + + /** + * Get list of tags by storage id + * + * @return array + */ + public function service_get_tags_by_storage($data) + { + $output = $this->getService()->get_tags_by_storage($data); + error_log("service_get_tags_by_storage: " . print_r($output, true)); + return $output; + } + + /** + * Get a vm configuration templates + * + * @return array + */ + public function vm_config_template_get($data) + { + error_log("vm_config_template_get"); + $vm_config_template = $this->di['db']->findOne('service_proxmox_vm_config_template', 'id=:id', array(':id' => $data['id'])); + if (!$vm_config_template) { + throw new \Box_Exception('VM template not found'); + } + $output = array( + 'id' => $vm_config_template->id, + 'name' => $vm_config_template->name, + 'cores' => $vm_config_template->cores, + 'description' => $vm_config_template->description, + 'memory' => $vm_config_template->memory, + 'balloon' => $vm_config_template->balloon, + 'balloon_size' => $vm_config_template->balloon_size, + 'os' => $vm_config_template->os, + 'bios' => $vm_config_template->bios, + 'onboot' => $vm_config_template->onboot, + 'agent' => $vm_config_template->agent, + 'created_at' => $vm_config_template->created_at, + 'updated_at' => $vm_config_template->updated_at, + ); + + return $output; + } + + /** + * Function to get storages for vm config template + * + * @return array + */ + + public function vm_config_template_get_storages($data) + { + error_log("Get list of storages for VM Template: " . print_r($data, true)); + $vm_config_template = $this->di['db']->find('service_proxmox_vm_storage_template', 'template_id=:id', array(':id' => $data['id'])); + //replace the storage_type with the name of the tag + foreach ($vm_config_template as $key => $value) { + $storage_tag_id = json_decode($value->storage_type); + error_log("storage_tag_id: " . print_r($storage_tag_id, true)); + $tag = $this->di['db']->findOne('service_proxmox_tag', 'id=:id', array(':id' => $storage_tag_id)); + $vm_config_template[$key]->storage_type = $tag->name; + } + return $vm_config_template; + } + + /** + * Get list of lxc configuration templates + * + * @return array + */ + public function lxc_config_template_get($data) + { + $lxc_config_template = $this->di['db']->findOne('service_proxmox_lxc_config_template', 'id=:id', array(':id' => $data['id'])); + if (!$lxc_config_template) { + throw new \Box_Exception('LXC template not found'); + } + + $output = array( + 'id' => $lxc_config_template->id, + 'name' => $lxc_config_template->name, + 'template_id' => $lxc_config_template->template_id, + 'cores' => $lxc_config_template->cores, + 'description' => $lxc_config_template->description, + 'memory' => $lxc_config_template->memory, + 'swap' => $lxc_config_template->swap, + 'ostemplate' => $lxc_config_template->ostemplate, + 'onboot' => $lxc_config_template->onboot, + 'created_at' => $lxc_config_template->created_at, + 'updated_at' => $lxc_config_template->updated_at, + ); + + return $output; + } + + + /** + * Get ip range + * + * @return array + */ + public function ip_range_get($data) + { + $ip_range = $this->di['db']->findOne('service_proxmox_ip_range', 'id=:id', array(':id' => $data['id'])); + if (!$ip_range) { + throw new \Box_Exception('IP range not found'); + } + $output = array( + 'id' => $ip_range->id, + 'cidr' => $ip_range->cidr, + 'gateway' => $ip_range->gateway, + 'broadcast' => $ip_range->broadcast, + 'type' => $ip_range->type, + 'created_at' => $ip_range->created_at, + 'updated_at' => $ip_range->updated_at, + ); + + return $output; + } + + /** + * Get vlan + */ + public function vlan_get($data) + { + $vlan = $this->di['db']->findOne('service_proxmox_client_vlan', 'id=:id', array(':id' => $data['id'])); + if (!$vlan) { + throw new \Box_Exception('VLAN not found'); + } + + // fill client_name field + $client = $this->di['db']->findOne('client', 'id=:id', array(':id' => $vlan->client_id)); + if (!$client) { + throw new \Box_Exception('Client not found'); + } + + // get IP Range cidr + $iprange = $this->di['db']->findOne('service_proxmox_ip_range', 'id=:id', array(':id' => $vlan->ip_range)); + $output = array( + 'id' => $vlan->id, + 'client_id' => $vlan->client_id, + 'client_name' => $client->first_name . " " . $client->last_name, + 'vlan' => $vlan->vlan, + 'ip_range' => $vlan->ip_range, + 'cidr' => $iprange->cidr, + 'created_at' => $vlan->created_at, + 'updated_at' => $vlan->updated_at, + ); + + return $output; + } + + + /** + * Create vm configuration template + * + * @return bool + */ + public function vm_config_template_create($data) + { + $required = array( + 'name' => 'Server name is missing', + ); + + $this->di['validator']->checkRequiredParamsForArray($required, $data); + + // dispense new vm_config_template + $vm_config_template = $this->di['db']->dispense('service_proxmox_vm_config_template'); + // Fill vm_config_template + $vm_config_template->name = $data['name']; + $vm_config_template->state = "draft"; + + $this->di['db']->store($vm_config_template); + + + $this->di['logger']->info('Create VM config Template %s', $vm_config_template->id); + return $vm_config_template; + } + + /** + * Update vm configuration template + * + * @return bool + */ + public function vm_template_update($data) + { + $required = array( + 'name' => 'Server name is missing', + 'description' => 'Server description is missing', + 'cpu_cores' => 'CPU cores are missing', + 'vmmemory' => 'memory is missing', + 'balloon' => 'Balloon is missing', + 'os' => 'OS is missing', + 'bios' => 'Bios Type is missing', + 'onboot' => 'Start on Boot is missing', + 'agent' => 'Run Agent is missing' + ); + + $this->di['validator']->checkRequiredParamsForArray($required, $data); + error_log("vm_template_update: " . print_r($data, true)); + // Retrieve associated vm_config_template + $vm_config_template = $this->di['db']->findOne('service_proxmox_vm_config_template', 'id=:id', array(':id' => $data['id'])); + + // Fill vm_config_template + $vm_config_template->name = $data['name']; + $vm_config_template->description = $data['description']; + $vm_config_template->cores = $data['cpu_cores']; + $vm_config_template->memory = $data['vmmemory']; + $vm_config_template->balloon = $data['balloon']; + // if $data['ballon'] is true, check if $data['ballon_size'] is set, otherwise use $data['memory'] + if ($data['balloon'] == true) { + if (isset($data['balloon_size'])) { + $vm_config_template->balloon_size = $data['balloon_size']; + } else { + $vm_config_template->balloon_size = $data['memory']; + } + } + $vm_config_template->os = $data['os']; + $vm_config_template->bios = $data['bios']; + $vm_config_template->onboot = $data['onboot']; + $vm_config_template->agent = $data['agent']; + $vm_config_template->updated_at = date('Y-m-d H:i:s'); + $this->di['db']->store($vm_config_template); + + $this->di['logger']->info('Update VM config Template %s', $vm_config_template->id); + return true; + } + + /** + * Delete vm configuration template + * + * @return bool + */ + public function vm_template_delete($id) + { + $vm_config_template = $this->di['db']->findOne('service_proxmox_vm_config_template', 'id = ?', [$id]); + + // TODO: Check if vm_config_template is used by any product + + $this->di['db']->trash($vm_config_template); + $this->di['logger']->info('Delete VM config Template %s', $id); + return true; + } + + /** + * Create lxc configuration template + * + * @return bool + */ + public function lxc_template_create($data) + { + $required = array( + 'description' => 'Template description is missing', + 'cpu_cores' => 'CPU cores are missing', + 'memory' => 'memory is missing', + 'swap' => 'Swap is missing', + 'ostemplate' => 'OS template is missing', + 'onboot' => 'Start on Boot is missing', + ); + + $this->di['validator']->checkRequiredParamsForArray($required, $data); + + // dispense new vm_config_template + $lxc_config_template = $this->di['db']->dispense('service_proxmox_lxc_config_template'); + // Fill vm_config_template + $lxc_config_template->description = $data['description']; + $lxc_config_template->cores = $data['cpu_cores']; + $lxc_config_template->memory = $data['memory']; + $lxc_config_template->swap = $data['swap']; + $lxc_config_template->template_id = $data['ostemplate']; + // get os template headline from $di['db'] and set it to $lxc_config_template->ostemplate + $ostemplate = $this->di['db']->findOne('service_proxmox_lxc_appliance', 'id = ?', [$data['ostemplate']]); + $lxc_config_template->ostemplate = $ostemplate->headline; + $lxc_config_template->onboot = $data['onboot']; + $lxc_config_template->created_at = date('Y-m-d H:i:s'); + $lxc_config_template->updated_at = date('Y-m-d H:i:s'); + $this->di['db']->store($lxc_config_template); + + $this->di['db']->store($lxc_config_template); + + + $this->di['logger']->info('Create LXC config Template %s', $lxc_config_template->id); + return true; + } + + + /** + * Update lxc configuration template + * + * @return bool + */ + public function lxc_template_update($data) + { + $required = array( + 'id' => 'Template ID is missing', + 'description' => 'Template description is missing', + 'name' => 'Server name is missing', + 'cpu_cores' => 'CPU cores are missing', + 'memory' => 'memory is missing', + 'swap' => 'Swap is missing', + 'ostemplate' => 'OS template is missing', + 'onboot' => 'Start on Boot is missing', + ); + + $this->di['validator']->checkRequiredParamsForArray($required, $data); + + // Retrieve associated lxc_config_template + $lxc_config_template = $this->di['db']->findOne('service_proxmox_lxc_config_template', 'id=:id', array(':id' => $data['id'])); + + // Fill lxc_config_template + $lxc_config_template->description = $data['description']; + $lxc_config_template->name = $data['name']; + $lxc_config_template->cores = $data['cpu_cores']; + $lxc_config_template->memory = $data['memory']; + $lxc_config_template->swap = $data['swap']; + $lxc_config_template->template_id = $data['ostemplate']; + // get os template headline from $di['db'] and set it to $lxc_config_template->ostemplate + $ostemplate = $this->di['db']->findOne('service_proxmox_lxc_appliance', 'id = ?', [$data['ostemplate']]); + $lxc_config_template->ostemplate = $ostemplate->headline; + $lxc_config_template->onboot = $data['onboot']; + $lxc_config_template->updated_at = date('Y-m-d H:i:s'); + $this->di['db']->store($lxc_config_template); + + $this->di['logger']->info('Update LXC config Template %s', $lxc_config_template->id); + return true; + } + + /** + * Delete lxc configuration template + * + * @return bool + */ + public function lxc_template_delete($id) + { + $lxc_config_template = $this->di['db']->findOne('service_proxmox_lxc_config_template', 'id = ?', [$id]); + $this->di['db']->trash($lxc_config_template); + $this->di['logger']->info('Delete LXC config Template %s', $id); + return true; + } + + /** + * Create VM Config Template Storage + * + * @return bool + */ + public function vm_config_template_storage_create($data) + { + $required = array( + 'template_id' => 'Template ID is missing', + 'storage_size' => 'Storage size is missing', + 'storage_type_tags' => 'Storage type is missing', + 'storage_controller' => 'Storage controller is missing', + ); + + $this->di['validator']->checkRequiredParamsForArray($required, $data); + + + // dispense new vm_config_template_storage + $vm_config_template_storage = $this->di['db']->dispense('service_proxmox_vm_storage_template'); + // Fill vm_config_template_storage + $vm_config_template_storage->template_id = $data['template_id']; + $vm_config_template_storage->size = $data['storage_size']; + $vm_config_template_storage->storage_type = json_encode($data['storage_type_tags']); + $vm_config_template_storage->controller = $data['storage_controller']; + $vm_config_template_storage->created_at = date('Y-m-d H:i:s'); + $vm_config_template_storage->updated_at = date('Y-m-d H:i:s'); + $this->di['db']->store($vm_config_template_storage); + + } + + + + /** + * Create ip range + * + * @return bool + */ + public function ip_range_create($data) + { + $required = array( + 'cidr' => 'CIDR is missing', + 'gateway' => 'Gateway is missing', + 'broadcast' => 'Broadcast is missing', + 'type' => 'Type is missing', + ); + + $this->di['validator']->checkRequiredParamsForArray($required, $data); + + // dispense new ip_network + $ip_range = $this->di['db']->dispense('service_proxmox_ip_range'); + // Fill ip_network + $ip_range->cidr = $data['cidr']; + $ip_range->gateway = $data['gateway']; + $ip_range->broadcast = $data['broadcast']; + $ip_range->type = $data['type']; + $ip_range->created_at = date('Y-m-d H:i:s'); + $ip_range->updated_at = date('Y-m-d H:i:s'); + $this->di['db']->store($ip_range); + + + $this->di['logger']->info('Create IP Network %s', $ip_range->id); + return true; + } + + /** + * Update ip range + * + * @return bool + */ + public function ip_range_update($data) + { + $required = array( + 'id' => 'ID is missing', + 'cidr' => 'CIDR is missing', + 'gateway' => 'Gateway is missing', + 'broadcast' => 'Broadcast is missing', + 'type' => 'Type is missing', + ); + + $this->di['validator']->checkRequiredParamsForArray($required, $data); + + // Retrieve associated ip_network + $ip_range = $this->di['db']->findOne('service_proxmox_ip_range', 'id=:id', array(':id' => $id)); + + // Fill ip_network + $ip_range->cidr = $data['cidr']; + $ip_range->gateway = $data['gateway']; + $ip_range->broadcast = $data['broadcast']; + $ip_range->type = $data['type']; + $ip_range->updated_at = date('Y-m-d H:i:s'); + $this->di['db']->store($ip_range); + + $this->di['logger']->info('Update IP Network %s', $ip_range->id); + return true; + } + + /** + * Delete ip range + * + * @param int $id + * @return array + */ + public function ip_range_delete($id) + { + $ip_network = $this->di['db']->findOne('service_proxmox_ip_range', 'id = ?', [$id]); + $this->di['db']->trash($ip_network); + $this->di['logger']->info('Delete IP Network %s', $id); + return true; + } + + + /** + * Create client vlan + * + * @return bool + */ + public function client_vlan_create($data) + { + $required = array( + 'client_id' => 'Client ID is missing', + 'vlan' => 'VLAN ID is missing', + 'ip_range' => 'IP_range is missing', + ); + + $this->di['validator']->checkRequiredParamsForArray($required, $data); + + // dispense new client_network + $client_network = $this->di['db']->dispense('service_proxmox_client_vlan'); + // Fill client_network + $client_network->client_id = $data['client_id']; + $client_network->vlan = $data['vlan']; + $client_network->ip_range = $data['ip_range']; + $client_network->created_at = date('Y-m-d H:i:s'); + $client_network->updated_at = date('Y-m-d H:i:s'); + $this->di['db']->store($client_network); + + + $this->di['logger']->info('Create Client Network %s', $client_network->id); + return true; + } + + /** + * Update client vlan + * + * @return bool + */ + public function client_vlan_update($data) + { + $required = array( + 'id' => 'ID is missing', + 'client_id' => 'Client ID is missing', + 'vlan' => 'VLAN ID is missing', + 'ip_range' => 'ip_range is missing', + ); + + $this->di['validator']->checkRequiredParamsForArray($required, $data); + + // Retrieve associated client_network + $client_network = $this->di['db']->findOne('service_proxmox_client_vlan', 'id=:id', array(':id' => $data['id'])); + + // Fill client_network + $client_network->client_id = $data['client_id']; + $client_network->vlan = $data['vlan']; + $client_network->ip_range = $data['ip_range']; + $client_network->updated_at = date('Y-m-d H:i:s'); + $this->di['db']->store($client_network); + + $this->di['logger']->info('Update Client Network %s', $client_network->id); + return true; + } + + + /** + * Delete client vlanx + * + * @param int $id + * @return bool + */ + public function client_vlan_delete($data) + { + $client_network = $this->di['db']->findOne('service_proxmox_client_vlan', 'id = ?', [$data['id']]); + $this->di['db']->trash($client_network); + $this->di['logger']->info('Delete Client Network %s', $id); + return true; + } + + /* ################################################################################################### */ + /* ######################################## Permissions ############################################ */ + /* ################################################################################################### */ + + + /* ################################################################################################### */ + /* ######################################## Maintenance ############################################ */ + /* ################################################################################################### */ + + /** + * Backup Module Configuration Tables + * + * @return bool + */ + public function proxmox_backup_config($data) + { + if ($this->getService()->pmxdbbackup($data)) { + return true; + } else { + return false; + } + } + + // Function to list backups + /** + * List existing Backups + * + * @return array + */ + public function proxmox_list_backups() + { + $output = $this->getService()->pmxbackuplist(); + return $output; + } + + /** + * Restore Backup + * + * @return bool + */ + public function proxmox_restore_backup($data) + { + $this->proxmox_backup_config('backup'); + $this->di['logger']->info('Restoring Proxmox server Backup: %s', $data['backup']); + if ($this->getService()->pmxbackuprestore($data)) { + return true; + } else { + return false; + } + } + + /** + * Get module version + * + * @return string + */ + public function get_module_version() + { + $config = $this->di['mod_config']('Serviceproxmox'); + return $config['version']; + } + +} // EOF \ No newline at end of file diff --git a/src/Api/Client.php b/src/Api/Client.php new file mode 100644 index 0000000..069fa8a --- /dev/null +++ b/src/Api/Client.php @@ -0,0 +1,220 @@ +getService()->getServiceproxmoxByOrderId($data['order_id']); + + return $this->getService()->customCall($model, $name, $data); + } + + /** + * Get server details + * + * @param int $id - server id + * @return array + * + * @throws \Box_Exception + */ + public function vm_get($data) + { + $required = array( + 'order_id' => 'Order ID is missing', + ); + $this->di['validator']->checkRequiredParamsForArray($required, $data); + + $order = $this->di['db']->findOne( + 'client_order', + "id=:id", + array(':id' => $data['order_id']) + ); + if (!$order) { + throw new \Box_Exception('Order not found'); + } + + $service = $this->di['db']->findOne( + 'service_proxmox', + "order_id=:id", + array(':id' => $data['order_id']) + ); + if (!$service) { + throw new \Box_Exception('Proxmox service not found'); + } + + // Retrieve associated + $server = $this->di['db']->findOne('service_proxmox_server', 'id=:id', array(':id' => $service['server_id'])); + + // if a server has been found, output its details, otherwise return an empty array + if (!$server) { + return array(); + } + $vm_info = $this->getService()->vm_info($order, $service); + $output = array( + 'server' => $server->hostname, + 'username' => 'root', + 'cli' => $this->getService()->vm_cli($order, $service), + 'status' => $vm_info['status'], + ); + return $output; + } + + public function server_get($data) + { + $required = array( + 'order_id' => 'Order ID is missing', + ); + $this->di['validator']->checkRequiredParamsForArray($required, $data); + + $order = $this->di['db']->findOne( + 'client_order', + "id=:id", + array(':id' => $data['order_id']) + ); + if (!$order) { + throw new \Box_Exception('Order not found'); + } + + $service = $this->di['db']->findOne( + 'service_proxmox', + "order_id=:id", + array(':id' => $data['order_id']) + ); + if (!$service) { + throw new \Box_Exception('Proxmox service not found'); + } + + // Retrieve associated + $server = $this->di['db']->findOne('service_proxmox_server', 'id=:id', array(':id' => $service['server_id'])); + + // if a server has been found, output its details, otherwise return an empty array + if (!$server) { + return array(); + } + return $server; + } + + // function to return proxmox_service information from order + public function get_proxmox_service($data) + { + $required = array( + 'order_id' => 'Order ID is missing', + ); + $this->di['validator']->checkRequiredParamsForArray($required, $data); + + $order = $this->di['db']->findOne( + 'client_order', + "id=:id", + array(':id' => $data['order_id']) + ); + if (!$order) { + throw new \Box_Exception('Order not found'); + } + + $service = $this->di['db']->findOne( + 'service_proxmox', + "order_id=:id", + array(':id' => $data['order_id']) + ); + if (!$service) { + throw new \Box_Exception('Proxmox service not found'); + } + return $service; + } + + + /** + * Reboot vm + */ + public function vm_manage($data) + { + $required = array( + 'order_id' => 'Order ID is missing', + 'method' => 'Method is missing', + ); + $this->di['validator']->checkRequiredParamsForArray($required, $data); + + $order = $this->di['db']->findOne( + 'client_order', + "id=:id", + array(':id' => $data['order_id']) + ); + if (!$order) { + throw new \Box_Exception('Order not found'); + } + + $service = $this->di['db']->findOne( + 'service_proxmox', + "order_id=:id", + array(':id' => $data['order_id']) + ); + if (!$service) { + throw new \Box_Exception('Proxmox service not found'); + } + + // Retrieve associated server + $server = $this->di['db']->findOne('service_proxmox_server', 'id=:id', array(':id' => $service['server_id'])); + + switch ($data['method']) { + case 'reboot': + $this->getService()->vm_reboot($order, $service); + break; + case 'start': + $this->getService()->vm_start($order, $service); + break; + case 'shutdown': + $this->getService()->vm_shutdown($order, $service); + default: + return false; + } + + return true; + } + + /** + * Get VNC console from PVE Host + */ + public function novnc_appjs_get($data) + { + $appjs = $this->getService()->get_novnc_appjs($data); + return $appjs; + } +} diff --git a/src/Controller/Admin.php b/src/Controller/Admin.php new file mode 100644 index 0000000..24752a7 --- /dev/null +++ b/src/Controller/Admin.php @@ -0,0 +1,228 @@ +di = $di; + } + + public function getDi(): ?\Pimple\Container + { + return $this->di; + } + + /** + * Fetches the navigation array for the admin area + * + * @return array + */ + public function fetchNavigation() + { + return array( + 'group' => array( + 'index' => 550, + 'location' => 'proxmox', + 'label' => __trans('Proxmox'), + 'class' => 'server', + 'sprite_class' => 'dark-sprite-icon sprite-graph', + ), + 'subpages' => array( + [ + 'location' => 'proxmox', + 'label' => __trans('Proxmox Servers'), + 'uri' => $this->di['url']->adminLink('serviceproxmox'), + 'index' => 100, + 'class' => '', + ], + [ + 'location' => 'proxmox', + 'label' => __trans('Proxmox Templates'), + 'uri' => $this->di['url']->adminLink('serviceproxmox/templates'), + 'index' => 200, + 'class' => '', + ], + [ + 'location' => 'proxmox', + 'label' => __trans('IP Address Management'), + 'uri' => $this->di['url']->adminLink('serviceproxmox/ipam'), + 'index' => 300, + 'class' => '', + ], + ), + ); + } + + /** + * Registers the admin area routes + * + */ + public function register(\Box_App &$app) + { + $app->get('/serviceproxmox', 'get_index', null, get_class($this)); + $app->get('/serviceproxmox/templates', 'get_templates', null, get_class($this)); + $app->get('/serviceproxmox/ipam', 'get_ipam', null, get_class($this)); + $app->get('/serviceproxmox/maintenance/backup', 'start_backup', null, get_class($this)); + $app->get('/serviceproxmox/server/:id', 'get_server', array('id' => '[0-9]+'), get_class($this)); + $app->get('/serviceproxmox/storage', 'get_storage', null, get_class($this)); + $app->get('/serviceproxmox/server/by_group/:id', 'get_server_by_group', array('id' => '[0-9]+'), get_class($this)); + $app->get('/serviceproxmox/storage/:id', 'get_storage', array('id' => '[0-9]+'), get_class($this)); + $app->get('/serviceproxmox/storageclass', 'get_storage', null, get_class($this)); + $app->get('/serviceproxmox/storageclass/:id', 'get_storageclass ', array('id' => '[0-9]+'), get_class($this)); + $app->get('/serviceproxmox/ipam/iprange', 'get_ip_range', null, get_class($this)); + $app->get('/serviceproxmox/ipam/iprange/:id', 'get_ip_range', array('id' => '[0-9]+'), get_class($this)); + $app->get('/serviceproxmox/ipam/client_vlan', 'get_client_vlan', null, get_class($this)); + $app->get('/serviceproxmox/ipam/client_vlan/:id', 'get_client_vlan', array('id' => '[0-9]+'), get_class($this)); + $app->get('/serviceproxmox/templates/lxc_config', 'get_lxc_config_template', null, get_class($this)); + $app->get('/serviceproxmox/templates/lxc_config/:id', 'get_lxc_config_template', array('id' => '[0-9]+'), get_class($this)); + $app->get('/serviceproxmox/templates/vm_config', 'get_vm_config_template', null, get_class($this)); + $app->get('/serviceproxmox/templates/vm_config/:id', 'get_vm_config_template', array('id' => '[0-9]+'), get_class($this)); + $app->get('/serviceproxmox/templates/enable/:id', 'enable_template', array('id' => '[0-9]+'), get_class($this)); + $app->get('/serviceproxmox/templates/disable/:id', 'disable_template', array('id' => '[0-9]+'), get_class($this)); + } + + /** + * Renders the admin area index page + */ + public function get_index(\Box_App $app) + { + $this->di['is_admin_logged']; + return $app->render('mod_serviceproxmox_index'); + } + + /** + * Renders the admin area templates page + */ + public function get_templates(\Box_App $app) + { + return $app->render('mod_serviceproxmox_templates'); + } + + /** + * Enables the QEMU Template + * + */ + public function enable_template(\Box_App $app, $id) + { + $api = $this->di['api_admin']; + $api->Serviceproxmox_vm_config_template_enable(array('id' => $id)); + return $app->redirect('serviceproxmox/templates'); + } + + /** + * Disables the QEMU Template + * + */ + public function disable_template(\Box_App $app, $id) + { + $api = $this->di['api_admin']; + $api->Serviceproxmox_vm_config_template_disable(array('id' => $id)); + return $app->redirect('serviceproxmox/templates'); + } + /** + * Renders the admin area ipam page + */ + public function get_ipam(\Box_App $app) + { + return $app->render('mod_serviceproxmox_ipam'); + } + + /** + * Handles CRUD for Proxmox Servers + */ + public function get_server(\Box_App $app, $id) + { + $api = $this->di['api_admin']; + $server = $api->Serviceproxmox_server_get(array('server_id' => $id)); + return $app->render('mod_serviceproxmox_server', array('server' => $server)); + } + + public function get_server_by_group(\Box_App $app, $id) + { + $api = $this->di['api_admin']; + $server = $api->Serviceproxmox_servers_in_group(array('group' => $id)); + return $app->render('mod_serviceproxmox_server', array('server' => $server)); + } + + /** + * Handles CRUD for Proxmox Storage + */ + public function get_storage(\Box_App $app, $id) + { + $api = $this->di['api_admin']; + $storage = $api->Serviceproxmox_storage_get(array('storage_id' => $id)); + return $app->render('mod_serviceproxmox_storage', array('storage' => $storage)); + } + + /** + * Handles CRUD for IP Range + */ + public function get_ip_range(\Box_App $app, $id) + { + $api = $this->di['api_admin']; + $ip_range = $api->Serviceproxmox_ip_range_get(array('id' => $id)); + return $app->render('mod_serviceproxmox_ipam_iprange', array('ip_range' => $ip_range)); + } + + /** + * Handles CRUD for Client VLAN + */ + public function client_vlan(\Box_App $app, $id) + { + $api = $this->di['api_admin']; + $client_vlan = $api->Serviceproxmox_vlan_get(array('id' => $id)); + return $app->render('mod_serviceproxmox_ipam_client_vlan', ['client_vlan' => $client_vlan]); + } + + /** + * Handles CRUD for LXC Config Templates + */ + public function get_lxc_config_template(\Box_App $app, $id) + { + $api = $this->di['api_admin']; + $lxc_config_template = $api->Serviceproxmox_lxc_config_template_get(array('id' => $id)); + return $app->render('mod_serviceproxmox_templates_lxc', array('lxc_config_template' => $lxc_config_template)); + } + + /** + * Handles CRUD for VM Config Templates + */ + public function get_vm_config_template(\Box_App $app, $id) + { + error_log("Controller get_vm_config_template"); + $api = $this->di['api_admin']; + $vm_config_template = $api->Serviceproxmox_vm_config_template_get(array('id' => $id)); + return $app->render('mod_serviceproxmox_templates_qemu', array('vm_config_template' => $vm_config_template)); + } + + /** + * Renders the admin area settings page + */ + public function start_backup(\Box_App $app) + { + + $api = $this->di['api_admin']; + $backup = $api->Serviceproxmox_proxmox_backup_config('backup'); + return $app->redirect('extension/settings/serviceproxmox'); + } +} diff --git a/src/Controller/Client.php b/src/Controller/Client.php new file mode 100644 index 0000000..b7fcdf0 --- /dev/null +++ b/src/Controller/Client.php @@ -0,0 +1,80 @@ +di = $di; + } + + public function getDi(): ?\Pimple\Container + { + return $this->di; + } + + public function register(\Box_App &$app) + { + // register all routers to load novnc app.js & dependencies from proxmox + $app->get('/serviceproxmox/novnc/:filename.:fileending', 'get_novnc_appjs_filename', [], static::class); + $app->get('/serviceproxmox/novnc/:folder/:filename.:fileending', 'get_novnc_appjs_folder_filename', [], static::class); + $app->get('/serviceproxmox/novnc/:folder/:subfolder/:filename.:fileending', 'get_novnc_appjs_folder_subfolder_filename', [], static::class); + } + + // create functions to call get_novnc_appjs with paths + // create function for only filename + public function get_novnc_appjs_filename(\Box_App $app, $filename, $fileending) + { + $file_path = $filename . '.' . $fileending; + return $this->get_novnc_appjs($app, $file_path); + } + // create function for filename and folder + public function get_novnc_appjs_folder_filename(\Box_App $app, $folder, $filename, $fileending) + { + $file_path = $folder . '/' . $filename . '.' . $fileending; + return $this->get_novnc_appjs($app, $file_path); + } + // create function for filename, folder and subfolder + public function get_novnc_appjs_folder_subfolder_filename(\Box_App $app, $folder, $subfolder, $filename, $fileending) + { + $file_path = $folder . '/' . $subfolder . '/' . $filename . '.' . $fileending; + return $this->get_novnc_appjs($app, $file_path); + } + + + // create get_novnc_appjs function + public function get_novnc_appjs(\Box_App $app, $file_path) + { + $api = $this->di['api_client']; + // print out $file; + // build path + $request_response = $api->Serviceproxmox_novnc_appjs_get($file_path); + // get content and content type from response + $content = $request_response->getContent(); + $content_headers = $request_response->getHeaders(); + + header("Content-type: " . $content_headers['Content-Type']); + // replace every occurence of /novnc/ with /serviceproxmox/novnc/ + $content = str_replace('/novnc/', '/serviceproxmox/novnc/', $content); + return $content; + } +} diff --git a/src/ProxmoxAuthentication.php b/src/ProxmoxAuthentication.php new file mode 100644 index 0000000..44bae86 --- /dev/null +++ b/src/ProxmoxAuthentication.php @@ -0,0 +1,283 @@ +di['mod_config']('Serviceproxmox'); + // Retrieve the server access information + $serveraccess = $this->find_access($server); + + // Create a new instance of the PVE2_API class with the server access details + $proxmox = new PVE2_API($serveraccess, $server->root_user, $server->realm, $server->root_password, port: $server->port, tokenid: $server->tokenname, tokensecret: $server->tokenvalue, debug: $config['pmx_debug_logging']); + + // Attempt to log in to the server using the API + if (!$proxmox->login()) { + throw new \Box_Exception("Failed to connect to the server."); + } + + // Create an API token for the Admin user if not logged in via API token + if (empty($server->tokenname) || empty($server->tokenvalue)) { + // Check if the connecting user has the 'Realm.AllocateUser' permission + $permissions = $proxmox->get("/access/permissions"); + $found_permission = 0; + // Iterate through the permissions and check for 'Realm.AllocateUser' permission + foreach ($permissions as $permission) { + if ($permission['Realm.AllocateUser'] == 1) { + $found_permission += 1; + } + } + // Throw an exception if the 'Realm.AllocateUser' permission is not found + if (!$found_permission) { + throw new \Box_Exception("User does not have 'Realm.AllocateUser' permission"); + } + + // Validate if there already is a group for fossbilling + $groups = $proxmox->get("/access/groups"); + $foundgroups = 0; + // Iterate through the groups and check for a group beginning with 'fossbilling' + foreach ($groups as $group) { + if (strpos($group['groupid'], 'fossbilling') === 0) { + $foundgroups += 1; + $groupid = $group['groupid']; + } + } + // Handle the cases where there are no groups, one group, or multiple groups + switch ($foundgroups) { + case 0: + // Create a new group + $groupid = 'fossbilling_' . rand(1000, 9999); + $newgroup = $proxmox->post("/access/groups", array('groupid' => $groupid, 'comment' => 'fossbilling group')); + break; + case 1: + // Use the existing group + break; + default: + throw new \Box_Exception("More than one group found"); + break; + } + + + // Validate if there already is a user and token for fossbilling + $users = $proxmox->get("/access/users"); + $found = 0; + // Iterate through the users and check for a user beginning with 'fb' + foreach ($users as $user) { + if (strpos($user['userid'], 'fb') === 0) { + $found += 1; + $userid = $user['userid']; + } + // Handle the cases where there are no users, one user, or multiple users + switch ($found) { + case 0: + // Create a new user + $userid = 'fb_' . rand(1000, 9999) . '@pve'; // TODO: Make realm configurable in the module settings + $newuser = $proxmox->post("/access/users", array('userid' => $userid, 'password' => $this->di['tools'], 'enable' => 1, 'comment' => 'fossbilling user', 'groups' => $groupid)); + + // Create a token for the new user + $token = $proxmox->post("/access/users/" . $userid . "/token/fb_access", array()); + + // Check if the token was created successfully + if ($token) { + $server->tokenname = $token['full-tokenid']; + $server->tokenvalue = $token['value']; + } else { + throw new \Box_Exception("Failed to create token for fossbilling user"); + break; + } + break; + case 1: + // Create a token for the existing user + $token = $proxmox->post("/access/users/" . $userid . "/token/fb_access", array()); + if ($token) { + $server->tokenname = $token['full-tokenid']; + $server->tokenvalue = $token['value']; + } else { + throw new \Box_Exception("Failed to create token for fossbilling user"); + break; + } + break; + default: + throw new \Box_Exception("There are more than one fossbilling users on the server. Please delete all but one."); + break; + } + // Create permissions for the newly created token + // Set up permissions for the token (Admin user) to manage users, groups, and other administrative tasks + $permissions = $proxmox->put("/access/acl/", array('path' => '/', 'roles' => 'PVEUserAdmin', 'propagate' => 1, 'users' => $userid)); + $permissions = $proxmox->put("/access/acl/", array('path' => '/', 'roles' => 'PVEAuditor', 'propagate' => 1, 'users' => $userid)); + $permissions = $proxmox->put("/access/acl/", array('path' => '/', 'roles' => 'PVESysAdmin', 'propagate' => 1, 'users' => $userid)); + $permissions = $proxmox->put("/access/acl/", array('path' => '/', 'roles' => 'PVEPoolAdmin', 'propagate' => 1, 'users' => $userid)); + $permissions = $proxmox->put("/access/acl/", array('path' => '/', 'roles' => 'PVEDatastoreAdmin', 'propagate' => 1, 'users' => $userid)); + $permissions = $proxmox->put("/access/acl/", array('path' => '/', 'roles' => 'PVEUserAdmin', 'propagate' => 1, 'tokens' => $server->tokenname)); + $permissions = $proxmox->put("/access/acl/", array('path' => '/', 'roles' => 'PVEAuditor', 'propagate' => 1, 'tokens' => $server->tokenname)); + $permissions = $proxmox->put("/access/acl/", array('path' => '/', 'roles' => 'PVESysAdmin', 'propagate' => 1, 'tokens' => $server->tokenname)); + $permissions = $proxmox->put("/access/acl/", array('path' => '/', 'roles' => 'PVEPoolAdmin', 'propagate' => 1, 'tokens' => $server->tokenname)); + $permissions = $proxmox->put("/access/acl/", array('path' => '/', 'roles' => 'PVEDatastoreAdmin', 'propagate' => 1, 'tokens' => $server->tokenname)); + + // Sleep for 5 seconds + sleep(5); + + // Check if the permissions were created correctly by logging in and creating another user + /* + echo ""; + echo ""; + echo ""; + echo ""; + echo "

"; + */ + + // Delete the root password and unset the PVE2_API instance + $server->root_password = null; + unset($proxmox); + + // Return the test_access result for the server + return $this->test_access($server); + } + } else { + // Validate Permissions for the token + $permissions = $proxmox->get("/access/acl/"); + // Check for 'PVEUserAdmin', 'PVEAuditor', 'PVESysAdmin', 'PVEPoolAdmin', and 'PVEDatastoreAdmin' permissions, and if they don't exist, try to create them. + $required_permissions = array('PVEUserAdmin', 'PVEAuditor', 'PVESysAdmin', 'PVEPoolAdmin', 'PVEDatastoreAdmin'); + foreach ($required_permissions as $permission) { + $found_permission = 0; + foreach ($permissions as $acl) { + if ($acl['roleid'] == $permission) { + $found_permission += 1; + } + } + if (!$found_permission) { + $permissions = $proxmox->put("/access/acl/", array('path' => '/', 'roles' => $permission, 'propagate' => 1, 'tokens' => $server->tokenname)); + } + } + + } + } + + + /** + * Tests the access to the server + * + * @param Server $server The server to test + * @return bool True if the test was successful, false otherwise + * @throws Box_Exception + */ + public function test_access($server) + { + // Retrieve the server access information + $serveraccess = $this->find_access($server); + $config = $this->di['mod_config']('Serviceproxmox'); + // Create a new instance of the PVE2_API class with the server access details + $proxmox = new PVE2_API($serveraccess, $server->root_user, $server->realm, $server->root_password, port: $server->port, tokenid: $server->tokenname, tokensecret: $server->tokenvalue, debug: $config['pmx_debug_logging']); + + // Attempt to log in to the server using the API + if (!$proxmox->login()) { + throw new \Box_Exception("Failed to connect to the server. testpmx"); + } + + // Generate a random test user ID + $userid = 'tfb_' . rand(1000, 9999) . '@pve'; // TODO: Make realm configurable in the module settings + + // Create a new user for testing purposes + $proxmox->post("/access/users", array('userid' => $userid, 'password' => $this->di['tools']->generatePassword(16, 4), 'enable' => '1', 'comment' => 'FOSSBilling test user ' . $userid)); + + // Retrieve the newly created user + $newuser = $proxmox->get("/access/users/" . $userid); + + // Check if the new user was successfully created + if (!$newuser) { + throw new \Box_Exception("Failed to create test user for fossbilling"); + } else { + // Delete the test user + $deleteuser = $proxmox->delete("/access/users/" . $userid); + + // Check if the test user was successfully deleted + $deleteuser = $proxmox->get("/access/users/" . $userid); + if ($deleteuser) { + throw new \Box_Exception("Failed to delete test user for fossbilling. Check permissions."); + } else { + // Remove the root password from the server object + $server->root_password = null; + return $server; + } + } + } + + + /** + * Creates a new client user on the server + * + * @param Server $server The server to create the user on + * @param Client $client The client to create the user for + * @return void + */ + public function create_client_user($server, $client) + { + $clientuser = $this->di['db']->dispense('service_proxmox_users'); + $clientuser->client_id = $client->id; + $this->di['db']->store($clientuser); + $serveraccess = $this->find_access($server); + $config = $this->di['mod_config']('Serviceproxmox'); + $proxmox = new PVE2_API($serveraccess, $server->root_user, $server->realm, $server->root_password, port: $server->port, tokenid: $server->tokenname, tokensecret: $server->tokenvalue, debug: $config['pmx_debug_logging']); + if (!$proxmox->login()) { + throw new \Box_Exception("Failed to connect to the server. create_client_user"); + } + $userid = 'fb_customer_' . $client->id . '@pve'; // TODO: Make realm configurable in the module settings + $newuser = $proxmox->post("/access/users", array('userid' => $userid, 'password' => $this->di['tools']->generatePassword(16, 4), 'enable' => '1', 'comment' => 'fossbilling user ' . $client->id)); + $newuser = $proxmox->get("/access/users/" . $userid); + + // Create Token for Client + $clientuser->admin_tokenname = 'fb_admin_' . $client->id; + $clientuser->server_id = $server->id; + $admintoken_response = $proxmox->post("/access/users/" . $userid . "/token/" . $clientuser->admin_tokenname, array('comment' => 'fossbilling admin token for client id: ' . $client->id)); + $clientuser->admin_tokenname = $admintoken_response['full-tokenid']; + $clientuser->admin_tokenvalue = $admintoken_response['value']; + $clientuser->view_tokenname = 'fb_view_' . $client->id; + $viewtoken_response = $proxmox->post("/access/users/" . $userid . "/token/" . $clientuser->view_tokenname, array('comment' => 'fossbilling view token for client id: ' . $client->id)); + $clientuser->view_tokenname = $viewtoken_response['full-tokenid']; + $clientuser->view_tokenvalue = $viewtoken_response['value']; + + + $this->di['db']->store($clientuser); + + // Check if the client already has a pool and if not create it. + $pool = $proxmox->get("/pools/" . $client->id); + if (!$pool) { + $pool = $proxmox->post("/pools", array('poolid' => 'fb_client_' . $client->id, 'comment' => 'fossbilling pool for client id: ' . $client->id)); + } + // Add permissions for client + + $permissions = $proxmox->put("/access/acl/", array('path' => '/pool/' . 'fb_client_' . $client->id, 'roles' => 'PVEVMUser,PVEVMAdmin,PVEDatastoreAdmin,PVEDatastoreUser', 'propagate' => 1, 'users' => $userid)); + + $permissions = $proxmox->put("/access/acl/", array('path' => '/pool/' . 'fb_client_' . $client->id, 'roles' => 'PVEVMUser,PVEDatastoreUser', 'propagate' => 1, 'tokens' => $clientuser->view_tokenname)); + + $permissions = $proxmox->put("/access/acl/", array('path' => '/pool/' . 'fb_client_' . $client->id, 'roles' => 'PVEVMAdmin,PVEDatastoreAdmin', 'propagate' => 1, 'tokens' => $clientuser->admin_tokenname)); + } +} diff --git a/src/ProxmoxIPAM.php b/src/ProxmoxIPAM.php new file mode 100644 index 0000000..5c1aa6a --- /dev/null +++ b/src/ProxmoxIPAM.php @@ -0,0 +1,63 @@ +di['db']->find('service_proxmox_ip_range'); + return $ip_ranges; + } + + // Function that gets all IP Adresses + public function get_ip_adresses() + { + // get all the VM templates from the service_proxmox_vm_config_template table + $ip_addresses = $this->di['db']->find('service_proxmox_ipadress'); + return $ip_addresses; + } + + // Function that gets all the LXC templates and returns them as an array + public function get_vlans() + { + // get all the LXC templates from the service_proxmox_lxc_config_template table + $vlans = $this->di['db']->find('service_proxmox_client_vlan'); + // Fill in the client name + foreach ($vlans as $vlan) { + $client = $this->di['db']->getExistingModelById('client', $vlan->client_id); + $vlan->client_name = $client->first_name . " " . $client->last_name; + } + + return $vlans; + } + + /* ################################################################################################### */ + /* ################################### Manage PVE Network ######################################### */ + /* ################################################################################################### */ +} diff --git a/src/ProxmoxServer.php b/src/ProxmoxServer.php new file mode 100644 index 0000000..840fb72 --- /dev/null +++ b/src/ProxmoxServer.php @@ -0,0 +1,260 @@ +find_access($server); + $config = $this->di['mod_config']('Serviceproxmox'); + $proxmox = new PVE2_API($serveraccess, $server->root_user, $server->realm, $server->root_password, port: $server->port, tokenid: $server->tokenname, tokensecret: $server->tokenvalue,debug: $config['pmx_debug_logging']); + // check if tokenname and tokenvalue contain values by checking their content + if (empty($server->tokenname) || empty($server->tokenvalue)) { + if (!empty($server->root_user) && !empty($server->root_password)) { + + if ($proxmox->login()) { + error_log("Serviceproxmox: Login with username and password successful"); + return true; + } else { + throw new \Box_Exception("Login to Proxmox Host failed"); + } + } else { + throw new \Box_Exception("No login information provided"); + } + } else if ($proxmox->getVersion()) { + error_log("Serviceproxmox: Login with token successful!"); + return true; + } else { + throw new \Box_Exception("Failed to connect to the server."); + } + } + + /* + Validate token access and setup + */ + public function test_token_connection($server) + { + // Test if login + $serveraccess = $this->find_access($server); + $config = $this->di['mod_config']('Serviceproxmox'); + $proxmox = new PVE2_API($serveraccess, $server->root_user, $server->realm, $server->root_password, port: $server->port, tokenid: $server->tokenname, tokensecret: $server->tokenvalue,debug: $config['pmx_debug_logging']); + // check if tokenname and tokenvalue contain values by checking their content + if (empty($server->tokenname) || empty($server->tokenvalue)) { + throw new \Box_Exception("Token Access Failed: No tokenname or tokenvalue provided"); + } else if ($proxmox->get_version()) { + error_log("Serviceproxmox: Login with token successful!"); + $permissions = $proxmox->get("/access/permissions"); + $found_permission = 0; + // Iterate through the permissions and check for 'Realm.AllocateUser' permission + foreach ($permissions as $permission) { + if ($permission['Realm.AllocateUser'] == 1) { + $found_permission += 1; + } + } + // Throw an exception if the 'Realm.AllocateUser' permission is not found + if (!$found_permission) { + throw new \Box_Exception("Token does not have 'Realm.AllocateUser' permission"); + } + + // Validate if there already is a group for fossbilling + $groups = $proxmox->get("/access/groups"); + $foundgroups = 0; + // Iterate through the groups and check for a group beginning with 'fossbilling' + foreach ($groups as $group) { + if (strpos($group['groupid'], 'fossbilling') === 0) { + $foundgroups += 1; + $groupid = $group['groupid']; + } + // check if groupid is the same as the id of the token (fb_1234@pve!fb_access) + $fb_token_instanceid = explode('@', $server->tokenname)[1]; + + + if ($group['groupid'] == $server->tokenname) { + $foundgroups += 1; + $groupid = $group['groupid']; + } + + } + return true; + } else { + throw new \Box_Exception("Failed to connect to the server."); + } + } + + + + /* Find best Server + */ + public function find_empty($product) + { + $productconfig = json_decode($product->config, 1); + $group = $productconfig['group']; + $filling = $productconfig['filling']; + + // retrieve overprovisioning information from extension settings + $config = $this->di['mod_config']('Serviceproxmox'); + $cpu_overprovion_percent = $config['cpu_overprovisioning']; + $ram_overprovion_percent = $config['ram_overprovisioning']; + $avoid_overprovision = $config['avoid_overprovision']; + + // Retrieve only non-full active servers sorted by ratio. + // priority is given to servers with the largest difference between ram and used ram + // if avoid_overprovision is set to true, servers with a ratio of >1 are ignored + $servers = $this->di['db']->find('service_proxmox_server', ['status' => 'active']); + //echo ""; + $server = null; + $server_ratio = 0; + // use values from database to calculate ratio and store the server id, cpu and ram usage ratio if it's better than the previous one + foreach ($servers as $s) { + $cpu_usage = $s['cpu_cores_allocated']; + $ram_usage = $s['ram_allocated']; + $cpu_cores = $s['cpu_cores']; + $ram = $s['ram']; + + $cpu_ratio = $cpu_usage / $cpu_cores; + $ram_ratio = $ram_usage / $ram; + + // if avoid_overprovision is set to true, servers with a ratio of >1 are ignored + if ($avoid_overprovision && ($cpu_ratio > 1 || $ram_ratio > 1)) { + continue; + } + // calculate ratio with overprovisioning + $cpu_ratio = $cpu_ratio * (1 + $cpu_overprovion_percent / 100); + $ram_ratio = $ram_ratio * (1 + $ram_overprovion_percent / 100); + // check current best ratio + if ($cpu_ratio + $ram_ratio > $server_ratio) { + $server_ratio = $cpu_ratio + $ram_ratio; + $server = $s['id']; + } + } + // if no server is found, return null + if ($server == null) { + return null; + } + // if a server is found, return the id + return $server; + } + + /* + Find access to server (hostname, ipv4, ipv6) + */ + public function find_access($server) + { + if (!empty($server->hostname)) { + return $server->hostname; + } else if (!empty($server->ipv4)) { + return $server->ipv4; + } else if (!empty($server->ipv6)) { + return $server->ipv6; + } else { + throw new \Box_Exception('No IPv6, IPv4 or Hostname found for server ' . $server->id); + } + } + + /* + Find server hardware usage information "getHardwareData" + */ + public function getHardwareData($server) + { + // Retrieve associated server + $serveraccess = $this->find_access($server); + $config = $this->di['mod_config']('Serviceproxmox'); + $proxmox = new PVE2_API($serveraccess, $server->root_user, $server->realm, $server->root_password, port: $server->port, tokenid: $server->tokenname, tokensecret: $server->tokenvalue,debug: $config['pmx_debug_logging']); + + if ($proxmox->login()) { + error_log("ProxmoxServer.php: getHardwareData: Login successful"); + $hardware = $proxmox->get("/nodes/" . $server->name . "/status"); + return $hardware; + } else { + throw new \Box_Exception("Failed to connect to the server. hw Token Access Failed"); + } + } + + public function getStorageData($server) + { + // Retrieve associated server + $serveraccess = $this->find_access($server); + $config = $this->di['mod_config']('Serviceproxmox'); + $proxmox = new PVE2_API($serveraccess, $server->root_user, $server->realm, $server->root_password, port: $server->port, tokenid: $server->tokenname, tokensecret: $server->tokenvalue,debug: $config['pmx_debug_logging']); + if ($proxmox->login()) { + $storage = $proxmox->get("/nodes/" . $server->name . "/storage"); + return $storage; + } else { + throw new \Box_Exception("Failed to connect to the server. st"); + } + } + + // function to get all assigned cpu_cores and ram on a server (used to find free resources) + public function getAssignedResources($server) + { + // Retrieve associated server + $serveraccess = $this->find_access($server); + $config = $this->di['mod_config']('Serviceproxmox'); + $proxmox = new PVE2_API($serveraccess, $server->root_user, $server->realm, $server->root_password, port: $server->port, tokenid: $server->tokenname, tokensecret: $server->tokenvalue,debug: $config['pmx_debug_logging']); + if ($proxmox->login()) { + $assigned_resources = $proxmox->get("/nodes/" . $server->name . "/qemu"); + return $assigned_resources; + } else { + throw new \Box_Exception("Failed to connect to the server. st"); + } + } + + // function to get available appliances from a server + public function getAvailableAppliances() + { + $server = $this->di['db']->getExistingModelById('service_proxmox_server', 1, 'Server not found'); + + $serveraccess = $this->find_access($server); + $config = $this->di['mod_config']('Serviceproxmox'); + $proxmox = new PVE2_API($serveraccess, $server->root_user, $server->realm, $server->root_password, port: $server->port, tokenid: $server->tokenname, tokensecret: $server->tokenvalue,debug: $config['pmx_debug_logging']); + if ($proxmox->login()) { + $appliances = $proxmox->get("/nodes/" . $server->name . "/aplinfo"); + return $appliances; + } else { + throw new \Box_Exception("Failed to connect to the server. st"); + } + } + + // function to get available template vms from a server + public function getQemuTemplates($server) + { + $serveraccess = $this->find_access($server); + $config = $this->di['mod_config']('Serviceproxmox'); + $proxmox = new PVE2_API($serveraccess, $server->root_user, $server->realm, $server->root_password, port: $server->port, tokenid: $server->tokenname, tokensecret: $server->tokenvalue,debug: $config['pmx_debug_logging']); + if ($proxmox->login()) { + $templates = $proxmox->get("/nodes/" . $server->name . "/qemu"); + return $templates; + } else { + throw new \Box_Exception("Failed to connect to the server. st"); + } + } +} diff --git a/src/ProxmoxTemplates.php b/src/ProxmoxTemplates.php new file mode 100644 index 0000000..23a8e26 --- /dev/null +++ b/src/ProxmoxTemplates.php @@ -0,0 +1,75 @@ +di['db']->findAll('service_proxmox_vm_config_template'); + return $templates; + } + + + // Function that gets all the LXC templates and returns them as an array + public function get_lxctemplates() + { + // get all the LXC templates from the service_proxmox_lxc_config_template table + $templates = $this->di['db']->findAll('service_proxmox_lxc_config_template'); + return $templates; + } + + // Function that gets all qemu templates and returns them as an array + public function get_qemutemplates() + { + // get all the qemu templates from the service_proxmox_qemu_template table + $qemu_templates = $this->di['db']->findAll('service_proxmox_qemu_template'); + // Get server name for each template + foreach ($qemu_templates as $qemu_template) { + $server = $this->di['db']->getExistingModelById('service_proxmox_server', $qemu_template->server_id); + $qemu_template->server_name = $server->name; + } + return $qemu_templates; + } + + // Function that gets a vm config template by id + public function get_vmconfig($id) + { + // get the vm config template from the service_proxmox_vm_config_template table + $template = $this->di['db']->getExistingModelById('service_proxmox_vm_config_template', $id); + return $template; + } + + // Function that gets a lxc config template by id + public function get_lxc_conftempl($id) + { + // get the lxc config template from the service_proxmox_lxc_config_template table + $template = $this->di['db']->getExistingModelById('service_proxmox_lxc_config_template', $id); + return $template; + } +} diff --git a/src/ProxmoxVM.php b/src/ProxmoxVM.php new file mode 100644 index 0000000..8b416e8 --- /dev/null +++ b/src/ProxmoxVM.php @@ -0,0 +1,314 @@ +vm_shutdown($order, $model); + // TODO: Check that the VM was shutdown, otherwise send an email to the admin + + $model->updated_at = date('Y-m-d H:i:s'); + $this->di['db']->store($model); + + return true; + } + + /** + * Unsuspend Proxmox VM + * @param $order + * @return boolean + */ + public function unsuspend($order, $model) + { + // Power on VM? + $this->vm_start($order, $model); + $model->updated_at = date('Y-m-d H:i:s'); + $this->di['db']->store($model); + + return true; + } + + /** + * Cancel Proxmox VM + * @param $order + * @return boolean + */ + public function cancel($order, $model) + { + return $this->suspend($order, $model); + } + + /** + * Uncancel Proxmox VM + * @param $order + * @return boolean + */ + public function uncancel($order, $model) + { + return $this->unsuspend($order, $model); + } + + /** + * Delete Proxmox VM + * @param $order + * @return boolean + */ + public function delete($order, $model) + { + if (is_object($model)) { + + $product = $this->di['db']->load('product', $order->product_id); + $product_config = json_decode($product->config, 1); + + // Retrieve associated server + $server = $this->di['db']->findOne('service_proxmox_server', 'id=:id', array(':id' => $model->server_id)); + + // Connect to YNH API + $serveraccess = $this->find_access($server); + $config = $this->di['mod_config']('Serviceproxmox'); + $proxmox = new PVE2_API($serveraccess, $server->root_user, $server->realm, $server->root_password, port: $server->port, tokenid: $server->tokenname, tokensecret: $server->tokenvalue,debug: $config['pmx_debug_logging']); + + if ($proxmox->login()) { + $proxmox->post("/nodes/" . $model->node . "/" . $product_config['virt'] . "/" . $model->vmid . "/status/shutdown", array()); + $status = $proxmox->get("/nodes/" . $model->node . "/" . $product_config['virt'] . "/" . $model->vmid . "/status/current"); + + // Wait until the server has been shut down if the server exists + if (!empty($status)) { + while ($status['status'] != 'stopped') { + sleep(10); + $proxmox->post("/nodes/" . $model->node . "/" . $product_config['virt'] . "/" . $model->vmid . "/status/shutdown", array()); + $status = $proxmox->get("/nodes/" . $model->node . "/" . $product_config['virt'] . "/" . $model->vmid . "/status/current"); + } + } else { + throw new \Box_Exception("VMID cannot be found"); + } + if ($proxmox->delete("/nodes/" . $model->node . "/" . $product_config['virt'] . "/" . $model->vmid)) { + // TODO: Check if that was the last VM for the client and delete the client user on Proxmox + return true; + } else { + throw new \Box_Exception("VM not deleted"); + } + } else { + throw new \Box_Exception("Login to Proxmox Host failed"); + } + } + } + + /* + * VM status + * + * TODO: Add more Information + */ + public function vm_info($order, $service) + { + $product = $this->di['db']->load('product', $order->product_id); + $product_config = json_decode($product->config, 1); + $client = $this->di['db']->load('client', $order->client_id); + + // Retrieve associated server + $server = $this->di['db']->findOne('service_proxmox_server', 'id=:id', array(':id' => $service->server_id)); + $clientuser = $this->di['db']->findOne('service_proxmox_users', 'server_id = ? and client_id = ?', array($server->id, $client->id)); + + // Test if login + $serveraccess = $this->find_access($server); + $proxmox = new PVE2_API($serveraccess, $server->root_user, $server->realm, $server->root_password, port: $server->port, tokenid: $clientuser->admin_tokenname, tokensecret: $clientuser->admin_tokenvalue,debug: $config['pmx_debug_logging']); + if ($proxmox->get_version()) { + $status = $proxmox->get("/nodes/" . $server->name . "/" . $product_config['virt'] . "/" . $service->vmid . "/status/current"); + // VM monitoring? + + $output = array( + 'status' => $status['status'] + ); + return $output; + } else { + throw new \Box_Exception("Login to Proxmox Host failed."); + } + } + + /* + Cold Reboot VM + */ + public function vm_reboot($order, $service) + { + $product = $this->di['db']->load('product', $order->product_id); + $product_config = json_decode($product->config, 1); + $client = $this->di['db']->load('client', $order->client_id); + + // Retrieve associated server + $server = $this->di['db']->findOne('service_proxmox_server', 'id=:id', array(':id' => $service->server_id)); + $clientuser = $this->di['db']->findOne('service_proxmox_users', 'server_id = ? and client_id = ?', array($server->id, $client->id)); + + // Test if login + $serveraccess = $this->find_access($server); + $proxmox = new PVE2_API($serveraccess, $server->root_user, $server->realm, $server->root_password, port: $server->port, tokenid: $clientuser->admin_tokenname, tokensecret: $clientuser->admin_tokenvalue,debug: $config['pmx_debug_logging']); + if ($proxmox->login()) { + $proxmox->post("/nodes/" . $server->name . "/" . $product_config['virt'] . "/" . $service->vmid . "/status/shutdown", array()); + $status = $proxmox->get("/nodes/" . $server->name . "/" . $product_config['virt'] . "/" . $service->vmid . "/status/current"); + + // Wait until the VM has been shut down if the VM exists + if (!empty($status)) { + while ($status['status'] != 'stopped') { + sleep(10); + $proxmox->post("/nodes/" . $server->name . "/" . $product_config['virt'] . "/" . $service->vmid . "/status/shutdown", array()); + $status = $proxmox->get("/nodes/" . $server->name . "/" . $product_config['virt'] . "/" . $service->vmid . "/status/current"); + } + } + // Restart + if ($proxmox->post("/nodes/" . $server->name . "/" . $product_config['virt'] . "/" . $service->vmid . "/status/start", array())) { + sleep(10); + $status = $proxmox->get("/nodes/" . $server->name . "/" . $product_config['virt'] . "/" . $service->vmid . "/status/current"); + while ($status['status'] != 'running') { + $proxmox->post("/nodes/" . $server->name . "/" . $product_config['virt'] . "/" . $service->vmid . "/status/start", array()); + sleep(10); + $status = $proxmox->get("/nodes/" . $server->name . "/" . $product_config['virt'] . "/" . $service->vmid . "/status/current"); + // Starting twice => error... + } + return true; + } else { + throw new \Box_Exception("Reboot failed"); + } + } else { + throw new \Box_Exception("Login to Proxmox Host failed."); + } + } + + /* + Start VM + */ + public function vm_start($order, $service) + { + $product = $this->di['db']->load('product', $order->product_id); + $product_config = json_decode($product->config, 1); + $client = $this->di['db']->load('client', $order->client_id); + // Retrieve associated server + // Retrieve associated server + $server = $this->di['db']->findOne('service_proxmox_server', 'id=:id', array(':id' => $service->server_id)); + $clientuser = $this->di['db']->findOne('service_proxmox_users', 'server_id = ? and client_id = ?', array($server->id, $client->id)); + + // Test if login + $serveraccess = $this->find_access($server); + $proxmox = new PVE2_API($serveraccess, $server->root_user, $server->realm, $server->root_password, port: $server->port, tokenid: $clientuser->admin_tokenname, tokensecret: $clientuser->admin_tokenvalue,debug: $config['pmx_debug_logging']); + if ($proxmox->login()) { + $proxmox->post("/nodes/" . $server->name . "/" . $product_config['virt'] . "/" . $service->vmid . "/status/start", array()); + return true; + } else { + throw new \Box_Exception("Login to Proxmox Host failed."); + } + } + + /* + Shutdown VM + */ + public function vm_shutdown($order, $service) + { + $product = $this->di['db']->load('product', $order->product_id); + $product_config = json_decode($product->config, 1); + $client = $this->di['db']->load('client', $order->client_id); + // Retrieve associated server + $server = $this->di['db']->findOne('service_proxmox_server', 'id=:id', array(':id' => $service->server_id)); + + // Test if login + // find service access for server + $clientuser = $this->di['db']->findOne('service_proxmox_users', 'server_id = ? and client_id = ?', array($server->id, $client->id)); + //echo "D: ".var_dump($order); + $serveraccess = $this->find_access($server); + $proxmox = new PVE2_API($serveraccess, $server->root_user, $server->realm, $server->root_password, port: $server->port, tokenid: $clientuser->admin_tokenname, tokensecret: $clientuser->admin_tokenvalue,debug: $config['pmx_debug_logging']); + if ($proxmox->login()) { + $settings = array( + 'forceStop' => true + ); + + $proxmox->post("/nodes/" . $server->name . "/" . $product_config['virt'] . "/" . $service->vmid . "/status/shutdown", $settings); + return true; + } else { + throw new \Box_Exception("Login to Proxmox Host failed."); + } + } + + + /* + VM iframe for Web CLI + */ + public function vm_cli($order, $service) + { + + $product = $this->di['db']->load('product', $order->product_id); + $product_config = json_decode($product->config, 1); + $client = $this->di['db']->load('client', $order->client_id); + + // Retrieve associated server + $server = $this->di['db']->findOne('service_proxmox_server', 'id=:id', array(':id' => $service->server_id)); + $clientuser = $this->di['db']->findOne('service_proxmox_users', 'server_id = ? and client_id = ?', array($server->id, $client->id)); + + // Find access route + $serveraccess = $this->find_access($server); + + // Setup console type + $console = ($product_config['virt'] == 'qemu') ? 'kvm' : 'shell'; + + // The user enters the password to see the iframe: TBD + //$password = 'test'; + //$proxmox = new PVE2_API($serveraccess, $client->id, $server->name, $password); + + $proxmox = new PVE2_API($serveraccess, $server->root_user, $server->realm, $server->root_password, port: $server->port, tokenid: $clientuser->view_tokenname, tokensecret: $clientuser->view_tokenvalue,debug: $config['pmx_debug_logging']); + + if ($proxmox->login()) { + // Get VNC Web proxy ticket by calling /nodes/{node}/{type}/{vmid}/vncproxy + $vncproxy_response = $proxmox->post("/nodes/" . $server->name . "/" . $product_config['virt'] . "/" . $service->vmid . "/vncproxy", array('node' => $server->name, 'vmid' => $service->vmid)); + $vncproxy_port = $vncproxy_response['data']['port']; + $vncproxy_ticket = $vncproxy_response['data']['ticket']; + // open a vnc web socket + // we'll do it ourselves until the upstream API supports it + $put_post_http_headers[] = "Authorization: PVEAPIToken={$clientuser->view_tokenname}={$clientuser->view_tokenvalue}"; + // add the vncticket to the post_http_headers + $put_post_http_headers[] = "vncticket: {$vncproxy_ticket}"; + // add the port to the post_http_headers + $put_post_http_headers[] = "port: {$vncproxy_port}"; + // add the host to the post_http_headers + $put_post_http_headers[] = "host: {$serveraccess}"; + // add the vmid to the post_http_headers + $put_post_http_headers[] = "vmid: {$service->vmid}"; + // open websocket connection and display the console + /* curl_setopt($prox_ch, CURLOPT_URL, "https://{$serveraccess}:8006/api2/json/nodes/{$server->name}/{$product_config['virt']}/{$service->vmid}/vncwebsocket"); + curl_setopt($prox_ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($prox_ch, CURLOPT_POSTFIELDS, $login_postfields_string); + curl_setopt($prox_ch, CURLOPT_SSL_VERIFYPEER, $this->verify_ssl); + curl_setopt($prox_ch, CURLOPT_SSL_VERIFYHOST, $this->verify_ssl); + */ + // return array of vncport & ticket + return array('port' => $vncproxy_port, 'ticket' => $vncproxy_ticket); + } else { + throw new \Box_Exception("Login to Proxmox VM failed."); + } + } +} diff --git a/src/Service.php b/src/Service.php new file mode 100644 index 0000000..2133495 --- /dev/null +++ b/src/Service.php @@ -0,0 +1,902 @@ +di = $di; + } + + public function getDi(): ?\Pimple\Container + { + return $this->di; + } + use ProxmoxAuthentication; + use ProxmoxServer; + use ProxmoxVM; + use ProxmoxTemplates; + use ProxmoxIPAM; + + + public function validateCustomForm(array &$data, array $product) + { + if ($product['form_id']) { + $formbuilderService = $this->di['mod_service']('formbuilder'); + $form = $formbuilderService->getForm($product['form_id']); + + foreach ($form['fields'] as $field) { + if ($field['required'] == 1) { + $field_name = $field['name']; + if ((!isset($data[$field_name]) || empty($data[$field_name]))) { + throw new \Box_Exception("You must fill in all required fields. " . $field['label'] . " is missing", null, 9684); + } + } + + if ($field['readonly'] == 1) { + $field_name = $field['name']; + if ($data[$field_name] != $field['default_value']) { + throw new \Box_Exception("Field " . $field['label'] . " is read only. You can not change its value", null, 5468); + } + } + } + } + } + + /** + * Method to install module. In most cases you will provide your own + * database table or tables to store extension related data. + * + * If your extension is not very complicated then extension_meta + * database table might be enough. + * + * @return bool + */ + public function install() + { + // read manifest.json to get current version number + $manifest = json_decode(file_get_contents(__DIR__ . '/manifest.json'), true); + $version = $manifest['version']; + + // check if there is a sqldump backup with "uninstall" in it's name in the pmxconfig folder, if so, restore it + $filesystem = new Filesystem(); + // list content of pmxconfig folder using symfony finder + $finder = new Finder(); + + // check if pmxconfig folder exists + if (!$filesystem->exists(PATH_ROOT . '/pmxconfig')) { + $filesystem->mkdir(PATH_ROOT . '/pmxconfig'); + } + + $pmxbackup_dir = $finder->in(PATH_ROOT . '/pmxconfig')->files()->name('proxmox_uninstall_*.sql'); + + // find newest file in pmxbackup_dir according to timestamp + $pmxbackup_file = array(); + foreach ($pmxbackup_dir as $file) { + $pmxbackup_file[$file->getMTime()] = $file->getFilename(); + } + ksort($pmxbackup_file); + $pmxbackup_file = array_reverse($pmxbackup_file); + $pmxbackup_file = reset($pmxbackup_file); + + // if pmxbackup_file is not empty, restore the sql dump to database + if (!empty($pmxbackup_file)) { + // Load the backup + $dump = file_get_contents(PATH_ROOT . '/pmxconfig/' . $pmxbackup_file); + + // Check if dump is not empty + if (!empty($dump)) { + // Check version number in first line of dump + $original_dump = $dump; + $version_line = strtok($dump, "\n"); + + // Get version number from line + $dump_version = str_replace('-- Proxmox module version: ', '', $version_line); + $dump = str_replace($version_line . "\n", '', $dump); + + // Get db config + $db_user = $this->di['config']['db']['user']; + $db_password = $this->di['config']['db']['password']; + $db_name = $this->di['config']['db']['name']; + + try { + // Create PDO instance + $pdo = new PDO('mysql:host=localhost;dbname=' . $db_name, $db_user, $db_password); + $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + // If version number in dump is smaller than current version number, restore dump and run upgrade function + if ($dump_version < $version) { + // Split the dump into an array by each sql command + $query_array = explode(";", $dump); + + // Execute each sql command + foreach ($query_array as $query) { + if (!empty(trim($query))) { + $pdo->exec($query); + } + } + + $this->upgrade($dump_version); // Runs all migrations between current and next version + } elseif ($dump_version == $version) { + // Split the dump into an array by each sql command + $query_array = explode(";", $dump); + + // Execute each sql command + foreach ($query_array as $query) { + if (!empty(trim($query))) { + $pdo->exec($query); + } + } + } else { + throw new \Box_Exception("The version number of the sql dump is bigger than the current version number of the module. Please check the installed Module version.", null, 9684); + } + } catch (Exception $e) { + throw new \Box_Exception('Error during restoration process: ' . $e->getMessage()); + } + } + } else { + // Get a list of all SQL migration files + $migrations = glob(__DIR__ . '/migrations/*.sql'); + + // Sort the array of migration files by their version numbers (which are in their file names) + usort($migrations, function ($a, $b) { + return version_compare(basename($a, '.sql'), basename($b, '.sql')); + }); + + try { + // Create a new PDO instance, connecting to your MySQL database + $pdo = new PDO( + 'mysql:host=' . $this->di['config']['db']['host'] . ';dbname=' . $this->di['config']['db']['name'], + $this->di['config']['db']['user'], + $this->di['config']['db']['password'] + ); + + // Loop through each migration file + foreach ($migrations as $migration) { + // Extract the version number from the file name + $filename = basename($migration, '.sql'); + $version = str_replace('_', '.', $filename); + + // Log the execution of the current migration + error_log('Running migration ' . $version . ' from ' . $migration); + + // Read the SQL statements from the file into a string + $sql = file_get_contents($migration); + + // Split the string of SQL statements into an array + // This uses the ';' character to identify the end of each SQL statement + $statements = explode(';', $sql); + + // Loop through each SQL statement + foreach ($statements as $statement) { + // If the statement is not empty or just whitespace + if (trim($statement)) { + // Execute the SQL statement + $pdo->exec($statement); + } + } + } + } catch (PDOException $e) { + // If any errors occur while connecting to the database or executing SQL, log the error message and terminate the script + error_log('PDO Exception: ' . $e->getMessage()); + exit(1); + } + + + // Create table for vm + } + // add default values to module config table: + // cpu_overprovisioning, ram_overprovisioning, storage_overprovisioning, avoid_overprovision, no_overprovision, use_auth_tokens + // example: $extensionService->setConfig(['ext' => 'mod_massmailer', 'limit' => '2', 'interval' => '10', 'test_client_id' => 1]); + $extensionService = $this->di['mod_service']('extension'); + $extensionService->setConfig(['ext' => 'mod_serviceproxmox', 'cpu_overprovisioning' => '1', 'ram_overprovisioning' => '1', 'storage_overprovisioning' => '1', 'avoid_overprovision' => '0', 'no_overprovision' => '1', 'use_auth_tokens' => '1']); + + + return true; + } + + + /** + * Method to uninstall module. + * Now creates a sql dump of the database tables and stores it in the pmxconfig folder + * + * @return bool + */ + public function uninstall() + { + $this->pmxdbbackup('uninstall'); + $this->di['db']->exec("DROP TABLE IF EXISTS `service_proxmox`"); + $this->di['db']->exec("DROP TABLE IF EXISTS `service_proxmox_server`"); + $this->di['db']->exec("DROP TABLE IF EXISTS `service_proxmox_users`"); + $this->di['db']->exec("DROP TABLE IF EXISTS `service_proxmox_storage`"); + $this->di['db']->exec("DROP TABLE IF EXISTS `service_proxmox_templates`"); + $this->di['db']->exec("DROP TABLE IF EXISTS `service_proxmox_vms`"); + $this->di['db']->exec("DROP TABLE IF EXISTS `service_proxmox_vm_config_template`"); + $this->di['db']->exec("DROP TABLE IF EXISTS `service_proxmox_vm_storage_template`"); + $this->di['db']->exec("DROP TABLE IF EXISTS `service_proxmox_vm_network_template`"); + $this->di['db']->exec("DROP TABLE IF EXISTS `service_proxmox_lxc_appliance`"); + $this->di['db']->exec("DROP TABLE IF EXISTS `service_proxmox_storageclass`"); + $this->di['db']->exec("DROP TABLE IF EXISTS `service_proxmox_client_network`"); + $this->di['db']->exec("DROP TABLE IF EXISTS `service_proxmox_ip_networks`"); + $this->di['db']->exec("DROP TABLE IF EXISTS `service_proxmox_ip_range`"); + $this->di['db']->exec("DROP TABLE IF EXISTS `service_proxmox_client_vlan`"); + $this->di['db']->exec("DROP TABLE IF EXISTS `service_proxmox_lxc_config_template`"); + $this->di['db']->exec("DROP TABLE IF EXISTS `service_proxmox_lxc_network_template`"); + $this->di['db']->exec("DROP TABLE IF EXISTS `service_proxmox_lxc_storage_template`"); + $this->di['db']->exec("DROP TABLE IF EXISTS `service_proxmox_qemu_template`"); + $this->di['db']->exec("DROP TABLE IF EXISTS `service_proxmox_ipam_settings`"); + $this->di['db']->exec("DROP TABLE IF EXISTS `service_proxmox_ipadress`"); + + return true; + } + + /** + * Method to upgrade module. + * + * @param string $previous_version + * @return bool + */ + public function upgrade($previous_version) + { + // read current module version from manifest.json + $manifest = json_decode(file_get_contents(__DIR__ . '/manifest.json'), true); + $current_version = $manifest['version']; + + // read migrations directory and find all files between current version and previous version + $migrations = glob(__DIR__ . '/migrations/*.sql'); + + // sort migrations by version number (Filenames: 0.0.1.sql, 0.0.2.sql etc.) + usort($migrations, function ($a, $b) { + return version_compare(basename($a, '.sql'), basename($b, '.sql')); + }); + + // Get db config + $db_user = $this->di['config']['db']['user']; + $db_password = $this->di['config']['db']['password']; + $db_name = $this->di['config']['db']['name']; + + // Create PDO instance + $pdo = new PDO('mysql:host=localhost;dbname=' . $db_name, $db_user, $db_password); + $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + foreach ($migrations as $migration) { + // get version from filename + // log to debug.log + error_log('found migration: ' . $migration); + $filename = basename($migration, '.sql'); + $version = str_replace('_', '.', $filename); + + // check if version is between previous and current version + error_log('version: ' . $version . ' previous_version: ' . $previous_version . ' current_version: ' . $current_version); + + // Apply migration if version is larger than previous version and smaller or equal to current version + error_log('version_compare: ' . version_compare($version, $previous_version, '>') . ' version_compare2: ' . version_compare($version, $current_version, '<=')); + + if (version_compare($version, $previous_version, '>') && version_compare($version, $current_version, '<=')) { + error_log('applying migration: ' . $migration); + + // run migration + $migration_sql = file_get_contents($migration); + $pdo->exec($migration_sql); + } + } + + return true; + } + + /** + * Method to check if all tables have been migrated to current Module Version. + * Not yet used, but will be in the admin settings page for the module + * + * @param string $action + * @return bool + */ + public function check_db_migration() + { + // read current module version from manifest.json + $manifest = json_decode(file_get_contents(__DIR__ . '/manifest.json'), true); + $current_version = $manifest['version']; + $tables = array( + 'service_proxmox_server', + 'service_proxmox', + 'service_proxmox_users', + 'service_proxmox_storageclass', + 'service_proxmox_storage', + 'service_proxmox_lxc_appliance', + 'service_proxmox_vm_config_template', + 'service_proxmox_vm_storage_template', + 'service_proxmox_vm_network_template', + 'service_proxmox_lxc_config_template', + 'service_proxmox_lxc_storage_template', + 'service_proxmox_lxc_network_template', + 'service_proxmox_qemu_template', + 'service_proxmox_client_vlan', + 'service_proxmox_ip_range' + + ); + + foreach ($tables as $table) { + $sql = "SELECT table_comment FROM INFORMATION_SCHEMA.TABLES WHERE table_schema='" . DB_NAME . "' AND table_name='" . $table . "'"; + $result = $this->di['db']->query($sql); + $row = $result->fetch(); + // check if version is the same as current version + if ($row['table_comment'] != $current_version) { + // if not, throw error to inform user about inconsistent database status + throw new \Box_Exception('Database migration is not up to date. Please run the database migration script.'); + } + } + return true; + } + + /** + * Method to create configuration Backups of Proxmox tables + * + * @param string $data - 'uninstall' or 'backup' + * @return bool + */ + public function pmxdbbackup($data) + { + // create backup of all Proxmox tables + try { + $filesystem = new Filesystem(); + $filesystem->mkdir([PATH_ROOT . '/pmxconfig'], 0750); + } catch (IOException $e) { + error_log($e->getMessage()); + throw new \Box_Exception('Unable to create directory pmxconfig'); + } + // create filename with timestamp + // check if $data is 'uninstall' or 'backup' + if ($data == 'uninstall') { + $filename = '/pmxconfig/proxmox_uninstall_' . date('Y-m-d_H-i-s') . '.sql'; + } else { + $filename = '/pmxconfig/proxmox_backup_' . date('Y-m-d_H-i-s') . '.sql'; + } + + // get db config + $db_user = $this->di['config']['db']['user']; + $db_password = $this->di['config']['db']['password']; + $db_name = $this->di['config']['db']['name']; + + try { + // create PDO instance + $pdo = new PDO('mysql:host=localhost;dbname=' . $db_name, $db_user, $db_password); + + // List of tables to backup + $tables = array( + 'service_proxmox_server', + 'service_proxmox', + 'service_proxmox_users', + 'service_proxmox_storageclass', + 'service_proxmox_storage', + 'service_proxmox_lxc_appliance', + 'service_proxmox_vm_config_template', + 'service_proxmox_vm_storage_template', + 'service_proxmox_vm_network_template', + 'service_proxmox_lxc_config_template', + 'service_proxmox_lxc_storage_template', + 'service_proxmox_lxc_network_template', + 'service_proxmox_qemu_template', + 'service_proxmox_client_vlan', + 'service_proxmox_ip_range', + 'service_proxmox_ipadress',/* + 'service_proxmox_ipam_settings' */ + ); + + // Initialize backup variable + $backup = ''; + + // Loop through tables and create SQL statement + foreach ($tables as $table) { + $result = $pdo->query('SELECT * FROM ' . $table); + $num_fields = $result->columnCount(); + + $backup .= 'DROP TABLE IF EXISTS ' . $table . ';'; + $row2 = $pdo->query('SHOW CREATE TABLE ' . $table)->fetch(PDO::FETCH_NUM); + $backup .= "\n\n" . $row2[1] . ";\n\n"; + + while ($row = $result->fetch(PDO::FETCH_NUM)) { + $backup .= 'INSERT INTO ' . $table . ' VALUES('; + for ($j = 0; $j < $num_fields; $j++) { + $row[$j] = addslashes($row[$j]); + $row[$j] = preg_replace("/\n/", "\\n", $row[$j]); + if (isset($row[$j])) { + $backup .= '"' . $row[$j] . '"'; + } else { + $backup .= '""'; + } + if ($j < ($num_fields - 1)) { + $backup .= ','; + } + } + $backup .= ");\n"; + } + $backup .= "\n\n\n"; + } + + // Save to file + $handle = fopen(PATH_ROOT . $filename, 'w+'); + fwrite($handle, $backup); + fclose($handle); + // create PDO instance + $pdo = new PDO('mysql:host=localhost;dbname=' . $db_name, $db_user, $db_password); + + // List of tables to backup + $tables = array( + 'service_proxmox_server', + 'service_proxmox', + 'service_proxmox_users', + 'service_proxmox_storageclass', + 'service_proxmox_storage', + 'service_proxmox_lxc_appliance', + 'service_proxmox_vm_config_template', + 'service_proxmox_vm_storage_template', + 'service_proxmox_vm_network_template', + 'service_proxmox_lxc_config_template', + 'service_proxmox_lxc_storage_template', + 'service_proxmox_lxc_network_template', + 'service_proxmox_qemu_template', + 'service_proxmox_client_vlan', + 'service_proxmox_ip_range' + ); + + // Initialize backup variable + $backup = ''; + + // Loop through tables and create SQL statement + foreach ($tables as $table) { + $result = $pdo->query('SELECT * FROM ' . $table); + $num_fields = $result->columnCount(); + + $backup .= 'DROP TABLE IF EXISTS ' . $table . ';'; + $row2 = $pdo->query('SHOW CREATE TABLE ' . $table)->fetch(PDO::FETCH_NUM); + $backup .= "\n\n" . $row2[1] . ";\n\n"; + + while ($row = $result->fetch(PDO::FETCH_NUM)) { + $backup .= 'INSERT INTO ' . $table . ' VALUES('; + for ($j = 0; $j < $num_fields; $j++) { + $row[$j] = addslashes($row[$j]); + $row[$j] = preg_replace("/\n/", "\\n", $row[$j]); + if (isset($row[$j])) { + $backup .= '"' . $row[$j] . '"'; + } else { + $backup .= '""'; + } + if ($j < ($num_fields - 1)) { + $backup .= ','; + } + } + $backup .= ");\n"; + } + $backup .= "\n\n\n"; + } + + // Save to file + $handle = fopen(PATH_ROOT . $filename, 'w+'); + fwrite($handle, $backup); + fclose($handle); + } catch (Exception $e) { + throw new \Box_Exception('Error during backup process: ' . $e->getMessage()); + } + + // read current module version from manifest.json + $manifest = json_decode(file_get_contents(__DIR__ . '/manifest.json'), true); + $current_version = $manifest['version']; + // add version comment to backup file + $version_comment = '-- Proxmox module version: ' . $current_version . "\n"; + $filename = PATH_ROOT . $filename; + $handle = fopen($filename, 'r+'); + $len = strlen($version_comment); + $final_len = filesize($filename) + $len; + $cache_old = fread($handle, $len); + rewind($handle); + $i = 1; + while (ftell($handle) < $final_len) { + fwrite($handle, $version_comment); + $version_comment = $cache_old; + $cache_old = fread($handle, $len); + fseek($handle, $i * $len); + $i++; + } + fclose($handle); + + return true; + } + + + /** + * Method to list all Proxmox backups + * + * @return array + */ + public function pmxbackuplist() + { + $files = glob(PATH_ROOT . '/pmxconfig/*.sql'); + $backups = array(); + foreach ($files as $file) { + $backups[] = basename($file); + } + return $backups; + } + + /** + * Method to restore Proxmox tables from backup + * It's a bit destructive, as it will drop & overwrite all existing tables + * + * @param string $data - filename of backup + * @return bool + */ + public function pmxbackuprestore($data) + { + // get filename from $data and see if it exists using finder + $manifest = json_decode(file_get_contents(__DIR__ . '/manifest.json'), true); + $version = $manifest['version']; + //if the file exists, restore it + $dump = file_get_contents(PATH_ROOT . '/pmxconfig/' . $data['backup']); + // check if dump is not empty + if (!empty($dump)) { + // check version number in first line of dump format: + // -- Proxmox module version: 0.0.5 + // get first line of dump + $version_line = strtok($dump, "\n"); + // get version number from line + $dump_version = str_replace('-- Proxmox module version: ', '', $version_line); + $dump = str_replace($version_line . "\n", '', $dump); + + // if version number in dump is smaller than current version number, restore dump and run upgrade function + if ($dump_version == $version) { + // get db config + $db_user = $this->di['config']['db']['user']; + $db_password = $this->di['config']['db']['password']; + $db_name = $this->di['config']['db']['name']; + + try { + // create PDO instance + $pdo = new PDO('mysql:host=localhost;dbname=' . $db_name, $db_user, $db_password); + $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + // split the dump into an array by each sql command + $query_array = explode(";", $dump); + + // execute each sql command + foreach ($query_array as $query) { + if (!empty(trim($query))) { + $pdo->exec($query); + } + } + + return true; + } catch (Exception $e) { + throw new \Box_Exception('Error during restoration process: ' . $e->getMessage()); + } + } else { + throw new \Box_Exception("The sql dump file (V: $dump_version) is not compatible with the current module version (V: $version). Please check the file.", null); + } + } else { + throw new \Box_Exception("The sql dump file is empty. Please check the file.", null); + } + } + + // Create function that runs with cron job hook + // This function will run every 5 minutes and update all servers + // Disabled for now + /* + public static function onBeforeAdminCronRun(\Box_Event $event) + { + // getting Dependency Injector + $di = $event->getDi(); + + // @note almost in all cases you will need Admin API + $api = $di['api_admin']; + // get all servers from database + // like this $vms = $this->di['db']->findAll('service_proxmox', 'server_id = :server_id', array(':server_id' => $data['id'])); + $servers = $di['db']->findAll('service_proxmox_server'); + // rum getHardwareData, getStorageData and getAssignedResources for each server + foreach ($servers as $server) { + $hardwareData = $api->getHardwareData($server['id']); + $storageData = $api->getStorageData($server['id']); + $assignedResources = $api->getAssignedResources($server['id']); + } + } */ + + + /* ################################################################################################### */ + /* ########################################### Orders ############################################## */ + /* ################################################################################################### */ + + /** + * @param \Model_ClientOrder $order + * @return void + */ + public function create($order) + { + $config = json_decode($order->config, 1); + + $product = $this->di['db']->getExistingModelById('Product', $order->product_id, 'Product not found'); + + $model = $this->di['db']->dispense('service_proxmox'); + $model->client_id = $order->client_id; + $model->order_id = $order->id; + $model->created_at = date('Y-m-d H:i:s'); + $model->updated_at = date('Y-m-d H:i:s'); + + // Find suitable server and save it to service_proxmox + $model->server_id = $this->find_empty($product); + // Retrieve server info + + $this->di['db']->store($model); + + return $model; + } + + /** + * @param \Model_ClientOrder $order + * @return boolean + */ + public function activate($order, $model) + { + if (!is_object($model)) { + throw new \Box_Exception('Could not activate order. Service was not created'); + } + $config = json_decode($order->config, 1); + + $client = $this->di['db']->load('client', $order->client_id); + $product = $this->di['db']->load('product', $order->product_id); + if (!$product) { + throw new \Box_Exception('Could not activate order because ordered product does not exists'); + } + $product_config = json_decode($product->config, 1); + + // Allocate to an appropriate server id + $server = $this->di['db']->load('service_proxmox_server', $model->server_id); + + // Retrieve or create client unser account in service_proxmox_users + $clientuser = $this->di['db']->findOne('service_proxmox_users', 'server_id = ? and client_id = ?', array($server->id, $client->id)); + if (!$clientuser) { + $this->create_client_user($server, $client); + } + + // Connect to Proxmox API + $serveraccess = $this->find_access($server); + // find client permissions for server + $clientuser = $this->di['db']->findOne('service_proxmox_users', 'server_id = ? and client_id = ?', array($server->id, $client->id)); + $config = $this->di['mod_config']('Serviceproxmox'); + $proxmox = new PVE2_API($serveraccess, $server->root_user, $server->realm, $server->root_password, port: $server->port, tokenid: $clientuser->admin_tokenname, tokensecret: $clientuser->admin_tokenvalue,debug: $config['pmx_debug_logging']); + + // Create Proxmox VM + if ($proxmox->login()) { + // compile VMID by combining the server id, the client id, and the order id separated by padded zeroes for thee numbers per variable + $vmid = $server->id . str_pad($client->id, 3, '0', STR_PAD_LEFT) . str_pad($order->id, 3, '0', STR_PAD_LEFT); + + // check if vmid is already in use + $vmid_in_use = $proxmox->get("/nodes/" . $server->node . "/qemu/" . $vmid); + if ($vmid_in_use) { + $vmid = $vmid . '1'; + } + + $proxmoxuser_password = $this->di['tools']->generatePassword(16, 4); // Generate password + + // Create VM + $clone = ''; + $container_settings = array(); + $description = 'Service package ' . $model->id . ' belonging to client id: ' . $client->id; + + if ($product_config['clone'] == true) { + $clone = '/' . $product_config['cloneid'] . '/clone'; // Define the route for cloning + $container_settings = array( + 'newid' => $vmid, + 'name' => $model->username, + 'description' => $description, + 'full' => true + ); + } else { // TODO: Implement Container templates + if ($product_config['virt'] == 'qemu') { + $container_settings = array( + 'vmid' => $vmid, + 'name' => 'vm' . $vmid, // Hostname to define + 'node' => $server->name, // Node to create the VM on + 'description' => $description, + 'storage' => $product_config['storage'], + 'memory' => $product_config['memory'], + 'scsihw' => 'virtio-scsi-single', + 'scsi0' => "Storage01:10", + 'ostype' => "other", + 'kvm' => "0", + 'ide2' => $product_config['cdrom'] . ',media=cdrom', + 'sockets' => $product_config['cpu'], + 'cores' => $product_config['cpu'], + 'numa' => "0", + 'pool' => 'fb_client_' . $client->id, + ); + } else { + $container_settings = array( + 'vmid' => $vmid, + 'hostname' => 'vm' . $vmid, // Hostname to define + 'description' => $description, + 'storage' => $product_config['storage'], + 'memory' => $product_config['memory'], + 'ostemplate' => $product_config['ostemplate'], + 'password' => $proxmoxuser_password, + 'net0' => $product_config['network'] + ); + // Storage to do for LXC + } + } + + // If the VM is properly created + $vmurl = "/nodes/" . $server->name . "/" . $product_config['virt'] . $clone; + + $vmcreate = $proxmox->post($vmurl, $container_settings); + //echo "Debug:\n " . var_dump($vmcreate) . "\n \n"; + if ($vmcreate) { + + // Start the vm + sleep(20); + $proxmox->post("/nodes/" . $server->name . "/" . $product_config['virt'] . "/" . $vmid . "/status/start", array()); + $status = $proxmox->get("/nodes/" . $server->name . "/" . $product_config['virt'] . "/" . $vmid . "/status/current"); + + if (!empty($status)) { + sleep(10); + // Wait until it has been started + while ($status['status'] != 'running') { + $proxmox->post("/nodes/" . $server->name . "/" . $product_config['virt'] . "/" . $vmid . "/status/start", array()); + sleep(10); + $status = $proxmox->get("/nodes/" . $server->name . "/" . $product_config['virt'] . "/" . $vmid . "/status/current"); + // TODO: Check Startup + } + } else { + throw new \Box_Exception("VMID cannot be found"); + } + } else { + throw new \Box_Exception("VPS has not been created"); + } + } else { + throw new \Box_Exception('Login to Proxmox Host failed with client credentials', null, 7457); + } + + // Retrieve VM IP + + $model->server_id = $model->server_id; + $model->updated_at = date('Y-m-d H:i:s'); + $model->vmid = $vmid; + $model->password = $proxmoxuser_password; + //$model->ipv4 = $ipv4; // TODO: Retrieve IP address of the VM from the PMX IPAM module + //$model->ipv6 = $ipv6; // TODO: Retrieve IP address of the VM from the PMX IPAM module + //$model->hostname = $hostname; // TODO: Retrieve hostname from the Order form + $this->di['db']->store($model); + + return array( + 'ip' => 'to be sent by us shortly', // $model->ipv4 - Return IP address of the VM + 'username' => 'root', + 'password' => 'See Admin Area', // Password won't be sen't by E-Mail. It will be stored in the database and can be retrieved from the client area + ); + } + + /** + * Get the API array representation of a model + * Important to interact with the Order + * @param object $model + * @return array + */ + public function toApiArray($model) + { + // Retrieve associated server + $server = $this->di['db']->findOne('service_proxmox_server', 'id=:id', array(':id' => $model->server_id)); + + return array( + 'id' => $model->id, + 'client_id' => $model->client_id, + 'server_id' => $model->server_id, + 'username' => $model->username, + 'mailbox_quota' => $model->mailbox_quota, + 'server' => $server, + ); + } + + // function to get novnc_appjs file + public function get_novnc_appjs($data) + { + // get list of servers + + $servers = $this->di['db']->find('service_proxmox_server'); + // select first server + $server = $servers['2']; + + $hostname = $server->hostname; + // build url + + $url = "https://$hostname:8006/novnc/" . $data; //$data['ver']; + // get file using symphony http client + // set options + $client = $this->getHttpClient()->withOptions([ + 'verify_peer' => false, + 'verify_host' => false, + 'timeout' => 60, + ]); + $result = $client->request('GET', $url); + //echo ""; + // return file + return $result; + } + + public function getHttpClient() + { + return \Symfony\Component\HttpClient\HttpClient::create(); + } + + // function to get tags for type + public function get_tags($data) + { + // get list of tags for input type + $tags = $this->di['db']->find('service_proxmox_tag', 'type=:type', array(':type' => $data['type'])); + // return tags + return $tags; + } + + // function to save tags for type + public function save_tag($data) + { + // $data contains 'type' and 'tag' + + // search if the tag already exists + error_log('saving tag: ' . print_r($data)); + $tag_exists = $this->di['db']->findOne('service_proxmox_tag', 'type=:type AND name=:name', array(':type' => $data['type'], ':name' => $data['tag'])); + // and if not create it + if (!$tag_exists) { + $model = $this->di['db']->dispense('service_proxmox_tag'); + $model->type = $data['type']; + $model->name = $data['tag']; + $this->di['db']->store($model); + return $model; + } + // return the tag that was just created + return $tag_exists; + } + + + // Function to return tags for storage (stored in service_proxmox_storage->storageclass) ($data contains storageid) + public function get_tags_by_storage($data) + { + // get storageclass for storage + // log to debug.log + error_log('get_tags_by_storage: ' . $data['storageid']); + $storage = $this->di['db']->findOne('service_proxmox_storage', 'id=:id', array(':id' => $data['storageid'])); + // return tags (saved in json format in $storage->storageclass) (F.ex ["ssd","hdd"]) + // as well as the service_proxmox_tag id for each tag so there is a key value pair with id and name. + + $tags = json_decode($storage->storageclass, true); + return $tags; + + + } + + + +} diff --git a/src/assets/css/proxmox.css b/src/assets/css/proxmox.css new file mode 100644 index 0000000..5345e4c --- /dev/null +++ b/src/assets/css/proxmox.css @@ -0,0 +1,63 @@ +/* The switch - the box around the slider */ +.switch { + position: relative; + display: inline-block; + width: 60px; + height: 34px; + } + + /* Hide default HTML checkbox */ + .switch input { + opacity: 0; + width: 0; + height: 0; + } + + /* The slider */ + .slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + -webkit-transition: .4s; + transition: .4s; + } + + .slider:before { + position: absolute; + content: ""; + height: 26px; + width: 26px; + left: 4px; + bottom: 4px; + background-color: white; + -webkit-transition: .4s; + transition: .4s; + } + + input:checked + .slider { + background-color: #2196F3; + } + + input:focus + .slider { + box-shadow: 0 0 1px #2196F3; + } + + input:checked + .slider:before { + -webkit-transform: translateX(26px); + -ms-transform: translateX(26px); + transform: translateX(26px); + } + + /* Rounded sliders */ + .slider.round { + border-radius: 34px; + } + + .slider.round:before { + border-radius: 50%; + } + \ No newline at end of file diff --git a/src/assets/vnc.html b/src/assets/vnc.html new file mode 100644 index 0000000..a8cb768 --- /dev/null +++ b/src/assets/vnc.html @@ -0,0 +1,364 @@ + + + + + + cl2n1 - Proxmox Console + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
noVNC encountered an error:
+
+
+
+
+ + +
+ +
+
+ +
+ +

no
VNC

+ +
+ + + + + +
+ +
+ + + +
+
+ + + + + + +
+
+ + + +
+
+
+ Power +
+ + + +
+
+ + + +
+
+
+ Clipboard +
+

+ Edit clipboard content in the textarea below. +

+ +
+
+ + + + + + +
+
+
+ Settings +
+
    +
  • + +
  • +
  • + +
  • +

  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + + +
  • +

  • +
  • +
    Advanced
    +
      +
    • + + +
    • +
    • + + +
    • +

    • +
    • + + +
    • +
    • +
      WebSocket
      +
        +
      • + +
      • +
      • + + +
      • +
      • + + +
      • +
      • + + +
      • +
      +
    • +

    • +
    • + +
    • +
    • + + +
    • +

    • +
    • + +
    • +

    • + +
    • + +
    • +
    +
  • +

  • +
  • + Version: + +
  • +
+
+
+ + + + +
+
+
+ Commands +
+ + + + + + + +
+
+ + + + + +
+
+ +
+ +
+
+
+
+ + +
+ + +
+
+ +
+ +
+
+
+ + +
+
+
+ Server identity +
+
+ The server has provided the following identifying information: +
+
+ Fingerprint: + +
+
+ Please verify that the information is correct and press + "Approve". Otherwise press "Reject". +
+
+ + +
+
+
+ +
+
+
Guest not running
+
+ Start Now +
+
+
+ + +
+
+
+ Credentials +
+
+ + +
+
+ + +
+
+ +
+
+
+ + +
+
+
+ +
+
+
+ + +
+ + +
+ + + + diff --git a/src/html_admin/mod_serviceproxmox_config.html.twig b/src/html_admin/mod_serviceproxmox_config.html.twig new file mode 100644 index 0000000..29600fd --- /dev/null +++ b/src/html_admin/mod_serviceproxmox_config.html.twig @@ -0,0 +1,248 @@ +
+ + +
+

+ {{ 'General Settings' | trans }} +

+
+
+
+ +
+
+ + +
+
+ + +
+
+
+
+ + {% set groups = admin.serviceproxmox_server_groups() %} +
+ +
+
+
+ +
+ +
+
+
+ +
+
+ + +
+
+ + +
+
+
+
+
+

+ {{ 'Product Settings' | trans }} +

+
+
+
+ +
+
+ + +
+
+ + +
+
+
+ {% set lxc_templates = admin.serviceproxmox_service_get_lxctemplates() %} + {% set vm_templates = admin.serviceproxmox_service_get_vmtemplates() %} + {% set qemu_templates = admin.serviceproxmox_service_get_qemutemplates() %} +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+ + +
+
+{% block js %} + +{% endblock %} + \ No newline at end of file diff --git a/src/html_admin/mod_serviceproxmox_index.html.twig b/src/html_admin/mod_serviceproxmox_index.html.twig new file mode 100644 index 0000000..7f8d043 --- /dev/null +++ b/src/html_admin/mod_serviceproxmox_index.html.twig @@ -0,0 +1,625 @@ +{% import 'macro_functions.html.twig' as mf %} +{% extends 'layout_default.html.twig' %} +{% block meta_title %} + {{ 'Proxmox servers' | trans }} +{% endblock %} +{% set active_menu = 'proxmox' %} + +{% block content %} + + +
+
+
+ +
+ Show help + +
+
+ +
+
+

+ {{ 'Proxmox Servers' | trans }} +

+
+ +
+
+ Legend:   + + + + Test Connection   |   + + + + Prepare Server   |   + + + + Edit Server   |   + + + + Delete Server +
+
+ {% set servers_grouped = admin.serviceproxmox_server_get_list() %} + {% for group in servers_grouped %} +
+ {{ group.group }} group +
+ + + + + + + + + + + + + + + + {% for server in group.servers %} + + + + + + + + + {% if server.active == 1 %} + + {% else %} + + {% endif %} + + + {% else %} + + + + {% endfor %} + + {% endfor %} +
+ {{ 'ID' | trans }} + + {{ 'Name' | trans }} + + {{ 'Hostname' | trans }} + + {{ 'IPv4' | trans }} + + {{ 'VMs' | trans }} + + {{ 'CPU' | trans }} + + {{ 'RAM' | trans }} + + {{ 'Active' | trans }} +
+ + {{ server.id }} + + + {{ server.name }} + + + {{ server.hostname }}:{{server.port}} + + + {{ server.ipv4 }} + + {{ server.vm_count }} + + {{ server.cpu_cores_allocated }} / {{ server.cpu_cores_overprovision }}
+ (Phy: {{ server.cpu_cores }}) +
+ {{ server.ram_used }} / {{ server.ram_overprovision }} GB
+ (Phy: {{ server.ram }} GB) +
+
+
+ +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ {{ 'The list is empty' | trans }} +
+
+ +
+ + + +
+
+
+
+ +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+ + +
+ +
+
+ + +
+
+ + +
+
+
+ + +
+ +
+ +
+
+
+ +
+ +
+ This password will only be used to create the access tokens on the pve server. +
+
+
+
+ +
+ +
+
+
+ +
+ +
+ This token will be used to authenticate with the pve server. +
+
+
+
+ +
+
+ + +
+
+ + +
+
+
+ +
+
+
+
+ +
+
+
+

+ {{ 'Proxmox Storage list' | trans }} +

+
+ + {% set storages_grouped = admin.serviceproxmox_storage_get_list() %} + {% for type in storages_grouped %} +
+
+ {{ type.group }} Storages +
+
+ + + + + + + + + + + + + {% for storage in type.storages %} + + + + + + + + + {% endfor %} + +
+ {{ 'Storage' | trans }} + + {{ 'Servername' | trans }} + + {{ 'Content' | trans }} + + {{ 'Storage Space' | trans }} + + {{ 'Active' | trans }} + +
+ {{ storage.name }} + + {{ storage.servername }} + + {% set content = storage.content|split(',') %} + {% for line in content %} + {{ line }}
+ {% endfor %} +
+ {{ storage.used }} / {{ storage.size }} GB +
+
+
+
+ {% if storage.active == 1 %} + + {% else %} + + {% endif %} + + + + + + +
+
+
+ {% endfor %} +
+
+ +
+
+
+

+ {{ 'Servicelist' | trans }} +

+
+ + {% set services = admin.serviceproxmox_service_proxmox_get_list() %} + +
+ + All Services + +
+ + + + + + + + + + + + + + + {% for service in services %} + + + + + + + + + + + {% endfor %} + +
+ {{ 'Server' | trans }} + + {{ 'VM ID' | trans }} + + {{ 'Host Name' | trans }} + + {{ 'CPU Cores' | trans }} + + {{ 'RAM' | trans }} + + {{ 'Status' | trans }} + + {{ 'Created At' | trans }} +
+ {{ service.server_id }} + + {{ service.id}} + + {{ service.hostname }} + + {{ service.cores }} + + {{ service.ram }} MB + + {% if service.status == 'running' %} + + {% else %} + + {% endif %} + + {{ service.created_at }} + + + + + + +
+
+
+{% endblock %} +{% block js%} + + +> +{% endblock %} diff --git a/src/html_admin/mod_serviceproxmox_ipam.html.twig b/src/html_admin/mod_serviceproxmox_ipam.html.twig new file mode 100644 index 0000000..ba67697 --- /dev/null +++ b/src/html_admin/mod_serviceproxmox_ipam.html.twig @@ -0,0 +1,585 @@ +{% import 'macro_functions.html.twig' as mf %} +{% extends 'layout_default.html.twig' %} +{% block meta_title %} + {{ 'Proxmox IP & Network management' | trans }} +{% endblock %} +{% set active_menu = 'proxmox' %} + +{% block content %} + + +
+ +
+
+
+

+ {{ 'IP Ranges' | trans }} +

+
+
+ {% set ipranges = admin.serviceproxmox_service_get_ip_ranges() %} +
+ + All IP Ranges + +
+ + + + + + + + + + + + {% for iprange in ipranges %} + + + + + + + + {% endfor %} + +
+ {{ 'CIDR' | trans }} + + {{ 'Gateway' | trans }} + + {{ 'Broadcast' | trans }} + + {{ 'Type' | trans }} +
+ {{ iprange.cidr }} + + {{ iprange.gateway }} + + {{ iprange.broadcast }} + + {{ iprange.type }} + + + + + + +
+
+
+
+ +
+
+
+
+ +
+ +
+ +
+
+
+ +
+ + +
+
+
+ +
+ +
+
+ + 1 Adress, /32 + +
+
+
+ +
+ +
+
+ +
+
+
+ +
+
+ + +
+
+ + +
+
+
+ +
+
+
+
+ +
+
+
+

+ {{ 'IP Addresses' | trans }} +

+
+
+ {% set ipadresses = admin.serviceproxmox_service_get_ip_adresses() %} +
+ + All IP Adresses + +
+ + + + + + + + + + + + + {% for ipadress in ipadresses %} + + + + + + + + + {% endfor %} + +
+ {{ 'IP' | trans }} + + {{ 'IP Range' | trans }} + + {{ 'Dedicated' | trans }} + + {{ 'Gateway' | trans }} + + {{ 'VLAN' | trans }} +
+ {{ ipadress.ip }} + + // display the ip_range cidr for ipaddress.ip_range_id found in the ip_ranges variable + {% for ip_range in ip_ranges %} + {% if ip_range.id == ipadress.ip_range_id %} + {{ ip_range.cidr }} + {% endif %} + {% endfor %} + + {{ ipadress.dedicated }} + + {{ ipadress.gateway }} + + {{ ipadress.vlan }} + + + + + + +
+
+
+
+ +
+
+
+
+ +
+ +
+ +
+
+
+ + +
+ +
+
+
+ +
+
+ + +
+
+ + +
+
+
+
+ +
+ + +
+
+
+ + {% set client_vlans = admin.serviceproxmox_service_get_vlans() %} +
+ +
+
+ +
+
+
+
+ +
+
+
+

+ {{ 'VLANs' | trans }} +

+
+
+ + {% set client_vlans = admin.serviceproxmox_service_get_vlans() %} +
+ + All Client VLANs + +
+ + + + + + + + + + + {% for client_vlan in client_vlans %} + + + + + + + {% endfor %} + +
+ {{ 'Client' | trans }} + + {{ 'VLAN ID' | trans }} + + {{ 'IP Range' | trans }} +
+ {{ client_vlan.client_name }} + + {{ client_vlan.vlan }} + + {{ client_vlan.ip_range }} + + + + + + + + + + + +
+
+
+
+ +
+
+
+
+ +
+ + {% set clients = admin.client_get_list() %} +
+ +
+
+
+ +
+ + +
+
+
+ +
+ +
+
+ +
+
+
+
+ +
+
+
+

+ {{ 'IPAM Settings' | trans }} +

+
+
+ +
+
+ +
+
+ +
+
+
+
+ {% endblock %} + +{% block js %} + +{% endblock %} diff --git a/src/html_admin/mod_serviceproxmox_ipam_client_vlan.html.twig b/src/html_admin/mod_serviceproxmox_ipam_client_vlan.html.twig new file mode 100644 index 0000000..2e193d0 --- /dev/null +++ b/src/html_admin/mod_serviceproxmox_ipam_client_vlan.html.twig @@ -0,0 +1,78 @@ +{% import 'macro_functions.html.twig' as mf %} +{% extends "layout_default.html.twig" %} +{% block meta_title %} + {{ 'Manage Client Network' | trans }} +{% endblock %} +{% set active_menu = 'proxmox' %} + +{% block content %} +
+
+

+ Manage VLAN: {{ clientnetwork.vlan }} +

+
+
+ {% set ipranges = admin.serviceproxmox_service_get_ipranges() %} +
+ + +
+ + {% set clients = admin.client_get_list() %} +
+ +
+
+
+ +
+ + +
+
+
+ +
+ +
+
+ +
+
+
+ {% endblock %} + {% block js %} + + {% endblock %} + + \ No newline at end of file diff --git a/src/html_admin/mod_serviceproxmox_ipam_iprange.html.twig b/src/html_admin/mod_serviceproxmox_ipam_iprange.html.twig new file mode 100644 index 0000000..62d3ad9 --- /dev/null +++ b/src/html_admin/mod_serviceproxmox_ipam_iprange.html.twig @@ -0,0 +1,140 @@ +{% import 'macro_functions.html.twig' as mf %} +{% extends "layout_default.html.twig" %} +{% block meta_title %} + {{ 'Manage IP Range' | trans }} +{% endblock %} +{% set active_menu = 'proxmox' %} + +{% block content %} +
+
+

+ Manage IP Range: {{ iprange.cidr }} +

+
+
+
+ + +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+ + 1 Adress, /32 + +
+
+
+ +
+ +
+
+
+ +
+
+ + +
+
+ + +
+
+
+ +
+
+
+ {% endblock %} + {% block js %} + + {% endblock %} + + \ No newline at end of file diff --git a/src/html_admin/mod_serviceproxmox_manage.html.twig b/src/html_admin/mod_serviceproxmox_manage.html.twig new file mode 100644 index 0000000..e2610bb --- /dev/null +++ b/src/html_admin/mod_serviceproxmox_manage.html.twig @@ -0,0 +1,135 @@ +{% set server = admin.serviceproxmox_server_get_from_order({"order_id":order.id}) %} + +
+

+ {{ 'Details' | trans }} +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ {{ 'Status' | trans }}: + + {{ mf.status_name(order.status) }} +
+ {{ 'Server Name' | trans }}: + + + {{ server.name }} + +
+ {{ 'Server Group' | trans }}: + + + {{ server.group }} + +
+ {{ 'Server IPv4' | trans }}: + + {{ server.ipv4 }} +
+ {{ 'Server IPv6' | trans }}: + + {{ server.ipv6 }} +
+ {{ 'Server Hostname' | trans }}: + + {{ server.hostname }} +
+ {{ 'Server root user' | trans }}: + + {{ server.root_user }} +
+ {{ 'Server root password' | trans }}: + + {{ server.root_password }} +
+ +
+ +
+

+ {{ 'Change account password'|trans }} +

+
+ +
+ +
+
+
+
+ +
+
+
+ + +
+
+ \ No newline at end of file diff --git a/src/html_admin/mod_serviceproxmox_server.html.twig b/src/html_admin/mod_serviceproxmox_server.html.twig new file mode 100644 index 0000000..640702e --- /dev/null +++ b/src/html_admin/mod_serviceproxmox_server.html.twig @@ -0,0 +1,152 @@ +{% import 'macro_functions.html.twig' as mf %} +{% extends "layout_default.html.twig" %} +{% block meta_title %} + {{ 'Manage Proxmox server' | trans }} +{% endblock %} +{% set active_menu = 'system' %} +{% block breadcrumbs %} + +{% endblock %} +{% block content %} +
+
+
+ {{ 'Manage Proxmox server' | trans }} +
+
+
+
+ + + +
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+ + +
+
+ + +
+
+
+ + + + + + + + + {{ 'Test connection' | trans }} + + +
+
+
+{% endblock %} +{% block js %}{% endblock %} diff --git a/src/html_admin/mod_serviceproxmox_settings.html.twig b/src/html_admin/mod_serviceproxmox_settings.html.twig new file mode 100644 index 0000000..6f1b75a --- /dev/null +++ b/src/html_admin/mod_serviceproxmox_settings.html.twig @@ -0,0 +1,324 @@ +{% extends request.ajax ? "layout_blank.html.twig" : "layout_default.html.twig" %} + +{% import "macro_functions.html.twig" as mf %} + +{% block meta_title %} + {{ 'Proxmox settings'|trans }} +{% endblock %} + +{% set active_menu = 'system' %} + +{% block breadcrumbs %} + +{% endblock %} + +{% block content %} +
+ + +
+
+
+ {% set params = admin.extension_config_get({ "ext": "mod_serviceproxmox" }) %} +
+ + +
+
+ +
+
+ + +
+
+ + +
+
+
+
+ +
+ +
+
+ + {{ params.cpu_overprovisioning|default(0) }}% + +
+ +
+ +
+
+ + {{ params.ram_overprovisioning|default(0) }}% + +
+ +
+ +
+
+ + {{ params.storage_overprovisioning|default(0) }}% + +
+
+
+ +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ +
+
+
+ {% set storages_grouped = admin.serviceproxmox_storage_get_list() %} + {% for type in storages_grouped %} +
+
+ {{ type.group }} Storages +
+
+ + + + + + + + + + + + + {% for storage in type.storages %} + + + + + + + + + {% endfor %} + +
+ {{ 'Storage' | trans }} + + {{ 'Servername' | trans }} + + {{ 'Content' | trans }} + + {{ 'Storage Space' | trans }} + + {{ 'Active' | trans }} + +
+ {{ storage.name }} + + {{ storage.servername }} + + {% set content = storage.content|split(',') %} + {% for line in content %} + {{ line }}
+ {% endfor %} +
+ {{ storage.used }} / {{ storage.size }} GB +
+
+
+
+ {% if storage.active == 1 %} + + {% else %} + + {% endif %} + + + + + + +
+
+
+ {% endfor %} +
+
+
+
+ +
+

{{ 'Backups'|trans }}

+
+ +
+ {% set backups = admin.serviceproxmox_proxmox_list_backups() %} +
+ +
+ +
+
+
+

+ {{ 'The backup will be restored to the backed up state of the server configuration.'|trans }}
+ {{ 'If you have added new vms or containers since the backup, they will be deleted.'|trans }}
+ + {{ 'All data will be overwritten.'|trans }} +

+
+ + +
+
+
+
+{% endblock %} + +{% block js %} +{% endblock %} diff --git a/src/html_admin/mod_serviceproxmox_storage.html.twig b/src/html_admin/mod_serviceproxmox_storage.html.twig new file mode 100644 index 0000000..23366a9 --- /dev/null +++ b/src/html_admin/mod_serviceproxmox_storage.html.twig @@ -0,0 +1,194 @@ +{% import 'macro_functions.html.twig' as mf %} +{% extends "layout_default.html.twig" %} +{% block meta_title %} + {{ 'Manage Proxmox storage' | trans }} +{% endblock %} +{% set active_menu = 'system' %} +{% block breadcrumbs %} + +{% endblock %} +{% block content %} +
+
+

Manage Storage "{{ storage.storage }}"

+
+
+
+ +
+
+ {% set usage_percent = (storage.used / storage.size * 100) | round(2) %} + {% if usage_percent < 50 %} + {% set bar_color = "bg-success" %} + {% elseif usage_percent < 80 %} + {% set bar_color = "bg-warning" %} + {% else %} + {% set bar_color = "bg-danger" %} + {% endif %} +
+ {{ usage_percent }}% +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
{{"Storage Name" | trans}}:{{ storage.storage }}
{{"Server" | trans}}:{{ storage.server_name }}
{{"Storage Type" | trans}}:{{ storage.type }}
{{"Content" | trans}}:{{ storage.content }}
{{"Usage" | trans}}:{{ storage.used }} / {{ storage.size }} GB
+ + + +
+ + + +
+ +
+
+ +
+
+
+ + +
+
+
+ {% endblock %} + {% block js %} + + + + +{% endblock %} + \ No newline at end of file diff --git a/src/html_admin/mod_serviceproxmox_storageclass.html.twig b/src/html_admin/mod_serviceproxmox_storageclass.html.twig new file mode 100644 index 0000000..0e4b40d --- /dev/null +++ b/src/html_admin/mod_serviceproxmox_storageclass.html.twig @@ -0,0 +1,43 @@ +{% import 'macro_functions.html.twig' as mf %} +{% extends "layout_default.html.twig" %} +{% block meta_title %} + {{ 'Manage Proxmox storage' | trans }} +{% endblock %} +{% set active_menu = 'system' %} + +{% block content %} +
+
+
+ Manage + + {{ storage.storage }} +
+
+
+
+ + +
+ +
+ +
+
+ + +
+
+
+ {% endblock %} + {% block js %}{% endblock %} + \ No newline at end of file diff --git a/src/html_admin/mod_serviceproxmox_templates.html.twig b/src/html_admin/mod_serviceproxmox_templates.html.twig new file mode 100644 index 0000000..6507a75 --- /dev/null +++ b/src/html_admin/mod_serviceproxmox_templates.html.twig @@ -0,0 +1,966 @@ +{% import 'macro_functions.html.twig' as mf %} +{% extends 'layout_default.html.twig' %} +{% block meta_title %} + {{ 'Proxmox Configuration Templates' | trans }} +{% endblock %} +{% set active_menu = 'proxmox' %} +{% block breadcrumbs %} + +{% endblock %} +{% block content %} + + +
+ +
+
+
+

+ {{ 'VM Config Templates' | trans }} +

+
+ + {% set vmtemplates = admin.serviceproxmox_service_get_vmtemplates() %} +
+ + All VM Config Templates + +
+ + + + + + + + + + + + + + {% for template in vmtemplates %} + + + + + + + + + + + {% endfor %} + +
+ {{ 'Name' | trans }} + + {{ 'Description' | trans }} + + {{ 'CPU Cores' | trans }} + + {{ 'Memory' | trans }} + + {{ 'Storage' | trans }} + + {{ 'Created at' | trans }} +
+ {{ template.name }} - + + {% if template.state == 'draft' %} + {% set statusClass = 'badge bg-secondary' %} + {% elseif template.state == 'active' %} + {% set statusClass = 'badge bg-success' %} + {% elseif template.state == 'inactive' %} + {% set statusClass = 'badge bg-danger' %} + {% endif %} + + + {{ template.state|capitalize }} + + + {{ template.description }} + + {{ template.cores }} + + {{ template.memory }} GB + + {# {% set content = template.storage|split('\n') %} + {% for line in content %} + {{ line }} +
+ {% endfor %} #} +
+ {{ template.created_at }} + + + + + + +
+
+
+ +
+
+
+
+ +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+ + 1 Core(s) + +
+
+
+ +
+ +
+
+ + 1 GB + +
+
+
+ +
+
+ + +
+
+ + +
+
+
+ + + +
+ +
+
+ + 1 GB + +
+
+
+ + + {% set os_list = admin.serviceproxmox_os_get_list() %} + +
+ +
+
+
+ + + {% set bios_list = admin.serviceproxmox_bios_get_list() %} + +
+ +
+
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ +
+
+ + +
+
+ + +
+
+
+
+
+ + +
+
+
+
+ + + + + + + + + + +
+ Storage TypeSize (GB)Controller
+
+
+
+
+
+
+
+ + +
+ +
+ +
+
+ + +
+ +
+ +
+
+ + 50 GB + +
+
+
+ + {% set controllers = admin.serviceproxmox_storage_controller_get_list() %} +
+ +
+
+
+ + +
+
+
+ + +
+
+
+ + + {% set lxctemplates = admin.serviceproxmox_get_lxc_config_template() %} +
+
+
+

+ {{ 'LXC Config Templates' | trans }} +

+
+ +
+ + All LXC Config Templates + +
+ + + + + + + + + + + + + + {% for template in lxctemplates %} + + + + + + + + + + {% endfor %} + +
+ {{ 'id' | trans }} + + {{ 'Template' | trans }} + + {{ 'CPU Cores' | trans }} + + {{ 'Memory' | trans }} + + {{ 'Storage' | trans }} + + {{ 'Created at' | trans }} +
+ {{ template.id }} + + {{ template.ostemplate }} + + {{ template.cores }} + + {{ template.memory }} + + {{ template.storage }} + + {{ template.created_at }} + + + + + + +
+
+
+ + {% set lxc_appliances = admin.serviceproxmox_lxc_appliance_get_list() %} +
+
+
+
+ +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+ + 1 Core(s) + +
+
+
+ +
+ +
+
+ + 1 GB + +
+
+
+ +
+ +
+
+ + 1 GB + +
+
+
+ + + +
+ +
+
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + {% set lxc_appliances = admin.serviceproxmox_lxc_appliance_get_list() %} +
+
+
+

+ {{ 'LXC Appliances' | trans }} +

+
+
+ + + + + + +
+
+
+ + All Template VMs + +
+ {% set qemu_templates = admin.serviceproxmox_service_get_qemutemplates() %} + + + + + + + + + + + {% for qemu_template in qemu_templates %} + + + + + + + {% endfor %} + +
+ {{ 'VM ID' | trans }} + + {{ 'Server' | trans }} + + {{ 'VM Name' | trans }} +
+ {{ qemu_template.vmid }} + + {{ qemu_template.server_name }} + + {{ qemu_template.name }} + + + + + + + +
+
+
+
+ + All LXC Appliances + +
+ + + + + + + + + + + + {% for lxc_appliance in lxc_appliances %} + + + + + + + + {% endfor %} + +
+ {{ 'Type' | trans }} + + {{ 'Description' | trans }} + + {{ 'Architecture' | trans }} + + {{ 'Version' | trans }} +
+ {{ lxc_appliance.type }} + + {{ lxc_appliance.description }} + + {{ lxc_appliance.architecture }} + + {{ lxc_appliance.version }} + + + + + + + +
+
+ +
+
+{% endblock %} +{% block js %} + + + +{% endblock %} \ No newline at end of file diff --git a/src/html_admin/mod_serviceproxmox_templates_lxc.html.twig b/src/html_admin/mod_serviceproxmox_templates_lxc.html.twig new file mode 100644 index 0000000..f14ff19 --- /dev/null +++ b/src/html_admin/mod_serviceproxmox_templates_lxc.html.twig @@ -0,0 +1,125 @@ +{% import 'macro_functions.html.twig' as mf %} +{% extends 'layout_default.html.twig' %} +{% block meta_title %} + {{ 'Proxmox Configuration Templates' | trans }} +{% endblock %} +{% set active_menu = 'proxmox' %} +{% block breadcrumbs %} + +{% endblock %} +{% block content %} +
+ + {% set lxc_appliances = admin.serviceproxmox_lxc_appliance_get_list() %} +
+
+ + +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+ + 1 Core(s) + +
+
+
+ +
+ +
+
+ + 1 GB + +
+
+
+ +
+ +
+
+ + 1 GB + +
+
+
+ +
+ +
+
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+
+{% endblock %} \ No newline at end of file diff --git a/src/html_admin/mod_serviceproxmox_templates_qemu.html.twig b/src/html_admin/mod_serviceproxmox_templates_qemu.html.twig new file mode 100644 index 0000000..770b33f --- /dev/null +++ b/src/html_admin/mod_serviceproxmox_templates_qemu.html.twig @@ -0,0 +1,584 @@ +{% import 'macro_functions.html.twig' as mf %} +{% extends 'layout_default.html.twig' %} +{% block meta_title %} + {{ 'Proxmox Configuration Templates' | trans }} +{% endblock %} +{% set active_menu = 'proxmox' %} +{% block breadcrumbs %} + +{% endblock %} +{% block content %} +
+
+ +
+ Show help + +
+
+ +
+ +
+ + +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+ + {{vm_config_template.cores}} Core(s) + +
+
+
+ +
+ +
+
+ + {{vm_config_template.memory}} GB + +
+
+
+ +
+
+ + +
+
+ + +
+
+
+
+ + + +
+ +
+
+ + 1 GB + +
+
+
+ + + {% set os_list = admin.serviceproxmox_os_get_list() %} + +
+ +
+
+
+ + + {% set bios_list = admin.serviceproxmox_bios_get_list() %} + +
+ +
+
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ +
+
+ + +
+
+ + +
+
+
+ +
+ +
+
+ Legend:   + + + + Test Connection   |   + + + + Prepare Server   |   + + + + Edit Server   |   + + + + Delete Server +
+
+
+
+
+ + + + + + + + + + +
+ Storage TypeSize (GB)Controller
+
+
+
+
+
+
+ + +
+ +
+ +
+
+ + +
+ +
+ +
+
+ + 50 GB + +
+
+
+ + {% set controllers = admin.serviceproxmox_storage_controller_get_list() %} +
+ +
+
+
+ + +
+
+
+ +
+
+
+ +{% endblock %} +{% block js %} + + + +{% endblock %} \ No newline at end of file diff --git a/src/html_client/mod_serviceproxmox_manage.html.twig b/src/html_client/mod_serviceproxmox_manage.html.twig new file mode 100644 index 0000000..6a8782d --- /dev/null +++ b/src/html_client/mod_serviceproxmox_manage.html.twig @@ -0,0 +1,45 @@ +{% block js %} + {# {{ '/serviceproxmox/novnc/app.js' | script_tag }} #} + + {{ '/serviceproxmox/novnc/app/styles/input.css' | stylesheet_tag }} + {{ '/serviceproxmox/novnc/app/styles/pve.css' | stylesheet_tag }} + {{ '/serviceproxmox/novnc/app/error-handler.js' | script_tag }} +{% endblock %} +{% if order.status == 'active' %} + {% set vm = client.serviceproxmox_vm_get({"order_id":order.id}) %} + {% set server = client.serviceproxmox_server_get({"order_id":order.id}) %} + + {% set proxmox_service = client.serviceproxmox_get_proxmox_service({"order_id":order.id}) %} + + +

+ Status: + + {{ server.status }} + +

+ + {{ 'Start VM' | trans }} + + + {{ 'Shutdown VM' | trans }} + + + {{ 'Reboot VM' | trans }} + +
+ +

+ {{ 'Console' | trans }} +

+ 1 + + {% if server.status == "running" %} + + {% else %} + {{ 'The server needs to be running' | trans }} + {% endif %} +

+ + {% endif %} + \ No newline at end of file diff --git a/src/html_email/mod_serviceproxmox_activated.html.twig b/src/html_email/mod_serviceproxmox_activated.html.twig new file mode 100644 index 0000000..102dd52 --- /dev/null +++ b/src/html_email/mod_serviceproxmox_activated.html.twig @@ -0,0 +1,27 @@ +{% block subject %}[{{ guest.system_company.name }}] {{ order.title }} Activated{% endblock %} +{% block content %} +{% apply markdown %} + +Hello {{ c.first_name }} {{ c.last_name }}, + +Your order **{{ order.title }}** is now active. + +Here are the details you can use to log in: +---------------- + +Address of your VM: {{ ip }} + +Username: {{ username }} + +Password: {{ password }} + + +Until you receive your VM IP address, you can already access it using the online console available at {{'login'|link({'email' : c.email }) }} + + +Your client area: {{'login'|link({'email' : c.email }) }} + +{{ guest.system_company.signature }} + +{% endapply %} +{% endblock %} diff --git a/src/html_email/mod_serviceproxmox_canceled.html.twig b/src/html_email/mod_serviceproxmox_canceled.html.twig new file mode 100644 index 0000000..bad1357 --- /dev/null +++ b/src/html_email/mod_serviceproxmox_canceled.html.twig @@ -0,0 +1,20 @@ +{% block subject %}[{{ guest.system_company.name }}] {{ order.title }} Canceled{% endblock %} +{% block content %} +{% apply markdown %} + +Hello {{ c.first_name }} {{ c.last_name }}, + +Your *{{ order.title }}* that was activated on *{{ order.activated_at|bb_date }}* is now canceled +{% if order.reason %} Reason: + +**{{ order.reason }}** {% endif %} + +If you have any questions regarding this message please login to the members area and submit a support ticket. + +Login to members area: {{'login'|link({'email' : c.email }) }} +Submit support ticket: {{ 'support'|link({'ticket' : 1}) }} + +{{ guest.system_company.signature }} + +{% endapply %} +{% endblock %} diff --git a/src/html_email/mod_serviceproxmox_renewed.html.twig b/src/html_email/mod_serviceproxmox_renewed.html.twig new file mode 100644 index 0000000..8df02a2 --- /dev/null +++ b/src/html_email/mod_serviceproxmox_renewed.html.twig @@ -0,0 +1,21 @@ +{% block subject %}[{{ guest.system_company.name }}] {{ order.title }} Renewed{% endblock %} +{% block content %} +{% apply markdown %} + +Hello {{ c.first_name }} {{ c.last_name }}, + +Your **{{ order.title }}** has been renewed. + +{% if order.expires_at %} + +Next renewal date: {{ order.expires_at|bb_date }} + +{% endif %} + +Login to members area: {{'login'|link({'email' : c.email }) }} +Manage order: {{ 'order/service/manage'|link }}/{{ order.id }} + +{{ guest.system_company.signature }} + +{% endapply %} +{% endblock %} diff --git a/src/html_email/mod_serviceproxmox_suspended.html.twig b/src/html_email/mod_serviceproxmox_suspended.html.twig new file mode 100644 index 0000000..5052b4b --- /dev/null +++ b/src/html_email/mod_serviceproxmox_suspended.html.twig @@ -0,0 +1,20 @@ +{% block subject %}[{{ guest.system_company.name }}] {{ order.title }} Suspended{% endblock %} +{% block content %} +{% apply markdown %} + +Hello {{ c.first_name }} {{ c.last_name }}, + +Your *{{ order.title }}* that was activated at *{{ order.activated_at|bb_date }}* is now suspended +{% if order.reason %} Reason: + +**{{ order.reason }}** {% endif %} + +If you have any questions regarding this message please login to the members area and submit a support ticket. + +Login to members area: {{'login'|link({'email' : c.email }) }} +Submit support ticket: {{ 'support'|link({'ticket' : 1}) }} + +{{ guest.system_company.signature }} + +{% endapply %} +{% endblock %} diff --git a/src/html_email/mod_serviceproxmox_unsuspended.html.twig b/src/html_email/mod_serviceproxmox_unsuspended.html.twig new file mode 100644 index 0000000..0856808 --- /dev/null +++ b/src/html_email/mod_serviceproxmox_unsuspended.html.twig @@ -0,0 +1,15 @@ +{% block subject %}[{{ guest.system_company.name }}] {{ order.title }} Reactivated {% endblock %} +{% block content %} +{% apply markdown %} + +Hello {{ c.first_name }} {{ c.last_name }}, + +Your *{{ order.title }}* has been reactivated. + +Login to members area: {{'login'|link({'email' : c.email }) }} +Manage order: {{ 'order/service/manage'|link }}/{{ order.id }} + +{{ guest.system_company.signature }} + +{% endapply %} +{% endblock %} diff --git a/src/migrations/0.0.5.sql b/src/migrations/0.0.5.sql new file mode 100755 index 0000000..769062e --- /dev/null +++ b/src/migrations/0.0.5.sql @@ -0,0 +1,265 @@ +-- Migration: 0.0.5 +-- Initial Migration for Proxmox Server Module + + +-- -------------------------------------------------------- +CREATE TABLE IF NOT EXISTS `service_proxmox_server` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `name` varchar(255) DEFAULT NULL, + `group` varchar(255) DEFAULT NULL, + `ipv4` varchar(255) DEFAULT NULL, + `ipv6` varchar(255) DEFAULT NULL, + `hostname` varchar(255) DEFAULT NULL, + `port` varchar(255) DEFAULT NULL, + `realm` varchar(255) DEFAULT NULL, + `cpu_cores` bigint(20) DEFAULT NULL, + `cpu_cores_allocated` bigint(20) DEFAULT NULL, + `ram` bigint(20) DEFAULT NULL, + `ram_allocated` bigint(20) DEFAULT NULL, + `root_user` varchar(255) DEFAULT NULL, + `root_password` varchar(255) DEFAULT NULL, + `tokenname` varchar(255) DEFAULT NULL, + `tokenvalue` varchar(255) DEFAULT NULL, + `config` text, + `active` bigint(20) DEFAULT NULL, + `created_at` varchar(35) DEFAULT NULL, + `updated_at` varchar(35) DEFAULT NULL, + PRIMARY KEY (`id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1; + +-- -------------------------------------------------------- +CREATE TABLE IF NOT EXISTS `service_proxmox` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `client_id` bigint(20) DEFAULT NULL, + `order_id` bigint(20) DEFAULT NULL, + `server_id` bigint(20) DEFAULT NULL, + `template_vmid` bigint(20) DEFAULT NULL, + `vm_config_template_id` bigint(20) DEFAULT NULL, + `vmid` bigint(20) DEFAULT NULL, + `ipv4` varchar(255) DEFAULT NULL, + `ipv6` varchar(255) DEFAULT NULL, + `hostname` varchar(255) DEFAULT NULL, + `password` varchar(255) DEFAULT NULL, + `config` text, + `status` varchar(255) DEFAULT NULL, + `storage` varchar(255) DEFAULT NULL, + `cpu_cores` varchar(255) DEFAULT NULL, + `created_at` varchar(35) DEFAULT NULL, + `updated_at` varchar(35) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `client_id_idx` (`client_id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1; + + +-- -------------------------------------------------------- +CREATE TABLE IF NOT EXISTS `service_proxmox_users` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `client_id` bigint(20) DEFAULT NULL, + `server_id` bigint(20) DEFAULT NULL, + `admin_tokenname` varchar(255) DEFAULT NULL, + `admin_tokenvalue` varchar(255) DEFAULT NULL, + `view_tokenname` varchar(255) DEFAULT NULL, + `view_tokenvalue` varchar(255) DEFAULT NULL, + `created_at` varchar(35) DEFAULT NULL, + `updated_at` varchar(35) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `client_id_idx` (`client_id`), + KEY `server_id_idx` (`server_id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1; + + +-- -------------------------------------------------------- +CREATE TABLE IF NOT EXISTS `service_proxmox_storageclass` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `storageclass` varchar(35) DEFAULT NULL, + PRIMARY KEY (`id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1; + +-- -------------------------------------------------------- +CREATE TABLE IF NOT EXISTS `service_proxmox_storage` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `server_id` bigint(20) DEFAULT NULL, + `storage` varchar(255) DEFAULT NULL, + `type` varchar(255) DEFAULT NULL, + `content` varchar(255) DEFAULT NULL, + `active` bigint(20) DEFAULT NULL, + `storageclass` varchar(35) DEFAULT NULL, + `size` bigint(20) DEFAULT NULL, + `used` bigint(20) DEFAULT NULL, + `free` bigint(20) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `server_storage_unique` (`server_id`, `storage`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1; + + +-- -------------------------------------------------------- +CREATE TABLE IF NOT EXISTS `service_proxmox_lxc_appliance` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `source` varchar(255) DEFAULT NULL, + `type` varchar(255) DEFAULT NULL, + `package` varchar(255) DEFAULT NULL, + `section` varchar(255) DEFAULT NULL, + + `location` varchar(255) DEFAULT NULL, + `headline` varchar(255) DEFAULT NULL, + `os` varchar(255) DEFAULT NULL, + `template` varchar(255) DEFAULT NULL, + `description` varchar(255) DEFAULT NULL, + `architecture` varchar(255) DEFAULT NULL, + `md5sum` varchar(255) DEFAULT NULL, + `sha512sum` varchar(255) DEFAULT NULL, + `version` varchar(255) DEFAULT NULL, + `infopage` varchar(255) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `sha512sum_idx` (`sha512sum`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1; + +CREATE TABLE IF NOT EXISTS `service_proxmox_vm_config_template` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `name` varchar(255) DEFAULT NULL, + `description` varchar(255) DEFAULT NULL, + `cores` bigint(20) DEFAULT NULL, + `memory` bigint(20) DEFAULT NULL, + `balloon` TINYINT(1) DEFAULT 0, + `balloon_size` bigint(20) DEFAULT NULL, + `os` varchar(255) DEFAULT NULL, + `bios` varchar(255) DEFAULT NULL, + `onboot` TINYINT(1) DEFAULT 1, + `agent` TINYINT(1) DEFAULT 1, + `created_at` varchar(35) DEFAULT NULL, + `updated_at` varchar(35) DEFAULT NULL, + PRIMARY KEY (`id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1; + +-- -------------------------------------------------------- +-- Create vm storage template table +-- -------------------------------------------------------- +CREATE TABLE IF NOT EXISTS `service_proxmox_vm_storage_template` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `template_id` bigint(20) DEFAULT NULL, + `storage_type` varchar(255) DEFAULT NULL, + `size` bigint(20) DEFAULT NULL, + `format` varchar(255) DEFAULT NULL, + `created_at` varchar(35) DEFAULT NULL, + `updated_at` varchar(35) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `template_id_idx` (`template_id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1; + + +-- -------------------------------------------------------- +-- Create vm network template table +-- -------------------------------------------------------- +CREATE TABLE IF NOT EXISTS `service_proxmox_vm_network_template` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `template_id` bigint(20) DEFAULT NULL, + `network_type` varchar(255) DEFAULT NULL, + `model` varchar(255) DEFAULT NULL, + `macaddr` varchar(255) DEFAULT NULL, + `bridge` varchar(255) DEFAULT NULL, + `tag` bigint(20) DEFAULT NULL, + `firewall` TINYINT(1) DEFAULT 0, + `queues` bigint(20) DEFAULT NULL, + `created_at` varchar(35) DEFAULT NULL, + `updated_at` varchar(35) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `template_id_idx` (`template_id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1; + + CREATE TABLE IF NOT EXISTS `service_proxmox_lxc_config_template` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `template_id` bigint(20) DEFAULT NULL, + `name` varchar(255) DEFAULT NULL, + `cores` bigint(20) DEFAULT NULL, + `description` varchar(255) DEFAULT NULL, + `memory` bigint(20) DEFAULT NULL, + `swap` bigint(20) DEFAULT NULL, + `ostemplate` varchar(255) DEFAULT NULL, + `onboot` TINYINT(1) DEFAULT 1, + `created_at` varchar(35) DEFAULT NULL, + `updated_at` varchar(35) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `template_id_idx` (`template_id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1; + +-- -------------------------------------------------------- +-- add lxc storage template table with a foreign key to service_proxmox_lxc_template +-- -------------------------------------------------------- +CREATE TABLE IF NOT EXISTS `service_proxmox_lxc_storage_template` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `template_id` bigint(20) DEFAULT NULL, + `storage_type` varchar(255) DEFAULT NULL, + `size` bigint(20) DEFAULT NULL, + `format` varchar(255) DEFAULT NULL, + `created_at` varchar(35) DEFAULT NULL, + `updated_at` varchar(35) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `template_id_idx` (`template_id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1; + +-- -------------------------------------------------------- +-- add lxc network template table with a foreign key to service_proxmox_lxc_template +-- -------------------------------------------------------- +CREATE TABLE IF NOT EXISTS `service_proxmox_lxc_network_template` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `template_id` bigint(20) DEFAULT NULL, + `network_type` varchar(255) DEFAULT NULL, + `model` varchar(255) DEFAULT NULL, + `macaddr` varchar(255) DEFAULT NULL, + `bridge` varchar(255) DEFAULT NULL, + `tag` bigint(20) DEFAULT NULL, + `firewall` TINYINT(1) DEFAULT 0, + `queues` bigint(20) DEFAULT NULL, + `created_at` varchar(35) DEFAULT NULL, + `updated_at` varchar(35) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `template_id_idx` (`template_id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1; +-- -------------------------------------------------------- +-- New Table: service_proxmox_client_vlan +-- -------------------------------------------------------- +-- Table to store client vlans +-- -------------------------------------------------------- +CREATE TABLE IF NOT EXISTS `service_proxmox_client_vlan` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `client_id` bigint(20) DEFAULT NULL, + `vlan` bigint(20) DEFAULT NULL, + `ip_range` varchar(255) DEFAULT NULL, + `created_at` varchar(35) DEFAULT NULL, + `updated_at` varchar(35) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `client_id_idx` (`client_id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1; + +-- -------------------------------------------------------- +-- New Table: service_proxmox_ip_range +-- -------------------------------------------------------- +-- Table to store ip networks +-- -------------------------------------------------------- +CREATE TABLE IF NOT EXISTS `service_proxmox_ip_range` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `cidr` varchar(255) DEFAULT NULL, + `gateway` varchar(255) DEFAULT NULL, + `broadcast` varchar(255) DEFAULT NULL, + `type` varchar(255) DEFAULT NULL, + `created_at` varchar(35) DEFAULT NULL, + `updated_at` varchar(35) DEFAULT NULL, + PRIMARY KEY (`id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1; + + + +-- add template vm table for storing information about qemu_template VMs +-- -------------------------------------------------------- +CREATE TABLE IF NOT EXISTS `service_proxmox_qemu_template` ( + `id` bigint(20) NOT NULL, + `server_id` bigint(20) DEFAULT NULL, + `name` varchar(255) DEFAULT NULL, + `created_at` varchar(35) DEFAULT NULL, + `updated_at` varchar(35) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `id_idx` (`id`), + UNIQUE KEY `vmid_server_id_idx` (`id`, `server_id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1; +-- -------------------------------------------------------- +COMMIT; diff --git a/src/migrations/0.0.6.sql b/src/migrations/0.0.6.sql new file mode 100755 index 0000000..5beae37 --- /dev/null +++ b/src/migrations/0.0.6.sql @@ -0,0 +1,19 @@ +-- -------------------------------------------------------- +-- increment all tables to 0.0.6 +-- -------------------------------------------------------- +ALTER TABLE `service_proxmox_server` COMMENT = '0.0.6'; +ALTER TABLE `service_proxmox` COMMENT = '0.0.6'; +ALTER TABLE `service_proxmox_users` COMMENT = '0.0.6'; +ALTER TABLE `service_proxmox_storageclass` COMMENT = '0.0.6'; +ALTER TABLE `service_proxmox_storage` COMMENT = '0.0.6'; +ALTER TABLE `service_proxmox_lxc_appliance` COMMENT = '0.0.6'; +ALTER TABLE `service_proxmox_vm_config_template` COMMENT = '0.0.6'; +ALTER TABLE `service_proxmox_vm_storage_template` COMMENT = '0.0.6'; +ALTER TABLE `service_proxmox_vm_network_template` COMMENT = '0.0.6'; +ALTER TABLE `service_proxmox_lxc_config_template` COMMENT = '0.0.6'; +ALTER TABLE `service_proxmox_lxc_storage_template` COMMENT = '0.0.6'; +ALTER TABLE `service_proxmox_lxc_network_template` COMMENT = '0.0.6'; +ALTER TABLE `service_proxmox_qemu_template` COMMENT = '0.0.6'; +ALTER TABLE `service_proxmox_client_vlan` COMMENT = '0.0.6'; +ALTER TABLE `service_proxmox_ip_range` COMMENT = '0.0.6'; +-- -------------------------------------------------------- \ No newline at end of file diff --git a/src/migrations/0.0.7.sql b/src/migrations/0.0.7.sql new file mode 100644 index 0000000..6b3d8d8 --- /dev/null +++ b/src/migrations/0.0.7.sql @@ -0,0 +1,84 @@ +-- Migration: 0.0.7 +-- -------------------------------------------------------- +-- Add new table for ipam settings +-- -------------------------------------------------------- +-- Fields: Minimum Network size (default 24), Maximum Network size (default 23), DNS Server 1, DNS Server 2 +-- -------------------------------------------------------- +CREATE TABLE IF NOT EXISTS `service_proxmox_ipam_settings` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `min_network_size` int(11) NOT NULL DEFAULT '24', + `max_network_size` int(11) NOT NULL DEFAULT '23', + `dns_server_1` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', + `dns_server_2` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', + `use_proxmox_sdn` tinyint(1) NOT NULL DEFAULT '0', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- -------------------------------------------------------- +-- Add new table for ip adresses +-- -------------------------------------------------------- +-- Fields: IP address, ip range, dedicated, gateway, vlan + +CREATE TABLE IF NOT EXISTS `service_proxmox_ipadress` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `ip` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `ip_range_id` int(11) NOT NULL, + `dedicated` tinyint(1) NOT NULL DEFAULT '0', + `gateway` tinyint(1) NOT NULL DEFAULT '0', + `vlan` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + KEY `ip_range_id` (`ip_range_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- -------------------------------------------------------- +-- alter service_proxmox_qemu_template to add auto increment to id, and add vmid field after id +-- -------------------------------------------------------- +ALTER TABLE `service_proxmox_qemu_template` CHANGE `id` `id` INT(11) NOT NULL AUTO_INCREMENT; +ALTER TABLE `service_proxmox_qemu_template` ADD `vmid` INT(11) NOT NULL AFTER `id`; + +-- -------------------------------------------------------- +-- alter service_proxmox_ip_range to add network field after gateway +-- -------------------------------------------------------- +ALTER TABLE `service_proxmox_ip_range` ADD `network` VARCHAR(255) NOT NULL AFTER `gateway`; + +-- -------------------------------------------------------- +-- alter service_proxmox_vm_config_template to add state field after id +-- -------------------------------------------------------- +ALTER TABLE `service_proxmox_vm_config_template` ADD `state` VARCHAR(255) NOT NULL AFTER `id`; +-- -------------------------------------------------------- +-- insert state "draft" for all existing templates + +UPDATE `service_proxmox_vm_config_template` SET `state` = 'draft'; + +-- -------------------------------------------------------- +-- add new table for tags and tag relations +-- -------------------------------------------------------- +CREATE TABLE IF NOT EXISTS `service_proxmox_tag` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- -------------------------------------------------------- +-- increment all tables to 0.0.7 +-- -------------------------------------------------------- +ALTER TABLE `service_proxmox_server` COMMENT = '0.0.7'; +ALTER TABLE `service_proxmox` COMMENT = '0.0.7'; +ALTER TABLE `service_proxmox_users` COMMENT = '0.0.7'; +ALTER TABLE `service_proxmox_storageclass` COMMENT = '0.0.7'; +ALTER TABLE `service_proxmox_storage` COMMENT = '0.0.7'; +ALTER TABLE `service_proxmox_lxc_appliance` COMMENT = '0.0.7'; +ALTER TABLE `service_proxmox_vm_config_template` COMMENT = '0.0.7'; +ALTER TABLE `service_proxmox_vm_storage_template` COMMENT = '0.0.7'; +ALTER TABLE `service_proxmox_vm_network_template` COMMENT = '0.0.7'; +ALTER TABLE `service_proxmox_lxc_config_template` COMMENT = '0.0.7'; +ALTER TABLE `service_proxmox_lxc_storage_template` COMMENT = '0.0.7'; +ALTER TABLE `service_proxmox_lxc_network_template` COMMENT = '0.0.7'; +ALTER TABLE `service_proxmox_qemu_template` COMMENT = '0.0.7'; +ALTER TABLE `service_proxmox_client_vlan` COMMENT = '0.0.7'; +ALTER TABLE `service_proxmox_ip_range` COMMENT = '0.0.7'; +ALTER TABLE `service_proxmox_ipam_settings` COMMENT = '0.0.7'; +ALTER TABLE `service_proxmox_ipadress` COMMENT = '0.0.7'; +ALTER TABLE `service_proxmox_tag` COMMENT = '0.0.7'; +-- -------------------------------------------------------- \ No newline at end of file diff --git a/src/migrations/0.0.8.sql b/src/migrations/0.0.8.sql new file mode 100755 index 0000000..459cdff --- /dev/null +++ b/src/migrations/0.0.8.sql @@ -0,0 +1,38 @@ +-- -------------------------------------------------------- +-- alter service_storage_proxmox to change storageclass from VARCHAR to text +-- -------------------------------------------------------- +ALTER TABLE `service_proxmox_storage` CHANGE `storageclass` `storageclass` TEXT DEFAULT NULL; + +-- -------------------------------------------------------- +-- alter `service_proxmox_vm_storage_template` so `format` is called `controller` +-- -------------------------------------------------------- +ALTER TABLE `service_proxmox_vm_storage_template` CHANGE `format` `controller` VARCHAR(255) DEFAULT NULL; + + +-- -------------------------------------------------------- +-- increment all tables to 0.0.8 +-- -------------------------------------------------------- +ALTER TABLE `service_proxmox_server` COMMENT = '0.0.8'; +ALTER TABLE `service_proxmox` COMMENT = '0.0.8'; +ALTER TABLE `service_proxmox_users` COMMENT = '0.0.8'; +ALTER TABLE `service_proxmox_storageclass` COMMENT = '0.0.8'; +ALTER TABLE `service_proxmox_storage` COMMENT = '0.0.8'; +ALTER TABLE `service_proxmox_lxc_appliance` COMMENT = '0.0.8'; +ALTER TABLE `service_proxmox_vm_config_template` COMMENT = '0.0.8'; +ALTER TABLE `service_proxmox_vm_storage_template` COMMENT = '0.0.8'; +ALTER TABLE `service_proxmox_vm_network_template` COMMENT = '0.0.8'; +ALTER TABLE `service_proxmox_lxc_config_template` COMMENT = '0.0.8'; +ALTER TABLE `service_proxmox_lxc_storage_template` COMMENT = '0.0.8'; +ALTER TABLE `service_proxmox_lxc_network_template` COMMENT = '0.0.8'; +ALTER TABLE `service_proxmox_qemu_template` COMMENT = '0.0.8'; +ALTER TABLE `service_proxmox_client_vlan` COMMENT = '0.0.8'; +ALTER TABLE `service_proxmox_ip_range` COMMENT = '0.0.8'; +ALTER TABLE `service_proxmox_ipam_settings` COMMENT = '0.0.8'; +ALTER TABLE `service_proxmox_ipadress` COMMENT = '0.0.8'; +ALTER TABLE `service_proxmox_tag` COMMENT = '0.0.8'; +-- -------------------------------------------------------- + +-- -------------------------------------------------------- +-- drop table service_proxmox_storageclass +-- -------------------------------------------------------- +DROP TABLE IF EXISTS `service_proxmox_storageclass`; \ No newline at end of file diff --git a/src/vendor/autoload.php b/src/vendor/autoload.php new file mode 100644 index 0000000..5df1dc5 --- /dev/null +++ b/src/vendor/autoload.php @@ -0,0 +1,25 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + /** @var \Closure(string):void */ + private static $includeFile; + + /** @var string|null */ + private $vendorDir; + + // PSR-4 + /** + * @var array> + */ + private $prefixLengthsPsr4 = array(); + /** + * @var array> + */ + private $prefixDirsPsr4 = array(); + /** + * @var list + */ + private $fallbackDirsPsr4 = array(); + + // PSR-0 + /** + * List of PSR-0 prefixes + * + * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2'))) + * + * @var array>> + */ + private $prefixesPsr0 = array(); + /** + * @var list + */ + private $fallbackDirsPsr0 = array(); + + /** @var bool */ + private $useIncludePath = false; + + /** + * @var array + */ + private $classMap = array(); + + /** @var bool */ + private $classMapAuthoritative = false; + + /** + * @var array + */ + private $missingClasses = array(); + + /** @var string|null */ + private $apcuPrefix; + + /** + * @var array + */ + private static $registeredLoaders = array(); + + /** + * @param string|null $vendorDir + */ + public function __construct($vendorDir = null) + { + $this->vendorDir = $vendorDir; + self::initializeIncludeClosure(); + } + + /** + * @return array> + */ + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); + } + + return array(); + } + + /** + * @return array> + */ + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + /** + * @return list + */ + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + /** + * @return list + */ + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + /** + * @return array Array of classname => path + */ + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + * + * @return void + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + * + * @return void + */ + public function add($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 base directories + * + * @return void + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + * + * @return void + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + * + * @return void + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + * + * @return void + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + * + * @return void + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + + if (null === $this->vendorDir) { + return; + } + + if ($prepend) { + self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; + } else { + unset(self::$registeredLoaders[$this->vendorDir]); + self::$registeredLoaders[$this->vendorDir] = $this; + } + } + + /** + * Unregisters this instance as an autoloader. + * + * @return void + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + + if (null !== $this->vendorDir) { + unset(self::$registeredLoaders[$this->vendorDir]); + } + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return true|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + $includeFile = self::$includeFile; + $includeFile($file); + + return true; + } + + return null; + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + /** + * Returns the currently registered loaders keyed by their corresponding vendor directories. + * + * @return array + */ + public static function getRegisteredLoaders() + { + return self::$registeredLoaders; + } + + /** + * @param string $class + * @param string $ext + * @return string|false + */ + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } + + /** + * @return void + */ + private static function initializeIncludeClosure() + { + if (self::$includeFile !== null) { + return; + } + + /** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + */ + self::$includeFile = \Closure::bind(static function($file) { + include $file; + }, null, null); + } +} diff --git a/src/vendor/composer/InstalledVersions.php b/src/vendor/composer/InstalledVersions.php new file mode 100644 index 0000000..51e734a --- /dev/null +++ b/src/vendor/composer/InstalledVersions.php @@ -0,0 +1,359 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Autoload\ClassLoader; +use Composer\Semver\VersionParser; + +/** + * This class is copied in every Composer installed project and available to all + * + * See also https://getcomposer.org/doc/07-runtime.md#installed-versions + * + * To require its presence, you can require `composer-runtime-api ^2.0` + * + * @final + */ +class InstalledVersions +{ + /** + * @var mixed[]|null + * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null + */ + private static $installed; + + /** + * @var bool|null + */ + private static $canGetVendors; + + /** + * @var array[] + * @psalm-var array}> + */ + private static $installedByVendor = array(); + + /** + * Returns a list of all package names which are present, either by being installed, replaced or provided + * + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackages() + { + $packages = array(); + foreach (self::getInstalled() as $installed) { + $packages[] = array_keys($installed['versions']); + } + + if (1 === \count($packages)) { + return $packages[0]; + } + + return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); + } + + /** + * Returns a list of all package names with a specific type e.g. 'library' + * + * @param string $type + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackagesByType($type) + { + $packagesByType = array(); + + foreach (self::getInstalled() as $installed) { + foreach ($installed['versions'] as $name => $package) { + if (isset($package['type']) && $package['type'] === $type) { + $packagesByType[] = $name; + } + } + } + + return $packagesByType; + } + + /** + * Checks whether the given package is installed + * + * This also returns true if the package name is provided or replaced by another package + * + * @param string $packageName + * @param bool $includeDevRequirements + * @return bool + */ + public static function isInstalled($packageName, $includeDevRequirements = true) + { + foreach (self::getInstalled() as $installed) { + if (isset($installed['versions'][$packageName])) { + return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; + } + } + + return false; + } + + /** + * Checks whether the given package satisfies a version constraint + * + * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: + * + * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') + * + * @param VersionParser $parser Install composer/semver to have access to this class and functionality + * @param string $packageName + * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package + * @return bool + */ + public static function satisfies(VersionParser $parser, $packageName, $constraint) + { + $constraint = $parser->parseConstraints((string) $constraint); + $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + + return $provided->matches($constraint); + } + + /** + * Returns a version constraint representing all the range(s) which are installed for a given package + * + * It is easier to use this via isInstalled() with the $constraint argument if you need to check + * whether a given version of a package is installed, and not just whether it exists + * + * @param string $packageName + * @return string Version constraint usable with composer/semver + */ + public static function getVersionRanges($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + $ranges = array(); + if (isset($installed['versions'][$packageName]['pretty_version'])) { + $ranges[] = $installed['versions'][$packageName]['pretty_version']; + } + if (array_key_exists('aliases', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); + } + if (array_key_exists('replaced', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); + } + if (array_key_exists('provided', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); + } + + return implode(' || ', $ranges); + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['version'])) { + return null; + } + + return $installed['versions'][$packageName]['version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getPrettyVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['pretty_version'])) { + return null; + } + + return $installed['versions'][$packageName]['pretty_version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference + */ + public static function getReference($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['reference'])) { + return null; + } + + return $installed['versions'][$packageName]['reference']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. + */ + public static function getInstallPath($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @return array + * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} + */ + public static function getRootPackage() + { + $installed = self::getInstalled(); + + return $installed[0]['root']; + } + + /** + * Returns the raw installed.php data for custom implementations + * + * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. + * @return array[] + * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} + */ + public static function getRawData() + { + @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = include __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + + return self::$installed; + } + + /** + * Returns the raw data of all installed.php which are currently loaded for custom implementations + * + * @return array[] + * @psalm-return list}> + */ + public static function getAllRawData() + { + return self::getInstalled(); + } + + /** + * Lets you reload the static array from another file + * + * This is only useful for complex integrations in which a project needs to use + * this class but then also needs to execute another project's autoloader in process, + * and wants to ensure both projects have access to their version of installed.php. + * + * A typical case would be PHPUnit, where it would need to make sure it reads all + * the data it needs from this class, then call reload() with + * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure + * the project in which it runs can then also use this class safely, without + * interference between PHPUnit's dependencies and the project's dependencies. + * + * @param array[] $data A vendor/composer/installed.php data set + * @return void + * + * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data + */ + public static function reload($data) + { + self::$installed = $data; + self::$installedByVendor = array(); + } + + /** + * @return array[] + * @psalm-return list}> + */ + private static function getInstalled() + { + if (null === self::$canGetVendors) { + self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); + } + + $installed = array(); + + if (self::$canGetVendors) { + foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { + if (isset(self::$installedByVendor[$vendorDir])) { + $installed[] = self::$installedByVendor[$vendorDir]; + } elseif (is_file($vendorDir.'/composer/installed.php')) { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require $vendorDir.'/composer/installed.php'; + $installed[] = self::$installedByVendor[$vendorDir] = $required; + if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { + self::$installed = $installed[count($installed) - 1]; + } + } + } + } + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require __DIR__ . '/installed.php'; + self::$installed = $required; + } else { + self::$installed = array(); + } + } + + if (self::$installed !== array()) { + $installed[] = self::$installed; + } + + return $installed; + } +} diff --git a/src/vendor/composer/LICENSE b/src/vendor/composer/LICENSE new file mode 100644 index 0000000..f27399a --- /dev/null +++ b/src/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/src/vendor/composer/autoload_classmap.php b/src/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..de9959c --- /dev/null +++ b/src/vendor/composer/autoload_classmap.php @@ -0,0 +1,18 @@ + $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', + 'CURLStringFile' => $vendorDir . '/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php', + 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', + 'JsonException' => $vendorDir . '/symfony/polyfill-php73/Resources/stubs/JsonException.php', + 'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', + 'ReturnTypeWillChange' => $vendorDir . '/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php', + 'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', + 'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', + 'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', +); diff --git a/src/vendor/composer/autoload_files.php b/src/vendor/composer/autoload_files.php new file mode 100644 index 0000000..5f83e89 --- /dev/null +++ b/src/vendor/composer/autoload_files.php @@ -0,0 +1,15 @@ + $vendorDir . '/symfony/deprecation-contracts/function.php', + 'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', + '0d59ee240a4cd96ddbb4ff164fccea4d' => $vendorDir . '/symfony/polyfill-php73/bootstrap.php', + '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', + '23c18046f52bef3eea034657bafda50f' => $vendorDir . '/symfony/polyfill-php81/bootstrap.php', +); diff --git a/src/vendor/composer/autoload_namespaces.php b/src/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..15a2ff3 --- /dev/null +++ b/src/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ + array($vendorDir . '/symfony/polyfill-php81'), + 'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'), + 'Symfony\\Polyfill\\Php73\\' => array($vendorDir . '/symfony/polyfill-php73'), + 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'), + 'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'), + 'Symfony\\Contracts\\Translation\\' => array($vendorDir . '/symfony/translation-contracts'), + 'Symfony\\Contracts\\Service\\' => array($vendorDir . '/symfony/service-contracts'), + 'Symfony\\Contracts\\HttpClient\\' => array($vendorDir . '/symfony/http-client-contracts'), + 'Symfony\\Component\\Validator\\' => array($vendorDir . '/symfony/validator'), + 'Symfony\\Component\\PVE2API\\' => array($baseDir . '/src', $vendorDir . '/pve2-api-client/pve2-api-client/src'), + 'Symfony\\Component\\HttpFoundation\\' => array($vendorDir . '/symfony/http-foundation'), + 'Symfony\\Component\\HttpClient\\' => array($vendorDir . '/symfony/http-client'), + 'Psr\\Log\\' => array($vendorDir . '/psr/log/src'), + 'Psr\\Container\\' => array($vendorDir . '/psr/container/src'), +); diff --git a/src/vendor/composer/autoload_real.php b/src/vendor/composer/autoload_real.php new file mode 100644 index 0000000..5575247 --- /dev/null +++ b/src/vendor/composer/autoload_real.php @@ -0,0 +1,50 @@ +register(true); + + $filesToLoad = \Composer\Autoload\ComposerStaticInite6fff2656a1872198fe978ddb5fb3374::$files; + $requireFile = \Closure::bind(static function ($fileIdentifier, $file) { + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + + require $file; + } + }, null, null); + foreach ($filesToLoad as $fileIdentifier => $file) { + $requireFile($fileIdentifier, $file); + } + + return $loader; + } +} diff --git a/src/vendor/composer/autoload_static.php b/src/vendor/composer/autoload_static.php new file mode 100644 index 0000000..43ee848 --- /dev/null +++ b/src/vendor/composer/autoload_static.php @@ -0,0 +1,122 @@ + __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php', + 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', + '0d59ee240a4cd96ddbb4ff164fccea4d' => __DIR__ . '/..' . '/symfony/polyfill-php73/bootstrap.php', + '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', + '23c18046f52bef3eea034657bafda50f' => __DIR__ . '/..' . '/symfony/polyfill-php81/bootstrap.php', + ); + + public static $prefixLengthsPsr4 = array ( + 'S' => + array ( + 'Symfony\\Polyfill\\Php81\\' => 23, + 'Symfony\\Polyfill\\Php80\\' => 23, + 'Symfony\\Polyfill\\Php73\\' => 23, + 'Symfony\\Polyfill\\Mbstring\\' => 26, + 'Symfony\\Polyfill\\Ctype\\' => 23, + 'Symfony\\Contracts\\Translation\\' => 30, + 'Symfony\\Contracts\\Service\\' => 26, + 'Symfony\\Contracts\\HttpClient\\' => 29, + 'Symfony\\Component\\Validator\\' => 28, + 'Symfony\\Component\\PVE2API\\' => 26, + 'Symfony\\Component\\HttpFoundation\\' => 33, + 'Symfony\\Component\\HttpClient\\' => 29, + ), + 'P' => + array ( + 'Psr\\Log\\' => 8, + 'Psr\\Container\\' => 14, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'Symfony\\Polyfill\\Php81\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php81', + ), + 'Symfony\\Polyfill\\Php80\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php80', + ), + 'Symfony\\Polyfill\\Php73\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php73', + ), + 'Symfony\\Polyfill\\Mbstring\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', + ), + 'Symfony\\Polyfill\\Ctype\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-ctype', + ), + 'Symfony\\Contracts\\Translation\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/translation-contracts', + ), + 'Symfony\\Contracts\\Service\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/service-contracts', + ), + 'Symfony\\Contracts\\HttpClient\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/http-client-contracts', + ), + 'Symfony\\Component\\Validator\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/validator', + ), + 'Symfony\\Component\\PVE2API\\' => + array ( + 0 => __DIR__ . '/../..' . '/src', + 1 => __DIR__ . '/..' . '/pve2-api-client/pve2-api-client/src', + ), + 'Symfony\\Component\\HttpFoundation\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/http-foundation', + ), + 'Symfony\\Component\\HttpClient\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/http-client', + ), + 'Psr\\Log\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/log/src', + ), + 'Psr\\Container\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/container/src', + ), + ); + + public static $classMap = array ( + 'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', + 'CURLStringFile' => __DIR__ . '/..' . '/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php', + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + 'JsonException' => __DIR__ . '/..' . '/symfony/polyfill-php73/Resources/stubs/JsonException.php', + 'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', + 'ReturnTypeWillChange' => __DIR__ . '/..' . '/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php', + 'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', + 'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', + 'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInite6fff2656a1872198fe978ddb5fb3374::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInite6fff2656a1872198fe978ddb5fb3374::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInite6fff2656a1872198fe978ddb5fb3374::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/src/vendor/composer/installed.json b/src/vendor/composer/installed.json new file mode 100644 index 0000000..f6f47e4 --- /dev/null +++ b/src/vendor/composer/installed.json @@ -0,0 +1,1191 @@ +{ + "packages": [ + { + "name": "psr/container", + "version": "2.0.2", + "version_normalized": "2.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "time": "2021-11-05T16:47:00+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "install-path": "../psr/container" + }, + { + "name": "psr/log", + "version": "3.0.0", + "version_normalized": "3.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "time": "2021-07-14T16:46:02+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "install-path": "../psr/log" + }, + { + "name": "pve2-api-client/pve2-api-client", + "version": "v1.0.0", + "version_normalized": "1.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/Anuril/symfony-pve2-api-client.git", + "reference": "7f05250bbacc8cf4ed2ea8deb826a731121f9416" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Anuril/symfony-pve2-api-client/zipball/7f05250bbacc8cf4ed2ea8deb826a731121f9416", + "reference": "7f05250bbacc8cf4ed2ea8deb826a731121f9416", + "shasum": "" + }, + "require": { + "php": "^8.0|^8.2", + "symfony/http-client": "^5.3", + "symfony/http-foundation": "^5.3", + "symfony/validator": "^5.3" + }, + "require-dev": { + "phpstan/extension-installer": "^1.2", + "phpstan/phpstan-deprecation-rules": "^1.1", + "phpunit/phpunit": "^9.6" + }, + "time": "2023-09-29T15:04:13+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\PVE2API\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christoph Schläpfer", + "email": "chris+github@cleverly.ch" + } + ], + "description": "A Proxmox VE API v2 client with symfony", + "support": { + "source": "https://github.com/Anuril/symfony-pve2-api-client/tree/v1.0.0" + }, + "install-path": "../pve2-api-client/pve2-api-client" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.3.0", + "version_normalized": "3.3.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "time": "2023-05-23T14:45:45+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/deprecation-contracts" + }, + { + "name": "symfony/http-client", + "version": "v5.4.26", + "version_normalized": "5.4.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client.git", + "reference": "19d48ef7f38e5057ed1789a503cd3eccef039bce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client/zipball/19d48ef7f38e5057ed1789a503cd3eccef039bce", + "reference": "19d48ef7f38e5057ed1789a503cd3eccef039bce", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/http-client-contracts": "^2.4", + "symfony/polyfill-php73": "^1.11", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.0|^2|^3" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "2.4" + }, + "require-dev": { + "amphp/amp": "^2.5", + "amphp/http-client": "^4.2.1", + "amphp/http-tunnel": "^1.0", + "amphp/socket": "^1.1", + "guzzlehttp/promises": "^1.4", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "php-http/message-factory": "^1.0", + "psr/http-client": "^1.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/http-kernel": "^4.4.13|^5.1.5|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/stopwatch": "^4.4|^5.0|^6.0" + }, + "time": "2023-07-03T12:14:50+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", + "homepage": "https://symfony.com", + "keywords": [ + "http" + ], + "support": { + "source": "https://github.com/symfony/http-client/tree/v5.4.26" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/http-client" + }, + { + "name": "symfony/http-client-contracts", + "version": "v2.5.2", + "version_normalized": "2.5.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "ba6a9f0e8f3edd190520ee3b9a958596b6ca2e70" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/ba6a9f0e8f3edd190520ee3b9a958596b6ca2e70", + "reference": "ba6a9f0e8f3edd190520ee3b9a958596b6ca2e70", + "shasum": "" + }, + "require": { + "php": ">=7.2.5" + }, + "suggest": { + "symfony/http-client-implementation": "" + }, + "time": "2022-04-12T15:48:08+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Contracts\\HttpClient\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to HTTP clients", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/http-client-contracts/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/http-client-contracts" + }, + { + "name": "symfony/http-foundation", + "version": "v5.4.28", + "version_normalized": "5.4.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "365992c83a836dfe635f1e903ccca43ee03d3dd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/365992c83a836dfe635f1e903ccca43ee03d3dd2", + "reference": "365992c83a836dfe635f1e903ccca43ee03d3dd2", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "predis/predis": "~1.0", + "symfony/cache": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4", + "symfony/mime": "^4.4|^5.0|^6.0", + "symfony/rate-limiter": "^5.2|^6.0" + }, + "suggest": { + "symfony/mime": "To use the file extension guesser" + }, + "time": "2023-08-21T07:23:18+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Defines an object-oriented layer for the HTTP specification", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-foundation/tree/v5.4.28" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/http-foundation" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.28.0", + "version_normalized": "1.28.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", + "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "time": "2023-01-26T09:26:14+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-ctype" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.28.0", + "version_normalized": "1.28.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "42292d99c55abe617799667f454222c54c60e229" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", + "reference": "42292d99c55abe617799667f454222c54c60e229", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "time": "2023-07-28T09:04:16+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-mbstring" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.28.0", + "version_normalized": "1.28.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "fe2f306d1d9d346a7fee353d0d5012e401e984b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fe2f306d1d9d346a7fee353d0d5012e401e984b5", + "reference": "fe2f306d1d9d346a7fee353d0d5012e401e984b5", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "time": "2023-01-26T09:26:14+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.28.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-php73" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.28.0", + "version_normalized": "1.28.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "time": "2023-01-26T09:26:14+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-php80" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.28.0", + "version_normalized": "1.28.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "7581cd600fa9fd681b797d00b02f068e2f13263b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/7581cd600fa9fd681b797d00b02f068e2f13263b", + "reference": "7581cd600fa9fd681b797d00b02f068e2f13263b", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "time": "2023-01-26T09:26:14+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.28.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-php81" + }, + { + "name": "symfony/service-contracts", + "version": "v3.3.0", + "version_normalized": "3.3.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/40da9cc13ec349d9e4966ce18b5fbcd724ab10a4", + "reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^2.0" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "time": "2023-05-23T14:45:45+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/service-contracts" + }, + { + "name": "symfony/translation-contracts", + "version": "v3.3.0", + "version_normalized": "3.3.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "02c24deb352fb0d79db5486c0c79905a85e37e86" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/02c24deb352fb0d79db5486c0c79905a85e37e86", + "reference": "02c24deb352fb0d79db5486c0c79905a85e37e86", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "time": "2023-05-30T17:17:10+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v3.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/translation-contracts" + }, + { + "name": "symfony/validator", + "version": "v5.4.28", + "version_normalized": "5.4.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/validator.git", + "reference": "0acdcb86a8fc5ffd71c3b060184d2ed20a76a2c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/validator/zipball/0acdcb86a8fc5ffd71c3b060184d2ed20a76a2c9", + "reference": "0acdcb86a8fc5ffd71c3b060184d2ed20a76a2c9", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "~1.0", + "symfony/polyfill-php80": "^1.16", + "symfony/polyfill-php81": "^1.22", + "symfony/translation-contracts": "^1.1|^2|^3" + }, + "conflict": { + "doctrine/annotations": "<1.13", + "doctrine/cache": "<1.11", + "doctrine/lexer": "<1.1", + "symfony/dependency-injection": "<4.4", + "symfony/expression-language": "<5.1", + "symfony/http-kernel": "<4.4", + "symfony/intl": "<4.4", + "symfony/property-info": "<5.3", + "symfony/translation": "<4.4", + "symfony/yaml": "<4.4" + }, + "require-dev": { + "doctrine/annotations": "^1.13|^2", + "doctrine/cache": "^1.11|^2.0", + "egulias/email-validator": "^2.1.10|^3|^4", + "symfony/cache": "^4.4|^5.0|^6.0", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/console": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/expression-language": "^5.1|^6.0", + "symfony/finder": "^4.4|^5.0|^6.0", + "symfony/http-client": "^4.4|^5.0|^6.0", + "symfony/http-foundation": "^4.4|^5.0|^6.0", + "symfony/http-kernel": "^4.4|^5.0|^6.0", + "symfony/intl": "^4.4|^5.0|^6.0", + "symfony/mime": "^4.4|^5.0|^6.0", + "symfony/property-access": "^4.4|^5.0|^6.0", + "symfony/property-info": "^5.3|^6.0", + "symfony/translation": "^4.4|^5.0|^6.0", + "symfony/yaml": "^4.4|^5.0|^6.0" + }, + "suggest": { + "egulias/email-validator": "Strict (RFC compliant) email validation", + "psr/cache-implementation": "For using the mapping cache.", + "symfony/config": "", + "symfony/expression-language": "For using the Expression validator and the ExpressionLanguageSyntax constraints", + "symfony/http-foundation": "", + "symfony/intl": "", + "symfony/property-access": "For accessing properties within comparison constraints", + "symfony/property-info": "To automatically add NotNull and Type constraints", + "symfony/translation": "For translating validation errors.", + "symfony/yaml": "" + }, + "time": "2023-08-14T13:04:17+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Validator\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to validate values", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/validator/tree/v5.4.28" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/validator" + } + ], + "dev": true, + "dev-package-names": [] +} diff --git a/src/vendor/composer/installed.php b/src/vendor/composer/installed.php new file mode 100644 index 0000000..355295d --- /dev/null +++ b/src/vendor/composer/installed.php @@ -0,0 +1,182 @@ + array( + 'name' => 'vendor/pve2-api-client', + 'pretty_version' => '0.1.0.x-dev', + 'version' => '0.1.0.9999999-dev', + 'reference' => 'abbc2ce780dfb6b6c3d6fefd047c852ad79799b7', + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev' => true, + ), + 'versions' => array( + 'php-http/async-client-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '*', + ), + ), + 'php-http/client-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '*', + ), + ), + 'psr/container' => array( + 'pretty_version' => '2.0.2', + 'version' => '2.0.2.0', + 'reference' => 'c71ecc56dfe541dbd90c5360474fbc405f8d5963', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/container', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/http-client-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0', + ), + ), + 'psr/log' => array( + 'pretty_version' => '3.0.0', + 'version' => '3.0.0.0', + 'reference' => 'fe5ea303b0887d5caefd3d431c3e61ad47037001', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/log', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'pve2-api-client/pve2-api-client' => array( + 'pretty_version' => 'v1.0.0', + 'version' => '1.0.0.0', + 'reference' => '7f05250bbacc8cf4ed2ea8deb826a731121f9416', + 'type' => 'library', + 'install_path' => __DIR__ . '/../pve2-api-client/pve2-api-client', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/deprecation-contracts' => array( + 'pretty_version' => 'v3.3.0', + 'version' => '3.3.0.0', + 'reference' => '7c3aff79d10325257a001fcf92d991f24fc967cf', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/deprecation-contracts', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/http-client' => array( + 'pretty_version' => 'v5.4.26', + 'version' => '5.4.26.0', + 'reference' => '19d48ef7f38e5057ed1789a503cd3eccef039bce', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/http-client', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/http-client-contracts' => array( + 'pretty_version' => 'v2.5.2', + 'version' => '2.5.2.0', + 'reference' => 'ba6a9f0e8f3edd190520ee3b9a958596b6ca2e70', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/http-client-contracts', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/http-client-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '2.4', + ), + ), + 'symfony/http-foundation' => array( + 'pretty_version' => 'v5.4.28', + 'version' => '5.4.28.0', + 'reference' => '365992c83a836dfe635f1e903ccca43ee03d3dd2', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/http-foundation', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/polyfill-ctype' => array( + 'pretty_version' => 'v1.28.0', + 'version' => '1.28.0.0', + 'reference' => 'ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-ctype', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/polyfill-mbstring' => array( + 'pretty_version' => 'v1.28.0', + 'version' => '1.28.0.0', + 'reference' => '42292d99c55abe617799667f454222c54c60e229', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-mbstring', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/polyfill-php73' => array( + 'pretty_version' => 'v1.28.0', + 'version' => '1.28.0.0', + 'reference' => 'fe2f306d1d9d346a7fee353d0d5012e401e984b5', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-php73', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/polyfill-php80' => array( + 'pretty_version' => 'v1.28.0', + 'version' => '1.28.0.0', + 'reference' => '6caa57379c4aec19c0a12a38b59b26487dcfe4b5', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-php80', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/polyfill-php81' => array( + 'pretty_version' => 'v1.28.0', + 'version' => '1.28.0.0', + 'reference' => '7581cd600fa9fd681b797d00b02f068e2f13263b', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-php81', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/service-contracts' => array( + 'pretty_version' => 'v3.3.0', + 'version' => '3.3.0.0', + 'reference' => '40da9cc13ec349d9e4966ce18b5fbcd724ab10a4', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/service-contracts', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/translation-contracts' => array( + 'pretty_version' => 'v3.3.0', + 'version' => '3.3.0.0', + 'reference' => '02c24deb352fb0d79db5486c0c79905a85e37e86', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/translation-contracts', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/validator' => array( + 'pretty_version' => 'v5.4.28', + 'version' => '5.4.28.0', + 'reference' => '0acdcb86a8fc5ffd71c3b060184d2ed20a76a2c9', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/validator', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'vendor/pve2-api-client' => array( + 'pretty_version' => '0.1.0.x-dev', + 'version' => '0.1.0.9999999-dev', + 'reference' => 'abbc2ce780dfb6b6c3d6fefd047c852ad79799b7', + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev_requirement' => false, + ), + ), +); diff --git a/src/vendor/composer/platform_check.php b/src/vendor/composer/platform_check.php new file mode 100644 index 0000000..4c3a5d6 --- /dev/null +++ b/src/vendor/composer/platform_check.php @@ -0,0 +1,26 @@ += 80100)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 8.1.0". You are running ' . PHP_VERSION . '.'; +} + +if ($issues) { + if (!headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + if (!ini_get('display_errors')) { + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); + } elseif (!headers_sent()) { + echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; + } + } + trigger_error( + 'Composer detected issues in your platform: ' . implode(' ', $issues), + E_USER_ERROR + ); +} diff --git a/src/vendor/psr/container/.gitignore b/src/vendor/psr/container/.gitignore new file mode 100644 index 0000000..b2395aa --- /dev/null +++ b/src/vendor/psr/container/.gitignore @@ -0,0 +1,3 @@ +composer.lock +composer.phar +/vendor/ diff --git a/src/vendor/psr/container/LICENSE b/src/vendor/psr/container/LICENSE new file mode 100644 index 0000000..2877a48 --- /dev/null +++ b/src/vendor/psr/container/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013-2016 container-interop +Copyright (c) 2016 PHP Framework Interoperability Group + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/vendor/psr/container/README.md b/src/vendor/psr/container/README.md new file mode 100644 index 0000000..1b9d9e5 --- /dev/null +++ b/src/vendor/psr/container/README.md @@ -0,0 +1,13 @@ +Container interface +============== + +This repository holds all interfaces related to [PSR-11 (Container Interface)][psr-url]. + +Note that this is not a Container implementation of its own. It is merely abstractions that describe the components of a Dependency Injection Container. + +The installable [package][package-url] and [implementations][implementation-url] are listed on Packagist. + +[psr-url]: https://www.php-fig.org/psr/psr-11/ +[package-url]: https://packagist.org/packages/psr/container +[implementation-url]: https://packagist.org/providers/psr/container-implementation + diff --git a/src/vendor/psr/container/composer.json b/src/vendor/psr/container/composer.json new file mode 100644 index 0000000..baf6cd1 --- /dev/null +++ b/src/vendor/psr/container/composer.json @@ -0,0 +1,27 @@ +{ + "name": "psr/container", + "type": "library", + "description": "Common Container Interface (PHP FIG PSR-11)", + "keywords": ["psr", "psr-11", "container", "container-interop", "container-interface"], + "homepage": "https://github.com/php-fig/container", + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "require": { + "php": ">=7.4.0" + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + } +} diff --git a/src/vendor/psr/container/src/ContainerExceptionInterface.php b/src/vendor/psr/container/src/ContainerExceptionInterface.php new file mode 100644 index 0000000..0f213f2 --- /dev/null +++ b/src/vendor/psr/container/src/ContainerExceptionInterface.php @@ -0,0 +1,12 @@ +logger = $logger; + } + + public function doSomething() + { + if ($this->logger) { + $this->logger->info('Doing work'); + } + + try { + $this->doSomethingElse(); + } catch (Exception $exception) { + $this->logger->error('Oh no!', array('exception' => $exception)); + } + + // do something useful + } +} +``` + +You can then pick one of the implementations of the interface to get a logger. + +If you want to implement the interface, you can require this package and +implement `Psr\Log\LoggerInterface` in your code. Please read the +[specification text](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) +for details. diff --git a/src/vendor/psr/log/composer.json b/src/vendor/psr/log/composer.json new file mode 100644 index 0000000..879fc6f --- /dev/null +++ b/src/vendor/psr/log/composer.json @@ -0,0 +1,26 @@ +{ + "name": "psr/log", + "description": "Common interface for logging libraries", + "keywords": ["psr", "psr-3", "log"], + "homepage": "https://github.com/php-fig/log", + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "require": { + "php": ">=8.0.0" + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + } +} diff --git a/src/vendor/psr/log/src/AbstractLogger.php b/src/vendor/psr/log/src/AbstractLogger.php new file mode 100644 index 0000000..d60a091 --- /dev/null +++ b/src/vendor/psr/log/src/AbstractLogger.php @@ -0,0 +1,15 @@ +logger = $logger; + } +} diff --git a/src/vendor/psr/log/src/LoggerInterface.php b/src/vendor/psr/log/src/LoggerInterface.php new file mode 100644 index 0000000..b3a24b5 --- /dev/null +++ b/src/vendor/psr/log/src/LoggerInterface.php @@ -0,0 +1,125 @@ +log(LogLevel::EMERGENCY, $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string|\Stringable $message + * @param array $context + * + * @return void + */ + public function alert(string|\Stringable $message, array $context = []): void + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string|\Stringable $message + * @param array $context + * + * @return void + */ + public function critical(string|\Stringable $message, array $context = []): void + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string|\Stringable $message + * @param array $context + * + * @return void + */ + public function error(string|\Stringable $message, array $context = []): void + { + $this->log(LogLevel::ERROR, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string|\Stringable $message + * @param array $context + * + * @return void + */ + public function warning(string|\Stringable $message, array $context = []): void + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string|\Stringable $message + * @param array $context + * + * @return void + */ + public function notice(string|\Stringable $message, array $context = []): void + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string|\Stringable $message + * @param array $context + * + * @return void + */ + public function info(string|\Stringable $message, array $context = []): void + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string|\Stringable $message + * @param array $context + * + * @return void + */ + public function debug(string|\Stringable $message, array $context = []): void + { + $this->log(LogLevel::DEBUG, $message, $context); + } + + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string|\Stringable $message + * @param array $context + * + * @return void + * + * @throws \Psr\Log\InvalidArgumentException + */ + abstract public function log($level, string|\Stringable $message, array $context = []): void; +} diff --git a/src/vendor/psr/log/src/NullLogger.php b/src/vendor/psr/log/src/NullLogger.php new file mode 100644 index 0000000..c1cc3c0 --- /dev/null +++ b/src/vendor/psr/log/src/NullLogger.php @@ -0,0 +1,30 @@ +logger) { }` + * blocks. + */ +class NullLogger extends AbstractLogger +{ + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string|\Stringable $message + * @param array $context + * + * @return void + * + * @throws \Psr\Log\InvalidArgumentException + */ + public function log($level, string|\Stringable $message, array $context = []): void + { + // noop + } +} diff --git a/src/vendor/pve2-api-client/pve2-api-client/.github/workflows/phpstan.yml b/src/vendor/pve2-api-client/pve2-api-client/.github/workflows/phpstan.yml new file mode 100755 index 0000000..f366219 --- /dev/null +++ b/src/vendor/pve2-api-client/pve2-api-client/.github/workflows/phpstan.yml @@ -0,0 +1,23 @@ +name: PHPStan + +on: [push] + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.0' + + - name: Install dependencies + run: composer install --prefer-dist --no-progress --no-suggest + + - name: Run PHPStan + run: vendor/bin/phpstan diff --git a/src/vendor/pve2-api-client/pve2-api-client/LICENSE b/src/vendor/pve2-api-client/pve2-api-client/LICENSE new file mode 100644 index 0000000..0e5f4fc --- /dev/null +++ b/src/vendor/pve2-api-client/pve2-api-client/LICENSE @@ -0,0 +1,8 @@ +Copyright (c) 2023 Christoph Schläpfer +Copyright (c) 2012 Nathan Sullivan + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/vendor/pve2-api-client/pve2-api-client/README.md b/src/vendor/pve2-api-client/pve2-api-client/README.md new file mode 100644 index 0000000..caccfab --- /dev/null +++ b/src/vendor/pve2-api-client/pve2-api-client/README.md @@ -0,0 +1,133 @@ +# PVE2 API wrapper for Symfony + +This class provides the building blocks for someone wanting to use PHP to talk to Proxmox's API, and is using symfony anyway. + +This is a fork and rewrite of [this](https://github.com/CpuID/pve2-api-php-client). +This library probably isn't a drop-in replacement for it, but it should be relatively simple to fix your code. + +Due to the nature of how the Proxmox VE API works, errors 500 / 501 will return null, otherwise requesting an invalid resource to see if it already exists doesn't work. Ideally, I'd like to work towards + + +Relatively simple piece of code, just provides a get/put/post/delete abstraction layer as methods +on top of Proxmox's REST API, while also handling the Login Ticket headers required for authentication. + + +## More information: + +See http://pve.proxmox.com/wiki/Proxmox_VE_API for information about how this API works. +API spec available at https://pve.proxmox.com/pve-docs/api-viewer/index.html + +## Requirements: ## + +PHP 8.1+ with symfony + +## Usage: ## + +Example - Return status array for each Proxmox Host in this cluster. +``` + require_once("./srv/pve2_api.class.php"); + + # You can try/catch exception handle the constructor here if you want. + $pve2 = new PVE2_API("hostname", "username", "realm", "password", port: "port", verify_ssl: true, tokenid: "tokenid", tokensecret: "tokensecret, debug: true); + # realm above can be pve, pam or any other realm available. + # if you provide tokenid and tokensecret, you can leave out username & password. + # debug generates more verbose exceptions. + # verify_ssl enables or disables ssl validation. + + + if ($pve2->login()) { + foreach ($pve2->get_node_list() as $node_name) { + print_r($pve2->get("/nodes/".$node_name."/status")); + } + } else { + print("Login to Proxmox Host failed.\n"); + exit; + } +``` + +Example - Create a new Linux Container (LXC) on the first host in the cluster. + +``` + require_once("./pve2-api-php-client/pve2_api.class.php"); + + # You can try/catch exception handle the constructor here if you want. + $pve2 = new PVE2_API("hostname", "username", "realm", "password", port: "port", verify_ssl: true, tokenid: "tokenid", tokensecret: "tokensecret, debug: true); + # realm above can be pve, pam or any other realm available. + + if ($pve2->login()) { + + # Get first node name. + $nodes = $pve2->get_node_list(); + $first_node = $nodes[0]; + unset($nodes); + + # Create a Linux Container (LXC) on the first node in the cluster. + $new_container_settings = array(); + $new_container_settings['ostemplate'] = "local:vztmpl/debian-6.0-standard_6.0-4_amd64.tar.gz"; + $new_container_settings['vmid'] = "1234"; + $new_container_settings['cpus'] = "2"; + $new_container_settings['description'] = "Test VM using Proxmox 2.0 API"; + $new_container_settings['disk'] = "8"; + $new_container_settings['hostname'] = "testapi.domain.tld"; + $new_container_settings['memory'] = "1024"; + $new_container_settings['nameserver'] = "4.2.2.1"; + + // print_r($new_container_settings); + print("---------------------------\n"); + + print_r($pve2->post("/nodes/".$first_node."/lxc", $new_container_settings)); + print("\n\n"); + } else { + print("Login to Proxmox Host failed.\n"); + exit; + } +``` + +Example - Modify DNS settings on an existing container on the first host. + +``` + require_once("./pve2-api-php-client/pve2_api.class.php"); + + # You can try/catch exception handle the constructor here if you want. + $pve2 = new PVE2_API("hostname", "username", "realm", "password", port: "port", verify_ssl: true, tokenid: "tokenid", tokensecret: "tokensecret, debug: true); + # realm above can be pve, pam or any other realm available. + + if ($pve2->login()) { + + # Get first node name. + $nodes = $pve2->get_node_list(); + $first_node = $nodes[0]; + unset($nodes); + + # Update container settings. + $container_settings = array(); + $container_settings['nameserver'] = "4.2.2.2"; + + # NOTE - replace XXXX with container ID. + var_dump($pve2->put("/nodes/".$first_node."/lxc/XXXX/config", $container_settings)); + } else { + print("Login to Proxmox Host failed.\n"); + exit; + } +``` + +Example - Delete an existing container. + +``` + require_once("./pve2-api-php-client/pve2_api.class.php"); + + # You can try/catch exception handle the constructor here if you want. + $pve2 = new PVE2_API("hostname", "username", "realm", "password", port: "port", verify_ssl: true, tokenid: "tokenid", tokensecret: "tokensecret, debug: true); + # realm above can be pve, pam or any other realm available. + + if ($pve2->login()) { + # NOTE - replace XXXX with node short name, and YYYY with container ID. + var_dump($pve2->delete("/nodes/XXXX/lxc/YYYY")); + } else { + print("Login to Proxmox Host failed.\n"); + exit; + } +``` + +Licensed under the MIT License. +See LICENSE file. diff --git a/src/vendor/pve2-api-client/pve2-api-client/composer.json b/src/vendor/pve2-api-client/pve2-api-client/composer.json new file mode 100755 index 0000000..4180979 --- /dev/null +++ b/src/vendor/pve2-api-client/pve2-api-client/composer.json @@ -0,0 +1,34 @@ +{ + "name": "pve2-api-client/pve2-api-client", + "description": "A Proxmox VE API v2 client with symfony", + "type": "library", + "require": { + "php": "^8.0|^8.2", + "symfony/http-client": "^5.3", + "symfony/http-foundation": "^5.3", + "symfony/validator": "^5.3" + }, + "require-dev": { + "phpstan/extension-installer": "^1.2", + "phpstan/phpstan-deprecation-rules": "^1.1", + "phpunit/phpunit": "^9.6" + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\PVE2API\\": "src/" + } + }, + "license": "MIT", + "authors": [ + { + "name": "Christoph Schläpfer", + "email": "chris+github@cleverly.ch" + } + ], + "minimum-stability": "stable", + "config": { + "allow-plugins": { + "phpstan/extension-installer": true + } + } +} diff --git a/src/vendor/pve2-api-client/pve2-api-client/phpstan.neon b/src/vendor/pve2-api-client/pve2-api-client/phpstan.neon new file mode 100755 index 0000000..63b0db6 --- /dev/null +++ b/src/vendor/pve2-api-client/pve2-api-client/phpstan.neon @@ -0,0 +1,5 @@ +parameters: + level: 7 + paths: + - src + treatPhpDocTypesAsCertain: false diff --git a/src/vendor/pve2-api-client/pve2-api-client/src/pve2_api.class.php b/src/vendor/pve2-api-client/pve2-api-client/src/pve2_api.class.php new file mode 100755 index 0000000..a8e76be --- /dev/null +++ b/src/vendor/pve2-api-client/pve2-api-client/src/pve2_api.class.php @@ -0,0 +1,497 @@ +|int get( string $actionPath, array $parameters = [] ) +* @method array post( string $actionPath, array $parameters = [] ) +* @method array put( string $actionPath, array $parameters = [] ) +* @method array delete( string $actionPath, array $parameters = [] ) +* +* @category Proxmox VE +* @package PVE2_API_Client +*/ + +class PVE2_API { + protected string $hostname; + protected ?string $username = null; + protected string $realm; + protected ?string $password = null; + protected int $port; + protected bool $verify_ssl; + protected ?string $tokenid = null; + protected ?string $tokensecret = null; + protected bool $api_token_access = false; + /** + * @var array|null + */ + protected ?array $login_ticket = null; + protected ?int $login_ticket_timestamp = null; + /** + * @var array|null + */ + protected ?array $clusterNodeList = null; + protected bool $debug = false; + /** + * @var array + */ + private array $supportedVmActions = [ + 'reboot', + 'reset', + 'resume', + 'shutdown', + 'start', + 'stop', + 'suspend' + ]; + + /** + * Constructor for the PVE2 API client. + */ + + public function __construct( + string $hostname, + ?string $username = null, + string $realm, + ?string $password = null, + int $port = 8006, + bool $verify_ssl = false, + ?string $tokenid = null, + ?string $tokensecret = null, + bool $debug = false + ) { + $validator = Validation::createValidator(); + + $constraints = new Assert\Collection( [ + 'hostname' => new Assert\NotBlank(), + 'realm' => new Assert\NotBlank(), + 'port' => new Assert\Range( [ 'min' => 1, 'max' => 65535 ] ), + 'verify_ssl' => new Assert\Type( 'bool' ), + ] ); + + $input = [ + 'hostname' => $hostname, + 'realm' => $realm, + 'port' => $port, + 'verify_ssl' => $verify_ssl, + ]; + + $violations = $validator->validate( $input, $constraints ); + + $violationsList = []; + foreach ( $violations as $violation ) { + $violationsList[] = $violation->getPropertyPath() . ': ' . $violation->getMessage(); + } + + if ( !empty( $violationsList ) ) { + throw new BadRequestException( 'Invalid input parameters: ' . implode( ', ', $violationsList ) ); + } + + if ( ( empty( $username ) || empty( $password ) ) && ( empty( $tokenid ) || empty( $tokensecret ) ) ) { + throw new BadRequestException( 'Either username and password OR tokenid and tokensecret must be provided.' ); + } + + if ( empty( $username ) && empty( $password ) && empty( $tokenid ) && empty( $tokensecret ) ) { + throw new BadRequestException( 'Both username/password and tokenid/tokensecret cannot be empty. At least one pair must be provided.' ); + } + + if ( gethostbyname( $hostname ) == $hostname && !filter_var( $hostname, FILTER_VALIDATE_IP ) ) { + throw new BadRequestException( "Cannot resolve {$hostname}." ); + } + + $this->hostname = $hostname; + $this->username = $username; + $this->realm = $realm; + $this->password = $password; + $this->port = $port; + $this->verify_ssl = $verify_ssl; + $this->tokenid = $tokenid; + $this->tokensecret = $tokensecret; + $this->debug = $debug; + + $this->api_token_access = !empty( $tokenid ) && !empty( $tokensecret ); + } + + /** + * Logs in to the Proxmox VE API using the provided credentials. + * + * @return bool Returns true if the login was successful, false otherwise. + */ + + public function login(): bool { + $apiUrlBase = "https://{$this->hostname}:{$this->port}/api2/json"; + $client = HttpClient::create( [ 'verify_peer' => $this->verify_ssl, 'verify_host' => $this->verify_ssl ] ); + + if ( $this->api_token_access ) { + $response = $client->request( 'GET', "$apiUrlBase/version", [ + 'headers' => [ 'Authorization' => "PVEAPIToken={$this->tokenid}={$this->tokensecret}" ], + ] ); + + if ( 200 !== $response->getStatusCode() ) { + // Handle error appropriately + return false; + } + + try { + $data = $response->toArray(); + $this->reloadNodeList(); + return true; + } catch ( JsonException $e ) { + // Handle JSON error + return false; + } + } + + $response = $client->request( 'POST', "$apiUrlBase/access/ticket", [ + 'body' => [ + 'username' => $this->username, + 'password' => $this->password, + 'realm' => $this->realm, + ] + ] ); + + if ( 200 !== $response->getStatusCode() ) { + return false; + } + + try { + $data = $response->toArray(); + $this->login_ticket = $data[ 'data' ]; + $this->login_ticket_timestamp = time(); + $this->reloadNodeList(); + return true; + } catch ( JsonException $e ) { + return false; + } + } + + /** + * Checks if the login ticket is valid. + * + * @return bool Returns true if the login ticket is valid, false otherwise. + */ + protected function checkLoginTicket(): bool { + if ( $this->api_token_access ) { + return true; + } + + if ( $this->login_ticket === null || $this->login_ticket_timestamp === null ) { + $this->resetLoginTicket(); + return false; + } + + // If the current timestamp is greater than the timestamp of the login ticket plus 7200 seconds ( 2 hours ), it is expired. + if ( time() >= $this->login_ticket_timestamp + 7200 ) { + $this->resetLoginTicket(); + return false; + } + + return true; + } + + /** + * Resets the login ticket used for authentication with the Proxmox VE API. + * + * @return void + */ + + private function resetLoginTicket(): void { + $this->login_ticket = null; + $this->login_ticket_timestamp = null; + } + + /** + * Performs an action on the Proxmox VE API. + * + * @param string $actionPath The path of the action to perform. + * @param string $httpMethod The HTTP method to use for the action. + * @param array $parameters An optional array of parameters to include in the request. + * @return mixed|int The response from the API. + */ + + private function action( string $actionPath, string $httpMethod, array $parameters = [] ): mixed { + $actionPath = $this->normalizeActionPath( $actionPath ); + + if ( !$this->checkLoginTicket() ) { + throw new PVE2_Exception( 'No valid connection to Proxmox host. No Login access ticket found, ticket expired or no API Token set up.', 3 ); + } + + $url = "https://{$this->hostname}:{$this->port}/api2/json{$actionPath}"; + + $client = HttpClient::create( [ + 'headers' => $this->buildHeaders(), + 'verify_peer' => $this->verify_ssl, + 'verify_host' => $this->verify_ssl, + ] ); + + try { + $response = $client->request( $httpMethod, $url, [ 'body' => $parameters ] ); + $statusCode = $response->getStatusCode(); + $errorMessage = "API Request failed. HTTP Response - {$statusCode}"; + if ( $this->debug ) { + $errorMessage .= PHP_EOL . "HTTP Method: {$httpMethod}" . PHP_EOL . "URL: {$url}" . PHP_EOL . 'Parameters: ' . json_encode( $parameters ) . PHP_EOL . 'Response Headers: ' . json_encode( $response->getHeaders( false ) ) . PHP_EOL . 'Response: ' . $response->getContent( false ); + } else { + $errorMessage = $response->toArray()[ 'errors' ] ?? $errorMessage; + } + + if ( $statusCode === 200 ) { + return $response->toArray()[ 'data' ] ?? true; + } + + // Write the Status code and the ReasonPhrase to the error log + error_log( "Action Failed. Status Code: {$statusCode} - " . $response->getInfo( 'response_headers' )[ 'reason_phrase' ] ); + + if ( $this->debug ) { + error_log( "Debug Information: {$errorMessage}" ); + } + + if ( $statusCode === 500 || $statusCode === 501 ) { + return null; + } + + throw new PVE2_Exception( $errorMessage, $statusCode ); + } catch ( TransportExceptionInterface $e ) { + $errorMessage = 'Transport Exception: ' . $e->getMessage(); + if ( $this->debug ) { + $errorMessage .= PHP_EOL . "HTTP Method: {$httpMethod}" . PHP_EOL . "URL: {$url}" . PHP_EOL . 'Parameters: ' . json_encode( $parameters ); + if ( isset( $response )) { + $errorMessage .= PHP_EOL . 'Response Headers: ' . json_encode( $response->getHeaders( false ) ) . PHP_EOL . 'Response: ' . $response->getContent(false); + } + } + throw new PVE2_Exception( $errorMessage, 0, $e ); + } + } + + /** + * Normalizes the given action path. + * + * @param string $actionPath The action path to be normalized. + * @return string The normalized action path. + */ + + private function normalizeActionPath( string $actionPath ): string { + return '/' . ltrim( $actionPath, '/' ); + } + + /** + * Builds an array of headers to be used in API requests. + * + * @return array An array of headers. + */ + + private function buildHeaders(): array { + $headers = [ + 'Content-Type' => 'application/x-www-form-urlencoded', + 'Accept' => 'application/json', + ]; + + if ( $this->api_token_access ) { + $headers[ 'Authorization' ] = "PVEAPIToken={$this->tokenid}={$this->tokensecret}"; + } else { + $headers[ 'CSRFPreventionToken' ] = $this->login_ticket[ 'CSRFPreventionToken' ]; + $headers[ 'Cookie' ] = 'PVEAuthCookie=' . $this->login_ticket[ 'ticket' ]; + } + + return $headers; + } + + /** + * Magic method that allows calling of PVE API methods dynamically. + * + * @param string $name The name of the method being called. + * @param array $arguments An array of arguments passed to the method. + * @return mixed The result of the method call. + */ + + public function __call( $name, $arguments ) { + if ( in_array( strtoupper( $name ), [ 'GET', 'POST', 'PUT', 'DELETE' ] ) ) { + $actionPath = $arguments[ 0 ] ?? ''; + $parameters = $arguments[ 1 ] ?? []; + return $this->action( $actionPath, strtoupper( $name ), $parameters ); + } + + throw new BadMethodCallException( "Method {$name} not exists in " . __CLASS__ ); + } + + /** + * Reloads the list of available nodes in the Proxmox VE cluster. + * + * @return bool Returns true on success, false otherwise. + */ + + public function reloadNodeList(): bool { + $nodeList = $this->get( '/nodes' ); + if ( is_array( $nodeList ) ) { + if ( count( $nodeList ) > 0) { + $this->clusterNodeList = array_map( static fn ( $node ) => $node[ 'node' ], $nodeList ); + return true; + } + } + + // Handle error according to your application’s error handling strategy + error_log( 'Empty list of nodes returned in this cluster.' ); + return false; + } + + /** + * Returns an array of nodes in the Proxmox VE cluster. + * + * @return array|null An array of nodes in the Proxmox VE cluster, or null if the request fails. + */ + + public function getNodeList(): ?array { + if ( $this->clusterNodeList === null && !$this->reloadNodeList() ) { + return null; + } + + return $this->clusterNodeList; + } + + /** + * Returns the next available VM ID. + * + * @return int|null The next available VM ID, or null if none are available. + */ + public function getNextVmid(): ?int { + /* @phpstan-ignore-next-line */ // The return type of getNextVmid() is int|null, but the return type of get() is array|int|null, and for '/cluster/nextid' it will always return int. + return $this->get( '/cluster/nextid' ) ?: null; + } + + /** + * Returns an array of virtual machines. + * + * @return array|null An array of virtual machines or null if there are no virtual machines. + */ + + public function getVms(): ?array { + $nodeList = $this->getNodeList(); + if ( !$nodeList ) { + return null; + } + + $result = []; + foreach ( $nodeList as $nodeName ) { + $vmsList = $this->get( "nodes/$nodeName/qemu/" ); + // if the vmsList is an array, add the node name to each row + if ( is_array( $vmsList ) ) { + array_walk( $vmsList, static fn ( &$row ) => $row[ 'node' ] = $nodeName ); + $result = array_merge( $result, $vmsList ); + } + } + return $result ?: null; + } + + /** + * Manage a virtual machine on a Proxmox VE node. + * + * @param string $node The name of the Proxmox VE node. + * @param int $vmid The ID of the virtual machine. + * @param string $action The action to perform on the virtual machine ( e.g. start, stop, reset ). + * @param int $timeout The maximum time in seconds to wait for the action to complete ( default: 60 ). + * + * @return bool Returns true if the action was successful, false otherwise. + */ + + public function manageVm( string $node, int $vmid, string $action, int $timeout = 60 ): bool { + if ( !in_array( $action, $this->supportedVmActions ) ) { + throw new \InvalidArgumentException( "Unsupported action: $action" ); + } + + $url = "/nodes/$node/qemu/$vmid/status/$action"; + + $parameters = [ + 'vmid' => $vmid, + 'node' => $node, + 'timeout' => $timeout + ]; + + return ( bool ) $this->post( $url, $parameters ); + } + + /** + * Clone a virtual machine on a Proxmox VE node. + * + * @param string $node The name of the Proxmox VE node. + * @param int $vmid The ID of the virtual machine to be cloned. + * @param int|null $newid The ID for the new cloned virtual machine. If not provided, the next available ID will be used. + * @return bool Returns true if the virtual machine was successfully cloned, false otherwise. + */ + + public function cloneVm( string $node, int $vmid, int $newid = null ): bool { + $newid = $newid ?? $this->getNextVmid(); + $url = "/nodes/$node/qemu/$vmid/clone"; + $parameters = [ 'vmid' => $vmid, 'node' => $node, 'newid' => $newid, 'full' => true ]; + + return ( bool ) $this->post( $url, $parameters ); + } + + /** + * Create a snapshot of a virtual machine. + * + * @param string $node The name of the Proxmox node. + * @param int $vmid The ID of the virtual machine. + * @param string|null $snapname The name of the snapshot ( optional ). + * + * @return bool Returns true on success, false otherwise. + */ + + public function snapshotVm( string $node, int $vmid, ?string $snapname = null ): bool { + $url = "/nodes/$node/qemu/$vmid/snapshot"; + $parameters = [ 'vmid' => $vmid, 'node' => $node, 'vmstate' => true, 'snapname' => $snapname ]; + + return ( bool ) $this->post( $url, $parameters ); + } + + /** + * Get the version of the Proxmox VE API. + * + * @return string|null The version of the Proxmox VE API, or null if it could not be determined. + */ + + public function getVersion(): ?string { + $version = $this->get( '/version' ); + return $version[ 'version' ] ?? null; + } +} diff --git a/src/vendor/symfony/deprecation-contracts/CHANGELOG.md b/src/vendor/symfony/deprecation-contracts/CHANGELOG.md new file mode 100644 index 0000000..7932e26 --- /dev/null +++ b/src/vendor/symfony/deprecation-contracts/CHANGELOG.md @@ -0,0 +1,5 @@ +CHANGELOG +========= + +The changelog is maintained for all Symfony contracts at the following URL: +https://github.com/symfony/contracts/blob/main/CHANGELOG.md diff --git a/src/vendor/symfony/deprecation-contracts/LICENSE b/src/vendor/symfony/deprecation-contracts/LICENSE new file mode 100644 index 0000000..0ed3a24 --- /dev/null +++ b/src/vendor/symfony/deprecation-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/vendor/symfony/deprecation-contracts/README.md b/src/vendor/symfony/deprecation-contracts/README.md new file mode 100644 index 0000000..9814864 --- /dev/null +++ b/src/vendor/symfony/deprecation-contracts/README.md @@ -0,0 +1,26 @@ +Symfony Deprecation Contracts +============================= + +A generic function and convention to trigger deprecation notices. + +This package provides a single global function named `trigger_deprecation()` that triggers silenced deprecation notices. + +By using a custom PHP error handler such as the one provided by the Symfony ErrorHandler component, +the triggered deprecations can be caught and logged for later discovery, both on dev and prod environments. + +The function requires at least 3 arguments: + - the name of the Composer package that is triggering the deprecation + - the version of the package that introduced the deprecation + - the message of the deprecation + - more arguments can be provided: they will be inserted in the message using `printf()` formatting + +Example: +```php +trigger_deprecation('symfony/blockchain', '8.9', 'Using "%s" is deprecated, use "%s" instead.', 'bitcoin', 'fabcoin'); +``` + +This will generate the following message: +`Since symfony/blockchain 8.9: Using "bitcoin" is deprecated, use "fabcoin" instead.` + +While not recommended, the deprecation notices can be completely ignored by declaring an empty +`function trigger_deprecation() {}` in your application. diff --git a/src/vendor/symfony/deprecation-contracts/composer.json b/src/vendor/symfony/deprecation-contracts/composer.json new file mode 100644 index 0000000..c6d02d8 --- /dev/null +++ b/src/vendor/symfony/deprecation-contracts/composer.json @@ -0,0 +1,35 @@ +{ + "name": "symfony/deprecation-contracts", + "type": "library", + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.1" + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + } +} diff --git a/src/vendor/symfony/deprecation-contracts/function.php b/src/vendor/symfony/deprecation-contracts/function.php new file mode 100644 index 0000000..2d56512 --- /dev/null +++ b/src/vendor/symfony/deprecation-contracts/function.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (!function_exists('trigger_deprecation')) { + /** + * Triggers a silenced deprecation notice. + * + * @param string $package The name of the Composer package that is triggering the deprecation + * @param string $version The version of the package that introduced the deprecation + * @param string $message The message of the deprecation + * @param mixed ...$args Values to insert in the message using printf() formatting + * + * @author Nicolas Grekas + */ + function trigger_deprecation(string $package, string $version, string $message, mixed ...$args): void + { + @trigger_error(($package || $version ? "Since $package $version: " : '').($args ? vsprintf($message, $args) : $message), \E_USER_DEPRECATED); + } +} diff --git a/src/vendor/symfony/http-client-contracts/.gitignore b/src/vendor/symfony/http-client-contracts/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/src/vendor/symfony/http-client-contracts/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/src/vendor/symfony/http-client-contracts/CHANGELOG.md b/src/vendor/symfony/http-client-contracts/CHANGELOG.md new file mode 100644 index 0000000..7932e26 --- /dev/null +++ b/src/vendor/symfony/http-client-contracts/CHANGELOG.md @@ -0,0 +1,5 @@ +CHANGELOG +========= + +The changelog is maintained for all Symfony contracts at the following URL: +https://github.com/symfony/contracts/blob/main/CHANGELOG.md diff --git a/src/vendor/symfony/http-client-contracts/ChunkInterface.php b/src/vendor/symfony/http-client-contracts/ChunkInterface.php new file mode 100644 index 0000000..0800cb3 --- /dev/null +++ b/src/vendor/symfony/http-client-contracts/ChunkInterface.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient; + +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; + +/** + * The interface of chunks returned by ResponseStreamInterface::current(). + * + * When the chunk is first, last or timeout, the content MUST be empty. + * When an unchecked timeout or a network error occurs, a TransportExceptionInterface + * MUST be thrown by the destructor unless one was already thrown by another method. + * + * @author Nicolas Grekas + */ +interface ChunkInterface +{ + /** + * Tells when the idle timeout has been reached. + * + * @throws TransportExceptionInterface on a network error + */ + public function isTimeout(): bool; + + /** + * Tells when headers just arrived. + * + * @throws TransportExceptionInterface on a network error or when the idle timeout is reached + */ + public function isFirst(): bool; + + /** + * Tells when the body just completed. + * + * @throws TransportExceptionInterface on a network error or when the idle timeout is reached + */ + public function isLast(): bool; + + /** + * Returns a [status code, headers] tuple when a 1xx status code was just received. + * + * @throws TransportExceptionInterface on a network error or when the idle timeout is reached + */ + public function getInformationalStatus(): ?array; + + /** + * Returns the content of the response chunk. + * + * @throws TransportExceptionInterface on a network error or when the idle timeout is reached + */ + public function getContent(): string; + + /** + * Returns the offset of the chunk in the response body. + */ + public function getOffset(): int; + + /** + * In case of error, returns the message that describes it. + */ + public function getError(): ?string; +} diff --git a/src/vendor/symfony/http-client-contracts/Exception/ClientExceptionInterface.php b/src/vendor/symfony/http-client-contracts/Exception/ClientExceptionInterface.php new file mode 100644 index 0000000..22d2b45 --- /dev/null +++ b/src/vendor/symfony/http-client-contracts/Exception/ClientExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient\Exception; + +/** + * When a 4xx response is returned. + * + * @author Nicolas Grekas + */ +interface ClientExceptionInterface extends HttpExceptionInterface +{ +} diff --git a/src/vendor/symfony/http-client-contracts/Exception/DecodingExceptionInterface.php b/src/vendor/symfony/http-client-contracts/Exception/DecodingExceptionInterface.php new file mode 100644 index 0000000..971a7a2 --- /dev/null +++ b/src/vendor/symfony/http-client-contracts/Exception/DecodingExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient\Exception; + +/** + * When a content-type cannot be decoded to the expected representation. + * + * @author Nicolas Grekas + */ +interface DecodingExceptionInterface extends ExceptionInterface +{ +} diff --git a/src/vendor/symfony/http-client-contracts/Exception/ExceptionInterface.php b/src/vendor/symfony/http-client-contracts/Exception/ExceptionInterface.php new file mode 100644 index 0000000..e553b47 --- /dev/null +++ b/src/vendor/symfony/http-client-contracts/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient\Exception; + +/** + * The base interface for all exceptions in the contract. + * + * @author Nicolas Grekas + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/src/vendor/symfony/http-client-contracts/Exception/HttpExceptionInterface.php b/src/vendor/symfony/http-client-contracts/Exception/HttpExceptionInterface.php new file mode 100644 index 0000000..17865ed --- /dev/null +++ b/src/vendor/symfony/http-client-contracts/Exception/HttpExceptionInterface.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient\Exception; + +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * Base interface for HTTP-related exceptions. + * + * @author Anton Chernikov + */ +interface HttpExceptionInterface extends ExceptionInterface +{ + public function getResponse(): ResponseInterface; +} diff --git a/src/vendor/symfony/http-client-contracts/Exception/RedirectionExceptionInterface.php b/src/vendor/symfony/http-client-contracts/Exception/RedirectionExceptionInterface.php new file mode 100644 index 0000000..edd9b8a --- /dev/null +++ b/src/vendor/symfony/http-client-contracts/Exception/RedirectionExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient\Exception; + +/** + * When a 3xx response is returned and the "max_redirects" option has been reached. + * + * @author Nicolas Grekas + */ +interface RedirectionExceptionInterface extends HttpExceptionInterface +{ +} diff --git a/src/vendor/symfony/http-client-contracts/Exception/ServerExceptionInterface.php b/src/vendor/symfony/http-client-contracts/Exception/ServerExceptionInterface.php new file mode 100644 index 0000000..9bfe135 --- /dev/null +++ b/src/vendor/symfony/http-client-contracts/Exception/ServerExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient\Exception; + +/** + * When a 5xx response is returned. + * + * @author Nicolas Grekas + */ +interface ServerExceptionInterface extends HttpExceptionInterface +{ +} diff --git a/src/vendor/symfony/http-client-contracts/Exception/TimeoutExceptionInterface.php b/src/vendor/symfony/http-client-contracts/Exception/TimeoutExceptionInterface.php new file mode 100644 index 0000000..08acf9f --- /dev/null +++ b/src/vendor/symfony/http-client-contracts/Exception/TimeoutExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient\Exception; + +/** + * When an idle timeout occurs. + * + * @author Nicolas Grekas + */ +interface TimeoutExceptionInterface extends TransportExceptionInterface +{ +} diff --git a/src/vendor/symfony/http-client-contracts/Exception/TransportExceptionInterface.php b/src/vendor/symfony/http-client-contracts/Exception/TransportExceptionInterface.php new file mode 100644 index 0000000..0c8d131 --- /dev/null +++ b/src/vendor/symfony/http-client-contracts/Exception/TransportExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient\Exception; + +/** + * When any error happens at the transport level. + * + * @author Nicolas Grekas + */ +interface TransportExceptionInterface extends ExceptionInterface +{ +} diff --git a/src/vendor/symfony/http-client-contracts/HttpClientInterface.php b/src/vendor/symfony/http-client-contracts/HttpClientInterface.php new file mode 100644 index 0000000..158c1a7 --- /dev/null +++ b/src/vendor/symfony/http-client-contracts/HttpClientInterface.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient; + +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\Test\HttpClientTestCase; + +/** + * Provides flexible methods for requesting HTTP resources synchronously or asynchronously. + * + * @see HttpClientTestCase for a reference test suite + * + * @method static withOptions(array $options) Returns a new instance of the client with new default options + * + * @author Nicolas Grekas + */ +interface HttpClientInterface +{ + public const OPTIONS_DEFAULTS = [ + 'auth_basic' => null, // array|string - an array containing the username as first value, and optionally the + // password as the second one; or string like username:password - enabling HTTP Basic + // authentication (RFC 7617) + 'auth_bearer' => null, // string - a token enabling HTTP Bearer authorization (RFC 6750) + 'query' => [], // string[] - associative array of query string values to merge with the request's URL + 'headers' => [], // iterable|string[]|string[][] - headers names provided as keys or as part of values + 'body' => '', // array|string|resource|\Traversable|\Closure - the callback SHOULD yield a string + // smaller than the amount requested as argument; the empty string signals EOF; if + // an array is passed, it is meant as a form payload of field names and values + 'json' => null, // mixed - if set, implementations MUST set the "body" option to the JSON-encoded + // value and set the "content-type" header to a JSON-compatible value if it is not + // explicitly defined in the headers option - typically "application/json" + 'user_data' => null, // mixed - any extra data to attach to the request (scalar, callable, object...) that + // MUST be available via $response->getInfo('user_data') - not used internally + 'max_redirects' => 20, // int - the maximum number of redirects to follow; a value lower than or equal to 0 + // means redirects should not be followed; "Authorization" and "Cookie" headers MUST + // NOT follow except for the initial host name + 'http_version' => null, // string - defaults to the best supported version, typically 1.1 or 2.0 + 'base_uri' => null, // string - the URI to resolve relative URLs, following rules in RFC 3986, section 2 + 'buffer' => true, // bool|resource|\Closure - whether the content of the response should be buffered or not, + // or a stream resource where the response body should be written, + // or a closure telling if/where the response should be buffered based on its headers + 'on_progress' => null, // callable(int $dlNow, int $dlSize, array $info) - throwing any exceptions MUST abort + // the request; it MUST be called on DNS resolution, on arrival of headers and on + // completion; it SHOULD be called on upload/download of data and at least 1/s + 'resolve' => [], // string[] - a map of host to IP address that SHOULD replace DNS resolution + 'proxy' => null, // string - by default, the proxy-related env vars handled by curl SHOULD be honored + 'no_proxy' => null, // string - a comma separated list of hosts that do not require a proxy to be reached + 'timeout' => null, // float - the idle timeout - defaults to ini_get('default_socket_timeout') + 'max_duration' => 0, // float - the maximum execution time for the request+response as a whole; + // a value lower than or equal to 0 means it is unlimited + 'bindto' => '0', // string - the interface or the local socket to bind to + 'verify_peer' => true, // see https://php.net/context.ssl for the following options + 'verify_host' => true, + 'cafile' => null, + 'capath' => null, + 'local_cert' => null, + 'local_pk' => null, + 'passphrase' => null, + 'ciphers' => null, + 'peer_fingerprint' => null, + 'capture_peer_cert_chain' => false, + 'extra' => [], // array - additional options that can be ignored if unsupported, unlike regular options + ]; + + /** + * Requests an HTTP resource. + * + * Responses MUST be lazy, but their status code MUST be + * checked even if none of their public methods are called. + * + * Implementations are not required to support all options described above; they can also + * support more custom options; but in any case, they MUST throw a TransportExceptionInterface + * when an unsupported option is passed. + * + * @throws TransportExceptionInterface When an unsupported option is passed + */ + public function request(string $method, string $url, array $options = []): ResponseInterface; + + /** + * Yields responses chunk by chunk as they complete. + * + * @param ResponseInterface|iterable $responses One or more responses created by the current HTTP client + * @param float|null $timeout The idle timeout before yielding timeout chunks + */ + public function stream($responses, float $timeout = null): ResponseStreamInterface; +} diff --git a/src/vendor/symfony/http-client-contracts/LICENSE b/src/vendor/symfony/http-client-contracts/LICENSE new file mode 100644 index 0000000..74cdc2d --- /dev/null +++ b/src/vendor/symfony/http-client-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2022 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/vendor/symfony/http-client-contracts/README.md b/src/vendor/symfony/http-client-contracts/README.md new file mode 100644 index 0000000..03b3a69 --- /dev/null +++ b/src/vendor/symfony/http-client-contracts/README.md @@ -0,0 +1,9 @@ +Symfony HttpClient Contracts +============================ + +A set of abstractions extracted out of the Symfony components. + +Can be used to build on semantics that the Symfony components proved useful - and +that already have battle tested implementations. + +See https://github.com/symfony/contracts/blob/main/README.md for more information. diff --git a/src/vendor/symfony/http-client-contracts/ResponseInterface.php b/src/vendor/symfony/http-client-contracts/ResponseInterface.php new file mode 100644 index 0000000..df71488 --- /dev/null +++ b/src/vendor/symfony/http-client-contracts/ResponseInterface.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient; + +use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\ExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; + +/** + * A (lazily retrieved) HTTP response. + * + * @author Nicolas Grekas + */ +interface ResponseInterface +{ + /** + * Gets the HTTP status code of the response. + * + * @throws TransportExceptionInterface when a network error occurs + */ + public function getStatusCode(): int; + + /** + * Gets the HTTP headers of the response. + * + * @param bool $throw Whether an exception should be thrown on 3/4/5xx status codes + * + * @return string[][] The headers of the response keyed by header names in lowercase + * + * @throws TransportExceptionInterface When a network error occurs + * @throws RedirectionExceptionInterface On a 3xx when $throw is true and the "max_redirects" option has been reached + * @throws ClientExceptionInterface On a 4xx when $throw is true + * @throws ServerExceptionInterface On a 5xx when $throw is true + */ + public function getHeaders(bool $throw = true): array; + + /** + * Gets the response body as a string. + * + * @param bool $throw Whether an exception should be thrown on 3/4/5xx status codes + * + * @throws TransportExceptionInterface When a network error occurs + * @throws RedirectionExceptionInterface On a 3xx when $throw is true and the "max_redirects" option has been reached + * @throws ClientExceptionInterface On a 4xx when $throw is true + * @throws ServerExceptionInterface On a 5xx when $throw is true + */ + public function getContent(bool $throw = true): string; + + /** + * Gets the response body decoded as array, typically from a JSON payload. + * + * @param bool $throw Whether an exception should be thrown on 3/4/5xx status codes + * + * @throws DecodingExceptionInterface When the body cannot be decoded to an array + * @throws TransportExceptionInterface When a network error occurs + * @throws RedirectionExceptionInterface On a 3xx when $throw is true and the "max_redirects" option has been reached + * @throws ClientExceptionInterface On a 4xx when $throw is true + * @throws ServerExceptionInterface On a 5xx when $throw is true + */ + public function toArray(bool $throw = true): array; + + /** + * Closes the response stream and all related buffers. + * + * No further chunk will be yielded after this method has been called. + */ + public function cancel(): void; + + /** + * Returns info coming from the transport layer. + * + * This method SHOULD NOT throw any ExceptionInterface and SHOULD be non-blocking. + * The returned info is "live": it can be empty and can change from one call to + * another, as the request/response progresses. + * + * The following info MUST be returned: + * - canceled (bool) - true if the response was canceled using ResponseInterface::cancel(), false otherwise + * - error (string|null) - the error message when the transfer was aborted, null otherwise + * - http_code (int) - the last response code or 0 when it is not known yet + * - http_method (string) - the HTTP verb of the last request + * - redirect_count (int) - the number of redirects followed while executing the request + * - redirect_url (string|null) - the resolved location of redirect responses, null otherwise + * - response_headers (array) - an array modelled after the special $http_response_header variable + * - start_time (float) - the time when the request was sent or 0.0 when it's pending + * - url (string) - the last effective URL of the request + * - user_data (mixed) - the value of the "user_data" request option, null if not set + * + * When the "capture_peer_cert_chain" option is true, the "peer_certificate_chain" + * attribute SHOULD list the peer certificates as an array of OpenSSL X.509 resources. + * + * Other info SHOULD be named after curl_getinfo()'s associative return value. + * + * @return mixed An array of all available info, or one of them when $type is + * provided, or null when an unsupported type is requested + */ + public function getInfo(string $type = null); +} diff --git a/src/vendor/symfony/http-client-contracts/ResponseStreamInterface.php b/src/vendor/symfony/http-client-contracts/ResponseStreamInterface.php new file mode 100644 index 0000000..fa3e5db --- /dev/null +++ b/src/vendor/symfony/http-client-contracts/ResponseStreamInterface.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient; + +/** + * Yields response chunks, returned by HttpClientInterface::stream(). + * + * @author Nicolas Grekas + * + * @extends \Iterator + */ +interface ResponseStreamInterface extends \Iterator +{ + public function key(): ResponseInterface; + + public function current(): ChunkInterface; +} diff --git a/src/vendor/symfony/http-client-contracts/Test/Fixtures/web/index.php b/src/vendor/symfony/http-client-contracts/Test/Fixtures/web/index.php new file mode 100644 index 0000000..30a7049 --- /dev/null +++ b/src/vendor/symfony/http-client-contracts/Test/Fixtures/web/index.php @@ -0,0 +1,192 @@ + $v) { + switch ($k) { + default: + if (0 !== strpos($k, 'HTTP_')) { + continue 2; + } + // no break + case 'SERVER_NAME': + case 'SERVER_PROTOCOL': + case 'REQUEST_URI': + case 'REQUEST_METHOD': + case 'PHP_AUTH_USER': + case 'PHP_AUTH_PW': + $vars[$k] = $v; + } +} + +$json = json_encode($vars, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE); + +switch ($vars['REQUEST_URI']) { + default: + exit; + + case '/head': + header('Content-Length: '.strlen($json), true); + break; + + case '/': + case '/?a=a&b=b': + case 'http://127.0.0.1:8057/': + case 'http://localhost:8057/': + ob_start('ob_gzhandler'); + break; + + case '/103': + header('HTTP/1.1 103 Early Hints'); + header('Link: ; rel=preload; as=style', false); + header('Link: ; rel=preload; as=script', false); + flush(); + usleep(1000); + echo "HTTP/1.1 200 OK\r\n"; + echo "Date: Fri, 26 May 2017 10:02:11 GMT\r\n"; + echo "Content-Length: 13\r\n"; + echo "\r\n"; + echo 'Here the body'; + exit; + + case '/404': + header('Content-Type: application/json', true, 404); + break; + + case '/404-gzipped': + header('Content-Type: text/plain', true, 404); + ob_start('ob_gzhandler'); + @ob_flush(); + flush(); + usleep(300000); + echo 'some text'; + exit; + + case '/301': + if ('Basic Zm9vOmJhcg==' === $vars['HTTP_AUTHORIZATION']) { + header('Location: http://127.0.0.1:8057/302', true, 301); + } + break; + + case '/301/bad-tld': + header('Location: http://foo.example.', true, 301); + break; + + case '/301/invalid': + header('Location: //?foo=bar', true, 301); + break; + + case '/302': + if (!isset($vars['HTTP_AUTHORIZATION'])) { + header('Location: http://localhost:8057/', true, 302); + } + break; + + case '/302/relative': + header('Location: ..', true, 302); + break; + + case '/304': + header('Content-Length: 10', true, 304); + echo '12345'; + + return; + + case '/307': + header('Location: http://localhost:8057/post', true, 307); + break; + + case '/length-broken': + header('Content-Length: 1000'); + break; + + case '/post': + $output = json_encode($_POST + ['REQUEST_METHOD' => $vars['REQUEST_METHOD']], \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE); + header('Content-Type: application/json', true); + header('Content-Length: '.strlen($output)); + echo $output; + exit; + + case '/timeout-header': + usleep(300000); + break; + + case '/timeout-body': + echo '<1>'; + @ob_flush(); + flush(); + usleep(500000); + echo '<2>'; + exit; + + case '/timeout-long': + ignore_user_abort(false); + sleep(1); + while (true) { + echo '<1>'; + @ob_flush(); + flush(); + usleep(500); + } + exit; + + case '/chunked': + header('Transfer-Encoding: chunked'); + echo "8\r\nSymfony \r\n5\r\nis aw\r\n6\r\nesome!\r\n0\r\n\r\n"; + exit; + + case '/chunked-broken': + header('Transfer-Encoding: chunked'); + echo "8\r\nSymfony \r\n5\r\nis aw\r\n6\r\ne"; + exit; + + case '/gzip-broken': + header('Content-Encoding: gzip'); + echo str_repeat('-', 1000); + exit; + + case '/max-duration': + ignore_user_abort(false); + while (true) { + echo '<1>'; + @ob_flush(); + flush(); + usleep(500); + } + exit; + + case '/json': + header('Content-Type: application/json'); + echo json_encode([ + 'documents' => [ + ['id' => '/json/1'], + ['id' => '/json/2'], + ['id' => '/json/3'], + ], + ]); + exit; + + case '/json/1': + case '/json/2': + case '/json/3': + header('Content-Type: application/json'); + echo json_encode([ + 'title' => $vars['REQUEST_URI'], + ]); + + exit; +} + +header('Content-Type: application/json', true); + +echo $json; diff --git a/src/vendor/symfony/http-client-contracts/Test/HttpClientTestCase.php b/src/vendor/symfony/http-client-contracts/Test/HttpClientTestCase.php new file mode 100644 index 0000000..7acd6b7 --- /dev/null +++ b/src/vendor/symfony/http-client-contracts/Test/HttpClientTestCase.php @@ -0,0 +1,1137 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient\Test; + +use PHPUnit\Framework\TestCase; +use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\TimeoutExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * A reference test suite for HttpClientInterface implementations. + */ +abstract class HttpClientTestCase extends TestCase +{ + public static function setUpBeforeClass(): void + { + TestHttpServer::start(); + } + + abstract protected function getHttpClient(string $testCase): HttpClientInterface; + + public function testGetRequest() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057', [ + 'headers' => ['Foo' => 'baR'], + 'user_data' => $data = new \stdClass(), + ]); + + $this->assertSame([], $response->getInfo('response_headers')); + $this->assertSame($data, $response->getInfo()['user_data']); + $this->assertSame(200, $response->getStatusCode()); + + $info = $response->getInfo(); + $this->assertNull($info['error']); + $this->assertSame(0, $info['redirect_count']); + $this->assertSame('HTTP/1.1 200 OK', $info['response_headers'][0]); + $this->assertSame('Host: localhost:8057', $info['response_headers'][1]); + $this->assertSame('http://localhost:8057/', $info['url']); + + $headers = $response->getHeaders(); + + $this->assertSame('localhost:8057', $headers['host'][0]); + $this->assertSame(['application/json'], $headers['content-type']); + + $body = json_decode($response->getContent(), true); + $this->assertSame($body, $response->toArray()); + + $this->assertSame('HTTP/1.1', $body['SERVER_PROTOCOL']); + $this->assertSame('/', $body['REQUEST_URI']); + $this->assertSame('GET', $body['REQUEST_METHOD']); + $this->assertSame('localhost:8057', $body['HTTP_HOST']); + $this->assertSame('baR', $body['HTTP_FOO']); + + $response = $client->request('GET', 'http://localhost:8057/length-broken'); + + $this->expectException(TransportExceptionInterface::class); + $response->getContent(); + } + + public function testHeadRequest() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('HEAD', 'http://localhost:8057/head', [ + 'headers' => ['Foo' => 'baR'], + 'user_data' => $data = new \stdClass(), + 'buffer' => false, + ]); + + $this->assertSame([], $response->getInfo('response_headers')); + $this->assertSame(200, $response->getStatusCode()); + + $info = $response->getInfo(); + $this->assertSame('HTTP/1.1 200 OK', $info['response_headers'][0]); + $this->assertSame('Host: localhost:8057', $info['response_headers'][1]); + + $headers = $response->getHeaders(); + + $this->assertSame('localhost:8057', $headers['host'][0]); + $this->assertSame(['application/json'], $headers['content-type']); + $this->assertTrue(0 < $headers['content-length'][0]); + + $this->assertSame('', $response->getContent()); + } + + public function testNonBufferedGetRequest() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057', [ + 'buffer' => false, + 'headers' => ['Foo' => 'baR'], + ]); + + $body = $response->toArray(); + $this->assertSame('baR', $body['HTTP_FOO']); + + $this->expectException(TransportExceptionInterface::class); + $response->getContent(); + } + + public function testBufferSink() + { + $sink = fopen('php://temp', 'w+'); + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057', [ + 'buffer' => $sink, + 'headers' => ['Foo' => 'baR'], + ]); + + $body = $response->toArray(); + $this->assertSame('baR', $body['HTTP_FOO']); + + rewind($sink); + $sink = stream_get_contents($sink); + $this->assertSame($sink, $response->getContent()); + } + + public function testConditionalBuffering() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057'); + $firstContent = $response->getContent(); + $secondContent = $response->getContent(); + + $this->assertSame($firstContent, $secondContent); + + $response = $client->request('GET', 'http://localhost:8057', ['buffer' => function () { return false; }]); + $response->getContent(); + + $this->expectException(TransportExceptionInterface::class); + $response->getContent(); + } + + public function testReentrantBufferCallback() + { + $client = $this->getHttpClient(__FUNCTION__); + + $response = $client->request('GET', 'http://localhost:8057', ['buffer' => function () use (&$response) { + $response->cancel(); + + return true; + }]); + + $this->assertSame(200, $response->getStatusCode()); + + $this->expectException(TransportExceptionInterface::class); + $response->getContent(); + } + + public function testThrowingBufferCallback() + { + $client = $this->getHttpClient(__FUNCTION__); + + $response = $client->request('GET', 'http://localhost:8057', ['buffer' => function () { + throw new \Exception('Boo.'); + }]); + + $this->assertSame(200, $response->getStatusCode()); + + $this->expectException(TransportExceptionInterface::class); + $this->expectExceptionMessage('Boo'); + $response->getContent(); + } + + public function testUnsupportedOption() + { + $client = $this->getHttpClient(__FUNCTION__); + + $this->expectException(\InvalidArgumentException::class); + $client->request('GET', 'http://localhost:8057', [ + 'capture_peer_cert' => 1.0, + ]); + } + + public function testHttpVersion() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057', [ + 'http_version' => 1.0, + ]); + + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame('HTTP/1.0 200 OK', $response->getInfo('response_headers')[0]); + + $body = $response->toArray(); + + $this->assertSame('HTTP/1.0', $body['SERVER_PROTOCOL']); + $this->assertSame('GET', $body['REQUEST_METHOD']); + $this->assertSame('/', $body['REQUEST_URI']); + } + + public function testChunkedEncoding() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/chunked'); + + $this->assertSame(['chunked'], $response->getHeaders()['transfer-encoding']); + $this->assertSame('Symfony is awesome!', $response->getContent()); + + $response = $client->request('GET', 'http://localhost:8057/chunked-broken'); + + $this->expectException(TransportExceptionInterface::class); + $response->getContent(); + } + + public function testClientError() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/404'); + + $client->stream($response)->valid(); + + $this->assertSame(404, $response->getInfo('http_code')); + + try { + $response->getHeaders(); + $this->fail(ClientExceptionInterface::class.' expected'); + } catch (ClientExceptionInterface $e) { + } + + try { + $response->getContent(); + $this->fail(ClientExceptionInterface::class.' expected'); + } catch (ClientExceptionInterface $e) { + } + + $this->assertSame(404, $response->getStatusCode()); + $this->assertSame(['application/json'], $response->getHeaders(false)['content-type']); + $this->assertNotEmpty($response->getContent(false)); + + $response = $client->request('GET', 'http://localhost:8057/404'); + + try { + foreach ($client->stream($response) as $chunk) { + $this->assertTrue($chunk->isFirst()); + } + $this->fail(ClientExceptionInterface::class.' expected'); + } catch (ClientExceptionInterface $e) { + } + } + + public function testIgnoreErrors() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/404'); + + $this->assertSame(404, $response->getStatusCode()); + } + + public function testDnsError() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/301/bad-tld'); + + try { + $response->getStatusCode(); + $this->fail(TransportExceptionInterface::class.' expected'); + } catch (TransportExceptionInterface $e) { + $this->addToAssertionCount(1); + } + + try { + $response->getStatusCode(); + $this->fail(TransportExceptionInterface::class.' still expected'); + } catch (TransportExceptionInterface $e) { + $this->addToAssertionCount(1); + } + + $response = $client->request('GET', 'http://localhost:8057/301/bad-tld'); + + try { + foreach ($client->stream($response) as $r => $chunk) { + } + $this->fail(TransportExceptionInterface::class.' expected'); + } catch (TransportExceptionInterface $e) { + $this->addToAssertionCount(1); + } + + $this->assertSame($response, $r); + $this->assertNotNull($chunk->getError()); + + $this->expectException(TransportExceptionInterface::class); + foreach ($client->stream($response) as $chunk) { + } + } + + public function testInlineAuth() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://foo:bar%3Dbar@localhost:8057'); + + $body = $response->toArray(); + + $this->assertSame('foo', $body['PHP_AUTH_USER']); + $this->assertSame('bar=bar', $body['PHP_AUTH_PW']); + } + + public function testBadRequestBody() + { + $client = $this->getHttpClient(__FUNCTION__); + + $this->expectException(TransportExceptionInterface::class); + + $response = $client->request('POST', 'http://localhost:8057/', [ + 'body' => function () { yield []; }, + ]); + + $response->getStatusCode(); + } + + public function test304() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/304', [ + 'headers' => ['If-Match' => '"abc"'], + 'buffer' => false, + ]); + + $this->assertSame(304, $response->getStatusCode()); + $this->assertSame('', $response->getContent(false)); + } + + /** + * @testWith [[]] + * [["Content-Length: 7"]] + */ + public function testRedirects(array $headers = []) + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('POST', 'http://localhost:8057/301', [ + 'auth_basic' => 'foo:bar', + 'headers' => $headers, + 'body' => function () { + yield 'foo=bar'; + }, + ]); + + $body = $response->toArray(); + $this->assertSame('GET', $body['REQUEST_METHOD']); + $this->assertSame('Basic Zm9vOmJhcg==', $body['HTTP_AUTHORIZATION']); + $this->assertSame('http://localhost:8057/', $response->getInfo('url')); + + $this->assertSame(2, $response->getInfo('redirect_count')); + $this->assertNull($response->getInfo('redirect_url')); + + $expected = [ + 'HTTP/1.1 301 Moved Permanently', + 'Location: http://127.0.0.1:8057/302', + 'Content-Type: application/json', + 'HTTP/1.1 302 Found', + 'Location: http://localhost:8057/', + 'Content-Type: application/json', + 'HTTP/1.1 200 OK', + 'Content-Type: application/json', + ]; + + $filteredHeaders = array_values(array_filter($response->getInfo('response_headers'), function ($h) { + return \in_array(substr($h, 0, 4), ['HTTP', 'Loca', 'Cont'], true) && 'Content-Encoding: gzip' !== $h; + })); + + $this->assertSame($expected, $filteredHeaders); + } + + public function testInvalidRedirect() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/301/invalid'); + + $this->assertSame(301, $response->getStatusCode()); + $this->assertSame(['//?foo=bar'], $response->getHeaders(false)['location']); + $this->assertSame(0, $response->getInfo('redirect_count')); + $this->assertNull($response->getInfo('redirect_url')); + + $this->expectException(RedirectionExceptionInterface::class); + $response->getHeaders(); + } + + public function testRelativeRedirects() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/302/relative'); + + $body = $response->toArray(); + + $this->assertSame('/', $body['REQUEST_URI']); + $this->assertNull($response->getInfo('redirect_url')); + + $response = $client->request('GET', 'http://localhost:8057/302/relative', [ + 'max_redirects' => 0, + ]); + + $this->assertSame(302, $response->getStatusCode()); + $this->assertSame('http://localhost:8057/', $response->getInfo('redirect_url')); + } + + public function testRedirect307() + { + $client = $this->getHttpClient(__FUNCTION__); + + $response = $client->request('POST', 'http://localhost:8057/307', [ + 'body' => function () { + yield 'foo=bar'; + }, + 'max_redirects' => 0, + ]); + + $this->assertSame(307, $response->getStatusCode()); + + $response = $client->request('POST', 'http://localhost:8057/307', [ + 'body' => 'foo=bar', + ]); + + $body = $response->toArray(); + + $this->assertSame(['foo' => 'bar', 'REQUEST_METHOD' => 'POST'], $body); + } + + public function testMaxRedirects() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/301', [ + 'max_redirects' => 1, + 'auth_basic' => 'foo:bar', + ]); + + try { + $response->getHeaders(); + $this->fail(RedirectionExceptionInterface::class.' expected'); + } catch (RedirectionExceptionInterface $e) { + } + + $this->assertSame(302, $response->getStatusCode()); + $this->assertSame(1, $response->getInfo('redirect_count')); + $this->assertSame('http://localhost:8057/', $response->getInfo('redirect_url')); + + $expected = [ + 'HTTP/1.1 301 Moved Permanently', + 'Location: http://127.0.0.1:8057/302', + 'Content-Type: application/json', + 'HTTP/1.1 302 Found', + 'Location: http://localhost:8057/', + 'Content-Type: application/json', + ]; + + $filteredHeaders = array_values(array_filter($response->getInfo('response_headers'), function ($h) { + return \in_array(substr($h, 0, 4), ['HTTP', 'Loca', 'Cont'], true); + })); + + $this->assertSame($expected, $filteredHeaders); + } + + public function testStream() + { + $client = $this->getHttpClient(__FUNCTION__); + + $response = $client->request('GET', 'http://localhost:8057'); + $chunks = $client->stream($response); + $result = []; + + foreach ($chunks as $r => $chunk) { + if ($chunk->isTimeout()) { + $result[] = 't'; + } elseif ($chunk->isLast()) { + $result[] = 'l'; + } elseif ($chunk->isFirst()) { + $result[] = 'f'; + } + } + + $this->assertSame($response, $r); + $this->assertSame(['f', 'l'], $result); + + $chunk = null; + $i = 0; + + foreach ($client->stream($response) as $chunk) { + ++$i; + } + + $this->assertSame(1, $i); + $this->assertTrue($chunk->isLast()); + } + + public function testAddToStream() + { + $client = $this->getHttpClient(__FUNCTION__); + + $r1 = $client->request('GET', 'http://localhost:8057'); + + $completed = []; + + $pool = [$r1]; + + while ($pool) { + $chunks = $client->stream($pool); + $pool = []; + + foreach ($chunks as $r => $chunk) { + if (!$chunk->isLast()) { + continue; + } + + if ($r1 === $r) { + $r2 = $client->request('GET', 'http://localhost:8057'); + $pool[] = $r2; + } + + $completed[] = $r; + } + } + + $this->assertSame([$r1, $r2], $completed); + } + + public function testCompleteTypeError() + { + $client = $this->getHttpClient(__FUNCTION__); + + $this->expectException(\TypeError::class); + $client->stream(123); + } + + public function testOnProgress() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('POST', 'http://localhost:8057/post', [ + 'headers' => ['Content-Length' => 14], + 'body' => 'foo=0123456789', + 'on_progress' => function (...$state) use (&$steps) { $steps[] = $state; }, + ]); + + $body = $response->toArray(); + + $this->assertSame(['foo' => '0123456789', 'REQUEST_METHOD' => 'POST'], $body); + $this->assertSame([0, 0], \array_slice($steps[0], 0, 2)); + $lastStep = \array_slice($steps, -1)[0]; + $this->assertSame([57, 57], \array_slice($lastStep, 0, 2)); + $this->assertSame('http://localhost:8057/post', $steps[0][2]['url']); + } + + public function testPostJson() + { + $client = $this->getHttpClient(__FUNCTION__); + + $response = $client->request('POST', 'http://localhost:8057/post', [ + 'json' => ['foo' => 'bar'], + ]); + + $body = $response->toArray(); + + $this->assertStringContainsString('json', $body['content-type']); + unset($body['content-type']); + $this->assertSame(['foo' => 'bar', 'REQUEST_METHOD' => 'POST'], $body); + } + + public function testPostArray() + { + $client = $this->getHttpClient(__FUNCTION__); + + $response = $client->request('POST', 'http://localhost:8057/post', [ + 'body' => ['foo' => 'bar'], + ]); + + $this->assertSame(['foo' => 'bar', 'REQUEST_METHOD' => 'POST'], $response->toArray()); + } + + public function testPostResource() + { + $client = $this->getHttpClient(__FUNCTION__); + + $h = fopen('php://temp', 'w+'); + fwrite($h, 'foo=0123456789'); + rewind($h); + + $response = $client->request('POST', 'http://localhost:8057/post', [ + 'body' => $h, + ]); + + $body = $response->toArray(); + + $this->assertSame(['foo' => '0123456789', 'REQUEST_METHOD' => 'POST'], $body); + } + + public function testPostCallback() + { + $client = $this->getHttpClient(__FUNCTION__); + + $response = $client->request('POST', 'http://localhost:8057/post', [ + 'body' => function () { + yield 'foo'; + yield ''; + yield '='; + yield '0123456789'; + }, + ]); + + $this->assertSame(['foo' => '0123456789', 'REQUEST_METHOD' => 'POST'], $response->toArray()); + } + + public function testCancel() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/timeout-header'); + + $response->cancel(); + $this->expectException(TransportExceptionInterface::class); + $response->getHeaders(); + } + + public function testInfoOnCanceledResponse() + { + $client = $this->getHttpClient(__FUNCTION__); + + $response = $client->request('GET', 'http://localhost:8057/timeout-header'); + + $this->assertFalse($response->getInfo('canceled')); + $response->cancel(); + $this->assertTrue($response->getInfo('canceled')); + } + + public function testCancelInStream() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/404'); + + foreach ($client->stream($response) as $chunk) { + $response->cancel(); + } + + $this->expectException(TransportExceptionInterface::class); + + foreach ($client->stream($response) as $chunk) { + } + } + + public function testOnProgressCancel() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/timeout-body', [ + 'on_progress' => function ($dlNow) { + if (0 < $dlNow) { + throw new \Exception('Aborting the request.'); + } + }, + ]); + + try { + foreach ($client->stream([$response]) as $chunk) { + } + $this->fail(ClientExceptionInterface::class.' expected'); + } catch (TransportExceptionInterface $e) { + $this->assertSame('Aborting the request.', $e->getPrevious()->getMessage()); + } + + $this->assertNotNull($response->getInfo('error')); + $this->expectException(TransportExceptionInterface::class); + $response->getContent(); + } + + public function testOnProgressError() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/timeout-body', [ + 'on_progress' => function ($dlNow) { + if (0 < $dlNow) { + throw new \Error('BUG.'); + } + }, + ]); + + try { + foreach ($client->stream([$response]) as $chunk) { + } + $this->fail('Error expected'); + } catch (\Error $e) { + $this->assertSame('BUG.', $e->getMessage()); + } + + $this->assertNotNull($response->getInfo('error')); + $this->expectException(TransportExceptionInterface::class); + $response->getContent(); + } + + public function testResolve() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://symfony.com:8057/', [ + 'resolve' => ['symfony.com' => '127.0.0.1'], + ]); + + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame(200, $client->request('GET', 'http://symfony.com:8057/')->getStatusCode()); + + $response = null; + $this->expectException(TransportExceptionInterface::class); + $client->request('GET', 'http://symfony.com:8057/', ['timeout' => 1]); + } + + public function testIdnResolve() + { + $client = $this->getHttpClient(__FUNCTION__); + + $response = $client->request('GET', 'http://0-------------------------------------------------------------0.com:8057/', [ + 'resolve' => ['0-------------------------------------------------------------0.com' => '127.0.0.1'], + ]); + + $this->assertSame(200, $response->getStatusCode()); + + $response = $client->request('GET', 'http://Bücher.example:8057/', [ + 'resolve' => ['xn--bcher-kva.example' => '127.0.0.1'], + ]); + + $this->assertSame(200, $response->getStatusCode()); + } + + public function testNotATimeout() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/timeout-header', [ + 'timeout' => 0.9, + ]); + sleep(1); + $this->assertSame(200, $response->getStatusCode()); + } + + public function testTimeoutOnAccess() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/timeout-header', [ + 'timeout' => 0.1, + ]); + + $this->expectException(TransportExceptionInterface::class); + $response->getHeaders(); + } + + public function testTimeoutIsNotAFatalError() + { + usleep(300000); // wait for the previous test to release the server + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/timeout-body', [ + 'timeout' => 0.25, + ]); + + try { + $response->getContent(); + $this->fail(TimeoutExceptionInterface::class.' expected'); + } catch (TimeoutExceptionInterface $e) { + } + + for ($i = 0; $i < 10; ++$i) { + try { + $this->assertSame('<1><2>', $response->getContent()); + break; + } catch (TimeoutExceptionInterface $e) { + } + } + + if (10 === $i) { + throw $e; + } + } + + public function testTimeoutOnStream() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/timeout-body'); + + $this->assertSame(200, $response->getStatusCode()); + $chunks = $client->stream([$response], 0.2); + + $result = []; + + foreach ($chunks as $r => $chunk) { + if ($chunk->isTimeout()) { + $result[] = 't'; + } else { + $result[] = $chunk->getContent(); + } + } + + $this->assertSame(['<1>', 't'], $result); + + $chunks = $client->stream([$response]); + + foreach ($chunks as $r => $chunk) { + $this->assertSame('<2>', $chunk->getContent()); + $this->assertSame('<1><2>', $r->getContent()); + + return; + } + + $this->fail('The response should have completed'); + } + + public function testUncheckedTimeoutThrows() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/timeout-body'); + $chunks = $client->stream([$response], 0.1); + + $this->expectException(TransportExceptionInterface::class); + + foreach ($chunks as $r => $chunk) { + } + } + + public function testTimeoutWithActiveConcurrentStream() + { + $p1 = TestHttpServer::start(8067); + $p2 = TestHttpServer::start(8077); + + $client = $this->getHttpClient(__FUNCTION__); + $streamingResponse = $client->request('GET', 'http://localhost:8067/max-duration'); + $blockingResponse = $client->request('GET', 'http://localhost:8077/timeout-body', [ + 'timeout' => 0.25, + ]); + + $this->assertSame(200, $streamingResponse->getStatusCode()); + $this->assertSame(200, $blockingResponse->getStatusCode()); + + $this->expectException(TransportExceptionInterface::class); + + try { + $blockingResponse->getContent(); + } finally { + $p1->stop(); + $p2->stop(); + } + } + + public function testTimeoutOnInitialize() + { + $p1 = TestHttpServer::start(8067); + $p2 = TestHttpServer::start(8077); + + $client = $this->getHttpClient(__FUNCTION__); + $start = microtime(true); + $responses = []; + + $responses[] = $client->request('GET', 'http://localhost:8067/timeout-header', ['timeout' => 0.25]); + $responses[] = $client->request('GET', 'http://localhost:8077/timeout-header', ['timeout' => 0.25]); + $responses[] = $client->request('GET', 'http://localhost:8067/timeout-header', ['timeout' => 0.25]); + $responses[] = $client->request('GET', 'http://localhost:8077/timeout-header', ['timeout' => 0.25]); + + try { + foreach ($responses as $response) { + try { + $response->getContent(); + $this->fail(TransportExceptionInterface::class.' expected'); + } catch (TransportExceptionInterface $e) { + } + } + $responses = []; + + $duration = microtime(true) - $start; + + $this->assertLessThan(1.0, $duration); + } finally { + $p1->stop(); + $p2->stop(); + } + } + + public function testTimeoutOnDestruct() + { + $p1 = TestHttpServer::start(8067); + $p2 = TestHttpServer::start(8077); + + $client = $this->getHttpClient(__FUNCTION__); + $start = microtime(true); + $responses = []; + + $responses[] = $client->request('GET', 'http://localhost:8067/timeout-header', ['timeout' => 0.25]); + $responses[] = $client->request('GET', 'http://localhost:8077/timeout-header', ['timeout' => 0.25]); + $responses[] = $client->request('GET', 'http://localhost:8067/timeout-header', ['timeout' => 0.25]); + $responses[] = $client->request('GET', 'http://localhost:8077/timeout-header', ['timeout' => 0.25]); + + try { + while ($response = array_shift($responses)) { + try { + unset($response); + $this->fail(TransportExceptionInterface::class.' expected'); + } catch (TransportExceptionInterface $e) { + } + } + + $duration = microtime(true) - $start; + + $this->assertLessThan(1.0, $duration); + } finally { + $p1->stop(); + $p2->stop(); + } + } + + public function testDestruct() + { + $client = $this->getHttpClient(__FUNCTION__); + + $start = microtime(true); + $client->request('GET', 'http://localhost:8057/timeout-long'); + $client = null; + $duration = microtime(true) - $start; + + $this->assertGreaterThan(1, $duration); + $this->assertLessThan(4, $duration); + } + + public function testGetContentAfterDestruct() + { + $client = $this->getHttpClient(__FUNCTION__); + + try { + $client->request('GET', 'http://localhost:8057/404'); + $this->fail(ClientExceptionInterface::class.' expected'); + } catch (ClientExceptionInterface $e) { + $this->assertSame('GET', $e->getResponse()->toArray(false)['REQUEST_METHOD']); + } + } + + public function testGetEncodedContentAfterDestruct() + { + $client = $this->getHttpClient(__FUNCTION__); + + try { + $client->request('GET', 'http://localhost:8057/404-gzipped'); + $this->fail(ClientExceptionInterface::class.' expected'); + } catch (ClientExceptionInterface $e) { + $this->assertSame('some text', $e->getResponse()->getContent(false)); + } + } + + public function testProxy() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/', [ + 'proxy' => 'http://localhost:8057', + ]); + + $body = $response->toArray(); + $this->assertSame('localhost:8057', $body['HTTP_HOST']); + $this->assertMatchesRegularExpression('#^http://(localhost|127\.0\.0\.1):8057/$#', $body['REQUEST_URI']); + + $response = $client->request('GET', 'http://localhost:8057/', [ + 'proxy' => 'http://foo:b%3Dar@localhost:8057', + ]); + + $body = $response->toArray(); + $this->assertSame('Basic Zm9vOmI9YXI=', $body['HTTP_PROXY_AUTHORIZATION']); + + $_SERVER['http_proxy'] = 'http://localhost:8057'; + try { + $response = $client->request('GET', 'http://localhost:8057/'); + $body = $response->toArray(); + $this->assertSame('localhost:8057', $body['HTTP_HOST']); + $this->assertMatchesRegularExpression('#^http://(localhost|127\.0\.0\.1):8057/$#', $body['REQUEST_URI']); + } finally { + unset($_SERVER['http_proxy']); + } + } + + public function testNoProxy() + { + putenv('no_proxy='.$_SERVER['no_proxy'] = 'example.com, localhost'); + + try { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/', [ + 'proxy' => 'http://localhost:8057', + ]); + + $body = $response->toArray(); + + $this->assertSame('HTTP/1.1', $body['SERVER_PROTOCOL']); + $this->assertSame('/', $body['REQUEST_URI']); + $this->assertSame('GET', $body['REQUEST_METHOD']); + } finally { + putenv('no_proxy'); + unset($_SERVER['no_proxy']); + } + } + + /** + * @requires extension zlib + */ + public function testAutoEncodingRequest() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057'); + + $this->assertSame(200, $response->getStatusCode()); + + $headers = $response->getHeaders(); + + $this->assertSame(['Accept-Encoding'], $headers['vary']); + $this->assertStringContainsString('gzip', $headers['content-encoding'][0]); + + $body = $response->toArray(); + + $this->assertStringContainsString('gzip', $body['HTTP_ACCEPT_ENCODING']); + } + + public function testBaseUri() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', '../404', [ + 'base_uri' => 'http://localhost:8057/abc/', + ]); + + $this->assertSame(404, $response->getStatusCode()); + $this->assertSame(['application/json'], $response->getHeaders(false)['content-type']); + } + + public function testQuery() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/?a=a', [ + 'query' => ['b' => 'b'], + ]); + + $body = $response->toArray(); + $this->assertSame('GET', $body['REQUEST_METHOD']); + $this->assertSame('/?a=a&b=b', $body['REQUEST_URI']); + } + + public function testInformationalResponse() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/103'); + + $this->assertSame('Here the body', $response->getContent()); + $this->assertSame(200, $response->getStatusCode()); + } + + public function testInformationalResponseStream() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/103'); + + $chunks = []; + foreach ($client->stream($response) as $chunk) { + $chunks[] = $chunk; + } + + $this->assertSame(103, $chunks[0]->getInformationalStatus()[0]); + $this->assertSame(['; rel=preload; as=style', '; rel=preload; as=script'], $chunks[0]->getInformationalStatus()[1]['link']); + $this->assertTrue($chunks[1]->isFirst()); + $this->assertSame('Here the body', $chunks[2]->getContent()); + $this->assertTrue($chunks[3]->isLast()); + $this->assertNull($chunks[3]->getInformationalStatus()); + + $this->assertSame(['date', 'content-length'], array_keys($response->getHeaders())); + $this->assertContains('Link: ; rel=preload; as=style', $response->getInfo('response_headers')); + } + + /** + * @requires extension zlib + */ + public function testUserlandEncodingRequest() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057', [ + 'headers' => ['Accept-Encoding' => 'gzip'], + ]); + + $headers = $response->getHeaders(); + + $this->assertSame(['Accept-Encoding'], $headers['vary']); + $this->assertStringContainsString('gzip', $headers['content-encoding'][0]); + + $body = $response->getContent(); + $this->assertSame("\x1F", $body[0]); + + $body = json_decode(gzdecode($body), true); + $this->assertSame('gzip', $body['HTTP_ACCEPT_ENCODING']); + } + + /** + * @requires extension zlib + */ + public function testGzipBroken() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/gzip-broken'); + + $this->expectException(TransportExceptionInterface::class); + $response->getContent(); + } + + public function testMaxDuration() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/max-duration', [ + 'max_duration' => 0.1, + ]); + + $start = microtime(true); + + try { + $response->getContent(); + } catch (TransportExceptionInterface $e) { + $this->addToAssertionCount(1); + } + + $duration = microtime(true) - $start; + + $this->assertLessThan(10, $duration); + } + + public function testWithOptions() + { + $client = $this->getHttpClient(__FUNCTION__); + if (!method_exists($client, 'withOptions')) { + $this->markTestSkipped(sprintf('Not implementing "%s::withOptions()" is deprecated.', get_debug_type($client))); + } + + $client2 = $client->withOptions(['base_uri' => 'http://localhost:8057/']); + + $this->assertNotSame($client, $client2); + $this->assertSame(\get_class($client), \get_class($client2)); + + $response = $client2->request('GET', '/'); + $this->assertSame(200, $response->getStatusCode()); + } +} diff --git a/src/vendor/symfony/http-client-contracts/Test/TestHttpServer.php b/src/vendor/symfony/http-client-contracts/Test/TestHttpServer.php new file mode 100644 index 0000000..55a744a --- /dev/null +++ b/src/vendor/symfony/http-client-contracts/Test/TestHttpServer.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\HttpClient\Test; + +use Symfony\Component\Process\PhpExecutableFinder; +use Symfony\Component\Process\Process; + +class TestHttpServer +{ + private static $process = []; + + /** + * @return Process + */ + public static function start(int $port = 8057) + { + if (isset(self::$process[$port])) { + self::$process[$port]->stop(); + } else { + register_shutdown_function(static function () use ($port) { + self::$process[$port]->stop(); + }); + } + + $finder = new PhpExecutableFinder(); + $process = new Process(array_merge([$finder->find(false)], $finder->findArguments(), ['-dopcache.enable=0', '-dvariables_order=EGPCS', '-S', '127.0.0.1:'.$port])); + $process->setWorkingDirectory(__DIR__.'/Fixtures/web'); + $process->start(); + self::$process[$port] = $process; + + do { + usleep(50000); + } while (!@fopen('http://127.0.0.1:'.$port, 'r')); + + return $process; + } +} diff --git a/src/vendor/symfony/http-client-contracts/composer.json b/src/vendor/symfony/http-client-contracts/composer.json new file mode 100644 index 0000000..b76cab8 --- /dev/null +++ b/src/vendor/symfony/http-client-contracts/composer.json @@ -0,0 +1,37 @@ +{ + "name": "symfony/http-client-contracts", + "type": "library", + "description": "Generic abstractions related to HTTP clients", + "keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.2.5" + }, + "suggest": { + "symfony/http-client-implementation": "" + }, + "autoload": { + "psr-4": { "Symfony\\Contracts\\HttpClient\\": "" } + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + } +} diff --git a/src/vendor/symfony/http-client/AmpHttpClient.php b/src/vendor/symfony/http-client/AmpHttpClient.php new file mode 100644 index 0000000..2ab7e27 --- /dev/null +++ b/src/vendor/symfony/http-client/AmpHttpClient.php @@ -0,0 +1,181 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Amp\CancelledException; +use Amp\Http\Client\DelegateHttpClient; +use Amp\Http\Client\InterceptedHttpClient; +use Amp\Http\Client\PooledHttpClient; +use Amp\Http\Client\Request; +use Amp\Http\Tunnel\Http1TunnelConnector; +use Amp\Promise; +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerAwareTrait; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpClient\Internal\AmpClientState; +use Symfony\Component\HttpClient\Response\AmpResponse; +use Symfony\Component\HttpClient\Response\ResponseStream; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\HttpClient\ResponseStreamInterface; +use Symfony\Contracts\Service\ResetInterface; + +if (!interface_exists(DelegateHttpClient::class)) { + throw new \LogicException('You cannot use "Symfony\Component\HttpClient\AmpHttpClient" as the "amphp/http-client" package is not installed. Try running "composer require amphp/http-client:^4.2.1".'); +} + +if (!interface_exists(Promise::class)) { + throw new \LogicException('You cannot use "Symfony\Component\HttpClient\AmpHttpClient" as the installed "amphp/http-client" is not compatible with this version of "symfony/http-client". Try downgrading "amphp/http-client" to "^4.2.1".'); +} + +/** + * A portable implementation of the HttpClientInterface contracts based on Amp's HTTP client. + * + * @author Nicolas Grekas + */ +final class AmpHttpClient implements HttpClientInterface, LoggerAwareInterface, ResetInterface +{ + use HttpClientTrait; + use LoggerAwareTrait; + + private $defaultOptions = self::OPTIONS_DEFAULTS; + private static $emptyDefaults = self::OPTIONS_DEFAULTS; + + /** @var AmpClientState */ + private $multi; + + /** + * @param array $defaultOptions Default requests' options + * @param callable|null $clientConfigurator A callable that builds a {@see DelegateHttpClient} from a {@see PooledHttpClient}; + * passing null builds an {@see InterceptedHttpClient} with 2 retries on failures + * @param int $maxHostConnections The maximum number of connections to a single host + * @param int $maxPendingPushes The maximum number of pushed responses to accept in the queue + * + * @see HttpClientInterface::OPTIONS_DEFAULTS for available options + */ + public function __construct(array $defaultOptions = [], callable $clientConfigurator = null, int $maxHostConnections = 6, int $maxPendingPushes = 50) + { + $this->defaultOptions['buffer'] = $this->defaultOptions['buffer'] ?? \Closure::fromCallable([__CLASS__, 'shouldBuffer']); + + if ($defaultOptions) { + [, $this->defaultOptions] = self::prepareRequest(null, null, $defaultOptions, $this->defaultOptions); + } + + $this->multi = new AmpClientState($clientConfigurator, $maxHostConnections, $maxPendingPushes, $this->logger); + } + + /** + * @see HttpClientInterface::OPTIONS_DEFAULTS for available options + * + * {@inheritdoc} + */ + public function request(string $method, string $url, array $options = []): ResponseInterface + { + [$url, $options] = self::prepareRequest($method, $url, $options, $this->defaultOptions); + + $options['proxy'] = self::getProxy($options['proxy'], $url, $options['no_proxy']); + + if (null !== $options['proxy'] && !class_exists(Http1TunnelConnector::class)) { + throw new \LogicException('You cannot use the "proxy" option as the "amphp/http-tunnel" package is not installed. Try running "composer require amphp/http-tunnel".'); + } + + if ($options['bindto']) { + if (0 === strpos($options['bindto'], 'if!')) { + throw new TransportException(__CLASS__.' cannot bind to network interfaces, use e.g. CurlHttpClient instead.'); + } + if (0 === strpos($options['bindto'], 'host!')) { + $options['bindto'] = substr($options['bindto'], 5); + } + } + + if (('' !== $options['body'] || 'POST' === $method || isset($options['normalized_headers']['content-length'])) && !isset($options['normalized_headers']['content-type'])) { + $options['headers'][] = 'Content-Type: application/x-www-form-urlencoded'; + } + + if (!isset($options['normalized_headers']['user-agent'])) { + $options['headers'][] = 'User-Agent: Symfony HttpClient/Amp'; + } + + if (0 < $options['max_duration']) { + $options['timeout'] = min($options['max_duration'], $options['timeout']); + } + + if ($options['resolve']) { + $this->multi->dnsCache = $options['resolve'] + $this->multi->dnsCache; + } + + if ($options['peer_fingerprint'] && !isset($options['peer_fingerprint']['pin-sha256'])) { + throw new TransportException(__CLASS__.' supports only "pin-sha256" fingerprints.'); + } + + $request = new Request(implode('', $url), $method); + + if ($options['http_version']) { + switch ((float) $options['http_version']) { + case 1.0: $request->setProtocolVersions(['1.0']); break; + case 1.1: $request->setProtocolVersions(['1.1', '1.0']); break; + default: $request->setProtocolVersions(['2', '1.1', '1.0']); break; + } + } + + foreach ($options['headers'] as $v) { + $h = explode(': ', $v, 2); + $request->addHeader($h[0], $h[1]); + } + + $request->setTcpConnectTimeout(1000 * $options['timeout']); + $request->setTlsHandshakeTimeout(1000 * $options['timeout']); + $request->setTransferTimeout(1000 * $options['max_duration']); + if (method_exists($request, 'setInactivityTimeout')) { + $request->setInactivityTimeout(0); + } + + if ('' !== $request->getUri()->getUserInfo() && !$request->hasHeader('authorization')) { + $auth = explode(':', $request->getUri()->getUserInfo(), 2); + $auth = array_map('rawurldecode', $auth) + [1 => '']; + $request->setHeader('Authorization', 'Basic '.base64_encode(implode(':', $auth))); + } + + return new AmpResponse($this->multi, $request, $options, $this->logger); + } + + /** + * {@inheritdoc} + */ + public function stream($responses, float $timeout = null): ResponseStreamInterface + { + if ($responses instanceof AmpResponse) { + $responses = [$responses]; + } elseif (!is_iterable($responses)) { + throw new \TypeError(sprintf('"%s()" expects parameter 1 to be an iterable of AmpResponse objects, "%s" given.', __METHOD__, get_debug_type($responses))); + } + + return new ResponseStream(AmpResponse::stream($responses, $timeout)); + } + + public function reset() + { + $this->multi->dnsCache = []; + + foreach ($this->multi->pushedResponses as $authority => $pushedResponses) { + foreach ($pushedResponses as [$pushedUrl, $pushDeferred]) { + $pushDeferred->fail(new CancelledException()); + + if ($this->logger) { + $this->logger->debug(sprintf('Unused pushed response: "%s"', $pushedUrl)); + } + } + } + + $this->multi->pushedResponses = []; + } +} diff --git a/src/vendor/symfony/http-client/AsyncDecoratorTrait.php b/src/vendor/symfony/http-client/AsyncDecoratorTrait.php new file mode 100644 index 0000000..aff402d --- /dev/null +++ b/src/vendor/symfony/http-client/AsyncDecoratorTrait.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Symfony\Component\HttpClient\Response\AsyncResponse; +use Symfony\Component\HttpClient\Response\ResponseStream; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\HttpClient\ResponseStreamInterface; + +/** + * Eases with processing responses while streaming them. + * + * @author Nicolas Grekas + */ +trait AsyncDecoratorTrait +{ + use DecoratorTrait; + + /** + * {@inheritdoc} + * + * @return AsyncResponse + */ + abstract public function request(string $method, string $url, array $options = []): ResponseInterface; + + /** + * {@inheritdoc} + */ + public function stream($responses, float $timeout = null): ResponseStreamInterface + { + if ($responses instanceof AsyncResponse) { + $responses = [$responses]; + } elseif (!is_iterable($responses)) { + throw new \TypeError(sprintf('"%s()" expects parameter 1 to be an iterable of AsyncResponse objects, "%s" given.', __METHOD__, get_debug_type($responses))); + } + + return new ResponseStream(AsyncResponse::stream($responses, $timeout, static::class)); + } +} diff --git a/src/vendor/symfony/http-client/CHANGELOG.md b/src/vendor/symfony/http-client/CHANGELOG.md new file mode 100644 index 0000000..7c2fc22 --- /dev/null +++ b/src/vendor/symfony/http-client/CHANGELOG.md @@ -0,0 +1,54 @@ +CHANGELOG +========= + +5.4 +--- + + * Add `MockHttpClient::setResponseFactory()` method to be able to set response factory after client creating + +5.3 +--- + + * Implement `HttpClientInterface::withOptions()` from `symfony/contracts` v2.4 + * Add `DecoratorTrait` to ease writing simple decorators + +5.2.0 +----- + + * added `AsyncDecoratorTrait` to ease processing responses without breaking async + * added support for pausing responses with a new `pause_handler` callable exposed as an info item + * added `StreamableInterface` to ease turning responses into PHP streams + * added `MockResponse::getRequestMethod()` and `getRequestUrl()` to allow inspecting which request has been sent + * added `EventSourceHttpClient` a Server-Sent events stream implementing the [EventSource specification](https://www.w3.org/TR/eventsource/#eventsource) + * added option "extra.curl" to allow setting additional curl options in `CurlHttpClient` + * added `RetryableHttpClient` to automatically retry failed HTTP requests. + * added `extra.trace_content` option to `TraceableHttpClient` to prevent it from keeping the content in memory + +5.1.0 +----- + + * added `NoPrivateNetworkHttpClient` decorator + * added `AmpHttpClient`, a portable HTTP/2 implementation based on Amp + * added `LoggerAwareInterface` to `ScopingHttpClient` and `TraceableHttpClient` + * made `HttpClient::create()` return an `AmpHttpClient` when `amphp/http-client` is found but curl is not or too old + +4.4.0 +----- + + * added `canceled` to `ResponseInterface::getInfo()` + * added `HttpClient::createForBaseUri()` + * added `HttplugClient` with support for sync and async requests + * added `max_duration` option + * added support for NTLM authentication + * added `StreamWrapper` to cast any `ResponseInterface` instances to PHP streams. + * added `$response->toStream()` to cast responses to regular PHP streams + * made `Psr18Client` implement relevant PSR-17 factories and have streaming responses + * added `TraceableHttpClient`, `HttpClientDataCollector` and `HttpClientPass` to integrate with the web profiler + * allow enabling buffering conditionally with a Closure + * allow option "buffer" to be a stream resource + * allow arbitrary values for the "json" option + +4.3.0 +----- + + * added the component diff --git a/src/vendor/symfony/http-client/CachingHttpClient.php b/src/vendor/symfony/http-client/CachingHttpClient.php new file mode 100644 index 0000000..e1d7023 --- /dev/null +++ b/src/vendor/symfony/http-client/CachingHttpClient.php @@ -0,0 +1,152 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Symfony\Component\HttpClient\Response\MockResponse; +use Symfony\Component\HttpClient\Response\ResponseStream; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpCache\HttpCache; +use Symfony\Component\HttpKernel\HttpCache\StoreInterface; +use Symfony\Component\HttpKernel\HttpClientKernel; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\HttpClient\ResponseStreamInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * Adds caching on top of an HTTP client. + * + * The implementation buffers responses in memory and doesn't stream directly from the network. + * You can disable/enable this layer by setting option "no_cache" under "extra" to true/false. + * By default, caching is enabled unless the "buffer" option is set to false. + * + * @author Nicolas Grekas + */ +class CachingHttpClient implements HttpClientInterface, ResetInterface +{ + use HttpClientTrait; + + private $client; + private $cache; + private $defaultOptions = self::OPTIONS_DEFAULTS; + + public function __construct(HttpClientInterface $client, StoreInterface $store, array $defaultOptions = []) + { + if (!class_exists(HttpClientKernel::class)) { + throw new \LogicException(sprintf('Using "%s" requires that the HttpKernel component version 4.3 or higher is installed, try running "composer require symfony/http-kernel:^5.4".', __CLASS__)); + } + + $this->client = $client; + $kernel = new HttpClientKernel($client); + $this->cache = new HttpCache($kernel, $store, null, $defaultOptions); + + unset($defaultOptions['debug']); + unset($defaultOptions['default_ttl']); + unset($defaultOptions['private_headers']); + unset($defaultOptions['allow_reload']); + unset($defaultOptions['allow_revalidate']); + unset($defaultOptions['stale_while_revalidate']); + unset($defaultOptions['stale_if_error']); + unset($defaultOptions['trace_level']); + unset($defaultOptions['trace_header']); + + if ($defaultOptions) { + [, $this->defaultOptions] = self::prepareRequest(null, null, $defaultOptions, $this->defaultOptions); + } + } + + /** + * {@inheritdoc} + */ + public function request(string $method, string $url, array $options = []): ResponseInterface + { + [$url, $options] = $this->prepareRequest($method, $url, $options, $this->defaultOptions, true); + $url = implode('', $url); + + if (!empty($options['body']) || !empty($options['extra']['no_cache']) || !\in_array($method, ['GET', 'HEAD', 'OPTIONS'])) { + return $this->client->request($method, $url, $options); + } + + $request = Request::create($url, $method); + $request->attributes->set('http_client_options', $options); + + foreach ($options['normalized_headers'] as $name => $values) { + if ('cookie' !== $name) { + foreach ($values as $value) { + $request->headers->set($name, substr($value, 2 + \strlen($name)), false); + } + + continue; + } + + foreach ($values as $cookies) { + foreach (explode('; ', substr($cookies, \strlen('Cookie: '))) as $cookie) { + if ('' !== $cookie) { + $cookie = explode('=', $cookie, 2); + $request->cookies->set($cookie[0], $cookie[1] ?? ''); + } + } + } + } + + $response = $this->cache->handle($request); + $response = new MockResponse($response->getContent(), [ + 'http_code' => $response->getStatusCode(), + 'response_headers' => $response->headers->allPreserveCase(), + ]); + + return MockResponse::fromRequest($method, $url, $options, $response); + } + + /** + * {@inheritdoc} + */ + public function stream($responses, float $timeout = null): ResponseStreamInterface + { + if ($responses instanceof ResponseInterface) { + $responses = [$responses]; + } elseif (!is_iterable($responses)) { + throw new \TypeError(sprintf('"%s()" expects parameter 1 to be an iterable of ResponseInterface objects, "%s" given.', __METHOD__, get_debug_type($responses))); + } + + $mockResponses = []; + $clientResponses = []; + + foreach ($responses as $response) { + if ($response instanceof MockResponse) { + $mockResponses[] = $response; + } else { + $clientResponses[] = $response; + } + } + + if (!$mockResponses) { + return $this->client->stream($clientResponses, $timeout); + } + + if (!$clientResponses) { + return new ResponseStream(MockResponse::stream($mockResponses, $timeout)); + } + + return new ResponseStream((function () use ($mockResponses, $clientResponses, $timeout) { + yield from MockResponse::stream($mockResponses, $timeout); + yield $this->client->stream($clientResponses, $timeout); + })()); + } + + public function reset() + { + if ($this->client instanceof ResetInterface) { + $this->client->reset(); + } + } +} diff --git a/src/vendor/symfony/http-client/Chunk/DataChunk.php b/src/vendor/symfony/http-client/Chunk/DataChunk.php new file mode 100644 index 0000000..37ca848 --- /dev/null +++ b/src/vendor/symfony/http-client/Chunk/DataChunk.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Chunk; + +use Symfony\Contracts\HttpClient\ChunkInterface; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class DataChunk implements ChunkInterface +{ + private $offset = 0; + private $content = ''; + + public function __construct(int $offset = 0, string $content = '') + { + $this->offset = $offset; + $this->content = $content; + } + + /** + * {@inheritdoc} + */ + public function isTimeout(): bool + { + return false; + } + + /** + * {@inheritdoc} + */ + public function isFirst(): bool + { + return false; + } + + /** + * {@inheritdoc} + */ + public function isLast(): bool + { + return false; + } + + /** + * {@inheritdoc} + */ + public function getInformationalStatus(): ?array + { + return null; + } + + /** + * {@inheritdoc} + */ + public function getContent(): string + { + return $this->content; + } + + /** + * {@inheritdoc} + */ + public function getOffset(): int + { + return $this->offset; + } + + /** + * {@inheritdoc} + */ + public function getError(): ?string + { + return null; + } +} diff --git a/src/vendor/symfony/http-client/Chunk/ErrorChunk.php b/src/vendor/symfony/http-client/Chunk/ErrorChunk.php new file mode 100644 index 0000000..a19f433 --- /dev/null +++ b/src/vendor/symfony/http-client/Chunk/ErrorChunk.php @@ -0,0 +1,140 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Chunk; + +use Symfony\Component\HttpClient\Exception\TimeoutException; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Contracts\HttpClient\ChunkInterface; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class ErrorChunk implements ChunkInterface +{ + private $didThrow = false; + private $offset; + private $errorMessage; + private $error; + + /** + * @param \Throwable|string $error + */ + public function __construct(int $offset, $error) + { + $this->offset = $offset; + + if (\is_string($error)) { + $this->errorMessage = $error; + } else { + $this->error = $error; + $this->errorMessage = $error->getMessage(); + } + } + + /** + * {@inheritdoc} + */ + public function isTimeout(): bool + { + $this->didThrow = true; + + if (null !== $this->error) { + throw new TransportException($this->errorMessage, 0, $this->error); + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function isFirst(): bool + { + $this->didThrow = true; + throw null !== $this->error ? new TransportException($this->errorMessage, 0, $this->error) : new TimeoutException($this->errorMessage); + } + + /** + * {@inheritdoc} + */ + public function isLast(): bool + { + $this->didThrow = true; + throw null !== $this->error ? new TransportException($this->errorMessage, 0, $this->error) : new TimeoutException($this->errorMessage); + } + + /** + * {@inheritdoc} + */ + public function getInformationalStatus(): ?array + { + $this->didThrow = true; + throw null !== $this->error ? new TransportException($this->errorMessage, 0, $this->error) : new TimeoutException($this->errorMessage); + } + + /** + * {@inheritdoc} + */ + public function getContent(): string + { + $this->didThrow = true; + throw null !== $this->error ? new TransportException($this->errorMessage, 0, $this->error) : new TimeoutException($this->errorMessage); + } + + /** + * {@inheritdoc} + */ + public function getOffset(): int + { + return $this->offset; + } + + /** + * {@inheritdoc} + */ + public function getError(): ?string + { + return $this->errorMessage; + } + + /** + * @return bool Whether the wrapped error has been thrown or not + */ + public function didThrow(bool $didThrow = null): bool + { + if (null !== $didThrow && $this->didThrow !== $didThrow) { + return !$this->didThrow = $didThrow; + } + + return $this->didThrow; + } + + public function __sleep(): array + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + if (!$this->didThrow) { + $this->didThrow = true; + throw null !== $this->error ? new TransportException($this->errorMessage, 0, $this->error) : new TimeoutException($this->errorMessage); + } + } +} diff --git a/src/vendor/symfony/http-client/Chunk/FirstChunk.php b/src/vendor/symfony/http-client/Chunk/FirstChunk.php new file mode 100644 index 0000000..d891ca8 --- /dev/null +++ b/src/vendor/symfony/http-client/Chunk/FirstChunk.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Chunk; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class FirstChunk extends DataChunk +{ + /** + * {@inheritdoc} + */ + public function isFirst(): bool + { + return true; + } +} diff --git a/src/vendor/symfony/http-client/Chunk/InformationalChunk.php b/src/vendor/symfony/http-client/Chunk/InformationalChunk.php new file mode 100644 index 0000000..c4452f1 --- /dev/null +++ b/src/vendor/symfony/http-client/Chunk/InformationalChunk.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Chunk; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class InformationalChunk extends DataChunk +{ + private $status; + + public function __construct(int $statusCode, array $headers) + { + $this->status = [$statusCode, $headers]; + } + + /** + * {@inheritdoc} + */ + public function getInformationalStatus(): ?array + { + return $this->status; + } +} diff --git a/src/vendor/symfony/http-client/Chunk/LastChunk.php b/src/vendor/symfony/http-client/Chunk/LastChunk.php new file mode 100644 index 0000000..84095d3 --- /dev/null +++ b/src/vendor/symfony/http-client/Chunk/LastChunk.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Chunk; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class LastChunk extends DataChunk +{ + /** + * {@inheritdoc} + */ + public function isLast(): bool + { + return true; + } +} diff --git a/src/vendor/symfony/http-client/Chunk/ServerSentEvent.php b/src/vendor/symfony/http-client/Chunk/ServerSentEvent.php new file mode 100644 index 0000000..f7ff4b9 --- /dev/null +++ b/src/vendor/symfony/http-client/Chunk/ServerSentEvent.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Chunk; + +use Symfony\Contracts\HttpClient\ChunkInterface; + +/** + * @author Antoine Bluchet + * @author Nicolas Grekas + */ +final class ServerSentEvent extends DataChunk implements ChunkInterface +{ + private $data = ''; + private $id = ''; + private $type = 'message'; + private $retry = 0; + + public function __construct(string $content) + { + parent::__construct(-1, $content); + + // remove BOM + if (0 === strpos($content, "\xEF\xBB\xBF")) { + $content = substr($content, 3); + } + + foreach (preg_split("/(?:\r\n|[\r\n])/", $content) as $line) { + if (0 === $i = strpos($line, ':')) { + continue; + } + + $i = false === $i ? \strlen($line) : $i; + $field = substr($line, 0, $i); + $i += 1 + (' ' === ($line[1 + $i] ?? '')); + + switch ($field) { + case 'id': $this->id = substr($line, $i); break; + case 'event': $this->type = substr($line, $i); break; + case 'data': $this->data .= ('' === $this->data ? '' : "\n").substr($line, $i); break; + case 'retry': + $retry = substr($line, $i); + + if ('' !== $retry && \strlen($retry) === strspn($retry, '0123456789')) { + $this->retry = $retry / 1000.0; + } + break; + } + } + } + + public function getId(): string + { + return $this->id; + } + + public function getType(): string + { + return $this->type; + } + + public function getData(): string + { + return $this->data; + } + + public function getRetry(): float + { + return $this->retry; + } +} diff --git a/src/vendor/symfony/http-client/CurlHttpClient.php b/src/vendor/symfony/http-client/CurlHttpClient.php new file mode 100644 index 0000000..ef6d700 --- /dev/null +++ b/src/vendor/symfony/http-client/CurlHttpClient.php @@ -0,0 +1,552 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpClient\Exception\InvalidArgumentException; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpClient\Internal\CurlClientState; +use Symfony\Component\HttpClient\Internal\PushedResponse; +use Symfony\Component\HttpClient\Response\CurlResponse; +use Symfony\Component\HttpClient\Response\ResponseStream; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\HttpClient\ResponseStreamInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * A performant implementation of the HttpClientInterface contracts based on the curl extension. + * + * This provides fully concurrent HTTP requests, with transparent + * HTTP/2 push when a curl version that supports it is installed. + * + * @author Nicolas Grekas + */ +final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface, ResetInterface +{ + use HttpClientTrait; + + private $defaultOptions = self::OPTIONS_DEFAULTS + [ + 'auth_ntlm' => null, // array|string - an array containing the username as first value, and optionally the + // password as the second one; or string like username:password - enabling NTLM auth + 'extra' => [ + 'curl' => [], // A list of extra curl options indexed by their corresponding CURLOPT_* + ], + ]; + private static $emptyDefaults = self::OPTIONS_DEFAULTS + ['auth_ntlm' => null]; + + /** + * @var LoggerInterface|null + */ + private $logger; + + /** + * An internal object to share state between the client and its responses. + * + * @var CurlClientState + */ + private $multi; + + /** + * @param array $defaultOptions Default request's options + * @param int $maxHostConnections The maximum number of connections to a single host + * @param int $maxPendingPushes The maximum number of pushed responses to accept in the queue + * + * @see HttpClientInterface::OPTIONS_DEFAULTS for available options + */ + public function __construct(array $defaultOptions = [], int $maxHostConnections = 6, int $maxPendingPushes = 50) + { + if (!\extension_loaded('curl')) { + throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\CurlHttpClient" as the "curl" extension is not installed.'); + } + + $this->defaultOptions['buffer'] = $this->defaultOptions['buffer'] ?? \Closure::fromCallable([__CLASS__, 'shouldBuffer']); + + if ($defaultOptions) { + [, $this->defaultOptions] = self::prepareRequest(null, null, $defaultOptions, $this->defaultOptions); + } + + $this->multi = new CurlClientState($maxHostConnections, $maxPendingPushes); + } + + public function setLogger(LoggerInterface $logger): void + { + $this->logger = $this->multi->logger = $logger; + } + + /** + * @see HttpClientInterface::OPTIONS_DEFAULTS for available options + * + * {@inheritdoc} + */ + public function request(string $method, string $url, array $options = []): ResponseInterface + { + [$url, $options] = self::prepareRequest($method, $url, $options, $this->defaultOptions); + $scheme = $url['scheme']; + $authority = $url['authority']; + $host = parse_url($authority, \PHP_URL_HOST); + $proxy = self::getProxyUrl($options['proxy'], $url); + $url = implode('', $url); + + if (!isset($options['normalized_headers']['user-agent'])) { + $options['headers'][] = 'User-Agent: Symfony HttpClient/Curl'; + } + + $curlopts = [ + \CURLOPT_URL => $url, + \CURLOPT_TCP_NODELAY => true, + \CURLOPT_PROTOCOLS => \CURLPROTO_HTTP | \CURLPROTO_HTTPS, + \CURLOPT_REDIR_PROTOCOLS => \CURLPROTO_HTTP | \CURLPROTO_HTTPS, + \CURLOPT_FOLLOWLOCATION => true, + \CURLOPT_MAXREDIRS => 0 < $options['max_redirects'] ? $options['max_redirects'] : 0, + \CURLOPT_COOKIEFILE => '', // Keep track of cookies during redirects + \CURLOPT_TIMEOUT => 0, + \CURLOPT_PROXY => $proxy, + \CURLOPT_NOPROXY => $options['no_proxy'] ?? $_SERVER['no_proxy'] ?? $_SERVER['NO_PROXY'] ?? '', + \CURLOPT_SSL_VERIFYPEER => $options['verify_peer'], + \CURLOPT_SSL_VERIFYHOST => $options['verify_host'] ? 2 : 0, + \CURLOPT_CAINFO => $options['cafile'], + \CURLOPT_CAPATH => $options['capath'], + \CURLOPT_SSL_CIPHER_LIST => $options['ciphers'], + \CURLOPT_SSLCERT => $options['local_cert'], + \CURLOPT_SSLKEY => $options['local_pk'], + \CURLOPT_KEYPASSWD => $options['passphrase'], + \CURLOPT_CERTINFO => $options['capture_peer_cert_chain'], + ]; + + if (1.0 === (float) $options['http_version']) { + $curlopts[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_0; + } elseif (1.1 === (float) $options['http_version']) { + $curlopts[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_1; + } elseif (\defined('CURL_VERSION_HTTP2') && (\CURL_VERSION_HTTP2 & CurlClientState::$curlVersion['features']) && ('https:' === $scheme || 2.0 === (float) $options['http_version'])) { + $curlopts[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_2_0; + } + + if (isset($options['auth_ntlm'])) { + $curlopts[\CURLOPT_HTTPAUTH] = \CURLAUTH_NTLM; + $curlopts[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_1; + + if (\is_array($options['auth_ntlm'])) { + $count = \count($options['auth_ntlm']); + if ($count <= 0 || $count > 2) { + throw new InvalidArgumentException(sprintf('Option "auth_ntlm" must contain 1 or 2 elements, %d given.', $count)); + } + + $options['auth_ntlm'] = implode(':', $options['auth_ntlm']); + } + + if (!\is_string($options['auth_ntlm'])) { + throw new InvalidArgumentException(sprintf('Option "auth_ntlm" must be a string or an array, "%s" given.', get_debug_type($options['auth_ntlm']))); + } + + $curlopts[\CURLOPT_USERPWD] = $options['auth_ntlm']; + } + + if (!\ZEND_THREAD_SAFE) { + $curlopts[\CURLOPT_DNS_USE_GLOBAL_CACHE] = false; + } + + if (\defined('CURLOPT_HEADEROPT') && \defined('CURLHEADER_SEPARATE')) { + $curlopts[\CURLOPT_HEADEROPT] = \CURLHEADER_SEPARATE; + } + + // curl's resolve feature varies by host:port but ours varies by host only, let's handle this with our own DNS map + if (isset($this->multi->dnsCache->hostnames[$host])) { + $options['resolve'] += [$host => $this->multi->dnsCache->hostnames[$host]]; + } + + if ($options['resolve'] || $this->multi->dnsCache->evictions) { + // First reset any old DNS cache entries then add the new ones + $resolve = $this->multi->dnsCache->evictions; + $this->multi->dnsCache->evictions = []; + $port = parse_url($authority, \PHP_URL_PORT) ?: ('http:' === $scheme ? 80 : 443); + + if ($resolve && 0x072A00 > CurlClientState::$curlVersion['version_number']) { + // DNS cache removals require curl 7.42 or higher + $this->multi->reset(); + } + + foreach ($options['resolve'] as $host => $ip) { + $resolve[] = null === $ip ? "-$host:$port" : "$host:$port:$ip"; + $this->multi->dnsCache->hostnames[$host] = $ip; + $this->multi->dnsCache->removals["-$host:$port"] = "-$host:$port"; + } + + $curlopts[\CURLOPT_RESOLVE] = $resolve; + } + + if ('POST' === $method) { + // Use CURLOPT_POST to have browser-like POST-to-GET redirects for 301, 302 and 303 + $curlopts[\CURLOPT_POST] = true; + } elseif ('HEAD' === $method) { + $curlopts[\CURLOPT_NOBODY] = true; + } else { + $curlopts[\CURLOPT_CUSTOMREQUEST] = $method; + } + + if ('\\' !== \DIRECTORY_SEPARATOR && $options['timeout'] < 1) { + $curlopts[\CURLOPT_NOSIGNAL] = true; + } + + if (\extension_loaded('zlib') && !isset($options['normalized_headers']['accept-encoding'])) { + $options['headers'][] = 'Accept-Encoding: gzip'; // Expose only one encoding, some servers mess up when more are provided + } + $body = $options['body']; + + foreach ($options['headers'] as $i => $header) { + if (\is_string($body) && '' !== $body && 0 === stripos($header, 'Content-Length: ')) { + // Let curl handle Content-Length headers + unset($options['headers'][$i]); + continue; + } + if (':' === $header[-2] && \strlen($header) - 2 === strpos($header, ': ')) { + // curl requires a special syntax to send empty headers + $curlopts[\CURLOPT_HTTPHEADER][] = substr_replace($header, ';', -2); + } else { + $curlopts[\CURLOPT_HTTPHEADER][] = $header; + } + } + + // Prevent curl from sending its default Accept and Expect headers + foreach (['accept', 'expect'] as $header) { + if (!isset($options['normalized_headers'][$header][0])) { + $curlopts[\CURLOPT_HTTPHEADER][] = $header.':'; + } + } + + if (!\is_string($body)) { + if (\is_resource($body)) { + $curlopts[\CURLOPT_INFILE] = $body; + } else { + $eof = false; + $buffer = ''; + $curlopts[\CURLOPT_READFUNCTION] = static function ($ch, $fd, $length) use ($body, &$buffer, &$eof) { + return self::readRequestBody($length, $body, $buffer, $eof); + }; + } + + if (isset($options['normalized_headers']['content-length'][0])) { + $curlopts[\CURLOPT_INFILESIZE] = (int) substr($options['normalized_headers']['content-length'][0], \strlen('Content-Length: ')); + } + if (!isset($options['normalized_headers']['transfer-encoding'])) { + $curlopts[\CURLOPT_HTTPHEADER][] = 'Transfer-Encoding:'.(isset($curlopts[\CURLOPT_INFILESIZE]) ? '' : ' chunked'); + } + + if ('POST' !== $method) { + $curlopts[\CURLOPT_UPLOAD] = true; + + if (!isset($options['normalized_headers']['content-type']) && 0 !== ($curlopts[\CURLOPT_INFILESIZE] ?? null)) { + $curlopts[\CURLOPT_HTTPHEADER][] = 'Content-Type: application/x-www-form-urlencoded'; + } + } + } elseif ('' !== $body || 'POST' === $method) { + $curlopts[\CURLOPT_POSTFIELDS] = $body; + } + + if ($options['peer_fingerprint']) { + if (!isset($options['peer_fingerprint']['pin-sha256'])) { + throw new TransportException(__CLASS__.' supports only "pin-sha256" fingerprints.'); + } + + $curlopts[\CURLOPT_PINNEDPUBLICKEY] = 'sha256//'.implode(';sha256//', $options['peer_fingerprint']['pin-sha256']); + } + + if ($options['bindto']) { + if (file_exists($options['bindto'])) { + $curlopts[\CURLOPT_UNIX_SOCKET_PATH] = $options['bindto']; + } elseif (!str_starts_with($options['bindto'], 'if!') && preg_match('/^(.*):(\d+)$/', $options['bindto'], $matches)) { + $curlopts[\CURLOPT_INTERFACE] = $matches[1]; + $curlopts[\CURLOPT_LOCALPORT] = $matches[2]; + } else { + $curlopts[\CURLOPT_INTERFACE] = $options['bindto']; + } + } + + if (0 < $options['max_duration']) { + $curlopts[\CURLOPT_TIMEOUT_MS] = 1000 * $options['max_duration']; + } + + if (!empty($options['extra']['curl']) && \is_array($options['extra']['curl'])) { + $this->validateExtraCurlOptions($options['extra']['curl']); + $curlopts += $options['extra']['curl']; + } + + if ($pushedResponse = $this->multi->pushedResponses[$url] ?? null) { + unset($this->multi->pushedResponses[$url]); + + if (self::acceptPushForRequest($method, $options, $pushedResponse)) { + $this->logger && $this->logger->debug(sprintf('Accepting pushed response: "%s %s"', $method, $url)); + + // Reinitialize the pushed response with request's options + $ch = $pushedResponse->handle; + $pushedResponse = $pushedResponse->response; + $pushedResponse->__construct($this->multi, $url, $options, $this->logger); + } else { + $this->logger && $this->logger->debug(sprintf('Rejecting pushed response: "%s"', $url)); + $pushedResponse = null; + } + } + + if (!$pushedResponse) { + $ch = curl_init(); + $this->logger && $this->logger->info(sprintf('Request: "%s %s"', $method, $url)); + $curlopts += [\CURLOPT_SHARE => $this->multi->share]; + } + + foreach ($curlopts as $opt => $value) { + if (null !== $value && !curl_setopt($ch, $opt, $value) && \CURLOPT_CERTINFO !== $opt && (!\defined('CURLOPT_HEADEROPT') || \CURLOPT_HEADEROPT !== $opt)) { + $constantName = $this->findConstantName($opt); + throw new TransportException(sprintf('Curl option "%s" is not supported.', $constantName ?? $opt)); + } + } + + return $pushedResponse ?? new CurlResponse($this->multi, $ch, $options, $this->logger, $method, self::createRedirectResolver($options, $host), CurlClientState::$curlVersion['version_number']); + } + + /** + * {@inheritdoc} + */ + public function stream($responses, float $timeout = null): ResponseStreamInterface + { + if ($responses instanceof CurlResponse) { + $responses = [$responses]; + } elseif (!is_iterable($responses)) { + throw new \TypeError(sprintf('"%s()" expects parameter 1 to be an iterable of CurlResponse objects, "%s" given.', __METHOD__, get_debug_type($responses))); + } + + if (\is_resource($this->multi->handle) || $this->multi->handle instanceof \CurlMultiHandle) { + $active = 0; + while (\CURLM_CALL_MULTI_PERFORM === curl_multi_exec($this->multi->handle, $active)) { + } + } + + return new ResponseStream(CurlResponse::stream($responses, $timeout)); + } + + public function reset() + { + $this->multi->reset(); + } + + /** + * Accepts pushed responses only if their headers related to authentication match the request. + */ + private static function acceptPushForRequest(string $method, array $options, PushedResponse $pushedResponse): bool + { + if ('' !== $options['body'] || $method !== $pushedResponse->requestHeaders[':method'][0]) { + return false; + } + + foreach (['proxy', 'no_proxy', 'bindto', 'local_cert', 'local_pk'] as $k) { + if ($options[$k] !== $pushedResponse->parentOptions[$k]) { + return false; + } + } + + foreach (['authorization', 'cookie', 'range', 'proxy-authorization'] as $k) { + $normalizedHeaders = $options['normalized_headers'][$k] ?? []; + foreach ($normalizedHeaders as $i => $v) { + $normalizedHeaders[$i] = substr($v, \strlen($k) + 2); + } + + if (($pushedResponse->requestHeaders[$k] ?? []) !== $normalizedHeaders) { + return false; + } + } + + return true; + } + + /** + * Wraps the request's body callback to allow it to return strings longer than curl requested. + */ + private static function readRequestBody(int $length, \Closure $body, string &$buffer, bool &$eof): string + { + if (!$eof && \strlen($buffer) < $length) { + if (!\is_string($data = $body($length))) { + throw new TransportException(sprintf('The return value of the "body" option callback must be a string, "%s" returned.', get_debug_type($data))); + } + + $buffer .= $data; + $eof = '' === $data; + } + + $data = substr($buffer, 0, $length); + $buffer = substr($buffer, $length); + + return $data; + } + + /** + * Resolves relative URLs on redirects and deals with authentication headers. + * + * Work around CVE-2018-1000007: Authorization and Cookie headers should not follow redirects - fixed in Curl 7.64 + */ + private static function createRedirectResolver(array $options, string $host): \Closure + { + $redirectHeaders = []; + if (0 < $options['max_redirects']) { + $redirectHeaders['host'] = $host; + $redirectHeaders['with_auth'] = $redirectHeaders['no_auth'] = array_filter($options['headers'], static function ($h) { + return 0 !== stripos($h, 'Host:'); + }); + + if (isset($options['normalized_headers']['authorization'][0]) || isset($options['normalized_headers']['cookie'][0])) { + $redirectHeaders['no_auth'] = array_filter($options['headers'], static function ($h) { + return 0 !== stripos($h, 'Authorization:') && 0 !== stripos($h, 'Cookie:'); + }); + } + } + + return static function ($ch, string $location, bool $noContent) use (&$redirectHeaders, $options) { + try { + $location = self::parseUrl($location); + } catch (InvalidArgumentException $e) { + return null; + } + + if ($noContent && $redirectHeaders) { + $filterContentHeaders = static function ($h) { + return 0 !== stripos($h, 'Content-Length:') && 0 !== stripos($h, 'Content-Type:') && 0 !== stripos($h, 'Transfer-Encoding:'); + }; + $redirectHeaders['no_auth'] = array_filter($redirectHeaders['no_auth'], $filterContentHeaders); + $redirectHeaders['with_auth'] = array_filter($redirectHeaders['with_auth'], $filterContentHeaders); + } + + if ($redirectHeaders && $host = parse_url('http:'.$location['authority'], \PHP_URL_HOST)) { + $requestHeaders = $redirectHeaders['host'] === $host ? $redirectHeaders['with_auth'] : $redirectHeaders['no_auth']; + curl_setopt($ch, \CURLOPT_HTTPHEADER, $requestHeaders); + } elseif ($noContent && $redirectHeaders) { + curl_setopt($ch, \CURLOPT_HTTPHEADER, $redirectHeaders['with_auth']); + } + + $url = self::parseUrl(curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL)); + $url = self::resolveUrl($location, $url); + + curl_setopt($ch, \CURLOPT_PROXY, self::getProxyUrl($options['proxy'], $url)); + + return implode('', $url); + }; + } + + private function findConstantName(int $opt): ?string + { + $constants = array_filter(get_defined_constants(), static function ($v, $k) use ($opt) { + return $v === $opt && 'C' === $k[0] && (str_starts_with($k, 'CURLOPT_') || str_starts_with($k, 'CURLINFO_')); + }, \ARRAY_FILTER_USE_BOTH); + + return key($constants); + } + + /** + * Prevents overriding options that are set internally throughout the request. + */ + private function validateExtraCurlOptions(array $options): void + { + $curloptsToConfig = [ + // options used in CurlHttpClient + \CURLOPT_HTTPAUTH => 'auth_ntlm', + \CURLOPT_USERPWD => 'auth_ntlm', + \CURLOPT_RESOLVE => 'resolve', + \CURLOPT_NOSIGNAL => 'timeout', + \CURLOPT_HTTPHEADER => 'headers', + \CURLOPT_INFILE => 'body', + \CURLOPT_READFUNCTION => 'body', + \CURLOPT_INFILESIZE => 'body', + \CURLOPT_POSTFIELDS => 'body', + \CURLOPT_UPLOAD => 'body', + \CURLOPT_INTERFACE => 'bindto', + \CURLOPT_TIMEOUT_MS => 'max_duration', + \CURLOPT_TIMEOUT => 'max_duration', + \CURLOPT_MAXREDIRS => 'max_redirects', + \CURLOPT_POSTREDIR => 'max_redirects', + \CURLOPT_PROXY => 'proxy', + \CURLOPT_NOPROXY => 'no_proxy', + \CURLOPT_SSL_VERIFYPEER => 'verify_peer', + \CURLOPT_SSL_VERIFYHOST => 'verify_host', + \CURLOPT_CAINFO => 'cafile', + \CURLOPT_CAPATH => 'capath', + \CURLOPT_SSL_CIPHER_LIST => 'ciphers', + \CURLOPT_SSLCERT => 'local_cert', + \CURLOPT_SSLKEY => 'local_pk', + \CURLOPT_KEYPASSWD => 'passphrase', + \CURLOPT_CERTINFO => 'capture_peer_cert_chain', + \CURLOPT_USERAGENT => 'normalized_headers', + \CURLOPT_REFERER => 'headers', + // options used in CurlResponse + \CURLOPT_NOPROGRESS => 'on_progress', + \CURLOPT_PROGRESSFUNCTION => 'on_progress', + ]; + + if (\defined('CURLOPT_UNIX_SOCKET_PATH')) { + $curloptsToConfig[\CURLOPT_UNIX_SOCKET_PATH] = 'bindto'; + } + + if (\defined('CURLOPT_PINNEDPUBLICKEY')) { + $curloptsToConfig[\CURLOPT_PINNEDPUBLICKEY] = 'peer_fingerprint'; + } + + $curloptsToCheck = [ + \CURLOPT_PRIVATE, + \CURLOPT_HEADERFUNCTION, + \CURLOPT_WRITEFUNCTION, + \CURLOPT_VERBOSE, + \CURLOPT_STDERR, + \CURLOPT_RETURNTRANSFER, + \CURLOPT_URL, + \CURLOPT_FOLLOWLOCATION, + \CURLOPT_HEADER, + \CURLOPT_CONNECTTIMEOUT, + \CURLOPT_CONNECTTIMEOUT_MS, + \CURLOPT_HTTP_VERSION, + \CURLOPT_PORT, + \CURLOPT_DNS_USE_GLOBAL_CACHE, + \CURLOPT_PROTOCOLS, + \CURLOPT_REDIR_PROTOCOLS, + \CURLOPT_COOKIEFILE, + \CURLINFO_REDIRECT_COUNT, + ]; + + if (\defined('CURLOPT_HTTP09_ALLOWED')) { + $curloptsToCheck[] = \CURLOPT_HTTP09_ALLOWED; + } + + if (\defined('CURLOPT_HEADEROPT')) { + $curloptsToCheck[] = \CURLOPT_HEADEROPT; + } + + $methodOpts = [ + \CURLOPT_POST, + \CURLOPT_PUT, + \CURLOPT_CUSTOMREQUEST, + \CURLOPT_HTTPGET, + \CURLOPT_NOBODY, + ]; + + foreach ($options as $opt => $optValue) { + if (isset($curloptsToConfig[$opt])) { + $constName = $this->findConstantName($opt) ?? $opt; + throw new InvalidArgumentException(sprintf('Cannot set "%s" with "extra.curl", use option "%s" instead.', $constName, $curloptsToConfig[$opt])); + } + + if (\in_array($opt, $methodOpts)) { + throw new InvalidArgumentException('The HTTP method cannot be overridden using "extra.curl".'); + } + + if (\in_array($opt, $curloptsToCheck)) { + $constName = $this->findConstantName($opt) ?? $opt; + throw new InvalidArgumentException(sprintf('Cannot set "%s" with "extra.curl".', $constName)); + } + } + } +} diff --git a/src/vendor/symfony/http-client/DataCollector/HttpClientDataCollector.php b/src/vendor/symfony/http-client/DataCollector/HttpClientDataCollector.php new file mode 100644 index 0000000..1925786 --- /dev/null +++ b/src/vendor/symfony/http-client/DataCollector/HttpClientDataCollector.php @@ -0,0 +1,176 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\DataCollector; + +use Symfony\Component\HttpClient\TraceableHttpClient; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\DataCollector\DataCollector; +use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface; +use Symfony\Component\VarDumper\Caster\ImgStub; + +/** + * @author Jérémy Romey + */ +final class HttpClientDataCollector extends DataCollector implements LateDataCollectorInterface +{ + /** + * @var TraceableHttpClient[] + */ + private $clients = []; + + public function registerClient(string $name, TraceableHttpClient $client) + { + $this->clients[$name] = $client; + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Throwable $exception = null) + { + $this->lateCollect(); + } + + public function lateCollect() + { + $this->data['request_count'] = $this->data['request_count'] ?? 0; + $this->data['error_count'] = $this->data['error_count'] ?? 0; + $this->data += ['clients' => []]; + + foreach ($this->clients as $name => $client) { + [$errorCount, $traces] = $this->collectOnClient($client); + + $this->data['clients'] += [ + $name => [ + 'traces' => [], + 'error_count' => 0, + ], + ]; + + $this->data['clients'][$name]['traces'] = array_merge($this->data['clients'][$name]['traces'], $traces); + $this->data['request_count'] += \count($traces); + $this->data['error_count'] += $errorCount; + $this->data['clients'][$name]['error_count'] += $errorCount; + + $client->reset(); + } + } + + public function getClients(): array + { + return $this->data['clients'] ?? []; + } + + public function getRequestCount(): int + { + return $this->data['request_count'] ?? 0; + } + + public function getErrorCount(): int + { + return $this->data['error_count'] ?? 0; + } + + /** + * {@inheritdoc} + */ + public function getName(): string + { + return 'http_client'; + } + + public function reset() + { + $this->data = [ + 'clients' => [], + 'request_count' => 0, + 'error_count' => 0, + ]; + } + + private function collectOnClient(TraceableHttpClient $client): array + { + $traces = $client->getTracedRequests(); + $errorCount = 0; + $baseInfo = [ + 'response_headers' => 1, + 'retry_count' => 1, + 'redirect_count' => 1, + 'redirect_url' => 1, + 'user_data' => 1, + 'error' => 1, + 'url' => 1, + ]; + + foreach ($traces as $i => $trace) { + if (400 <= ($trace['info']['http_code'] ?? 0)) { + ++$errorCount; + } + + $info = $trace['info']; + $traces[$i]['http_code'] = $info['http_code'] ?? 0; + + unset($info['filetime'], $info['http_code'], $info['ssl_verify_result'], $info['content_type']); + + if (($info['http_method'] ?? null) === $trace['method']) { + unset($info['http_method']); + } + + if (($info['url'] ?? null) === $trace['url']) { + unset($info['url']); + } + + foreach ($info as $k => $v) { + if (!$v || (is_numeric($v) && 0 > $v)) { + unset($info[$k]); + } + } + + if (\is_string($content = $trace['content'])) { + $contentType = 'application/octet-stream'; + + foreach ($info['response_headers'] ?? [] as $h) { + if (0 === stripos($h, 'content-type: ')) { + $contentType = substr($h, \strlen('content-type: ')); + break; + } + } + + if (0 === strpos($contentType, 'image/') && class_exists(ImgStub::class)) { + $content = new ImgStub($content, $contentType, ''); + } else { + $content = [$content]; + } + + $content = ['response_content' => $content]; + } elseif (\is_array($content)) { + $content = ['response_json' => $content]; + } else { + $content = []; + } + + if (isset($info['retry_count'])) { + $content['retries'] = $info['previous_info']; + unset($info['previous_info']); + } + + $debugInfo = array_diff_key($info, $baseInfo); + $info = ['info' => $debugInfo] + array_diff_key($info, $debugInfo) + $content; + unset($traces[$i]['info']); // break PHP reference used by TraceableHttpClient + $traces[$i]['info'] = $this->cloneVar($info); + $traces[$i]['options'] = $this->cloneVar($trace['options']); + } + + return [$errorCount, $traces]; + } +} diff --git a/src/vendor/symfony/http-client/DecoratorTrait.php b/src/vendor/symfony/http-client/DecoratorTrait.php new file mode 100644 index 0000000..790fc32 --- /dev/null +++ b/src/vendor/symfony/http-client/DecoratorTrait.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\HttpClient\ResponseStreamInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * Eases with writing decorators. + * + * @author Nicolas Grekas + */ +trait DecoratorTrait +{ + private $client; + + public function __construct(HttpClientInterface $client = null) + { + $this->client = $client ?? HttpClient::create(); + } + + /** + * {@inheritdoc} + */ + public function request(string $method, string $url, array $options = []): ResponseInterface + { + return $this->client->request($method, $url, $options); + } + + /** + * {@inheritdoc} + */ + public function stream($responses, float $timeout = null): ResponseStreamInterface + { + return $this->client->stream($responses, $timeout); + } + + /** + * {@inheritdoc} + */ + public function withOptions(array $options): self + { + $clone = clone $this; + $clone->client = $this->client->withOptions($options); + + return $clone; + } + + public function reset() + { + if ($this->client instanceof ResetInterface) { + $this->client->reset(); + } + } +} diff --git a/src/vendor/symfony/http-client/DependencyInjection/HttpClientPass.php b/src/vendor/symfony/http-client/DependencyInjection/HttpClientPass.php new file mode 100644 index 0000000..8f3c3c5 --- /dev/null +++ b/src/vendor/symfony/http-client/DependencyInjection/HttpClientPass.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\HttpClient\TraceableHttpClient; + +final class HttpClientPass implements CompilerPassInterface +{ + private $clientTag; + + public function __construct(string $clientTag = 'http_client.client') + { + if (0 < \func_num_args()) { + trigger_deprecation('symfony/http-client', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); + } + + $this->clientTag = $clientTag; + } + + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition('data_collector.http_client')) { + return; + } + + foreach ($container->findTaggedServiceIds($this->clientTag) as $id => $tags) { + $container->register('.debug.'.$id, TraceableHttpClient::class) + ->setArguments([new Reference('.debug.'.$id.'.inner'), new Reference('debug.stopwatch', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)]) + ->addTag('kernel.reset', ['method' => 'reset']) + ->setDecoratedService($id, null, 5); + $container->getDefinition('data_collector.http_client') + ->addMethodCall('registerClient', [$id, new Reference('.debug.'.$id)]); + } + } +} diff --git a/src/vendor/symfony/http-client/EventSourceHttpClient.php b/src/vendor/symfony/http-client/EventSourceHttpClient.php new file mode 100644 index 0000000..60e4e82 --- /dev/null +++ b/src/vendor/symfony/http-client/EventSourceHttpClient.php @@ -0,0 +1,159 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Symfony\Component\HttpClient\Chunk\ServerSentEvent; +use Symfony\Component\HttpClient\Exception\EventSourceException; +use Symfony\Component\HttpClient\Response\AsyncContext; +use Symfony\Component\HttpClient\Response\AsyncResponse; +use Symfony\Contracts\HttpClient\ChunkInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * @author Antoine Bluchet + * @author Nicolas Grekas + */ +final class EventSourceHttpClient implements HttpClientInterface, ResetInterface +{ + use AsyncDecoratorTrait, HttpClientTrait { + AsyncDecoratorTrait::withOptions insteadof HttpClientTrait; + } + + private $reconnectionTime; + + public function __construct(HttpClientInterface $client = null, float $reconnectionTime = 10.0) + { + $this->client = $client ?? HttpClient::create(); + $this->reconnectionTime = $reconnectionTime; + } + + public function connect(string $url, array $options = []): ResponseInterface + { + return $this->request('GET', $url, self::mergeDefaultOptions($options, [ + 'buffer' => false, + 'headers' => [ + 'Accept' => 'text/event-stream', + 'Cache-Control' => 'no-cache', + ], + ], true)); + } + + public function request(string $method, string $url, array $options = []): ResponseInterface + { + $state = new class() { + public $buffer = null; + public $lastEventId = null; + public $reconnectionTime; + public $lastError = null; + }; + $state->reconnectionTime = $this->reconnectionTime; + + if ($accept = self::normalizeHeaders($options['headers'] ?? [])['accept'] ?? []) { + $state->buffer = \in_array($accept, [['Accept: text/event-stream'], ['accept: text/event-stream']], true) ? '' : null; + + if (null !== $state->buffer) { + $options['extra']['trace_content'] = false; + } + } + + return new AsyncResponse($this->client, $method, $url, $options, static function (ChunkInterface $chunk, AsyncContext $context) use ($state, $method, $url, $options) { + if (null !== $state->buffer) { + $context->setInfo('reconnection_time', $state->reconnectionTime); + $isTimeout = false; + } + $lastError = $state->lastError; + $state->lastError = null; + + try { + $isTimeout = $chunk->isTimeout(); + + if (null !== $chunk->getInformationalStatus() || $context->getInfo('canceled')) { + yield $chunk; + + return; + } + } catch (TransportExceptionInterface $e) { + $state->lastError = $lastError ?? microtime(true); + + if (null === $state->buffer || ($isTimeout && microtime(true) - $state->lastError < $state->reconnectionTime)) { + yield $chunk; + } else { + $options['headers']['Last-Event-ID'] = $state->lastEventId; + $state->buffer = ''; + $state->lastError = microtime(true); + $context->getResponse()->cancel(); + $context->replaceRequest($method, $url, $options); + if ($isTimeout) { + yield $chunk; + } else { + $context->pause($state->reconnectionTime); + } + } + + return; + } + + if ($chunk->isFirst()) { + if (preg_match('/^text\/event-stream(;|$)/i', $context->getHeaders()['content-type'][0] ?? '')) { + $state->buffer = ''; + } elseif (null !== $lastError || (null !== $state->buffer && 200 === $context->getStatusCode())) { + throw new EventSourceException(sprintf('Response content-type is "%s" while "text/event-stream" was expected for "%s".', $context->getHeaders()['content-type'][0] ?? '', $context->getInfo('url'))); + } else { + $context->passthru(); + } + + if (null === $lastError) { + yield $chunk; + } + + return; + } + + $rx = '/((?:\r\n|[\r\n]){2,})/'; + $content = $state->buffer.$chunk->getContent(); + + if ($chunk->isLast()) { + $rx = substr_replace($rx, '|$', -2, 0); + } + $events = preg_split($rx, $content, -1, \PREG_SPLIT_DELIM_CAPTURE); + $state->buffer = array_pop($events); + + for ($i = 0; isset($events[$i]); $i += 2) { + $event = new ServerSentEvent($events[$i].$events[1 + $i]); + + if ('' !== $event->getId()) { + $context->setInfo('last_event_id', $state->lastEventId = $event->getId()); + } + + if ($event->getRetry()) { + $context->setInfo('reconnection_time', $state->reconnectionTime = $event->getRetry()); + } + + yield $event; + } + + if (preg_match('/^(?::[^\r\n]*+(?:\r\n|[\r\n]))+$/m', $state->buffer)) { + $content = $state->buffer; + $state->buffer = ''; + + yield $context->createChunk($content); + } + + if ($chunk->isLast()) { + yield $chunk; + } + }); + } +} diff --git a/src/vendor/symfony/http-client/Exception/ClientException.php b/src/vendor/symfony/http-client/Exception/ClientException.php new file mode 100644 index 0000000..4264534 --- /dev/null +++ b/src/vendor/symfony/http-client/Exception/ClientException.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Exception; + +use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; + +/** + * Represents a 4xx response. + * + * @author Nicolas Grekas + */ +final class ClientException extends \RuntimeException implements ClientExceptionInterface +{ + use HttpExceptionTrait; +} diff --git a/src/vendor/symfony/http-client/Exception/EventSourceException.php b/src/vendor/symfony/http-client/Exception/EventSourceException.php new file mode 100644 index 0000000..30ab795 --- /dev/null +++ b/src/vendor/symfony/http-client/Exception/EventSourceException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Exception; + +use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface; + +/** + * @author Nicolas Grekas + */ +final class EventSourceException extends \RuntimeException implements DecodingExceptionInterface +{ +} diff --git a/src/vendor/symfony/http-client/Exception/HttpExceptionTrait.php b/src/vendor/symfony/http-client/Exception/HttpExceptionTrait.php new file mode 100644 index 0000000..8cbaa1c --- /dev/null +++ b/src/vendor/symfony/http-client/Exception/HttpExceptionTrait.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Exception; + +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * @author Nicolas Grekas + * + * @internal + */ +trait HttpExceptionTrait +{ + private $response; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + $code = $response->getInfo('http_code'); + $url = $response->getInfo('url'); + $message = sprintf('HTTP %d returned for "%s".', $code, $url); + + $httpCodeFound = false; + $isJson = false; + foreach (array_reverse($response->getInfo('response_headers')) as $h) { + if (str_starts_with($h, 'HTTP/')) { + if ($httpCodeFound) { + break; + } + + $message = sprintf('%s returned for "%s".', $h, $url); + $httpCodeFound = true; + } + + if (0 === stripos($h, 'content-type:')) { + if (preg_match('/\bjson\b/i', $h)) { + $isJson = true; + } + + if ($httpCodeFound) { + break; + } + } + } + + // Try to guess a better error message using common API error formats + // The MIME type isn't explicitly checked because some formats inherit from others + // Ex: JSON:API follows RFC 7807 semantics, Hydra can be used in any JSON-LD-compatible format + if ($isJson && $body = json_decode($response->getContent(false), true)) { + if (isset($body['hydra:title']) || isset($body['hydra:description'])) { + // see http://www.hydra-cg.com/spec/latest/core/#description-of-http-status-codes-and-errors + $separator = isset($body['hydra:title'], $body['hydra:description']) ? "\n\n" : ''; + $message = ($body['hydra:title'] ?? '').$separator.($body['hydra:description'] ?? ''); + } elseif ((isset($body['title']) || isset($body['detail'])) + && (\is_scalar($body['title'] ?? '') && \is_scalar($body['detail'] ?? ''))) { + // see RFC 7807 and https://jsonapi.org/format/#error-objects + $separator = isset($body['title'], $body['detail']) ? "\n\n" : ''; + $message = ($body['title'] ?? '').$separator.($body['detail'] ?? ''); + } + } + + parent::__construct($message, $code); + } + + public function getResponse(): ResponseInterface + { + return $this->response; + } +} diff --git a/src/vendor/symfony/http-client/Exception/InvalidArgumentException.php b/src/vendor/symfony/http-client/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..6c2fae7 --- /dev/null +++ b/src/vendor/symfony/http-client/Exception/InvalidArgumentException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Exception; + +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; + +/** + * @author Nicolas Grekas + */ +final class InvalidArgumentException extends \InvalidArgumentException implements TransportExceptionInterface +{ +} diff --git a/src/vendor/symfony/http-client/Exception/JsonException.php b/src/vendor/symfony/http-client/Exception/JsonException.php new file mode 100644 index 0000000..54502e6 --- /dev/null +++ b/src/vendor/symfony/http-client/Exception/JsonException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Exception; + +use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface; + +/** + * Thrown by responses' toArray() method when their content cannot be JSON-decoded. + * + * @author Nicolas Grekas + */ +final class JsonException extends \JsonException implements DecodingExceptionInterface +{ +} diff --git a/src/vendor/symfony/http-client/Exception/RedirectionException.php b/src/vendor/symfony/http-client/Exception/RedirectionException.php new file mode 100644 index 0000000..5b93670 --- /dev/null +++ b/src/vendor/symfony/http-client/Exception/RedirectionException.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Exception; + +use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface; + +/** + * Represents a 3xx response. + * + * @author Nicolas Grekas + */ +final class RedirectionException extends \RuntimeException implements RedirectionExceptionInterface +{ + use HttpExceptionTrait; +} diff --git a/src/vendor/symfony/http-client/Exception/ServerException.php b/src/vendor/symfony/http-client/Exception/ServerException.php new file mode 100644 index 0000000..c6f8273 --- /dev/null +++ b/src/vendor/symfony/http-client/Exception/ServerException.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Exception; + +use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface; + +/** + * Represents a 5xx response. + * + * @author Nicolas Grekas + */ +final class ServerException extends \RuntimeException implements ServerExceptionInterface +{ + use HttpExceptionTrait; +} diff --git a/src/vendor/symfony/http-client/Exception/TimeoutException.php b/src/vendor/symfony/http-client/Exception/TimeoutException.php new file mode 100644 index 0000000..a9155cc --- /dev/null +++ b/src/vendor/symfony/http-client/Exception/TimeoutException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Exception; + +use Symfony\Contracts\HttpClient\Exception\TimeoutExceptionInterface; + +/** + * @author Nicolas Grekas + */ +final class TimeoutException extends TransportException implements TimeoutExceptionInterface +{ +} diff --git a/src/vendor/symfony/http-client/Exception/TransportException.php b/src/vendor/symfony/http-client/Exception/TransportException.php new file mode 100644 index 0000000..a3a80c6 --- /dev/null +++ b/src/vendor/symfony/http-client/Exception/TransportException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Exception; + +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; + +/** + * @author Nicolas Grekas + */ +class TransportException extends \RuntimeException implements TransportExceptionInterface +{ +} diff --git a/src/vendor/symfony/http-client/HttpClient.php b/src/vendor/symfony/http-client/HttpClient.php new file mode 100644 index 0000000..8de6f9f --- /dev/null +++ b/src/vendor/symfony/http-client/HttpClient.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Amp\Http\Client\Connection\ConnectionLimitingPool; +use Amp\Promise; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * A factory to instantiate the best possible HTTP client for the runtime. + * + * @author Nicolas Grekas + */ +final class HttpClient +{ + /** + * @param array $defaultOptions Default request's options + * @param int $maxHostConnections The maximum number of connections to a single host + * @param int $maxPendingPushes The maximum number of pushed responses to accept in the queue + * + * @see HttpClientInterface::OPTIONS_DEFAULTS for available options + */ + public static function create(array $defaultOptions = [], int $maxHostConnections = 6, int $maxPendingPushes = 50): HttpClientInterface + { + if ($amp = class_exists(ConnectionLimitingPool::class) && interface_exists(Promise::class)) { + if (!\extension_loaded('curl')) { + return new AmpHttpClient($defaultOptions, null, $maxHostConnections, $maxPendingPushes); + } + + // Skip curl when HTTP/2 push is unsupported or buggy, see https://bugs.php.net/77535 + if (\PHP_VERSION_ID < 70217 || (\PHP_VERSION_ID >= 70300 && \PHP_VERSION_ID < 70304) || !\defined('CURLMOPT_PUSHFUNCTION')) { + return new AmpHttpClient($defaultOptions, null, $maxHostConnections, $maxPendingPushes); + } + + static $curlVersion = null; + $curlVersion = $curlVersion ?? curl_version(); + + // HTTP/2 push crashes before curl 7.61 + if (0x073D00 > $curlVersion['version_number'] || !(\CURL_VERSION_HTTP2 & $curlVersion['features'])) { + return new AmpHttpClient($defaultOptions, null, $maxHostConnections, $maxPendingPushes); + } + } + + if (\extension_loaded('curl')) { + if ('\\' !== \DIRECTORY_SEPARATOR || isset($defaultOptions['cafile']) || isset($defaultOptions['capath']) || \ini_get('curl.cainfo') || \ini_get('openssl.cafile') || \ini_get('openssl.capath')) { + return new CurlHttpClient($defaultOptions, $maxHostConnections, $maxPendingPushes); + } + + @trigger_error('Configure the "curl.cainfo", "openssl.cafile" or "openssl.capath" php.ini setting to enable the CurlHttpClient', \E_USER_WARNING); + } + + if ($amp) { + return new AmpHttpClient($defaultOptions, null, $maxHostConnections, $maxPendingPushes); + } + + @trigger_error((\extension_loaded('curl') ? 'Upgrade' : 'Install').' the curl extension or run "composer require amphp/http-client:^4.2.1" to perform async HTTP operations, including full HTTP/2 support', \E_USER_NOTICE); + + return new NativeHttpClient($defaultOptions, $maxHostConnections); + } + + /** + * Creates a client that adds options (e.g. authentication headers) only when the request URL matches the provided base URI. + */ + public static function createForBaseUri(string $baseUri, array $defaultOptions = [], int $maxHostConnections = 6, int $maxPendingPushes = 50): HttpClientInterface + { + $client = self::create([], $maxHostConnections, $maxPendingPushes); + + return ScopingHttpClient::forBaseUri($client, $baseUri, $defaultOptions); + } +} diff --git a/src/vendor/symfony/http-client/HttpClientTrait.php b/src/vendor/symfony/http-client/HttpClientTrait.php new file mode 100644 index 0000000..3d60443 --- /dev/null +++ b/src/vendor/symfony/http-client/HttpClientTrait.php @@ -0,0 +1,710 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Symfony\Component\HttpClient\Exception\InvalidArgumentException; +use Symfony\Component\HttpClient\Exception\TransportException; + +/** + * Provides the common logic from writing HttpClientInterface implementations. + * + * All private methods are static to prevent implementers from creating memory leaks via circular references. + * + * @author Nicolas Grekas + */ +trait HttpClientTrait +{ + private static $CHUNK_SIZE = 16372; + + /** + * {@inheritdoc} + */ + public function withOptions(array $options): self + { + $clone = clone $this; + $clone->defaultOptions = self::mergeDefaultOptions($options, $this->defaultOptions); + + return $clone; + } + + /** + * Validates and normalizes method, URL and options, and merges them with defaults. + * + * @throws InvalidArgumentException When a not-supported option is found + */ + private static function prepareRequest(?string $method, ?string $url, array $options, array $defaultOptions = [], bool $allowExtraOptions = false): array + { + if (null !== $method) { + if (\strlen($method) !== strspn($method, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')) { + throw new InvalidArgumentException(sprintf('Invalid HTTP method "%s", only uppercase letters are accepted.', $method)); + } + if (!$method) { + throw new InvalidArgumentException('The HTTP method cannot be empty.'); + } + } + + $options = self::mergeDefaultOptions($options, $defaultOptions, $allowExtraOptions); + + $buffer = $options['buffer'] ?? true; + + if ($buffer instanceof \Closure) { + $options['buffer'] = static function (array $headers) use ($buffer) { + if (!\is_bool($buffer = $buffer($headers))) { + if (!\is_array($bufferInfo = @stream_get_meta_data($buffer))) { + throw new \LogicException(sprintf('The closure passed as option "buffer" must return bool or stream resource, got "%s".', get_debug_type($buffer))); + } + + if (false === strpbrk($bufferInfo['mode'], 'acew+')) { + throw new \LogicException(sprintf('The stream returned by the closure passed as option "buffer" must be writeable, got mode "%s".', $bufferInfo['mode'])); + } + } + + return $buffer; + }; + } elseif (!\is_bool($buffer)) { + if (!\is_array($bufferInfo = @stream_get_meta_data($buffer))) { + throw new InvalidArgumentException(sprintf('Option "buffer" must be bool, stream resource or Closure, "%s" given.', get_debug_type($buffer))); + } + + if (false === strpbrk($bufferInfo['mode'], 'acew+')) { + throw new InvalidArgumentException(sprintf('The stream in option "buffer" must be writeable, mode "%s" given.', $bufferInfo['mode'])); + } + } + + if (isset($options['json'])) { + if (isset($options['body']) && '' !== $options['body']) { + throw new InvalidArgumentException('Define either the "json" or the "body" option, setting both is not supported.'); + } + $options['body'] = self::jsonEncode($options['json']); + unset($options['json']); + + if (!isset($options['normalized_headers']['content-type'])) { + $options['normalized_headers']['content-type'] = ['Content-Type: application/json']; + } + } + + if (!isset($options['normalized_headers']['accept'])) { + $options['normalized_headers']['accept'] = ['Accept: */*']; + } + + if (isset($options['body'])) { + $options['body'] = self::normalizeBody($options['body']); + + if (\is_string($options['body']) + && (string) \strlen($options['body']) !== substr($h = $options['normalized_headers']['content-length'][0] ?? '', 16) + && ('' !== $h || '' !== $options['body']) + ) { + if ('chunked' === substr($options['normalized_headers']['transfer-encoding'][0] ?? '', \strlen('Transfer-Encoding: '))) { + unset($options['normalized_headers']['transfer-encoding']); + $options['body'] = self::dechunk($options['body']); + } + + $options['normalized_headers']['content-length'] = [substr_replace($h ?: 'Content-Length: ', \strlen($options['body']), 16)]; + } + } + + if (isset($options['peer_fingerprint'])) { + $options['peer_fingerprint'] = self::normalizePeerFingerprint($options['peer_fingerprint']); + } + + // Validate on_progress + if (isset($options['on_progress']) && !\is_callable($onProgress = $options['on_progress'])) { + throw new InvalidArgumentException(sprintf('Option "on_progress" must be callable, "%s" given.', get_debug_type($onProgress))); + } + + if (\is_array($options['auth_basic'] ?? null)) { + $count = \count($options['auth_basic']); + if ($count <= 0 || $count > 2) { + throw new InvalidArgumentException(sprintf('Option "auth_basic" must contain 1 or 2 elements, "%s" given.', $count)); + } + + $options['auth_basic'] = implode(':', $options['auth_basic']); + } + + if (!\is_string($options['auth_basic'] ?? '')) { + throw new InvalidArgumentException(sprintf('Option "auth_basic" must be string or an array, "%s" given.', get_debug_type($options['auth_basic']))); + } + + if (isset($options['auth_bearer'])) { + if (!\is_string($options['auth_bearer'])) { + throw new InvalidArgumentException(sprintf('Option "auth_bearer" must be a string, "%s" given.', get_debug_type($options['auth_bearer']))); + } + if (preg_match('{[^\x21-\x7E]}', $options['auth_bearer'])) { + throw new InvalidArgumentException('Invalid character found in option "auth_bearer": '.json_encode($options['auth_bearer']).'.'); + } + } + + if (isset($options['auth_basic'], $options['auth_bearer'])) { + throw new InvalidArgumentException('Define either the "auth_basic" or the "auth_bearer" option, setting both is not supported.'); + } + + if (null !== $url) { + // Merge auth with headers + if (($options['auth_basic'] ?? false) && !($options['normalized_headers']['authorization'] ?? false)) { + $options['normalized_headers']['authorization'] = ['Authorization: Basic '.base64_encode($options['auth_basic'])]; + } + // Merge bearer with headers + if (($options['auth_bearer'] ?? false) && !($options['normalized_headers']['authorization'] ?? false)) { + $options['normalized_headers']['authorization'] = ['Authorization: Bearer '.$options['auth_bearer']]; + } + + unset($options['auth_basic'], $options['auth_bearer']); + + // Parse base URI + if (\is_string($options['base_uri'])) { + $options['base_uri'] = self::parseUrl($options['base_uri']); + } + + // Validate and resolve URL + $url = self::parseUrl($url, $options['query']); + $url = self::resolveUrl($url, $options['base_uri'], $defaultOptions['query'] ?? []); + } + + // Finalize normalization of options + $options['http_version'] = (string) ($options['http_version'] ?? '') ?: null; + if (0 > $options['timeout'] = (float) ($options['timeout'] ?? \ini_get('default_socket_timeout'))) { + $options['timeout'] = 172800.0; // 2 days + } + + $options['max_duration'] = isset($options['max_duration']) ? (float) $options['max_duration'] : 0; + $options['headers'] = array_merge(...array_values($options['normalized_headers'])); + + return [$url, $options]; + } + + /** + * @throws InvalidArgumentException When an invalid option is found + */ + private static function mergeDefaultOptions(array $options, array $defaultOptions, bool $allowExtraOptions = false): array + { + $options['normalized_headers'] = self::normalizeHeaders($options['headers'] ?? []); + + if ($defaultOptions['headers'] ?? false) { + $options['normalized_headers'] += self::normalizeHeaders($defaultOptions['headers']); + } + + $options['headers'] = array_merge(...array_values($options['normalized_headers']) ?: [[]]); + + if ($resolve = $options['resolve'] ?? false) { + $options['resolve'] = []; + foreach ($resolve as $k => $v) { + $options['resolve'][substr(self::parseUrl('http://'.$k)['authority'], 2)] = (string) $v; + } + } + + // Option "query" is never inherited from defaults + $options['query'] = $options['query'] ?? []; + + $options += $defaultOptions; + + if (isset(self::$emptyDefaults)) { + foreach (self::$emptyDefaults as $k => $v) { + if (!isset($options[$k])) { + $options[$k] = $v; + } + } + } + + if (isset($defaultOptions['extra'])) { + $options['extra'] += $defaultOptions['extra']; + } + + if ($resolve = $defaultOptions['resolve'] ?? false) { + foreach ($resolve as $k => $v) { + $options['resolve'] += [substr(self::parseUrl('http://'.$k)['authority'], 2) => (string) $v]; + } + } + + if ($allowExtraOptions || !$defaultOptions) { + return $options; + } + + // Look for unsupported options + foreach ($options as $name => $v) { + if (\array_key_exists($name, $defaultOptions) || 'normalized_headers' === $name) { + continue; + } + + if ('auth_ntlm' === $name) { + if (!\extension_loaded('curl')) { + $msg = 'try installing the "curl" extension to use "%s" instead.'; + } else { + $msg = 'try using "%s" instead.'; + } + + throw new InvalidArgumentException(sprintf('Option "auth_ntlm" is not supported by "%s", '.$msg, __CLASS__, CurlHttpClient::class)); + } + + $alternatives = []; + + foreach ($defaultOptions as $k => $v) { + if (levenshtein($name, $k) <= \strlen($name) / 3 || str_contains($k, $name)) { + $alternatives[] = $k; + } + } + + throw new InvalidArgumentException(sprintf('Unsupported option "%s" passed to "%s", did you mean "%s"?', $name, __CLASS__, implode('", "', $alternatives ?: array_keys($defaultOptions)))); + } + + return $options; + } + + /** + * @return string[][] + * + * @throws InvalidArgumentException When an invalid header is found + */ + private static function normalizeHeaders(array $headers): array + { + $normalizedHeaders = []; + + foreach ($headers as $name => $values) { + if (\is_object($values) && method_exists($values, '__toString')) { + $values = (string) $values; + } + + if (\is_int($name)) { + if (!\is_string($values)) { + throw new InvalidArgumentException(sprintf('Invalid value for header "%s": expected string, "%s" given.', $name, get_debug_type($values))); + } + [$name, $values] = explode(':', $values, 2); + $values = [ltrim($values)]; + } elseif (!is_iterable($values)) { + if (\is_object($values)) { + throw new InvalidArgumentException(sprintf('Invalid value for header "%s": expected string, "%s" given.', $name, get_debug_type($values))); + } + + $values = (array) $values; + } + + $lcName = strtolower($name); + $normalizedHeaders[$lcName] = []; + + foreach ($values as $value) { + $normalizedHeaders[$lcName][] = $value = $name.': '.$value; + + if (\strlen($value) !== strcspn($value, "\r\n\0")) { + throw new InvalidArgumentException(sprintf('Invalid header: CR/LF/NUL found in "%s".', $value)); + } + } + } + + return $normalizedHeaders; + } + + /** + * @param array|string|resource|\Traversable|\Closure $body + * + * @return string|resource|\Closure + * + * @throws InvalidArgumentException When an invalid body is passed + */ + private static function normalizeBody($body) + { + if (\is_array($body)) { + array_walk_recursive($body, $caster = static function (&$v) use (&$caster) { + if (\is_object($v)) { + if ($vars = get_object_vars($v)) { + array_walk_recursive($vars, $caster); + $v = $vars; + } elseif (method_exists($v, '__toString')) { + $v = (string) $v; + } + } + }); + + return http_build_query($body, '', '&'); + } + + if (\is_string($body)) { + return $body; + } + + $generatorToCallable = static function (\Generator $body): \Closure { + return static function () use ($body) { + while ($body->valid()) { + $chunk = $body->current(); + $body->next(); + + if ('' !== $chunk) { + return $chunk; + } + } + + return ''; + }; + }; + + if ($body instanceof \Generator) { + return $generatorToCallable($body); + } + + if ($body instanceof \Traversable) { + return $generatorToCallable((static function ($body) { yield from $body; })($body)); + } + + if ($body instanceof \Closure) { + $r = new \ReflectionFunction($body); + $body = $r->getClosure(); + + if ($r->isGenerator()) { + $body = $body(self::$CHUNK_SIZE); + + return $generatorToCallable($body); + } + + return $body; + } + + if (!\is_array(@stream_get_meta_data($body))) { + throw new InvalidArgumentException(sprintf('Option "body" must be string, stream resource, iterable or callable, "%s" given.', get_debug_type($body))); + } + + return $body; + } + + private static function dechunk(string $body): string + { + $h = fopen('php://temp', 'w+'); + stream_filter_append($h, 'dechunk', \STREAM_FILTER_WRITE); + fwrite($h, $body); + $body = stream_get_contents($h, -1, 0); + rewind($h); + ftruncate($h, 0); + + if (fwrite($h, '-') && '' !== stream_get_contents($h, -1, 0)) { + throw new TransportException('Request body has broken chunked encoding.'); + } + + return $body; + } + + /** + * @param string|string[] $fingerprint + * + * @throws InvalidArgumentException When an invalid fingerprint is passed + */ + private static function normalizePeerFingerprint($fingerprint): array + { + if (\is_string($fingerprint)) { + switch (\strlen($fingerprint = str_replace(':', '', $fingerprint))) { + case 32: $fingerprint = ['md5' => $fingerprint]; break; + case 40: $fingerprint = ['sha1' => $fingerprint]; break; + case 44: $fingerprint = ['pin-sha256' => [$fingerprint]]; break; + case 64: $fingerprint = ['sha256' => $fingerprint]; break; + default: throw new InvalidArgumentException(sprintf('Cannot auto-detect fingerprint algorithm for "%s".', $fingerprint)); + } + } elseif (\is_array($fingerprint)) { + foreach ($fingerprint as $algo => $hash) { + $fingerprint[$algo] = 'pin-sha256' === $algo ? (array) $hash : str_replace(':', '', $hash); + } + } else { + throw new InvalidArgumentException(sprintf('Option "peer_fingerprint" must be string or array, "%s" given.', get_debug_type($fingerprint))); + } + + return $fingerprint; + } + + /** + * @param mixed $value + * + * @throws InvalidArgumentException When the value cannot be json-encoded + */ + private static function jsonEncode($value, int $flags = null, int $maxDepth = 512): string + { + $flags = $flags ?? (\JSON_HEX_TAG | \JSON_HEX_APOS | \JSON_HEX_AMP | \JSON_HEX_QUOT | \JSON_PRESERVE_ZERO_FRACTION); + + try { + $value = json_encode($value, $flags | (\PHP_VERSION_ID >= 70300 ? \JSON_THROW_ON_ERROR : 0), $maxDepth); + } catch (\JsonException $e) { + throw new InvalidArgumentException('Invalid value for "json" option: '.$e->getMessage()); + } + + if (\PHP_VERSION_ID < 70300 && \JSON_ERROR_NONE !== json_last_error() && (false === $value || !($flags & \JSON_PARTIAL_OUTPUT_ON_ERROR))) { + throw new InvalidArgumentException('Invalid value for "json" option: '.json_last_error_msg()); + } + + return $value; + } + + /** + * Resolves a URL against a base URI. + * + * @see https://tools.ietf.org/html/rfc3986#section-5.2.2 + * + * @throws InvalidArgumentException When an invalid URL is passed + */ + private static function resolveUrl(array $url, ?array $base, array $queryDefaults = []): array + { + if (null !== $base && '' === ($base['scheme'] ?? '').($base['authority'] ?? '')) { + throw new InvalidArgumentException(sprintf('Invalid "base_uri" option: host or scheme is missing in "%s".', implode('', $base))); + } + + if (null === $url['scheme'] && (null === $base || null === $base['scheme'])) { + throw new InvalidArgumentException(sprintf('Invalid URL: scheme is missing in "%s". Did you forget to add "http(s)://"?', implode('', $base ?? $url))); + } + + if (null === $base && '' === $url['scheme'].$url['authority']) { + throw new InvalidArgumentException(sprintf('Invalid URL: no "base_uri" option was provided and host or scheme is missing in "%s".', implode('', $url))); + } + + if (null !== $url['scheme']) { + $url['path'] = self::removeDotSegments($url['path'] ?? ''); + } else { + if (null !== $url['authority']) { + $url['path'] = self::removeDotSegments($url['path'] ?? ''); + } else { + if (null === $url['path']) { + $url['path'] = $base['path']; + $url['query'] = $url['query'] ?? $base['query']; + } else { + if ('/' !== $url['path'][0]) { + if (null === $base['path']) { + $url['path'] = '/'.$url['path']; + } else { + $segments = explode('/', $base['path']); + array_splice($segments, -1, 1, [$url['path']]); + $url['path'] = implode('/', $segments); + } + } + + $url['path'] = self::removeDotSegments($url['path']); + } + + $url['authority'] = $base['authority']; + + if ($queryDefaults) { + $url['query'] = '?'.self::mergeQueryString(substr($url['query'] ?? '', 1), $queryDefaults, false); + } + } + + $url['scheme'] = $base['scheme']; + } + + if ('' === ($url['path'] ?? '')) { + $url['path'] = '/'; + } + + if ('?' === ($url['query'] ?? '')) { + $url['query'] = null; + } + + return $url; + } + + /** + * Parses a URL and fixes its encoding if needed. + * + * @throws InvalidArgumentException When an invalid URL is passed + */ + private static function parseUrl(string $url, array $query = [], array $allowedSchemes = ['http' => 80, 'https' => 443]): array + { + if (false === $parts = parse_url($url)) { + throw new InvalidArgumentException(sprintf('Malformed URL "%s".', $url)); + } + + if ($query) { + $parts['query'] = self::mergeQueryString($parts['query'] ?? null, $query, true); + } + + $port = $parts['port'] ?? 0; + + if (null !== $scheme = $parts['scheme'] ?? null) { + if (!isset($allowedSchemes[$scheme = strtolower($scheme)])) { + throw new InvalidArgumentException(sprintf('Unsupported scheme in "%s".', $url)); + } + + $port = $allowedSchemes[$scheme] === $port ? 0 : $port; + $scheme .= ':'; + } + + if (null !== $host = $parts['host'] ?? null) { + if (!\defined('INTL_IDNA_VARIANT_UTS46') && preg_match('/[\x80-\xFF]/', $host)) { + throw new InvalidArgumentException(sprintf('Unsupported IDN "%s", try enabling the "intl" PHP extension or running "composer require symfony/polyfill-intl-idn".', $host)); + } + + $host = \defined('INTL_IDNA_VARIANT_UTS46') ? idn_to_ascii($host, \IDNA_DEFAULT | \IDNA_USE_STD3_RULES | \IDNA_CHECK_BIDI | \IDNA_CHECK_CONTEXTJ | \IDNA_NONTRANSITIONAL_TO_ASCII, \INTL_IDNA_VARIANT_UTS46) ?: strtolower($host) : strtolower($host); + $host .= $port ? ':'.$port : ''; + } + + foreach (['user', 'pass', 'path', 'query', 'fragment'] as $part) { + if (!isset($parts[$part])) { + continue; + } + + if (str_contains($parts[$part], '%')) { + // https://tools.ietf.org/html/rfc3986#section-2.3 + $parts[$part] = preg_replace_callback('/%(?:2[DE]|3[0-9]|[46][1-9A-F]|5F|[57][0-9A]|7E)++/i', function ($m) { return rawurldecode($m[0]); }, $parts[$part]); + } + + // https://tools.ietf.org/html/rfc3986#section-3.3 + $parts[$part] = preg_replace_callback("#[^-A-Za-z0-9._~!$&/'()[\]*+,;=:@{}%]++#", function ($m) { return rawurlencode($m[0]); }, $parts[$part]); + } + + return [ + 'scheme' => $scheme, + 'authority' => null !== $host ? '//'.(isset($parts['user']) ? $parts['user'].(isset($parts['pass']) ? ':'.$parts['pass'] : '').'@' : '').$host : null, + 'path' => isset($parts['path'][0]) ? $parts['path'] : null, + 'query' => isset($parts['query']) ? '?'.$parts['query'] : null, + 'fragment' => isset($parts['fragment']) ? '#'.$parts['fragment'] : null, + ]; + } + + /** + * Removes dot-segments from a path. + * + * @see https://tools.ietf.org/html/rfc3986#section-5.2.4 + */ + private static function removeDotSegments(string $path) + { + $result = ''; + + while (!\in_array($path, ['', '.', '..'], true)) { + if ('.' === $path[0] && (str_starts_with($path, $p = '../') || str_starts_with($path, $p = './'))) { + $path = substr($path, \strlen($p)); + } elseif ('/.' === $path || str_starts_with($path, '/./')) { + $path = substr_replace($path, '/', 0, 3); + } elseif ('/..' === $path || str_starts_with($path, '/../')) { + $i = strrpos($result, '/'); + $result = $i ? substr($result, 0, $i) : ''; + $path = substr_replace($path, '/', 0, 4); + } else { + $i = strpos($path, '/', 1) ?: \strlen($path); + $result .= substr($path, 0, $i); + $path = substr($path, $i); + } + } + + return $result; + } + + /** + * Merges and encodes a query array with a query string. + * + * @throws InvalidArgumentException When an invalid query-string value is passed + */ + private static function mergeQueryString(?string $queryString, array $queryArray, bool $replace): ?string + { + if (!$queryArray) { + return $queryString; + } + + $query = []; + + if (null !== $queryString) { + foreach (explode('&', $queryString) as $v) { + if ('' !== $v) { + $k = urldecode(explode('=', $v, 2)[0]); + $query[$k] = (isset($query[$k]) ? $query[$k].'&' : '').$v; + } + } + } + + if ($replace) { + foreach ($queryArray as $k => $v) { + if (null === $v) { + unset($query[$k]); + } + } + } + + $queryString = http_build_query($queryArray, '', '&', \PHP_QUERY_RFC3986); + $queryArray = []; + + if ($queryString) { + if (str_contains($queryString, '%')) { + // https://tools.ietf.org/html/rfc3986#section-2.3 + some chars not encoded by browsers + $queryString = strtr($queryString, [ + '%21' => '!', + '%24' => '$', + '%28' => '(', + '%29' => ')', + '%2A' => '*', + '%2F' => '/', + '%3A' => ':', + '%3B' => ';', + '%40' => '@', + '%5B' => '[', + '%5D' => ']', + ]); + } + + foreach (explode('&', $queryString) as $v) { + $queryArray[rawurldecode(explode('=', $v, 2)[0])] = $v; + } + } + + return implode('&', $replace ? array_replace($query, $queryArray) : ($query + $queryArray)); + } + + /** + * Loads proxy configuration from the same environment variables as curl when no proxy is explicitly set. + */ + private static function getProxy(?string $proxy, array $url, ?string $noProxy): ?array + { + if (null === $proxy = self::getProxyUrl($proxy, $url)) { + return null; + } + + $proxy = (parse_url($proxy) ?: []) + ['scheme' => 'http']; + + if (!isset($proxy['host'])) { + throw new TransportException('Invalid HTTP proxy: host is missing.'); + } + + if ('http' === $proxy['scheme']) { + $proxyUrl = 'tcp://'.$proxy['host'].':'.($proxy['port'] ?? '80'); + } elseif ('https' === $proxy['scheme']) { + $proxyUrl = 'ssl://'.$proxy['host'].':'.($proxy['port'] ?? '443'); + } else { + throw new TransportException(sprintf('Unsupported proxy scheme "%s": "http" or "https" expected.', $proxy['scheme'])); + } + + $noProxy = $noProxy ?? $_SERVER['no_proxy'] ?? $_SERVER['NO_PROXY'] ?? ''; + $noProxy = $noProxy ? preg_split('/[\s,]+/', $noProxy) : []; + + return [ + 'url' => $proxyUrl, + 'auth' => isset($proxy['user']) ? 'Basic '.base64_encode(rawurldecode($proxy['user']).':'.rawurldecode($proxy['pass'] ?? '')) : null, + 'no_proxy' => $noProxy, + ]; + } + + private static function getProxyUrl(?string $proxy, array $url): ?string + { + if (null !== $proxy) { + return $proxy; + } + + // Ignore HTTP_PROXY except on the CLI to work around httpoxy set of vulnerabilities + $proxy = $_SERVER['http_proxy'] ?? (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) ? $_SERVER['HTTP_PROXY'] ?? null : null) ?? $_SERVER['all_proxy'] ?? $_SERVER['ALL_PROXY'] ?? null; + + if ('https:' === $url['scheme']) { + $proxy = $_SERVER['https_proxy'] ?? $_SERVER['HTTPS_PROXY'] ?? $proxy; + } + + return $proxy; + } + + private static function shouldBuffer(array $headers): bool + { + if (null === $contentType = $headers['content-type'][0] ?? null) { + return false; + } + + if (false !== $i = strpos($contentType, ';')) { + $contentType = substr($contentType, 0, $i); + } + + return $contentType && preg_match('#^(?:text/|application/(?:.+\+)?(?:json|xml)$)#i', $contentType); + } +} diff --git a/src/vendor/symfony/http-client/HttpOptions.php b/src/vendor/symfony/http-client/HttpOptions.php new file mode 100644 index 0000000..da55f99 --- /dev/null +++ b/src/vendor/symfony/http-client/HttpOptions.php @@ -0,0 +1,331 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * A helper providing autocompletion for available options. + * + * @see HttpClientInterface for a description of each options. + * + * @author Nicolas Grekas + */ +class HttpOptions +{ + private $options = []; + + public function toArray(): array + { + return $this->options; + } + + /** + * @return $this + */ + public function setAuthBasic(string $user, string $password = '') + { + $this->options['auth_basic'] = $user; + + if ('' !== $password) { + $this->options['auth_basic'] .= ':'.$password; + } + + return $this; + } + + /** + * @return $this + */ + public function setAuthBearer(string $token) + { + $this->options['auth_bearer'] = $token; + + return $this; + } + + /** + * @return $this + */ + public function setQuery(array $query) + { + $this->options['query'] = $query; + + return $this; + } + + /** + * @return $this + */ + public function setHeaders(iterable $headers) + { + $this->options['headers'] = $headers; + + return $this; + } + + /** + * @param array|string|resource|\Traversable|\Closure $body + * + * @return $this + */ + public function setBody($body) + { + $this->options['body'] = $body; + + return $this; + } + + /** + * @param mixed $json + * + * @return $this + */ + public function setJson($json) + { + $this->options['json'] = $json; + + return $this; + } + + /** + * @return $this + */ + public function setUserData($data) + { + $this->options['user_data'] = $data; + + return $this; + } + + /** + * @return $this + */ + public function setMaxRedirects(int $max) + { + $this->options['max_redirects'] = $max; + + return $this; + } + + /** + * @return $this + */ + public function setHttpVersion(string $version) + { + $this->options['http_version'] = $version; + + return $this; + } + + /** + * @return $this + */ + public function setBaseUri(string $uri) + { + $this->options['base_uri'] = $uri; + + return $this; + } + + /** + * @return $this + */ + public function buffer(bool $buffer) + { + $this->options['buffer'] = $buffer; + + return $this; + } + + /** + * @return $this + */ + public function setOnProgress(callable $callback) + { + $this->options['on_progress'] = $callback; + + return $this; + } + + /** + * @return $this + */ + public function resolve(array $hostIps) + { + $this->options['resolve'] = $hostIps; + + return $this; + } + + /** + * @return $this + */ + public function setProxy(string $proxy) + { + $this->options['proxy'] = $proxy; + + return $this; + } + + /** + * @return $this + */ + public function setNoProxy(string $noProxy) + { + $this->options['no_proxy'] = $noProxy; + + return $this; + } + + /** + * @return $this + */ + public function setTimeout(float $timeout) + { + $this->options['timeout'] = $timeout; + + return $this; + } + + /** + * @return $this + */ + public function setMaxDuration(float $maxDuration) + { + $this->options['max_duration'] = $maxDuration; + + return $this; + } + + /** + * @return $this + */ + public function bindTo(string $bindto) + { + $this->options['bindto'] = $bindto; + + return $this; + } + + /** + * @return $this + */ + public function verifyPeer(bool $verify) + { + $this->options['verify_peer'] = $verify; + + return $this; + } + + /** + * @return $this + */ + public function verifyHost(bool $verify) + { + $this->options['verify_host'] = $verify; + + return $this; + } + + /** + * @return $this + */ + public function setCaFile(string $cafile) + { + $this->options['cafile'] = $cafile; + + return $this; + } + + /** + * @return $this + */ + public function setCaPath(string $capath) + { + $this->options['capath'] = $capath; + + return $this; + } + + /** + * @return $this + */ + public function setLocalCert(string $cert) + { + $this->options['local_cert'] = $cert; + + return $this; + } + + /** + * @return $this + */ + public function setLocalPk(string $pk) + { + $this->options['local_pk'] = $pk; + + return $this; + } + + /** + * @return $this + */ + public function setPassphrase(string $passphrase) + { + $this->options['passphrase'] = $passphrase; + + return $this; + } + + /** + * @return $this + */ + public function setCiphers(string $ciphers) + { + $this->options['ciphers'] = $ciphers; + + return $this; + } + + /** + * @param string|array $fingerprint + * + * @return $this + */ + public function setPeerFingerprint($fingerprint) + { + $this->options['peer_fingerprint'] = $fingerprint; + + return $this; + } + + /** + * @return $this + */ + public function capturePeerCertChain(bool $capture) + { + $this->options['capture_peer_cert_chain'] = $capture; + + return $this; + } + + /** + * @return $this + */ + public function setExtra(string $name, $value) + { + $this->options['extra'][$name] = $value; + + return $this; + } +} diff --git a/src/vendor/symfony/http-client/HttplugClient.php b/src/vendor/symfony/http-client/HttplugClient.php new file mode 100644 index 0000000..2d9eec3 --- /dev/null +++ b/src/vendor/symfony/http-client/HttplugClient.php @@ -0,0 +1,276 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use GuzzleHttp\Promise\Promise as GuzzlePromise; +use GuzzleHttp\Promise\RejectedPromise; +use GuzzleHttp\Promise\Utils; +use Http\Client\Exception\NetworkException; +use Http\Client\Exception\RequestException; +use Http\Client\HttpAsyncClient; +use Http\Client\HttpClient as HttplugInterface; +use Http\Discovery\Exception\NotFoundException; +use Http\Discovery\Psr17FactoryDiscovery; +use Http\Message\RequestFactory; +use Http\Message\StreamFactory; +use Http\Message\UriFactory; +use Http\Promise\Promise; +use Nyholm\Psr7\Factory\Psr17Factory; +use Nyholm\Psr7\Request; +use Nyholm\Psr7\Uri; +use Psr\Http\Message\RequestFactoryInterface; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseFactoryInterface; +use Psr\Http\Message\ResponseInterface as Psr7ResponseInterface; +use Psr\Http\Message\StreamFactoryInterface; +use Psr\Http\Message\StreamInterface; +use Psr\Http\Message\UriFactoryInterface; +use Psr\Http\Message\UriInterface; +use Symfony\Component\HttpClient\Internal\HttplugWaitLoop; +use Symfony\Component\HttpClient\Response\HttplugPromise; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\Service\ResetInterface; + +if (!interface_exists(HttplugInterface::class)) { + throw new \LogicException('You cannot use "Symfony\Component\HttpClient\HttplugClient" as the "php-http/httplug" package is not installed. Try running "composer require php-http/httplug".'); +} + +if (!interface_exists(RequestFactory::class)) { + throw new \LogicException('You cannot use "Symfony\Component\HttpClient\HttplugClient" as the "php-http/message-factory" package is not installed. Try running "composer require php-http/message-factory".'); +} + +/** + * An adapter to turn a Symfony HttpClientInterface into an Httplug client. + * + * Run "composer require nyholm/psr7" to install an efficient implementation of response + * and stream factories with flex-provided autowiring aliases. + * + * @author Nicolas Grekas + */ +final class HttplugClient implements HttplugInterface, HttpAsyncClient, RequestFactory, StreamFactory, UriFactory, ResetInterface +{ + private $client; + private $responseFactory; + private $streamFactory; + + /** + * @var \SplObjectStorage|null + */ + private $promisePool; + + private $waitLoop; + + public function __construct(HttpClientInterface $client = null, ResponseFactoryInterface $responseFactory = null, StreamFactoryInterface $streamFactory = null) + { + $this->client = $client ?? HttpClient::create(); + $this->responseFactory = $responseFactory; + $this->streamFactory = $streamFactory ?? ($responseFactory instanceof StreamFactoryInterface ? $responseFactory : null); + $this->promisePool = class_exists(Utils::class) ? new \SplObjectStorage() : null; + + if (null === $this->responseFactory || null === $this->streamFactory) { + if (!class_exists(Psr17Factory::class) && !class_exists(Psr17FactoryDiscovery::class)) { + throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\HttplugClient" as no PSR-17 factories have been provided. Try running "composer require nyholm/psr7".'); + } + + try { + $psr17Factory = class_exists(Psr17Factory::class, false) ? new Psr17Factory() : null; + $this->responseFactory = $this->responseFactory ?? $psr17Factory ?? Psr17FactoryDiscovery::findResponseFactory(); + $this->streamFactory = $this->streamFactory ?? $psr17Factory ?? Psr17FactoryDiscovery::findStreamFactory(); + } catch (NotFoundException $e) { + throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\HttplugClient" as no PSR-17 factories have been found. Try running "composer require nyholm/psr7".', 0, $e); + } + } + + $this->waitLoop = new HttplugWaitLoop($this->client, $this->promisePool, $this->responseFactory, $this->streamFactory); + } + + /** + * {@inheritdoc} + */ + public function sendRequest(RequestInterface $request): Psr7ResponseInterface + { + try { + return $this->waitLoop->createPsr7Response($this->sendPsr7Request($request)); + } catch (TransportExceptionInterface $e) { + throw new NetworkException($e->getMessage(), $request, $e); + } + } + + /** + * {@inheritdoc} + * + * @return HttplugPromise + */ + public function sendAsyncRequest(RequestInterface $request): Promise + { + if (!$promisePool = $this->promisePool) { + throw new \LogicException(sprintf('You cannot use "%s()" as the "guzzlehttp/promises" package is not installed. Try running "composer require guzzlehttp/promises".', __METHOD__)); + } + + try { + $response = $this->sendPsr7Request($request, true); + } catch (NetworkException $e) { + return new HttplugPromise(new RejectedPromise($e)); + } + + $waitLoop = $this->waitLoop; + + $promise = new GuzzlePromise(static function () use ($response, $waitLoop) { + $waitLoop->wait($response); + }, static function () use ($response, $promisePool) { + $response->cancel(); + unset($promisePool[$response]); + }); + + $promisePool[$response] = [$request, $promise]; + + return new HttplugPromise($promise); + } + + /** + * Resolves pending promises that complete before the timeouts are reached. + * + * When $maxDuration is null and $idleTimeout is reached, promises are rejected. + * + * @return int The number of remaining pending promises + */ + public function wait(float $maxDuration = null, float $idleTimeout = null): int + { + return $this->waitLoop->wait(null, $maxDuration, $idleTimeout); + } + + /** + * {@inheritdoc} + */ + public function createRequest($method, $uri, array $headers = [], $body = null, $protocolVersion = '1.1'): RequestInterface + { + if ($this->responseFactory instanceof RequestFactoryInterface) { + $request = $this->responseFactory->createRequest($method, $uri); + } elseif (class_exists(Request::class)) { + $request = new Request($method, $uri); + } elseif (class_exists(Psr17FactoryDiscovery::class)) { + $request = Psr17FactoryDiscovery::findRequestFactory()->createRequest($method, $uri); + } else { + throw new \LogicException(sprintf('You cannot use "%s()" as the "nyholm/psr7" package is not installed. Try running "composer require nyholm/psr7".', __METHOD__)); + } + + $request = $request + ->withProtocolVersion($protocolVersion) + ->withBody($this->createStream($body)) + ; + + foreach ($headers as $name => $value) { + $request = $request->withAddedHeader($name, $value); + } + + return $request; + } + + /** + * {@inheritdoc} + */ + public function createStream($body = null): StreamInterface + { + if ($body instanceof StreamInterface) { + return $body; + } + + if (\is_string($body ?? '')) { + $stream = $this->streamFactory->createStream($body ?? ''); + } elseif (\is_resource($body)) { + $stream = $this->streamFactory->createStreamFromResource($body); + } else { + throw new \InvalidArgumentException(sprintf('"%s()" expects string, resource or StreamInterface, "%s" given.', __METHOD__, get_debug_type($body))); + } + + if ($stream->isSeekable()) { + $stream->seek(0); + } + + return $stream; + } + + /** + * {@inheritdoc} + */ + public function createUri($uri): UriInterface + { + if ($uri instanceof UriInterface) { + return $uri; + } + + if ($this->responseFactory instanceof UriFactoryInterface) { + return $this->responseFactory->createUri($uri); + } + + if (class_exists(Uri::class)) { + return new Uri($uri); + } + + if (class_exists(Psr17FactoryDiscovery::class)) { + return Psr17FactoryDiscovery::findUrlFactory()->createUri($uri); + } + + throw new \LogicException(sprintf('You cannot use "%s()" as the "nyholm/psr7" package is not installed. Try running "composer require nyholm/psr7".', __METHOD__)); + } + + public function __sleep(): array + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + $this->wait(); + } + + public function reset() + { + if ($this->client instanceof ResetInterface) { + $this->client->reset(); + } + } + + private function sendPsr7Request(RequestInterface $request, bool $buffer = null): ResponseInterface + { + try { + $body = $request->getBody(); + + if ($body->isSeekable()) { + $body->seek(0); + } + + $options = [ + 'headers' => $request->getHeaders(), + 'body' => $body->getContents(), + 'buffer' => $buffer, + ]; + + if ('1.0' === $request->getProtocolVersion()) { + $options['http_version'] = '1.0'; + } + + return $this->client->request($request->getMethod(), (string) $request->getUri(), $options); + } catch (\InvalidArgumentException $e) { + throw new RequestException($e->getMessage(), $request, $e); + } catch (TransportExceptionInterface $e) { + throw new NetworkException($e->getMessage(), $request, $e); + } + } +} diff --git a/src/vendor/symfony/http-client/Internal/AmpBody.php b/src/vendor/symfony/http-client/Internal/AmpBody.php new file mode 100644 index 0000000..b99742b --- /dev/null +++ b/src/vendor/symfony/http-client/Internal/AmpBody.php @@ -0,0 +1,142 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Internal; + +use Amp\ByteStream\InputStream; +use Amp\ByteStream\ResourceInputStream; +use Amp\Http\Client\RequestBody; +use Amp\Promise; +use Amp\Success; +use Symfony\Component\HttpClient\Exception\TransportException; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class AmpBody implements RequestBody, InputStream +{ + private $body; + private $info; + private $onProgress; + private $offset = 0; + private $length = -1; + private $uploaded; + + public function __construct($body, &$info, \Closure $onProgress) + { + $this->body = $body; + $this->info = &$info; + $this->onProgress = $onProgress; + + if (\is_resource($body)) { + $this->offset = ftell($body); + $this->length = fstat($body)['size']; + $this->body = new ResourceInputStream($body); + } elseif (\is_string($body)) { + $this->length = \strlen($body); + } + } + + public function createBodyStream(): InputStream + { + if (null !== $this->uploaded) { + $this->uploaded = null; + + if (\is_string($this->body)) { + $this->offset = 0; + } elseif ($this->body instanceof ResourceInputStream) { + fseek($this->body->getResource(), $this->offset); + } + } + + return $this; + } + + public function getHeaders(): Promise + { + return new Success([]); + } + + public function getBodyLength(): Promise + { + return new Success($this->length - $this->offset); + } + + public function read(): Promise + { + $this->info['size_upload'] += $this->uploaded; + $this->uploaded = 0; + ($this->onProgress)(); + + $chunk = $this->doRead(); + $chunk->onResolve(function ($e, $data) { + if (null !== $data) { + $this->uploaded = \strlen($data); + } else { + $this->info['upload_content_length'] = $this->info['size_upload']; + } + }); + + return $chunk; + } + + public static function rewind(RequestBody $body): RequestBody + { + if (!$body instanceof self) { + return $body; + } + + $body->uploaded = null; + + if ($body->body instanceof ResourceInputStream) { + fseek($body->body->getResource(), $body->offset); + + return new $body($body->body, $body->info, $body->onProgress); + } + + if (\is_string($body->body)) { + $body->offset = 0; + } + + return $body; + } + + private function doRead(): Promise + { + if ($this->body instanceof ResourceInputStream) { + return $this->body->read(); + } + + if (null === $this->offset || !$this->length) { + return new Success(); + } + + if (\is_string($this->body)) { + $this->offset = null; + + return new Success($this->body); + } + + if ('' === $data = ($this->body)(16372)) { + $this->offset = null; + + return new Success(); + } + + if (!\is_string($data)) { + throw new TransportException(sprintf('Return value of the "body" option callback must be string, "%s" returned.', get_debug_type($data))); + } + + return new Success($data); + } +} diff --git a/src/vendor/symfony/http-client/Internal/AmpClientState.php b/src/vendor/symfony/http-client/Internal/AmpClientState.php new file mode 100644 index 0000000..3061f08 --- /dev/null +++ b/src/vendor/symfony/http-client/Internal/AmpClientState.php @@ -0,0 +1,217 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Internal; + +use Amp\CancellationToken; +use Amp\Deferred; +use Amp\Http\Client\Connection\ConnectionLimitingPool; +use Amp\Http\Client\Connection\DefaultConnectionFactory; +use Amp\Http\Client\InterceptedHttpClient; +use Amp\Http\Client\Interceptor\RetryRequests; +use Amp\Http\Client\PooledHttpClient; +use Amp\Http\Client\Request; +use Amp\Http\Client\Response; +use Amp\Http\Tunnel\Http1TunnelConnector; +use Amp\Http\Tunnel\Https1TunnelConnector; +use Amp\Promise; +use Amp\Socket\Certificate; +use Amp\Socket\ClientTlsContext; +use Amp\Socket\ConnectContext; +use Amp\Socket\Connector; +use Amp\Socket\DnsConnector; +use Amp\Socket\SocketAddress; +use Amp\Success; +use Psr\Log\LoggerInterface; + +/** + * Internal representation of the Amp client's state. + * + * @author Nicolas Grekas + * + * @internal + */ +final class AmpClientState extends ClientState +{ + public $dnsCache = []; + public $responseCount = 0; + public $pushedResponses = []; + + private $clients = []; + private $clientConfigurator; + private $maxHostConnections; + private $maxPendingPushes; + private $logger; + + public function __construct(?callable $clientConfigurator, int $maxHostConnections, int $maxPendingPushes, ?LoggerInterface &$logger) + { + $this->clientConfigurator = $clientConfigurator ?? static function (PooledHttpClient $client) { + return new InterceptedHttpClient($client, new RetryRequests(2)); + }; + $this->maxHostConnections = $maxHostConnections; + $this->maxPendingPushes = $maxPendingPushes; + $this->logger = &$logger; + } + + /** + * @return Promise + */ + public function request(array $options, Request $request, CancellationToken $cancellation, array &$info, \Closure $onProgress, &$handle): Promise + { + if ($options['proxy']) { + if ($request->hasHeader('proxy-authorization')) { + $options['proxy']['auth'] = $request->getHeader('proxy-authorization'); + } + + // Matching "no_proxy" should follow the behavior of curl + $host = $request->getUri()->getHost(); + foreach ($options['proxy']['no_proxy'] as $rule) { + $dotRule = '.'.ltrim($rule, '.'); + + if ('*' === $rule || $host === $rule || substr($host, -\strlen($dotRule)) === $dotRule) { + $options['proxy'] = null; + break; + } + } + } + + $request = clone $request; + + if ($request->hasHeader('proxy-authorization')) { + $request->removeHeader('proxy-authorization'); + } + + if ($options['capture_peer_cert_chain']) { + $info['peer_certificate_chain'] = []; + } + + $request->addEventListener(new AmpListener($info, $options['peer_fingerprint']['pin-sha256'] ?? [], $onProgress, $handle)); + $request->setPushHandler(function ($request, $response) use ($options): Promise { + return $this->handlePush($request, $response, $options); + }); + + ($request->hasHeader('content-length') ? new Success((int) $request->getHeader('content-length')) : $request->getBody()->getBodyLength()) + ->onResolve(static function ($e, $bodySize) use (&$info) { + if (null !== $bodySize && 0 <= $bodySize) { + $info['upload_content_length'] = ((1 + $info['upload_content_length']) ?? 1) - 1 + $bodySize; + } + }); + + [$client, $connector] = $this->getClient($options); + $response = $client->request($request, $cancellation); + $response->onResolve(static function ($e) use ($connector, &$handle) { + if (null === $e) { + $handle = $connector->handle; + } + }); + + return $response; + } + + private function getClient(array $options): array + { + $options = [ + 'bindto' => $options['bindto'] ?: '0', + 'verify_peer' => $options['verify_peer'], + 'capath' => $options['capath'], + 'cafile' => $options['cafile'], + 'local_cert' => $options['local_cert'], + 'local_pk' => $options['local_pk'], + 'ciphers' => $options['ciphers'], + 'capture_peer_cert_chain' => $options['capture_peer_cert_chain'] || $options['peer_fingerprint'], + 'proxy' => $options['proxy'], + ]; + + $key = md5(serialize($options)); + + if (isset($this->clients[$key])) { + return $this->clients[$key]; + } + + $context = new ClientTlsContext(''); + $options['verify_peer'] || $context = $context->withoutPeerVerification(); + $options['cafile'] && $context = $context->withCaFile($options['cafile']); + $options['capath'] && $context = $context->withCaPath($options['capath']); + $options['local_cert'] && $context = $context->withCertificate(new Certificate($options['local_cert'], $options['local_pk'])); + $options['ciphers'] && $context = $context->withCiphers($options['ciphers']); + $options['capture_peer_cert_chain'] && $context = $context->withPeerCapturing(); + + $connector = $handleConnector = new class() implements Connector { + public $connector; + public $uri; + public $handle; + + public function connect(string $uri, ConnectContext $context = null, CancellationToken $token = null): Promise + { + $result = $this->connector->connect($this->uri ?? $uri, $context, $token); + $result->onResolve(function ($e, $socket) { + $this->handle = null !== $socket ? $socket->getResource() : false; + }); + + return $result; + } + }; + $connector->connector = new DnsConnector(new AmpResolver($this->dnsCache)); + + $context = (new ConnectContext()) + ->withTcpNoDelay() + ->withTlsContext($context); + + if ($options['bindto']) { + if (file_exists($options['bindto'])) { + $connector->uri = 'unix://'.$options['bindto']; + } else { + $context = $context->withBindTo($options['bindto']); + } + } + + if ($options['proxy']) { + $proxyUrl = parse_url($options['proxy']['url']); + $proxySocket = new SocketAddress($proxyUrl['host'], $proxyUrl['port']); + $proxyHeaders = $options['proxy']['auth'] ? ['Proxy-Authorization' => $options['proxy']['auth']] : []; + + if ('ssl' === $proxyUrl['scheme']) { + $connector = new Https1TunnelConnector($proxySocket, $context->getTlsContext(), $proxyHeaders, $connector); + } else { + $connector = new Http1TunnelConnector($proxySocket, $proxyHeaders, $connector); + } + } + + $maxHostConnections = 0 < $this->maxHostConnections ? $this->maxHostConnections : \PHP_INT_MAX; + $pool = new DefaultConnectionFactory($connector, $context); + $pool = ConnectionLimitingPool::byAuthority($maxHostConnections, $pool); + + return $this->clients[$key] = [($this->clientConfigurator)(new PooledHttpClient($pool)), $handleConnector]; + } + + private function handlePush(Request $request, Promise $response, array $options): Promise + { + $deferred = new Deferred(); + $authority = $request->getUri()->getAuthority(); + + if ($this->maxPendingPushes <= \count($this->pushedResponses[$authority] ?? [])) { + $fifoUrl = key($this->pushedResponses[$authority]); + unset($this->pushedResponses[$authority][$fifoUrl]); + $this->logger && $this->logger->debug(sprintf('Evicting oldest pushed response: "%s"', $fifoUrl)); + } + + $url = (string) $request->getUri(); + $this->logger && $this->logger->debug(sprintf('Queueing pushed response: "%s"', $url)); + $this->pushedResponses[$authority][] = [$url, $deferred, $request, $response, [ + 'proxy' => $options['proxy'], + 'bindto' => $options['bindto'], + 'local_cert' => $options['local_cert'], + 'local_pk' => $options['local_pk'], + ]]; + + return $deferred->promise(); + } +} diff --git a/src/vendor/symfony/http-client/Internal/AmpListener.php b/src/vendor/symfony/http-client/Internal/AmpListener.php new file mode 100644 index 0000000..cb3235b --- /dev/null +++ b/src/vendor/symfony/http-client/Internal/AmpListener.php @@ -0,0 +1,183 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Internal; + +use Amp\Http\Client\Connection\Stream; +use Amp\Http\Client\EventListener; +use Amp\Http\Client\Request; +use Amp\Promise; +use Amp\Success; +use Symfony\Component\HttpClient\Exception\TransportException; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class AmpListener implements EventListener +{ + private $info; + private $pinSha256; + private $onProgress; + private $handle; + + public function __construct(array &$info, array $pinSha256, \Closure $onProgress, &$handle) + { + $info += [ + 'connect_time' => 0.0, + 'pretransfer_time' => 0.0, + 'starttransfer_time' => 0.0, + 'total_time' => 0.0, + 'namelookup_time' => 0.0, + 'primary_ip' => '', + 'primary_port' => 0, + ]; + + $this->info = &$info; + $this->pinSha256 = $pinSha256; + $this->onProgress = $onProgress; + $this->handle = &$handle; + } + + public function startRequest(Request $request): Promise + { + $this->info['start_time'] = $this->info['start_time'] ?? microtime(true); + ($this->onProgress)(); + + return new Success(); + } + + public function startDnsResolution(Request $request): Promise + { + ($this->onProgress)(); + + return new Success(); + } + + public function startConnectionCreation(Request $request): Promise + { + ($this->onProgress)(); + + return new Success(); + } + + public function startTlsNegotiation(Request $request): Promise + { + ($this->onProgress)(); + + return new Success(); + } + + public function startSendingRequest(Request $request, Stream $stream): Promise + { + $host = $stream->getRemoteAddress()->getHost(); + + if (false !== strpos($host, ':')) { + $host = '['.$host.']'; + } + + $this->info['primary_ip'] = $host; + $this->info['primary_port'] = $stream->getRemoteAddress()->getPort(); + $this->info['pretransfer_time'] = microtime(true) - $this->info['start_time']; + $this->info['debug'] .= sprintf("* Connected to %s (%s) port %d\n", $request->getUri()->getHost(), $host, $this->info['primary_port']); + + if ((isset($this->info['peer_certificate_chain']) || $this->pinSha256) && null !== $tlsInfo = $stream->getTlsInfo()) { + foreach ($tlsInfo->getPeerCertificates() as $cert) { + $this->info['peer_certificate_chain'][] = openssl_x509_read($cert->toPem()); + } + + if ($this->pinSha256) { + $pin = openssl_pkey_get_public($this->info['peer_certificate_chain'][0]); + $pin = openssl_pkey_get_details($pin)['key']; + $pin = \array_slice(explode("\n", $pin), 1, -2); + $pin = base64_decode(implode('', $pin)); + $pin = base64_encode(hash('sha256', $pin, true)); + + if (!\in_array($pin, $this->pinSha256, true)) { + throw new TransportException(sprintf('SSL public key does not match pinned public key for "%s".', $this->info['url'])); + } + } + } + ($this->onProgress)(); + + $uri = $request->getUri(); + $requestUri = $uri->getPath() ?: '/'; + + if ('' !== $query = $uri->getQuery()) { + $requestUri .= '?'.$query; + } + + if ('CONNECT' === $method = $request->getMethod()) { + $requestUri = $uri->getHost().': '.($uri->getPort() ?? ('https' === $uri->getScheme() ? 443 : 80)); + } + + $this->info['debug'] .= sprintf("> %s %s HTTP/%s \r\n", $method, $requestUri, $request->getProtocolVersions()[0]); + + foreach ($request->getRawHeaders() as [$name, $value]) { + $this->info['debug'] .= $name.': '.$value."\r\n"; + } + $this->info['debug'] .= "\r\n"; + + return new Success(); + } + + public function completeSendingRequest(Request $request, Stream $stream): Promise + { + ($this->onProgress)(); + + return new Success(); + } + + public function startReceivingResponse(Request $request, Stream $stream): Promise + { + $this->info['starttransfer_time'] = microtime(true) - $this->info['start_time']; + ($this->onProgress)(); + + return new Success(); + } + + public function completeReceivingResponse(Request $request, Stream $stream): Promise + { + $this->handle = null; + ($this->onProgress)(); + + return new Success(); + } + + public function completeDnsResolution(Request $request): Promise + { + $this->info['namelookup_time'] = microtime(true) - $this->info['start_time']; + ($this->onProgress)(); + + return new Success(); + } + + public function completeConnectionCreation(Request $request): Promise + { + $this->info['connect_time'] = microtime(true) - $this->info['start_time']; + ($this->onProgress)(); + + return new Success(); + } + + public function completeTlsNegotiation(Request $request): Promise + { + ($this->onProgress)(); + + return new Success(); + } + + public function abort(Request $request, \Throwable $cause): Promise + { + return new Success(); + } +} diff --git a/src/vendor/symfony/http-client/Internal/AmpResolver.php b/src/vendor/symfony/http-client/Internal/AmpResolver.php new file mode 100644 index 0000000..d31476a --- /dev/null +++ b/src/vendor/symfony/http-client/Internal/AmpResolver.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Internal; + +use Amp\Dns; +use Amp\Dns\Record; +use Amp\Promise; +use Amp\Success; + +/** + * Handles local overrides for the DNS resolver. + * + * @author Nicolas Grekas + * + * @internal + */ +class AmpResolver implements Dns\Resolver +{ + private $dnsMap; + + public function __construct(array &$dnsMap) + { + $this->dnsMap = &$dnsMap; + } + + public function resolve(string $name, int $typeRestriction = null): Promise + { + if (!isset($this->dnsMap[$name]) || !\in_array($typeRestriction, [Record::A, null], true)) { + return Dns\resolver()->resolve($name, $typeRestriction); + } + + return new Success([new Record($this->dnsMap[$name], Record::A, null)]); + } + + public function query(string $name, int $type): Promise + { + if (!isset($this->dnsMap[$name]) || Record::A !== $type) { + return Dns\resolver()->query($name, $type); + } + + return new Success([new Record($this->dnsMap[$name], Record::A, null)]); + } +} diff --git a/src/vendor/symfony/http-client/Internal/Canary.php b/src/vendor/symfony/http-client/Internal/Canary.php new file mode 100644 index 0000000..3d14b5f --- /dev/null +++ b/src/vendor/symfony/http-client/Internal/Canary.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Internal; + +/** + * @author Nicolas Grekas + * + * @internal + */ +final class Canary +{ + private $canceller; + + public function __construct(\Closure $canceller) + { + $this->canceller = $canceller; + } + + public function cancel() + { + if (($canceller = $this->canceller) instanceof \Closure) { + $this->canceller = null; + $canceller(); + } + } + + public function __destruct() + { + $this->cancel(); + } +} diff --git a/src/vendor/symfony/http-client/Internal/ClientState.php b/src/vendor/symfony/http-client/Internal/ClientState.php new file mode 100644 index 0000000..52fe3c8 --- /dev/null +++ b/src/vendor/symfony/http-client/Internal/ClientState.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Internal; + +/** + * Internal representation of the client state. + * + * @author Alexander M. Turek + * + * @internal + */ +class ClientState +{ + public $handlesActivity = []; + public $openHandles = []; + public $lastTimeout; +} diff --git a/src/vendor/symfony/http-client/Internal/CurlClientState.php b/src/vendor/symfony/http-client/Internal/CurlClientState.php new file mode 100644 index 0000000..80473fe --- /dev/null +++ b/src/vendor/symfony/http-client/Internal/CurlClientState.php @@ -0,0 +1,149 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Internal; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpClient\Response\CurlResponse; + +/** + * Internal representation of the cURL client's state. + * + * @author Alexander M. Turek + * + * @internal + */ +final class CurlClientState extends ClientState +{ + /** @var \CurlMultiHandle|resource|null */ + public $handle; + /** @var \CurlShareHandle|resource|null */ + public $share; + /** @var PushedResponse[] */ + public $pushedResponses = []; + /** @var DnsCache */ + public $dnsCache; + /** @var float[] */ + public $pauseExpiries = []; + public $execCounter = \PHP_INT_MIN; + /** @var LoggerInterface|null */ + public $logger; + public $performing = false; + + public static $curlVersion; + + public function __construct(int $maxHostConnections, int $maxPendingPushes) + { + self::$curlVersion = self::$curlVersion ?? curl_version(); + + $this->handle = curl_multi_init(); + $this->dnsCache = new DnsCache(); + $this->reset(); + + // Don't enable HTTP/1.1 pipelining: it forces responses to be sent in order + if (\defined('CURLPIPE_MULTIPLEX')) { + curl_multi_setopt($this->handle, \CURLMOPT_PIPELINING, \CURLPIPE_MULTIPLEX); + } + if (\defined('CURLMOPT_MAX_HOST_CONNECTIONS')) { + $maxHostConnections = curl_multi_setopt($this->handle, \CURLMOPT_MAX_HOST_CONNECTIONS, 0 < $maxHostConnections ? $maxHostConnections : \PHP_INT_MAX) ? 0 : $maxHostConnections; + } + if (\defined('CURLMOPT_MAXCONNECTS') && 0 < $maxHostConnections) { + curl_multi_setopt($this->handle, \CURLMOPT_MAXCONNECTS, $maxHostConnections); + } + + // Skip configuring HTTP/2 push when it's unsupported or buggy, see https://bugs.php.net/77535 + if (0 >= $maxPendingPushes || \PHP_VERSION_ID < 70217 || (\PHP_VERSION_ID >= 70300 && \PHP_VERSION_ID < 70304)) { + return; + } + + // HTTP/2 push crashes before curl 7.61 + if (!\defined('CURLMOPT_PUSHFUNCTION') || 0x073D00 > self::$curlVersion['version_number'] || !(\CURL_VERSION_HTTP2 & self::$curlVersion['features'])) { + return; + } + + // Clone to prevent a circular reference + $multi = clone $this; + $multi->handle = null; + $multi->share = null; + $multi->pushedResponses = &$this->pushedResponses; + $multi->logger = &$this->logger; + $multi->handlesActivity = &$this->handlesActivity; + $multi->openHandles = &$this->openHandles; + + curl_multi_setopt($this->handle, \CURLMOPT_PUSHFUNCTION, static function ($parent, $pushed, array $requestHeaders) use ($multi, $maxPendingPushes) { + return $multi->handlePush($parent, $pushed, $requestHeaders, $maxPendingPushes); + }); + } + + public function reset() + { + foreach ($this->pushedResponses as $url => $response) { + $this->logger && $this->logger->debug(sprintf('Unused pushed response: "%s"', $url)); + curl_multi_remove_handle($this->handle, $response->handle); + curl_close($response->handle); + } + + $this->pushedResponses = []; + $this->dnsCache->evictions = $this->dnsCache->evictions ?: $this->dnsCache->removals; + $this->dnsCache->removals = $this->dnsCache->hostnames = []; + + $this->share = curl_share_init(); + + curl_share_setopt($this->share, \CURLSHOPT_SHARE, \CURL_LOCK_DATA_DNS); + curl_share_setopt($this->share, \CURLSHOPT_SHARE, \CURL_LOCK_DATA_SSL_SESSION); + + if (\defined('CURL_LOCK_DATA_CONNECT') && \PHP_VERSION_ID >= 80000) { + curl_share_setopt($this->share, \CURLSHOPT_SHARE, \CURL_LOCK_DATA_CONNECT); + } + } + + private function handlePush($parent, $pushed, array $requestHeaders, int $maxPendingPushes): int + { + $headers = []; + $origin = curl_getinfo($parent, \CURLINFO_EFFECTIVE_URL); + + foreach ($requestHeaders as $h) { + if (false !== $i = strpos($h, ':', 1)) { + $headers[substr($h, 0, $i)][] = substr($h, 1 + $i); + } + } + + if (!isset($headers[':method']) || !isset($headers[':scheme']) || !isset($headers[':authority']) || !isset($headers[':path'])) { + $this->logger && $this->logger->debug(sprintf('Rejecting pushed response from "%s": pushed headers are invalid', $origin)); + + return \CURL_PUSH_DENY; + } + + $url = $headers[':scheme'][0].'://'.$headers[':authority'][0]; + + // curl before 7.65 doesn't validate the pushed ":authority" header, + // but this is a MUST in the HTTP/2 RFC; let's restrict pushes to the original host, + // ignoring domains mentioned as alt-name in the certificate for now (same as curl). + if (!str_starts_with($origin, $url.'/')) { + $this->logger && $this->logger->debug(sprintf('Rejecting pushed response from "%s": server is not authoritative for "%s"', $origin, $url)); + + return \CURL_PUSH_DENY; + } + + if ($maxPendingPushes <= \count($this->pushedResponses)) { + $fifoUrl = key($this->pushedResponses); + unset($this->pushedResponses[$fifoUrl]); + $this->logger && $this->logger->debug(sprintf('Evicting oldest pushed response: "%s"', $fifoUrl)); + } + + $url .= $headers[':path'][0]; + $this->logger && $this->logger->debug(sprintf('Queueing pushed response: "%s"', $url)); + + $this->pushedResponses[$url] = new PushedResponse(new CurlResponse($this, $pushed), $headers, $this->openHandles[(int) $parent][1] ?? [], $pushed); + + return \CURL_PUSH_OK; + } +} diff --git a/src/vendor/symfony/http-client/Internal/DnsCache.php b/src/vendor/symfony/http-client/Internal/DnsCache.php new file mode 100644 index 0000000..bd23f77 --- /dev/null +++ b/src/vendor/symfony/http-client/Internal/DnsCache.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Internal; + +/** + * Cache for resolved DNS queries. + * + * @author Alexander M. Turek + * + * @internal + */ +final class DnsCache +{ + /** + * Resolved hostnames (hostname => IP address). + * + * @var string[] + */ + public $hostnames = []; + + /** + * @var string[] + */ + public $removals = []; + + /** + * @var string[] + */ + public $evictions = []; +} diff --git a/src/vendor/symfony/http-client/Internal/HttplugWaitLoop.php b/src/vendor/symfony/http-client/Internal/HttplugWaitLoop.php new file mode 100644 index 0000000..c61be22 --- /dev/null +++ b/src/vendor/symfony/http-client/Internal/HttplugWaitLoop.php @@ -0,0 +1,145 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Internal; + +use Http\Client\Exception\NetworkException; +use Http\Promise\Promise; +use Psr\Http\Message\RequestInterface as Psr7RequestInterface; +use Psr\Http\Message\ResponseFactoryInterface; +use Psr\Http\Message\ResponseInterface as Psr7ResponseInterface; +use Psr\Http\Message\StreamFactoryInterface; +use Symfony\Component\HttpClient\Response\StreamableInterface; +use Symfony\Component\HttpClient\Response\StreamWrapper; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * @author Nicolas Grekas + * + * @internal + */ +final class HttplugWaitLoop +{ + private $client; + private $promisePool; + private $responseFactory; + private $streamFactory; + + /** + * @param \SplObjectStorage|null $promisePool + */ + public function __construct(HttpClientInterface $client, ?\SplObjectStorage $promisePool, ResponseFactoryInterface $responseFactory, StreamFactoryInterface $streamFactory) + { + $this->client = $client; + $this->promisePool = $promisePool; + $this->responseFactory = $responseFactory; + $this->streamFactory = $streamFactory; + } + + public function wait(?ResponseInterface $pendingResponse, float $maxDuration = null, float $idleTimeout = null): int + { + if (!$this->promisePool) { + return 0; + } + + $guzzleQueue = \GuzzleHttp\Promise\Utils::queue(); + + if (0.0 === $remainingDuration = $maxDuration) { + $idleTimeout = 0.0; + } elseif (null !== $maxDuration) { + $startTime = microtime(true); + $idleTimeout = max(0.0, min($maxDuration / 5, $idleTimeout ?? $maxDuration)); + } + + do { + foreach ($this->client->stream($this->promisePool, $idleTimeout) as $response => $chunk) { + try { + if (null !== $maxDuration && $chunk->isTimeout()) { + goto check_duration; + } + + if ($chunk->isFirst()) { + // Deactivate throwing on 3/4/5xx + $response->getStatusCode(); + } + + if (!$chunk->isLast()) { + goto check_duration; + } + + if ([, $promise] = $this->promisePool[$response] ?? null) { + unset($this->promisePool[$response]); + $promise->resolve($this->createPsr7Response($response, true)); + } + } catch (\Exception $e) { + if ([$request, $promise] = $this->promisePool[$response] ?? null) { + unset($this->promisePool[$response]); + + if ($e instanceof TransportExceptionInterface) { + $e = new NetworkException($e->getMessage(), $request, $e); + } + + $promise->reject($e); + } + } + + $guzzleQueue->run(); + + if ($pendingResponse === $response) { + return $this->promisePool->count(); + } + + check_duration: + if (null !== $maxDuration && $idleTimeout && $idleTimeout > $remainingDuration = max(0.0, $maxDuration - microtime(true) + $startTime)) { + $idleTimeout = $remainingDuration / 5; + break; + } + } + + if (!$count = $this->promisePool->count()) { + return 0; + } + } while (null === $maxDuration || 0 < $remainingDuration); + + return $count; + } + + public function createPsr7Response(ResponseInterface $response, bool $buffer = false): Psr7ResponseInterface + { + $psrResponse = $this->responseFactory->createResponse($response->getStatusCode()); + + foreach ($response->getHeaders(false) as $name => $values) { + foreach ($values as $value) { + try { + $psrResponse = $psrResponse->withAddedHeader($name, $value); + } catch (\InvalidArgumentException $e) { + // ignore invalid header + } + } + } + + if ($response instanceof StreamableInterface) { + $body = $this->streamFactory->createStreamFromResource($response->toStream(false)); + } elseif (!$buffer) { + $body = $this->streamFactory->createStreamFromResource(StreamWrapper::createResource($response, $this->client)); + } else { + $body = $this->streamFactory->createStream($response->getContent(false)); + } + + if ($body->isSeekable()) { + $body->seek(0); + } + + return $psrResponse->withBody($body); + } +} diff --git a/src/vendor/symfony/http-client/Internal/NativeClientState.php b/src/vendor/symfony/http-client/Internal/NativeClientState.php new file mode 100644 index 0000000..20b2727 --- /dev/null +++ b/src/vendor/symfony/http-client/Internal/NativeClientState.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Internal; + +/** + * Internal representation of the native client's state. + * + * @author Alexander M. Turek + * + * @internal + */ +final class NativeClientState extends ClientState +{ + /** @var int */ + public $id; + /** @var int */ + public $maxHostConnections = \PHP_INT_MAX; + /** @var int */ + public $responseCount = 0; + /** @var string[] */ + public $dnsCache = []; + /** @var bool */ + public $sleep = false; + /** @var int[] */ + public $hosts = []; + + public function __construct() + { + $this->id = random_int(\PHP_INT_MIN, \PHP_INT_MAX); + } + + public function reset() + { + $this->responseCount = 0; + $this->dnsCache = []; + $this->hosts = []; + } +} diff --git a/src/vendor/symfony/http-client/Internal/PushedResponse.php b/src/vendor/symfony/http-client/Internal/PushedResponse.php new file mode 100644 index 0000000..08fca60 --- /dev/null +++ b/src/vendor/symfony/http-client/Internal/PushedResponse.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Internal; + +use Symfony\Component\HttpClient\Response\CurlResponse; + +/** + * A pushed response with its request headers. + * + * @author Alexander M. Turek + * + * @internal + */ +final class PushedResponse +{ + public $response; + + /** @var string[] */ + public $requestHeaders; + + public $parentOptions = []; + + public $handle; + + public function __construct(CurlResponse $response, array $requestHeaders, array $parentOptions, $handle) + { + $this->response = $response; + $this->requestHeaders = $requestHeaders; + $this->parentOptions = $parentOptions; + $this->handle = $handle; + } +} diff --git a/src/vendor/symfony/http-client/LICENSE b/src/vendor/symfony/http-client/LICENSE new file mode 100644 index 0000000..7536cae --- /dev/null +++ b/src/vendor/symfony/http-client/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/vendor/symfony/http-client/MockHttpClient.php b/src/vendor/symfony/http-client/MockHttpClient.php new file mode 100644 index 0000000..fecba0e --- /dev/null +++ b/src/vendor/symfony/http-client/MockHttpClient.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpClient\Response\MockResponse; +use Symfony\Component\HttpClient\Response\ResponseStream; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\HttpClient\ResponseStreamInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * A test-friendly HttpClient that doesn't make actual HTTP requests. + * + * @author Nicolas Grekas + */ +class MockHttpClient implements HttpClientInterface, ResetInterface +{ + use HttpClientTrait; + + private $responseFactory; + private $requestsCount = 0; + private $defaultOptions = []; + + /** + * @param callable|callable[]|ResponseInterface|ResponseInterface[]|iterable|null $responseFactory + */ + public function __construct($responseFactory = null, ?string $baseUri = 'https://example.com') + { + $this->setResponseFactory($responseFactory); + $this->defaultOptions['base_uri'] = $baseUri; + } + + /** + * @param callable|callable[]|ResponseInterface|ResponseInterface[]|iterable|null $responseFactory + */ + public function setResponseFactory($responseFactory): void + { + if ($responseFactory instanceof ResponseInterface) { + $responseFactory = [$responseFactory]; + } + + if (!$responseFactory instanceof \Iterator && null !== $responseFactory && !\is_callable($responseFactory)) { + $responseFactory = (static function () use ($responseFactory) { + yield from $responseFactory; + })(); + } + + $this->responseFactory = $responseFactory; + } + + /** + * {@inheritdoc} + */ + public function request(string $method, string $url, array $options = []): ResponseInterface + { + [$url, $options] = $this->prepareRequest($method, $url, $options, $this->defaultOptions, true); + $url = implode('', $url); + + if (null === $this->responseFactory) { + $response = new MockResponse(); + } elseif (\is_callable($this->responseFactory)) { + $response = ($this->responseFactory)($method, $url, $options); + } elseif (!$this->responseFactory->valid()) { + throw new TransportException('The response factory iterator passed to MockHttpClient is empty.'); + } else { + $responseFactory = $this->responseFactory->current(); + $response = \is_callable($responseFactory) ? $responseFactory($method, $url, $options) : $responseFactory; + $this->responseFactory->next(); + } + ++$this->requestsCount; + + if (!$response instanceof ResponseInterface) { + throw new TransportException(sprintf('The response factory passed to MockHttpClient must return/yield an instance of ResponseInterface, "%s" given.', \is_object($response) ? \get_class($response) : \gettype($response))); + } + + return MockResponse::fromRequest($method, $url, $options, $response); + } + + /** + * {@inheritdoc} + */ + public function stream($responses, float $timeout = null): ResponseStreamInterface + { + if ($responses instanceof ResponseInterface) { + $responses = [$responses]; + } elseif (!is_iterable($responses)) { + throw new \TypeError(sprintf('"%s()" expects parameter 1 to be an iterable of MockResponse objects, "%s" given.', __METHOD__, get_debug_type($responses))); + } + + return new ResponseStream(MockResponse::stream($responses, $timeout)); + } + + public function getRequestsCount(): int + { + return $this->requestsCount; + } + + /** + * {@inheritdoc} + */ + public function withOptions(array $options): self + { + $clone = clone $this; + $clone->defaultOptions = self::mergeDefaultOptions($options, $this->defaultOptions, true); + + return $clone; + } + + public function reset() + { + $this->requestsCount = 0; + } +} diff --git a/src/vendor/symfony/http-client/NativeHttpClient.php b/src/vendor/symfony/http-client/NativeHttpClient.php new file mode 100644 index 0000000..63fcc1c --- /dev/null +++ b/src/vendor/symfony/http-client/NativeHttpClient.php @@ -0,0 +1,468 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerAwareTrait; +use Symfony\Component\HttpClient\Exception\InvalidArgumentException; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpClient\Internal\NativeClientState; +use Symfony\Component\HttpClient\Response\NativeResponse; +use Symfony\Component\HttpClient\Response\ResponseStream; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\HttpClient\ResponseStreamInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * A portable implementation of the HttpClientInterface contracts based on PHP stream wrappers. + * + * PHP stream wrappers are able to fetch response bodies concurrently, + * but each request is opened synchronously. + * + * @author Nicolas Grekas + */ +final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterface, ResetInterface +{ + use HttpClientTrait; + use LoggerAwareTrait; + + private $defaultOptions = self::OPTIONS_DEFAULTS; + private static $emptyDefaults = self::OPTIONS_DEFAULTS; + + /** @var NativeClientState */ + private $multi; + + /** + * @param array $defaultOptions Default request's options + * @param int $maxHostConnections The maximum number of connections to open + * + * @see HttpClientInterface::OPTIONS_DEFAULTS for available options + */ + public function __construct(array $defaultOptions = [], int $maxHostConnections = 6) + { + $this->defaultOptions['buffer'] = $this->defaultOptions['buffer'] ?? \Closure::fromCallable([__CLASS__, 'shouldBuffer']); + + if ($defaultOptions) { + [, $this->defaultOptions] = self::prepareRequest(null, null, $defaultOptions, $this->defaultOptions); + } + + $this->multi = new NativeClientState(); + $this->multi->maxHostConnections = 0 < $maxHostConnections ? $maxHostConnections : \PHP_INT_MAX; + } + + /** + * @see HttpClientInterface::OPTIONS_DEFAULTS for available options + * + * {@inheritdoc} + */ + public function request(string $method, string $url, array $options = []): ResponseInterface + { + [$url, $options] = self::prepareRequest($method, $url, $options, $this->defaultOptions); + + if ($options['bindto']) { + if (file_exists($options['bindto'])) { + throw new TransportException(__CLASS__.' cannot bind to local Unix sockets, use e.g. CurlHttpClient instead.'); + } + if (str_starts_with($options['bindto'], 'if!')) { + throw new TransportException(__CLASS__.' cannot bind to network interfaces, use e.g. CurlHttpClient instead.'); + } + if (str_starts_with($options['bindto'], 'host!')) { + $options['bindto'] = substr($options['bindto'], 5); + } + } + + $hasContentLength = isset($options['normalized_headers']['content-length']); + $hasBody = '' !== $options['body'] || 'POST' === $method || $hasContentLength; + + $options['body'] = self::getBodyAsString($options['body']); + + if ('chunked' === substr($options['normalized_headers']['transfer-encoding'][0] ?? '', \strlen('Transfer-Encoding: '))) { + unset($options['normalized_headers']['transfer-encoding']); + $options['headers'] = array_merge(...array_values($options['normalized_headers'])); + $options['body'] = self::dechunk($options['body']); + } + if ('' === $options['body'] && $hasBody && !$hasContentLength) { + $options['headers'][] = 'Content-Length: 0'; + } + if ($hasBody && !isset($options['normalized_headers']['content-type'])) { + $options['headers'][] = 'Content-Type: application/x-www-form-urlencoded'; + } + + if (\extension_loaded('zlib') && !isset($options['normalized_headers']['accept-encoding'])) { + // gzip is the most widely available algo, no need to deal with deflate + $options['headers'][] = 'Accept-Encoding: gzip'; + } + + if ($options['peer_fingerprint']) { + if (isset($options['peer_fingerprint']['pin-sha256']) && 1 === \count($options['peer_fingerprint'])) { + throw new TransportException(__CLASS__.' cannot verify "pin-sha256" fingerprints, please provide a "sha256" one.'); + } + + unset($options['peer_fingerprint']['pin-sha256']); + } + + $info = [ + 'response_headers' => [], + 'url' => $url, + 'error' => null, + 'canceled' => false, + 'http_method' => $method, + 'http_code' => 0, + 'redirect_count' => 0, + 'start_time' => 0.0, + 'connect_time' => 0.0, + 'redirect_time' => 0.0, + 'pretransfer_time' => 0.0, + 'starttransfer_time' => 0.0, + 'total_time' => 0.0, + 'namelookup_time' => 0.0, + 'size_upload' => 0, + 'size_download' => 0, + 'size_body' => \strlen($options['body']), + 'primary_ip' => '', + 'primary_port' => 'http:' === $url['scheme'] ? 80 : 443, + 'debug' => \extension_loaded('curl') ? '' : "* Enable the curl extension for better performance\n", + ]; + + if ($onProgress = $options['on_progress']) { + // Memoize the last progress to ease calling the callback periodically when no network transfer happens + $lastProgress = [0, 0]; + $maxDuration = 0 < $options['max_duration'] ? $options['max_duration'] : \INF; + $onProgress = static function (...$progress) use ($onProgress, &$lastProgress, &$info, $maxDuration) { + if ($info['total_time'] >= $maxDuration) { + throw new TransportException(sprintf('Max duration was reached for "%s".', implode('', $info['url']))); + } + + $progressInfo = $info; + $progressInfo['url'] = implode('', $info['url']); + unset($progressInfo['size_body']); + + if ($progress && -1 === $progress[0]) { + // Response completed + $lastProgress[0] = max($lastProgress); + } else { + $lastProgress = $progress ?: $lastProgress; + } + + $onProgress($lastProgress[0], $lastProgress[1], $progressInfo); + }; + } elseif (0 < $options['max_duration']) { + $maxDuration = $options['max_duration']; + $onProgress = static function () use (&$info, $maxDuration): void { + if ($info['total_time'] >= $maxDuration) { + throw new TransportException(sprintf('Max duration was reached for "%s".', implode('', $info['url']))); + } + }; + } + + // Always register a notification callback to compute live stats about the response + $notification = static function (int $code, int $severity, ?string $msg, int $msgCode, int $dlNow, int $dlSize) use ($onProgress, &$info) { + $info['total_time'] = microtime(true) - $info['start_time']; + + if (\STREAM_NOTIFY_PROGRESS === $code) { + $info['starttransfer_time'] = $info['starttransfer_time'] ?: $info['total_time']; + $info['size_upload'] += $dlNow ? 0 : $info['size_body']; + $info['size_download'] = $dlNow; + } elseif (\STREAM_NOTIFY_CONNECT === $code) { + $info['connect_time'] = $info['total_time']; + $info['debug'] .= $info['request_header']; + unset($info['request_header']); + } else { + return; + } + + if ($onProgress) { + $onProgress($dlNow, $dlSize); + } + }; + + if ($options['resolve']) { + $this->multi->dnsCache = $options['resolve'] + $this->multi->dnsCache; + } + + $this->logger && $this->logger->info(sprintf('Request: "%s %s"', $method, implode('', $url))); + + if (!isset($options['normalized_headers']['user-agent'])) { + $options['headers'][] = 'User-Agent: Symfony HttpClient/Native'; + } + + if (0 < $options['max_duration']) { + $options['timeout'] = min($options['max_duration'], $options['timeout']); + } + + $bindto = $options['bindto']; + if (!$bindto && (70322 === \PHP_VERSION_ID || 70410 === \PHP_VERSION_ID)) { + $bindto = '0:0'; + } + + $context = [ + 'http' => [ + 'protocol_version' => min($options['http_version'] ?: '1.1', '1.1'), + 'method' => $method, + 'content' => $options['body'], + 'ignore_errors' => true, + 'curl_verify_ssl_peer' => $options['verify_peer'], + 'curl_verify_ssl_host' => $options['verify_host'], + 'auto_decode' => false, // Disable dechunk filter, it's incompatible with stream_select() + 'timeout' => $options['timeout'], + 'follow_location' => false, // We follow redirects ourselves - the native logic is too limited + ], + 'ssl' => array_filter([ + 'verify_peer' => $options['verify_peer'], + 'verify_peer_name' => $options['verify_host'], + 'cafile' => $options['cafile'], + 'capath' => $options['capath'], + 'local_cert' => $options['local_cert'], + 'local_pk' => $options['local_pk'], + 'passphrase' => $options['passphrase'], + 'ciphers' => $options['ciphers'], + 'peer_fingerprint' => $options['peer_fingerprint'], + 'capture_peer_cert_chain' => $options['capture_peer_cert_chain'], + 'allow_self_signed' => (bool) $options['peer_fingerprint'], + 'SNI_enabled' => true, + 'disable_compression' => true, + ], static function ($v) { return null !== $v; }), + 'socket' => [ + 'bindto' => $bindto, + 'tcp_nodelay' => true, + ], + ]; + + $context = stream_context_create($context, ['notification' => $notification]); + + $resolver = static function ($multi) use ($context, $options, $url, &$info, $onProgress) { + [$host, $port] = self::parseHostPort($url, $info); + + if (!isset($options['normalized_headers']['host'])) { + $options['headers'][] = 'Host: '.$host.$port; + } + + $proxy = self::getProxy($options['proxy'], $url, $options['no_proxy']); + + if (!self::configureHeadersAndProxy($context, $host, $options['headers'], $proxy, 'https:' === $url['scheme'])) { + $ip = self::dnsResolve($host, $multi, $info, $onProgress); + $url['authority'] = substr_replace($url['authority'], $ip, -\strlen($host) - \strlen($port), \strlen($host)); + } + + return [self::createRedirectResolver($options, $host, $proxy, $info, $onProgress), implode('', $url)]; + }; + + return new NativeResponse($this->multi, $context, implode('', $url), $options, $info, $resolver, $onProgress, $this->logger); + } + + /** + * {@inheritdoc} + */ + public function stream($responses, float $timeout = null): ResponseStreamInterface + { + if ($responses instanceof NativeResponse) { + $responses = [$responses]; + } elseif (!is_iterable($responses)) { + throw new \TypeError(sprintf('"%s()" expects parameter 1 to be an iterable of NativeResponse objects, "%s" given.', __METHOD__, get_debug_type($responses))); + } + + return new ResponseStream(NativeResponse::stream($responses, $timeout)); + } + + public function reset() + { + $this->multi->reset(); + } + + private static function getBodyAsString($body): string + { + if (\is_resource($body)) { + return stream_get_contents($body); + } + + if (!$body instanceof \Closure) { + return $body; + } + + $result = ''; + + while ('' !== $data = $body(self::$CHUNK_SIZE)) { + if (!\is_string($data)) { + throw new TransportException(sprintf('Return value of the "body" option callback must be string, "%s" returned.', get_debug_type($data))); + } + + $result .= $data; + } + + return $result; + } + + /** + * Extracts the host and the port from the URL. + */ + private static function parseHostPort(array $url, array &$info): array + { + if ($port = parse_url($url['authority'], \PHP_URL_PORT) ?: '') { + $info['primary_port'] = $port; + $port = ':'.$port; + } else { + $info['primary_port'] = 'http:' === $url['scheme'] ? 80 : 443; + } + + return [parse_url($url['authority'], \PHP_URL_HOST), $port]; + } + + /** + * Resolves the IP of the host using the local DNS cache if possible. + */ + private static function dnsResolve($host, NativeClientState $multi, array &$info, ?\Closure $onProgress): string + { + if (null === $ip = $multi->dnsCache[$host] ?? null) { + $info['debug'] .= "* Hostname was NOT found in DNS cache\n"; + $now = microtime(true); + + if (!$ip = gethostbynamel($host)) { + throw new TransportException(sprintf('Could not resolve host "%s".', $host)); + } + + $info['namelookup_time'] = microtime(true) - ($info['start_time'] ?: $now); + $multi->dnsCache[$host] = $ip = $ip[0]; + $info['debug'] .= "* Added {$host}:0:{$ip} to DNS cache\n"; + } else { + $info['debug'] .= "* Hostname was found in DNS cache\n"; + } + + $info['primary_ip'] = $ip; + + if ($onProgress) { + // Notify DNS resolution + $onProgress(); + } + + return $ip; + } + + /** + * Handles redirects - the native logic is too buggy to be used. + */ + private static function createRedirectResolver(array $options, string $host, ?array $proxy, array &$info, ?\Closure $onProgress): \Closure + { + $redirectHeaders = []; + if (0 < $maxRedirects = $options['max_redirects']) { + $redirectHeaders = ['host' => $host]; + $redirectHeaders['with_auth'] = $redirectHeaders['no_auth'] = array_filter($options['headers'], static function ($h) { + return 0 !== stripos($h, 'Host:'); + }); + + if (isset($options['normalized_headers']['authorization']) || isset($options['normalized_headers']['cookie'])) { + $redirectHeaders['no_auth'] = array_filter($redirectHeaders['no_auth'], static function ($h) { + return 0 !== stripos($h, 'Authorization:') && 0 !== stripos($h, 'Cookie:'); + }); + } + } + + return static function (NativeClientState $multi, ?string $location, $context) use (&$redirectHeaders, $proxy, &$info, $maxRedirects, $onProgress): ?string { + if (null === $location || $info['http_code'] < 300 || 400 <= $info['http_code']) { + $info['redirect_url'] = null; + + return null; + } + + try { + $url = self::parseUrl($location); + } catch (InvalidArgumentException $e) { + $info['redirect_url'] = null; + + return null; + } + + $url = self::resolveUrl($url, $info['url']); + $info['redirect_url'] = implode('', $url); + + if ($info['redirect_count'] >= $maxRedirects) { + return null; + } + + $info['url'] = $url; + ++$info['redirect_count']; + $info['redirect_time'] = microtime(true) - $info['start_time']; + + // Do like curl and browsers: turn POST to GET on 301, 302 and 303 + if (\in_array($info['http_code'], [301, 302, 303], true)) { + $options = stream_context_get_options($context)['http']; + + if ('POST' === $options['method'] || 303 === $info['http_code']) { + $info['http_method'] = $options['method'] = 'HEAD' === $options['method'] ? 'HEAD' : 'GET'; + $options['content'] = ''; + $filterContentHeaders = static function ($h) { + return 0 !== stripos($h, 'Content-Length:') && 0 !== stripos($h, 'Content-Type:') && 0 !== stripos($h, 'Transfer-Encoding:'); + }; + $options['header'] = array_filter($options['header'], $filterContentHeaders); + $redirectHeaders['no_auth'] = array_filter($redirectHeaders['no_auth'], $filterContentHeaders); + $redirectHeaders['with_auth'] = array_filter($redirectHeaders['with_auth'], $filterContentHeaders); + + stream_context_set_option($context, ['http' => $options]); + } + } + + [$host, $port] = self::parseHostPort($url, $info); + + if (false !== (parse_url($location, \PHP_URL_HOST) ?? false)) { + // Authorization and Cookie headers MUST NOT follow except for the initial host name + $requestHeaders = $redirectHeaders['host'] === $host ? $redirectHeaders['with_auth'] : $redirectHeaders['no_auth']; + $requestHeaders[] = 'Host: '.$host.$port; + $dnsResolve = !self::configureHeadersAndProxy($context, $host, $requestHeaders, $proxy, 'https:' === $url['scheme']); + } else { + $dnsResolve = isset(stream_context_get_options($context)['ssl']['peer_name']); + } + + if ($dnsResolve) { + $ip = self::dnsResolve($host, $multi, $info, $onProgress); + $url['authority'] = substr_replace($url['authority'], $ip, -\strlen($host) - \strlen($port), \strlen($host)); + } + + return implode('', $url); + }; + } + + private static function configureHeadersAndProxy($context, string $host, array $requestHeaders, ?array $proxy, bool $isSsl): bool + { + if (null === $proxy) { + stream_context_set_option($context, 'http', 'header', $requestHeaders); + stream_context_set_option($context, 'ssl', 'peer_name', $host); + + return false; + } + + // Matching "no_proxy" should follow the behavior of curl + + foreach ($proxy['no_proxy'] as $rule) { + $dotRule = '.'.ltrim($rule, '.'); + + if ('*' === $rule || $host === $rule || str_ends_with($host, $dotRule)) { + stream_context_set_option($context, 'http', 'proxy', null); + stream_context_set_option($context, 'http', 'request_fulluri', false); + stream_context_set_option($context, 'http', 'header', $requestHeaders); + stream_context_set_option($context, 'ssl', 'peer_name', $host); + + return false; + } + } + + if (null !== $proxy['auth']) { + $requestHeaders[] = 'Proxy-Authorization: '.$proxy['auth']; + } + + stream_context_set_option($context, 'http', 'proxy', $proxy['url']); + stream_context_set_option($context, 'http', 'request_fulluri', !$isSsl); + stream_context_set_option($context, 'http', 'header', $requestHeaders); + stream_context_set_option($context, 'ssl', 'peer_name', null); + + return true; + } +} diff --git a/src/vendor/symfony/http-client/NoPrivateNetworkHttpClient.php b/src/vendor/symfony/http-client/NoPrivateNetworkHttpClient.php new file mode 100644 index 0000000..911cce9 --- /dev/null +++ b/src/vendor/symfony/http-client/NoPrivateNetworkHttpClient.php @@ -0,0 +1,132 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpClient\Exception\InvalidArgumentException; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpFoundation\IpUtils; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\HttpClient\ResponseStreamInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * Decorator that blocks requests to private networks by default. + * + * @author Hallison Boaventura + */ +final class NoPrivateNetworkHttpClient implements HttpClientInterface, LoggerAwareInterface, ResetInterface +{ + use HttpClientTrait; + + private const PRIVATE_SUBNETS = [ + '127.0.0.0/8', + '10.0.0.0/8', + '192.168.0.0/16', + '172.16.0.0/12', + '169.254.0.0/16', + '0.0.0.0/8', + '240.0.0.0/4', + '::1/128', + 'fc00::/7', + 'fe80::/10', + '::ffff:0:0/96', + '::/128', + ]; + + private $client; + private $subnets; + + /** + * @param string|array|null $subnets String or array of subnets using CIDR notation that will be used by IpUtils. + * If null is passed, the standard private subnets will be used. + */ + public function __construct(HttpClientInterface $client, $subnets = null) + { + if (!(\is_array($subnets) || \is_string($subnets) || null === $subnets)) { + throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be of the type array, string or null. "%s" given.', __METHOD__, get_debug_type($subnets))); + } + + if (!class_exists(IpUtils::class)) { + throw new \LogicException(sprintf('You cannot use "%s" if the HttpFoundation component is not installed. Try running "composer require symfony/http-foundation".', __CLASS__)); + } + + $this->client = $client; + $this->subnets = $subnets; + } + + /** + * {@inheritdoc} + */ + public function request(string $method, string $url, array $options = []): ResponseInterface + { + $onProgress = $options['on_progress'] ?? null; + if (null !== $onProgress && !\is_callable($onProgress)) { + throw new InvalidArgumentException(sprintf('Option "on_progress" must be callable, "%s" given.', get_debug_type($onProgress))); + } + + $subnets = $this->subnets; + $lastPrimaryIp = ''; + + $options['on_progress'] = function (int $dlNow, int $dlSize, array $info) use ($onProgress, $subnets, &$lastPrimaryIp): void { + if ($info['primary_ip'] !== $lastPrimaryIp) { + if ($info['primary_ip'] && IpUtils::checkIp($info['primary_ip'], $subnets ?? self::PRIVATE_SUBNETS)) { + throw new TransportException(sprintf('IP "%s" is blocked for "%s".', $info['primary_ip'], $info['url'])); + } + + $lastPrimaryIp = $info['primary_ip']; + } + + null !== $onProgress && $onProgress($dlNow, $dlSize, $info); + }; + + return $this->client->request($method, $url, $options); + } + + /** + * {@inheritdoc} + */ + public function stream($responses, float $timeout = null): ResponseStreamInterface + { + return $this->client->stream($responses, $timeout); + } + + /** + * {@inheritdoc} + */ + public function setLogger(LoggerInterface $logger): void + { + if ($this->client instanceof LoggerAwareInterface) { + $this->client->setLogger($logger); + } + } + + /** + * {@inheritdoc} + */ + public function withOptions(array $options): self + { + $clone = clone $this; + $clone->client = $this->client->withOptions($options); + + return $clone; + } + + public function reset() + { + if ($this->client instanceof ResetInterface) { + $this->client->reset(); + } + } +} diff --git a/src/vendor/symfony/http-client/Psr18Client.php b/src/vendor/symfony/http-client/Psr18Client.php new file mode 100644 index 0000000..2ec758a --- /dev/null +++ b/src/vendor/symfony/http-client/Psr18Client.php @@ -0,0 +1,248 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Http\Discovery\Exception\NotFoundException; +use Http\Discovery\Psr17FactoryDiscovery; +use Nyholm\Psr7\Factory\Psr17Factory; +use Nyholm\Psr7\Request; +use Nyholm\Psr7\Uri; +use Psr\Http\Client\ClientInterface; +use Psr\Http\Client\NetworkExceptionInterface; +use Psr\Http\Client\RequestExceptionInterface; +use Psr\Http\Message\RequestFactoryInterface; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseFactoryInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamFactoryInterface; +use Psr\Http\Message\StreamInterface; +use Psr\Http\Message\UriFactoryInterface; +use Psr\Http\Message\UriInterface; +use Symfony\Component\HttpClient\Response\StreamableInterface; +use Symfony\Component\HttpClient\Response\StreamWrapper; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\Service\ResetInterface; + +if (!interface_exists(RequestFactoryInterface::class)) { + throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\Psr18Client" as the "psr/http-factory" package is not installed. Try running "composer require nyholm/psr7".'); +} + +if (!interface_exists(ClientInterface::class)) { + throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\Psr18Client" as the "psr/http-client" package is not installed. Try running "composer require psr/http-client".'); +} + +/** + * An adapter to turn a Symfony HttpClientInterface into a PSR-18 ClientInterface. + * + * Run "composer require psr/http-client" to install the base ClientInterface. Run + * "composer require nyholm/psr7" to install an efficient implementation of response + * and stream factories with flex-provided autowiring aliases. + * + * @author Nicolas Grekas + */ +final class Psr18Client implements ClientInterface, RequestFactoryInterface, StreamFactoryInterface, UriFactoryInterface, ResetInterface +{ + private $client; + private $responseFactory; + private $streamFactory; + + public function __construct(HttpClientInterface $client = null, ResponseFactoryInterface $responseFactory = null, StreamFactoryInterface $streamFactory = null) + { + $this->client = $client ?? HttpClient::create(); + $this->responseFactory = $responseFactory; + $this->streamFactory = $streamFactory ?? ($responseFactory instanceof StreamFactoryInterface ? $responseFactory : null); + + if (null !== $this->responseFactory && null !== $this->streamFactory) { + return; + } + + if (!class_exists(Psr17Factory::class) && !class_exists(Psr17FactoryDiscovery::class)) { + throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\Psr18Client" as no PSR-17 factories have been provided. Try running "composer require nyholm/psr7".'); + } + + try { + $psr17Factory = class_exists(Psr17Factory::class, false) ? new Psr17Factory() : null; + $this->responseFactory = $this->responseFactory ?? $psr17Factory ?? Psr17FactoryDiscovery::findResponseFactory(); + $this->streamFactory = $this->streamFactory ?? $psr17Factory ?? Psr17FactoryDiscovery::findStreamFactory(); + } catch (NotFoundException $e) { + throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\HttplugClient" as no PSR-17 factories have been found. Try running "composer require nyholm/psr7".', 0, $e); + } + } + + /** + * {@inheritdoc} + */ + public function sendRequest(RequestInterface $request): ResponseInterface + { + try { + $body = $request->getBody(); + + if ($body->isSeekable()) { + $body->seek(0); + } + + $options = [ + 'headers' => $request->getHeaders(), + 'body' => $body->getContents(), + ]; + + if ('1.0' === $request->getProtocolVersion()) { + $options['http_version'] = '1.0'; + } + + $response = $this->client->request($request->getMethod(), (string) $request->getUri(), $options); + + $psrResponse = $this->responseFactory->createResponse($response->getStatusCode()); + + foreach ($response->getHeaders(false) as $name => $values) { + foreach ($values as $value) { + try { + $psrResponse = $psrResponse->withAddedHeader($name, $value); + } catch (\InvalidArgumentException $e) { + // ignore invalid header + } + } + } + + $body = $response instanceof StreamableInterface ? $response->toStream(false) : StreamWrapper::createResource($response, $this->client); + $body = $this->streamFactory->createStreamFromResource($body); + + if ($body->isSeekable()) { + $body->seek(0); + } + + return $psrResponse->withBody($body); + } catch (TransportExceptionInterface $e) { + if ($e instanceof \InvalidArgumentException) { + throw new Psr18RequestException($e, $request); + } + + throw new Psr18NetworkException($e, $request); + } + } + + /** + * {@inheritdoc} + */ + public function createRequest(string $method, $uri): RequestInterface + { + if ($this->responseFactory instanceof RequestFactoryInterface) { + return $this->responseFactory->createRequest($method, $uri); + } + + if (class_exists(Request::class)) { + return new Request($method, $uri); + } + + if (class_exists(Psr17FactoryDiscovery::class)) { + return Psr17FactoryDiscovery::findRequestFactory()->createRequest($method, $uri); + } + + throw new \LogicException(sprintf('You cannot use "%s()" as the "nyholm/psr7" package is not installed. Try running "composer require nyholm/psr7".', __METHOD__)); + } + + /** + * {@inheritdoc} + */ + public function createStream(string $content = ''): StreamInterface + { + $stream = $this->streamFactory->createStream($content); + + if ($stream->isSeekable()) { + $stream->seek(0); + } + + return $stream; + } + + /** + * {@inheritdoc} + */ + public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface + { + return $this->streamFactory->createStreamFromFile($filename, $mode); + } + + /** + * {@inheritdoc} + */ + public function createStreamFromResource($resource): StreamInterface + { + return $this->streamFactory->createStreamFromResource($resource); + } + + /** + * {@inheritdoc} + */ + public function createUri(string $uri = ''): UriInterface + { + if ($this->responseFactory instanceof UriFactoryInterface) { + return $this->responseFactory->createUri($uri); + } + + if (class_exists(Uri::class)) { + return new Uri($uri); + } + + if (class_exists(Psr17FactoryDiscovery::class)) { + return Psr17FactoryDiscovery::findUrlFactory()->createUri($uri); + } + + throw new \LogicException(sprintf('You cannot use "%s()" as the "nyholm/psr7" package is not installed. Try running "composer require nyholm/psr7".', __METHOD__)); + } + + public function reset() + { + if ($this->client instanceof ResetInterface) { + $this->client->reset(); + } + } +} + +/** + * @internal + */ +class Psr18NetworkException extends \RuntimeException implements NetworkExceptionInterface +{ + private $request; + + public function __construct(TransportExceptionInterface $e, RequestInterface $request) + { + parent::__construct($e->getMessage(), 0, $e); + $this->request = $request; + } + + public function getRequest(): RequestInterface + { + return $this->request; + } +} + +/** + * @internal + */ +class Psr18RequestException extends \InvalidArgumentException implements RequestExceptionInterface +{ + private $request; + + public function __construct(TransportExceptionInterface $e, RequestInterface $request) + { + parent::__construct($e->getMessage(), 0, $e); + $this->request = $request; + } + + public function getRequest(): RequestInterface + { + return $this->request; + } +} diff --git a/src/vendor/symfony/http-client/README.md b/src/vendor/symfony/http-client/README.md new file mode 100644 index 0000000..0c55ccc --- /dev/null +++ b/src/vendor/symfony/http-client/README.md @@ -0,0 +1,27 @@ +HttpClient component +==================== + +The HttpClient component provides powerful methods to fetch HTTP resources synchronously or asynchronously. + +Sponsor +------- + +The Httpclient component for Symfony 5.4/6.0 is [backed][1] by [Klaxoon][2]. + +Klaxoon is a platform that empowers organizations to run effective and +productive workshops easily in a hybrid environment. Anytime, Anywhere. + +Help Symfony by [sponsoring][3] its development! + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/http_client.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) + +[1]: https://symfony.com/backers +[2]: https://klaxoon.com +[3]: https://symfony.com/sponsor diff --git a/src/vendor/symfony/http-client/Response/AmpResponse.php b/src/vendor/symfony/http-client/Response/AmpResponse.php new file mode 100644 index 0000000..900c70d --- /dev/null +++ b/src/vendor/symfony/http-client/Response/AmpResponse.php @@ -0,0 +1,460 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Response; + +use Amp\ByteStream\StreamException; +use Amp\CancellationTokenSource; +use Amp\Coroutine; +use Amp\Deferred; +use Amp\Http\Client\HttpException; +use Amp\Http\Client\Request; +use Amp\Http\Client\Response; +use Amp\Loop; +use Amp\Promise; +use Amp\Success; +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpClient\Chunk\FirstChunk; +use Symfony\Component\HttpClient\Chunk\InformationalChunk; +use Symfony\Component\HttpClient\Exception\InvalidArgumentException; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpClient\HttpClientTrait; +use Symfony\Component\HttpClient\Internal\AmpBody; +use Symfony\Component\HttpClient\Internal\AmpClientState; +use Symfony\Component\HttpClient\Internal\Canary; +use Symfony\Component\HttpClient\Internal\ClientState; +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * @author Nicolas Grekas + * + * @internal + */ +final class AmpResponse implements ResponseInterface, StreamableInterface +{ + use CommonResponseTrait; + use TransportResponseTrait; + + private static $nextId = 'a'; + + private $multi; + private $options; + private $onProgress; + + private static $delay; + + /** + * @internal + */ + public function __construct(AmpClientState $multi, Request $request, array $options, ?LoggerInterface $logger) + { + $this->multi = $multi; + $this->options = &$options; + $this->logger = $logger; + $this->timeout = $options['timeout']; + $this->shouldBuffer = $options['buffer']; + + if ($this->inflate = \extension_loaded('zlib') && !$request->hasHeader('accept-encoding')) { + $request->setHeader('Accept-Encoding', 'gzip'); + } + + $this->initializer = static function (self $response) { + return null !== $response->options; + }; + + $info = &$this->info; + $headers = &$this->headers; + $canceller = new CancellationTokenSource(); + $handle = &$this->handle; + + $info['url'] = (string) $request->getUri(); + $info['http_method'] = $request->getMethod(); + $info['start_time'] = null; + $info['redirect_url'] = null; + $info['redirect_time'] = 0.0; + $info['redirect_count'] = 0; + $info['size_upload'] = 0.0; + $info['size_download'] = 0.0; + $info['upload_content_length'] = -1.0; + $info['download_content_length'] = -1.0; + $info['user_data'] = $options['user_data']; + $info['max_duration'] = $options['max_duration']; + $info['debug'] = ''; + + $onProgress = $options['on_progress'] ?? static function () {}; + $onProgress = $this->onProgress = static function () use (&$info, $onProgress) { + $info['total_time'] = microtime(true) - $info['start_time']; + $onProgress((int) $info['size_download'], ((int) (1 + $info['download_content_length']) ?: 1) - 1, (array) $info); + }; + + $pauseDeferred = new Deferred(); + $pause = new Success(); + + $throttleWatcher = null; + + $this->id = $id = self::$nextId++; + Loop::defer(static function () use ($request, $multi, &$id, &$info, &$headers, $canceller, &$options, $onProgress, &$handle, $logger, &$pause) { + return new Coroutine(self::generateResponse($request, $multi, $id, $info, $headers, $canceller, $options, $onProgress, $handle, $logger, $pause)); + }); + + $info['pause_handler'] = static function (float $duration) use (&$throttleWatcher, &$pauseDeferred, &$pause) { + if (null !== $throttleWatcher) { + Loop::cancel($throttleWatcher); + } + + $pause = $pauseDeferred->promise(); + + if ($duration <= 0) { + $deferred = $pauseDeferred; + $pauseDeferred = new Deferred(); + $deferred->resolve(); + } else { + $throttleWatcher = Loop::delay(ceil(1000 * $duration), static function () use (&$pauseDeferred) { + $deferred = $pauseDeferred; + $pauseDeferred = new Deferred(); + $deferred->resolve(); + }); + } + }; + + $multi->lastTimeout = null; + $multi->openHandles[$id] = $id; + ++$multi->responseCount; + + $this->canary = new Canary(static function () use ($canceller, $multi, $id) { + $canceller->cancel(); + unset($multi->openHandles[$id], $multi->handlesActivity[$id]); + }); + } + + /** + * {@inheritdoc} + */ + public function getInfo(string $type = null) + { + return null !== $type ? $this->info[$type] ?? null : $this->info; + } + + public function __sleep(): array + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + try { + $this->doDestruct(); + } finally { + // Clear the DNS cache when all requests completed + if (0 >= --$this->multi->responseCount) { + $this->multi->responseCount = 0; + $this->multi->dnsCache = []; + } + } + } + + /** + * {@inheritdoc} + */ + private static function schedule(self $response, array &$runningResponses): void + { + if (isset($runningResponses[0])) { + $runningResponses[0][1][$response->id] = $response; + } else { + $runningResponses[0] = [$response->multi, [$response->id => $response]]; + } + + if (!isset($response->multi->openHandles[$response->id])) { + $response->multi->handlesActivity[$response->id][] = null; + $response->multi->handlesActivity[$response->id][] = null !== $response->info['error'] ? new TransportException($response->info['error']) : null; + } + } + + /** + * {@inheritdoc} + * + * @param AmpClientState $multi + */ + private static function perform(ClientState $multi, array &$responses = null): void + { + if ($responses) { + foreach ($responses as $response) { + try { + if ($response->info['start_time']) { + $response->info['total_time'] = microtime(true) - $response->info['start_time']; + ($response->onProgress)(); + } + } catch (\Throwable $e) { + $multi->handlesActivity[$response->id][] = null; + $multi->handlesActivity[$response->id][] = $e; + } + } + } + } + + /** + * {@inheritdoc} + * + * @param AmpClientState $multi + */ + private static function select(ClientState $multi, float $timeout): int + { + $timeout += microtime(true); + self::$delay = Loop::defer(static function () use ($timeout) { + if (0 < $timeout -= microtime(true)) { + self::$delay = Loop::delay(ceil(1000 * $timeout), [Loop::class, 'stop']); + } else { + Loop::stop(); + } + }); + + Loop::run(); + + return null === self::$delay ? 1 : 0; + } + + private static function generateResponse(Request $request, AmpClientState $multi, string $id, array &$info, array &$headers, CancellationTokenSource $canceller, array &$options, \Closure $onProgress, &$handle, ?LoggerInterface $logger, Promise &$pause) + { + $request->setInformationalResponseHandler(static function (Response $response) use ($multi, $id, &$info, &$headers) { + self::addResponseHeaders($response, $info, $headers); + $multi->handlesActivity[$id][] = new InformationalChunk($response->getStatus(), $response->getHeaders()); + self::stopLoop(); + }); + + try { + /* @var Response $response */ + if (null === $response = yield from self::getPushedResponse($request, $multi, $info, $headers, $options, $logger)) { + $logger && $logger->info(sprintf('Request: "%s %s"', $info['http_method'], $info['url'])); + + $response = yield from self::followRedirects($request, $multi, $info, $headers, $canceller, $options, $onProgress, $handle, $logger, $pause); + } + + $options = null; + + $multi->handlesActivity[$id][] = new FirstChunk(); + + if ('HEAD' === $response->getRequest()->getMethod() || \in_array($info['http_code'], [204, 304], true)) { + $multi->handlesActivity[$id][] = null; + $multi->handlesActivity[$id][] = null; + self::stopLoop(); + + return; + } + + if ($response->hasHeader('content-length')) { + $info['download_content_length'] = (float) $response->getHeader('content-length'); + } + + $body = $response->getBody(); + + while (true) { + self::stopLoop(); + + yield $pause; + + if (null === $data = yield $body->read()) { + break; + } + + $info['size_download'] += \strlen($data); + $multi->handlesActivity[$id][] = $data; + } + + $multi->handlesActivity[$id][] = null; + $multi->handlesActivity[$id][] = null; + } catch (\Throwable $e) { + $multi->handlesActivity[$id][] = null; + $multi->handlesActivity[$id][] = $e; + } finally { + $info['download_content_length'] = $info['size_download']; + } + + self::stopLoop(); + } + + private static function followRedirects(Request $originRequest, AmpClientState $multi, array &$info, array &$headers, CancellationTokenSource $canceller, array $options, \Closure $onProgress, &$handle, ?LoggerInterface $logger, Promise &$pause) + { + yield $pause; + + $originRequest->setBody(new AmpBody($options['body'], $info, $onProgress)); + $response = yield $multi->request($options, $originRequest, $canceller->getToken(), $info, $onProgress, $handle); + $previousUrl = null; + + while (true) { + self::addResponseHeaders($response, $info, $headers); + $status = $response->getStatus(); + + if (!\in_array($status, [301, 302, 303, 307, 308], true) || null === $location = $response->getHeader('location')) { + return $response; + } + + $urlResolver = new class() { + use HttpClientTrait { + parseUrl as public; + resolveUrl as public; + } + }; + + try { + $previousUrl = $previousUrl ?? $urlResolver::parseUrl($info['url']); + $location = $urlResolver::parseUrl($location); + $location = $urlResolver::resolveUrl($location, $previousUrl); + $info['redirect_url'] = implode('', $location); + } catch (InvalidArgumentException $e) { + return $response; + } + + if (0 >= $options['max_redirects'] || $info['redirect_count'] >= $options['max_redirects']) { + return $response; + } + + $logger && $logger->info(sprintf('Redirecting: "%s %s"', $status, $info['url'])); + + try { + // Discard body of redirects + while (null !== yield $response->getBody()->read()) { + } + } catch (HttpException|StreamException $e) { + // Ignore streaming errors on previous responses + } + + ++$info['redirect_count']; + $info['url'] = $info['redirect_url']; + $info['redirect_url'] = null; + $previousUrl = $location; + + $request = new Request($info['url'], $info['http_method']); + $request->setProtocolVersions($originRequest->getProtocolVersions()); + $request->setTcpConnectTimeout($originRequest->getTcpConnectTimeout()); + $request->setTlsHandshakeTimeout($originRequest->getTlsHandshakeTimeout()); + $request->setTransferTimeout($originRequest->getTransferTimeout()); + + if (\in_array($status, [301, 302, 303], true)) { + $originRequest->removeHeader('transfer-encoding'); + $originRequest->removeHeader('content-length'); + $originRequest->removeHeader('content-type'); + + // Do like curl and browsers: turn POST to GET on 301, 302 and 303 + if ('POST' === $response->getRequest()->getMethod() || 303 === $status) { + $info['http_method'] = 'HEAD' === $response->getRequest()->getMethod() ? 'HEAD' : 'GET'; + $request->setMethod($info['http_method']); + } + } else { + $request->setBody(AmpBody::rewind($response->getRequest()->getBody())); + } + + foreach ($originRequest->getRawHeaders() as [$name, $value]) { + $request->addHeader($name, $value); + } + + if ($request->getUri()->getAuthority() !== $originRequest->getUri()->getAuthority()) { + $request->removeHeader('authorization'); + $request->removeHeader('cookie'); + $request->removeHeader('host'); + } + + yield $pause; + + $response = yield $multi->request($options, $request, $canceller->getToken(), $info, $onProgress, $handle); + $info['redirect_time'] = microtime(true) - $info['start_time']; + } + } + + private static function addResponseHeaders(Response $response, array &$info, array &$headers): void + { + $info['http_code'] = $response->getStatus(); + + if ($headers) { + $info['debug'] .= "< \r\n"; + $headers = []; + } + + $h = sprintf('HTTP/%s %s %s', $response->getProtocolVersion(), $response->getStatus(), $response->getReason()); + $info['debug'] .= "< {$h}\r\n"; + $info['response_headers'][] = $h; + + foreach ($response->getRawHeaders() as [$name, $value]) { + $headers[strtolower($name)][] = $value; + $h = $name.': '.$value; + $info['debug'] .= "< {$h}\r\n"; + $info['response_headers'][] = $h; + } + + $info['debug'] .= "< \r\n"; + } + + /** + * Accepts pushed responses only if their headers related to authentication match the request. + */ + private static function getPushedResponse(Request $request, AmpClientState $multi, array &$info, array &$headers, array $options, ?LoggerInterface $logger) + { + if ('' !== $options['body']) { + return null; + } + + $authority = $request->getUri()->getAuthority(); + + foreach ($multi->pushedResponses[$authority] ?? [] as $i => [$pushedUrl, $pushDeferred, $pushedRequest, $pushedResponse, $parentOptions]) { + if ($info['url'] !== $pushedUrl || $info['http_method'] !== $pushedRequest->getMethod()) { + continue; + } + + foreach ($parentOptions as $k => $v) { + if ($options[$k] !== $v) { + continue 2; + } + } + + foreach (['authorization', 'cookie', 'range', 'proxy-authorization'] as $k) { + if ($pushedRequest->getHeaderArray($k) !== $request->getHeaderArray($k)) { + continue 2; + } + } + + $response = yield $pushedResponse; + + foreach ($response->getHeaderArray('vary') as $vary) { + foreach (preg_split('/\s*+,\s*+/', $vary) as $v) { + if ('*' === $v || ($pushedRequest->getHeaderArray($v) !== $request->getHeaderArray($v) && 'accept-encoding' !== strtolower($v))) { + $logger && $logger->debug(sprintf('Skipping pushed response: "%s"', $info['url'])); + continue 3; + } + } + } + + $pushDeferred->resolve(); + $logger && $logger->debug(sprintf('Accepting pushed response: "%s %s"', $info['http_method'], $info['url'])); + self::addResponseHeaders($response, $info, $headers); + unset($multi->pushedResponses[$authority][$i]); + + if (!$multi->pushedResponses[$authority]) { + unset($multi->pushedResponses[$authority]); + } + + return $response; + } + } + + private static function stopLoop(): void + { + if (null !== self::$delay) { + Loop::cancel(self::$delay); + self::$delay = null; + } + + Loop::defer([Loop::class, 'stop']); + } +} diff --git a/src/vendor/symfony/http-client/Response/AsyncContext.php b/src/vendor/symfony/http-client/Response/AsyncContext.php new file mode 100644 index 0000000..646458e --- /dev/null +++ b/src/vendor/symfony/http-client/Response/AsyncContext.php @@ -0,0 +1,195 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Response; + +use Symfony\Component\HttpClient\Chunk\DataChunk; +use Symfony\Component\HttpClient\Chunk\LastChunk; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Contracts\HttpClient\ChunkInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * A DTO to work with AsyncResponse. + * + * @author Nicolas Grekas + */ +final class AsyncContext +{ + private $passthru; + private $client; + private $response; + private $info = []; + private $content; + private $offset; + + public function __construct(&$passthru, HttpClientInterface $client, ResponseInterface &$response, array &$info, $content, int $offset) + { + $this->passthru = &$passthru; + $this->client = $client; + $this->response = &$response; + $this->info = &$info; + $this->content = $content; + $this->offset = $offset; + } + + /** + * Returns the HTTP status without consuming the response. + */ + public function getStatusCode(): int + { + return $this->response->getInfo('http_code'); + } + + /** + * Returns the headers without consuming the response. + */ + public function getHeaders(): array + { + $headers = []; + + foreach ($this->response->getInfo('response_headers') as $h) { + if (11 <= \strlen($h) && '/' === $h[4] && preg_match('#^HTTP/\d+(?:\.\d+)? ([123456789]\d\d)(?: |$)#', $h, $m)) { + $headers = []; + } elseif (2 === \count($m = explode(':', $h, 2))) { + $headers[strtolower($m[0])][] = ltrim($m[1]); + } + } + + return $headers; + } + + /** + * @return resource|null The PHP stream resource where the content is buffered, if it is + */ + public function getContent() + { + return $this->content; + } + + /** + * Creates a new chunk of content. + */ + public function createChunk(string $data): ChunkInterface + { + return new DataChunk($this->offset, $data); + } + + /** + * Pauses the request for the given number of seconds. + */ + public function pause(float $duration): void + { + if (\is_callable($pause = $this->response->getInfo('pause_handler'))) { + $pause($duration); + } elseif (0 < $duration) { + usleep(1E6 * $duration); + } + } + + /** + * Cancels the request and returns the last chunk to yield. + */ + public function cancel(): ChunkInterface + { + $this->info['canceled'] = true; + $this->info['error'] = 'Response has been canceled.'; + $this->response->cancel(); + + return new LastChunk(); + } + + /** + * Returns the current info of the response. + */ + public function getInfo(string $type = null) + { + if (null !== $type) { + return $this->info[$type] ?? $this->response->getInfo($type); + } + + return $this->info + $this->response->getInfo(); + } + + /** + * Attaches an info to the response. + * + * @return $this + */ + public function setInfo(string $type, $value): self + { + if ('canceled' === $type && $value !== $this->info['canceled']) { + throw new \LogicException('You cannot set the "canceled" info directly.'); + } + + if (null === $value) { + unset($this->info[$type]); + } else { + $this->info[$type] = $value; + } + + return $this; + } + + /** + * Returns the currently processed response. + */ + public function getResponse(): ResponseInterface + { + return $this->response; + } + + /** + * Replaces the currently processed response by doing a new request. + */ + public function replaceRequest(string $method, string $url, array $options = []): ResponseInterface + { + $this->info['previous_info'][] = $info = $this->response->getInfo(); + if (null !== $onProgress = $options['on_progress'] ?? null) { + $thisInfo = &$this->info; + $options['on_progress'] = static function (int $dlNow, int $dlSize, array $info) use (&$thisInfo, $onProgress) { + $onProgress($dlNow, $dlSize, $thisInfo + $info); + }; + } + if (0 < ($info['max_duration'] ?? 0) && 0 < ($info['total_time'] ?? 0)) { + if (0 >= $options['max_duration'] = $info['max_duration'] - $info['total_time']) { + throw new TransportException(sprintf('Max duration was reached for "%s".', $info['url'])); + } + } + + return $this->response = $this->client->request($method, $url, ['buffer' => false] + $options); + } + + /** + * Replaces the currently processed response by another one. + */ + public function replaceResponse(ResponseInterface $response): ResponseInterface + { + $this->info['previous_info'][] = $this->response->getInfo(); + + return $this->response = $response; + } + + /** + * Replaces or removes the chunk filter iterator. + * + * @param ?callable(ChunkInterface, self): ?\Iterator $passthru + */ + public function passthru(callable $passthru = null): void + { + $this->passthru = $passthru ?? static function ($chunk, $context) { + $context->passthru = null; + + yield $chunk; + }; + } +} diff --git a/src/vendor/symfony/http-client/Response/AsyncResponse.php b/src/vendor/symfony/http-client/Response/AsyncResponse.php new file mode 100644 index 0000000..80c9f7d --- /dev/null +++ b/src/vendor/symfony/http-client/Response/AsyncResponse.php @@ -0,0 +1,478 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Response; + +use Symfony\Component\HttpClient\Chunk\ErrorChunk; +use Symfony\Component\HttpClient\Chunk\FirstChunk; +use Symfony\Component\HttpClient\Chunk\LastChunk; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Contracts\HttpClient\ChunkInterface; +use Symfony\Contracts\HttpClient\Exception\ExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\HttpExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * Provides a single extension point to process a response's content stream. + * + * @author Nicolas Grekas + */ +final class AsyncResponse implements ResponseInterface, StreamableInterface +{ + use CommonResponseTrait; + + private const FIRST_CHUNK_YIELDED = 1; + private const LAST_CHUNK_YIELDED = 2; + + private $client; + private $response; + private $info = ['canceled' => false]; + private $passthru; + private $stream; + private $yieldedState; + + /** + * @param ?callable(ChunkInterface, AsyncContext): ?\Iterator $passthru + */ + public function __construct(HttpClientInterface $client, string $method, string $url, array $options, callable $passthru = null) + { + $this->client = $client; + $this->shouldBuffer = $options['buffer'] ?? true; + + if (null !== $onProgress = $options['on_progress'] ?? null) { + $thisInfo = &$this->info; + $options['on_progress'] = static function (int $dlNow, int $dlSize, array $info) use (&$thisInfo, $onProgress) { + $onProgress($dlNow, $dlSize, $thisInfo + $info); + }; + } + $this->response = $client->request($method, $url, ['buffer' => false] + $options); + $this->passthru = $passthru; + $this->initializer = static function (self $response, float $timeout = null) { + if (null === $response->shouldBuffer) { + return false; + } + + while (true) { + foreach (self::stream([$response], $timeout) as $chunk) { + if ($chunk->isTimeout() && $response->passthru) { + foreach (self::passthru($response->client, $response, new ErrorChunk($response->offset, new TransportException($chunk->getError()))) as $chunk) { + if ($chunk->isFirst()) { + return false; + } + } + + continue 2; + } + + if ($chunk->isFirst()) { + return false; + } + } + + return false; + } + }; + if (\array_key_exists('user_data', $options)) { + $this->info['user_data'] = $options['user_data']; + } + if (\array_key_exists('max_duration', $options)) { + $this->info['max_duration'] = $options['max_duration']; + } + } + + public function getStatusCode(): int + { + if ($this->initializer) { + self::initialize($this); + } + + return $this->response->getStatusCode(); + } + + public function getHeaders(bool $throw = true): array + { + if ($this->initializer) { + self::initialize($this); + } + + $headers = $this->response->getHeaders(false); + + if ($throw) { + $this->checkStatusCode(); + } + + return $headers; + } + + public function getInfo(string $type = null) + { + if (null !== $type) { + return $this->info[$type] ?? $this->response->getInfo($type); + } + + return $this->info + $this->response->getInfo(); + } + + /** + * {@inheritdoc} + */ + public function toStream(bool $throw = true) + { + if ($throw) { + // Ensure headers arrived + $this->getHeaders(true); + } + + $handle = function () { + $stream = $this->response instanceof StreamableInterface ? $this->response->toStream(false) : StreamWrapper::createResource($this->response); + + return stream_get_meta_data($stream)['wrapper_data']->stream_cast(\STREAM_CAST_FOR_SELECT); + }; + + $stream = StreamWrapper::createResource($this); + stream_get_meta_data($stream)['wrapper_data'] + ->bindHandles($handle, $this->content); + + return $stream; + } + + /** + * {@inheritdoc} + */ + public function cancel(): void + { + if ($this->info['canceled']) { + return; + } + + $this->info['canceled'] = true; + $this->info['error'] = 'Response has been canceled.'; + $this->close(); + $client = $this->client; + $this->client = null; + + if (!$this->passthru) { + return; + } + + try { + foreach (self::passthru($client, $this, new LastChunk()) as $chunk) { + // no-op + } + + $this->passthru = null; + } catch (ExceptionInterface $e) { + // ignore any errors when canceling + } + } + + public function __destruct() + { + $httpException = null; + + if ($this->initializer && null === $this->getInfo('error')) { + try { + self::initialize($this, -0.0); + $this->getHeaders(true); + } catch (HttpExceptionInterface $httpException) { + // no-op + } + } + + if ($this->passthru && null === $this->getInfo('error')) { + $this->info['canceled'] = true; + + try { + foreach (self::passthru($this->client, $this, new LastChunk()) as $chunk) { + // no-op + } + } catch (ExceptionInterface $e) { + // ignore any errors when destructing + } + } + + if (null !== $httpException) { + throw $httpException; + } + } + + /** + * @internal + */ + public static function stream(iterable $responses, float $timeout = null, string $class = null): \Generator + { + while ($responses) { + $wrappedResponses = []; + $asyncMap = new \SplObjectStorage(); + $client = null; + + foreach ($responses as $r) { + if (!$r instanceof self) { + throw new \TypeError(sprintf('"%s::stream()" expects parameter 1 to be an iterable of AsyncResponse objects, "%s" given.', $class ?? static::class, get_debug_type($r))); + } + + if (null !== $e = $r->info['error'] ?? null) { + yield $r => $chunk = new ErrorChunk($r->offset, new TransportException($e)); + $chunk->didThrow() ?: $chunk->getContent(); + continue; + } + + if (null === $client) { + $client = $r->client; + } elseif ($r->client !== $client) { + throw new TransportException('Cannot stream AsyncResponse objects with many clients.'); + } + + $asyncMap[$r->response] = $r; + $wrappedResponses[] = $r->response; + + if ($r->stream) { + yield from self::passthruStream($response = $r->response, $r, new FirstChunk(), $asyncMap); + + if (!isset($asyncMap[$response])) { + array_pop($wrappedResponses); + } + + if ($r->response !== $response && !isset($asyncMap[$r->response])) { + $asyncMap[$r->response] = $r; + $wrappedResponses[] = $r->response; + } + } + } + + if (!$client || !$wrappedResponses) { + return; + } + + foreach ($client->stream($wrappedResponses, $timeout) as $response => $chunk) { + $r = $asyncMap[$response]; + + if (null === $chunk->getError()) { + if ($chunk->isFirst()) { + // Ensure no exception is thrown on destruct for the wrapped response + $r->response->getStatusCode(); + } elseif (0 === $r->offset && null === $r->content && $chunk->isLast()) { + $r->content = fopen('php://memory', 'w+'); + } + } + + if (!$r->passthru) { + if (null !== $chunk->getError() || $chunk->isLast()) { + unset($asyncMap[$response]); + } elseif (null !== $r->content && '' !== ($content = $chunk->getContent()) && \strlen($content) !== fwrite($r->content, $content)) { + $chunk = new ErrorChunk($r->offset, new TransportException(sprintf('Failed writing %d bytes to the response buffer.', \strlen($content)))); + $r->info['error'] = $chunk->getError(); + $r->response->cancel(); + } + + yield $r => $chunk; + continue; + } + + if (null !== $chunk->getError()) { + // no-op + } elseif ($chunk->isFirst()) { + $r->yieldedState = self::FIRST_CHUNK_YIELDED; + } elseif (self::FIRST_CHUNK_YIELDED !== $r->yieldedState && null === $chunk->getInformationalStatus()) { + throw new \LogicException(sprintf('Instance of "%s" is already consumed and cannot be managed by "%s". A decorated client should not call any of the response\'s methods in its "request()" method.', get_debug_type($response), $class ?? static::class)); + } + + foreach (self::passthru($r->client, $r, $chunk, $asyncMap) as $chunk) { + yield $r => $chunk; + } + + if ($r->response !== $response && isset($asyncMap[$response])) { + break; + } + } + + if (null === $chunk->getError() && $chunk->isLast()) { + $r->yieldedState = self::LAST_CHUNK_YIELDED; + } + if (null === $chunk->getError() && self::LAST_CHUNK_YIELDED !== $r->yieldedState && $r->response === $response && null !== $r->client) { + throw new \LogicException('A chunk passthru must yield an "isLast()" chunk before ending a stream.'); + } + + $responses = []; + foreach ($asyncMap as $response) { + $r = $asyncMap[$response]; + + if (null !== $r->client) { + $responses[] = $asyncMap[$response]; + } + } + } + } + + /** + * @param \SplObjectStorage|null $asyncMap + */ + private static function passthru(HttpClientInterface $client, self $r, ChunkInterface $chunk, \SplObjectStorage $asyncMap = null): \Generator + { + $r->stream = null; + $response = $r->response; + $context = new AsyncContext($r->passthru, $client, $r->response, $r->info, $r->content, $r->offset); + if (null === $stream = ($r->passthru)($chunk, $context)) { + if ($r->response === $response && (null !== $chunk->getError() || $chunk->isLast())) { + throw new \LogicException('A chunk passthru cannot swallow the last chunk.'); + } + + return; + } + + if (!$stream instanceof \Iterator) { + throw new \LogicException(sprintf('A chunk passthru must return an "Iterator", "%s" returned.', get_debug_type($stream))); + } + $r->stream = $stream; + + yield from self::passthruStream($response, $r, null, $asyncMap); + } + + /** + * @param \SplObjectStorage|null $asyncMap + */ + private static function passthruStream(ResponseInterface $response, self $r, ?ChunkInterface $chunk, ?\SplObjectStorage $asyncMap): \Generator + { + while (true) { + try { + if (null !== $chunk && $r->stream) { + $r->stream->next(); + } + + if (!$r->stream || !$r->stream->valid() || !$r->stream) { + $r->stream = null; + break; + } + } catch (\Throwable $e) { + unset($asyncMap[$response]); + $r->stream = null; + $r->info['error'] = $e->getMessage(); + $r->response->cancel(); + + yield $r => $chunk = new ErrorChunk($r->offset, $e); + $chunk->didThrow() ?: $chunk->getContent(); + break; + } + + $chunk = $r->stream->current(); + + if (!$chunk instanceof ChunkInterface) { + throw new \LogicException(sprintf('A chunk passthru must yield instances of "%s", "%s" yielded.', ChunkInterface::class, get_debug_type($chunk))); + } + + if (null !== $chunk->getError()) { + // no-op + } elseif ($chunk->isFirst()) { + $e = $r->openBuffer(); + + yield $r => $chunk; + + if ($r->initializer && null === $r->getInfo('error')) { + // Ensure the HTTP status code is always checked + $r->getHeaders(true); + } + + if (null === $e) { + continue; + } + + $r->response->cancel(); + $chunk = new ErrorChunk($r->offset, $e); + } elseif ('' !== $content = $chunk->getContent()) { + if (null !== $r->shouldBuffer) { + throw new \LogicException('A chunk passthru must yield an "isFirst()" chunk before any content chunk.'); + } + + if (null !== $r->content && \strlen($content) !== fwrite($r->content, $content)) { + $chunk = new ErrorChunk($r->offset, new TransportException(sprintf('Failed writing %d bytes to the response buffer.', \strlen($content)))); + $r->info['error'] = $chunk->getError(); + $r->response->cancel(); + } + } + + if (null !== $chunk->getError() || $chunk->isLast()) { + $stream = $r->stream; + $r->stream = null; + unset($asyncMap[$response]); + } + + if (null === $chunk->getError()) { + $r->offset += \strlen($content); + + yield $r => $chunk; + + if (!$chunk->isLast()) { + continue; + } + + $stream->next(); + + if ($stream->valid()) { + throw new \LogicException('A chunk passthru cannot yield after an "isLast()" chunk.'); + } + + $r->passthru = null; + } else { + if ($chunk instanceof ErrorChunk) { + $chunk->didThrow(false); + } else { + try { + $chunk = new ErrorChunk($chunk->getOffset(), !$chunk->isTimeout() ?: $chunk->getError()); + } catch (TransportExceptionInterface $e) { + $chunk = new ErrorChunk($chunk->getOffset(), $e); + } + } + + yield $r => $chunk; + $chunk->didThrow() ?: $chunk->getContent(); + } + + break; + } + } + + private function openBuffer(): ?\Throwable + { + if (null === $shouldBuffer = $this->shouldBuffer) { + throw new \LogicException('A chunk passthru cannot yield more than one "isFirst()" chunk.'); + } + + $e = $this->shouldBuffer = null; + + if ($shouldBuffer instanceof \Closure) { + try { + $shouldBuffer = $shouldBuffer($this->getHeaders(false)); + + if (null !== $e = $this->response->getInfo('error')) { + throw new TransportException($e); + } + } catch (\Throwable $e) { + $this->info['error'] = $e->getMessage(); + $this->response->cancel(); + } + } + + if (true === $shouldBuffer) { + $this->content = fopen('php://temp', 'w+'); + } elseif (\is_resource($shouldBuffer)) { + $this->content = $shouldBuffer; + } + + return $e; + } + + private function close(): void + { + $this->response->cancel(); + } +} diff --git a/src/vendor/symfony/http-client/Response/CommonResponseTrait.php b/src/vendor/symfony/http-client/Response/CommonResponseTrait.php new file mode 100644 index 0000000..11a8d6c --- /dev/null +++ b/src/vendor/symfony/http-client/Response/CommonResponseTrait.php @@ -0,0 +1,185 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Response; + +use Symfony\Component\HttpClient\Exception\ClientException; +use Symfony\Component\HttpClient\Exception\JsonException; +use Symfony\Component\HttpClient\Exception\RedirectionException; +use Symfony\Component\HttpClient\Exception\ServerException; +use Symfony\Component\HttpClient\Exception\TransportException; + +/** + * Implements common logic for response classes. + * + * @author Nicolas Grekas + * + * @internal + */ +trait CommonResponseTrait +{ + /** + * @var callable|null A callback that tells whether we're waiting for response headers + */ + private $initializer; + private $shouldBuffer; + private $content; + private $offset = 0; + private $jsonData; + + /** + * {@inheritdoc} + */ + public function getContent(bool $throw = true): string + { + if ($this->initializer) { + self::initialize($this); + } + + if ($throw) { + $this->checkStatusCode(); + } + + if (null === $this->content) { + $content = null; + + foreach (self::stream([$this]) as $chunk) { + if (!$chunk->isLast()) { + $content .= $chunk->getContent(); + } + } + + if (null !== $content) { + return $content; + } + + if (null === $this->content) { + throw new TransportException('Cannot get the content of the response twice: buffering is disabled.'); + } + } else { + foreach (self::stream([$this]) as $chunk) { + // Chunks are buffered in $this->content already + } + } + + rewind($this->content); + + return stream_get_contents($this->content); + } + + /** + * {@inheritdoc} + */ + public function toArray(bool $throw = true): array + { + if ('' === $content = $this->getContent($throw)) { + throw new JsonException('Response body is empty.'); + } + + if (null !== $this->jsonData) { + return $this->jsonData; + } + + try { + $content = json_decode($content, true, 512, \JSON_BIGINT_AS_STRING | (\PHP_VERSION_ID >= 70300 ? \JSON_THROW_ON_ERROR : 0)); + } catch (\JsonException $e) { + throw new JsonException($e->getMessage().sprintf(' for "%s".', $this->getInfo('url')), $e->getCode()); + } + + if (\PHP_VERSION_ID < 70300 && \JSON_ERROR_NONE !== json_last_error()) { + throw new JsonException(json_last_error_msg().sprintf(' for "%s".', $this->getInfo('url')), json_last_error()); + } + + if (!\is_array($content)) { + throw new JsonException(sprintf('JSON content was expected to decode to an array, "%s" returned for "%s".', get_debug_type($content), $this->getInfo('url'))); + } + + if (null !== $this->content) { + // Option "buffer" is true + return $this->jsonData = $content; + } + + return $content; + } + + /** + * {@inheritdoc} + */ + public function toStream(bool $throw = true) + { + if ($throw) { + // Ensure headers arrived + $this->getHeaders($throw); + } + + $stream = StreamWrapper::createResource($this); + stream_get_meta_data($stream)['wrapper_data'] + ->bindHandles($this->handle, $this->content); + + return $stream; + } + + public function __sleep(): array + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + /** + * Closes the response and all its network handles. + */ + abstract protected function close(): void; + + private static function initialize(self $response): void + { + if (null !== $response->getInfo('error')) { + throw new TransportException($response->getInfo('error')); + } + + try { + if (($response->initializer)($response, -0.0)) { + foreach (self::stream([$response], -0.0) as $chunk) { + if ($chunk->isFirst()) { + break; + } + } + } + } catch (\Throwable $e) { + // Persist timeouts thrown during initialization + $response->info['error'] = $e->getMessage(); + $response->close(); + throw $e; + } + + $response->initializer = null; + } + + private function checkStatusCode() + { + $code = $this->getInfo('http_code'); + + if (500 <= $code) { + throw new ServerException($this); + } + + if (400 <= $code) { + throw new ClientException($this); + } + + if (300 <= $code) { + throw new RedirectionException($this); + } + } +} diff --git a/src/vendor/symfony/http-client/Response/CurlResponse.php b/src/vendor/symfony/http-client/Response/CurlResponse.php new file mode 100644 index 0000000..2418203 --- /dev/null +++ b/src/vendor/symfony/http-client/Response/CurlResponse.php @@ -0,0 +1,473 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Response; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpClient\Chunk\FirstChunk; +use Symfony\Component\HttpClient\Chunk\InformationalChunk; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpClient\Internal\Canary; +use Symfony\Component\HttpClient\Internal\ClientState; +use Symfony\Component\HttpClient\Internal\CurlClientState; +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * @author Nicolas Grekas + * + * @internal + */ +final class CurlResponse implements ResponseInterface, StreamableInterface +{ + use CommonResponseTrait { + getContent as private doGetContent; + } + use TransportResponseTrait; + + private $multi; + private $debugBuffer; + + /** + * @param \CurlHandle|resource|string $ch + * + * @internal + */ + public function __construct(CurlClientState $multi, $ch, array $options = null, LoggerInterface $logger = null, string $method = 'GET', callable $resolveRedirect = null, int $curlVersion = null) + { + $this->multi = $multi; + + if (\is_resource($ch) || $ch instanceof \CurlHandle) { + $this->handle = $ch; + $this->debugBuffer = fopen('php://temp', 'w+'); + if (0x074000 === $curlVersion) { + fwrite($this->debugBuffer, 'Due to a bug in curl 7.64.0, the debug log is disabled; use another version to work around the issue.'); + } else { + curl_setopt($ch, \CURLOPT_VERBOSE, true); + curl_setopt($ch, \CURLOPT_STDERR, $this->debugBuffer); + } + } else { + $this->info['url'] = $ch; + $ch = $this->handle; + } + + $this->id = $id = (int) $ch; + $this->logger = $logger; + $this->shouldBuffer = $options['buffer'] ?? true; + $this->timeout = $options['timeout'] ?? null; + $this->info['http_method'] = $method; + $this->info['user_data'] = $options['user_data'] ?? null; + $this->info['max_duration'] = $options['max_duration'] ?? null; + $this->info['start_time'] = $this->info['start_time'] ?? microtime(true); + $info = &$this->info; + $headers = &$this->headers; + $debugBuffer = $this->debugBuffer; + + if (!$info['response_headers']) { + // Used to keep track of what we're waiting for + curl_setopt($ch, \CURLOPT_PRIVATE, \in_array($method, ['GET', 'HEAD', 'OPTIONS', 'TRACE'], true) && 1.0 < (float) ($options['http_version'] ?? 1.1) ? 'H2' : 'H0'); // H = headers + retry counter + } + + curl_setopt($ch, \CURLOPT_HEADERFUNCTION, static function ($ch, string $data) use (&$info, &$headers, $options, $multi, $id, &$location, $resolveRedirect, $logger): int { + return self::parseHeaderLine($ch, $data, $info, $headers, $options, $multi, $id, $location, $resolveRedirect, $logger); + }); + + if (null === $options) { + // Pushed response: buffer until requested + curl_setopt($ch, \CURLOPT_WRITEFUNCTION, static function ($ch, string $data) use ($multi, $id): int { + $multi->handlesActivity[$id][] = $data; + curl_pause($ch, \CURLPAUSE_RECV); + + return \strlen($data); + }); + + return; + } + + $execCounter = $multi->execCounter; + $this->info['pause_handler'] = static function (float $duration) use ($ch, $multi, $execCounter) { + if (0 < $duration) { + if ($execCounter === $multi->execCounter) { + $multi->execCounter = !\is_float($execCounter) ? 1 + $execCounter : \PHP_INT_MIN; + curl_multi_remove_handle($multi->handle, $ch); + } + + $lastExpiry = end($multi->pauseExpiries); + $multi->pauseExpiries[(int) $ch] = $duration += microtime(true); + if (false !== $lastExpiry && $lastExpiry > $duration) { + asort($multi->pauseExpiries); + } + curl_pause($ch, \CURLPAUSE_ALL); + } else { + unset($multi->pauseExpiries[(int) $ch]); + curl_pause($ch, \CURLPAUSE_CONT); + curl_multi_add_handle($multi->handle, $ch); + } + }; + + $this->inflate = !isset($options['normalized_headers']['accept-encoding']); + curl_pause($ch, \CURLPAUSE_CONT); + + if ($onProgress = $options['on_progress']) { + $url = isset($info['url']) ? ['url' => $info['url']] : []; + curl_setopt($ch, \CURLOPT_NOPROGRESS, false); + curl_setopt($ch, \CURLOPT_PROGRESSFUNCTION, static function ($ch, $dlSize, $dlNow) use ($onProgress, &$info, $url, $multi, $debugBuffer) { + try { + rewind($debugBuffer); + $debug = ['debug' => stream_get_contents($debugBuffer)]; + $onProgress($dlNow, $dlSize, $url + curl_getinfo($ch) + $info + $debug); + } catch (\Throwable $e) { + $multi->handlesActivity[(int) $ch][] = null; + $multi->handlesActivity[(int) $ch][] = $e; + + return 1; // Abort the request + } + + return null; + }); + } + + curl_setopt($ch, \CURLOPT_WRITEFUNCTION, static function ($ch, string $data) use ($multi, $id): int { + if ('H' === (curl_getinfo($ch, \CURLINFO_PRIVATE)[0] ?? null)) { + $multi->handlesActivity[$id][] = null; + $multi->handlesActivity[$id][] = new TransportException(sprintf('Unsupported protocol for "%s"', curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL))); + + return 0; + } + + curl_setopt($ch, \CURLOPT_WRITEFUNCTION, static function ($ch, string $data) use ($multi, $id): int { + $multi->handlesActivity[$id][] = $data; + + return \strlen($data); + }); + + $multi->handlesActivity[$id][] = $data; + + return \strlen($data); + }); + + $this->initializer = static function (self $response) { + $waitFor = curl_getinfo($ch = $response->handle, \CURLINFO_PRIVATE); + + return 'H' === $waitFor[0]; + }; + + // Schedule the request in a non-blocking way + $multi->lastTimeout = null; + $multi->openHandles[$id] = [$ch, $options]; + curl_multi_add_handle($multi->handle, $ch); + + $this->canary = new Canary(static function () use ($ch, $multi, $id) { + unset($multi->pauseExpiries[$id], $multi->openHandles[$id], $multi->handlesActivity[$id]); + curl_setopt($ch, \CURLOPT_PRIVATE, '_0'); + + if ($multi->performing) { + return; + } + + curl_multi_remove_handle($multi->handle, $ch); + curl_setopt_array($ch, [ + \CURLOPT_NOPROGRESS => true, + \CURLOPT_PROGRESSFUNCTION => null, + \CURLOPT_HEADERFUNCTION => null, + \CURLOPT_WRITEFUNCTION => null, + \CURLOPT_READFUNCTION => null, + \CURLOPT_INFILE => null, + ]); + + if (!$multi->openHandles) { + // Schedule DNS cache eviction for the next request + $multi->dnsCache->evictions = $multi->dnsCache->evictions ?: $multi->dnsCache->removals; + $multi->dnsCache->removals = $multi->dnsCache->hostnames = []; + } + }); + } + + /** + * {@inheritdoc} + */ + public function getInfo(string $type = null) + { + if (!$info = $this->finalInfo) { + $info = array_merge($this->info, curl_getinfo($this->handle)); + $info['url'] = $this->info['url'] ?? $info['url']; + $info['redirect_url'] = $this->info['redirect_url'] ?? null; + + // workaround curl not subtracting the time offset for pushed responses + if (isset($this->info['url']) && $info['start_time'] / 1000 < $info['total_time']) { + $info['total_time'] -= $info['starttransfer_time'] ?: $info['total_time']; + $info['starttransfer_time'] = 0.0; + } + + rewind($this->debugBuffer); + $info['debug'] = stream_get_contents($this->debugBuffer); + $waitFor = curl_getinfo($this->handle, \CURLINFO_PRIVATE); + + if ('H' !== $waitFor[0] && 'C' !== $waitFor[0]) { + curl_setopt($this->handle, \CURLOPT_VERBOSE, false); + rewind($this->debugBuffer); + ftruncate($this->debugBuffer, 0); + $this->finalInfo = $info; + } + } + + return null !== $type ? $info[$type] ?? null : $info; + } + + /** + * {@inheritdoc} + */ + public function getContent(bool $throw = true): string + { + $performing = $this->multi->performing; + $this->multi->performing = $performing || '_0' === curl_getinfo($this->handle, \CURLINFO_PRIVATE); + + try { + return $this->doGetContent($throw); + } finally { + $this->multi->performing = $performing; + } + } + + public function __destruct() + { + try { + if (null === $this->timeout) { + return; // Unused pushed response + } + + $this->doDestruct(); + } finally { + if (\is_resource($this->handle) || $this->handle instanceof \CurlHandle) { + curl_setopt($this->handle, \CURLOPT_VERBOSE, false); + } + } + } + + /** + * {@inheritdoc} + */ + private static function schedule(self $response, array &$runningResponses): void + { + if (isset($runningResponses[$i = (int) $response->multi->handle])) { + $runningResponses[$i][1][$response->id] = $response; + } else { + $runningResponses[$i] = [$response->multi, [$response->id => $response]]; + } + + if ('_0' === curl_getinfo($ch = $response->handle, \CURLINFO_PRIVATE)) { + // Response already completed + $response->multi->handlesActivity[$response->id][] = null; + $response->multi->handlesActivity[$response->id][] = null !== $response->info['error'] ? new TransportException($response->info['error']) : null; + } + } + + /** + * {@inheritdoc} + * + * @param CurlClientState $multi + */ + private static function perform(ClientState $multi, array &$responses = null): void + { + if ($multi->performing) { + if ($responses) { + $response = current($responses); + $multi->handlesActivity[(int) $response->handle][] = null; + $multi->handlesActivity[(int) $response->handle][] = new TransportException(sprintf('Userland callback cannot use the client nor the response while processing "%s".', curl_getinfo($response->handle, \CURLINFO_EFFECTIVE_URL))); + } + + return; + } + + try { + $multi->performing = true; + ++$multi->execCounter; + $active = 0; + while (\CURLM_CALL_MULTI_PERFORM === ($err = curl_multi_exec($multi->handle, $active))) { + } + + if (\CURLM_OK !== $err) { + throw new TransportException(curl_multi_strerror($err)); + } + + while ($info = curl_multi_info_read($multi->handle)) { + if (\CURLMSG_DONE !== $info['msg']) { + continue; + } + $result = $info['result']; + $id = (int) $ch = $info['handle']; + $waitFor = @curl_getinfo($ch, \CURLINFO_PRIVATE) ?: '_0'; + + if (\in_array($result, [\CURLE_SEND_ERROR, \CURLE_RECV_ERROR, /* CURLE_HTTP2 */ 16, /* CURLE_HTTP2_STREAM */ 92], true) && $waitFor[1] && 'C' !== $waitFor[0]) { + curl_multi_remove_handle($multi->handle, $ch); + $waitFor[1] = (string) ((int) $waitFor[1] - 1); // decrement the retry counter + curl_setopt($ch, \CURLOPT_PRIVATE, $waitFor); + curl_setopt($ch, \CURLOPT_FORBID_REUSE, true); + + if (0 === curl_multi_add_handle($multi->handle, $ch)) { + continue; + } + } + + if (\CURLE_RECV_ERROR === $result && 'H' === $waitFor[0] && 400 <= ($responses[(int) $ch]->info['http_code'] ?? 0)) { + $multi->handlesActivity[$id][] = new FirstChunk(); + } + + $multi->handlesActivity[$id][] = null; + $multi->handlesActivity[$id][] = \in_array($result, [\CURLE_OK, \CURLE_TOO_MANY_REDIRECTS], true) || '_0' === $waitFor || curl_getinfo($ch, \CURLINFO_SIZE_DOWNLOAD) === curl_getinfo($ch, \CURLINFO_CONTENT_LENGTH_DOWNLOAD) ? null : new TransportException(ucfirst(curl_error($ch) ?: curl_strerror($result)).sprintf(' for "%s".', curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL))); + } + } finally { + $multi->performing = false; + } + } + + /** + * {@inheritdoc} + * + * @param CurlClientState $multi + */ + private static function select(ClientState $multi, float $timeout): int + { + if (\PHP_VERSION_ID < 70211) { + // workaround https://bugs.php.net/76480 + $timeout = min($timeout, 0.01); + } + + if ($multi->pauseExpiries) { + $now = microtime(true); + + foreach ($multi->pauseExpiries as $id => $pauseExpiry) { + if ($now < $pauseExpiry) { + $timeout = min($timeout, $pauseExpiry - $now); + break; + } + + unset($multi->pauseExpiries[$id]); + curl_pause($multi->openHandles[$id][0], \CURLPAUSE_CONT); + curl_multi_add_handle($multi->handle, $multi->openHandles[$id][0]); + } + } + + if (0 !== $selected = curl_multi_select($multi->handle, $timeout)) { + return $selected; + } + + if ($multi->pauseExpiries && 0 < $timeout -= microtime(true) - $now) { + usleep((int) (1E6 * $timeout)); + } + + return 0; + } + + /** + * Parses header lines as curl yields them to us. + */ + private static function parseHeaderLine($ch, string $data, array &$info, array &$headers, ?array $options, CurlClientState $multi, int $id, ?string &$location, ?callable $resolveRedirect, ?LoggerInterface $logger): int + { + if (!str_ends_with($data, "\r\n")) { + return 0; + } + + $waitFor = @curl_getinfo($ch, \CURLINFO_PRIVATE) ?: '_0'; + + if ('H' !== $waitFor[0]) { + return \strlen($data); // Ignore HTTP trailers + } + + $statusCode = curl_getinfo($ch, \CURLINFO_RESPONSE_CODE); + + if ($statusCode !== $info['http_code'] && !preg_match("#^HTTP/\d+(?:\.\d+)? {$statusCode}(?: |\r\n$)#", $data)) { + return \strlen($data); // Ignore headers from responses to CONNECT requests + } + + if ("\r\n" !== $data) { + // Regular header line: add it to the list + self::addResponseHeaders([substr($data, 0, -2)], $info, $headers); + + if (!str_starts_with($data, 'HTTP/')) { + if (0 === stripos($data, 'Location:')) { + $location = trim(substr($data, 9, -2)); + } + + return \strlen($data); + } + + if (\function_exists('openssl_x509_read') && $certinfo = curl_getinfo($ch, \CURLINFO_CERTINFO)) { + $info['peer_certificate_chain'] = array_map('openssl_x509_read', array_column($certinfo, 'Cert')); + } + + if (300 <= $info['http_code'] && $info['http_code'] < 400) { + if (curl_getinfo($ch, \CURLINFO_REDIRECT_COUNT) === $options['max_redirects']) { + curl_setopt($ch, \CURLOPT_FOLLOWLOCATION, false); + } elseif (303 === $info['http_code'] || ('POST' === $info['http_method'] && \in_array($info['http_code'], [301, 302], true))) { + curl_setopt($ch, \CURLOPT_POSTFIELDS, ''); + } + } + + return \strlen($data); + } + + // End of headers: handle informational responses, redirects, etc. + + if (200 > $statusCode) { + $multi->handlesActivity[$id][] = new InformationalChunk($statusCode, $headers); + $location = null; + + return \strlen($data); + } + + $info['redirect_url'] = null; + + if (300 <= $statusCode && $statusCode < 400 && null !== $location) { + if ($noContent = 303 === $statusCode || ('POST' === $info['http_method'] && \in_array($statusCode, [301, 302], true))) { + $info['http_method'] = 'HEAD' === $info['http_method'] ? 'HEAD' : 'GET'; + curl_setopt($ch, \CURLOPT_CUSTOMREQUEST, $info['http_method']); + } + + if (null === $info['redirect_url'] = $resolveRedirect($ch, $location, $noContent)) { + $options['max_redirects'] = curl_getinfo($ch, \CURLINFO_REDIRECT_COUNT); + curl_setopt($ch, \CURLOPT_FOLLOWLOCATION, false); + curl_setopt($ch, \CURLOPT_MAXREDIRS, $options['max_redirects']); + } else { + $url = parse_url($location ?? ':'); + + if (isset($url['host']) && null !== $ip = $multi->dnsCache->hostnames[$url['host'] = strtolower($url['host'])] ?? null) { + // Populate DNS cache for redirects if needed + $port = $url['port'] ?? ('http' === ($url['scheme'] ?? parse_url(curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL), \PHP_URL_SCHEME)) ? 80 : 443); + curl_setopt($ch, \CURLOPT_RESOLVE, ["{$url['host']}:$port:$ip"]); + $multi->dnsCache->removals["-{$url['host']}:$port"] = "-{$url['host']}:$port"; + } + } + } + + if (401 === $statusCode && isset($options['auth_ntlm']) && 0 === strncasecmp($headers['www-authenticate'][0] ?? '', 'NTLM ', 5)) { + // Continue with NTLM auth + } elseif ($statusCode < 300 || 400 <= $statusCode || null === $location || curl_getinfo($ch, \CURLINFO_REDIRECT_COUNT) === $options['max_redirects']) { + // Headers and redirects completed, time to get the response's content + $multi->handlesActivity[$id][] = new FirstChunk(); + + if ('HEAD' === $info['http_method'] || \in_array($statusCode, [204, 304], true)) { + $waitFor = '_0'; // no content expected + $multi->handlesActivity[$id][] = null; + $multi->handlesActivity[$id][] = null; + } else { + $waitFor[0] = 'C'; // C = content + } + + curl_setopt($ch, \CURLOPT_PRIVATE, $waitFor); + } elseif (null !== $info['redirect_url'] && $logger) { + $logger->info(sprintf('Redirecting: "%s %s"', $info['http_code'], $info['redirect_url'])); + } + + $location = null; + + return \strlen($data); + } +} diff --git a/src/vendor/symfony/http-client/Response/HttplugPromise.php b/src/vendor/symfony/http-client/Response/HttplugPromise.php new file mode 100644 index 0000000..2efacca --- /dev/null +++ b/src/vendor/symfony/http-client/Response/HttplugPromise.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Response; + +use GuzzleHttp\Promise\Create; +use GuzzleHttp\Promise\PromiseInterface as GuzzlePromiseInterface; +use Http\Promise\Promise as HttplugPromiseInterface; +use Psr\Http\Message\ResponseInterface as Psr7ResponseInterface; + +/** + * @author Tobias Nyholm + * + * @internal + */ +final class HttplugPromise implements HttplugPromiseInterface +{ + private $promise; + + public function __construct(GuzzlePromiseInterface $promise) + { + $this->promise = $promise; + } + + public function then(callable $onFulfilled = null, callable $onRejected = null): self + { + return new self($this->promise->then( + $this->wrapThenCallback($onFulfilled), + $this->wrapThenCallback($onRejected) + )); + } + + public function cancel(): void + { + $this->promise->cancel(); + } + + /** + * {@inheritdoc} + */ + public function getState(): string + { + return $this->promise->getState(); + } + + /** + * {@inheritdoc} + * + * @return Psr7ResponseInterface|mixed + */ + public function wait($unwrap = true) + { + $result = $this->promise->wait($unwrap); + + while ($result instanceof HttplugPromiseInterface || $result instanceof GuzzlePromiseInterface) { + $result = $result->wait($unwrap); + } + + return $result; + } + + private function wrapThenCallback(?callable $callback): ?callable + { + if (null === $callback) { + return null; + } + + return static function ($value) use ($callback) { + return Create::promiseFor($callback($value)); + }; + } +} diff --git a/src/vendor/symfony/http-client/Response/MockResponse.php b/src/vendor/symfony/http-client/Response/MockResponse.php new file mode 100644 index 0000000..2c00108 --- /dev/null +++ b/src/vendor/symfony/http-client/Response/MockResponse.php @@ -0,0 +1,343 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Response; + +use Symfony\Component\HttpClient\Chunk\ErrorChunk; +use Symfony\Component\HttpClient\Chunk\FirstChunk; +use Symfony\Component\HttpClient\Exception\InvalidArgumentException; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpClient\Internal\ClientState; +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * A test-friendly response. + * + * @author Nicolas Grekas + */ +class MockResponse implements ResponseInterface, StreamableInterface +{ + use CommonResponseTrait; + use TransportResponseTrait { + doDestruct as public __destruct; + } + + private $body; + private $requestOptions = []; + private $requestUrl; + private $requestMethod; + + private static $mainMulti; + private static $idSequence = 0; + + /** + * @param string|string[]|iterable $body The response body as a string or an iterable of strings, + * yielding an empty string simulates an idle timeout, + * throwing an exception yields an ErrorChunk + * + * @see ResponseInterface::getInfo() for possible info, e.g. "response_headers" + */ + public function __construct($body = '', array $info = []) + { + $this->body = is_iterable($body) ? $body : (string) $body; + $this->info = $info + ['http_code' => 200] + $this->info; + + if (!isset($info['response_headers'])) { + return; + } + + $responseHeaders = []; + + foreach ($info['response_headers'] as $k => $v) { + foreach ((array) $v as $v) { + $responseHeaders[] = (\is_string($k) ? $k.': ' : '').$v; + } + } + + $this->info['response_headers'] = []; + self::addResponseHeaders($responseHeaders, $this->info, $this->headers); + } + + /** + * Returns the options used when doing the request. + */ + public function getRequestOptions(): array + { + return $this->requestOptions; + } + + /** + * Returns the URL used when doing the request. + */ + public function getRequestUrl(): string + { + return $this->requestUrl; + } + + /** + * Returns the method used when doing the request. + */ + public function getRequestMethod(): string + { + return $this->requestMethod; + } + + /** + * {@inheritdoc} + */ + public function getInfo(string $type = null) + { + return null !== $type ? $this->info[$type] ?? null : $this->info; + } + + /** + * {@inheritdoc} + */ + public function cancel(): void + { + $this->info['canceled'] = true; + $this->info['error'] = 'Response has been canceled.'; + try { + $this->body = null; + } catch (TransportException $e) { + // ignore errors when canceling + } + + $onProgress = $this->requestOptions['on_progress'] ?? static function () {}; + $dlSize = isset($this->headers['content-encoding']) || 'HEAD' === ($this->info['http_method'] ?? null) || \in_array($this->info['http_code'], [204, 304], true) ? 0 : (int) ($this->headers['content-length'][0] ?? 0); + $onProgress($this->offset, $dlSize, $this->info); + } + + /** + * {@inheritdoc} + */ + protected function close(): void + { + $this->inflate = null; + $this->body = []; + } + + /** + * @internal + */ + public static function fromRequest(string $method, string $url, array $options, ResponseInterface $mock): self + { + $response = new self([]); + $response->requestOptions = $options; + $response->id = ++self::$idSequence; + $response->shouldBuffer = $options['buffer'] ?? true; + $response->initializer = static function (self $response) { + return \is_array($response->body[0] ?? null); + }; + + $response->info['redirect_count'] = 0; + $response->info['redirect_url'] = null; + $response->info['start_time'] = microtime(true); + $response->info['http_method'] = $method; + $response->info['http_code'] = 0; + $response->info['user_data'] = $options['user_data'] ?? null; + $response->info['max_duration'] = $options['max_duration'] ?? null; + $response->info['url'] = $url; + + if ($mock instanceof self) { + $mock->requestOptions = $response->requestOptions; + $mock->requestMethod = $method; + $mock->requestUrl = $url; + } + + self::writeRequest($response, $options, $mock); + $response->body[] = [$options, $mock]; + + return $response; + } + + /** + * {@inheritdoc} + */ + protected static function schedule(self $response, array &$runningResponses): void + { + if (!$response->id) { + throw new InvalidArgumentException('MockResponse instances must be issued by MockHttpClient before processing.'); + } + + $multi = self::$mainMulti ?? self::$mainMulti = new ClientState(); + + if (!isset($runningResponses[0])) { + $runningResponses[0] = [$multi, []]; + } + + $runningResponses[0][1][$response->id] = $response; + } + + /** + * {@inheritdoc} + */ + protected static function perform(ClientState $multi, array &$responses): void + { + foreach ($responses as $response) { + $id = $response->id; + + if (null === $response->body) { + // Canceled response + $response->body = []; + } elseif ([] === $response->body) { + // Error chunk + $multi->handlesActivity[$id][] = null; + $multi->handlesActivity[$id][] = null !== $response->info['error'] ? new TransportException($response->info['error']) : null; + } elseif (null === $chunk = array_shift($response->body)) { + // Last chunk + $multi->handlesActivity[$id][] = null; + $multi->handlesActivity[$id][] = array_shift($response->body); + } elseif (\is_array($chunk)) { + // First chunk + try { + $offset = 0; + $chunk[1]->getStatusCode(); + $chunk[1]->getHeaders(false); + self::readResponse($response, $chunk[0], $chunk[1], $offset); + $multi->handlesActivity[$id][] = new FirstChunk(); + $buffer = $response->requestOptions['buffer'] ?? null; + + if ($buffer instanceof \Closure && $response->content = $buffer($response->headers) ?: null) { + $response->content = \is_resource($response->content) ? $response->content : fopen('php://temp', 'w+'); + } + } catch (\Throwable $e) { + $multi->handlesActivity[$id][] = null; + $multi->handlesActivity[$id][] = $e; + } + } elseif ($chunk instanceof \Throwable) { + $multi->handlesActivity[$id][] = null; + $multi->handlesActivity[$id][] = $chunk; + } else { + // Data or timeout chunk + $multi->handlesActivity[$id][] = $chunk; + } + } + } + + /** + * {@inheritdoc} + */ + protected static function select(ClientState $multi, float $timeout): int + { + return 42; + } + + /** + * Simulates sending the request. + */ + private static function writeRequest(self $response, array $options, ResponseInterface $mock) + { + $onProgress = $options['on_progress'] ?? static function () {}; + $response->info += $mock->getInfo() ?: []; + + // simulate "size_upload" if it is set + if (isset($response->info['size_upload'])) { + $response->info['size_upload'] = 0.0; + } + + // simulate "total_time" if it is not set + if (!isset($response->info['total_time'])) { + $response->info['total_time'] = microtime(true) - $response->info['start_time']; + } + + // "notify" DNS resolution + $onProgress(0, 0, $response->info); + + // consume the request body + if (\is_resource($body = $options['body'] ?? '')) { + $data = stream_get_contents($body); + if (isset($response->info['size_upload'])) { + $response->info['size_upload'] += \strlen($data); + } + } elseif ($body instanceof \Closure) { + while ('' !== $data = $body(16372)) { + if (!\is_string($data)) { + throw new TransportException(sprintf('Return value of the "body" option callback must be string, "%s" returned.', get_debug_type($data))); + } + + // "notify" upload progress + if (isset($response->info['size_upload'])) { + $response->info['size_upload'] += \strlen($data); + } + + $onProgress(0, 0, $response->info); + } + } + } + + /** + * Simulates reading the response. + */ + private static function readResponse(self $response, array $options, ResponseInterface $mock, int &$offset) + { + $onProgress = $options['on_progress'] ?? static function () {}; + + // populate info related to headers + $info = $mock->getInfo() ?: []; + $response->info['http_code'] = ($info['http_code'] ?? 0) ?: $mock->getStatusCode() ?: 200; + $response->addResponseHeaders($info['response_headers'] ?? [], $response->info, $response->headers); + $dlSize = isset($response->headers['content-encoding']) || 'HEAD' === $response->info['http_method'] || \in_array($response->info['http_code'], [204, 304], true) ? 0 : (int) ($response->headers['content-length'][0] ?? 0); + + $response->info = [ + 'start_time' => $response->info['start_time'], + 'user_data' => $response->info['user_data'], + 'max_duration' => $response->info['max_duration'], + 'http_code' => $response->info['http_code'], + ] + $info + $response->info; + + if (null !== $response->info['error']) { + throw new TransportException($response->info['error']); + } + + if (!isset($response->info['total_time'])) { + $response->info['total_time'] = microtime(true) - $response->info['start_time']; + } + + // "notify" headers arrival + $onProgress(0, $dlSize, $response->info); + + // cast response body to activity list + $body = $mock instanceof self ? $mock->body : $mock->getContent(false); + + if (!\is_string($body)) { + try { + foreach ($body as $chunk) { + if ('' === $chunk = (string) $chunk) { + // simulate an idle timeout + $response->body[] = new ErrorChunk($offset, sprintf('Idle timeout reached for "%s".', $response->info['url'])); + } else { + $response->body[] = $chunk; + $offset += \strlen($chunk); + // "notify" download progress + $onProgress($offset, $dlSize, $response->info); + } + } + } catch (\Throwable $e) { + $response->body[] = $e; + } + } elseif ('' !== $body) { + $response->body[] = $body; + $offset = \strlen($body); + } + + if (!isset($response->info['total_time'])) { + $response->info['total_time'] = microtime(true) - $response->info['start_time']; + } + + // "notify" completion + $onProgress($offset, $dlSize, $response->info); + + if ($dlSize && $offset !== $dlSize) { + throw new TransportException(sprintf('Transfer closed with %d bytes remaining to read.', $dlSize - $offset)); + } + } +} diff --git a/src/vendor/symfony/http-client/Response/NativeResponse.php b/src/vendor/symfony/http-client/Response/NativeResponse.php new file mode 100644 index 0000000..c00e946 --- /dev/null +++ b/src/vendor/symfony/http-client/Response/NativeResponse.php @@ -0,0 +1,376 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Response; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpClient\Chunk\FirstChunk; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpClient\Internal\Canary; +use Symfony\Component\HttpClient\Internal\ClientState; +use Symfony\Component\HttpClient\Internal\NativeClientState; +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * @author Nicolas Grekas + * + * @internal + */ +final class NativeResponse implements ResponseInterface, StreamableInterface +{ + use CommonResponseTrait; + use TransportResponseTrait; + + private $context; + private $url; + private $resolver; + private $onProgress; + private $remaining; + private $buffer; + private $multi; + private $pauseExpiry = 0; + + /** + * @internal + */ + public function __construct(NativeClientState $multi, $context, string $url, array $options, array &$info, callable $resolver, ?callable $onProgress, ?LoggerInterface $logger) + { + $this->multi = $multi; + $this->id = $id = (int) $context; + $this->context = $context; + $this->url = $url; + $this->logger = $logger; + $this->timeout = $options['timeout']; + $this->info = &$info; + $this->resolver = $resolver; + $this->onProgress = $onProgress; + $this->inflate = !isset($options['normalized_headers']['accept-encoding']); + $this->shouldBuffer = $options['buffer'] ?? true; + + // Temporary resource to dechunk the response stream + $this->buffer = fopen('php://temp', 'w+'); + + $info['user_data'] = $options['user_data']; + $info['max_duration'] = $options['max_duration']; + ++$multi->responseCount; + + $this->initializer = static function (self $response) { + return null === $response->remaining; + }; + + $pauseExpiry = &$this->pauseExpiry; + $info['pause_handler'] = static function (float $duration) use (&$pauseExpiry) { + $pauseExpiry = 0 < $duration ? microtime(true) + $duration : 0; + }; + + $this->canary = new Canary(static function () use ($multi, $id) { + if (null !== ($host = $multi->openHandles[$id][6] ?? null) && 0 >= --$multi->hosts[$host]) { + unset($multi->hosts[$host]); + } + unset($multi->openHandles[$id], $multi->handlesActivity[$id]); + }); + } + + /** + * {@inheritdoc} + */ + public function getInfo(string $type = null) + { + if (!$info = $this->finalInfo) { + $info = $this->info; + $info['url'] = implode('', $info['url']); + unset($info['size_body'], $info['request_header']); + + if (null === $this->buffer) { + $this->finalInfo = $info; + } + } + + return null !== $type ? $info[$type] ?? null : $info; + } + + public function __destruct() + { + try { + $this->doDestruct(); + } finally { + // Clear the DNS cache when all requests completed + if (0 >= --$this->multi->responseCount) { + $this->multi->responseCount = 0; + $this->multi->dnsCache = []; + } + } + } + + private function open(): void + { + $url = $this->url; + + set_error_handler(function ($type, $msg) use (&$url) { + if (\E_NOTICE !== $type || 'fopen(): Content-type not specified assuming application/x-www-form-urlencoded' !== $msg) { + throw new TransportException($msg); + } + + $this->logger && $this->logger->info(sprintf('%s for "%s".', $msg, $url ?? $this->url)); + }); + + try { + $this->info['start_time'] = microtime(true); + + [$resolver, $url] = ($this->resolver)($this->multi); + + while (true) { + $context = stream_context_get_options($this->context); + + if ($proxy = $context['http']['proxy'] ?? null) { + $this->info['debug'] .= "* Establish HTTP proxy tunnel to {$proxy}\n"; + $this->info['request_header'] = $url; + } else { + $this->info['debug'] .= "* Trying {$this->info['primary_ip']}...\n"; + $this->info['request_header'] = $this->info['url']['path'].$this->info['url']['query']; + } + + $this->info['request_header'] = sprintf("> %s %s HTTP/%s \r\n", $context['http']['method'], $this->info['request_header'], $context['http']['protocol_version']); + $this->info['request_header'] .= implode("\r\n", $context['http']['header'])."\r\n\r\n"; + + if (\array_key_exists('peer_name', $context['ssl']) && null === $context['ssl']['peer_name']) { + unset($context['ssl']['peer_name']); + $this->context = stream_context_create([], ['options' => $context] + stream_context_get_params($this->context)); + } + + // Send request and follow redirects when needed + $this->handle = $h = fopen($url, 'r', false, $this->context); + self::addResponseHeaders(stream_get_meta_data($h)['wrapper_data'], $this->info, $this->headers, $this->info['debug']); + $url = $resolver($this->multi, $this->headers['location'][0] ?? null, $this->context); + + if (null === $url) { + break; + } + + $this->logger && $this->logger->info(sprintf('Redirecting: "%s %s"', $this->info['http_code'], $url ?? $this->url)); + } + } catch (\Throwable $e) { + $this->close(); + $this->multi->handlesActivity[$this->id][] = null; + $this->multi->handlesActivity[$this->id][] = $e; + + return; + } finally { + $this->info['pretransfer_time'] = $this->info['total_time'] = microtime(true) - $this->info['start_time']; + restore_error_handler(); + } + + if (isset($context['ssl']['capture_peer_cert_chain']) && isset(($context = stream_context_get_options($this->context))['ssl']['peer_certificate_chain'])) { + $this->info['peer_certificate_chain'] = $context['ssl']['peer_certificate_chain']; + } + + stream_set_blocking($h, false); + $this->context = $this->resolver = null; + + // Create dechunk buffers + if (isset($this->headers['content-length'])) { + $this->remaining = (int) $this->headers['content-length'][0]; + } elseif ('chunked' === ($this->headers['transfer-encoding'][0] ?? null)) { + stream_filter_append($this->buffer, 'dechunk', \STREAM_FILTER_WRITE); + $this->remaining = -1; + } else { + $this->remaining = -2; + } + + $this->multi->handlesActivity[$this->id] = [new FirstChunk()]; + + if ('HEAD' === $context['http']['method'] || \in_array($this->info['http_code'], [204, 304], true)) { + $this->multi->handlesActivity[$this->id][] = null; + $this->multi->handlesActivity[$this->id][] = null; + + return; + } + + $host = parse_url($this->info['redirect_url'] ?? $this->url, \PHP_URL_HOST); + $this->multi->lastTimeout = null; + $this->multi->openHandles[$this->id] = [&$this->pauseExpiry, $h, $this->buffer, $this->onProgress, &$this->remaining, &$this->info, $host]; + $this->multi->hosts[$host] = 1 + ($this->multi->hosts[$host] ?? 0); + } + + /** + * {@inheritdoc} + */ + private function close(): void + { + $this->canary->cancel(); + $this->handle = $this->buffer = $this->inflate = $this->onProgress = null; + } + + /** + * {@inheritdoc} + */ + private static function schedule(self $response, array &$runningResponses): void + { + if (!isset($runningResponses[$i = $response->multi->id])) { + $runningResponses[$i] = [$response->multi, []]; + } + + $runningResponses[$i][1][$response->id] = $response; + + if (null === $response->buffer) { + // Response already completed + $response->multi->handlesActivity[$response->id][] = null; + $response->multi->handlesActivity[$response->id][] = null !== $response->info['error'] ? new TransportException($response->info['error']) : null; + } + } + + /** + * {@inheritdoc} + * + * @param NativeClientState $multi + */ + private static function perform(ClientState $multi, array &$responses = null): void + { + foreach ($multi->openHandles as $i => [$pauseExpiry, $h, $buffer, $onProgress]) { + if ($pauseExpiry) { + if (microtime(true) < $pauseExpiry) { + continue; + } + + $multi->openHandles[$i][0] = 0; + } + + $hasActivity = false; + $remaining = &$multi->openHandles[$i][4]; + $info = &$multi->openHandles[$i][5]; + $e = null; + + // Read incoming buffer and write it to the dechunk one + try { + if ($remaining && '' !== $data = (string) fread($h, 0 > $remaining ? 16372 : $remaining)) { + fwrite($buffer, $data); + $hasActivity = true; + $multi->sleep = false; + + if (-1 !== $remaining) { + $remaining -= \strlen($data); + } + } + } catch (\Throwable $e) { + $hasActivity = $onProgress = false; + } + + if (!$hasActivity) { + if ($onProgress) { + try { + // Notify the progress callback so that it can e.g. cancel + // the request if the stream is inactive for too long + $info['total_time'] = microtime(true) - $info['start_time']; + $onProgress(); + } catch (\Throwable $e) { + // no-op + } + } + } elseif ('' !== $data = stream_get_contents($buffer, -1, 0)) { + rewind($buffer); + ftruncate($buffer, 0); + + if (null === $e) { + $multi->handlesActivity[$i][] = $data; + } + } + + if (null !== $e || !$remaining || feof($h)) { + // Stream completed + $info['total_time'] = microtime(true) - $info['start_time']; + $info['starttransfer_time'] = $info['starttransfer_time'] ?: $info['total_time']; + + if ($onProgress) { + try { + $onProgress(-1); + } catch (\Throwable $e) { + // no-op + } + } + + if (null === $e) { + if (0 < $remaining) { + $e = new TransportException(sprintf('Transfer closed with %s bytes remaining to read.', $remaining)); + } elseif (-1 === $remaining && fwrite($buffer, '-') && '' !== stream_get_contents($buffer, -1, 0)) { + $e = new TransportException('Transfer closed with outstanding data remaining from chunked response.'); + } + } + + $multi->handlesActivity[$i][] = null; + $multi->handlesActivity[$i][] = $e; + if (null !== ($host = $multi->openHandles[$i][6] ?? null) && 0 >= --$multi->hosts[$host]) { + unset($multi->hosts[$host]); + } + unset($multi->openHandles[$i]); + $multi->sleep = false; + } + } + + if (null === $responses) { + return; + } + + $maxHosts = $multi->maxHostConnections; + + foreach ($responses as $i => $response) { + if (null !== $response->remaining || null === $response->buffer) { + continue; + } + + if ($response->pauseExpiry && microtime(true) < $response->pauseExpiry) { + // Create empty open handles to tell we still have pending requests + $multi->openHandles[$i] = [\INF, null, null, null]; + } elseif ($maxHosts && $maxHosts > ($multi->hosts[parse_url($response->url, \PHP_URL_HOST)] ?? 0)) { + // Open the next pending request - this is a blocking operation so we do only one of them + $response->open(); + $multi->sleep = false; + self::perform($multi); + $maxHosts = 0; + } + } + } + + /** + * {@inheritdoc} + * + * @param NativeClientState $multi + */ + private static function select(ClientState $multi, float $timeout): int + { + if (!$multi->sleep = !$multi->sleep) { + return -1; + } + + $_ = $handles = []; + $now = null; + + foreach ($multi->openHandles as [$pauseExpiry, $h]) { + if (null === $h) { + continue; + } + + if ($pauseExpiry && ($now ?? $now = microtime(true)) < $pauseExpiry) { + $timeout = min($timeout, $pauseExpiry - $now); + continue; + } + + $handles[] = $h; + } + + if (!$handles) { + usleep((int) (1E6 * $timeout)); + + return 0; + } + + return stream_select($handles, $_, $_, (int) $timeout, (int) (1E6 * ($timeout - (int) $timeout))); + } +} diff --git a/src/vendor/symfony/http-client/Response/ResponseStream.php b/src/vendor/symfony/http-client/Response/ResponseStream.php new file mode 100644 index 0000000..f86d2d4 --- /dev/null +++ b/src/vendor/symfony/http-client/Response/ResponseStream.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Response; + +use Symfony\Contracts\HttpClient\ChunkInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\HttpClient\ResponseStreamInterface; + +/** + * @author Nicolas Grekas + */ +final class ResponseStream implements ResponseStreamInterface +{ + private $generator; + + public function __construct(\Generator $generator) + { + $this->generator = $generator; + } + + public function key(): ResponseInterface + { + return $this->generator->key(); + } + + public function current(): ChunkInterface + { + return $this->generator->current(); + } + + public function next(): void + { + $this->generator->next(); + } + + public function rewind(): void + { + $this->generator->rewind(); + } + + public function valid(): bool + { + return $this->generator->valid(); + } +} diff --git a/src/vendor/symfony/http-client/Response/StreamWrapper.php b/src/vendor/symfony/http-client/Response/StreamWrapper.php new file mode 100644 index 0000000..50a7c36 --- /dev/null +++ b/src/vendor/symfony/http-client/Response/StreamWrapper.php @@ -0,0 +1,313 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Response; + +use Symfony\Contracts\HttpClient\Exception\ExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * Allows turning ResponseInterface instances to PHP streams. + * + * @author Nicolas Grekas + */ +class StreamWrapper +{ + /** @var resource|null */ + public $context; + + /** @var HttpClientInterface */ + private $client; + + /** @var ResponseInterface */ + private $response; + + /** @var resource|string|null */ + private $content; + + /** @var resource|null */ + private $handle; + + private $blocking = true; + private $timeout; + private $eof = false; + private $offset = 0; + + /** + * Creates a PHP stream resource from a ResponseInterface. + * + * @return resource + */ + public static function createResource(ResponseInterface $response, HttpClientInterface $client = null) + { + if ($response instanceof StreamableInterface) { + $stack = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS, 2); + + if ($response !== ($stack[1]['object'] ?? null)) { + return $response->toStream(false); + } + } + + if (null === $client && !method_exists($response, 'stream')) { + throw new \InvalidArgumentException(sprintf('Providing a client to "%s()" is required when the response doesn\'t have any "stream()" method.', __CLASS__)); + } + + static $registered = false; + + if (!$registered = $registered || stream_wrapper_register(strtr(__CLASS__, '\\', '-'), __CLASS__)) { + throw new \RuntimeException(error_get_last()['message'] ?? 'Registering the "symfony" stream wrapper failed.'); + } + + $context = [ + 'client' => $client ?? $response, + 'response' => $response, + ]; + + return fopen(strtr(__CLASS__, '\\', '-').'://'.$response->getInfo('url'), 'r', false, stream_context_create(['symfony' => $context])); + } + + public function getResponse(): ResponseInterface + { + return $this->response; + } + + /** + * @param resource|callable|null $handle The resource handle that should be monitored when + * stream_select() is used on the created stream + * @param resource|null $content The seekable resource where the response body is buffered + */ + public function bindHandles(&$handle, &$content): void + { + $this->handle = &$handle; + $this->content = &$content; + $this->offset = null; + } + + public function stream_open(string $path, string $mode, int $options): bool + { + if ('r' !== $mode) { + if ($options & \STREAM_REPORT_ERRORS) { + trigger_error(sprintf('Invalid mode "%s": only "r" is supported.', $mode), \E_USER_WARNING); + } + + return false; + } + + $context = stream_context_get_options($this->context)['symfony'] ?? null; + $this->client = $context['client'] ?? null; + $this->response = $context['response'] ?? null; + $this->context = null; + + if (null !== $this->client && null !== $this->response) { + return true; + } + + if ($options & \STREAM_REPORT_ERRORS) { + trigger_error('Missing options "client" or "response" in "symfony" stream context.', \E_USER_WARNING); + } + + return false; + } + + public function stream_read(int $count) + { + if (\is_resource($this->content)) { + // Empty the internal activity list + foreach ($this->client->stream([$this->response], 0) as $chunk) { + try { + if (!$chunk->isTimeout() && $chunk->isFirst()) { + $this->response->getStatusCode(); // ignore 3/4/5xx + } + } catch (ExceptionInterface $e) { + trigger_error($e->getMessage(), \E_USER_WARNING); + + return false; + } + } + + if (0 !== fseek($this->content, $this->offset ?? 0)) { + return false; + } + + if ('' !== $data = fread($this->content, $count)) { + fseek($this->content, 0, \SEEK_END); + $this->offset += \strlen($data); + + return $data; + } + } + + if (\is_string($this->content)) { + if (\strlen($this->content) <= $count) { + $data = $this->content; + $this->content = null; + } else { + $data = substr($this->content, 0, $count); + $this->content = substr($this->content, $count); + } + $this->offset += \strlen($data); + + return $data; + } + + foreach ($this->client->stream([$this->response], $this->blocking ? $this->timeout : 0) as $chunk) { + try { + $this->eof = true; + $this->eof = !$chunk->isTimeout(); + + if (!$this->eof && !$this->blocking) { + return ''; + } + + $this->eof = $chunk->isLast(); + + if ($chunk->isFirst()) { + $this->response->getStatusCode(); // ignore 3/4/5xx + } + + if ('' !== $data = $chunk->getContent()) { + if (\strlen($data) > $count) { + if (null === $this->content) { + $this->content = substr($data, $count); + } + $data = substr($data, 0, $count); + } + $this->offset += \strlen($data); + + return $data; + } + } catch (ExceptionInterface $e) { + trigger_error($e->getMessage(), \E_USER_WARNING); + + return false; + } + } + + return ''; + } + + public function stream_set_option(int $option, int $arg1, ?int $arg2): bool + { + if (\STREAM_OPTION_BLOCKING === $option) { + $this->blocking = (bool) $arg1; + } elseif (\STREAM_OPTION_READ_TIMEOUT === $option) { + $this->timeout = $arg1 + $arg2 / 1e6; + } else { + return false; + } + + return true; + } + + public function stream_tell(): int + { + return $this->offset ?? 0; + } + + public function stream_eof(): bool + { + return $this->eof && !\is_string($this->content); + } + + public function stream_seek(int $offset, int $whence = \SEEK_SET): bool + { + if (null === $this->content && null === $this->offset) { + $this->response->getStatusCode(); + $this->offset = 0; + } + + if (!\is_resource($this->content) || 0 !== fseek($this->content, 0, \SEEK_END)) { + return false; + } + + $size = ftell($this->content); + + if (\SEEK_CUR === $whence) { + $offset += $this->offset ?? 0; + } + + if (\SEEK_END === $whence || $size < $offset) { + foreach ($this->client->stream([$this->response]) as $chunk) { + try { + if ($chunk->isFirst()) { + $this->response->getStatusCode(); // ignore 3/4/5xx + } + + // Chunks are buffered in $this->content already + $size += \strlen($chunk->getContent()); + + if (\SEEK_END !== $whence && $offset <= $size) { + break; + } + } catch (ExceptionInterface $e) { + trigger_error($e->getMessage(), \E_USER_WARNING); + + return false; + } + } + + if (\SEEK_END === $whence) { + $offset += $size; + } + } + + if (0 <= $offset && $offset <= $size) { + $this->eof = false; + $this->offset = $offset; + + return true; + } + + return false; + } + + public function stream_cast(int $castAs) + { + if (\STREAM_CAST_FOR_SELECT === $castAs) { + $this->response->getHeaders(false); + + return (\is_callable($this->handle) ? ($this->handle)() : $this->handle) ?? false; + } + + return false; + } + + public function stream_stat(): array + { + try { + $headers = $this->response->getHeaders(false); + } catch (ExceptionInterface $e) { + trigger_error($e->getMessage(), \E_USER_WARNING); + $headers = []; + } + + return [ + 'dev' => 0, + 'ino' => 0, + 'mode' => 33060, + 'nlink' => 0, + 'uid' => 0, + 'gid' => 0, + 'rdev' => 0, + 'size' => (int) ($headers['content-length'][0] ?? -1), + 'atime' => 0, + 'mtime' => strtotime($headers['last-modified'][0] ?? '') ?: 0, + 'ctime' => 0, + 'blksize' => 0, + 'blocks' => 0, + ]; + } + + private function __construct() + { + } +} diff --git a/src/vendor/symfony/http-client/Response/StreamableInterface.php b/src/vendor/symfony/http-client/Response/StreamableInterface.php new file mode 100644 index 0000000..eb1f933 --- /dev/null +++ b/src/vendor/symfony/http-client/Response/StreamableInterface.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Response; + +use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; + +/** + * @author Nicolas Grekas + */ +interface StreamableInterface +{ + /** + * Casts the response to a PHP stream resource. + * + * @return resource + * + * @throws TransportExceptionInterface When a network error occurs + * @throws RedirectionExceptionInterface On a 3xx when $throw is true and the "max_redirects" option has been reached + * @throws ClientExceptionInterface On a 4xx when $throw is true + * @throws ServerExceptionInterface On a 5xx when $throw is true + */ + public function toStream(bool $throw = true); +} diff --git a/src/vendor/symfony/http-client/Response/TraceableResponse.php b/src/vendor/symfony/http-client/Response/TraceableResponse.php new file mode 100644 index 0000000..d656c0a --- /dev/null +++ b/src/vendor/symfony/http-client/Response/TraceableResponse.php @@ -0,0 +1,219 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Response; + +use Symfony\Component\HttpClient\Chunk\ErrorChunk; +use Symfony\Component\HttpClient\Exception\ClientException; +use Symfony\Component\HttpClient\Exception\RedirectionException; +use Symfony\Component\HttpClient\Exception\ServerException; +use Symfony\Component\HttpClient\TraceableHttpClient; +use Symfony\Component\Stopwatch\StopwatchEvent; +use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class TraceableResponse implements ResponseInterface, StreamableInterface +{ + private $client; + private $response; + private $content; + private $event; + + public function __construct(HttpClientInterface $client, ResponseInterface $response, &$content, StopwatchEvent $event = null) + { + $this->client = $client; + $this->response = $response; + $this->content = &$content; + $this->event = $event; + } + + public function __sleep(): array + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + try { + $this->response->__destruct(); + } finally { + if ($this->event && $this->event->isStarted()) { + $this->event->stop(); + } + } + } + + public function getStatusCode(): int + { + try { + return $this->response->getStatusCode(); + } finally { + if ($this->event && $this->event->isStarted()) { + $this->event->lap(); + } + } + } + + public function getHeaders(bool $throw = true): array + { + try { + return $this->response->getHeaders($throw); + } finally { + if ($this->event && $this->event->isStarted()) { + $this->event->lap(); + } + } + } + + public function getContent(bool $throw = true): string + { + try { + if (false === $this->content) { + return $this->response->getContent($throw); + } + + return $this->content = $this->response->getContent(false); + } finally { + if ($this->event && $this->event->isStarted()) { + $this->event->stop(); + } + if ($throw) { + $this->checkStatusCode($this->response->getStatusCode()); + } + } + } + + public function toArray(bool $throw = true): array + { + try { + if (false === $this->content) { + return $this->response->toArray($throw); + } + + return $this->content = $this->response->toArray(false); + } finally { + if ($this->event && $this->event->isStarted()) { + $this->event->stop(); + } + if ($throw) { + $this->checkStatusCode($this->response->getStatusCode()); + } + } + } + + public function cancel(): void + { + $this->response->cancel(); + + if ($this->event && $this->event->isStarted()) { + $this->event->stop(); + } + } + + public function getInfo(string $type = null) + { + return $this->response->getInfo($type); + } + + /** + * Casts the response to a PHP stream resource. + * + * @return resource + * + * @throws TransportExceptionInterface When a network error occurs + * @throws RedirectionExceptionInterface On a 3xx when $throw is true and the "max_redirects" option has been reached + * @throws ClientExceptionInterface On a 4xx when $throw is true + * @throws ServerExceptionInterface On a 5xx when $throw is true + */ + public function toStream(bool $throw = true) + { + if ($throw) { + // Ensure headers arrived + $this->response->getHeaders(true); + } + + if ($this->response instanceof StreamableInterface) { + return $this->response->toStream(false); + } + + return StreamWrapper::createResource($this->response, $this->client); + } + + /** + * @internal + */ + public static function stream(HttpClientInterface $client, iterable $responses, ?float $timeout): \Generator + { + $wrappedResponses = []; + $traceableMap = new \SplObjectStorage(); + + foreach ($responses as $r) { + if (!$r instanceof self) { + throw new \TypeError(sprintf('"%s::stream()" expects parameter 1 to be an iterable of TraceableResponse objects, "%s" given.', TraceableHttpClient::class, get_debug_type($r))); + } + + $traceableMap[$r->response] = $r; + $wrappedResponses[] = $r->response; + if ($r->event && !$r->event->isStarted()) { + $r->event->start(); + } + } + + foreach ($client->stream($wrappedResponses, $timeout) as $r => $chunk) { + if ($traceableMap[$r]->event && $traceableMap[$r]->event->isStarted()) { + try { + if ($chunk->isTimeout() || !$chunk->isLast()) { + $traceableMap[$r]->event->lap(); + } else { + $traceableMap[$r]->event->stop(); + } + } catch (TransportExceptionInterface $e) { + $traceableMap[$r]->event->stop(); + if ($chunk instanceof ErrorChunk) { + $chunk->didThrow(false); + } else { + $chunk = new ErrorChunk($chunk->getOffset(), $e); + } + } + } + yield $traceableMap[$r] => $chunk; + } + } + + private function checkStatusCode(int $code) + { + if (500 <= $code) { + throw new ServerException($this); + } + + if (400 <= $code) { + throw new ClientException($this); + } + + if (300 <= $code) { + throw new RedirectionException($this); + } + } +} diff --git a/src/vendor/symfony/http-client/Response/TransportResponseTrait.php b/src/vendor/symfony/http-client/Response/TransportResponseTrait.php new file mode 100644 index 0000000..566d61e --- /dev/null +++ b/src/vendor/symfony/http-client/Response/TransportResponseTrait.php @@ -0,0 +1,312 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Response; + +use Symfony\Component\HttpClient\Chunk\DataChunk; +use Symfony\Component\HttpClient\Chunk\ErrorChunk; +use Symfony\Component\HttpClient\Chunk\FirstChunk; +use Symfony\Component\HttpClient\Chunk\LastChunk; +use Symfony\Component\HttpClient\Exception\TransportException; +use Symfony\Component\HttpClient\Internal\ClientState; + +/** + * Implements common logic for transport-level response classes. + * + * @author Nicolas Grekas + * + * @internal + */ +trait TransportResponseTrait +{ + private $canary; + private $headers = []; + private $info = [ + 'response_headers' => [], + 'http_code' => 0, + 'error' => null, + 'canceled' => false, + ]; + + /** @var object|resource */ + private $handle; + private $id; + private $timeout = 0; + private $inflate; + private $finalInfo; + private $logger; + + /** + * {@inheritdoc} + */ + public function getStatusCode(): int + { + if ($this->initializer) { + self::initialize($this); + } + + return $this->info['http_code']; + } + + /** + * {@inheritdoc} + */ + public function getHeaders(bool $throw = true): array + { + if ($this->initializer) { + self::initialize($this); + } + + if ($throw) { + $this->checkStatusCode(); + } + + return $this->headers; + } + + /** + * {@inheritdoc} + */ + public function cancel(): void + { + $this->info['canceled'] = true; + $this->info['error'] = 'Response has been canceled.'; + $this->close(); + } + + /** + * Closes the response and all its network handles. + */ + protected function close(): void + { + $this->canary->cancel(); + $this->inflate = null; + } + + /** + * Adds pending responses to the activity list. + */ + abstract protected static function schedule(self $response, array &$runningResponses): void; + + /** + * Performs all pending non-blocking operations. + */ + abstract protected static function perform(ClientState $multi, array &$responses): void; + + /** + * Waits for network activity. + */ + abstract protected static function select(ClientState $multi, float $timeout): int; + + private static function addResponseHeaders(array $responseHeaders, array &$info, array &$headers, string &$debug = ''): void + { + foreach ($responseHeaders as $h) { + if (11 <= \strlen($h) && '/' === $h[4] && preg_match('#^HTTP/\d+(?:\.\d+)? (\d\d\d)(?: |$)#', $h, $m)) { + if ($headers) { + $debug .= "< \r\n"; + $headers = []; + } + $info['http_code'] = (int) $m[1]; + } elseif (2 === \count($m = explode(':', $h, 2))) { + $headers[strtolower($m[0])][] = ltrim($m[1]); + } + + $debug .= "< {$h}\r\n"; + $info['response_headers'][] = $h; + } + + $debug .= "< \r\n"; + } + + /** + * Ensures the request is always sent and that the response code was checked. + */ + private function doDestruct() + { + $this->shouldBuffer = true; + + if ($this->initializer && null === $this->info['error']) { + self::initialize($this); + $this->checkStatusCode(); + } + } + + /** + * Implements an event loop based on a buffer activity queue. + * + * @param iterable $responses + * + * @internal + */ + public static function stream(iterable $responses, float $timeout = null): \Generator + { + $runningResponses = []; + + foreach ($responses as $response) { + self::schedule($response, $runningResponses); + } + + $lastActivity = microtime(true); + $elapsedTimeout = 0; + + if ($fromLastTimeout = 0.0 === $timeout && '-0' === (string) $timeout) { + $timeout = null; + } elseif ($fromLastTimeout = 0 > $timeout) { + $timeout = -$timeout; + } + + while (true) { + $hasActivity = false; + $timeoutMax = 0; + $timeoutMin = $timeout ?? \INF; + + /** @var ClientState $multi */ + foreach ($runningResponses as $i => [$multi]) { + $responses = &$runningResponses[$i][1]; + self::perform($multi, $responses); + + foreach ($responses as $j => $response) { + $timeoutMax = $timeout ?? max($timeoutMax, $response->timeout); + $timeoutMin = min($timeoutMin, $response->timeout, 1); + $chunk = false; + + if ($fromLastTimeout && null !== $multi->lastTimeout) { + $elapsedTimeout = microtime(true) - $multi->lastTimeout; + } + + if (isset($multi->handlesActivity[$j])) { + $multi->lastTimeout = null; + } elseif (!isset($multi->openHandles[$j])) { + unset($responses[$j]); + continue; + } elseif ($elapsedTimeout >= $timeoutMax) { + $multi->handlesActivity[$j] = [new ErrorChunk($response->offset, sprintf('Idle timeout reached for "%s".', $response->getInfo('url')))]; + $multi->lastTimeout ?? $multi->lastTimeout = $lastActivity; + } else { + continue; + } + + while ($multi->handlesActivity[$j] ?? false) { + $hasActivity = true; + $elapsedTimeout = 0; + + if (\is_string($chunk = array_shift($multi->handlesActivity[$j]))) { + if (null !== $response->inflate && false === $chunk = @inflate_add($response->inflate, $chunk)) { + $multi->handlesActivity[$j] = [null, new TransportException(sprintf('Error while processing content unencoding for "%s".', $response->getInfo('url')))]; + continue; + } + + if ('' !== $chunk && null !== $response->content && \strlen($chunk) !== fwrite($response->content, $chunk)) { + $multi->handlesActivity[$j] = [null, new TransportException(sprintf('Failed writing %d bytes to the response buffer.', \strlen($chunk)))]; + continue; + } + + $chunkLen = \strlen($chunk); + $chunk = new DataChunk($response->offset, $chunk); + $response->offset += $chunkLen; + } elseif (null === $chunk) { + $e = $multi->handlesActivity[$j][0]; + unset($responses[$j], $multi->handlesActivity[$j]); + $response->close(); + + if (null !== $e) { + $response->info['error'] = $e->getMessage(); + + if ($e instanceof \Error) { + throw $e; + } + + $chunk = new ErrorChunk($response->offset, $e); + } else { + if (0 === $response->offset && null === $response->content) { + $response->content = fopen('php://memory', 'w+'); + } + + $chunk = new LastChunk($response->offset); + } + } elseif ($chunk instanceof ErrorChunk) { + unset($responses[$j]); + $elapsedTimeout = $timeoutMax; + } elseif ($chunk instanceof FirstChunk) { + if ($response->logger) { + $info = $response->getInfo(); + $response->logger->info(sprintf('Response: "%s %s"', $info['http_code'], $info['url'])); + } + + $response->inflate = \extension_loaded('zlib') && $response->inflate && 'gzip' === ($response->headers['content-encoding'][0] ?? null) ? inflate_init(\ZLIB_ENCODING_GZIP) : null; + + if ($response->shouldBuffer instanceof \Closure) { + try { + $response->shouldBuffer = ($response->shouldBuffer)($response->headers); + + if (null !== $response->info['error']) { + throw new TransportException($response->info['error']); + } + } catch (\Throwable $e) { + $response->close(); + $multi->handlesActivity[$j] = [null, $e]; + } + } + + if (true === $response->shouldBuffer) { + $response->content = fopen('php://temp', 'w+'); + } elseif (\is_resource($response->shouldBuffer)) { + $response->content = $response->shouldBuffer; + } + $response->shouldBuffer = null; + + yield $response => $chunk; + + if ($response->initializer && null === $response->info['error']) { + // Ensure the HTTP status code is always checked + $response->getHeaders(true); + } + + continue; + } + + yield $response => $chunk; + } + + unset($multi->handlesActivity[$j]); + + if ($chunk instanceof ErrorChunk && !$chunk->didThrow()) { + // Ensure transport exceptions are always thrown + $chunk->getContent(); + } + } + + if (!$responses) { + unset($runningResponses[$i]); + } + + // Prevent memory leaks + $multi->handlesActivity = $multi->handlesActivity ?: []; + $multi->openHandles = $multi->openHandles ?: []; + } + + if (!$runningResponses) { + break; + } + + if ($hasActivity) { + $lastActivity = microtime(true); + continue; + } + + if (-1 === self::select($multi, min($timeoutMin, $timeoutMax - $elapsedTimeout))) { + usleep(min(500, 1E6 * $timeoutMin)); + } + + $elapsedTimeout = microtime(true) - $lastActivity; + } + } +} diff --git a/src/vendor/symfony/http-client/Retry/GenericRetryStrategy.php b/src/vendor/symfony/http-client/Retry/GenericRetryStrategy.php new file mode 100644 index 0000000..3241a5e --- /dev/null +++ b/src/vendor/symfony/http-client/Retry/GenericRetryStrategy.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Retry; + +use Symfony\Component\HttpClient\Exception\InvalidArgumentException; +use Symfony\Component\HttpClient\Response\AsyncContext; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; + +/** + * Decides to retry the request when HTTP status codes belong to the given list of codes. + * + * @author Jérémy Derussé + */ +class GenericRetryStrategy implements RetryStrategyInterface +{ + public const IDEMPOTENT_METHODS = ['GET', 'HEAD', 'PUT', 'DELETE', 'OPTIONS', 'TRACE']; + public const DEFAULT_RETRY_STATUS_CODES = [ + 0 => self::IDEMPOTENT_METHODS, // for transport exceptions + 423, + 425, + 429, + 500 => self::IDEMPOTENT_METHODS, + 502, + 503, + 504 => self::IDEMPOTENT_METHODS, + 507 => self::IDEMPOTENT_METHODS, + 510 => self::IDEMPOTENT_METHODS, + ]; + + private $statusCodes; + private $delayMs; + private $multiplier; + private $maxDelayMs; + private $jitter; + + /** + * @param array $statusCodes List of HTTP status codes that trigger a retry + * @param int $delayMs Amount of time to delay (or the initial value when multiplier is used) + * @param float $multiplier Multiplier to apply to the delay each time a retry occurs + * @param int $maxDelayMs Maximum delay to allow (0 means no maximum) + * @param float $jitter Probability of randomness int delay (0 = none, 1 = 100% random) + */ + public function __construct(array $statusCodes = self::DEFAULT_RETRY_STATUS_CODES, int $delayMs = 1000, float $multiplier = 2.0, int $maxDelayMs = 0, float $jitter = 0.1) + { + $this->statusCodes = $statusCodes; + + if ($delayMs < 0) { + throw new InvalidArgumentException(sprintf('Delay must be greater than or equal to zero: "%s" given.', $delayMs)); + } + $this->delayMs = $delayMs; + + if ($multiplier < 1) { + throw new InvalidArgumentException(sprintf('Multiplier must be greater than or equal to one: "%s" given.', $multiplier)); + } + $this->multiplier = $multiplier; + + if ($maxDelayMs < 0) { + throw new InvalidArgumentException(sprintf('Max delay must be greater than or equal to zero: "%s" given.', $maxDelayMs)); + } + $this->maxDelayMs = $maxDelayMs; + + if ($jitter < 0 || $jitter > 1) { + throw new InvalidArgumentException(sprintf('Jitter must be between 0 and 1: "%s" given.', $jitter)); + } + $this->jitter = $jitter; + } + + public function shouldRetry(AsyncContext $context, ?string $responseContent, ?TransportExceptionInterface $exception): ?bool + { + $statusCode = $context->getStatusCode(); + if (\in_array($statusCode, $this->statusCodes, true)) { + return true; + } + if (isset($this->statusCodes[$statusCode]) && \is_array($this->statusCodes[$statusCode])) { + return \in_array($context->getInfo('http_method'), $this->statusCodes[$statusCode], true); + } + if (null === $exception) { + return false; + } + + if (\in_array(0, $this->statusCodes, true)) { + return true; + } + if (isset($this->statusCodes[0]) && \is_array($this->statusCodes[0])) { + return \in_array($context->getInfo('http_method'), $this->statusCodes[0], true); + } + + return false; + } + + public function getDelay(AsyncContext $context, ?string $responseContent, ?TransportExceptionInterface $exception): int + { + $delay = $this->delayMs * $this->multiplier ** $context->getInfo('retry_count'); + + if ($this->jitter > 0) { + $randomness = (int) ($delay * $this->jitter); + $delay = $delay + random_int(-$randomness, +$randomness); + } + + if ($delay > $this->maxDelayMs && 0 !== $this->maxDelayMs) { + return $this->maxDelayMs; + } + + return (int) $delay; + } +} diff --git a/src/vendor/symfony/http-client/Retry/RetryStrategyInterface.php b/src/vendor/symfony/http-client/Retry/RetryStrategyInterface.php new file mode 100644 index 0000000..2576433 --- /dev/null +++ b/src/vendor/symfony/http-client/Retry/RetryStrategyInterface.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient\Retry; + +use Symfony\Component\HttpClient\Response\AsyncContext; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; + +/** + * @author Jérémy Derussé + * @author Nicolas Grekas + */ +interface RetryStrategyInterface +{ + /** + * Returns whether the request should be retried. + * + * @param ?string $responseContent Null is passed when the body did not arrive yet + * + * @return bool|null Returns null to signal that the body is required to take a decision + */ + public function shouldRetry(AsyncContext $context, ?string $responseContent, ?TransportExceptionInterface $exception): ?bool; + + /** + * Returns the time to wait in milliseconds. + */ + public function getDelay(AsyncContext $context, ?string $responseContent, ?TransportExceptionInterface $exception): int; +} diff --git a/src/vendor/symfony/http-client/RetryableHttpClient.php b/src/vendor/symfony/http-client/RetryableHttpClient.php new file mode 100644 index 0000000..bec1378 --- /dev/null +++ b/src/vendor/symfony/http-client/RetryableHttpClient.php @@ -0,0 +1,171 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; +use Symfony\Component\HttpClient\Response\AsyncContext; +use Symfony\Component\HttpClient\Response\AsyncResponse; +use Symfony\Component\HttpClient\Retry\GenericRetryStrategy; +use Symfony\Component\HttpClient\Retry\RetryStrategyInterface; +use Symfony\Contracts\HttpClient\ChunkInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * Automatically retries failing HTTP requests. + * + * @author Jérémy Derussé + */ +class RetryableHttpClient implements HttpClientInterface, ResetInterface +{ + use AsyncDecoratorTrait; + + private $strategy; + private $maxRetries; + private $logger; + + /** + * @param int $maxRetries The maximum number of times to retry + */ + public function __construct(HttpClientInterface $client, RetryStrategyInterface $strategy = null, int $maxRetries = 3, LoggerInterface $logger = null) + { + $this->client = $client; + $this->strategy = $strategy ?? new GenericRetryStrategy(); + $this->maxRetries = $maxRetries; + $this->logger = $logger ?? new NullLogger(); + } + + public function request(string $method, string $url, array $options = []): ResponseInterface + { + if ($this->maxRetries <= 0) { + return new AsyncResponse($this->client, $method, $url, $options); + } + + $retryCount = 0; + $content = ''; + $firstChunk = null; + + return new AsyncResponse($this->client, $method, $url, $options, function (ChunkInterface $chunk, AsyncContext $context) use ($method, $url, $options, &$retryCount, &$content, &$firstChunk) { + $exception = null; + try { + if ($context->getInfo('canceled') || $chunk->isTimeout() || null !== $chunk->getInformationalStatus()) { + yield $chunk; + + return; + } + } catch (TransportExceptionInterface $exception) { + // catch TransportExceptionInterface to send it to the strategy + } + if (null !== $exception) { + // always retry request that fail to resolve DNS + if ('' !== $context->getInfo('primary_ip')) { + $shouldRetry = $this->strategy->shouldRetry($context, null, $exception); + if (null === $shouldRetry) { + throw new \LogicException(sprintf('The "%s::shouldRetry()" method must not return null when called with an exception.', \get_class($this->strategy))); + } + + if (false === $shouldRetry) { + yield from $this->passthru($context, $firstChunk, $content, $chunk); + + return; + } + } + } elseif ($chunk->isFirst()) { + if (false === $shouldRetry = $this->strategy->shouldRetry($context, null, null)) { + yield from $this->passthru($context, $firstChunk, $content, $chunk); + + return; + } + + // Body is needed to decide + if (null === $shouldRetry) { + $firstChunk = $chunk; + $content = ''; + + return; + } + } else { + if (!$chunk->isLast()) { + $content .= $chunk->getContent(); + + return; + } + + if (null === $shouldRetry = $this->strategy->shouldRetry($context, $content, null)) { + throw new \LogicException(sprintf('The "%s::shouldRetry()" method must not return null when called with a body.', \get_class($this->strategy))); + } + + if (false === $shouldRetry) { + yield from $this->passthru($context, $firstChunk, $content, $chunk); + + return; + } + } + + $context->getResponse()->cancel(); + + $delay = $this->getDelayFromHeader($context->getHeaders()) ?? $this->strategy->getDelay($context, !$exception && $chunk->isLast() ? $content : null, $exception); + ++$retryCount; + $content = ''; + $firstChunk = null; + + $this->logger->info('Try #{count} after {delay}ms'.($exception ? ': '.$exception->getMessage() : ', status code: '.$context->getStatusCode()), [ + 'count' => $retryCount, + 'delay' => $delay, + ]); + + $context->setInfo('retry_count', $retryCount); + $context->replaceRequest($method, $url, $options); + $context->pause($delay / 1000); + + if ($retryCount >= $this->maxRetries) { + $context->passthru(); + } + }); + } + + private function getDelayFromHeader(array $headers): ?int + { + if (null !== $after = $headers['retry-after'][0] ?? null) { + if (is_numeric($after)) { + return (int) ($after * 1000); + } + + if (false !== $time = strtotime($after)) { + return max(0, $time - time()) * 1000; + } + } + + return null; + } + + private function passthru(AsyncContext $context, ?ChunkInterface $firstChunk, string &$content, ChunkInterface $lastChunk): \Generator + { + $context->passthru(); + + if (null !== $firstChunk) { + yield $firstChunk; + } + + if ('' !== $content) { + $chunk = $context->createChunk($content); + $content = ''; + + yield $chunk; + } + + yield $lastChunk; + } +} diff --git a/src/vendor/symfony/http-client/ScopingHttpClient.php b/src/vendor/symfony/http-client/ScopingHttpClient.php new file mode 100644 index 0000000..85fa26a --- /dev/null +++ b/src/vendor/symfony/http-client/ScopingHttpClient.php @@ -0,0 +1,131 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpClient\Exception\InvalidArgumentException; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\HttpClient\ResponseStreamInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * Auto-configure the default options based on the requested URL. + * + * @author Anthony Martin + */ +class ScopingHttpClient implements HttpClientInterface, ResetInterface, LoggerAwareInterface +{ + use HttpClientTrait; + + private $client; + private $defaultOptionsByRegexp; + private $defaultRegexp; + + public function __construct(HttpClientInterface $client, array $defaultOptionsByRegexp, string $defaultRegexp = null) + { + $this->client = $client; + $this->defaultOptionsByRegexp = $defaultOptionsByRegexp; + $this->defaultRegexp = $defaultRegexp; + + if (null !== $defaultRegexp && !isset($defaultOptionsByRegexp[$defaultRegexp])) { + throw new InvalidArgumentException(sprintf('No options are mapped to the provided "%s" default regexp.', $defaultRegexp)); + } + } + + public static function forBaseUri(HttpClientInterface $client, string $baseUri, array $defaultOptions = [], string $regexp = null): self + { + if (null === $regexp) { + $regexp = preg_quote(implode('', self::resolveUrl(self::parseUrl('.'), self::parseUrl($baseUri)))); + } + + $defaultOptions['base_uri'] = $baseUri; + + return new self($client, [$regexp => $defaultOptions], $regexp); + } + + /** + * {@inheritdoc} + */ + public function request(string $method, string $url, array $options = []): ResponseInterface + { + $e = null; + $url = self::parseUrl($url, $options['query'] ?? []); + + if (\is_string($options['base_uri'] ?? null)) { + $options['base_uri'] = self::parseUrl($options['base_uri']); + } + + try { + $url = implode('', self::resolveUrl($url, $options['base_uri'] ?? null)); + } catch (InvalidArgumentException $e) { + if (null === $this->defaultRegexp) { + throw $e; + } + + $defaultOptions = $this->defaultOptionsByRegexp[$this->defaultRegexp]; + $options = self::mergeDefaultOptions($options, $defaultOptions, true); + if (\is_string($options['base_uri'] ?? null)) { + $options['base_uri'] = self::parseUrl($options['base_uri']); + } + $url = implode('', self::resolveUrl($url, $options['base_uri'] ?? null, $defaultOptions['query'] ?? [])); + } + + foreach ($this->defaultOptionsByRegexp as $regexp => $defaultOptions) { + if (preg_match("{{$regexp}}A", $url)) { + if (null === $e || $regexp !== $this->defaultRegexp) { + $options = self::mergeDefaultOptions($options, $defaultOptions, true); + } + break; + } + } + + return $this->client->request($method, $url, $options); + } + + /** + * {@inheritdoc} + */ + public function stream($responses, float $timeout = null): ResponseStreamInterface + { + return $this->client->stream($responses, $timeout); + } + + public function reset() + { + if ($this->client instanceof ResetInterface) { + $this->client->reset(); + } + } + + /** + * {@inheritdoc} + */ + public function setLogger(LoggerInterface $logger): void + { + if ($this->client instanceof LoggerAwareInterface) { + $this->client->setLogger($logger); + } + } + + /** + * {@inheritdoc} + */ + public function withOptions(array $options): self + { + $clone = clone $this; + $clone->client = $this->client->withOptions($options); + + return $clone; + } +} diff --git a/src/vendor/symfony/http-client/TraceableHttpClient.php b/src/vendor/symfony/http-client/TraceableHttpClient.php new file mode 100644 index 0000000..76c9282 --- /dev/null +++ b/src/vendor/symfony/http-client/TraceableHttpClient.php @@ -0,0 +1,120 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpClient; + +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpClient\Response\ResponseStream; +use Symfony\Component\HttpClient\Response\TraceableResponse; +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\HttpClient\ResponseStreamInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * @author Jérémy Romey + */ +final class TraceableHttpClient implements HttpClientInterface, ResetInterface, LoggerAwareInterface +{ + private $client; + private $stopwatch; + private $tracedRequests; + + public function __construct(HttpClientInterface $client, Stopwatch $stopwatch = null) + { + $this->client = $client; + $this->stopwatch = $stopwatch; + $this->tracedRequests = new \ArrayObject(); + } + + /** + * {@inheritdoc} + */ + public function request(string $method, string $url, array $options = []): ResponseInterface + { + $content = null; + $traceInfo = []; + $this->tracedRequests[] = [ + 'method' => $method, + 'url' => $url, + 'options' => $options, + 'info' => &$traceInfo, + 'content' => &$content, + ]; + $onProgress = $options['on_progress'] ?? null; + + if (false === ($options['extra']['trace_content'] ?? true)) { + unset($content); + $content = false; + } + + $options['on_progress'] = function (int $dlNow, int $dlSize, array $info) use (&$traceInfo, $onProgress) { + $traceInfo = $info; + + if (null !== $onProgress) { + $onProgress($dlNow, $dlSize, $info); + } + }; + + return new TraceableResponse($this->client, $this->client->request($method, $url, $options), $content, null === $this->stopwatch ? null : $this->stopwatch->start("$method $url", 'http_client')); + } + + /** + * {@inheritdoc} + */ + public function stream($responses, float $timeout = null): ResponseStreamInterface + { + if ($responses instanceof TraceableResponse) { + $responses = [$responses]; + } elseif (!is_iterable($responses)) { + throw new \TypeError(sprintf('"%s()" expects parameter 1 to be an iterable of TraceableResponse objects, "%s" given.', __METHOD__, get_debug_type($responses))); + } + + return new ResponseStream(TraceableResponse::stream($this->client, $responses, $timeout)); + } + + public function getTracedRequests(): array + { + return $this->tracedRequests->getArrayCopy(); + } + + public function reset() + { + if ($this->client instanceof ResetInterface) { + $this->client->reset(); + } + + $this->tracedRequests->exchangeArray([]); + } + + /** + * {@inheritdoc} + */ + public function setLogger(LoggerInterface $logger): void + { + if ($this->client instanceof LoggerAwareInterface) { + $this->client->setLogger($logger); + } + } + + /** + * {@inheritdoc} + */ + public function withOptions(array $options): self + { + $clone = clone $this; + $clone->client = $this->client->withOptions($options); + + return $clone; + } +} diff --git a/src/vendor/symfony/http-client/composer.json b/src/vendor/symfony/http-client/composer.json new file mode 100644 index 0000000..7f546b3 --- /dev/null +++ b/src/vendor/symfony/http-client/composer.json @@ -0,0 +1,55 @@ +{ + "name": "symfony/http-client", + "type": "library", + "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", + "keywords": ["http"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "2.4" + }, + "require": { + "php": ">=7.2.5", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/http-client-contracts": "^2.4", + "symfony/polyfill-php73": "^1.11", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.0|^2|^3" + }, + "require-dev": { + "amphp/amp": "^2.5", + "amphp/http-client": "^4.2.1", + "amphp/http-tunnel": "^1.0", + "amphp/socket": "^1.1", + "guzzlehttp/promises": "^1.4", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "php-http/message-factory": "^1.0", + "psr/http-client": "^1.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/http-kernel": "^4.4.13|^5.1.5|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/stopwatch": "^4.4|^5.0|^6.0" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\HttpClient\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/src/vendor/symfony/http-foundation/AcceptHeader.php b/src/vendor/symfony/http-foundation/AcceptHeader.php new file mode 100644 index 0000000..057c6b5 --- /dev/null +++ b/src/vendor/symfony/http-foundation/AcceptHeader.php @@ -0,0 +1,168 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +// Help opcache.preload discover always-needed symbols +class_exists(AcceptHeaderItem::class); + +/** + * Represents an Accept-* header. + * + * An accept header is compound with a list of items, + * sorted by descending quality. + * + * @author Jean-François Simon + */ +class AcceptHeader +{ + /** + * @var AcceptHeaderItem[] + */ + private $items = []; + + /** + * @var bool + */ + private $sorted = true; + + /** + * @param AcceptHeaderItem[] $items + */ + public function __construct(array $items) + { + foreach ($items as $item) { + $this->add($item); + } + } + + /** + * Builds an AcceptHeader instance from a string. + * + * @return self + */ + public static function fromString(?string $headerValue) + { + $index = 0; + + $parts = HeaderUtils::split($headerValue ?? '', ',;='); + + return new self(array_map(function ($subParts) use (&$index) { + $part = array_shift($subParts); + $attributes = HeaderUtils::combine($subParts); + + $item = new AcceptHeaderItem($part[0], $attributes); + $item->setIndex($index++); + + return $item; + }, $parts)); + } + + /** + * Returns header value's string representation. + * + * @return string + */ + public function __toString() + { + return implode(',', $this->items); + } + + /** + * Tests if header has given value. + * + * @return bool + */ + public function has(string $value) + { + return isset($this->items[$value]); + } + + /** + * Returns given value's item, if exists. + * + * @return AcceptHeaderItem|null + */ + public function get(string $value) + { + return $this->items[$value] ?? $this->items[explode('/', $value)[0].'/*'] ?? $this->items['*/*'] ?? $this->items['*'] ?? null; + } + + /** + * Adds an item. + * + * @return $this + */ + public function add(AcceptHeaderItem $item) + { + $this->items[$item->getValue()] = $item; + $this->sorted = false; + + return $this; + } + + /** + * Returns all items. + * + * @return AcceptHeaderItem[] + */ + public function all() + { + $this->sort(); + + return $this->items; + } + + /** + * Filters items on their value using given regex. + * + * @return self + */ + public function filter(string $pattern) + { + return new self(array_filter($this->items, function (AcceptHeaderItem $item) use ($pattern) { + return preg_match($pattern, $item->getValue()); + })); + } + + /** + * Returns first item. + * + * @return AcceptHeaderItem|null + */ + public function first() + { + $this->sort(); + + return !empty($this->items) ? reset($this->items) : null; + } + + /** + * Sorts items by descending quality. + */ + private function sort(): void + { + if (!$this->sorted) { + uasort($this->items, function (AcceptHeaderItem $a, AcceptHeaderItem $b) { + $qA = $a->getQuality(); + $qB = $b->getQuality(); + + if ($qA === $qB) { + return $a->getIndex() > $b->getIndex() ? 1 : -1; + } + + return $qA > $qB ? -1 : 1; + }); + + $this->sorted = true; + } + } +} diff --git a/src/vendor/symfony/http-foundation/AcceptHeaderItem.php b/src/vendor/symfony/http-foundation/AcceptHeaderItem.php new file mode 100644 index 0000000..8b86eee --- /dev/null +++ b/src/vendor/symfony/http-foundation/AcceptHeaderItem.php @@ -0,0 +1,177 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Represents an Accept-* header item. + * + * @author Jean-François Simon + */ +class AcceptHeaderItem +{ + private $value; + private $quality = 1.0; + private $index = 0; + private $attributes = []; + + public function __construct(string $value, array $attributes = []) + { + $this->value = $value; + foreach ($attributes as $name => $value) { + $this->setAttribute($name, $value); + } + } + + /** + * Builds an AcceptHeaderInstance instance from a string. + * + * @return self + */ + public static function fromString(?string $itemValue) + { + $parts = HeaderUtils::split($itemValue ?? '', ';='); + + $part = array_shift($parts); + $attributes = HeaderUtils::combine($parts); + + return new self($part[0], $attributes); + } + + /** + * Returns header value's string representation. + * + * @return string + */ + public function __toString() + { + $string = $this->value.($this->quality < 1 ? ';q='.$this->quality : ''); + if (\count($this->attributes) > 0) { + $string .= '; '.HeaderUtils::toString($this->attributes, ';'); + } + + return $string; + } + + /** + * Set the item value. + * + * @return $this + */ + public function setValue(string $value) + { + $this->value = $value; + + return $this; + } + + /** + * Returns the item value. + * + * @return string + */ + public function getValue() + { + return $this->value; + } + + /** + * Set the item quality. + * + * @return $this + */ + public function setQuality(float $quality) + { + $this->quality = $quality; + + return $this; + } + + /** + * Returns the item quality. + * + * @return float + */ + public function getQuality() + { + return $this->quality; + } + + /** + * Set the item index. + * + * @return $this + */ + public function setIndex(int $index) + { + $this->index = $index; + + return $this; + } + + /** + * Returns the item index. + * + * @return int + */ + public function getIndex() + { + return $this->index; + } + + /** + * Tests if an attribute exists. + * + * @return bool + */ + public function hasAttribute(string $name) + { + return isset($this->attributes[$name]); + } + + /** + * Returns an attribute by its name. + * + * @param mixed $default + * + * @return mixed + */ + public function getAttribute(string $name, $default = null) + { + return $this->attributes[$name] ?? $default; + } + + /** + * Returns all attributes. + * + * @return array + */ + public function getAttributes() + { + return $this->attributes; + } + + /** + * Set an attribute. + * + * @return $this + */ + public function setAttribute(string $name, string $value) + { + if ('q' === $name) { + $this->quality = (float) $value; + } else { + $this->attributes[$name] = $value; + } + + return $this; + } +} diff --git a/src/vendor/symfony/http-foundation/BinaryFileResponse.php b/src/vendor/symfony/http-foundation/BinaryFileResponse.php new file mode 100644 index 0000000..d3caa36 --- /dev/null +++ b/src/vendor/symfony/http-foundation/BinaryFileResponse.php @@ -0,0 +1,414 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\HttpFoundation\File\Exception\FileException; +use Symfony\Component\HttpFoundation\File\File; + +/** + * BinaryFileResponse represents an HTTP response delivering a file. + * + * @author Niklas Fiekas + * @author stealth35 + * @author Igor Wiedler + * @author Jordan Alliot + * @author Sergey Linnik + */ +class BinaryFileResponse extends Response +{ + protected static $trustXSendfileTypeHeader = false; + + /** + * @var File + */ + protected $file; + protected $offset = 0; + protected $maxlen = -1; + protected $deleteFileAfterSend = false; + protected $chunkSize = 16 * 1024; + + /** + * @param \SplFileInfo|string $file The file to stream + * @param int $status The response status code + * @param array $headers An array of response headers + * @param bool $public Files are public by default + * @param string|null $contentDisposition The type of Content-Disposition to set automatically with the filename + * @param bool $autoEtag Whether the ETag header should be automatically set + * @param bool $autoLastModified Whether the Last-Modified header should be automatically set + */ + public function __construct($file, int $status = 200, array $headers = [], bool $public = true, string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true) + { + parent::__construct(null, $status, $headers); + + $this->setFile($file, $contentDisposition, $autoEtag, $autoLastModified); + + if ($public) { + $this->setPublic(); + } + } + + /** + * @param \SplFileInfo|string $file The file to stream + * @param int $status The response status code + * @param array $headers An array of response headers + * @param bool $public Files are public by default + * @param string|null $contentDisposition The type of Content-Disposition to set automatically with the filename + * @param bool $autoEtag Whether the ETag header should be automatically set + * @param bool $autoLastModified Whether the Last-Modified header should be automatically set + * + * @return static + * + * @deprecated since Symfony 5.2, use __construct() instead. + */ + public static function create($file = null, int $status = 200, array $headers = [], bool $public = true, string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true) + { + trigger_deprecation('symfony/http-foundation', '5.2', 'The "%s()" method is deprecated, use "new %s()" instead.', __METHOD__, static::class); + + return new static($file, $status, $headers, $public, $contentDisposition, $autoEtag, $autoLastModified); + } + + /** + * Sets the file to stream. + * + * @param \SplFileInfo|string $file The file to stream + * + * @return $this + * + * @throws FileException + */ + public function setFile($file, string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true) + { + if (!$file instanceof File) { + if ($file instanceof \SplFileInfo) { + $file = new File($file->getPathname()); + } else { + $file = new File((string) $file); + } + } + + if (!$file->isReadable()) { + throw new FileException('File must be readable.'); + } + + $this->file = $file; + + if ($autoEtag) { + $this->setAutoEtag(); + } + + if ($autoLastModified) { + $this->setAutoLastModified(); + } + + if ($contentDisposition) { + $this->setContentDisposition($contentDisposition); + } + + return $this; + } + + /** + * Gets the file. + * + * @return File + */ + public function getFile() + { + return $this->file; + } + + /** + * Sets the response stream chunk size. + * + * @return $this + */ + public function setChunkSize(int $chunkSize): self + { + if ($chunkSize < 1 || $chunkSize > \PHP_INT_MAX) { + throw new \LogicException('The chunk size of a BinaryFileResponse cannot be less than 1 or greater than PHP_INT_MAX.'); + } + + $this->chunkSize = $chunkSize; + + return $this; + } + + /** + * Automatically sets the Last-Modified header according the file modification date. + * + * @return $this + */ + public function setAutoLastModified() + { + $this->setLastModified(\DateTime::createFromFormat('U', $this->file->getMTime())); + + return $this; + } + + /** + * Automatically sets the ETag header according to the checksum of the file. + * + * @return $this + */ + public function setAutoEtag() + { + $this->setEtag(base64_encode(hash_file('sha256', $this->file->getPathname(), true))); + + return $this; + } + + /** + * Sets the Content-Disposition header with the given filename. + * + * @param string $disposition ResponseHeaderBag::DISPOSITION_INLINE or ResponseHeaderBag::DISPOSITION_ATTACHMENT + * @param string $filename Optionally use this UTF-8 encoded filename instead of the real name of the file + * @param string $filenameFallback A fallback filename, containing only ASCII characters. Defaults to an automatically encoded filename + * + * @return $this + */ + public function setContentDisposition(string $disposition, string $filename = '', string $filenameFallback = '') + { + if ('' === $filename) { + $filename = $this->file->getFilename(); + } + + if ('' === $filenameFallback && (!preg_match('/^[\x20-\x7e]*$/', $filename) || str_contains($filename, '%'))) { + $encoding = mb_detect_encoding($filename, null, true) ?: '8bit'; + + for ($i = 0, $filenameLength = mb_strlen($filename, $encoding); $i < $filenameLength; ++$i) { + $char = mb_substr($filename, $i, 1, $encoding); + + if ('%' === $char || \ord($char) < 32 || \ord($char) > 126) { + $filenameFallback .= '_'; + } else { + $filenameFallback .= $char; + } + } + } + + $dispositionHeader = $this->headers->makeDisposition($disposition, $filename, $filenameFallback); + $this->headers->set('Content-Disposition', $dispositionHeader); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function prepare(Request $request) + { + if ($this->isInformational() || $this->isEmpty()) { + parent::prepare($request); + + $this->maxlen = 0; + + return $this; + } + + if (!$this->headers->has('Content-Type')) { + $this->headers->set('Content-Type', $this->file->getMimeType() ?: 'application/octet-stream'); + } + + parent::prepare($request); + + $this->offset = 0; + $this->maxlen = -1; + + if (false === $fileSize = $this->file->getSize()) { + return $this; + } + $this->headers->remove('Transfer-Encoding'); + $this->headers->set('Content-Length', $fileSize); + + if (!$this->headers->has('Accept-Ranges')) { + // Only accept ranges on safe HTTP methods + $this->headers->set('Accept-Ranges', $request->isMethodSafe() ? 'bytes' : 'none'); + } + + if (self::$trustXSendfileTypeHeader && $request->headers->has('X-Sendfile-Type')) { + // Use X-Sendfile, do not send any content. + $type = $request->headers->get('X-Sendfile-Type'); + $path = $this->file->getRealPath(); + // Fall back to scheme://path for stream wrapped locations. + if (false === $path) { + $path = $this->file->getPathname(); + } + if ('x-accel-redirect' === strtolower($type)) { + // Do X-Accel-Mapping substitutions. + // @link https://www.nginx.com/resources/wiki/start/topics/examples/x-accel/#x-accel-redirect + $parts = HeaderUtils::split($request->headers->get('X-Accel-Mapping', ''), ',='); + foreach ($parts as $part) { + [$pathPrefix, $location] = $part; + if (substr($path, 0, \strlen($pathPrefix)) === $pathPrefix) { + $path = $location.substr($path, \strlen($pathPrefix)); + // Only set X-Accel-Redirect header if a valid URI can be produced + // as nginx does not serve arbitrary file paths. + $this->headers->set($type, $path); + $this->maxlen = 0; + break; + } + } + } else { + $this->headers->set($type, $path); + $this->maxlen = 0; + } + } elseif ($request->headers->has('Range') && $request->isMethod('GET')) { + // Process the range headers. + if (!$request->headers->has('If-Range') || $this->hasValidIfRangeHeader($request->headers->get('If-Range'))) { + $range = $request->headers->get('Range'); + + if (str_starts_with($range, 'bytes=')) { + [$start, $end] = explode('-', substr($range, 6), 2) + [1 => 0]; + + $end = ('' === $end) ? $fileSize - 1 : (int) $end; + + if ('' === $start) { + $start = $fileSize - $end; + $end = $fileSize - 1; + } else { + $start = (int) $start; + } + + if ($start <= $end) { + $end = min($end, $fileSize - 1); + if ($start < 0 || $start > $end) { + $this->setStatusCode(416); + $this->headers->set('Content-Range', sprintf('bytes */%s', $fileSize)); + } elseif ($end - $start < $fileSize - 1) { + $this->maxlen = $end < $fileSize ? $end - $start + 1 : -1; + $this->offset = $start; + + $this->setStatusCode(206); + $this->headers->set('Content-Range', sprintf('bytes %s-%s/%s', $start, $end, $fileSize)); + $this->headers->set('Content-Length', $end - $start + 1); + } + } + } + } + } + + if ($request->isMethod('HEAD')) { + $this->maxlen = 0; + } + + return $this; + } + + private function hasValidIfRangeHeader(?string $header): bool + { + if ($this->getEtag() === $header) { + return true; + } + + if (null === $lastModified = $this->getLastModified()) { + return false; + } + + return $lastModified->format('D, d M Y H:i:s').' GMT' === $header; + } + + /** + * {@inheritdoc} + */ + public function sendContent() + { + try { + if (!$this->isSuccessful()) { + return parent::sendContent(); + } + + if (0 === $this->maxlen) { + return $this; + } + + $out = fopen('php://output', 'w'); + $file = fopen($this->file->getPathname(), 'r'); + + ignore_user_abort(true); + + if (0 !== $this->offset) { + fseek($file, $this->offset); + } + + $length = $this->maxlen; + while ($length && !feof($file)) { + $read = $length > $this->chunkSize || 0 > $length ? $this->chunkSize : $length; + + if (false === $data = fread($file, $read)) { + break; + } + while ('' !== $data) { + $read = fwrite($out, $data); + if (false === $read || connection_aborted()) { + break 2; + } + if (0 < $length) { + $length -= $read; + } + $data = substr($data, $read); + } + } + + fclose($out); + fclose($file); + } finally { + if ($this->deleteFileAfterSend && is_file($this->file->getPathname())) { + unlink($this->file->getPathname()); + } + } + + return $this; + } + + /** + * {@inheritdoc} + * + * @throws \LogicException when the content is not null + */ + public function setContent(?string $content) + { + if (null !== $content) { + throw new \LogicException('The content cannot be set on a BinaryFileResponse instance.'); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getContent() + { + return false; + } + + /** + * Trust X-Sendfile-Type header. + */ + public static function trustXSendfileTypeHeader() + { + self::$trustXSendfileTypeHeader = true; + } + + /** + * If this is set to true, the file will be unlinked after the request is sent + * Note: If the X-Sendfile header is used, the deleteFileAfterSend setting will not be used. + * + * @return $this + */ + public function deleteFileAfterSend(bool $shouldDelete = true) + { + $this->deleteFileAfterSend = $shouldDelete; + + return $this; + } +} diff --git a/src/vendor/symfony/http-foundation/CHANGELOG.md b/src/vendor/symfony/http-foundation/CHANGELOG.md new file mode 100644 index 0000000..ad7607a --- /dev/null +++ b/src/vendor/symfony/http-foundation/CHANGELOG.md @@ -0,0 +1,296 @@ +CHANGELOG +========= + +5.4 +--- + + * Deprecate passing `null` as `$requestIp` to `IpUtils::__checkIp()`, `IpUtils::__checkIp4()` or `IpUtils::__checkIp6()`, pass an empty string instead. + * Add the `litespeed_finish_request` method to work with Litespeed + * Deprecate `upload_progress.*` and `url_rewriter.tags` session options + * Allow setting session options via DSN + +5.3 +--- + + * Add the `SessionFactory`, `NativeSessionStorageFactory`, `PhpBridgeSessionStorageFactory` and `MockFileSessionStorageFactory` classes + * Calling `Request::getSession()` when there is no available session throws a `SessionNotFoundException` + * Add the `RequestStack::getSession` method + * Deprecate the `NamespacedAttributeBag` class + * Add `ResponseFormatSame` PHPUnit constraint + * Deprecate the `RequestStack::getMasterRequest()` method and add `getMainRequest()` as replacement + +5.2.0 +----- + + * added support for `X-Forwarded-Prefix` header + * added `HeaderUtils::parseQuery()`: it does the same as `parse_str()` but preserves dots in variable names + * added `File::getContent()` + * added ability to use comma separated ip addresses for `RequestMatcher::matchIps()` + * added `Request::toArray()` to parse a JSON request body to an array + * added `RateLimiter\RequestRateLimiterInterface` and `RateLimiter\AbstractRequestRateLimiter` + * deprecated not passing a `Closure` together with `FILTER_CALLBACK` to `ParameterBag::filter()`; wrap your filter in a closure instead. + * Deprecated the `Request::HEADER_X_FORWARDED_ALL` constant, use either `HEADER_X_FORWARDED_FOR | HEADER_X_FORWARDED_HOST | HEADER_X_FORWARDED_PORT | HEADER_X_FORWARDED_PROTO` or `HEADER_X_FORWARDED_AWS_ELB` or `HEADER_X_FORWARDED_TRAEFIK` constants instead. + * Deprecated `BinaryFileResponse::create()`, use `__construct()` instead + +5.1.0 +----- + + * added `Cookie::withValue`, `Cookie::withDomain`, `Cookie::withExpires`, + `Cookie::withPath`, `Cookie::withSecure`, `Cookie::withHttpOnly`, + `Cookie::withRaw`, `Cookie::withSameSite` + * Deprecate `Response::create()`, `JsonResponse::create()`, + `RedirectResponse::create()`, and `StreamedResponse::create()` methods (use + `__construct()` instead) + * added `Request::preferSafeContent()` and `Response::setContentSafe()` to handle "safe" HTTP preference + according to [RFC 8674](https://tools.ietf.org/html/rfc8674) + * made the Mime component an optional dependency + * added `MarshallingSessionHandler`, `IdentityMarshaller` + * made `Session` accept a callback to report when the session is being used + * Add support for all core cache control directives + * Added `Symfony\Component\HttpFoundation\InputBag` + * Deprecated retrieving non-string values using `InputBag::get()`, use `InputBag::all()` if you need access to the collection of values + +5.0.0 +----- + + * made `Cookie` auto-secure and lax by default + * removed classes in the `MimeType` namespace, use the Symfony Mime component instead + * removed method `UploadedFile::getClientSize()` and the related constructor argument + * made `Request::getSession()` throw if the session has not been set before + * removed `Response::HTTP_RESERVED_FOR_WEBDAV_ADVANCED_COLLECTIONS_EXPIRED_PROPOSAL` + * passing a null url when instantiating a `RedirectResponse` is not allowed + +4.4.0 +----- + + * passing arguments to `Request::isMethodSafe()` is deprecated. + * `ApacheRequest` is deprecated, use the `Request` class instead. + * passing a third argument to `HeaderBag::get()` is deprecated, use method `all()` instead + * [BC BREAK] `PdoSessionHandler` with MySQL changed the type of the lifetime column, + make sure to run `ALTER TABLE sessions MODIFY sess_lifetime INTEGER UNSIGNED NOT NULL` to + update your database. + * `PdoSessionHandler` now precalculates the expiry timestamp in the lifetime column, + make sure to run `CREATE INDEX EXPIRY ON sessions (sess_lifetime)` to update your database + to speed up garbage collection of expired sessions. + * added `SessionHandlerFactory` to create session handlers with a DSN + * added `IpUtils::anonymize()` to help with GDPR compliance. + +4.3.0 +----- + + * added PHPUnit constraints: `RequestAttributeValueSame`, `ResponseCookieValueSame`, `ResponseHasCookie`, + `ResponseHasHeader`, `ResponseHeaderSame`, `ResponseIsRedirected`, `ResponseIsSuccessful`, and `ResponseStatusCodeSame` + * deprecated `MimeTypeGuesserInterface` and `ExtensionGuesserInterface` in favor of `Symfony\Component\Mime\MimeTypesInterface`. + * deprecated `MimeType` and `MimeTypeExtensionGuesser` in favor of `Symfony\Component\Mime\MimeTypes`. + * deprecated `FileBinaryMimeTypeGuesser` in favor of `Symfony\Component\Mime\FileBinaryMimeTypeGuesser`. + * deprecated `FileinfoMimeTypeGuesser` in favor of `Symfony\Component\Mime\FileinfoMimeTypeGuesser`. + * added `UrlHelper` that allows to get an absolute URL and a relative path for a given path + +4.2.0 +----- + + * the default value of the "$secure" and "$samesite" arguments of Cookie's constructor + will respectively change from "false" to "null" and from "null" to "lax" in Symfony + 5.0, you should define their values explicitly or use "Cookie::create()" instead. + * added `matchPort()` in RequestMatcher + +4.1.3 +----- + + * [BC BREAK] Support for the IIS-only `X_ORIGINAL_URL` and `X_REWRITE_URL` + HTTP headers has been dropped for security reasons. + +4.1.0 +----- + + * Query string normalization uses `parse_str()` instead of custom parsing logic. + * Passing the file size to the constructor of the `UploadedFile` class is deprecated. + * The `getClientSize()` method of the `UploadedFile` class is deprecated. Use `getSize()` instead. + * added `RedisSessionHandler` to use Redis as a session storage + * The `get()` method of the `AcceptHeader` class now takes into account the + `*` and `*/*` default values (if they are present in the Accept HTTP header) + when looking for items. + * deprecated `Request::getSession()` when no session has been set. Use `Request::hasSession()` instead. + * added `CannotWriteFileException`, `ExtensionFileException`, `FormSizeFileException`, + `IniSizeFileException`, `NoFileException`, `NoTmpDirFileException`, `PartialFileException` to + handle failed `UploadedFile`. + * added `MigratingSessionHandler` for migrating between two session handlers without losing sessions + * added `HeaderUtils`. + +4.0.0 +----- + + * the `Request::setTrustedHeaderName()` and `Request::getTrustedHeaderName()` + methods have been removed + * the `Request::HEADER_CLIENT_IP` constant has been removed, use + `Request::HEADER_X_FORWARDED_FOR` instead + * the `Request::HEADER_CLIENT_HOST` constant has been removed, use + `Request::HEADER_X_FORWARDED_HOST` instead + * the `Request::HEADER_CLIENT_PROTO` constant has been removed, use + `Request::HEADER_X_FORWARDED_PROTO` instead + * the `Request::HEADER_CLIENT_PORT` constant has been removed, use + `Request::HEADER_X_FORWARDED_PORT` instead + * checking for cacheable HTTP methods using the `Request::isMethodSafe()` + method (by not passing `false` as its argument) is not supported anymore and + throws a `\BadMethodCallException` + * the `WriteCheckSessionHandler`, `NativeSessionHandler` and `NativeProxy` classes have been removed + * setting session save handlers that do not implement `\SessionHandlerInterface` in + `NativeSessionStorage::setSaveHandler()` is not supported anymore and throws a + `\TypeError` + +3.4.0 +----- + + * implemented PHP 7.0's `SessionUpdateTimestampHandlerInterface` with a new + `AbstractSessionHandler` base class and a new `StrictSessionHandler` wrapper + * deprecated the `WriteCheckSessionHandler`, `NativeSessionHandler` and `NativeProxy` classes + * deprecated setting session save handlers that do not implement `\SessionHandlerInterface` in `NativeSessionStorage::setSaveHandler()` + * deprecated using `MongoDbSessionHandler` with the legacy mongo extension; use it with the mongodb/mongodb package and ext-mongodb instead + * deprecated `MemcacheSessionHandler`; use `MemcachedSessionHandler` instead + +3.3.0 +----- + + * the `Request::setTrustedProxies()` method takes a new `$trustedHeaderSet` argument, + see https://symfony.com/doc/current/deployment/proxies.html for more info, + * deprecated the `Request::setTrustedHeaderName()` and `Request::getTrustedHeaderName()` methods, + * added `File\Stream`, to be passed to `BinaryFileResponse` when the size of the served file is unknown, + disabling `Range` and `Content-Length` handling, switching to chunked encoding instead + * added the `Cookie::fromString()` method that allows to create a cookie from a + raw header string + +3.1.0 +----- + + * Added support for creating `JsonResponse` with a string of JSON data + +3.0.0 +----- + + * The precedence of parameters returned from `Request::get()` changed from "GET, PATH, BODY" to "PATH, GET, BODY" + +2.8.0 +----- + + * Finding deep items in `ParameterBag::get()` is deprecated since version 2.8 and + will be removed in 3.0. + +2.6.0 +----- + + * PdoSessionHandler changes + - implemented different session locking strategies to prevent loss of data by concurrent access to the same session + - [BC BREAK] save session data in a binary column without base64_encode + - [BC BREAK] added lifetime column to the session table which allows to have different lifetimes for each session + - implemented lazy connections that are only opened when a session is used by either passing a dsn string + explicitly or falling back to session.save_path ini setting + - added a createTable method that initializes a correctly defined table depending on the database vendor + +2.5.0 +----- + + * added `JsonResponse::setEncodingOptions()` & `JsonResponse::getEncodingOptions()` for easier manipulation + of the options used while encoding data to JSON format. + +2.4.0 +----- + + * added RequestStack + * added Request::getEncodings() + * added accessors methods to session handlers + +2.3.0 +----- + + * added support for ranges of IPs in trusted proxies + * `UploadedFile::isValid` now returns false if the file was not uploaded via HTTP (in a non-test mode) + * Improved error-handling of `\Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler` + to ensure the supplied PDO handler throws Exceptions on error (as the class expects). Added related test cases + to verify that Exceptions are properly thrown when the PDO queries fail. + +2.2.0 +----- + + * fixed the Request::create() precedence (URI information always take precedence now) + * added Request::getTrustedProxies() + * deprecated Request::isProxyTrusted() + * [BC BREAK] JsonResponse does not turn a top level empty array to an object anymore, use an ArrayObject to enforce objects + * added a IpUtils class to check if an IP belongs to a CIDR + * added Request::getRealMethod() to get the "real" HTTP method (getMethod() returns the "intended" HTTP method) + * disabled _method request parameter support by default (call Request::enableHttpMethodParameterOverride() to + enable it, and Request::getHttpMethodParameterOverride() to check if it is supported) + * Request::splitHttpAcceptHeader() method is deprecated and will be removed in 2.3 + * Deprecated Flashbag::count() and \Countable interface, will be removed in 2.3 + +2.1.0 +----- + + * added Request::getSchemeAndHttpHost() and Request::getUserInfo() + * added a fluent interface to the Response class + * added Request::isProxyTrusted() + * added JsonResponse + * added a getTargetUrl method to RedirectResponse + * added support for streamed responses + * made Response::prepare() method the place to enforce HTTP specification + * [BC BREAK] moved management of the locale from the Session class to the Request class + * added a generic access to the PHP built-in filter mechanism: ParameterBag::filter() + * made FileBinaryMimeTypeGuesser command configurable + * added Request::getUser() and Request::getPassword() + * added support for the PATCH method in Request + * removed the ContentTypeMimeTypeGuesser class as it is deprecated and never used on PHP 5.3 + * added ResponseHeaderBag::makeDisposition() (implements RFC 6266) + * made mimetype to extension conversion configurable + * [BC BREAK] Moved all session related classes and interfaces into own namespace, as + `Symfony\Component\HttpFoundation\Session` and renamed classes accordingly. + Session handlers are located in the subnamespace `Symfony\Component\HttpFoundation\Session\Handler`. + * SessionHandlers must implement `\SessionHandlerInterface` or extend from the + `Symfony\Component\HttpFoundation\Storage\Handler\NativeSessionHandler` base class. + * Added internal storage driver proxy mechanism for forward compatibility with + PHP 5.4 `\SessionHandler` class. + * Added session handlers for custom Memcache, Memcached and Null session save handlers. + * [BC BREAK] Removed `NativeSessionStorage` and replaced with `NativeFileSessionHandler`. + * [BC BREAK] `SessionStorageInterface` methods removed: `write()`, `read()` and + `remove()`. Added `getBag()`, `registerBag()`. The `NativeSessionStorage` class + is a mediator for the session storage internals including the session handlers + which do the real work of participating in the internal PHP session workflow. + * [BC BREAK] Introduced mock implementations of `SessionStorage` to enable unit + and functional testing without starting real PHP sessions. Removed + `ArraySessionStorage`, and replaced with `MockArraySessionStorage` for unit + tests; removed `FilesystemSessionStorage`, and replaced with`MockFileSessionStorage` + for functional tests. These do not interact with global session ini + configuration values, session functions or `$_SESSION` superglobal. This means + they can be configured directly allowing multiple instances to work without + conflicting in the same PHP process. + * [BC BREAK] Removed the `close()` method from the `Session` class, as this is + now redundant. + * Deprecated the following methods from the Session class: `setFlash()`, `setFlashes()` + `getFlash()`, `hasFlash()`, and `removeFlash()`. Use `getFlashBag()` instead + which returns a `FlashBagInterface`. + * `Session->clear()` now only clears session attributes as before it cleared + flash messages and attributes. `Session->getFlashBag()->all()` clears flashes now. + * Session data is now managed by `SessionBagInterface` to better encapsulate + session data. + * Refactored session attribute and flash messages system to their own + `SessionBagInterface` implementations. + * Added `FlashBag`. Flashes expire when retrieved by `get()` or `all()`. This + implementation is ESI compatible. + * Added `AutoExpireFlashBag` (default) to replicate Symfony 2.0.x auto expire + behavior of messages auto expiring after one page page load. Messages must + be retrieved by `get()` or `all()`. + * Added `Symfony\Component\HttpFoundation\Attribute\AttributeBag` to replicate + attributes storage behavior from 2.0.x (default). + * Added `Symfony\Component\HttpFoundation\Attribute\NamespacedAttributeBag` for + namespace session attributes. + * Flash API can stores messages in an array so there may be multiple messages + per flash type. The old `Session` class API remains without BC break as it + will allow single messages as before. + * Added basic session meta-data to the session to record session create time, + last updated time, and the lifetime of the session cookie that was provided + to the client. + * Request::getClientIp() method doesn't take a parameter anymore but bases + itself on the trustProxy parameter. + * Added isMethod() to Request object. + * [BC BREAK] The methods `getPathInfo()`, `getBaseUrl()` and `getBasePath()` of + a `Request` now all return a raw value (vs a urldecoded value before). Any call + to one of these methods must be checked and wrapped in a `rawurldecode()` if + needed. diff --git a/src/vendor/symfony/http-foundation/Cookie.php b/src/vendor/symfony/http-foundation/Cookie.php new file mode 100644 index 0000000..9102453 --- /dev/null +++ b/src/vendor/symfony/http-foundation/Cookie.php @@ -0,0 +1,422 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Represents a cookie. + * + * @author Johannes M. Schmitt + */ +class Cookie +{ + public const SAMESITE_NONE = 'none'; + public const SAMESITE_LAX = 'lax'; + public const SAMESITE_STRICT = 'strict'; + + protected $name; + protected $value; + protected $domain; + protected $expire; + protected $path; + protected $secure; + protected $httpOnly; + + private $raw; + private $sameSite; + private $secureDefault = false; + + private const RESERVED_CHARS_LIST = "=,; \t\r\n\v\f"; + private const RESERVED_CHARS_FROM = ['=', ',', ';', ' ', "\t", "\r", "\n", "\v", "\f"]; + private const RESERVED_CHARS_TO = ['%3D', '%2C', '%3B', '%20', '%09', '%0D', '%0A', '%0B', '%0C']; + + /** + * Creates cookie from raw header string. + * + * @return static + */ + public static function fromString(string $cookie, bool $decode = false) + { + $data = [ + 'expires' => 0, + 'path' => '/', + 'domain' => null, + 'secure' => false, + 'httponly' => false, + 'raw' => !$decode, + 'samesite' => null, + ]; + + $parts = HeaderUtils::split($cookie, ';='); + $part = array_shift($parts); + + $name = $decode ? urldecode($part[0]) : $part[0]; + $value = isset($part[1]) ? ($decode ? urldecode($part[1]) : $part[1]) : null; + + $data = HeaderUtils::combine($parts) + $data; + $data['expires'] = self::expiresTimestamp($data['expires']); + + if (isset($data['max-age']) && ($data['max-age'] > 0 || $data['expires'] > time())) { + $data['expires'] = time() + (int) $data['max-age']; + } + + return new static($name, $value, $data['expires'], $data['path'], $data['domain'], $data['secure'], $data['httponly'], $data['raw'], $data['samesite']); + } + + public static function create(string $name, string $value = null, $expire = 0, ?string $path = '/', string $domain = null, bool $secure = null, bool $httpOnly = true, bool $raw = false, ?string $sameSite = self::SAMESITE_LAX): self + { + return new self($name, $value, $expire, $path, $domain, $secure, $httpOnly, $raw, $sameSite); + } + + /** + * @param string $name The name of the cookie + * @param string|null $value The value of the cookie + * @param int|string|\DateTimeInterface $expire The time the cookie expires + * @param string|null $path The path on the server in which the cookie will be available on + * @param string|null $domain The domain that the cookie is available to + * @param bool|null $secure Whether the client should send back the cookie only over HTTPS or null to auto-enable this when the request is already using HTTPS + * @param bool $httpOnly Whether the cookie will be made accessible only through the HTTP protocol + * @param bool $raw Whether the cookie value should be sent with no url encoding + * @param string|null $sameSite Whether the cookie will be available for cross-site requests + * + * @throws \InvalidArgumentException + */ + public function __construct(string $name, string $value = null, $expire = 0, ?string $path = '/', string $domain = null, bool $secure = null, bool $httpOnly = true, bool $raw = false, ?string $sameSite = 'lax') + { + // from PHP source code + if ($raw && false !== strpbrk($name, self::RESERVED_CHARS_LIST)) { + throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name)); + } + + if (empty($name)) { + throw new \InvalidArgumentException('The cookie name cannot be empty.'); + } + + $this->name = $name; + $this->value = $value; + $this->domain = $domain; + $this->expire = self::expiresTimestamp($expire); + $this->path = empty($path) ? '/' : $path; + $this->secure = $secure; + $this->httpOnly = $httpOnly; + $this->raw = $raw; + $this->sameSite = $this->withSameSite($sameSite)->sameSite; + } + + /** + * Creates a cookie copy with a new value. + * + * @return static + */ + public function withValue(?string $value): self + { + $cookie = clone $this; + $cookie->value = $value; + + return $cookie; + } + + /** + * Creates a cookie copy with a new domain that the cookie is available to. + * + * @return static + */ + public function withDomain(?string $domain): self + { + $cookie = clone $this; + $cookie->domain = $domain; + + return $cookie; + } + + /** + * Creates a cookie copy with a new time the cookie expires. + * + * @param int|string|\DateTimeInterface $expire + * + * @return static + */ + public function withExpires($expire = 0): self + { + $cookie = clone $this; + $cookie->expire = self::expiresTimestamp($expire); + + return $cookie; + } + + /** + * Converts expires formats to a unix timestamp. + * + * @param int|string|\DateTimeInterface $expire + */ + private static function expiresTimestamp($expire = 0): int + { + // convert expiration time to a Unix timestamp + if ($expire instanceof \DateTimeInterface) { + $expire = $expire->format('U'); + } elseif (!is_numeric($expire)) { + $expire = strtotime($expire); + + if (false === $expire) { + throw new \InvalidArgumentException('The cookie expiration time is not valid.'); + } + } + + return 0 < $expire ? (int) $expire : 0; + } + + /** + * Creates a cookie copy with a new path on the server in which the cookie will be available on. + * + * @return static + */ + public function withPath(string $path): self + { + $cookie = clone $this; + $cookie->path = '' === $path ? '/' : $path; + + return $cookie; + } + + /** + * Creates a cookie copy that only be transmitted over a secure HTTPS connection from the client. + * + * @return static + */ + public function withSecure(bool $secure = true): self + { + $cookie = clone $this; + $cookie->secure = $secure; + + return $cookie; + } + + /** + * Creates a cookie copy that be accessible only through the HTTP protocol. + * + * @return static + */ + public function withHttpOnly(bool $httpOnly = true): self + { + $cookie = clone $this; + $cookie->httpOnly = $httpOnly; + + return $cookie; + } + + /** + * Creates a cookie copy that uses no url encoding. + * + * @return static + */ + public function withRaw(bool $raw = true): self + { + if ($raw && false !== strpbrk($this->name, self::RESERVED_CHARS_LIST)) { + throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $this->name)); + } + + $cookie = clone $this; + $cookie->raw = $raw; + + return $cookie; + } + + /** + * Creates a cookie copy with SameSite attribute. + * + * @return static + */ + public function withSameSite(?string $sameSite): self + { + if ('' === $sameSite) { + $sameSite = null; + } elseif (null !== $sameSite) { + $sameSite = strtolower($sameSite); + } + + if (!\in_array($sameSite, [self::SAMESITE_LAX, self::SAMESITE_STRICT, self::SAMESITE_NONE, null], true)) { + throw new \InvalidArgumentException('The "sameSite" parameter value is not valid.'); + } + + $cookie = clone $this; + $cookie->sameSite = $sameSite; + + return $cookie; + } + + /** + * Returns the cookie as a string. + * + * @return string + */ + public function __toString() + { + if ($this->isRaw()) { + $str = $this->getName(); + } else { + $str = str_replace(self::RESERVED_CHARS_FROM, self::RESERVED_CHARS_TO, $this->getName()); + } + + $str .= '='; + + if ('' === (string) $this->getValue()) { + $str .= 'deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; Max-Age=0'; + } else { + $str .= $this->isRaw() ? $this->getValue() : rawurlencode($this->getValue()); + + if (0 !== $this->getExpiresTime()) { + $str .= '; expires='.gmdate('D, d-M-Y H:i:s T', $this->getExpiresTime()).'; Max-Age='.$this->getMaxAge(); + } + } + + if ($this->getPath()) { + $str .= '; path='.$this->getPath(); + } + + if ($this->getDomain()) { + $str .= '; domain='.$this->getDomain(); + } + + if (true === $this->isSecure()) { + $str .= '; secure'; + } + + if (true === $this->isHttpOnly()) { + $str .= '; httponly'; + } + + if (null !== $this->getSameSite()) { + $str .= '; samesite='.$this->getSameSite(); + } + + return $str; + } + + /** + * Gets the name of the cookie. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Gets the value of the cookie. + * + * @return string|null + */ + public function getValue() + { + return $this->value; + } + + /** + * Gets the domain that the cookie is available to. + * + * @return string|null + */ + public function getDomain() + { + return $this->domain; + } + + /** + * Gets the time the cookie expires. + * + * @return int + */ + public function getExpiresTime() + { + return $this->expire; + } + + /** + * Gets the max-age attribute. + * + * @return int + */ + public function getMaxAge() + { + $maxAge = $this->expire - time(); + + return 0 >= $maxAge ? 0 : $maxAge; + } + + /** + * Gets the path on the server in which the cookie will be available on. + * + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * Checks whether the cookie should only be transmitted over a secure HTTPS connection from the client. + * + * @return bool + */ + public function isSecure() + { + return $this->secure ?? $this->secureDefault; + } + + /** + * Checks whether the cookie will be made accessible only through the HTTP protocol. + * + * @return bool + */ + public function isHttpOnly() + { + return $this->httpOnly; + } + + /** + * Whether this cookie is about to be cleared. + * + * @return bool + */ + public function isCleared() + { + return 0 !== $this->expire && $this->expire < time(); + } + + /** + * Checks if the cookie value should be sent with no url encoding. + * + * @return bool + */ + public function isRaw() + { + return $this->raw; + } + + /** + * Gets the SameSite attribute. + * + * @return string|null + */ + public function getSameSite() + { + return $this->sameSite; + } + + /** + * @param bool $default The default value of the "secure" flag when it is set to null + */ + public function setSecureDefault(bool $default): void + { + $this->secureDefault = $default; + } +} diff --git a/src/vendor/symfony/http-foundation/Exception/BadRequestException.php b/src/vendor/symfony/http-foundation/Exception/BadRequestException.php new file mode 100644 index 0000000..e4bb309 --- /dev/null +++ b/src/vendor/symfony/http-foundation/Exception/BadRequestException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Exception; + +/** + * Raised when a user sends a malformed request. + */ +class BadRequestException extends \UnexpectedValueException implements RequestExceptionInterface +{ +} diff --git a/src/vendor/symfony/http-foundation/Exception/ConflictingHeadersException.php b/src/vendor/symfony/http-foundation/Exception/ConflictingHeadersException.php new file mode 100644 index 0000000..5fcf5b4 --- /dev/null +++ b/src/vendor/symfony/http-foundation/Exception/ConflictingHeadersException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Exception; + +/** + * The HTTP request contains headers with conflicting information. + * + * @author Magnus Nordlander + */ +class ConflictingHeadersException extends \UnexpectedValueException implements RequestExceptionInterface +{ +} diff --git a/src/vendor/symfony/http-foundation/Exception/JsonException.php b/src/vendor/symfony/http-foundation/Exception/JsonException.php new file mode 100644 index 0000000..5990e76 --- /dev/null +++ b/src/vendor/symfony/http-foundation/Exception/JsonException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Exception; + +/** + * Thrown by Request::toArray() when the content cannot be JSON-decoded. + * + * @author Tobias Nyholm + */ +final class JsonException extends \UnexpectedValueException implements RequestExceptionInterface +{ +} diff --git a/src/vendor/symfony/http-foundation/Exception/RequestExceptionInterface.php b/src/vendor/symfony/http-foundation/Exception/RequestExceptionInterface.php new file mode 100644 index 0000000..478d0dc --- /dev/null +++ b/src/vendor/symfony/http-foundation/Exception/RequestExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Exception; + +/** + * Interface for Request exceptions. + * + * Exceptions implementing this interface should trigger an HTTP 400 response in the application code. + */ +interface RequestExceptionInterface +{ +} diff --git a/src/vendor/symfony/http-foundation/Exception/SessionNotFoundException.php b/src/vendor/symfony/http-foundation/Exception/SessionNotFoundException.php new file mode 100644 index 0000000..94b0cb6 --- /dev/null +++ b/src/vendor/symfony/http-foundation/Exception/SessionNotFoundException.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Exception; + +/** + * Raised when a session does not exist. This happens in the following cases: + * - the session is not enabled + * - attempt to read a session outside a request context (ie. cli script). + * + * @author Jérémy Derussé + */ +class SessionNotFoundException extends \LogicException implements RequestExceptionInterface +{ + public function __construct(string $message = 'There is currently no session available.', int $code = 0, \Throwable $previous = null) + { + parent::__construct($message, $code, $previous); + } +} diff --git a/src/vendor/symfony/http-foundation/Exception/SuspiciousOperationException.php b/src/vendor/symfony/http-foundation/Exception/SuspiciousOperationException.php new file mode 100644 index 0000000..ae7a5f1 --- /dev/null +++ b/src/vendor/symfony/http-foundation/Exception/SuspiciousOperationException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Exception; + +/** + * Raised when a user has performed an operation that should be considered + * suspicious from a security perspective. + */ +class SuspiciousOperationException extends \UnexpectedValueException implements RequestExceptionInterface +{ +} diff --git a/src/vendor/symfony/http-foundation/ExpressionRequestMatcher.php b/src/vendor/symfony/http-foundation/ExpressionRequestMatcher.php new file mode 100644 index 0000000..26bed7d --- /dev/null +++ b/src/vendor/symfony/http-foundation/ExpressionRequestMatcher.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; + +/** + * ExpressionRequestMatcher uses an expression to match a Request. + * + * @author Fabien Potencier + */ +class ExpressionRequestMatcher extends RequestMatcher +{ + private $language; + private $expression; + + public function setExpression(ExpressionLanguage $language, $expression) + { + $this->language = $language; + $this->expression = $expression; + } + + public function matches(Request $request) + { + if (!$this->language) { + throw new \LogicException('Unable to match the request as the expression language is not available.'); + } + + return $this->language->evaluate($this->expression, [ + 'request' => $request, + 'method' => $request->getMethod(), + 'path' => rawurldecode($request->getPathInfo()), + 'host' => $request->getHost(), + 'ip' => $request->getClientIp(), + 'attributes' => $request->attributes->all(), + ]) && parent::matches($request); + } +} diff --git a/src/vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php b/src/vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php new file mode 100644 index 0000000..136d2a9 --- /dev/null +++ b/src/vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when the access on a file was denied. + * + * @author Bernhard Schussek + */ +class AccessDeniedException extends FileException +{ + public function __construct(string $path) + { + parent::__construct(sprintf('The file %s could not be accessed', $path)); + } +} diff --git a/src/vendor/symfony/http-foundation/File/Exception/CannotWriteFileException.php b/src/vendor/symfony/http-foundation/File/Exception/CannotWriteFileException.php new file mode 100644 index 0000000..c49f53a --- /dev/null +++ b/src/vendor/symfony/http-foundation/File/Exception/CannotWriteFileException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an UPLOAD_ERR_CANT_WRITE error occurred with UploadedFile. + * + * @author Florent Mata + */ +class CannotWriteFileException extends FileException +{ +} diff --git a/src/vendor/symfony/http-foundation/File/Exception/ExtensionFileException.php b/src/vendor/symfony/http-foundation/File/Exception/ExtensionFileException.php new file mode 100644 index 0000000..ed83499 --- /dev/null +++ b/src/vendor/symfony/http-foundation/File/Exception/ExtensionFileException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an UPLOAD_ERR_EXTENSION error occurred with UploadedFile. + * + * @author Florent Mata + */ +class ExtensionFileException extends FileException +{ +} diff --git a/src/vendor/symfony/http-foundation/File/Exception/FileException.php b/src/vendor/symfony/http-foundation/File/Exception/FileException.php new file mode 100644 index 0000000..fad5133 --- /dev/null +++ b/src/vendor/symfony/http-foundation/File/Exception/FileException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an error occurred in the component File. + * + * @author Bernhard Schussek + */ +class FileException extends \RuntimeException +{ +} diff --git a/src/vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php b/src/vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php new file mode 100644 index 0000000..31bdf68 --- /dev/null +++ b/src/vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when a file was not found. + * + * @author Bernhard Schussek + */ +class FileNotFoundException extends FileException +{ + public function __construct(string $path) + { + parent::__construct(sprintf('The file "%s" does not exist', $path)); + } +} diff --git a/src/vendor/symfony/http-foundation/File/Exception/FormSizeFileException.php b/src/vendor/symfony/http-foundation/File/Exception/FormSizeFileException.php new file mode 100644 index 0000000..8741be0 --- /dev/null +++ b/src/vendor/symfony/http-foundation/File/Exception/FormSizeFileException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an UPLOAD_ERR_FORM_SIZE error occurred with UploadedFile. + * + * @author Florent Mata + */ +class FormSizeFileException extends FileException +{ +} diff --git a/src/vendor/symfony/http-foundation/File/Exception/IniSizeFileException.php b/src/vendor/symfony/http-foundation/File/Exception/IniSizeFileException.php new file mode 100644 index 0000000..c8fde61 --- /dev/null +++ b/src/vendor/symfony/http-foundation/File/Exception/IniSizeFileException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an UPLOAD_ERR_INI_SIZE error occurred with UploadedFile. + * + * @author Florent Mata + */ +class IniSizeFileException extends FileException +{ +} diff --git a/src/vendor/symfony/http-foundation/File/Exception/NoFileException.php b/src/vendor/symfony/http-foundation/File/Exception/NoFileException.php new file mode 100644 index 0000000..4b48cc7 --- /dev/null +++ b/src/vendor/symfony/http-foundation/File/Exception/NoFileException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an UPLOAD_ERR_NO_FILE error occurred with UploadedFile. + * + * @author Florent Mata + */ +class NoFileException extends FileException +{ +} diff --git a/src/vendor/symfony/http-foundation/File/Exception/NoTmpDirFileException.php b/src/vendor/symfony/http-foundation/File/Exception/NoTmpDirFileException.php new file mode 100644 index 0000000..bdead2d --- /dev/null +++ b/src/vendor/symfony/http-foundation/File/Exception/NoTmpDirFileException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an UPLOAD_ERR_NO_TMP_DIR error occurred with UploadedFile. + * + * @author Florent Mata + */ +class NoTmpDirFileException extends FileException +{ +} diff --git a/src/vendor/symfony/http-foundation/File/Exception/PartialFileException.php b/src/vendor/symfony/http-foundation/File/Exception/PartialFileException.php new file mode 100644 index 0000000..4641efb --- /dev/null +++ b/src/vendor/symfony/http-foundation/File/Exception/PartialFileException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an UPLOAD_ERR_PARTIAL error occurred with UploadedFile. + * + * @author Florent Mata + */ +class PartialFileException extends FileException +{ +} diff --git a/src/vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php b/src/vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php new file mode 100644 index 0000000..8533f99 --- /dev/null +++ b/src/vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +class UnexpectedTypeException extends FileException +{ + public function __construct($value, string $expectedType) + { + parent::__construct(sprintf('Expected argument of type %s, %s given', $expectedType, get_debug_type($value))); + } +} diff --git a/src/vendor/symfony/http-foundation/File/Exception/UploadException.php b/src/vendor/symfony/http-foundation/File/Exception/UploadException.php new file mode 100644 index 0000000..7074e76 --- /dev/null +++ b/src/vendor/symfony/http-foundation/File/Exception/UploadException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an error occurred during file upload. + * + * @author Bernhard Schussek + */ +class UploadException extends FileException +{ +} diff --git a/src/vendor/symfony/http-foundation/File/File.php b/src/vendor/symfony/http-foundation/File/File.php new file mode 100644 index 0000000..d941577 --- /dev/null +++ b/src/vendor/symfony/http-foundation/File/File.php @@ -0,0 +1,152 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File; + +use Symfony\Component\HttpFoundation\File\Exception\FileException; +use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; +use Symfony\Component\Mime\MimeTypes; + +/** + * A file in the file system. + * + * @author Bernhard Schussek + */ +class File extends \SplFileInfo +{ + /** + * Constructs a new file from the given path. + * + * @param string $path The path to the file + * @param bool $checkPath Whether to check the path or not + * + * @throws FileNotFoundException If the given path is not a file + */ + public function __construct(string $path, bool $checkPath = true) + { + if ($checkPath && !is_file($path)) { + throw new FileNotFoundException($path); + } + + parent::__construct($path); + } + + /** + * Returns the extension based on the mime type. + * + * If the mime type is unknown, returns null. + * + * This method uses the mime type as guessed by getMimeType() + * to guess the file extension. + * + * @return string|null + * + * @see MimeTypes + * @see getMimeType() + */ + public function guessExtension() + { + if (!class_exists(MimeTypes::class)) { + throw new \LogicException('You cannot guess the extension as the Mime component is not installed. Try running "composer require symfony/mime".'); + } + + return MimeTypes::getDefault()->getExtensions($this->getMimeType())[0] ?? null; + } + + /** + * Returns the mime type of the file. + * + * The mime type is guessed using a MimeTypeGuesserInterface instance, + * which uses finfo_file() then the "file" system binary, + * depending on which of those are available. + * + * @return string|null + * + * @see MimeTypes + */ + public function getMimeType() + { + if (!class_exists(MimeTypes::class)) { + throw new \LogicException('You cannot guess the mime type as the Mime component is not installed. Try running "composer require symfony/mime".'); + } + + return MimeTypes::getDefault()->guessMimeType($this->getPathname()); + } + + /** + * Moves the file to a new location. + * + * @return self + * + * @throws FileException if the target file could not be created + */ + public function move(string $directory, string $name = null) + { + $target = $this->getTargetFile($directory, $name); + + set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); + try { + $renamed = rename($this->getPathname(), $target); + } finally { + restore_error_handler(); + } + if (!$renamed) { + throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s).', $this->getPathname(), $target, strip_tags($error))); + } + + @chmod($target, 0666 & ~umask()); + + return $target; + } + + public function getContent(): string + { + $content = file_get_contents($this->getPathname()); + + if (false === $content) { + throw new FileException(sprintf('Could not get the content of the file "%s".', $this->getPathname())); + } + + return $content; + } + + /** + * @return self + */ + protected function getTargetFile(string $directory, string $name = null) + { + if (!is_dir($directory)) { + if (false === @mkdir($directory, 0777, true) && !is_dir($directory)) { + throw new FileException(sprintf('Unable to create the "%s" directory.', $directory)); + } + } elseif (!is_writable($directory)) { + throw new FileException(sprintf('Unable to write in the "%s" directory.', $directory)); + } + + $target = rtrim($directory, '/\\').\DIRECTORY_SEPARATOR.(null === $name ? $this->getBasename() : $this->getName($name)); + + return new self($target, false); + } + + /** + * Returns locale independent base name of the given path. + * + * @return string + */ + protected function getName(string $name) + { + $originalName = str_replace('\\', '/', $name); + $pos = strrpos($originalName, '/'); + $originalName = false === $pos ? $originalName : substr($originalName, $pos + 1); + + return $originalName; + } +} diff --git a/src/vendor/symfony/http-foundation/File/Stream.php b/src/vendor/symfony/http-foundation/File/Stream.php new file mode 100644 index 0000000..cef3e03 --- /dev/null +++ b/src/vendor/symfony/http-foundation/File/Stream.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File; + +/** + * A PHP stream of unknown size. + * + * @author Nicolas Grekas + */ +class Stream extends File +{ + /** + * {@inheritdoc} + * + * @return int|false + */ + #[\ReturnTypeWillChange] + public function getSize() + { + return false; + } +} diff --git a/src/vendor/symfony/http-foundation/File/UploadedFile.php b/src/vendor/symfony/http-foundation/File/UploadedFile.php new file mode 100644 index 0000000..1161556 --- /dev/null +++ b/src/vendor/symfony/http-foundation/File/UploadedFile.php @@ -0,0 +1,290 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File; + +use Symfony\Component\HttpFoundation\File\Exception\CannotWriteFileException; +use Symfony\Component\HttpFoundation\File\Exception\ExtensionFileException; +use Symfony\Component\HttpFoundation\File\Exception\FileException; +use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; +use Symfony\Component\HttpFoundation\File\Exception\FormSizeFileException; +use Symfony\Component\HttpFoundation\File\Exception\IniSizeFileException; +use Symfony\Component\HttpFoundation\File\Exception\NoFileException; +use Symfony\Component\HttpFoundation\File\Exception\NoTmpDirFileException; +use Symfony\Component\HttpFoundation\File\Exception\PartialFileException; +use Symfony\Component\Mime\MimeTypes; + +/** + * A file uploaded through a form. + * + * @author Bernhard Schussek + * @author Florian Eckerstorfer + * @author Fabien Potencier + */ +class UploadedFile extends File +{ + private $test; + private $originalName; + private $mimeType; + private $error; + + /** + * Accepts the information of the uploaded file as provided by the PHP global $_FILES. + * + * The file object is only created when the uploaded file is valid (i.e. when the + * isValid() method returns true). Otherwise the only methods that could be called + * on an UploadedFile instance are: + * + * * getClientOriginalName, + * * getClientMimeType, + * * isValid, + * * getError. + * + * Calling any other method on an non-valid instance will cause an unpredictable result. + * + * @param string $path The full temporary path to the file + * @param string $originalName The original file name of the uploaded file + * @param string|null $mimeType The type of the file as provided by PHP; null defaults to application/octet-stream + * @param int|null $error The error constant of the upload (one of PHP's UPLOAD_ERR_XXX constants); null defaults to UPLOAD_ERR_OK + * @param bool $test Whether the test mode is active + * Local files are used in test mode hence the code should not enforce HTTP uploads + * + * @throws FileException If file_uploads is disabled + * @throws FileNotFoundException If the file does not exist + */ + public function __construct(string $path, string $originalName, string $mimeType = null, int $error = null, bool $test = false) + { + $this->originalName = $this->getName($originalName); + $this->mimeType = $mimeType ?: 'application/octet-stream'; + $this->error = $error ?: \UPLOAD_ERR_OK; + $this->test = $test; + + parent::__construct($path, \UPLOAD_ERR_OK === $this->error); + } + + /** + * Returns the original file name. + * + * It is extracted from the request from which the file has been uploaded. + * This should not be considered as a safe value to use for a file name on your servers. + * + * @return string + */ + public function getClientOriginalName() + { + return $this->originalName; + } + + /** + * Returns the original file extension. + * + * It is extracted from the original file name that was uploaded. + * This should not be considered as a safe value to use for a file name on your servers. + * + * @return string + */ + public function getClientOriginalExtension() + { + return pathinfo($this->originalName, \PATHINFO_EXTENSION); + } + + /** + * Returns the file mime type. + * + * The client mime type is extracted from the request from which the file + * was uploaded, so it should not be considered as a safe value. + * + * For a trusted mime type, use getMimeType() instead (which guesses the mime + * type based on the file content). + * + * @return string + * + * @see getMimeType() + */ + public function getClientMimeType() + { + return $this->mimeType; + } + + /** + * Returns the extension based on the client mime type. + * + * If the mime type is unknown, returns null. + * + * This method uses the mime type as guessed by getClientMimeType() + * to guess the file extension. As such, the extension returned + * by this method cannot be trusted. + * + * For a trusted extension, use guessExtension() instead (which guesses + * the extension based on the guessed mime type for the file). + * + * @return string|null + * + * @see guessExtension() + * @see getClientMimeType() + */ + public function guessClientExtension() + { + if (!class_exists(MimeTypes::class)) { + throw new \LogicException('You cannot guess the extension as the Mime component is not installed. Try running "composer require symfony/mime".'); + } + + return MimeTypes::getDefault()->getExtensions($this->getClientMimeType())[0] ?? null; + } + + /** + * Returns the upload error. + * + * If the upload was successful, the constant UPLOAD_ERR_OK is returned. + * Otherwise one of the other UPLOAD_ERR_XXX constants is returned. + * + * @return int + */ + public function getError() + { + return $this->error; + } + + /** + * Returns whether the file has been uploaded with HTTP and no error occurred. + * + * @return bool + */ + public function isValid() + { + $isOk = \UPLOAD_ERR_OK === $this->error; + + return $this->test ? $isOk : $isOk && is_uploaded_file($this->getPathname()); + } + + /** + * Moves the file to a new location. + * + * @return File + * + * @throws FileException if, for any reason, the file could not have been moved + */ + public function move(string $directory, string $name = null) + { + if ($this->isValid()) { + if ($this->test) { + return parent::move($directory, $name); + } + + $target = $this->getTargetFile($directory, $name); + + set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); + try { + $moved = move_uploaded_file($this->getPathname(), $target); + } finally { + restore_error_handler(); + } + if (!$moved) { + throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s).', $this->getPathname(), $target, strip_tags($error))); + } + + @chmod($target, 0666 & ~umask()); + + return $target; + } + + switch ($this->error) { + case \UPLOAD_ERR_INI_SIZE: + throw new IniSizeFileException($this->getErrorMessage()); + case \UPLOAD_ERR_FORM_SIZE: + throw new FormSizeFileException($this->getErrorMessage()); + case \UPLOAD_ERR_PARTIAL: + throw new PartialFileException($this->getErrorMessage()); + case \UPLOAD_ERR_NO_FILE: + throw new NoFileException($this->getErrorMessage()); + case \UPLOAD_ERR_CANT_WRITE: + throw new CannotWriteFileException($this->getErrorMessage()); + case \UPLOAD_ERR_NO_TMP_DIR: + throw new NoTmpDirFileException($this->getErrorMessage()); + case \UPLOAD_ERR_EXTENSION: + throw new ExtensionFileException($this->getErrorMessage()); + } + + throw new FileException($this->getErrorMessage()); + } + + /** + * Returns the maximum size of an uploaded file as configured in php.ini. + * + * @return int|float The maximum size of an uploaded file in bytes (returns float if size > PHP_INT_MAX) + */ + public static function getMaxFilesize() + { + $sizePostMax = self::parseFilesize(\ini_get('post_max_size')); + $sizeUploadMax = self::parseFilesize(\ini_get('upload_max_filesize')); + + return min($sizePostMax ?: \PHP_INT_MAX, $sizeUploadMax ?: \PHP_INT_MAX); + } + + /** + * Returns the given size from an ini value in bytes. + * + * @return int|float Returns float if size > PHP_INT_MAX + */ + private static function parseFilesize(string $size) + { + if ('' === $size) { + return 0; + } + + $size = strtolower($size); + + $max = ltrim($size, '+'); + if (str_starts_with($max, '0x')) { + $max = \intval($max, 16); + } elseif (str_starts_with($max, '0')) { + $max = \intval($max, 8); + } else { + $max = (int) $max; + } + + switch (substr($size, -1)) { + case 't': $max *= 1024; + // no break + case 'g': $max *= 1024; + // no break + case 'm': $max *= 1024; + // no break + case 'k': $max *= 1024; + } + + return $max; + } + + /** + * Returns an informative upload error message. + * + * @return string + */ + public function getErrorMessage() + { + static $errors = [ + \UPLOAD_ERR_INI_SIZE => 'The file "%s" exceeds your upload_max_filesize ini directive (limit is %d KiB).', + \UPLOAD_ERR_FORM_SIZE => 'The file "%s" exceeds the upload limit defined in your form.', + \UPLOAD_ERR_PARTIAL => 'The file "%s" was only partially uploaded.', + \UPLOAD_ERR_NO_FILE => 'No file was uploaded.', + \UPLOAD_ERR_CANT_WRITE => 'The file "%s" could not be written on disk.', + \UPLOAD_ERR_NO_TMP_DIR => 'File could not be uploaded: missing temporary directory.', + \UPLOAD_ERR_EXTENSION => 'File upload was stopped by a PHP extension.', + ]; + + $errorCode = $this->error; + $maxFilesize = \UPLOAD_ERR_INI_SIZE === $errorCode ? self::getMaxFilesize() / 1024 : 0; + $message = $errors[$errorCode] ?? 'The file "%s" was not uploaded due to an unknown error.'; + + return sprintf($message, $this->getClientOriginalName(), $maxFilesize); + } +} diff --git a/src/vendor/symfony/http-foundation/FileBag.php b/src/vendor/symfony/http-foundation/FileBag.php new file mode 100644 index 0000000..ff5ab77 --- /dev/null +++ b/src/vendor/symfony/http-foundation/FileBag.php @@ -0,0 +1,140 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\HttpFoundation\File\UploadedFile; + +/** + * FileBag is a container for uploaded files. + * + * @author Fabien Potencier + * @author Bulat Shakirzyanov + */ +class FileBag extends ParameterBag +{ + private const FILE_KEYS = ['error', 'name', 'size', 'tmp_name', 'type']; + + /** + * @param array|UploadedFile[] $parameters An array of HTTP files + */ + public function __construct(array $parameters = []) + { + $this->replace($parameters); + } + + /** + * {@inheritdoc} + */ + public function replace(array $files = []) + { + $this->parameters = []; + $this->add($files); + } + + /** + * {@inheritdoc} + */ + public function set(string $key, $value) + { + if (!\is_array($value) && !$value instanceof UploadedFile) { + throw new \InvalidArgumentException('An uploaded file must be an array or an instance of UploadedFile.'); + } + + parent::set($key, $this->convertFileInformation($value)); + } + + /** + * {@inheritdoc} + */ + public function add(array $files = []) + { + foreach ($files as $key => $file) { + $this->set($key, $file); + } + } + + /** + * Converts uploaded files to UploadedFile instances. + * + * @param array|UploadedFile $file A (multi-dimensional) array of uploaded file information + * + * @return UploadedFile[]|UploadedFile|null + */ + protected function convertFileInformation($file) + { + if ($file instanceof UploadedFile) { + return $file; + } + + $file = $this->fixPhpFilesArray($file); + $keys = array_keys($file); + sort($keys); + + if (self::FILE_KEYS == $keys) { + if (\UPLOAD_ERR_NO_FILE == $file['error']) { + $file = null; + } else { + $file = new UploadedFile($file['tmp_name'], $file['name'], $file['type'], $file['error'], false); + } + } else { + $file = array_map(function ($v) { return $v instanceof UploadedFile || \is_array($v) ? $this->convertFileInformation($v) : $v; }, $file); + if (array_keys($keys) === $keys) { + $file = array_filter($file); + } + } + + return $file; + } + + /** + * Fixes a malformed PHP $_FILES array. + * + * PHP has a bug that the format of the $_FILES array differs, depending on + * whether the uploaded file fields had normal field names or array-like + * field names ("normal" vs. "parent[child]"). + * + * This method fixes the array to look like the "normal" $_FILES array. + * + * It's safe to pass an already converted array, in which case this method + * just returns the original array unmodified. + * + * @return array + */ + protected function fixPhpFilesArray(array $data) + { + // Remove extra key added by PHP 8.1. + unset($data['full_path']); + $keys = array_keys($data); + sort($keys); + + if (self::FILE_KEYS != $keys || !isset($data['name']) || !\is_array($data['name'])) { + return $data; + } + + $files = $data; + foreach (self::FILE_KEYS as $k) { + unset($files[$k]); + } + + foreach ($data['name'] as $key => $name) { + $files[$key] = $this->fixPhpFilesArray([ + 'error' => $data['error'][$key], + 'name' => $name, + 'type' => $data['type'][$key], + 'tmp_name' => $data['tmp_name'][$key], + 'size' => $data['size'][$key], + ]); + } + + return $files; + } +} diff --git a/src/vendor/symfony/http-foundation/HeaderBag.php b/src/vendor/symfony/http-foundation/HeaderBag.php new file mode 100644 index 0000000..4683a68 --- /dev/null +++ b/src/vendor/symfony/http-foundation/HeaderBag.php @@ -0,0 +1,295 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * HeaderBag is a container for HTTP headers. + * + * @author Fabien Potencier + * + * @implements \IteratorAggregate> + */ +class HeaderBag implements \IteratorAggregate, \Countable +{ + protected const UPPER = '_ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + protected const LOWER = '-abcdefghijklmnopqrstuvwxyz'; + + /** + * @var array> + */ + protected $headers = []; + protected $cacheControl = []; + + public function __construct(array $headers = []) + { + foreach ($headers as $key => $values) { + $this->set($key, $values); + } + } + + /** + * Returns the headers as a string. + * + * @return string + */ + public function __toString() + { + if (!$headers = $this->all()) { + return ''; + } + + ksort($headers); + $max = max(array_map('strlen', array_keys($headers))) + 1; + $content = ''; + foreach ($headers as $name => $values) { + $name = ucwords($name, '-'); + foreach ($values as $value) { + $content .= sprintf("%-{$max}s %s\r\n", $name.':', $value); + } + } + + return $content; + } + + /** + * Returns the headers. + * + * @param string|null $key The name of the headers to return or null to get them all + * + * @return array>|array + */ + public function all(string $key = null) + { + if (null !== $key) { + return $this->headers[strtr($key, self::UPPER, self::LOWER)] ?? []; + } + + return $this->headers; + } + + /** + * Returns the parameter keys. + * + * @return string[] + */ + public function keys() + { + return array_keys($this->all()); + } + + /** + * Replaces the current HTTP headers by a new set. + */ + public function replace(array $headers = []) + { + $this->headers = []; + $this->add($headers); + } + + /** + * Adds new headers the current HTTP headers set. + */ + public function add(array $headers) + { + foreach ($headers as $key => $values) { + $this->set($key, $values); + } + } + + /** + * Returns the first header by name or the default one. + * + * @return string|null + */ + public function get(string $key, string $default = null) + { + $headers = $this->all($key); + + if (!$headers) { + return $default; + } + + if (null === $headers[0]) { + return null; + } + + return (string) $headers[0]; + } + + /** + * Sets a header by name. + * + * @param string|string[]|null $values The value or an array of values + * @param bool $replace Whether to replace the actual value or not (true by default) + */ + public function set(string $key, $values, bool $replace = true) + { + $key = strtr($key, self::UPPER, self::LOWER); + + if (\is_array($values)) { + $values = array_values($values); + + if (true === $replace || !isset($this->headers[$key])) { + $this->headers[$key] = $values; + } else { + $this->headers[$key] = array_merge($this->headers[$key], $values); + } + } else { + if (true === $replace || !isset($this->headers[$key])) { + $this->headers[$key] = [$values]; + } else { + $this->headers[$key][] = $values; + } + } + + if ('cache-control' === $key) { + $this->cacheControl = $this->parseCacheControl(implode(', ', $this->headers[$key])); + } + } + + /** + * Returns true if the HTTP header is defined. + * + * @return bool + */ + public function has(string $key) + { + return \array_key_exists(strtr($key, self::UPPER, self::LOWER), $this->all()); + } + + /** + * Returns true if the given HTTP header contains the given value. + * + * @return bool + */ + public function contains(string $key, string $value) + { + return \in_array($value, $this->all($key)); + } + + /** + * Removes a header. + */ + public function remove(string $key) + { + $key = strtr($key, self::UPPER, self::LOWER); + + unset($this->headers[$key]); + + if ('cache-control' === $key) { + $this->cacheControl = []; + } + } + + /** + * Returns the HTTP header value converted to a date. + * + * @return \DateTimeInterface|null + * + * @throws \RuntimeException When the HTTP header is not parseable + */ + public function getDate(string $key, \DateTime $default = null) + { + if (null === $value = $this->get($key)) { + return $default; + } + + if (false === $date = \DateTime::createFromFormat(\DATE_RFC2822, $value)) { + throw new \RuntimeException(sprintf('The "%s" HTTP header is not parseable (%s).', $key, $value)); + } + + return $date; + } + + /** + * Adds a custom Cache-Control directive. + * + * @param bool|string $value The Cache-Control directive value + */ + public function addCacheControlDirective(string $key, $value = true) + { + $this->cacheControl[$key] = $value; + + $this->set('Cache-Control', $this->getCacheControlHeader()); + } + + /** + * Returns true if the Cache-Control directive is defined. + * + * @return bool + */ + public function hasCacheControlDirective(string $key) + { + return \array_key_exists($key, $this->cacheControl); + } + + /** + * Returns a Cache-Control directive value by name. + * + * @return bool|string|null + */ + public function getCacheControlDirective(string $key) + { + return $this->cacheControl[$key] ?? null; + } + + /** + * Removes a Cache-Control directive. + */ + public function removeCacheControlDirective(string $key) + { + unset($this->cacheControl[$key]); + + $this->set('Cache-Control', $this->getCacheControlHeader()); + } + + /** + * Returns an iterator for headers. + * + * @return \ArrayIterator> + */ + #[\ReturnTypeWillChange] + public function getIterator() + { + return new \ArrayIterator($this->headers); + } + + /** + * Returns the number of headers. + * + * @return int + */ + #[\ReturnTypeWillChange] + public function count() + { + return \count($this->headers); + } + + protected function getCacheControlHeader() + { + ksort($this->cacheControl); + + return HeaderUtils::toString($this->cacheControl, ','); + } + + /** + * Parses a Cache-Control HTTP header. + * + * @return array + */ + protected function parseCacheControl(string $header) + { + $parts = HeaderUtils::split($header, ',='); + + return HeaderUtils::combine($parts); + } +} diff --git a/src/vendor/symfony/http-foundation/HeaderUtils.php b/src/vendor/symfony/http-foundation/HeaderUtils.php new file mode 100644 index 0000000..46b1e6a --- /dev/null +++ b/src/vendor/symfony/http-foundation/HeaderUtils.php @@ -0,0 +1,293 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * HTTP header utility functions. + * + * @author Christian Schmidt + */ +class HeaderUtils +{ + public const DISPOSITION_ATTACHMENT = 'attachment'; + public const DISPOSITION_INLINE = 'inline'; + + /** + * This class should not be instantiated. + */ + private function __construct() + { + } + + /** + * Splits an HTTP header by one or more separators. + * + * Example: + * + * HeaderUtils::split("da, en-gb;q=0.8", ",;") + * // => ['da'], ['en-gb', 'q=0.8']] + * + * @param string $separators List of characters to split on, ordered by + * precedence, e.g. ",", ";=", or ",;=" + * + * @return array Nested array with as many levels as there are characters in + * $separators + */ + public static function split(string $header, string $separators): array + { + $quotedSeparators = preg_quote($separators, '/'); + + preg_match_all(' + / + (?!\s) + (?: + # quoted-string + "(?:[^"\\\\]|\\\\.)*(?:"|\\\\|$) + | + # token + [^"'.$quotedSeparators.']+ + )+ + (?['.$quotedSeparators.']) + \s* + /x', trim($header), $matches, \PREG_SET_ORDER); + + return self::groupParts($matches, $separators); + } + + /** + * Combines an array of arrays into one associative array. + * + * Each of the nested arrays should have one or two elements. The first + * value will be used as the keys in the associative array, and the second + * will be used as the values, or true if the nested array only contains one + * element. Array keys are lowercased. + * + * Example: + * + * HeaderUtils::combine([["foo", "abc"], ["bar"]]) + * // => ["foo" => "abc", "bar" => true] + */ + public static function combine(array $parts): array + { + $assoc = []; + foreach ($parts as $part) { + $name = strtolower($part[0]); + $value = $part[1] ?? true; + $assoc[$name] = $value; + } + + return $assoc; + } + + /** + * Joins an associative array into a string for use in an HTTP header. + * + * The key and value of each entry are joined with "=", and all entries + * are joined with the specified separator and an additional space (for + * readability). Values are quoted if necessary. + * + * Example: + * + * HeaderUtils::toString(["foo" => "abc", "bar" => true, "baz" => "a b c"], ",") + * // => 'foo=abc, bar, baz="a b c"' + */ + public static function toString(array $assoc, string $separator): string + { + $parts = []; + foreach ($assoc as $name => $value) { + if (true === $value) { + $parts[] = $name; + } else { + $parts[] = $name.'='.self::quote($value); + } + } + + return implode($separator.' ', $parts); + } + + /** + * Encodes a string as a quoted string, if necessary. + * + * If a string contains characters not allowed by the "token" construct in + * the HTTP specification, it is backslash-escaped and enclosed in quotes + * to match the "quoted-string" construct. + */ + public static function quote(string $s): string + { + if (preg_match('/^[a-z0-9!#$%&\'*.^_`|~-]+$/i', $s)) { + return $s; + } + + return '"'.addcslashes($s, '"\\"').'"'; + } + + /** + * Decodes a quoted string. + * + * If passed an unquoted string that matches the "token" construct (as + * defined in the HTTP specification), it is passed through verbatim. + */ + public static function unquote(string $s): string + { + return preg_replace('/\\\\(.)|"/', '$1', $s); + } + + /** + * Generates an HTTP Content-Disposition field-value. + * + * @param string $disposition One of "inline" or "attachment" + * @param string $filename A unicode string + * @param string $filenameFallback A string containing only ASCII characters that + * is semantically equivalent to $filename. If the filename is already ASCII, + * it can be omitted, or just copied from $filename + * + * @throws \InvalidArgumentException + * + * @see RFC 6266 + */ + public static function makeDisposition(string $disposition, string $filename, string $filenameFallback = ''): string + { + if (!\in_array($disposition, [self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE])) { + throw new \InvalidArgumentException(sprintf('The disposition must be either "%s" or "%s".', self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE)); + } + + if ('' === $filenameFallback) { + $filenameFallback = $filename; + } + + // filenameFallback is not ASCII. + if (!preg_match('/^[\x20-\x7e]*$/', $filenameFallback)) { + throw new \InvalidArgumentException('The filename fallback must only contain ASCII characters.'); + } + + // percent characters aren't safe in fallback. + if (str_contains($filenameFallback, '%')) { + throw new \InvalidArgumentException('The filename fallback cannot contain the "%" character.'); + } + + // path separators aren't allowed in either. + if (str_contains($filename, '/') || str_contains($filename, '\\') || str_contains($filenameFallback, '/') || str_contains($filenameFallback, '\\')) { + throw new \InvalidArgumentException('The filename and the fallback cannot contain the "/" and "\\" characters.'); + } + + $params = ['filename' => $filenameFallback]; + if ($filename !== $filenameFallback) { + $params['filename*'] = "utf-8''".rawurlencode($filename); + } + + return $disposition.'; '.self::toString($params, ';'); + } + + /** + * Like parse_str(), but preserves dots in variable names. + */ + public static function parseQuery(string $query, bool $ignoreBrackets = false, string $separator = '&'): array + { + $q = []; + + foreach (explode($separator, $query) as $v) { + if (false !== $i = strpos($v, "\0")) { + $v = substr($v, 0, $i); + } + + if (false === $i = strpos($v, '=')) { + $k = urldecode($v); + $v = ''; + } else { + $k = urldecode(substr($v, 0, $i)); + $v = substr($v, $i); + } + + if (false !== $i = strpos($k, "\0")) { + $k = substr($k, 0, $i); + } + + $k = ltrim($k, ' '); + + if ($ignoreBrackets) { + $q[$k][] = urldecode(substr($v, 1)); + + continue; + } + + if (false === $i = strpos($k, '[')) { + $q[] = bin2hex($k).$v; + } else { + $q[] = bin2hex(substr($k, 0, $i)).rawurlencode(substr($k, $i)).$v; + } + } + + if ($ignoreBrackets) { + return $q; + } + + parse_str(implode('&', $q), $q); + + $query = []; + + foreach ($q as $k => $v) { + if (false !== $i = strpos($k, '_')) { + $query[substr_replace($k, hex2bin(substr($k, 0, $i)).'[', 0, 1 + $i)] = $v; + } else { + $query[hex2bin($k)] = $v; + } + } + + return $query; + } + + private static function groupParts(array $matches, string $separators, bool $first = true): array + { + $separator = $separators[0]; + $partSeparators = substr($separators, 1); + + $i = 0; + $partMatches = []; + $previousMatchWasSeparator = false; + foreach ($matches as $match) { + if (!$first && $previousMatchWasSeparator && isset($match['separator']) && $match['separator'] === $separator) { + $previousMatchWasSeparator = true; + $partMatches[$i][] = $match; + } elseif (isset($match['separator']) && $match['separator'] === $separator) { + $previousMatchWasSeparator = true; + ++$i; + } else { + $previousMatchWasSeparator = false; + $partMatches[$i][] = $match; + } + } + + $parts = []; + if ($partSeparators) { + foreach ($partMatches as $matches) { + $parts[] = self::groupParts($matches, $partSeparators, false); + } + } else { + foreach ($partMatches as $matches) { + $parts[] = self::unquote($matches[0][0]); + } + + if (!$first && 2 < \count($parts)) { + $parts = [ + $parts[0], + implode($separator, \array_slice($parts, 1)), + ]; + } + } + + return $parts; + } +} diff --git a/src/vendor/symfony/http-foundation/InputBag.php b/src/vendor/symfony/http-foundation/InputBag.php new file mode 100644 index 0000000..a9d3cd8 --- /dev/null +++ b/src/vendor/symfony/http-foundation/InputBag.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\HttpFoundation\Exception\BadRequestException; + +/** + * InputBag is a container for user input values such as $_GET, $_POST, $_REQUEST, and $_COOKIE. + * + * @author Saif Eddin Gmati + */ +final class InputBag extends ParameterBag +{ + /** + * Returns a scalar input value by name. + * + * @param string|int|float|bool|null $default The default value if the input key does not exist + * + * @return string|int|float|bool|null + */ + public function get(string $key, $default = null) + { + if (null !== $default && !\is_scalar($default) && !(\is_object($default) && method_exists($default, '__toString'))) { + trigger_deprecation('symfony/http-foundation', '5.1', 'Passing a non-scalar value as 2nd argument to "%s()" is deprecated, pass a scalar or null instead.', __METHOD__); + } + + $value = parent::get($key, $this); + + if (null !== $value && $this !== $value && !\is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { + trigger_deprecation('symfony/http-foundation', '5.1', 'Retrieving a non-scalar value from "%s()" is deprecated, and will throw a "%s" exception in Symfony 6.0, use "%s::all($key)" instead.', __METHOD__, BadRequestException::class, __CLASS__); + } + + return $this === $value ? $default : $value; + } + + /** + * {@inheritdoc} + */ + public function all(string $key = null): array + { + return parent::all($key); + } + + /** + * Replaces the current input values by a new set. + */ + public function replace(array $inputs = []) + { + $this->parameters = []; + $this->add($inputs); + } + + /** + * Adds input values. + */ + public function add(array $inputs = []) + { + foreach ($inputs as $input => $value) { + $this->set($input, $value); + } + } + + /** + * Sets an input by name. + * + * @param string|int|float|bool|array|null $value + */ + public function set(string $key, $value) + { + if (null !== $value && !\is_scalar($value) && !\is_array($value) && !method_exists($value, '__toString')) { + trigger_deprecation('symfony/http-foundation', '5.1', 'Passing "%s" as a 2nd Argument to "%s()" is deprecated, pass a scalar, array, or null instead.', get_debug_type($value), __METHOD__); + } + + $this->parameters[$key] = $value; + } + + /** + * {@inheritdoc} + */ + public function filter(string $key, $default = null, int $filter = \FILTER_DEFAULT, $options = []) + { + $value = $this->has($key) ? $this->all()[$key] : $default; + + // Always turn $options into an array - this allows filter_var option shortcuts. + if (!\is_array($options) && $options) { + $options = ['flags' => $options]; + } + + if (\is_array($value) && !(($options['flags'] ?? 0) & (\FILTER_REQUIRE_ARRAY | \FILTER_FORCE_ARRAY))) { + trigger_deprecation('symfony/http-foundation', '5.1', 'Filtering an array value with "%s()" without passing the FILTER_REQUIRE_ARRAY or FILTER_FORCE_ARRAY flag is deprecated', __METHOD__); + + if (!isset($options['flags'])) { + $options['flags'] = \FILTER_REQUIRE_ARRAY; + } + } + + if ((\FILTER_CALLBACK & $filter) && !(($options['options'] ?? null) instanceof \Closure)) { + trigger_deprecation('symfony/http-foundation', '5.2', 'Not passing a Closure together with FILTER_CALLBACK to "%s()" is deprecated. Wrap your filter in a closure instead.', __METHOD__); + // throw new \InvalidArgumentException(sprintf('A Closure must be passed to "%s()" when FILTER_CALLBACK is used, "%s" given.', __METHOD__, get_debug_type($options['options'] ?? null))); + } + + return filter_var($value, $filter, $options); + } +} diff --git a/src/vendor/symfony/http-foundation/IpUtils.php b/src/vendor/symfony/http-foundation/IpUtils.php new file mode 100644 index 0000000..49d9a9d --- /dev/null +++ b/src/vendor/symfony/http-foundation/IpUtils.php @@ -0,0 +1,216 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Http utility functions. + * + * @author Fabien Potencier + */ +class IpUtils +{ + private static $checkedIps = []; + + /** + * This class should not be instantiated. + */ + private function __construct() + { + } + + /** + * Checks if an IPv4 or IPv6 address is contained in the list of given IPs or subnets. + * + * @param string|array $ips List of IPs or subnets (can be a string if only a single one) + * + * @return bool + */ + public static function checkIp(?string $requestIp, $ips) + { + if (null === $requestIp) { + trigger_deprecation('symfony/http-foundation', '5.4', 'Passing null as $requestIp to "%s()" is deprecated, pass an empty string instead.', __METHOD__); + + return false; + } + + if (!\is_array($ips)) { + $ips = [$ips]; + } + + $method = substr_count($requestIp, ':') > 1 ? 'checkIp6' : 'checkIp4'; + + foreach ($ips as $ip) { + if (self::$method($requestIp, $ip)) { + return true; + } + } + + return false; + } + + /** + * Compares two IPv4 addresses. + * In case a subnet is given, it checks if it contains the request IP. + * + * @param string $ip IPv4 address or subnet in CIDR notation + * + * @return bool Whether the request IP matches the IP, or whether the request IP is within the CIDR subnet + */ + public static function checkIp4(?string $requestIp, string $ip) + { + if (null === $requestIp) { + trigger_deprecation('symfony/http-foundation', '5.4', 'Passing null as $requestIp to "%s()" is deprecated, pass an empty string instead.', __METHOD__); + + return false; + } + + $cacheKey = $requestIp.'-'.$ip.'-v4'; + if (isset(self::$checkedIps[$cacheKey])) { + return self::$checkedIps[$cacheKey]; + } + + if (!filter_var($requestIp, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)) { + return self::$checkedIps[$cacheKey] = false; + } + + if (str_contains($ip, '/')) { + [$address, $netmask] = explode('/', $ip, 2); + + if ('0' === $netmask) { + return self::$checkedIps[$cacheKey] = false !== filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4); + } + + if ($netmask < 0 || $netmask > 32) { + return self::$checkedIps[$cacheKey] = false; + } + } else { + $address = $ip; + $netmask = 32; + } + + if (false === ip2long($address)) { + return self::$checkedIps[$cacheKey] = false; + } + + return self::$checkedIps[$cacheKey] = 0 === substr_compare(sprintf('%032b', ip2long($requestIp)), sprintf('%032b', ip2long($address)), 0, $netmask); + } + + /** + * Compares two IPv6 addresses. + * In case a subnet is given, it checks if it contains the request IP. + * + * @author David Soria Parra + * + * @see https://github.com/dsp/v6tools + * + * @param string $ip IPv6 address or subnet in CIDR notation + * + * @return bool + * + * @throws \RuntimeException When IPV6 support is not enabled + */ + public static function checkIp6(?string $requestIp, string $ip) + { + if (null === $requestIp) { + trigger_deprecation('symfony/http-foundation', '5.4', 'Passing null as $requestIp to "%s()" is deprecated, pass an empty string instead.', __METHOD__); + + return false; + } + + $cacheKey = $requestIp.'-'.$ip.'-v6'; + if (isset(self::$checkedIps[$cacheKey])) { + return self::$checkedIps[$cacheKey]; + } + + if (!((\extension_loaded('sockets') && \defined('AF_INET6')) || @inet_pton('::1'))) { + throw new \RuntimeException('Unable to check Ipv6. Check that PHP was not compiled with option "disable-ipv6".'); + } + + // Check to see if we were given a IP4 $requestIp or $ip by mistake + if (!filter_var($requestIp, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) { + return self::$checkedIps[$cacheKey] = false; + } + + if (str_contains($ip, '/')) { + [$address, $netmask] = explode('/', $ip, 2); + + if (!filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) { + return self::$checkedIps[$cacheKey] = false; + } + + if ('0' === $netmask) { + return (bool) unpack('n*', @inet_pton($address)); + } + + if ($netmask < 1 || $netmask > 128) { + return self::$checkedIps[$cacheKey] = false; + } + } else { + if (!filter_var($ip, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) { + return self::$checkedIps[$cacheKey] = false; + } + + $address = $ip; + $netmask = 128; + } + + $bytesAddr = unpack('n*', @inet_pton($address)); + $bytesTest = unpack('n*', @inet_pton($requestIp)); + + if (!$bytesAddr || !$bytesTest) { + return self::$checkedIps[$cacheKey] = false; + } + + for ($i = 1, $ceil = ceil($netmask / 16); $i <= $ceil; ++$i) { + $left = $netmask - 16 * ($i - 1); + $left = ($left <= 16) ? $left : 16; + $mask = ~(0xFFFF >> $left) & 0xFFFF; + if (($bytesAddr[$i] & $mask) != ($bytesTest[$i] & $mask)) { + return self::$checkedIps[$cacheKey] = false; + } + } + + return self::$checkedIps[$cacheKey] = true; + } + + /** + * Anonymizes an IP/IPv6. + * + * Removes the last byte for v4 and the last 8 bytes for v6 IPs + */ + public static function anonymize(string $ip): string + { + $wrappedIPv6 = false; + if ('[' === substr($ip, 0, 1) && ']' === substr($ip, -1, 1)) { + $wrappedIPv6 = true; + $ip = substr($ip, 1, -1); + } + + $packedAddress = inet_pton($ip); + if (4 === \strlen($packedAddress)) { + $mask = '255.255.255.0'; + } elseif ($ip === inet_ntop($packedAddress & inet_pton('::ffff:ffff:ffff'))) { + $mask = '::ffff:ffff:ff00'; + } elseif ($ip === inet_ntop($packedAddress & inet_pton('::ffff:ffff'))) { + $mask = '::ffff:ff00'; + } else { + $mask = 'ffff:ffff:ffff:ffff:0000:0000:0000:0000'; + } + $ip = inet_ntop($packedAddress & inet_pton($mask)); + + if ($wrappedIPv6) { + $ip = '['.$ip.']'; + } + + return $ip; + } +} diff --git a/src/vendor/symfony/http-foundation/JsonResponse.php b/src/vendor/symfony/http-foundation/JsonResponse.php new file mode 100644 index 0000000..501a638 --- /dev/null +++ b/src/vendor/symfony/http-foundation/JsonResponse.php @@ -0,0 +1,221 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Response represents an HTTP response in JSON format. + * + * Note that this class does not force the returned JSON content to be an + * object. It is however recommended that you do return an object as it + * protects yourself against XSSI and JSON-JavaScript Hijacking. + * + * @see https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/AJAX_Security_Cheat_Sheet.md#always-return-json-with-an-object-on-the-outside + * + * @author Igor Wiedler + */ +class JsonResponse extends Response +{ + protected $data; + protected $callback; + + // Encode <, >, ', &, and " characters in the JSON, making it also safe to be embedded into HTML. + // 15 === JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT + public const DEFAULT_ENCODING_OPTIONS = 15; + + protected $encodingOptions = self::DEFAULT_ENCODING_OPTIONS; + + /** + * @param mixed $data The response data + * @param int $status The response status code + * @param array $headers An array of response headers + * @param bool $json If the data is already a JSON string + */ + public function __construct($data = null, int $status = 200, array $headers = [], bool $json = false) + { + parent::__construct('', $status, $headers); + + if ($json && !\is_string($data) && !is_numeric($data) && !\is_callable([$data, '__toString'])) { + throw new \TypeError(sprintf('"%s": If $json is set to true, argument $data must be a string or object implementing __toString(), "%s" given.', __METHOD__, get_debug_type($data))); + } + + if (null === $data) { + $data = new \ArrayObject(); + } + + $json ? $this->setJson($data) : $this->setData($data); + } + + /** + * Factory method for chainability. + * + * Example: + * + * return JsonResponse::create(['key' => 'value']) + * ->setSharedMaxAge(300); + * + * @param mixed $data The JSON response data + * @param int $status The response status code + * @param array $headers An array of response headers + * + * @return static + * + * @deprecated since Symfony 5.1, use __construct() instead. + */ + public static function create($data = null, int $status = 200, array $headers = []) + { + trigger_deprecation('symfony/http-foundation', '5.1', 'The "%s()" method is deprecated, use "new %s()" instead.', __METHOD__, static::class); + + return new static($data, $status, $headers); + } + + /** + * Factory method for chainability. + * + * Example: + * + * return JsonResponse::fromJsonString('{"key": "value"}') + * ->setSharedMaxAge(300); + * + * @param string $data The JSON response string + * @param int $status The response status code + * @param array $headers An array of response headers + * + * @return static + */ + public static function fromJsonString(string $data, int $status = 200, array $headers = []) + { + return new static($data, $status, $headers, true); + } + + /** + * Sets the JSONP callback. + * + * @param string|null $callback The JSONP callback or null to use none + * + * @return $this + * + * @throws \InvalidArgumentException When the callback name is not valid + */ + public function setCallback(string $callback = null) + { + if (null !== $callback) { + // partially taken from https://geekality.net/2011/08/03/valid-javascript-identifier/ + // partially taken from https://github.com/willdurand/JsonpCallbackValidator + // JsonpCallbackValidator is released under the MIT License. See https://github.com/willdurand/JsonpCallbackValidator/blob/v1.1.0/LICENSE for details. + // (c) William Durand + $pattern = '/^[$_\p{L}][$_\p{L}\p{Mn}\p{Mc}\p{Nd}\p{Pc}\x{200C}\x{200D}]*(?:\[(?:"(?:\\\.|[^"\\\])*"|\'(?:\\\.|[^\'\\\])*\'|\d+)\])*?$/u'; + $reserved = [ + 'break', 'do', 'instanceof', 'typeof', 'case', 'else', 'new', 'var', 'catch', 'finally', 'return', 'void', 'continue', 'for', 'switch', 'while', + 'debugger', 'function', 'this', 'with', 'default', 'if', 'throw', 'delete', 'in', 'try', 'class', 'enum', 'extends', 'super', 'const', 'export', + 'import', 'implements', 'let', 'private', 'public', 'yield', 'interface', 'package', 'protected', 'static', 'null', 'true', 'false', + ]; + $parts = explode('.', $callback); + foreach ($parts as $part) { + if (!preg_match($pattern, $part) || \in_array($part, $reserved, true)) { + throw new \InvalidArgumentException('The callback name is not valid.'); + } + } + } + + $this->callback = $callback; + + return $this->update(); + } + + /** + * Sets a raw string containing a JSON document to be sent. + * + * @return $this + */ + public function setJson(string $json) + { + $this->data = $json; + + return $this->update(); + } + + /** + * Sets the data to be sent as JSON. + * + * @param mixed $data + * + * @return $this + * + * @throws \InvalidArgumentException + */ + public function setData($data = []) + { + try { + $data = json_encode($data, $this->encodingOptions); + } catch (\Exception $e) { + if ('Exception' === \get_class($e) && str_starts_with($e->getMessage(), 'Failed calling ')) { + throw $e->getPrevious() ?: $e; + } + throw $e; + } + + if (\PHP_VERSION_ID >= 70300 && (\JSON_THROW_ON_ERROR & $this->encodingOptions)) { + return $this->setJson($data); + } + + if (\JSON_ERROR_NONE !== json_last_error()) { + throw new \InvalidArgumentException(json_last_error_msg()); + } + + return $this->setJson($data); + } + + /** + * Returns options used while encoding data to JSON. + * + * @return int + */ + public function getEncodingOptions() + { + return $this->encodingOptions; + } + + /** + * Sets options used while encoding data to JSON. + * + * @return $this + */ + public function setEncodingOptions(int $encodingOptions) + { + $this->encodingOptions = $encodingOptions; + + return $this->setData(json_decode($this->data)); + } + + /** + * Updates the content and headers according to the JSON data and callback. + * + * @return $this + */ + protected function update() + { + if (null !== $this->callback) { + // Not using application/javascript for compatibility reasons with older browsers. + $this->headers->set('Content-Type', 'text/javascript'); + + return $this->setContent(sprintf('/**/%s(%s);', $this->callback, $this->data)); + } + + // Only set the header when there is none or when it equals 'text/javascript' (from a previous update with callback) + // in order to not overwrite a custom definition. + if (!$this->headers->has('Content-Type') || 'text/javascript' === $this->headers->get('Content-Type')) { + $this->headers->set('Content-Type', 'application/json'); + } + + return $this->setContent($this->data); + } +} diff --git a/src/vendor/symfony/http-foundation/LICENSE b/src/vendor/symfony/http-foundation/LICENSE new file mode 100644 index 0000000..0138f8f --- /dev/null +++ b/src/vendor/symfony/http-foundation/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/vendor/symfony/http-foundation/ParameterBag.php b/src/vendor/symfony/http-foundation/ParameterBag.php new file mode 100644 index 0000000..e1f89d6 --- /dev/null +++ b/src/vendor/symfony/http-foundation/ParameterBag.php @@ -0,0 +1,228 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\HttpFoundation\Exception\BadRequestException; + +/** + * ParameterBag is a container for key/value pairs. + * + * @author Fabien Potencier + * + * @implements \IteratorAggregate + */ +class ParameterBag implements \IteratorAggregate, \Countable +{ + /** + * Parameter storage. + */ + protected $parameters; + + public function __construct(array $parameters = []) + { + $this->parameters = $parameters; + } + + /** + * Returns the parameters. + * + * @param string|null $key The name of the parameter to return or null to get them all + * + * @return array + */ + public function all(/* string $key = null */) + { + $key = \func_num_args() > 0 ? func_get_arg(0) : null; + + if (null === $key) { + return $this->parameters; + } + + if (!\is_array($value = $this->parameters[$key] ?? [])) { + throw new BadRequestException(sprintf('Unexpected value for parameter "%s": expecting "array", got "%s".', $key, get_debug_type($value))); + } + + return $value; + } + + /** + * Returns the parameter keys. + * + * @return array + */ + public function keys() + { + return array_keys($this->parameters); + } + + /** + * Replaces the current parameters by a new set. + */ + public function replace(array $parameters = []) + { + $this->parameters = $parameters; + } + + /** + * Adds parameters. + */ + public function add(array $parameters = []) + { + $this->parameters = array_replace($this->parameters, $parameters); + } + + /** + * Returns a parameter by name. + * + * @param mixed $default The default value if the parameter key does not exist + * + * @return mixed + */ + public function get(string $key, $default = null) + { + return \array_key_exists($key, $this->parameters) ? $this->parameters[$key] : $default; + } + + /** + * Sets a parameter by name. + * + * @param mixed $value The value + */ + public function set(string $key, $value) + { + $this->parameters[$key] = $value; + } + + /** + * Returns true if the parameter is defined. + * + * @return bool + */ + public function has(string $key) + { + return \array_key_exists($key, $this->parameters); + } + + /** + * Removes a parameter. + */ + public function remove(string $key) + { + unset($this->parameters[$key]); + } + + /** + * Returns the alphabetic characters of the parameter value. + * + * @return string + */ + public function getAlpha(string $key, string $default = '') + { + return preg_replace('/[^[:alpha:]]/', '', $this->get($key, $default)); + } + + /** + * Returns the alphabetic characters and digits of the parameter value. + * + * @return string + */ + public function getAlnum(string $key, string $default = '') + { + return preg_replace('/[^[:alnum:]]/', '', $this->get($key, $default)); + } + + /** + * Returns the digits of the parameter value. + * + * @return string + */ + public function getDigits(string $key, string $default = '') + { + // we need to remove - and + because they're allowed in the filter + return str_replace(['-', '+'], '', $this->filter($key, $default, \FILTER_SANITIZE_NUMBER_INT)); + } + + /** + * Returns the parameter value converted to integer. + * + * @return int + */ + public function getInt(string $key, int $default = 0) + { + return (int) $this->get($key, $default); + } + + /** + * Returns the parameter value converted to boolean. + * + * @return bool + */ + public function getBoolean(string $key, bool $default = false) + { + return $this->filter($key, $default, \FILTER_VALIDATE_BOOLEAN); + } + + /** + * Filter key. + * + * @param mixed $default Default = null + * @param int $filter FILTER_* constant + * @param mixed $options Filter options + * + * @see https://php.net/filter-var + * + * @return mixed + */ + public function filter(string $key, $default = null, int $filter = \FILTER_DEFAULT, $options = []) + { + $value = $this->get($key, $default); + + // Always turn $options into an array - this allows filter_var option shortcuts. + if (!\is_array($options) && $options) { + $options = ['flags' => $options]; + } + + // Add a convenience check for arrays. + if (\is_array($value) && !isset($options['flags'])) { + $options['flags'] = \FILTER_REQUIRE_ARRAY; + } + + if ((\FILTER_CALLBACK & $filter) && !(($options['options'] ?? null) instanceof \Closure)) { + trigger_deprecation('symfony/http-foundation', '5.2', 'Not passing a Closure together with FILTER_CALLBACK to "%s()" is deprecated. Wrap your filter in a closure instead.', __METHOD__); + // throw new \InvalidArgumentException(sprintf('A Closure must be passed to "%s()" when FILTER_CALLBACK is used, "%s" given.', __METHOD__, get_debug_type($options['options'] ?? null))); + } + + return filter_var($value, $filter, $options); + } + + /** + * Returns an iterator for parameters. + * + * @return \ArrayIterator + */ + #[\ReturnTypeWillChange] + public function getIterator() + { + return new \ArrayIterator($this->parameters); + } + + /** + * Returns the number of parameters. + * + * @return int + */ + #[\ReturnTypeWillChange] + public function count() + { + return \count($this->parameters); + } +} diff --git a/src/vendor/symfony/http-foundation/README.md b/src/vendor/symfony/http-foundation/README.md new file mode 100644 index 0000000..424f2c4 --- /dev/null +++ b/src/vendor/symfony/http-foundation/README.md @@ -0,0 +1,28 @@ +HttpFoundation Component +======================== + +The HttpFoundation component defines an object-oriented layer for the HTTP +specification. + +Sponsor +------- + +The HttpFoundation component for Symfony 5.4/6.0 is [backed][1] by [Laravel][2]. + +Laravel is a PHP web development framework that is passionate about maximum developer +happiness. Laravel is built using a variety of bespoke and Symfony based components. + +Help Symfony by [sponsoring][3] its development! + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/http_foundation.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) + +[1]: https://symfony.com/backers +[2]: https://laravel.com/ +[3]: https://symfony.com/sponsor diff --git a/src/vendor/symfony/http-foundation/RateLimiter/AbstractRequestRateLimiter.php b/src/vendor/symfony/http-foundation/RateLimiter/AbstractRequestRateLimiter.php new file mode 100644 index 0000000..a6dd993 --- /dev/null +++ b/src/vendor/symfony/http-foundation/RateLimiter/AbstractRequestRateLimiter.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\RateLimiter; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\RateLimiter\LimiterInterface; +use Symfony\Component\RateLimiter\Policy\NoLimiter; +use Symfony\Component\RateLimiter\RateLimit; + +/** + * An implementation of RequestRateLimiterInterface that + * fits most use-cases. + * + * @author Wouter de Jong + */ +abstract class AbstractRequestRateLimiter implements RequestRateLimiterInterface +{ + public function consume(Request $request): RateLimit + { + $limiters = $this->getLimiters($request); + if (0 === \count($limiters)) { + $limiters = [new NoLimiter()]; + } + + $minimalRateLimit = null; + foreach ($limiters as $limiter) { + $rateLimit = $limiter->consume(1); + + $minimalRateLimit = $minimalRateLimit ? self::getMinimalRateLimit($minimalRateLimit, $rateLimit) : $rateLimit; + } + + return $minimalRateLimit; + } + + public function reset(Request $request): void + { + foreach ($this->getLimiters($request) as $limiter) { + $limiter->reset(); + } + } + + /** + * @return LimiterInterface[] a set of limiters using keys extracted from the request + */ + abstract protected function getLimiters(Request $request): array; + + private static function getMinimalRateLimit(RateLimit $first, RateLimit $second): RateLimit + { + if ($first->isAccepted() !== $second->isAccepted()) { + return $first->isAccepted() ? $second : $first; + } + + $firstRemainingTokens = $first->getRemainingTokens(); + $secondRemainingTokens = $second->getRemainingTokens(); + + if ($firstRemainingTokens === $secondRemainingTokens) { + return $first->getRetryAfter() < $second->getRetryAfter() ? $second : $first; + } + + return $firstRemainingTokens > $secondRemainingTokens ? $second : $first; + } +} diff --git a/src/vendor/symfony/http-foundation/RateLimiter/RequestRateLimiterInterface.php b/src/vendor/symfony/http-foundation/RateLimiter/RequestRateLimiterInterface.php new file mode 100644 index 0000000..4c87a40 --- /dev/null +++ b/src/vendor/symfony/http-foundation/RateLimiter/RequestRateLimiterInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\RateLimiter; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\RateLimiter\RateLimit; + +/** + * A special type of limiter that deals with requests. + * + * This allows to limit on different types of information + * from the requests. + * + * @author Wouter de Jong + */ +interface RequestRateLimiterInterface +{ + public function consume(Request $request): RateLimit; + + public function reset(Request $request): void; +} diff --git a/src/vendor/symfony/http-foundation/RedirectResponse.php b/src/vendor/symfony/http-foundation/RedirectResponse.php new file mode 100644 index 0000000..2103280 --- /dev/null +++ b/src/vendor/symfony/http-foundation/RedirectResponse.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * RedirectResponse represents an HTTP response doing a redirect. + * + * @author Fabien Potencier + */ +class RedirectResponse extends Response +{ + protected $targetUrl; + + /** + * Creates a redirect response so that it conforms to the rules defined for a redirect status code. + * + * @param string $url The URL to redirect to. The URL should be a full URL, with schema etc., + * but practically every browser redirects on paths only as well + * @param int $status The status code (302 by default) + * @param array $headers The headers (Location is always set to the given URL) + * + * @throws \InvalidArgumentException + * + * @see https://tools.ietf.org/html/rfc2616#section-10.3 + */ + public function __construct(string $url, int $status = 302, array $headers = []) + { + parent::__construct('', $status, $headers); + + $this->setTargetUrl($url); + + if (!$this->isRedirect()) { + throw new \InvalidArgumentException(sprintf('The HTTP status code is not a redirect ("%s" given).', $status)); + } + + if (301 == $status && !\array_key_exists('cache-control', array_change_key_case($headers, \CASE_LOWER))) { + $this->headers->remove('cache-control'); + } + } + + /** + * Factory method for chainability. + * + * @param string $url The URL to redirect to + * + * @return static + * + * @deprecated since Symfony 5.1, use __construct() instead. + */ + public static function create($url = '', int $status = 302, array $headers = []) + { + trigger_deprecation('symfony/http-foundation', '5.1', 'The "%s()" method is deprecated, use "new %s()" instead.', __METHOD__, static::class); + + return new static($url, $status, $headers); + } + + /** + * Returns the target URL. + * + * @return string + */ + public function getTargetUrl() + { + return $this->targetUrl; + } + + /** + * Sets the redirect target of this response. + * + * @return $this + * + * @throws \InvalidArgumentException + */ + public function setTargetUrl(string $url) + { + if ('' === $url) { + throw new \InvalidArgumentException('Cannot redirect to an empty URL.'); + } + + $this->targetUrl = $url; + + $this->setContent( + sprintf(' + + + + + + Redirecting to %1$s + + + Redirecting to %1$s. + +', htmlspecialchars($url, \ENT_QUOTES, 'UTF-8'))); + + $this->headers->set('Location', $url); + + return $this; + } +} diff --git a/src/vendor/symfony/http-foundation/Request.php b/src/vendor/symfony/http-foundation/Request.php new file mode 100644 index 0000000..f8e3421 --- /dev/null +++ b/src/vendor/symfony/http-foundation/Request.php @@ -0,0 +1,2172 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException; +use Symfony\Component\HttpFoundation\Exception\JsonException; +use Symfony\Component\HttpFoundation\Exception\SessionNotFoundException; +use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException; +use Symfony\Component\HttpFoundation\Session\SessionInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(AcceptHeader::class); +class_exists(FileBag::class); +class_exists(HeaderBag::class); +class_exists(HeaderUtils::class); +class_exists(InputBag::class); +class_exists(ParameterBag::class); +class_exists(ServerBag::class); + +/** + * Request represents an HTTP request. + * + * The methods dealing with URL accept / return a raw path (% encoded): + * * getBasePath + * * getBaseUrl + * * getPathInfo + * * getRequestUri + * * getUri + * * getUriForPath + * + * @author Fabien Potencier + */ +class Request +{ + public const HEADER_FORWARDED = 0b000001; // When using RFC 7239 + public const HEADER_X_FORWARDED_FOR = 0b000010; + public const HEADER_X_FORWARDED_HOST = 0b000100; + public const HEADER_X_FORWARDED_PROTO = 0b001000; + public const HEADER_X_FORWARDED_PORT = 0b010000; + public const HEADER_X_FORWARDED_PREFIX = 0b100000; + + /** @deprecated since Symfony 5.2, use either "HEADER_X_FORWARDED_FOR | HEADER_X_FORWARDED_HOST | HEADER_X_FORWARDED_PORT | HEADER_X_FORWARDED_PROTO" or "HEADER_X_FORWARDED_AWS_ELB" or "HEADER_X_FORWARDED_TRAEFIK" constants instead. */ + public const HEADER_X_FORWARDED_ALL = 0b1011110; // All "X-Forwarded-*" headers sent by "usual" reverse proxy + public const HEADER_X_FORWARDED_AWS_ELB = 0b0011010; // AWS ELB doesn't send X-Forwarded-Host + public const HEADER_X_FORWARDED_TRAEFIK = 0b0111110; // All "X-Forwarded-*" headers sent by Traefik reverse proxy + + public const METHOD_HEAD = 'HEAD'; + public const METHOD_GET = 'GET'; + public const METHOD_POST = 'POST'; + public const METHOD_PUT = 'PUT'; + public const METHOD_PATCH = 'PATCH'; + public const METHOD_DELETE = 'DELETE'; + public const METHOD_PURGE = 'PURGE'; + public const METHOD_OPTIONS = 'OPTIONS'; + public const METHOD_TRACE = 'TRACE'; + public const METHOD_CONNECT = 'CONNECT'; + + /** + * @var string[] + */ + protected static $trustedProxies = []; + + /** + * @var string[] + */ + protected static $trustedHostPatterns = []; + + /** + * @var string[] + */ + protected static $trustedHosts = []; + + protected static $httpMethodParameterOverride = false; + + /** + * Custom parameters. + * + * @var ParameterBag + */ + public $attributes; + + /** + * Request body parameters ($_POST). + * + * @var InputBag + */ + public $request; + + /** + * Query string parameters ($_GET). + * + * @var InputBag + */ + public $query; + + /** + * Server and execution environment parameters ($_SERVER). + * + * @var ServerBag + */ + public $server; + + /** + * Uploaded files ($_FILES). + * + * @var FileBag + */ + public $files; + + /** + * Cookies ($_COOKIE). + * + * @var InputBag + */ + public $cookies; + + /** + * Headers (taken from the $_SERVER). + * + * @var HeaderBag + */ + public $headers; + + /** + * @var string|resource|false|null + */ + protected $content; + + /** + * @var array + */ + protected $languages; + + /** + * @var array + */ + protected $charsets; + + /** + * @var array + */ + protected $encodings; + + /** + * @var array + */ + protected $acceptableContentTypes; + + /** + * @var string + */ + protected $pathInfo; + + /** + * @var string + */ + protected $requestUri; + + /** + * @var string + */ + protected $baseUrl; + + /** + * @var string + */ + protected $basePath; + + /** + * @var string + */ + protected $method; + + /** + * @var string + */ + protected $format; + + /** + * @var SessionInterface|callable(): SessionInterface + */ + protected $session; + + /** + * @var string|null + */ + protected $locale; + + /** + * @var string + */ + protected $defaultLocale = 'en'; + + /** + * @var array + */ + protected static $formats; + + protected static $requestFactory; + + /** + * @var string|null + */ + private $preferredFormat; + private $isHostValid = true; + private $isForwardedValid = true; + + /** + * @var bool|null + */ + private $isSafeContentPreferred; + + private static $trustedHeaderSet = -1; + + private const FORWARDED_PARAMS = [ + self::HEADER_X_FORWARDED_FOR => 'for', + self::HEADER_X_FORWARDED_HOST => 'host', + self::HEADER_X_FORWARDED_PROTO => 'proto', + self::HEADER_X_FORWARDED_PORT => 'host', + ]; + + /** + * Names for headers that can be trusted when + * using trusted proxies. + * + * The FORWARDED header is the standard as of rfc7239. + * + * The other headers are non-standard, but widely used + * by popular reverse proxies (like Apache mod_proxy or Amazon EC2). + */ + private const TRUSTED_HEADERS = [ + self::HEADER_FORWARDED => 'FORWARDED', + self::HEADER_X_FORWARDED_FOR => 'X_FORWARDED_FOR', + self::HEADER_X_FORWARDED_HOST => 'X_FORWARDED_HOST', + self::HEADER_X_FORWARDED_PROTO => 'X_FORWARDED_PROTO', + self::HEADER_X_FORWARDED_PORT => 'X_FORWARDED_PORT', + self::HEADER_X_FORWARDED_PREFIX => 'X_FORWARDED_PREFIX', + ]; + + /** @var bool */ + private $isIisRewrite = false; + + /** + * @param array $query The GET parameters + * @param array $request The POST parameters + * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...) + * @param array $cookies The COOKIE parameters + * @param array $files The FILES parameters + * @param array $server The SERVER parameters + * @param string|resource|null $content The raw body data + */ + public function __construct(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null) + { + $this->initialize($query, $request, $attributes, $cookies, $files, $server, $content); + } + + /** + * Sets the parameters for this request. + * + * This method also re-initializes all properties. + * + * @param array $query The GET parameters + * @param array $request The POST parameters + * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...) + * @param array $cookies The COOKIE parameters + * @param array $files The FILES parameters + * @param array $server The SERVER parameters + * @param string|resource|null $content The raw body data + */ + public function initialize(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null) + { + $this->request = new InputBag($request); + $this->query = new InputBag($query); + $this->attributes = new ParameterBag($attributes); + $this->cookies = new InputBag($cookies); + $this->files = new FileBag($files); + $this->server = new ServerBag($server); + $this->headers = new HeaderBag($this->server->getHeaders()); + + $this->content = $content; + $this->languages = null; + $this->charsets = null; + $this->encodings = null; + $this->acceptableContentTypes = null; + $this->pathInfo = null; + $this->requestUri = null; + $this->baseUrl = null; + $this->basePath = null; + $this->method = null; + $this->format = null; + } + + /** + * Creates a new request with values from PHP's super globals. + * + * @return static + */ + public static function createFromGlobals() + { + $request = self::createRequestFromFactory($_GET, $_POST, [], $_COOKIE, $_FILES, $_SERVER); + + if (str_starts_with($request->headers->get('CONTENT_TYPE', ''), 'application/x-www-form-urlencoded') + && \in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), ['PUT', 'DELETE', 'PATCH']) + ) { + parse_str($request->getContent(), $data); + $request->request = new InputBag($data); + } + + return $request; + } + + /** + * Creates a Request based on a given URI and configuration. + * + * The information contained in the URI always take precedence + * over the other information (server and parameters). + * + * @param string $uri The URI + * @param string $method The HTTP method + * @param array $parameters The query (GET) or request (POST) parameters + * @param array $cookies The request cookies ($_COOKIE) + * @param array $files The request files ($_FILES) + * @param array $server The server parameters ($_SERVER) + * @param string|resource|null $content The raw body data + * + * @return static + */ + public static function create(string $uri, string $method = 'GET', array $parameters = [], array $cookies = [], array $files = [], array $server = [], $content = null) + { + $server = array_replace([ + 'SERVER_NAME' => 'localhost', + 'SERVER_PORT' => 80, + 'HTTP_HOST' => 'localhost', + 'HTTP_USER_AGENT' => 'Symfony', + 'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'HTTP_ACCEPT_LANGUAGE' => 'en-us,en;q=0.5', + 'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', + 'REMOTE_ADDR' => '127.0.0.1', + 'SCRIPT_NAME' => '', + 'SCRIPT_FILENAME' => '', + 'SERVER_PROTOCOL' => 'HTTP/1.1', + 'REQUEST_TIME' => time(), + 'REQUEST_TIME_FLOAT' => microtime(true), + ], $server); + + $server['PATH_INFO'] = ''; + $server['REQUEST_METHOD'] = strtoupper($method); + + $components = parse_url($uri); + if (isset($components['host'])) { + $server['SERVER_NAME'] = $components['host']; + $server['HTTP_HOST'] = $components['host']; + } + + if (isset($components['scheme'])) { + if ('https' === $components['scheme']) { + $server['HTTPS'] = 'on'; + $server['SERVER_PORT'] = 443; + } else { + unset($server['HTTPS']); + $server['SERVER_PORT'] = 80; + } + } + + if (isset($components['port'])) { + $server['SERVER_PORT'] = $components['port']; + $server['HTTP_HOST'] .= ':'.$components['port']; + } + + if (isset($components['user'])) { + $server['PHP_AUTH_USER'] = $components['user']; + } + + if (isset($components['pass'])) { + $server['PHP_AUTH_PW'] = $components['pass']; + } + + if (!isset($components['path'])) { + $components['path'] = '/'; + } + + switch (strtoupper($method)) { + case 'POST': + case 'PUT': + case 'DELETE': + if (!isset($server['CONTENT_TYPE'])) { + $server['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'; + } + // no break + case 'PATCH': + $request = $parameters; + $query = []; + break; + default: + $request = []; + $query = $parameters; + break; + } + + $queryString = ''; + if (isset($components['query'])) { + parse_str(html_entity_decode($components['query']), $qs); + + if ($query) { + $query = array_replace($qs, $query); + $queryString = http_build_query($query, '', '&'); + } else { + $query = $qs; + $queryString = $components['query']; + } + } elseif ($query) { + $queryString = http_build_query($query, '', '&'); + } + + $server['REQUEST_URI'] = $components['path'].('' !== $queryString ? '?'.$queryString : ''); + $server['QUERY_STRING'] = $queryString; + + return self::createRequestFromFactory($query, $request, [], $cookies, $files, $server, $content); + } + + /** + * Sets a callable able to create a Request instance. + * + * This is mainly useful when you need to override the Request class + * to keep BC with an existing system. It should not be used for any + * other purpose. + */ + public static function setFactory(?callable $callable) + { + self::$requestFactory = $callable; + } + + /** + * Clones a request and overrides some of its parameters. + * + * @param array|null $query The GET parameters + * @param array|null $request The POST parameters + * @param array|null $attributes The request attributes (parameters parsed from the PATH_INFO, ...) + * @param array|null $cookies The COOKIE parameters + * @param array|null $files The FILES parameters + * @param array|null $server The SERVER parameters + * + * @return static + */ + public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null) + { + $dup = clone $this; + if (null !== $query) { + $dup->query = new InputBag($query); + } + if (null !== $request) { + $dup->request = new InputBag($request); + } + if (null !== $attributes) { + $dup->attributes = new ParameterBag($attributes); + } + if (null !== $cookies) { + $dup->cookies = new InputBag($cookies); + } + if (null !== $files) { + $dup->files = new FileBag($files); + } + if (null !== $server) { + $dup->server = new ServerBag($server); + $dup->headers = new HeaderBag($dup->server->getHeaders()); + } + $dup->languages = null; + $dup->charsets = null; + $dup->encodings = null; + $dup->acceptableContentTypes = null; + $dup->pathInfo = null; + $dup->requestUri = null; + $dup->baseUrl = null; + $dup->basePath = null; + $dup->method = null; + $dup->format = null; + + if (!$dup->get('_format') && $this->get('_format')) { + $dup->attributes->set('_format', $this->get('_format')); + } + + if (!$dup->getRequestFormat(null)) { + $dup->setRequestFormat($this->getRequestFormat(null)); + } + + return $dup; + } + + /** + * Clones the current request. + * + * Note that the session is not cloned as duplicated requests + * are most of the time sub-requests of the main one. + */ + public function __clone() + { + $this->query = clone $this->query; + $this->request = clone $this->request; + $this->attributes = clone $this->attributes; + $this->cookies = clone $this->cookies; + $this->files = clone $this->files; + $this->server = clone $this->server; + $this->headers = clone $this->headers; + } + + /** + * Returns the request as a string. + * + * @return string + */ + public function __toString() + { + $content = $this->getContent(); + + $cookieHeader = ''; + $cookies = []; + + foreach ($this->cookies as $k => $v) { + $cookies[] = \is_array($v) ? http_build_query([$k => $v], '', '; ', \PHP_QUERY_RFC3986) : "$k=$v"; + } + + if ($cookies) { + $cookieHeader = 'Cookie: '.implode('; ', $cookies)."\r\n"; + } + + return + sprintf('%s %s %s', $this->getMethod(), $this->getRequestUri(), $this->server->get('SERVER_PROTOCOL'))."\r\n". + $this->headers. + $cookieHeader."\r\n". + $content; + } + + /** + * Overrides the PHP global variables according to this request instance. + * + * It overrides $_GET, $_POST, $_REQUEST, $_SERVER, $_COOKIE. + * $_FILES is never overridden, see rfc1867 + */ + public function overrideGlobals() + { + $this->server->set('QUERY_STRING', static::normalizeQueryString(http_build_query($this->query->all(), '', '&'))); + + $_GET = $this->query->all(); + $_POST = $this->request->all(); + $_SERVER = $this->server->all(); + $_COOKIE = $this->cookies->all(); + + foreach ($this->headers->all() as $key => $value) { + $key = strtoupper(str_replace('-', '_', $key)); + if (\in_array($key, ['CONTENT_TYPE', 'CONTENT_LENGTH', 'CONTENT_MD5'], true)) { + $_SERVER[$key] = implode(', ', $value); + } else { + $_SERVER['HTTP_'.$key] = implode(', ', $value); + } + } + + $request = ['g' => $_GET, 'p' => $_POST, 'c' => $_COOKIE]; + + $requestOrder = \ini_get('request_order') ?: \ini_get('variables_order'); + $requestOrder = preg_replace('#[^cgp]#', '', strtolower($requestOrder)) ?: 'gp'; + + $_REQUEST = [[]]; + + foreach (str_split($requestOrder) as $order) { + $_REQUEST[] = $request[$order]; + } + + $_REQUEST = array_merge(...$_REQUEST); + } + + /** + * Sets a list of trusted proxies. + * + * You should only list the reverse proxies that you manage directly. + * + * @param array $proxies A list of trusted proxies, the string 'REMOTE_ADDR' will be replaced with $_SERVER['REMOTE_ADDR'] + * @param int $trustedHeaderSet A bit field of Request::HEADER_*, to set which headers to trust from your proxies + */ + public static function setTrustedProxies(array $proxies, int $trustedHeaderSet) + { + if (self::HEADER_X_FORWARDED_ALL === $trustedHeaderSet) { + trigger_deprecation('symfony/http-foundation', '5.2', 'The "HEADER_X_FORWARDED_ALL" constant is deprecated, use either "HEADER_X_FORWARDED_FOR | HEADER_X_FORWARDED_HOST | HEADER_X_FORWARDED_PORT | HEADER_X_FORWARDED_PROTO" or "HEADER_X_FORWARDED_AWS_ELB" or "HEADER_X_FORWARDED_TRAEFIK" constants instead.'); + } + self::$trustedProxies = array_reduce($proxies, function ($proxies, $proxy) { + if ('REMOTE_ADDR' !== $proxy) { + $proxies[] = $proxy; + } elseif (isset($_SERVER['REMOTE_ADDR'])) { + $proxies[] = $_SERVER['REMOTE_ADDR']; + } + + return $proxies; + }, []); + self::$trustedHeaderSet = $trustedHeaderSet; + } + + /** + * Gets the list of trusted proxies. + * + * @return array + */ + public static function getTrustedProxies() + { + return self::$trustedProxies; + } + + /** + * Gets the set of trusted headers from trusted proxies. + * + * @return int A bit field of Request::HEADER_* that defines which headers are trusted from your proxies + */ + public static function getTrustedHeaderSet() + { + return self::$trustedHeaderSet; + } + + /** + * Sets a list of trusted host patterns. + * + * You should only list the hosts you manage using regexs. + * + * @param array $hostPatterns A list of trusted host patterns + */ + public static function setTrustedHosts(array $hostPatterns) + { + self::$trustedHostPatterns = array_map(function ($hostPattern) { + return sprintf('{%s}i', $hostPattern); + }, $hostPatterns); + // we need to reset trusted hosts on trusted host patterns change + self::$trustedHosts = []; + } + + /** + * Gets the list of trusted host patterns. + * + * @return array + */ + public static function getTrustedHosts() + { + return self::$trustedHostPatterns; + } + + /** + * Normalizes a query string. + * + * It builds a normalized query string, where keys/value pairs are alphabetized, + * have consistent escaping and unneeded delimiters are removed. + * + * @return string + */ + public static function normalizeQueryString(?string $qs) + { + if ('' === ($qs ?? '')) { + return ''; + } + + $qs = HeaderUtils::parseQuery($qs); + ksort($qs); + + return http_build_query($qs, '', '&', \PHP_QUERY_RFC3986); + } + + /** + * Enables support for the _method request parameter to determine the intended HTTP method. + * + * Be warned that enabling this feature might lead to CSRF issues in your code. + * Check that you are using CSRF tokens when required. + * If the HTTP method parameter override is enabled, an html-form with method "POST" can be altered + * and used to send a "PUT" or "DELETE" request via the _method request parameter. + * If these methods are not protected against CSRF, this presents a possible vulnerability. + * + * The HTTP method can only be overridden when the real HTTP method is POST. + */ + public static function enableHttpMethodParameterOverride() + { + self::$httpMethodParameterOverride = true; + } + + /** + * Checks whether support for the _method request parameter is enabled. + * + * @return bool + */ + public static function getHttpMethodParameterOverride() + { + return self::$httpMethodParameterOverride; + } + + /** + * Gets a "parameter" value from any bag. + * + * This method is mainly useful for libraries that want to provide some flexibility. If you don't need the + * flexibility in controllers, it is better to explicitly get request parameters from the appropriate + * public property instead (attributes, query, request). + * + * Order of precedence: PATH (routing placeholders or custom attributes), GET, POST + * + * @param mixed $default The default value if the parameter key does not exist + * + * @return mixed + * + * @internal since Symfony 5.4, use explicit input sources instead + */ + public function get(string $key, $default = null) + { + if ($this !== $result = $this->attributes->get($key, $this)) { + return $result; + } + + if ($this->query->has($key)) { + return $this->query->all()[$key]; + } + + if ($this->request->has($key)) { + return $this->request->all()[$key]; + } + + return $default; + } + + /** + * Gets the Session. + * + * @return SessionInterface + */ + public function getSession() + { + $session = $this->session; + if (!$session instanceof SessionInterface && null !== $session) { + $this->setSession($session = $session()); + } + + if (null === $session) { + throw new SessionNotFoundException('Session has not been set.'); + } + + return $session; + } + + /** + * Whether the request contains a Session which was started in one of the + * previous requests. + * + * @return bool + */ + public function hasPreviousSession() + { + // the check for $this->session avoids malicious users trying to fake a session cookie with proper name + return $this->hasSession() && $this->cookies->has($this->getSession()->getName()); + } + + /** + * Whether the request contains a Session object. + * + * This method does not give any information about the state of the session object, + * like whether the session is started or not. It is just a way to check if this Request + * is associated with a Session instance. + * + * @param bool $skipIfUninitialized When true, ignores factories injected by `setSessionFactory` + * + * @return bool + */ + public function hasSession(/* bool $skipIfUninitialized = false */) + { + $skipIfUninitialized = \func_num_args() > 0 ? func_get_arg(0) : false; + + return null !== $this->session && (!$skipIfUninitialized || $this->session instanceof SessionInterface); + } + + public function setSession(SessionInterface $session) + { + $this->session = $session; + } + + /** + * @internal + * + * @param callable(): SessionInterface $factory + */ + public function setSessionFactory(callable $factory) + { + $this->session = $factory; + } + + /** + * Returns the client IP addresses. + * + * In the returned array the most trusted IP address is first, and the + * least trusted one last. The "real" client IP address is the last one, + * but this is also the least trusted one. Trusted proxies are stripped. + * + * Use this method carefully; you should use getClientIp() instead. + * + * @return array + * + * @see getClientIp() + */ + public function getClientIps() + { + $ip = $this->server->get('REMOTE_ADDR'); + + if (!$this->isFromTrustedProxy()) { + return [$ip]; + } + + return $this->getTrustedValues(self::HEADER_X_FORWARDED_FOR, $ip) ?: [$ip]; + } + + /** + * Returns the client IP address. + * + * This method can read the client IP address from the "X-Forwarded-For" header + * when trusted proxies were set via "setTrustedProxies()". The "X-Forwarded-For" + * header value is a comma+space separated list of IP addresses, the left-most + * being the original client, and each successive proxy that passed the request + * adding the IP address where it received the request from. + * + * If your reverse proxy uses a different header name than "X-Forwarded-For", + * ("Client-Ip" for instance), configure it via the $trustedHeaderSet + * argument of the Request::setTrustedProxies() method instead. + * + * @return string|null + * + * @see getClientIps() + * @see https://wikipedia.org/wiki/X-Forwarded-For + */ + public function getClientIp() + { + $ipAddresses = $this->getClientIps(); + + return $ipAddresses[0]; + } + + /** + * Returns current script name. + * + * @return string + */ + public function getScriptName() + { + return $this->server->get('SCRIPT_NAME', $this->server->get('ORIG_SCRIPT_NAME', '')); + } + + /** + * Returns the path being requested relative to the executed script. + * + * The path info always starts with a /. + * + * Suppose this request is instantiated from /mysite on localhost: + * + * * http://localhost/mysite returns an empty string + * * http://localhost/mysite/about returns '/about' + * * http://localhost/mysite/enco%20ded returns '/enco%20ded' + * * http://localhost/mysite/about?var=1 returns '/about' + * + * @return string The raw path (i.e. not urldecoded) + */ + public function getPathInfo() + { + if (null === $this->pathInfo) { + $this->pathInfo = $this->preparePathInfo(); + } + + return $this->pathInfo; + } + + /** + * Returns the root path from which this request is executed. + * + * Suppose that an index.php file instantiates this request object: + * + * * http://localhost/index.php returns an empty string + * * http://localhost/index.php/page returns an empty string + * * http://localhost/web/index.php returns '/web' + * * http://localhost/we%20b/index.php returns '/we%20b' + * + * @return string The raw path (i.e. not urldecoded) + */ + public function getBasePath() + { + if (null === $this->basePath) { + $this->basePath = $this->prepareBasePath(); + } + + return $this->basePath; + } + + /** + * Returns the root URL from which this request is executed. + * + * The base URL never ends with a /. + * + * This is similar to getBasePath(), except that it also includes the + * script filename (e.g. index.php) if one exists. + * + * @return string The raw URL (i.e. not urldecoded) + */ + public function getBaseUrl() + { + $trustedPrefix = ''; + + // the proxy prefix must be prepended to any prefix being needed at the webserver level + if ($this->isFromTrustedProxy() && $trustedPrefixValues = $this->getTrustedValues(self::HEADER_X_FORWARDED_PREFIX)) { + $trustedPrefix = rtrim($trustedPrefixValues[0], '/'); + } + + return $trustedPrefix.$this->getBaseUrlReal(); + } + + /** + * Returns the real base URL received by the webserver from which this request is executed. + * The URL does not include trusted reverse proxy prefix. + * + * @return string The raw URL (i.e. not urldecoded) + */ + private function getBaseUrlReal(): string + { + if (null === $this->baseUrl) { + $this->baseUrl = $this->prepareBaseUrl(); + } + + return $this->baseUrl; + } + + /** + * Gets the request's scheme. + * + * @return string + */ + public function getScheme() + { + return $this->isSecure() ? 'https' : 'http'; + } + + /** + * Returns the port on which the request is made. + * + * This method can read the client port from the "X-Forwarded-Port" header + * when trusted proxies were set via "setTrustedProxies()". + * + * The "X-Forwarded-Port" header must contain the client port. + * + * @return int|string|null Can be a string if fetched from the server bag + */ + public function getPort() + { + if ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_X_FORWARDED_PORT)) { + $host = $host[0]; + } elseif ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_X_FORWARDED_HOST)) { + $host = $host[0]; + } elseif (!$host = $this->headers->get('HOST')) { + return $this->server->get('SERVER_PORT'); + } + + if ('[' === $host[0]) { + $pos = strpos($host, ':', strrpos($host, ']')); + } else { + $pos = strrpos($host, ':'); + } + + if (false !== $pos && $port = substr($host, $pos + 1)) { + return (int) $port; + } + + return 'https' === $this->getScheme() ? 443 : 80; + } + + /** + * Returns the user. + * + * @return string|null + */ + public function getUser() + { + return $this->headers->get('PHP_AUTH_USER'); + } + + /** + * Returns the password. + * + * @return string|null + */ + public function getPassword() + { + return $this->headers->get('PHP_AUTH_PW'); + } + + /** + * Gets the user info. + * + * @return string|null A user name if any and, optionally, scheme-specific information about how to gain authorization to access the server + */ + public function getUserInfo() + { + $userinfo = $this->getUser(); + + $pass = $this->getPassword(); + if ('' != $pass) { + $userinfo .= ":$pass"; + } + + return $userinfo; + } + + /** + * Returns the HTTP host being requested. + * + * The port name will be appended to the host if it's non-standard. + * + * @return string + */ + public function getHttpHost() + { + $scheme = $this->getScheme(); + $port = $this->getPort(); + + if (('http' == $scheme && 80 == $port) || ('https' == $scheme && 443 == $port)) { + return $this->getHost(); + } + + return $this->getHost().':'.$port; + } + + /** + * Returns the requested URI (path and query string). + * + * @return string The raw URI (i.e. not URI decoded) + */ + public function getRequestUri() + { + if (null === $this->requestUri) { + $this->requestUri = $this->prepareRequestUri(); + } + + return $this->requestUri; + } + + /** + * Gets the scheme and HTTP host. + * + * If the URL was called with basic authentication, the user + * and the password are not added to the generated string. + * + * @return string + */ + public function getSchemeAndHttpHost() + { + return $this->getScheme().'://'.$this->getHttpHost(); + } + + /** + * Generates a normalized URI (URL) for the Request. + * + * @return string + * + * @see getQueryString() + */ + public function getUri() + { + if (null !== $qs = $this->getQueryString()) { + $qs = '?'.$qs; + } + + return $this->getSchemeAndHttpHost().$this->getBaseUrl().$this->getPathInfo().$qs; + } + + /** + * Generates a normalized URI for the given path. + * + * @param string $path A path to use instead of the current one + * + * @return string + */ + public function getUriForPath(string $path) + { + return $this->getSchemeAndHttpHost().$this->getBaseUrl().$path; + } + + /** + * Returns the path as relative reference from the current Request path. + * + * Only the URIs path component (no schema, host etc.) is relevant and must be given. + * Both paths must be absolute and not contain relative parts. + * Relative URLs from one resource to another are useful when generating self-contained downloadable document archives. + * Furthermore, they can be used to reduce the link size in documents. + * + * Example target paths, given a base path of "/a/b/c/d": + * - "/a/b/c/d" -> "" + * - "/a/b/c/" -> "./" + * - "/a/b/" -> "../" + * - "/a/b/c/other" -> "other" + * - "/a/x/y" -> "../../x/y" + * + * @return string + */ + public function getRelativeUriForPath(string $path) + { + // be sure that we are dealing with an absolute path + if (!isset($path[0]) || '/' !== $path[0]) { + return $path; + } + + if ($path === $basePath = $this->getPathInfo()) { + return ''; + } + + $sourceDirs = explode('/', isset($basePath[0]) && '/' === $basePath[0] ? substr($basePath, 1) : $basePath); + $targetDirs = explode('/', substr($path, 1)); + array_pop($sourceDirs); + $targetFile = array_pop($targetDirs); + + foreach ($sourceDirs as $i => $dir) { + if (isset($targetDirs[$i]) && $dir === $targetDirs[$i]) { + unset($sourceDirs[$i], $targetDirs[$i]); + } else { + break; + } + } + + $targetDirs[] = $targetFile; + $path = str_repeat('../', \count($sourceDirs)).implode('/', $targetDirs); + + // A reference to the same base directory or an empty subdirectory must be prefixed with "./". + // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used + // as the first segment of a relative-path reference, as it would be mistaken for a scheme name + // (see https://tools.ietf.org/html/rfc3986#section-4.2). + return !isset($path[0]) || '/' === $path[0] + || false !== ($colonPos = strpos($path, ':')) && ($colonPos < ($slashPos = strpos($path, '/')) || false === $slashPos) + ? "./$path" : $path; + } + + /** + * Generates the normalized query string for the Request. + * + * It builds a normalized query string, where keys/value pairs are alphabetized + * and have consistent escaping. + * + * @return string|null + */ + public function getQueryString() + { + $qs = static::normalizeQueryString($this->server->get('QUERY_STRING')); + + return '' === $qs ? null : $qs; + } + + /** + * Checks whether the request is secure or not. + * + * This method can read the client protocol from the "X-Forwarded-Proto" header + * when trusted proxies were set via "setTrustedProxies()". + * + * The "X-Forwarded-Proto" header must contain the protocol: "https" or "http". + * + * @return bool + */ + public function isSecure() + { + if ($this->isFromTrustedProxy() && $proto = $this->getTrustedValues(self::HEADER_X_FORWARDED_PROTO)) { + return \in_array(strtolower($proto[0]), ['https', 'on', 'ssl', '1'], true); + } + + $https = $this->server->get('HTTPS'); + + return !empty($https) && 'off' !== strtolower($https); + } + + /** + * Returns the host name. + * + * This method can read the client host name from the "X-Forwarded-Host" header + * when trusted proxies were set via "setTrustedProxies()". + * + * The "X-Forwarded-Host" header must contain the client host name. + * + * @return string + * + * @throws SuspiciousOperationException when the host name is invalid or not trusted + */ + public function getHost() + { + if ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_X_FORWARDED_HOST)) { + $host = $host[0]; + } elseif (!$host = $this->headers->get('HOST')) { + if (!$host = $this->server->get('SERVER_NAME')) { + $host = $this->server->get('SERVER_ADDR', ''); + } + } + + // trim and remove port number from host + // host is lowercase as per RFC 952/2181 + $host = strtolower(preg_replace('/:\d+$/', '', trim($host))); + + // as the host can come from the user (HTTP_HOST and depending on the configuration, SERVER_NAME too can come from the user) + // check that it does not contain forbidden characters (see RFC 952 and RFC 2181) + // use preg_replace() instead of preg_match() to prevent DoS attacks with long host names + if ($host && '' !== preg_replace('/(?:^\[)?[a-zA-Z0-9-:\]_]+\.?/', '', $host)) { + if (!$this->isHostValid) { + return ''; + } + $this->isHostValid = false; + + throw new SuspiciousOperationException(sprintf('Invalid Host "%s".', $host)); + } + + if (\count(self::$trustedHostPatterns) > 0) { + // to avoid host header injection attacks, you should provide a list of trusted host patterns + + if (\in_array($host, self::$trustedHosts)) { + return $host; + } + + foreach (self::$trustedHostPatterns as $pattern) { + if (preg_match($pattern, $host)) { + self::$trustedHosts[] = $host; + + return $host; + } + } + + if (!$this->isHostValid) { + return ''; + } + $this->isHostValid = false; + + throw new SuspiciousOperationException(sprintf('Untrusted Host "%s".', $host)); + } + + return $host; + } + + /** + * Sets the request method. + */ + public function setMethod(string $method) + { + $this->method = null; + $this->server->set('REQUEST_METHOD', $method); + } + + /** + * Gets the request "intended" method. + * + * If the X-HTTP-Method-Override header is set, and if the method is a POST, + * then it is used to determine the "real" intended HTTP method. + * + * The _method request parameter can also be used to determine the HTTP method, + * but only if enableHttpMethodParameterOverride() has been called. + * + * The method is always an uppercased string. + * + * @return string + * + * @see getRealMethod() + */ + public function getMethod() + { + if (null !== $this->method) { + return $this->method; + } + + $this->method = strtoupper($this->server->get('REQUEST_METHOD', 'GET')); + + if ('POST' !== $this->method) { + return $this->method; + } + + $method = $this->headers->get('X-HTTP-METHOD-OVERRIDE'); + + if (!$method && self::$httpMethodParameterOverride) { + $method = $this->request->get('_method', $this->query->get('_method', 'POST')); + } + + if (!\is_string($method)) { + return $this->method; + } + + $method = strtoupper($method); + + if (\in_array($method, ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'PATCH', 'PURGE', 'TRACE'], true)) { + return $this->method = $method; + } + + if (!preg_match('/^[A-Z]++$/D', $method)) { + throw new SuspiciousOperationException(sprintf('Invalid method override "%s".', $method)); + } + + return $this->method = $method; + } + + /** + * Gets the "real" request method. + * + * @return string + * + * @see getMethod() + */ + public function getRealMethod() + { + return strtoupper($this->server->get('REQUEST_METHOD', 'GET')); + } + + /** + * Gets the mime type associated with the format. + * + * @return string|null + */ + public function getMimeType(string $format) + { + if (null === static::$formats) { + static::initializeFormats(); + } + + return isset(static::$formats[$format]) ? static::$formats[$format][0] : null; + } + + /** + * Gets the mime types associated with the format. + * + * @return array + */ + public static function getMimeTypes(string $format) + { + if (null === static::$formats) { + static::initializeFormats(); + } + + return static::$formats[$format] ?? []; + } + + /** + * Gets the format associated with the mime type. + * + * @return string|null + */ + public function getFormat(?string $mimeType) + { + $canonicalMimeType = null; + if ($mimeType && false !== $pos = strpos($mimeType, ';')) { + $canonicalMimeType = trim(substr($mimeType, 0, $pos)); + } + + if (null === static::$formats) { + static::initializeFormats(); + } + + foreach (static::$formats as $format => $mimeTypes) { + if (\in_array($mimeType, (array) $mimeTypes)) { + return $format; + } + if (null !== $canonicalMimeType && \in_array($canonicalMimeType, (array) $mimeTypes)) { + return $format; + } + } + + return null; + } + + /** + * Associates a format with mime types. + * + * @param string|array $mimeTypes The associated mime types (the preferred one must be the first as it will be used as the content type) + */ + public function setFormat(?string $format, $mimeTypes) + { + if (null === static::$formats) { + static::initializeFormats(); + } + + static::$formats[$format] = \is_array($mimeTypes) ? $mimeTypes : [$mimeTypes]; + } + + /** + * Gets the request format. + * + * Here is the process to determine the format: + * + * * format defined by the user (with setRequestFormat()) + * * _format request attribute + * * $default + * + * @see getPreferredFormat + * + * @return string|null + */ + public function getRequestFormat(?string $default = 'html') + { + if (null === $this->format) { + $this->format = $this->attributes->get('_format'); + } + + return $this->format ?? $default; + } + + /** + * Sets the request format. + */ + public function setRequestFormat(?string $format) + { + $this->format = $format; + } + + /** + * Gets the format associated with the request. + * + * @return string|null + */ + public function getContentType() + { + return $this->getFormat($this->headers->get('CONTENT_TYPE', '')); + } + + /** + * Sets the default locale. + */ + public function setDefaultLocale(string $locale) + { + $this->defaultLocale = $locale; + + if (null === $this->locale) { + $this->setPhpDefaultLocale($locale); + } + } + + /** + * Get the default locale. + * + * @return string + */ + public function getDefaultLocale() + { + return $this->defaultLocale; + } + + /** + * Sets the locale. + */ + public function setLocale(string $locale) + { + $this->setPhpDefaultLocale($this->locale = $locale); + } + + /** + * Get the locale. + * + * @return string + */ + public function getLocale() + { + return $this->locale ?? $this->defaultLocale; + } + + /** + * Checks if the request method is of specified type. + * + * @param string $method Uppercase request method (GET, POST etc) + * + * @return bool + */ + public function isMethod(string $method) + { + return $this->getMethod() === strtoupper($method); + } + + /** + * Checks whether or not the method is safe. + * + * @see https://tools.ietf.org/html/rfc7231#section-4.2.1 + * + * @return bool + */ + public function isMethodSafe() + { + return \in_array($this->getMethod(), ['GET', 'HEAD', 'OPTIONS', 'TRACE']); + } + + /** + * Checks whether or not the method is idempotent. + * + * @return bool + */ + public function isMethodIdempotent() + { + return \in_array($this->getMethod(), ['HEAD', 'GET', 'PUT', 'DELETE', 'TRACE', 'OPTIONS', 'PURGE']); + } + + /** + * Checks whether the method is cacheable or not. + * + * @see https://tools.ietf.org/html/rfc7231#section-4.2.3 + * + * @return bool + */ + public function isMethodCacheable() + { + return \in_array($this->getMethod(), ['GET', 'HEAD']); + } + + /** + * Returns the protocol version. + * + * If the application is behind a proxy, the protocol version used in the + * requests between the client and the proxy and between the proxy and the + * server might be different. This returns the former (from the "Via" header) + * if the proxy is trusted (see "setTrustedProxies()"), otherwise it returns + * the latter (from the "SERVER_PROTOCOL" server parameter). + * + * @return string|null + */ + public function getProtocolVersion() + { + if ($this->isFromTrustedProxy()) { + preg_match('~^(HTTP/)?([1-9]\.[0-9]) ~', $this->headers->get('Via') ?? '', $matches); + + if ($matches) { + return 'HTTP/'.$matches[2]; + } + } + + return $this->server->get('SERVER_PROTOCOL'); + } + + /** + * Returns the request body content. + * + * @param bool $asResource If true, a resource will be returned + * + * @return string|resource + */ + public function getContent(bool $asResource = false) + { + $currentContentIsResource = \is_resource($this->content); + + if (true === $asResource) { + if ($currentContentIsResource) { + rewind($this->content); + + return $this->content; + } + + // Content passed in parameter (test) + if (\is_string($this->content)) { + $resource = fopen('php://temp', 'r+'); + fwrite($resource, $this->content); + rewind($resource); + + return $resource; + } + + $this->content = false; + + return fopen('php://input', 'r'); + } + + if ($currentContentIsResource) { + rewind($this->content); + + return stream_get_contents($this->content); + } + + if (null === $this->content || false === $this->content) { + $this->content = file_get_contents('php://input'); + } + + return $this->content; + } + + /** + * Gets the request body decoded as array, typically from a JSON payload. + * + * @return array + * + * @throws JsonException When the body cannot be decoded to an array + */ + public function toArray() + { + if ('' === $content = $this->getContent()) { + throw new JsonException('Request body is empty.'); + } + + try { + $content = json_decode($content, true, 512, \JSON_BIGINT_AS_STRING | (\PHP_VERSION_ID >= 70300 ? \JSON_THROW_ON_ERROR : 0)); + } catch (\JsonException $e) { + throw new JsonException('Could not decode request body.', $e->getCode(), $e); + } + + if (\PHP_VERSION_ID < 70300 && \JSON_ERROR_NONE !== json_last_error()) { + throw new JsonException('Could not decode request body: '.json_last_error_msg(), json_last_error()); + } + + if (!\is_array($content)) { + throw new JsonException(sprintf('JSON content was expected to decode to an array, "%s" returned.', get_debug_type($content))); + } + + return $content; + } + + /** + * Gets the Etags. + * + * @return array + */ + public function getETags() + { + return preg_split('/\s*,\s*/', $this->headers->get('If-None-Match', ''), -1, \PREG_SPLIT_NO_EMPTY); + } + + /** + * @return bool + */ + public function isNoCache() + { + return $this->headers->hasCacheControlDirective('no-cache') || 'no-cache' == $this->headers->get('Pragma'); + } + + /** + * Gets the preferred format for the response by inspecting, in the following order: + * * the request format set using setRequestFormat; + * * the values of the Accept HTTP header. + * + * Note that if you use this method, you should send the "Vary: Accept" header + * in the response to prevent any issues with intermediary HTTP caches. + */ + public function getPreferredFormat(?string $default = 'html'): ?string + { + if (null !== $this->preferredFormat || null !== $this->preferredFormat = $this->getRequestFormat(null)) { + return $this->preferredFormat; + } + + foreach ($this->getAcceptableContentTypes() as $mimeType) { + if ($this->preferredFormat = $this->getFormat($mimeType)) { + return $this->preferredFormat; + } + } + + return $default; + } + + /** + * Returns the preferred language. + * + * @param string[] $locales An array of ordered available locales + * + * @return string|null + */ + public function getPreferredLanguage(array $locales = null) + { + $preferredLanguages = $this->getLanguages(); + + if (empty($locales)) { + return $preferredLanguages[0] ?? null; + } + + if (!$preferredLanguages) { + return $locales[0]; + } + + $extendedPreferredLanguages = []; + foreach ($preferredLanguages as $language) { + $extendedPreferredLanguages[] = $language; + if (false !== $position = strpos($language, '_')) { + $superLanguage = substr($language, 0, $position); + if (!\in_array($superLanguage, $preferredLanguages)) { + $extendedPreferredLanguages[] = $superLanguage; + } + } + } + + $preferredLanguages = array_values(array_intersect($extendedPreferredLanguages, $locales)); + + return $preferredLanguages[0] ?? $locales[0]; + } + + /** + * Gets a list of languages acceptable by the client browser ordered in the user browser preferences. + * + * @return array + */ + public function getLanguages() + { + if (null !== $this->languages) { + return $this->languages; + } + + $languages = AcceptHeader::fromString($this->headers->get('Accept-Language'))->all(); + $this->languages = []; + foreach ($languages as $acceptHeaderItem) { + $lang = $acceptHeaderItem->getValue(); + if (str_contains($lang, '-')) { + $codes = explode('-', $lang); + if ('i' === $codes[0]) { + // Language not listed in ISO 639 that are not variants + // of any listed language, which can be registered with the + // i-prefix, such as i-cherokee + if (\count($codes) > 1) { + $lang = $codes[1]; + } + } else { + for ($i = 0, $max = \count($codes); $i < $max; ++$i) { + if (0 === $i) { + $lang = strtolower($codes[0]); + } else { + $lang .= '_'.strtoupper($codes[$i]); + } + } + } + } + + $this->languages[] = $lang; + } + + return $this->languages; + } + + /** + * Gets a list of charsets acceptable by the client browser in preferable order. + * + * @return array + */ + public function getCharsets() + { + if (null !== $this->charsets) { + return $this->charsets; + } + + return $this->charsets = array_map('strval', array_keys(AcceptHeader::fromString($this->headers->get('Accept-Charset'))->all())); + } + + /** + * Gets a list of encodings acceptable by the client browser in preferable order. + * + * @return array + */ + public function getEncodings() + { + if (null !== $this->encodings) { + return $this->encodings; + } + + return $this->encodings = array_map('strval', array_keys(AcceptHeader::fromString($this->headers->get('Accept-Encoding'))->all())); + } + + /** + * Gets a list of content types acceptable by the client browser in preferable order. + * + * @return array + */ + public function getAcceptableContentTypes() + { + if (null !== $this->acceptableContentTypes) { + return $this->acceptableContentTypes; + } + + return $this->acceptableContentTypes = array_map('strval', array_keys(AcceptHeader::fromString($this->headers->get('Accept'))->all())); + } + + /** + * Returns true if the request is an XMLHttpRequest. + * + * It works if your JavaScript library sets an X-Requested-With HTTP header. + * It is known to work with common JavaScript frameworks: + * + * @see https://wikipedia.org/wiki/List_of_Ajax_frameworks#JavaScript + * + * @return bool + */ + public function isXmlHttpRequest() + { + return 'XMLHttpRequest' == $this->headers->get('X-Requested-With'); + } + + /** + * Checks whether the client browser prefers safe content or not according to RFC8674. + * + * @see https://tools.ietf.org/html/rfc8674 + */ + public function preferSafeContent(): bool + { + if (null !== $this->isSafeContentPreferred) { + return $this->isSafeContentPreferred; + } + + if (!$this->isSecure()) { + // see https://tools.ietf.org/html/rfc8674#section-3 + return $this->isSafeContentPreferred = false; + } + + return $this->isSafeContentPreferred = AcceptHeader::fromString($this->headers->get('Prefer'))->has('safe'); + } + + /* + * The following methods are derived from code of the Zend Framework (1.10dev - 2010-01-24) + * + * Code subject to the new BSD license (https://framework.zend.com/license). + * + * Copyright (c) 2005-2010 Zend Technologies USA Inc. (https://www.zend.com/) + */ + + protected function prepareRequestUri() + { + $requestUri = ''; + + if ($this->isIisRewrite() && '' != $this->server->get('UNENCODED_URL')) { + // IIS7 with URL Rewrite: make sure we get the unencoded URL (double slash problem) + $requestUri = $this->server->get('UNENCODED_URL'); + $this->server->remove('UNENCODED_URL'); + } elseif ($this->server->has('REQUEST_URI')) { + $requestUri = $this->server->get('REQUEST_URI'); + + if ('' !== $requestUri && '/' === $requestUri[0]) { + // To only use path and query remove the fragment. + if (false !== $pos = strpos($requestUri, '#')) { + $requestUri = substr($requestUri, 0, $pos); + } + } else { + // HTTP proxy reqs setup request URI with scheme and host [and port] + the URL path, + // only use URL path. + $uriComponents = parse_url($requestUri); + + if (isset($uriComponents['path'])) { + $requestUri = $uriComponents['path']; + } + + if (isset($uriComponents['query'])) { + $requestUri .= '?'.$uriComponents['query']; + } + } + } elseif ($this->server->has('ORIG_PATH_INFO')) { + // IIS 5.0, PHP as CGI + $requestUri = $this->server->get('ORIG_PATH_INFO'); + if ('' != $this->server->get('QUERY_STRING')) { + $requestUri .= '?'.$this->server->get('QUERY_STRING'); + } + $this->server->remove('ORIG_PATH_INFO'); + } + + // normalize the request URI to ease creating sub-requests from this request + $this->server->set('REQUEST_URI', $requestUri); + + return $requestUri; + } + + /** + * Prepares the base URL. + * + * @return string + */ + protected function prepareBaseUrl() + { + $filename = basename($this->server->get('SCRIPT_FILENAME', '')); + + if (basename($this->server->get('SCRIPT_NAME', '')) === $filename) { + $baseUrl = $this->server->get('SCRIPT_NAME'); + } elseif (basename($this->server->get('PHP_SELF', '')) === $filename) { + $baseUrl = $this->server->get('PHP_SELF'); + } elseif (basename($this->server->get('ORIG_SCRIPT_NAME', '')) === $filename) { + $baseUrl = $this->server->get('ORIG_SCRIPT_NAME'); // 1and1 shared hosting compatibility + } else { + // Backtrack up the script_filename to find the portion matching + // php_self + $path = $this->server->get('PHP_SELF', ''); + $file = $this->server->get('SCRIPT_FILENAME', ''); + $segs = explode('/', trim($file, '/')); + $segs = array_reverse($segs); + $index = 0; + $last = \count($segs); + $baseUrl = ''; + do { + $seg = $segs[$index]; + $baseUrl = '/'.$seg.$baseUrl; + ++$index; + } while ($last > $index && (false !== $pos = strpos($path, $baseUrl)) && 0 != $pos); + } + + // Does the baseUrl have anything in common with the request_uri? + $requestUri = $this->getRequestUri(); + if ('' !== $requestUri && '/' !== $requestUri[0]) { + $requestUri = '/'.$requestUri; + } + + if ($baseUrl && null !== $prefix = $this->getUrlencodedPrefix($requestUri, $baseUrl)) { + // full $baseUrl matches + return $prefix; + } + + if ($baseUrl && null !== $prefix = $this->getUrlencodedPrefix($requestUri, rtrim(\dirname($baseUrl), '/'.\DIRECTORY_SEPARATOR).'/')) { + // directory portion of $baseUrl matches + return rtrim($prefix, '/'.\DIRECTORY_SEPARATOR); + } + + $truncatedRequestUri = $requestUri; + if (false !== $pos = strpos($requestUri, '?')) { + $truncatedRequestUri = substr($requestUri, 0, $pos); + } + + $basename = basename($baseUrl ?? ''); + if (empty($basename) || !strpos(rawurldecode($truncatedRequestUri), $basename)) { + // no match whatsoever; set it blank + return ''; + } + + // If using mod_rewrite or ISAPI_Rewrite strip the script filename + // out of baseUrl. $pos !== 0 makes sure it is not matching a value + // from PATH_INFO or QUERY_STRING + if (\strlen($requestUri) >= \strlen($baseUrl) && (false !== $pos = strpos($requestUri, $baseUrl)) && 0 !== $pos) { + $baseUrl = substr($requestUri, 0, $pos + \strlen($baseUrl)); + } + + return rtrim($baseUrl, '/'.\DIRECTORY_SEPARATOR); + } + + /** + * Prepares the base path. + * + * @return string + */ + protected function prepareBasePath() + { + $baseUrl = $this->getBaseUrl(); + if (empty($baseUrl)) { + return ''; + } + + $filename = basename($this->server->get('SCRIPT_FILENAME')); + if (basename($baseUrl) === $filename) { + $basePath = \dirname($baseUrl); + } else { + $basePath = $baseUrl; + } + + if ('\\' === \DIRECTORY_SEPARATOR) { + $basePath = str_replace('\\', '/', $basePath); + } + + return rtrim($basePath, '/'); + } + + /** + * Prepares the path info. + * + * @return string + */ + protected function preparePathInfo() + { + if (null === ($requestUri = $this->getRequestUri())) { + return '/'; + } + + // Remove the query string from REQUEST_URI + if (false !== $pos = strpos($requestUri, '?')) { + $requestUri = substr($requestUri, 0, $pos); + } + if ('' !== $requestUri && '/' !== $requestUri[0]) { + $requestUri = '/'.$requestUri; + } + + if (null === ($baseUrl = $this->getBaseUrlReal())) { + return $requestUri; + } + + $pathInfo = substr($requestUri, \strlen($baseUrl)); + if (false === $pathInfo || '' === $pathInfo) { + // If substr() returns false then PATH_INFO is set to an empty string + return '/'; + } + + return $pathInfo; + } + + /** + * Initializes HTTP request formats. + */ + protected static function initializeFormats() + { + static::$formats = [ + 'html' => ['text/html', 'application/xhtml+xml'], + 'txt' => ['text/plain'], + 'js' => ['application/javascript', 'application/x-javascript', 'text/javascript'], + 'css' => ['text/css'], + 'json' => ['application/json', 'application/x-json'], + 'jsonld' => ['application/ld+json'], + 'xml' => ['text/xml', 'application/xml', 'application/x-xml'], + 'rdf' => ['application/rdf+xml'], + 'atom' => ['application/atom+xml'], + 'rss' => ['application/rss+xml'], + 'form' => ['application/x-www-form-urlencoded', 'multipart/form-data'], + ]; + } + + private function setPhpDefaultLocale(string $locale): void + { + // if either the class Locale doesn't exist, or an exception is thrown when + // setting the default locale, the intl module is not installed, and + // the call can be ignored: + try { + if (class_exists(\Locale::class, false)) { + \Locale::setDefault($locale); + } + } catch (\Exception $e) { + } + } + + /** + * Returns the prefix as encoded in the string when the string starts with + * the given prefix, null otherwise. + */ + private function getUrlencodedPrefix(string $string, string $prefix): ?string + { + if ($this->isIisRewrite()) { + // ISS with UrlRewriteModule might report SCRIPT_NAME/PHP_SELF with wrong case + // see https://github.com/php/php-src/issues/11981 + if (0 !== stripos(rawurldecode($string), $prefix)) { + return null; + } + } elseif (!str_starts_with(rawurldecode($string), $prefix)) { + return null; + } + + $len = \strlen($prefix); + + if (preg_match(sprintf('#^(%%[[:xdigit:]]{2}|.){%d}#', $len), $string, $match)) { + return $match[0]; + } + + return null; + } + + private static function createRequestFromFactory(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null): self + { + if (self::$requestFactory) { + $request = (self::$requestFactory)($query, $request, $attributes, $cookies, $files, $server, $content); + + if (!$request instanceof self) { + throw new \LogicException('The Request factory must return an instance of Symfony\Component\HttpFoundation\Request.'); + } + + return $request; + } + + return new static($query, $request, $attributes, $cookies, $files, $server, $content); + } + + /** + * Indicates whether this request originated from a trusted proxy. + * + * This can be useful to determine whether or not to trust the + * contents of a proxy-specific header. + * + * @return bool + */ + public function isFromTrustedProxy() + { + return self::$trustedProxies && IpUtils::checkIp($this->server->get('REMOTE_ADDR', ''), self::$trustedProxies); + } + + private function getTrustedValues(int $type, string $ip = null): array + { + $clientValues = []; + $forwardedValues = []; + + if ((self::$trustedHeaderSet & $type) && $this->headers->has(self::TRUSTED_HEADERS[$type])) { + foreach (explode(',', $this->headers->get(self::TRUSTED_HEADERS[$type])) as $v) { + $clientValues[] = (self::HEADER_X_FORWARDED_PORT === $type ? '0.0.0.0:' : '').trim($v); + } + } + + if ((self::$trustedHeaderSet & self::HEADER_FORWARDED) && (isset(self::FORWARDED_PARAMS[$type])) && $this->headers->has(self::TRUSTED_HEADERS[self::HEADER_FORWARDED])) { + $forwarded = $this->headers->get(self::TRUSTED_HEADERS[self::HEADER_FORWARDED]); + $parts = HeaderUtils::split($forwarded, ',;='); + $forwardedValues = []; + $param = self::FORWARDED_PARAMS[$type]; + foreach ($parts as $subParts) { + if (null === $v = HeaderUtils::combine($subParts)[$param] ?? null) { + continue; + } + if (self::HEADER_X_FORWARDED_PORT === $type) { + if (str_ends_with($v, ']') || false === $v = strrchr($v, ':')) { + $v = $this->isSecure() ? ':443' : ':80'; + } + $v = '0.0.0.0'.$v; + } + $forwardedValues[] = $v; + } + } + + if (null !== $ip) { + $clientValues = $this->normalizeAndFilterClientIps($clientValues, $ip); + $forwardedValues = $this->normalizeAndFilterClientIps($forwardedValues, $ip); + } + + if ($forwardedValues === $clientValues || !$clientValues) { + return $forwardedValues; + } + + if (!$forwardedValues) { + return $clientValues; + } + + if (!$this->isForwardedValid) { + return null !== $ip ? ['0.0.0.0', $ip] : []; + } + $this->isForwardedValid = false; + + throw new ConflictingHeadersException(sprintf('The request has both a trusted "%s" header and a trusted "%s" header, conflicting with each other. You should either configure your proxy to remove one of them, or configure your project to distrust the offending one.', self::TRUSTED_HEADERS[self::HEADER_FORWARDED], self::TRUSTED_HEADERS[$type])); + } + + private function normalizeAndFilterClientIps(array $clientIps, string $ip): array + { + if (!$clientIps) { + return []; + } + $clientIps[] = $ip; // Complete the IP chain with the IP the request actually came from + $firstTrustedIp = null; + + foreach ($clientIps as $key => $clientIp) { + if (strpos($clientIp, '.')) { + // Strip :port from IPv4 addresses. This is allowed in Forwarded + // and may occur in X-Forwarded-For. + $i = strpos($clientIp, ':'); + if ($i) { + $clientIps[$key] = $clientIp = substr($clientIp, 0, $i); + } + } elseif (str_starts_with($clientIp, '[')) { + // Strip brackets and :port from IPv6 addresses. + $i = strpos($clientIp, ']', 1); + $clientIps[$key] = $clientIp = substr($clientIp, 1, $i - 1); + } + + if (!filter_var($clientIp, \FILTER_VALIDATE_IP)) { + unset($clientIps[$key]); + + continue; + } + + if (IpUtils::checkIp($clientIp, self::$trustedProxies)) { + unset($clientIps[$key]); + + // Fallback to this when the client IP falls into the range of trusted proxies + if (null === $firstTrustedIp) { + $firstTrustedIp = $clientIp; + } + } + } + + // Now the IP chain contains only untrusted proxies and the client IP + return $clientIps ? array_reverse($clientIps) : [$firstTrustedIp]; + } + + /** + * Is this IIS with UrlRewriteModule? + * + * This method consumes, caches and removed the IIS_WasUrlRewritten env var, + * so we don't inherit it to sub-requests. + */ + private function isIisRewrite(): bool + { + if (1 === $this->server->getInt('IIS_WasUrlRewritten')) { + $this->isIisRewrite = true; + $this->server->remove('IIS_WasUrlRewritten'); + } + + return $this->isIisRewrite; + } +} diff --git a/src/vendor/symfony/http-foundation/RequestMatcher.php b/src/vendor/symfony/http-foundation/RequestMatcher.php new file mode 100644 index 0000000..f2645f9 --- /dev/null +++ b/src/vendor/symfony/http-foundation/RequestMatcher.php @@ -0,0 +1,196 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * RequestMatcher compares a pre-defined set of checks against a Request instance. + * + * @author Fabien Potencier + */ +class RequestMatcher implements RequestMatcherInterface +{ + /** + * @var string|null + */ + private $path; + + /** + * @var string|null + */ + private $host; + + /** + * @var int|null + */ + private $port; + + /** + * @var string[] + */ + private $methods = []; + + /** + * @var string[] + */ + private $ips = []; + + /** + * @var array + */ + private $attributes = []; + + /** + * @var string[] + */ + private $schemes = []; + + /** + * @param string|string[]|null $methods + * @param string|string[]|null $ips + * @param string|string[]|null $schemes + */ + public function __construct(string $path = null, string $host = null, $methods = null, $ips = null, array $attributes = [], $schemes = null, int $port = null) + { + $this->matchPath($path); + $this->matchHost($host); + $this->matchMethod($methods); + $this->matchIps($ips); + $this->matchScheme($schemes); + $this->matchPort($port); + + foreach ($attributes as $k => $v) { + $this->matchAttribute($k, $v); + } + } + + /** + * Adds a check for the HTTP scheme. + * + * @param string|string[]|null $scheme An HTTP scheme or an array of HTTP schemes + */ + public function matchScheme($scheme) + { + $this->schemes = null !== $scheme ? array_map('strtolower', (array) $scheme) : []; + } + + /** + * Adds a check for the URL host name. + */ + public function matchHost(?string $regexp) + { + $this->host = $regexp; + } + + /** + * Adds a check for the the URL port. + * + * @param int|null $port The port number to connect to + */ + public function matchPort(?int $port) + { + $this->port = $port; + } + + /** + * Adds a check for the URL path info. + */ + public function matchPath(?string $regexp) + { + $this->path = $regexp; + } + + /** + * Adds a check for the client IP. + * + * @param string $ip A specific IP address or a range specified using IP/netmask like 192.168.1.0/24 + */ + public function matchIp(string $ip) + { + $this->matchIps($ip); + } + + /** + * Adds a check for the client IP. + * + * @param string|string[]|null $ips A specific IP address or a range specified using IP/netmask like 192.168.1.0/24 + */ + public function matchIps($ips) + { + $ips = null !== $ips ? (array) $ips : []; + + $this->ips = array_reduce($ips, static function (array $ips, string $ip) { + return array_merge($ips, preg_split('/\s*,\s*/', $ip)); + }, []); + } + + /** + * Adds a check for the HTTP method. + * + * @param string|string[]|null $method An HTTP method or an array of HTTP methods + */ + public function matchMethod($method) + { + $this->methods = null !== $method ? array_map('strtoupper', (array) $method) : []; + } + + /** + * Adds a check for request attribute. + */ + public function matchAttribute(string $key, string $regexp) + { + $this->attributes[$key] = $regexp; + } + + /** + * {@inheritdoc} + */ + public function matches(Request $request) + { + if ($this->schemes && !\in_array($request->getScheme(), $this->schemes, true)) { + return false; + } + + if ($this->methods && !\in_array($request->getMethod(), $this->methods, true)) { + return false; + } + + foreach ($this->attributes as $key => $pattern) { + $requestAttribute = $request->attributes->get($key); + if (!\is_string($requestAttribute)) { + return false; + } + if (!preg_match('{'.$pattern.'}', $requestAttribute)) { + return false; + } + } + + if (null !== $this->path && !preg_match('{'.$this->path.'}', rawurldecode($request->getPathInfo()))) { + return false; + } + + if (null !== $this->host && !preg_match('{'.$this->host.'}i', $request->getHost())) { + return false; + } + + if (null !== $this->port && 0 < $this->port && $request->getPort() !== $this->port) { + return false; + } + + if (IpUtils::checkIp($request->getClientIp() ?? '', $this->ips)) { + return true; + } + + // Note to future implementors: add additional checks above the + // foreach above or else your check might not be run! + return 0 === \count($this->ips); + } +} diff --git a/src/vendor/symfony/http-foundation/RequestMatcherInterface.php b/src/vendor/symfony/http-foundation/RequestMatcherInterface.php new file mode 100644 index 0000000..c2e1478 --- /dev/null +++ b/src/vendor/symfony/http-foundation/RequestMatcherInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * RequestMatcherInterface is an interface for strategies to match a Request. + * + * @author Fabien Potencier + */ +interface RequestMatcherInterface +{ + /** + * Decides whether the rule(s) implemented by the strategy matches the supplied request. + * + * @return bool + */ + public function matches(Request $request); +} diff --git a/src/vendor/symfony/http-foundation/RequestStack.php b/src/vendor/symfony/http-foundation/RequestStack.php new file mode 100644 index 0000000..855b518 --- /dev/null +++ b/src/vendor/symfony/http-foundation/RequestStack.php @@ -0,0 +1,128 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\HttpFoundation\Exception\SessionNotFoundException; +use Symfony\Component\HttpFoundation\Session\SessionInterface; + +/** + * Request stack that controls the lifecycle of requests. + * + * @author Benjamin Eberlei + */ +class RequestStack +{ + /** + * @var Request[] + */ + private $requests = []; + + /** + * Pushes a Request on the stack. + * + * This method should generally not be called directly as the stack + * management should be taken care of by the application itself. + */ + public function push(Request $request) + { + $this->requests[] = $request; + } + + /** + * Pops the current request from the stack. + * + * This operation lets the current request go out of scope. + * + * This method should generally not be called directly as the stack + * management should be taken care of by the application itself. + * + * @return Request|null + */ + public function pop() + { + if (!$this->requests) { + return null; + } + + return array_pop($this->requests); + } + + /** + * @return Request|null + */ + public function getCurrentRequest() + { + return end($this->requests) ?: null; + } + + /** + * Gets the main request. + * + * Be warned that making your code aware of the main request + * might make it un-compatible with other features of your framework + * like ESI support. + */ + public function getMainRequest(): ?Request + { + if (!$this->requests) { + return null; + } + + return $this->requests[0]; + } + + /** + * Gets the master request. + * + * @return Request|null + * + * @deprecated since symfony/http-foundation 5.3, use getMainRequest() instead + */ + public function getMasterRequest() + { + trigger_deprecation('symfony/http-foundation', '5.3', '"%s()" is deprecated, use "getMainRequest()" instead.', __METHOD__); + + return $this->getMainRequest(); + } + + /** + * Returns the parent request of the current. + * + * Be warned that making your code aware of the parent request + * might make it un-compatible with other features of your framework + * like ESI support. + * + * If current Request is the main request, it returns null. + * + * @return Request|null + */ + public function getParentRequest() + { + $pos = \count($this->requests) - 2; + + return $this->requests[$pos] ?? null; + } + + /** + * Gets the current session. + * + * @throws SessionNotFoundException + */ + public function getSession(): SessionInterface + { + if ((null !== $request = end($this->requests) ?: null) && $request->hasSession()) { + return $request->getSession(); + } + + throw new SessionNotFoundException(); + } +} diff --git a/src/vendor/symfony/http-foundation/Response.php b/src/vendor/symfony/http-foundation/Response.php new file mode 100644 index 0000000..23bfb21 --- /dev/null +++ b/src/vendor/symfony/http-foundation/Response.php @@ -0,0 +1,1288 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +// Help opcache.preload discover always-needed symbols +class_exists(ResponseHeaderBag::class); + +/** + * Response represents an HTTP response. + * + * @author Fabien Potencier + */ +class Response +{ + public const HTTP_CONTINUE = 100; + public const HTTP_SWITCHING_PROTOCOLS = 101; + public const HTTP_PROCESSING = 102; // RFC2518 + public const HTTP_EARLY_HINTS = 103; // RFC8297 + public const HTTP_OK = 200; + public const HTTP_CREATED = 201; + public const HTTP_ACCEPTED = 202; + public const HTTP_NON_AUTHORITATIVE_INFORMATION = 203; + public const HTTP_NO_CONTENT = 204; + public const HTTP_RESET_CONTENT = 205; + public const HTTP_PARTIAL_CONTENT = 206; + public const HTTP_MULTI_STATUS = 207; // RFC4918 + public const HTTP_ALREADY_REPORTED = 208; // RFC5842 + public const HTTP_IM_USED = 226; // RFC3229 + public const HTTP_MULTIPLE_CHOICES = 300; + public const HTTP_MOVED_PERMANENTLY = 301; + public const HTTP_FOUND = 302; + public const HTTP_SEE_OTHER = 303; + public const HTTP_NOT_MODIFIED = 304; + public const HTTP_USE_PROXY = 305; + public const HTTP_RESERVED = 306; + public const HTTP_TEMPORARY_REDIRECT = 307; + public const HTTP_PERMANENTLY_REDIRECT = 308; // RFC7238 + public const HTTP_BAD_REQUEST = 400; + public const HTTP_UNAUTHORIZED = 401; + public const HTTP_PAYMENT_REQUIRED = 402; + public const HTTP_FORBIDDEN = 403; + public const HTTP_NOT_FOUND = 404; + public const HTTP_METHOD_NOT_ALLOWED = 405; + public const HTTP_NOT_ACCEPTABLE = 406; + public const HTTP_PROXY_AUTHENTICATION_REQUIRED = 407; + public const HTTP_REQUEST_TIMEOUT = 408; + public const HTTP_CONFLICT = 409; + public const HTTP_GONE = 410; + public const HTTP_LENGTH_REQUIRED = 411; + public const HTTP_PRECONDITION_FAILED = 412; + public const HTTP_REQUEST_ENTITY_TOO_LARGE = 413; + public const HTTP_REQUEST_URI_TOO_LONG = 414; + public const HTTP_UNSUPPORTED_MEDIA_TYPE = 415; + public const HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416; + public const HTTP_EXPECTATION_FAILED = 417; + public const HTTP_I_AM_A_TEAPOT = 418; // RFC2324 + public const HTTP_MISDIRECTED_REQUEST = 421; // RFC7540 + public const HTTP_UNPROCESSABLE_ENTITY = 422; // RFC4918 + public const HTTP_LOCKED = 423; // RFC4918 + public const HTTP_FAILED_DEPENDENCY = 424; // RFC4918 + public const HTTP_TOO_EARLY = 425; // RFC-ietf-httpbis-replay-04 + public const HTTP_UPGRADE_REQUIRED = 426; // RFC2817 + public const HTTP_PRECONDITION_REQUIRED = 428; // RFC6585 + public const HTTP_TOO_MANY_REQUESTS = 429; // RFC6585 + public const HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431; // RFC6585 + public const HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451; // RFC7725 + public const HTTP_INTERNAL_SERVER_ERROR = 500; + public const HTTP_NOT_IMPLEMENTED = 501; + public const HTTP_BAD_GATEWAY = 502; + public const HTTP_SERVICE_UNAVAILABLE = 503; + public const HTTP_GATEWAY_TIMEOUT = 504; + public const HTTP_VERSION_NOT_SUPPORTED = 505; + public const HTTP_VARIANT_ALSO_NEGOTIATES_EXPERIMENTAL = 506; // RFC2295 + public const HTTP_INSUFFICIENT_STORAGE = 507; // RFC4918 + public const HTTP_LOOP_DETECTED = 508; // RFC5842 + public const HTTP_NOT_EXTENDED = 510; // RFC2774 + public const HTTP_NETWORK_AUTHENTICATION_REQUIRED = 511; // RFC6585 + + /** + * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control + */ + private const HTTP_RESPONSE_CACHE_CONTROL_DIRECTIVES = [ + 'must_revalidate' => false, + 'no_cache' => false, + 'no_store' => false, + 'no_transform' => false, + 'public' => false, + 'private' => false, + 'proxy_revalidate' => false, + 'max_age' => true, + 's_maxage' => true, + 'immutable' => false, + 'last_modified' => true, + 'etag' => true, + ]; + + /** + * @var ResponseHeaderBag + */ + public $headers; + + /** + * @var string + */ + protected $content; + + /** + * @var string + */ + protected $version; + + /** + * @var int + */ + protected $statusCode; + + /** + * @var string + */ + protected $statusText; + + /** + * @var string + */ + protected $charset; + + /** + * Status codes translation table. + * + * The list of codes is complete according to the + * {@link https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml Hypertext Transfer Protocol (HTTP) Status Code Registry} + * (last updated 2021-10-01). + * + * Unless otherwise noted, the status code is defined in RFC2616. + * + * @var array + */ + public static $statusTexts = [ + 100 => 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', // RFC2518 + 103 => 'Early Hints', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-Status', // RFC4918 + 208 => 'Already Reported', // RFC5842 + 226 => 'IM Used', // RFC3229 + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 307 => 'Temporary Redirect', + 308 => 'Permanent Redirect', // RFC7238 + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Content Too Large', // RFC-ietf-httpbis-semantics + 414 => 'URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Range Not Satisfiable', + 417 => 'Expectation Failed', + 418 => 'I\'m a teapot', // RFC2324 + 421 => 'Misdirected Request', // RFC7540 + 422 => 'Unprocessable Content', // RFC-ietf-httpbis-semantics + 423 => 'Locked', // RFC4918 + 424 => 'Failed Dependency', // RFC4918 + 425 => 'Too Early', // RFC-ietf-httpbis-replay-04 + 426 => 'Upgrade Required', // RFC2817 + 428 => 'Precondition Required', // RFC6585 + 429 => 'Too Many Requests', // RFC6585 + 431 => 'Request Header Fields Too Large', // RFC6585 + 451 => 'Unavailable For Legal Reasons', // RFC7725 + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + 506 => 'Variant Also Negotiates', // RFC2295 + 507 => 'Insufficient Storage', // RFC4918 + 508 => 'Loop Detected', // RFC5842 + 510 => 'Not Extended', // RFC2774 + 511 => 'Network Authentication Required', // RFC6585 + ]; + + /** + * @throws \InvalidArgumentException When the HTTP status code is not valid + */ + public function __construct(?string $content = '', int $status = 200, array $headers = []) + { + $this->headers = new ResponseHeaderBag($headers); + $this->setContent($content); + $this->setStatusCode($status); + $this->setProtocolVersion('1.0'); + } + + /** + * Factory method for chainability. + * + * Example: + * + * return Response::create($body, 200) + * ->setSharedMaxAge(300); + * + * @return static + * + * @deprecated since Symfony 5.1, use __construct() instead. + */ + public static function create(?string $content = '', int $status = 200, array $headers = []) + { + trigger_deprecation('symfony/http-foundation', '5.1', 'The "%s()" method is deprecated, use "new %s()" instead.', __METHOD__, static::class); + + return new static($content, $status, $headers); + } + + /** + * Returns the Response as an HTTP string. + * + * The string representation of the Response is the same as the + * one that will be sent to the client only if the prepare() method + * has been called before. + * + * @return string + * + * @see prepare() + */ + public function __toString() + { + return + sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText)."\r\n". + $this->headers."\r\n". + $this->getContent(); + } + + /** + * Clones the current Response instance. + */ + public function __clone() + { + $this->headers = clone $this->headers; + } + + /** + * Prepares the Response before it is sent to the client. + * + * This method tweaks the Response to ensure that it is + * compliant with RFC 2616. Most of the changes are based on + * the Request that is "associated" with this Response. + * + * @return $this + */ + public function prepare(Request $request) + { + $headers = $this->headers; + + if ($this->isInformational() || $this->isEmpty()) { + $this->setContent(null); + $headers->remove('Content-Type'); + $headers->remove('Content-Length'); + // prevent PHP from sending the Content-Type header based on default_mimetype + ini_set('default_mimetype', ''); + } else { + // Content-type based on the Request + if (!$headers->has('Content-Type')) { + $format = $request->getRequestFormat(null); + if (null !== $format && $mimeType = $request->getMimeType($format)) { + $headers->set('Content-Type', $mimeType); + } + } + + // Fix Content-Type + $charset = $this->charset ?: 'UTF-8'; + if (!$headers->has('Content-Type')) { + $headers->set('Content-Type', 'text/html; charset='.$charset); + } elseif (0 === stripos($headers->get('Content-Type') ?? '', 'text/') && false === stripos($headers->get('Content-Type') ?? '', 'charset')) { + // add the charset + $headers->set('Content-Type', $headers->get('Content-Type').'; charset='.$charset); + } + + // Fix Content-Length + if ($headers->has('Transfer-Encoding')) { + $headers->remove('Content-Length'); + } + + if ($request->isMethod('HEAD')) { + // cf. RFC2616 14.13 + $length = $headers->get('Content-Length'); + $this->setContent(null); + if ($length) { + $headers->set('Content-Length', $length); + } + } + } + + // Fix protocol + if ('HTTP/1.0' != $request->server->get('SERVER_PROTOCOL')) { + $this->setProtocolVersion('1.1'); + } + + // Check if we need to send extra expire info headers + if ('1.0' == $this->getProtocolVersion() && str_contains($headers->get('Cache-Control', ''), 'no-cache')) { + $headers->set('pragma', 'no-cache'); + $headers->set('expires', -1); + } + + $this->ensureIEOverSSLCompatibility($request); + + if ($request->isSecure()) { + foreach ($headers->getCookies() as $cookie) { + $cookie->setSecureDefault(true); + } + } + + return $this; + } + + /** + * Sends HTTP headers. + * + * @return $this + */ + public function sendHeaders() + { + // headers have already been sent by the developer + if (headers_sent()) { + return $this; + } + + // headers + foreach ($this->headers->allPreserveCaseWithoutCookies() as $name => $values) { + $replace = 0 === strcasecmp($name, 'Content-Type'); + foreach ($values as $value) { + header($name.': '.$value, $replace, $this->statusCode); + } + } + + // cookies + foreach ($this->headers->getCookies() as $cookie) { + header('Set-Cookie: '.$cookie, false, $this->statusCode); + } + + // status + header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode); + + return $this; + } + + /** + * Sends content for the current web response. + * + * @return $this + */ + public function sendContent() + { + echo $this->content; + + return $this; + } + + /** + * Sends HTTP headers and content. + * + * @return $this + */ + public function send() + { + $this->sendHeaders(); + $this->sendContent(); + + if (\function_exists('fastcgi_finish_request')) { + fastcgi_finish_request(); + } elseif (\function_exists('litespeed_finish_request')) { + litespeed_finish_request(); + } elseif (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) { + static::closeOutputBuffers(0, true); + flush(); + } + + return $this; + } + + /** + * Sets the response content. + * + * @return $this + */ + public function setContent(?string $content) + { + $this->content = $content ?? ''; + + return $this; + } + + /** + * Gets the current response content. + * + * @return string|false + */ + public function getContent() + { + return $this->content; + } + + /** + * Sets the HTTP protocol version (1.0 or 1.1). + * + * @return $this + * + * @final + */ + public function setProtocolVersion(string $version): object + { + $this->version = $version; + + return $this; + } + + /** + * Gets the HTTP protocol version. + * + * @final + */ + public function getProtocolVersion(): string + { + return $this->version; + } + + /** + * Sets the response status code. + * + * If the status text is null it will be automatically populated for the known + * status codes and left empty otherwise. + * + * @return $this + * + * @throws \InvalidArgumentException When the HTTP status code is not valid + * + * @final + */ + public function setStatusCode(int $code, string $text = null): object + { + $this->statusCode = $code; + if ($this->isInvalid()) { + throw new \InvalidArgumentException(sprintf('The HTTP status code "%s" is not valid.', $code)); + } + + if (null === $text) { + $this->statusText = self::$statusTexts[$code] ?? 'unknown status'; + + return $this; + } + + if (false === $text) { + $this->statusText = ''; + + return $this; + } + + $this->statusText = $text; + + return $this; + } + + /** + * Retrieves the status code for the current web response. + * + * @final + */ + public function getStatusCode(): int + { + return $this->statusCode; + } + + /** + * Sets the response charset. + * + * @return $this + * + * @final + */ + public function setCharset(string $charset): object + { + $this->charset = $charset; + + return $this; + } + + /** + * Retrieves the response charset. + * + * @final + */ + public function getCharset(): ?string + { + return $this->charset; + } + + /** + * Returns true if the response may safely be kept in a shared (surrogate) cache. + * + * Responses marked "private" with an explicit Cache-Control directive are + * considered uncacheable. + * + * Responses with neither a freshness lifetime (Expires, max-age) nor cache + * validator (Last-Modified, ETag) are considered uncacheable because there is + * no way to tell when or how to remove them from the cache. + * + * Note that RFC 7231 and RFC 7234 possibly allow for a more permissive implementation, + * for example "status codes that are defined as cacheable by default [...] + * can be reused by a cache with heuristic expiration unless otherwise indicated" + * (https://tools.ietf.org/html/rfc7231#section-6.1) + * + * @final + */ + public function isCacheable(): bool + { + if (!\in_array($this->statusCode, [200, 203, 300, 301, 302, 404, 410])) { + return false; + } + + if ($this->headers->hasCacheControlDirective('no-store') || $this->headers->getCacheControlDirective('private')) { + return false; + } + + return $this->isValidateable() || $this->isFresh(); + } + + /** + * Returns true if the response is "fresh". + * + * Fresh responses may be served from cache without any interaction with the + * origin. A response is considered fresh when it includes a Cache-Control/max-age + * indicator or Expires header and the calculated age is less than the freshness lifetime. + * + * @final + */ + public function isFresh(): bool + { + return $this->getTtl() > 0; + } + + /** + * Returns true if the response includes headers that can be used to validate + * the response with the origin server using a conditional GET request. + * + * @final + */ + public function isValidateable(): bool + { + return $this->headers->has('Last-Modified') || $this->headers->has('ETag'); + } + + /** + * Marks the response as "private". + * + * It makes the response ineligible for serving other clients. + * + * @return $this + * + * @final + */ + public function setPrivate(): object + { + $this->headers->removeCacheControlDirective('public'); + $this->headers->addCacheControlDirective('private'); + + return $this; + } + + /** + * Marks the response as "public". + * + * It makes the response eligible for serving other clients. + * + * @return $this + * + * @final + */ + public function setPublic(): object + { + $this->headers->addCacheControlDirective('public'); + $this->headers->removeCacheControlDirective('private'); + + return $this; + } + + /** + * Marks the response as "immutable". + * + * @return $this + * + * @final + */ + public function setImmutable(bool $immutable = true): object + { + if ($immutable) { + $this->headers->addCacheControlDirective('immutable'); + } else { + $this->headers->removeCacheControlDirective('immutable'); + } + + return $this; + } + + /** + * Returns true if the response is marked as "immutable". + * + * @final + */ + public function isImmutable(): bool + { + return $this->headers->hasCacheControlDirective('immutable'); + } + + /** + * Returns true if the response must be revalidated by shared caches once it has become stale. + * + * This method indicates that the response must not be served stale by a + * cache in any circumstance without first revalidating with the origin. + * When present, the TTL of the response should not be overridden to be + * greater than the value provided by the origin. + * + * @final + */ + public function mustRevalidate(): bool + { + return $this->headers->hasCacheControlDirective('must-revalidate') || $this->headers->hasCacheControlDirective('proxy-revalidate'); + } + + /** + * Returns the Date header as a DateTime instance. + * + * @throws \RuntimeException When the header is not parseable + * + * @final + */ + public function getDate(): ?\DateTimeInterface + { + return $this->headers->getDate('Date'); + } + + /** + * Sets the Date header. + * + * @return $this + * + * @final + */ + public function setDate(\DateTimeInterface $date): object + { + if ($date instanceof \DateTime) { + $date = \DateTimeImmutable::createFromMutable($date); + } + + $date = $date->setTimezone(new \DateTimeZone('UTC')); + $this->headers->set('Date', $date->format('D, d M Y H:i:s').' GMT'); + + return $this; + } + + /** + * Returns the age of the response in seconds. + * + * @final + */ + public function getAge(): int + { + if (null !== $age = $this->headers->get('Age')) { + return (int) $age; + } + + return max(time() - (int) $this->getDate()->format('U'), 0); + } + + /** + * Marks the response stale by setting the Age header to be equal to the maximum age of the response. + * + * @return $this + */ + public function expire() + { + if ($this->isFresh()) { + $this->headers->set('Age', $this->getMaxAge()); + $this->headers->remove('Expires'); + } + + return $this; + } + + /** + * Returns the value of the Expires header as a DateTime instance. + * + * @final + */ + public function getExpires(): ?\DateTimeInterface + { + try { + return $this->headers->getDate('Expires'); + } catch (\RuntimeException $e) { + // according to RFC 2616 invalid date formats (e.g. "0" and "-1") must be treated as in the past + return \DateTime::createFromFormat('U', time() - 172800); + } + } + + /** + * Sets the Expires HTTP header with a DateTime instance. + * + * Passing null as value will remove the header. + * + * @return $this + * + * @final + */ + public function setExpires(\DateTimeInterface $date = null): object + { + if (null === $date) { + $this->headers->remove('Expires'); + + return $this; + } + + if ($date instanceof \DateTime) { + $date = \DateTimeImmutable::createFromMutable($date); + } + + $date = $date->setTimezone(new \DateTimeZone('UTC')); + $this->headers->set('Expires', $date->format('D, d M Y H:i:s').' GMT'); + + return $this; + } + + /** + * Returns the number of seconds after the time specified in the response's Date + * header when the response should no longer be considered fresh. + * + * First, it checks for a s-maxage directive, then a max-age directive, and then it falls + * back on an expires header. It returns null when no maximum age can be established. + * + * @final + */ + public function getMaxAge(): ?int + { + if ($this->headers->hasCacheControlDirective('s-maxage')) { + return (int) $this->headers->getCacheControlDirective('s-maxage'); + } + + if ($this->headers->hasCacheControlDirective('max-age')) { + return (int) $this->headers->getCacheControlDirective('max-age'); + } + + if (null !== $expires = $this->getExpires()) { + $maxAge = (int) $expires->format('U') - (int) $this->getDate()->format('U'); + + return max($maxAge, 0); + } + + return null; + } + + /** + * Sets the number of seconds after which the response should no longer be considered fresh. + * + * This methods sets the Cache-Control max-age directive. + * + * @return $this + * + * @final + */ + public function setMaxAge(int $value): object + { + $this->headers->addCacheControlDirective('max-age', $value); + + return $this; + } + + /** + * Sets the number of seconds after which the response should no longer be considered fresh by shared caches. + * + * This methods sets the Cache-Control s-maxage directive. + * + * @return $this + * + * @final + */ + public function setSharedMaxAge(int $value): object + { + $this->setPublic(); + $this->headers->addCacheControlDirective('s-maxage', $value); + + return $this; + } + + /** + * Returns the response's time-to-live in seconds. + * + * It returns null when no freshness information is present in the response. + * + * When the response's TTL is 0, the response may not be served from cache without first + * revalidating with the origin. + * + * @final + */ + public function getTtl(): ?int + { + $maxAge = $this->getMaxAge(); + + return null !== $maxAge ? max($maxAge - $this->getAge(), 0) : null; + } + + /** + * Sets the response's time-to-live for shared caches in seconds. + * + * This method adjusts the Cache-Control/s-maxage directive. + * + * @return $this + * + * @final + */ + public function setTtl(int $seconds): object + { + $this->setSharedMaxAge($this->getAge() + $seconds); + + return $this; + } + + /** + * Sets the response's time-to-live for private/client caches in seconds. + * + * This method adjusts the Cache-Control/max-age directive. + * + * @return $this + * + * @final + */ + public function setClientTtl(int $seconds): object + { + $this->setMaxAge($this->getAge() + $seconds); + + return $this; + } + + /** + * Returns the Last-Modified HTTP header as a DateTime instance. + * + * @throws \RuntimeException When the HTTP header is not parseable + * + * @final + */ + public function getLastModified(): ?\DateTimeInterface + { + return $this->headers->getDate('Last-Modified'); + } + + /** + * Sets the Last-Modified HTTP header with a DateTime instance. + * + * Passing null as value will remove the header. + * + * @return $this + * + * @final + */ + public function setLastModified(\DateTimeInterface $date = null): object + { + if (null === $date) { + $this->headers->remove('Last-Modified'); + + return $this; + } + + if ($date instanceof \DateTime) { + $date = \DateTimeImmutable::createFromMutable($date); + } + + $date = $date->setTimezone(new \DateTimeZone('UTC')); + $this->headers->set('Last-Modified', $date->format('D, d M Y H:i:s').' GMT'); + + return $this; + } + + /** + * Returns the literal value of the ETag HTTP header. + * + * @final + */ + public function getEtag(): ?string + { + return $this->headers->get('ETag'); + } + + /** + * Sets the ETag value. + * + * @param string|null $etag The ETag unique identifier or null to remove the header + * @param bool $weak Whether you want a weak ETag or not + * + * @return $this + * + * @final + */ + public function setEtag(string $etag = null, bool $weak = false): object + { + if (null === $etag) { + $this->headers->remove('Etag'); + } else { + if (!str_starts_with($etag, '"')) { + $etag = '"'.$etag.'"'; + } + + $this->headers->set('ETag', (true === $weak ? 'W/' : '').$etag); + } + + return $this; + } + + /** + * Sets the response's cache headers (validation and/or expiration). + * + * Available options are: must_revalidate, no_cache, no_store, no_transform, public, private, proxy_revalidate, max_age, s_maxage, immutable, last_modified and etag. + * + * @return $this + * + * @throws \InvalidArgumentException + * + * @final + */ + public function setCache(array $options): object + { + if ($diff = array_diff(array_keys($options), array_keys(self::HTTP_RESPONSE_CACHE_CONTROL_DIRECTIVES))) { + throw new \InvalidArgumentException(sprintf('Response does not support the following options: "%s".', implode('", "', $diff))); + } + + if (isset($options['etag'])) { + $this->setEtag($options['etag']); + } + + if (isset($options['last_modified'])) { + $this->setLastModified($options['last_modified']); + } + + if (isset($options['max_age'])) { + $this->setMaxAge($options['max_age']); + } + + if (isset($options['s_maxage'])) { + $this->setSharedMaxAge($options['s_maxage']); + } + + foreach (self::HTTP_RESPONSE_CACHE_CONTROL_DIRECTIVES as $directive => $hasValue) { + if (!$hasValue && isset($options[$directive])) { + if ($options[$directive]) { + $this->headers->addCacheControlDirective(str_replace('_', '-', $directive)); + } else { + $this->headers->removeCacheControlDirective(str_replace('_', '-', $directive)); + } + } + } + + if (isset($options['public'])) { + if ($options['public']) { + $this->setPublic(); + } else { + $this->setPrivate(); + } + } + + if (isset($options['private'])) { + if ($options['private']) { + $this->setPrivate(); + } else { + $this->setPublic(); + } + } + + return $this; + } + + /** + * Modifies the response so that it conforms to the rules defined for a 304 status code. + * + * This sets the status, removes the body, and discards any headers + * that MUST NOT be included in 304 responses. + * + * @return $this + * + * @see https://tools.ietf.org/html/rfc2616#section-10.3.5 + * + * @final + */ + public function setNotModified(): object + { + $this->setStatusCode(304); + $this->setContent(null); + + // remove headers that MUST NOT be included with 304 Not Modified responses + foreach (['Allow', 'Content-Encoding', 'Content-Language', 'Content-Length', 'Content-MD5', 'Content-Type', 'Last-Modified'] as $header) { + $this->headers->remove($header); + } + + return $this; + } + + /** + * Returns true if the response includes a Vary header. + * + * @final + */ + public function hasVary(): bool + { + return null !== $this->headers->get('Vary'); + } + + /** + * Returns an array of header names given in the Vary header. + * + * @final + */ + public function getVary(): array + { + if (!$vary = $this->headers->all('Vary')) { + return []; + } + + $ret = []; + foreach ($vary as $item) { + $ret[] = preg_split('/[\s,]+/', $item); + } + + return array_merge([], ...$ret); + } + + /** + * Sets the Vary header. + * + * @param string|array $headers + * @param bool $replace Whether to replace the actual value or not (true by default) + * + * @return $this + * + * @final + */ + public function setVary($headers, bool $replace = true): object + { + $this->headers->set('Vary', $headers, $replace); + + return $this; + } + + /** + * Determines if the Response validators (ETag, Last-Modified) match + * a conditional value specified in the Request. + * + * If the Response is not modified, it sets the status code to 304 and + * removes the actual content by calling the setNotModified() method. + * + * @final + */ + public function isNotModified(Request $request): bool + { + if (!$request->isMethodCacheable()) { + return false; + } + + $notModified = false; + $lastModified = $this->headers->get('Last-Modified'); + $modifiedSince = $request->headers->get('If-Modified-Since'); + + if (($ifNoneMatchEtags = $request->getETags()) && (null !== $etag = $this->getEtag())) { + if (0 == strncmp($etag, 'W/', 2)) { + $etag = substr($etag, 2); + } + + // Use weak comparison as per https://tools.ietf.org/html/rfc7232#section-3.2. + foreach ($ifNoneMatchEtags as $ifNoneMatchEtag) { + if (0 == strncmp($ifNoneMatchEtag, 'W/', 2)) { + $ifNoneMatchEtag = substr($ifNoneMatchEtag, 2); + } + + if ($ifNoneMatchEtag === $etag || '*' === $ifNoneMatchEtag) { + $notModified = true; + break; + } + } + } + // Only do If-Modified-Since date comparison when If-None-Match is not present as per https://tools.ietf.org/html/rfc7232#section-3.3. + elseif ($modifiedSince && $lastModified) { + $notModified = strtotime($modifiedSince) >= strtotime($lastModified); + } + + if ($notModified) { + $this->setNotModified(); + } + + return $notModified; + } + + /** + * Is response invalid? + * + * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html + * + * @final + */ + public function isInvalid(): bool + { + return $this->statusCode < 100 || $this->statusCode >= 600; + } + + /** + * Is response informative? + * + * @final + */ + public function isInformational(): bool + { + return $this->statusCode >= 100 && $this->statusCode < 200; + } + + /** + * Is response successful? + * + * @final + */ + public function isSuccessful(): bool + { + return $this->statusCode >= 200 && $this->statusCode < 300; + } + + /** + * Is the response a redirect? + * + * @final + */ + public function isRedirection(): bool + { + return $this->statusCode >= 300 && $this->statusCode < 400; + } + + /** + * Is there a client error? + * + * @final + */ + public function isClientError(): bool + { + return $this->statusCode >= 400 && $this->statusCode < 500; + } + + /** + * Was there a server side error? + * + * @final + */ + public function isServerError(): bool + { + return $this->statusCode >= 500 && $this->statusCode < 600; + } + + /** + * Is the response OK? + * + * @final + */ + public function isOk(): bool + { + return 200 === $this->statusCode; + } + + /** + * Is the response forbidden? + * + * @final + */ + public function isForbidden(): bool + { + return 403 === $this->statusCode; + } + + /** + * Is the response a not found error? + * + * @final + */ + public function isNotFound(): bool + { + return 404 === $this->statusCode; + } + + /** + * Is the response a redirect of some form? + * + * @final + */ + public function isRedirect(string $location = null): bool + { + return \in_array($this->statusCode, [201, 301, 302, 303, 307, 308]) && (null === $location ?: $location == $this->headers->get('Location')); + } + + /** + * Is the response empty? + * + * @final + */ + public function isEmpty(): bool + { + return \in_array($this->statusCode, [204, 304]); + } + + /** + * Cleans or flushes output buffers up to target level. + * + * Resulting level can be greater than target level if a non-removable buffer has been encountered. + * + * @final + */ + public static function closeOutputBuffers(int $targetLevel, bool $flush): void + { + $status = ob_get_status(true); + $level = \count($status); + $flags = \PHP_OUTPUT_HANDLER_REMOVABLE | ($flush ? \PHP_OUTPUT_HANDLER_FLUSHABLE : \PHP_OUTPUT_HANDLER_CLEANABLE); + + while ($level-- > $targetLevel && ($s = $status[$level]) && (!isset($s['del']) ? !isset($s['flags']) || ($s['flags'] & $flags) === $flags : $s['del'])) { + if ($flush) { + ob_end_flush(); + } else { + ob_end_clean(); + } + } + } + + /** + * Marks a response as safe according to RFC8674. + * + * @see https://tools.ietf.org/html/rfc8674 + */ + public function setContentSafe(bool $safe = true): void + { + if ($safe) { + $this->headers->set('Preference-Applied', 'safe'); + } elseif ('safe' === $this->headers->get('Preference-Applied')) { + $this->headers->remove('Preference-Applied'); + } + + $this->setVary('Prefer', false); + } + + /** + * Checks if we need to remove Cache-Control for SSL encrypted downloads when using IE < 9. + * + * @see http://support.microsoft.com/kb/323308 + * + * @final + */ + protected function ensureIEOverSSLCompatibility(Request $request): void + { + if (false !== stripos($this->headers->get('Content-Disposition') ?? '', 'attachment') && 1 == preg_match('/MSIE (.*?);/i', $request->server->get('HTTP_USER_AGENT') ?? '', $match) && true === $request->isSecure()) { + if ((int) preg_replace('/(MSIE )(.*?);/', '$2', $match[0]) < 9) { + $this->headers->remove('Cache-Control'); + } + } + } +} diff --git a/src/vendor/symfony/http-foundation/ResponseHeaderBag.php b/src/vendor/symfony/http-foundation/ResponseHeaderBag.php new file mode 100644 index 0000000..1df13fa --- /dev/null +++ b/src/vendor/symfony/http-foundation/ResponseHeaderBag.php @@ -0,0 +1,291 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * ResponseHeaderBag is a container for Response HTTP headers. + * + * @author Fabien Potencier + */ +class ResponseHeaderBag extends HeaderBag +{ + public const COOKIES_FLAT = 'flat'; + public const COOKIES_ARRAY = 'array'; + + public const DISPOSITION_ATTACHMENT = 'attachment'; + public const DISPOSITION_INLINE = 'inline'; + + protected $computedCacheControl = []; + protected $cookies = []; + protected $headerNames = []; + + public function __construct(array $headers = []) + { + parent::__construct($headers); + + if (!isset($this->headers['cache-control'])) { + $this->set('Cache-Control', ''); + } + + /* RFC2616 - 14.18 says all Responses need to have a Date */ + if (!isset($this->headers['date'])) { + $this->initDate(); + } + } + + /** + * Returns the headers, with original capitalizations. + * + * @return array + */ + public function allPreserveCase() + { + $headers = []; + foreach ($this->all() as $name => $value) { + $headers[$this->headerNames[$name] ?? $name] = $value; + } + + return $headers; + } + + public function allPreserveCaseWithoutCookies() + { + $headers = $this->allPreserveCase(); + if (isset($this->headerNames['set-cookie'])) { + unset($headers[$this->headerNames['set-cookie']]); + } + + return $headers; + } + + /** + * {@inheritdoc} + */ + public function replace(array $headers = []) + { + $this->headerNames = []; + + parent::replace($headers); + + if (!isset($this->headers['cache-control'])) { + $this->set('Cache-Control', ''); + } + + if (!isset($this->headers['date'])) { + $this->initDate(); + } + } + + /** + * {@inheritdoc} + */ + public function all(string $key = null) + { + $headers = parent::all(); + + if (null !== $key) { + $key = strtr($key, self::UPPER, self::LOWER); + + return 'set-cookie' !== $key ? $headers[$key] ?? [] : array_map('strval', $this->getCookies()); + } + + foreach ($this->getCookies() as $cookie) { + $headers['set-cookie'][] = (string) $cookie; + } + + return $headers; + } + + /** + * {@inheritdoc} + */ + public function set(string $key, $values, bool $replace = true) + { + $uniqueKey = strtr($key, self::UPPER, self::LOWER); + + if ('set-cookie' === $uniqueKey) { + if ($replace) { + $this->cookies = []; + } + foreach ((array) $values as $cookie) { + $this->setCookie(Cookie::fromString($cookie)); + } + $this->headerNames[$uniqueKey] = $key; + + return; + } + + $this->headerNames[$uniqueKey] = $key; + + parent::set($key, $values, $replace); + + // ensure the cache-control header has sensible defaults + if (\in_array($uniqueKey, ['cache-control', 'etag', 'last-modified', 'expires'], true) && '' !== $computed = $this->computeCacheControlValue()) { + $this->headers['cache-control'] = [$computed]; + $this->headerNames['cache-control'] = 'Cache-Control'; + $this->computedCacheControl = $this->parseCacheControl($computed); + } + } + + /** + * {@inheritdoc} + */ + public function remove(string $key) + { + $uniqueKey = strtr($key, self::UPPER, self::LOWER); + unset($this->headerNames[$uniqueKey]); + + if ('set-cookie' === $uniqueKey) { + $this->cookies = []; + + return; + } + + parent::remove($key); + + if ('cache-control' === $uniqueKey) { + $this->computedCacheControl = []; + } + + if ('date' === $uniqueKey) { + $this->initDate(); + } + } + + /** + * {@inheritdoc} + */ + public function hasCacheControlDirective(string $key) + { + return \array_key_exists($key, $this->computedCacheControl); + } + + /** + * {@inheritdoc} + */ + public function getCacheControlDirective(string $key) + { + return $this->computedCacheControl[$key] ?? null; + } + + public function setCookie(Cookie $cookie) + { + $this->cookies[$cookie->getDomain()][$cookie->getPath()][$cookie->getName()] = $cookie; + $this->headerNames['set-cookie'] = 'Set-Cookie'; + } + + /** + * Removes a cookie from the array, but does not unset it in the browser. + */ + public function removeCookie(string $name, ?string $path = '/', string $domain = null) + { + if (null === $path) { + $path = '/'; + } + + unset($this->cookies[$domain][$path][$name]); + + if (empty($this->cookies[$domain][$path])) { + unset($this->cookies[$domain][$path]); + + if (empty($this->cookies[$domain])) { + unset($this->cookies[$domain]); + } + } + + if (empty($this->cookies)) { + unset($this->headerNames['set-cookie']); + } + } + + /** + * Returns an array with all cookies. + * + * @return Cookie[] + * + * @throws \InvalidArgumentException When the $format is invalid + */ + public function getCookies(string $format = self::COOKIES_FLAT) + { + if (!\in_array($format, [self::COOKIES_FLAT, self::COOKIES_ARRAY])) { + throw new \InvalidArgumentException(sprintf('Format "%s" invalid (%s).', $format, implode(', ', [self::COOKIES_FLAT, self::COOKIES_ARRAY]))); + } + + if (self::COOKIES_ARRAY === $format) { + return $this->cookies; + } + + $flattenedCookies = []; + foreach ($this->cookies as $path) { + foreach ($path as $cookies) { + foreach ($cookies as $cookie) { + $flattenedCookies[] = $cookie; + } + } + } + + return $flattenedCookies; + } + + /** + * Clears a cookie in the browser. + */ + public function clearCookie(string $name, ?string $path = '/', string $domain = null, bool $secure = false, bool $httpOnly = true, string $sameSite = null) + { + $this->setCookie(new Cookie($name, null, 1, $path, $domain, $secure, $httpOnly, false, $sameSite)); + } + + /** + * @see HeaderUtils::makeDisposition() + */ + public function makeDisposition(string $disposition, string $filename, string $filenameFallback = '') + { + return HeaderUtils::makeDisposition($disposition, $filename, $filenameFallback); + } + + /** + * Returns the calculated value of the cache-control header. + * + * This considers several other headers and calculates or modifies the + * cache-control header to a sensible, conservative value. + * + * @return string + */ + protected function computeCacheControlValue() + { + if (!$this->cacheControl) { + if ($this->has('Last-Modified') || $this->has('Expires')) { + return 'private, must-revalidate'; // allows for heuristic expiration (RFC 7234 Section 4.2.2) in the case of "Last-Modified" + } + + // conservative by default + return 'no-cache, private'; + } + + $header = $this->getCacheControlHeader(); + if (isset($this->cacheControl['public']) || isset($this->cacheControl['private'])) { + return $header; + } + + // public if s-maxage is defined, private otherwise + if (!isset($this->cacheControl['s-maxage'])) { + return $header.', private'; + } + + return $header; + } + + private function initDate(): void + { + $this->set('Date', gmdate('D, d M Y H:i:s').' GMT'); + } +} diff --git a/src/vendor/symfony/http-foundation/ServerBag.php b/src/vendor/symfony/http-foundation/ServerBag.php new file mode 100644 index 0000000..004af57 --- /dev/null +++ b/src/vendor/symfony/http-foundation/ServerBag.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * ServerBag is a container for HTTP headers from the $_SERVER variable. + * + * @author Fabien Potencier + * @author Bulat Shakirzyanov + * @author Robert Kiss + */ +class ServerBag extends ParameterBag +{ + /** + * Gets the HTTP headers. + * + * @return array + */ + public function getHeaders() + { + $headers = []; + foreach ($this->parameters as $key => $value) { + if (str_starts_with($key, 'HTTP_')) { + $headers[substr($key, 5)] = $value; + } elseif (\in_array($key, ['CONTENT_TYPE', 'CONTENT_LENGTH', 'CONTENT_MD5'], true)) { + $headers[$key] = $value; + } + } + + if (isset($this->parameters['PHP_AUTH_USER'])) { + $headers['PHP_AUTH_USER'] = $this->parameters['PHP_AUTH_USER']; + $headers['PHP_AUTH_PW'] = $this->parameters['PHP_AUTH_PW'] ?? ''; + } else { + /* + * php-cgi under Apache does not pass HTTP Basic user/pass to PHP by default + * For this workaround to work, add these lines to your .htaccess file: + * RewriteCond %{HTTP:Authorization} .+ + * RewriteRule ^ - [E=HTTP_AUTHORIZATION:%0] + * + * A sample .htaccess file: + * RewriteEngine On + * RewriteCond %{HTTP:Authorization} .+ + * RewriteRule ^ - [E=HTTP_AUTHORIZATION:%0] + * RewriteCond %{REQUEST_FILENAME} !-f + * RewriteRule ^(.*)$ index.php [QSA,L] + */ + + $authorizationHeader = null; + if (isset($this->parameters['HTTP_AUTHORIZATION'])) { + $authorizationHeader = $this->parameters['HTTP_AUTHORIZATION']; + } elseif (isset($this->parameters['REDIRECT_HTTP_AUTHORIZATION'])) { + $authorizationHeader = $this->parameters['REDIRECT_HTTP_AUTHORIZATION']; + } + + if (null !== $authorizationHeader) { + if (0 === stripos($authorizationHeader, 'basic ')) { + // Decode AUTHORIZATION header into PHP_AUTH_USER and PHP_AUTH_PW when authorization header is basic + $exploded = explode(':', base64_decode(substr($authorizationHeader, 6)), 2); + if (2 == \count($exploded)) { + [$headers['PHP_AUTH_USER'], $headers['PHP_AUTH_PW']] = $exploded; + } + } elseif (empty($this->parameters['PHP_AUTH_DIGEST']) && (0 === stripos($authorizationHeader, 'digest '))) { + // In some circumstances PHP_AUTH_DIGEST needs to be set + $headers['PHP_AUTH_DIGEST'] = $authorizationHeader; + $this->parameters['PHP_AUTH_DIGEST'] = $authorizationHeader; + } elseif (0 === stripos($authorizationHeader, 'bearer ')) { + /* + * XXX: Since there is no PHP_AUTH_BEARER in PHP predefined variables, + * I'll just set $headers['AUTHORIZATION'] here. + * https://php.net/reserved.variables.server + */ + $headers['AUTHORIZATION'] = $authorizationHeader; + } + } + } + + if (isset($headers['AUTHORIZATION'])) { + return $headers; + } + + // PHP_AUTH_USER/PHP_AUTH_PW + if (isset($headers['PHP_AUTH_USER'])) { + $headers['AUTHORIZATION'] = 'Basic '.base64_encode($headers['PHP_AUTH_USER'].':'.($headers['PHP_AUTH_PW'] ?? '')); + } elseif (isset($headers['PHP_AUTH_DIGEST'])) { + $headers['AUTHORIZATION'] = $headers['PHP_AUTH_DIGEST']; + } + + return $headers; + } +} diff --git a/src/vendor/symfony/http-foundation/Session/Attribute/AttributeBag.php b/src/vendor/symfony/http-foundation/Session/Attribute/AttributeBag.php new file mode 100644 index 0000000..f4f051c --- /dev/null +++ b/src/vendor/symfony/http-foundation/Session/Attribute/AttributeBag.php @@ -0,0 +1,152 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Attribute; + +/** + * This class relates to session attribute storage. + * + * @implements \IteratorAggregate + */ +class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Countable +{ + private $name = 'attributes'; + private $storageKey; + + protected $attributes = []; + + /** + * @param string $storageKey The key used to store attributes in the session + */ + public function __construct(string $storageKey = '_sf2_attributes') + { + $this->storageKey = $storageKey; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + public function setName(string $name) + { + $this->name = $name; + } + + /** + * {@inheritdoc} + */ + public function initialize(array &$attributes) + { + $this->attributes = &$attributes; + } + + /** + * {@inheritdoc} + */ + public function getStorageKey() + { + return $this->storageKey; + } + + /** + * {@inheritdoc} + */ + public function has(string $name) + { + return \array_key_exists($name, $this->attributes); + } + + /** + * {@inheritdoc} + */ + public function get(string $name, $default = null) + { + return \array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default; + } + + /** + * {@inheritdoc} + */ + public function set(string $name, $value) + { + $this->attributes[$name] = $value; + } + + /** + * {@inheritdoc} + */ + public function all() + { + return $this->attributes; + } + + /** + * {@inheritdoc} + */ + public function replace(array $attributes) + { + $this->attributes = []; + foreach ($attributes as $key => $value) { + $this->set($key, $value); + } + } + + /** + * {@inheritdoc} + */ + public function remove(string $name) + { + $retval = null; + if (\array_key_exists($name, $this->attributes)) { + $retval = $this->attributes[$name]; + unset($this->attributes[$name]); + } + + return $retval; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + $return = $this->attributes; + $this->attributes = []; + + return $return; + } + + /** + * Returns an iterator for attributes. + * + * @return \ArrayIterator + */ + #[\ReturnTypeWillChange] + public function getIterator() + { + return new \ArrayIterator($this->attributes); + } + + /** + * Returns the number of attributes. + * + * @return int + */ + #[\ReturnTypeWillChange] + public function count() + { + return \count($this->attributes); + } +} diff --git a/src/vendor/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php b/src/vendor/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php new file mode 100644 index 0000000..cb50696 --- /dev/null +++ b/src/vendor/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Attribute; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * Attributes store. + * + * @author Drak + */ +interface AttributeBagInterface extends SessionBagInterface +{ + /** + * Checks if an attribute is defined. + * + * @return bool + */ + public function has(string $name); + + /** + * Returns an attribute. + * + * @param mixed $default The default value if not found + * + * @return mixed + */ + public function get(string $name, $default = null); + + /** + * Sets an attribute. + * + * @param mixed $value + */ + public function set(string $name, $value); + + /** + * Returns attributes. + * + * @return array + */ + public function all(); + + public function replace(array $attributes); + + /** + * Removes an attribute. + * + * @return mixed The removed value or null when it does not exist + */ + public function remove(string $name); +} diff --git a/src/vendor/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php b/src/vendor/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php new file mode 100644 index 0000000..864b35f --- /dev/null +++ b/src/vendor/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php @@ -0,0 +1,161 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Attribute; + +trigger_deprecation('symfony/http-foundation', '5.3', 'The "%s" class is deprecated.', NamespacedAttributeBag::class); + +/** + * This class provides structured storage of session attributes using + * a name spacing character in the key. + * + * @author Drak + * + * @deprecated since Symfony 5.3 + */ +class NamespacedAttributeBag extends AttributeBag +{ + private $namespaceCharacter; + + /** + * @param string $storageKey Session storage key + * @param string $namespaceCharacter Namespace character to use in keys + */ + public function __construct(string $storageKey = '_sf2_attributes', string $namespaceCharacter = '/') + { + $this->namespaceCharacter = $namespaceCharacter; + parent::__construct($storageKey); + } + + /** + * {@inheritdoc} + */ + public function has(string $name) + { + // reference mismatch: if fixed, re-introduced in array_key_exists; keep as it is + $attributes = $this->resolveAttributePath($name); + $name = $this->resolveKey($name); + + if (null === $attributes) { + return false; + } + + return \array_key_exists($name, $attributes); + } + + /** + * {@inheritdoc} + */ + public function get(string $name, $default = null) + { + // reference mismatch: if fixed, re-introduced in array_key_exists; keep as it is + $attributes = $this->resolveAttributePath($name); + $name = $this->resolveKey($name); + + if (null === $attributes) { + return $default; + } + + return \array_key_exists($name, $attributes) ? $attributes[$name] : $default; + } + + /** + * {@inheritdoc} + */ + public function set(string $name, $value) + { + $attributes = &$this->resolveAttributePath($name, true); + $name = $this->resolveKey($name); + $attributes[$name] = $value; + } + + /** + * {@inheritdoc} + */ + public function remove(string $name) + { + $retval = null; + $attributes = &$this->resolveAttributePath($name); + $name = $this->resolveKey($name); + if (null !== $attributes && \array_key_exists($name, $attributes)) { + $retval = $attributes[$name]; + unset($attributes[$name]); + } + + return $retval; + } + + /** + * Resolves a path in attributes property and returns it as a reference. + * + * This method allows structured namespacing of session attributes. + * + * @param string $name Key name + * @param bool $writeContext Write context, default false + * + * @return array|null + */ + protected function &resolveAttributePath(string $name, bool $writeContext = false) + { + $array = &$this->attributes; + $name = (str_starts_with($name, $this->namespaceCharacter)) ? substr($name, 1) : $name; + + // Check if there is anything to do, else return + if (!$name) { + return $array; + } + + $parts = explode($this->namespaceCharacter, $name); + if (\count($parts) < 2) { + if (!$writeContext) { + return $array; + } + + $array[$parts[0]] = []; + + return $array; + } + + unset($parts[\count($parts) - 1]); + + foreach ($parts as $part) { + if (null !== $array && !\array_key_exists($part, $array)) { + if (!$writeContext) { + $null = null; + + return $null; + } + + $array[$part] = []; + } + + $array = &$array[$part]; + } + + return $array; + } + + /** + * Resolves the key from the name. + * + * This is the last part in a dot separated string. + * + * @return string + */ + protected function resolveKey(string $name) + { + if (false !== $pos = strrpos($name, $this->namespaceCharacter)) { + $name = substr($name, $pos + 1); + } + + return $name; + } +} diff --git a/src/vendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php b/src/vendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php new file mode 100644 index 0000000..8aab3a1 --- /dev/null +++ b/src/vendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php @@ -0,0 +1,161 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Flash; + +/** + * AutoExpireFlashBag flash message container. + * + * @author Drak + */ +class AutoExpireFlashBag implements FlashBagInterface +{ + private $name = 'flashes'; + private $flashes = ['display' => [], 'new' => []]; + private $storageKey; + + /** + * @param string $storageKey The key used to store flashes in the session + */ + public function __construct(string $storageKey = '_symfony_flashes') + { + $this->storageKey = $storageKey; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + public function setName(string $name) + { + $this->name = $name; + } + + /** + * {@inheritdoc} + */ + public function initialize(array &$flashes) + { + $this->flashes = &$flashes; + + // The logic: messages from the last request will be stored in new, so we move them to previous + // This request we will show what is in 'display'. What is placed into 'new' this time round will + // be moved to display next time round. + $this->flashes['display'] = \array_key_exists('new', $this->flashes) ? $this->flashes['new'] : []; + $this->flashes['new'] = []; + } + + /** + * {@inheritdoc} + */ + public function add(string $type, $message) + { + $this->flashes['new'][$type][] = $message; + } + + /** + * {@inheritdoc} + */ + public function peek(string $type, array $default = []) + { + return $this->has($type) ? $this->flashes['display'][$type] : $default; + } + + /** + * {@inheritdoc} + */ + public function peekAll() + { + return \array_key_exists('display', $this->flashes) ? $this->flashes['display'] : []; + } + + /** + * {@inheritdoc} + */ + public function get(string $type, array $default = []) + { + $return = $default; + + if (!$this->has($type)) { + return $return; + } + + if (isset($this->flashes['display'][$type])) { + $return = $this->flashes['display'][$type]; + unset($this->flashes['display'][$type]); + } + + return $return; + } + + /** + * {@inheritdoc} + */ + public function all() + { + $return = $this->flashes['display']; + $this->flashes['display'] = []; + + return $return; + } + + /** + * {@inheritdoc} + */ + public function setAll(array $messages) + { + $this->flashes['new'] = $messages; + } + + /** + * {@inheritdoc} + */ + public function set(string $type, $messages) + { + $this->flashes['new'][$type] = (array) $messages; + } + + /** + * {@inheritdoc} + */ + public function has(string $type) + { + return \array_key_exists($type, $this->flashes['display']) && $this->flashes['display'][$type]; + } + + /** + * {@inheritdoc} + */ + public function keys() + { + return array_keys($this->flashes['display']); + } + + /** + * {@inheritdoc} + */ + public function getStorageKey() + { + return $this->storageKey; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + return $this->all(); + } +} diff --git a/src/vendor/symfony/http-foundation/Session/Flash/FlashBag.php b/src/vendor/symfony/http-foundation/Session/Flash/FlashBag.php new file mode 100644 index 0000000..88df750 --- /dev/null +++ b/src/vendor/symfony/http-foundation/Session/Flash/FlashBag.php @@ -0,0 +1,152 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Flash; + +/** + * FlashBag flash message container. + * + * @author Drak + */ +class FlashBag implements FlashBagInterface +{ + private $name = 'flashes'; + private $flashes = []; + private $storageKey; + + /** + * @param string $storageKey The key used to store flashes in the session + */ + public function __construct(string $storageKey = '_symfony_flashes') + { + $this->storageKey = $storageKey; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + public function setName(string $name) + { + $this->name = $name; + } + + /** + * {@inheritdoc} + */ + public function initialize(array &$flashes) + { + $this->flashes = &$flashes; + } + + /** + * {@inheritdoc} + */ + public function add(string $type, $message) + { + $this->flashes[$type][] = $message; + } + + /** + * {@inheritdoc} + */ + public function peek(string $type, array $default = []) + { + return $this->has($type) ? $this->flashes[$type] : $default; + } + + /** + * {@inheritdoc} + */ + public function peekAll() + { + return $this->flashes; + } + + /** + * {@inheritdoc} + */ + public function get(string $type, array $default = []) + { + if (!$this->has($type)) { + return $default; + } + + $return = $this->flashes[$type]; + + unset($this->flashes[$type]); + + return $return; + } + + /** + * {@inheritdoc} + */ + public function all() + { + $return = $this->peekAll(); + $this->flashes = []; + + return $return; + } + + /** + * {@inheritdoc} + */ + public function set(string $type, $messages) + { + $this->flashes[$type] = (array) $messages; + } + + /** + * {@inheritdoc} + */ + public function setAll(array $messages) + { + $this->flashes = $messages; + } + + /** + * {@inheritdoc} + */ + public function has(string $type) + { + return \array_key_exists($type, $this->flashes) && $this->flashes[$type]; + } + + /** + * {@inheritdoc} + */ + public function keys() + { + return array_keys($this->flashes); + } + + /** + * {@inheritdoc} + */ + public function getStorageKey() + { + return $this->storageKey; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + return $this->all(); + } +} diff --git a/src/vendor/symfony/http-foundation/Session/Flash/FlashBagInterface.php b/src/vendor/symfony/http-foundation/Session/Flash/FlashBagInterface.php new file mode 100644 index 0000000..8713e71 --- /dev/null +++ b/src/vendor/symfony/http-foundation/Session/Flash/FlashBagInterface.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Flash; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * FlashBagInterface. + * + * @author Drak + */ +interface FlashBagInterface extends SessionBagInterface +{ + /** + * Adds a flash message for the given type. + * + * @param mixed $message + */ + public function add(string $type, $message); + + /** + * Registers one or more messages for a given type. + * + * @param string|array $messages + */ + public function set(string $type, $messages); + + /** + * Gets flash messages for a given type. + * + * @param string $type Message category type + * @param array $default Default value if $type does not exist + * + * @return array + */ + public function peek(string $type, array $default = []); + + /** + * Gets all flash messages. + * + * @return array + */ + public function peekAll(); + + /** + * Gets and clears flash from the stack. + * + * @param array $default Default value if $type does not exist + * + * @return array + */ + public function get(string $type, array $default = []); + + /** + * Gets and clears flashes from the stack. + * + * @return array + */ + public function all(); + + /** + * Sets all flash messages. + */ + public function setAll(array $messages); + + /** + * Has flash messages for a given type? + * + * @return bool + */ + public function has(string $type); + + /** + * Returns a list of all defined types. + * + * @return array + */ + public function keys(); +} diff --git a/src/vendor/symfony/http-foundation/Session/Session.php b/src/vendor/symfony/http-foundation/Session/Session.php new file mode 100644 index 0000000..022e398 --- /dev/null +++ b/src/vendor/symfony/http-foundation/Session/Session.php @@ -0,0 +1,285 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBagInterface; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface; +use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; +use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(AttributeBag::class); +class_exists(FlashBag::class); +class_exists(SessionBagProxy::class); + +/** + * @author Fabien Potencier + * @author Drak + * + * @implements \IteratorAggregate + */ +class Session implements SessionInterface, \IteratorAggregate, \Countable +{ + protected $storage; + + private $flashName; + private $attributeName; + private $data = []; + private $usageIndex = 0; + private $usageReporter; + + public function __construct(SessionStorageInterface $storage = null, AttributeBagInterface $attributes = null, FlashBagInterface $flashes = null, callable $usageReporter = null) + { + $this->storage = $storage ?? new NativeSessionStorage(); + $this->usageReporter = $usageReporter; + + $attributes = $attributes ?? new AttributeBag(); + $this->attributeName = $attributes->getName(); + $this->registerBag($attributes); + + $flashes = $flashes ?? new FlashBag(); + $this->flashName = $flashes->getName(); + $this->registerBag($flashes); + } + + /** + * {@inheritdoc} + */ + public function start() + { + return $this->storage->start(); + } + + /** + * {@inheritdoc} + */ + public function has(string $name) + { + return $this->getAttributeBag()->has($name); + } + + /** + * {@inheritdoc} + */ + public function get(string $name, $default = null) + { + return $this->getAttributeBag()->get($name, $default); + } + + /** + * {@inheritdoc} + */ + public function set(string $name, $value) + { + $this->getAttributeBag()->set($name, $value); + } + + /** + * {@inheritdoc} + */ + public function all() + { + return $this->getAttributeBag()->all(); + } + + /** + * {@inheritdoc} + */ + public function replace(array $attributes) + { + $this->getAttributeBag()->replace($attributes); + } + + /** + * {@inheritdoc} + */ + public function remove(string $name) + { + return $this->getAttributeBag()->remove($name); + } + + /** + * {@inheritdoc} + */ + public function clear() + { + $this->getAttributeBag()->clear(); + } + + /** + * {@inheritdoc} + */ + public function isStarted() + { + return $this->storage->isStarted(); + } + + /** + * Returns an iterator for attributes. + * + * @return \ArrayIterator + */ + #[\ReturnTypeWillChange] + public function getIterator() + { + return new \ArrayIterator($this->getAttributeBag()->all()); + } + + /** + * Returns the number of attributes. + * + * @return int + */ + #[\ReturnTypeWillChange] + public function count() + { + return \count($this->getAttributeBag()->all()); + } + + public function &getUsageIndex(): int + { + return $this->usageIndex; + } + + /** + * @internal + */ + public function isEmpty(): bool + { + if ($this->isStarted()) { + ++$this->usageIndex; + if ($this->usageReporter && 0 <= $this->usageIndex) { + ($this->usageReporter)(); + } + } + foreach ($this->data as &$data) { + if (!empty($data)) { + return false; + } + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function invalidate(int $lifetime = null) + { + $this->storage->clear(); + + return $this->migrate(true, $lifetime); + } + + /** + * {@inheritdoc} + */ + public function migrate(bool $destroy = false, int $lifetime = null) + { + return $this->storage->regenerate($destroy, $lifetime); + } + + /** + * {@inheritdoc} + */ + public function save() + { + $this->storage->save(); + } + + /** + * {@inheritdoc} + */ + public function getId() + { + return $this->storage->getId(); + } + + /** + * {@inheritdoc} + */ + public function setId(string $id) + { + if ($this->storage->getId() !== $id) { + $this->storage->setId($id); + } + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->storage->getName(); + } + + /** + * {@inheritdoc} + */ + public function setName(string $name) + { + $this->storage->setName($name); + } + + /** + * {@inheritdoc} + */ + public function getMetadataBag() + { + ++$this->usageIndex; + if ($this->usageReporter && 0 <= $this->usageIndex) { + ($this->usageReporter)(); + } + + return $this->storage->getMetadataBag(); + } + + /** + * {@inheritdoc} + */ + public function registerBag(SessionBagInterface $bag) + { + $this->storage->registerBag(new SessionBagProxy($bag, $this->data, $this->usageIndex, $this->usageReporter)); + } + + /** + * {@inheritdoc} + */ + public function getBag(string $name) + { + $bag = $this->storage->getBag($name); + + return method_exists($bag, 'getBag') ? $bag->getBag() : $bag; + } + + /** + * Gets the flashbag interface. + * + * @return FlashBagInterface + */ + public function getFlashBag() + { + return $this->getBag($this->flashName); + } + + /** + * Gets the attributebag interface. + * + * Note that this method was added to help with IDE autocompletion. + */ + private function getAttributeBag(): AttributeBagInterface + { + return $this->getBag($this->attributeName); + } +} diff --git a/src/vendor/symfony/http-foundation/Session/SessionBagInterface.php b/src/vendor/symfony/http-foundation/Session/SessionBagInterface.php new file mode 100644 index 0000000..8e37d06 --- /dev/null +++ b/src/vendor/symfony/http-foundation/Session/SessionBagInterface.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +/** + * Session Bag store. + * + * @author Drak + */ +interface SessionBagInterface +{ + /** + * Gets this bag's name. + * + * @return string + */ + public function getName(); + + /** + * Initializes the Bag. + */ + public function initialize(array &$array); + + /** + * Gets the storage key for this bag. + * + * @return string + */ + public function getStorageKey(); + + /** + * Clears out data from bag. + * + * @return mixed Whatever data was contained + */ + public function clear(); +} diff --git a/src/vendor/symfony/http-foundation/Session/SessionBagProxy.php b/src/vendor/symfony/http-foundation/Session/SessionBagProxy.php new file mode 100644 index 0000000..90aa010 --- /dev/null +++ b/src/vendor/symfony/http-foundation/Session/SessionBagProxy.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +/** + * @author Nicolas Grekas + * + * @internal + */ +final class SessionBagProxy implements SessionBagInterface +{ + private $bag; + private $data; + private $usageIndex; + private $usageReporter; + + public function __construct(SessionBagInterface $bag, array &$data, ?int &$usageIndex, ?callable $usageReporter) + { + $this->bag = $bag; + $this->data = &$data; + $this->usageIndex = &$usageIndex; + $this->usageReporter = $usageReporter; + } + + public function getBag(): SessionBagInterface + { + ++$this->usageIndex; + if ($this->usageReporter && 0 <= $this->usageIndex) { + ($this->usageReporter)(); + } + + return $this->bag; + } + + public function isEmpty(): bool + { + if (!isset($this->data[$this->bag->getStorageKey()])) { + return true; + } + ++$this->usageIndex; + if ($this->usageReporter && 0 <= $this->usageIndex) { + ($this->usageReporter)(); + } + + return empty($this->data[$this->bag->getStorageKey()]); + } + + /** + * {@inheritdoc} + */ + public function getName(): string + { + return $this->bag->getName(); + } + + /** + * {@inheritdoc} + */ + public function initialize(array &$array): void + { + ++$this->usageIndex; + if ($this->usageReporter && 0 <= $this->usageIndex) { + ($this->usageReporter)(); + } + + $this->data[$this->bag->getStorageKey()] = &$array; + + $this->bag->initialize($array); + } + + /** + * {@inheritdoc} + */ + public function getStorageKey(): string + { + return $this->bag->getStorageKey(); + } + + /** + * {@inheritdoc} + */ + public function clear() + { + return $this->bag->clear(); + } +} diff --git a/src/vendor/symfony/http-foundation/Session/SessionFactory.php b/src/vendor/symfony/http-foundation/Session/SessionFactory.php new file mode 100644 index 0000000..04c4b06 --- /dev/null +++ b/src/vendor/symfony/http-foundation/Session/SessionFactory.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageFactoryInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(Session::class); + +/** + * @author Jérémy Derussé + */ +class SessionFactory implements SessionFactoryInterface +{ + private $requestStack; + private $storageFactory; + private $usageReporter; + + public function __construct(RequestStack $requestStack, SessionStorageFactoryInterface $storageFactory, callable $usageReporter = null) + { + $this->requestStack = $requestStack; + $this->storageFactory = $storageFactory; + $this->usageReporter = $usageReporter; + } + + public function createSession(): SessionInterface + { + return new Session($this->storageFactory->createStorage($this->requestStack->getMainRequest()), null, null, $this->usageReporter); + } +} diff --git a/src/vendor/symfony/http-foundation/Session/SessionFactoryInterface.php b/src/vendor/symfony/http-foundation/Session/SessionFactoryInterface.php new file mode 100644 index 0000000..b24fdc4 --- /dev/null +++ b/src/vendor/symfony/http-foundation/Session/SessionFactoryInterface.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +/** + * @author Kevin Bond + */ +interface SessionFactoryInterface +{ + public function createSession(): SessionInterface; +} diff --git a/src/vendor/symfony/http-foundation/Session/SessionInterface.php b/src/vendor/symfony/http-foundation/Session/SessionInterface.php new file mode 100644 index 0000000..e673383 --- /dev/null +++ b/src/vendor/symfony/http-foundation/Session/SessionInterface.php @@ -0,0 +1,166 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +use Symfony\Component\HttpFoundation\Session\Storage\MetadataBag; + +/** + * Interface for the session. + * + * @author Drak + */ +interface SessionInterface +{ + /** + * Starts the session storage. + * + * @return bool + * + * @throws \RuntimeException if session fails to start + */ + public function start(); + + /** + * Returns the session ID. + * + * @return string + */ + public function getId(); + + /** + * Sets the session ID. + */ + public function setId(string $id); + + /** + * Returns the session name. + * + * @return string + */ + public function getName(); + + /** + * Sets the session name. + */ + public function setName(string $name); + + /** + * Invalidates the current session. + * + * Clears all session attributes and flashes and regenerates the + * session and deletes the old session from persistence. + * + * @param int|null $lifetime Sets the cookie lifetime for the session cookie. A null value + * will leave the system settings unchanged, 0 sets the cookie + * to expire with browser session. Time is in seconds, and is + * not a Unix timestamp. + * + * @return bool + */ + public function invalidate(int $lifetime = null); + + /** + * Migrates the current session to a new session id while maintaining all + * session attributes. + * + * @param bool $destroy Whether to delete the old session or leave it to garbage collection + * @param int|null $lifetime Sets the cookie lifetime for the session cookie. A null value + * will leave the system settings unchanged, 0 sets the cookie + * to expire with browser session. Time is in seconds, and is + * not a Unix timestamp. + * + * @return bool + */ + public function migrate(bool $destroy = false, int $lifetime = null); + + /** + * Force the session to be saved and closed. + * + * This method is generally not required for real sessions as + * the session will be automatically saved at the end of + * code execution. + */ + public function save(); + + /** + * Checks if an attribute is defined. + * + * @return bool + */ + public function has(string $name); + + /** + * Returns an attribute. + * + * @param mixed $default The default value if not found + * + * @return mixed + */ + public function get(string $name, $default = null); + + /** + * Sets an attribute. + * + * @param mixed $value + */ + public function set(string $name, $value); + + /** + * Returns attributes. + * + * @return array + */ + public function all(); + + /** + * Sets attributes. + */ + public function replace(array $attributes); + + /** + * Removes an attribute. + * + * @return mixed The removed value or null when it does not exist + */ + public function remove(string $name); + + /** + * Clears all attributes. + */ + public function clear(); + + /** + * Checks if the session was started. + * + * @return bool + */ + public function isStarted(); + + /** + * Registers a SessionBagInterface with the session. + */ + public function registerBag(SessionBagInterface $bag); + + /** + * Gets a bag instance by name. + * + * @return SessionBagInterface + */ + public function getBag(string $name); + + /** + * Gets session meta. + * + * @return MetadataBag + */ + public function getMetadataBag(); +} diff --git a/src/vendor/symfony/http-foundation/Session/SessionUtils.php b/src/vendor/symfony/http-foundation/Session/SessionUtils.php new file mode 100644 index 0000000..b5bce4a --- /dev/null +++ b/src/vendor/symfony/http-foundation/Session/SessionUtils.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +/** + * Session utility functions. + * + * @author Nicolas Grekas + * @author Rémon van de Kamp + * + * @internal + */ +final class SessionUtils +{ + /** + * Finds the session header amongst the headers that are to be sent, removes it, and returns + * it so the caller can process it further. + */ + public static function popSessionCookie(string $sessionName, string $sessionId): ?string + { + $sessionCookie = null; + $sessionCookiePrefix = sprintf(' %s=', urlencode($sessionName)); + $sessionCookieWithId = sprintf('%s%s;', $sessionCookiePrefix, urlencode($sessionId)); + $otherCookies = []; + foreach (headers_list() as $h) { + if (0 !== stripos($h, 'Set-Cookie:')) { + continue; + } + if (11 === strpos($h, $sessionCookiePrefix, 11)) { + $sessionCookie = $h; + + if (11 !== strpos($h, $sessionCookieWithId, 11)) { + $otherCookies[] = $h; + } + } else { + $otherCookies[] = $h; + } + } + if (null === $sessionCookie) { + return null; + } + + header_remove('Set-Cookie'); + foreach ($otherCookies as $h) { + header($h, false); + } + + return $sessionCookie; + } +} diff --git a/src/vendor/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php b/src/vendor/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php new file mode 100644 index 0000000..35d7b4b --- /dev/null +++ b/src/vendor/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php @@ -0,0 +1,155 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +use Symfony\Component\HttpFoundation\Session\SessionUtils; + +/** + * This abstract session handler provides a generic implementation + * of the PHP 7.0 SessionUpdateTimestampHandlerInterface, + * enabling strict and lazy session handling. + * + * @author Nicolas Grekas + */ +abstract class AbstractSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface +{ + private $sessionName; + private $prefetchId; + private $prefetchData; + private $newSessionId; + private $igbinaryEmptyData; + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function open($savePath, $sessionName) + { + $this->sessionName = $sessionName; + if (!headers_sent() && !\ini_get('session.cache_limiter') && '0' !== \ini_get('session.cache_limiter')) { + header(sprintf('Cache-Control: max-age=%d, private, must-revalidate', 60 * (int) \ini_get('session.cache_expire'))); + } + + return true; + } + + /** + * @return string + */ + abstract protected function doRead(string $sessionId); + + /** + * @return bool + */ + abstract protected function doWrite(string $sessionId, string $data); + + /** + * @return bool + */ + abstract protected function doDestroy(string $sessionId); + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function validateId($sessionId) + { + $this->prefetchData = $this->read($sessionId); + $this->prefetchId = $sessionId; + + if (\PHP_VERSION_ID < 70317 || (70400 <= \PHP_VERSION_ID && \PHP_VERSION_ID < 70405)) { + // work around https://bugs.php.net/79413 + foreach (debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS) as $frame) { + if (!isset($frame['class']) && isset($frame['function']) && \in_array($frame['function'], ['session_regenerate_id', 'session_create_id'], true)) { + return '' === $this->prefetchData; + } + } + } + + return '' !== $this->prefetchData; + } + + /** + * @return string + */ + #[\ReturnTypeWillChange] + public function read($sessionId) + { + if (null !== $this->prefetchId) { + $prefetchId = $this->prefetchId; + $prefetchData = $this->prefetchData; + $this->prefetchId = $this->prefetchData = null; + + if ($prefetchId === $sessionId || '' === $prefetchData) { + $this->newSessionId = '' === $prefetchData ? $sessionId : null; + + return $prefetchData; + } + } + + $data = $this->doRead($sessionId); + $this->newSessionId = '' === $data ? $sessionId : null; + + return $data; + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function write($sessionId, $data) + { + if (null === $this->igbinaryEmptyData) { + // see https://github.com/igbinary/igbinary/issues/146 + $this->igbinaryEmptyData = \function_exists('igbinary_serialize') ? igbinary_serialize([]) : ''; + } + if ('' === $data || $this->igbinaryEmptyData === $data) { + return $this->destroy($sessionId); + } + $this->newSessionId = null; + + return $this->doWrite($sessionId, $data); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function destroy($sessionId) + { + if (!headers_sent() && filter_var(\ini_get('session.use_cookies'), \FILTER_VALIDATE_BOOLEAN)) { + if (!$this->sessionName) { + throw new \LogicException(sprintf('Session name cannot be empty, did you forget to call "parent::open()" in "%s"?.', static::class)); + } + $cookie = SessionUtils::popSessionCookie($this->sessionName, $sessionId); + + /* + * We send an invalidation Set-Cookie header (zero lifetime) + * when either the session was started or a cookie with + * the session name was sent by the client (in which case + * we know it's invalid as a valid session cookie would've + * started the session). + */ + if (null === $cookie || isset($_COOKIE[$this->sessionName])) { + if (\PHP_VERSION_ID < 70300) { + setcookie($this->sessionName, '', 0, \ini_get('session.cookie_path'), \ini_get('session.cookie_domain'), filter_var(\ini_get('session.cookie_secure'), \FILTER_VALIDATE_BOOLEAN), filter_var(\ini_get('session.cookie_httponly'), \FILTER_VALIDATE_BOOLEAN)); + } else { + $params = session_get_cookie_params(); + unset($params['lifetime']); + setcookie($this->sessionName, '', $params); + } + } + } + + return $this->newSessionId === $sessionId || $this->doDestroy($sessionId); + } +} diff --git a/src/vendor/symfony/http-foundation/Session/Storage/Handler/IdentityMarshaller.php b/src/vendor/symfony/http-foundation/Session/Storage/Handler/IdentityMarshaller.php new file mode 100644 index 0000000..bea3a32 --- /dev/null +++ b/src/vendor/symfony/http-foundation/Session/Storage/Handler/IdentityMarshaller.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +use Symfony\Component\Cache\Marshaller\MarshallerInterface; + +/** + * @author Ahmed TAILOULOUTE + */ +class IdentityMarshaller implements MarshallerInterface +{ + /** + * {@inheritdoc} + */ + public function marshall(array $values, ?array &$failed): array + { + foreach ($values as $key => $value) { + if (!\is_string($value)) { + throw new \LogicException(sprintf('%s accepts only string as data.', __METHOD__)); + } + } + + return $values; + } + + /** + * {@inheritdoc} + */ + public function unmarshall(string $value): string + { + return $value; + } +} diff --git a/src/vendor/symfony/http-foundation/Session/Storage/Handler/MarshallingSessionHandler.php b/src/vendor/symfony/http-foundation/Session/Storage/Handler/MarshallingSessionHandler.php new file mode 100644 index 0000000..c321c8c --- /dev/null +++ b/src/vendor/symfony/http-foundation/Session/Storage/Handler/MarshallingSessionHandler.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +use Symfony\Component\Cache\Marshaller\MarshallerInterface; + +/** + * @author Ahmed TAILOULOUTE + */ +class MarshallingSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface +{ + private $handler; + private $marshaller; + + public function __construct(AbstractSessionHandler $handler, MarshallerInterface $marshaller) + { + $this->handler = $handler; + $this->marshaller = $marshaller; + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function open($savePath, $name) + { + return $this->handler->open($savePath, $name); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function close() + { + return $this->handler->close(); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function destroy($sessionId) + { + return $this->handler->destroy($sessionId); + } + + /** + * @return int|false + */ + #[\ReturnTypeWillChange] + public function gc($maxlifetime) + { + return $this->handler->gc($maxlifetime); + } + + /** + * @return string + */ + #[\ReturnTypeWillChange] + public function read($sessionId) + { + return $this->marshaller->unmarshall($this->handler->read($sessionId)); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function write($sessionId, $data) + { + $failed = []; + $marshalledData = $this->marshaller->marshall(['data' => $data], $failed); + + if (isset($failed['data'])) { + return false; + } + + return $this->handler->write($sessionId, $marshalledData['data']); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function validateId($sessionId) + { + return $this->handler->validateId($sessionId); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function updateTimestamp($sessionId, $data) + { + return $this->handler->updateTimestamp($sessionId, $data); + } +} diff --git a/src/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php b/src/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php new file mode 100644 index 0000000..e0ec4d2 --- /dev/null +++ b/src/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * Memcached based session storage handler based on the Memcached class + * provided by the PHP memcached extension. + * + * @see https://php.net/memcached + * + * @author Drak + */ +class MemcachedSessionHandler extends AbstractSessionHandler +{ + private $memcached; + + /** + * @var int Time to live in seconds + */ + private $ttl; + + /** + * @var string Key prefix for shared environments + */ + private $prefix; + + /** + * Constructor. + * + * List of available options: + * * prefix: The prefix to use for the memcached keys in order to avoid collision + * * ttl: The time to live in seconds. + * + * @throws \InvalidArgumentException When unsupported options are passed + */ + public function __construct(\Memcached $memcached, array $options = []) + { + $this->memcached = $memcached; + + if ($diff = array_diff(array_keys($options), ['prefix', 'expiretime', 'ttl'])) { + throw new \InvalidArgumentException(sprintf('The following options are not supported "%s".', implode(', ', $diff))); + } + + $this->ttl = $options['expiretime'] ?? $options['ttl'] ?? null; + $this->prefix = $options['prefix'] ?? 'sf2s'; + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function close() + { + return $this->memcached->quit(); + } + + /** + * {@inheritdoc} + */ + protected function doRead(string $sessionId) + { + return $this->memcached->get($this->prefix.$sessionId) ?: ''; + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function updateTimestamp($sessionId, $data) + { + $this->memcached->touch($this->prefix.$sessionId, $this->getCompatibleTtl()); + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doWrite(string $sessionId, string $data) + { + return $this->memcached->set($this->prefix.$sessionId, $data, $this->getCompatibleTtl()); + } + + private function getCompatibleTtl(): int + { + $ttl = (int) ($this->ttl ?? \ini_get('session.gc_maxlifetime')); + + // If the relative TTL that is used exceeds 30 days, memcached will treat the value as Unix time. + // We have to convert it to an absolute Unix time at this point, to make sure the TTL is correct. + if ($ttl > 60 * 60 * 24 * 30) { + $ttl += time(); + } + + return $ttl; + } + + /** + * {@inheritdoc} + */ + protected function doDestroy(string $sessionId) + { + $result = $this->memcached->delete($this->prefix.$sessionId); + + return $result || \Memcached::RES_NOTFOUND == $this->memcached->getResultCode(); + } + + /** + * @return int|false + */ + #[\ReturnTypeWillChange] + public function gc($maxlifetime) + { + // not required here because memcached will auto expire the records anyhow. + return 0; + } + + /** + * Return a Memcached instance. + * + * @return \Memcached + */ + protected function getMemcached() + { + return $this->memcached; + } +} diff --git a/src/vendor/symfony/http-foundation/Session/Storage/Handler/MigratingSessionHandler.php b/src/vendor/symfony/http-foundation/Session/Storage/Handler/MigratingSessionHandler.php new file mode 100644 index 0000000..bf27ca6 --- /dev/null +++ b/src/vendor/symfony/http-foundation/Session/Storage/Handler/MigratingSessionHandler.php @@ -0,0 +1,139 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * Migrating session handler for migrating from one handler to another. It reads + * from the current handler and writes both the current and new ones. + * + * It ignores errors from the new handler. + * + * @author Ross Motley + * @author Oliver Radwell + */ +class MigratingSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface +{ + /** + * @var \SessionHandlerInterface&\SessionUpdateTimestampHandlerInterface + */ + private $currentHandler; + + /** + * @var \SessionHandlerInterface&\SessionUpdateTimestampHandlerInterface + */ + private $writeOnlyHandler; + + public function __construct(\SessionHandlerInterface $currentHandler, \SessionHandlerInterface $writeOnlyHandler) + { + if (!$currentHandler instanceof \SessionUpdateTimestampHandlerInterface) { + $currentHandler = new StrictSessionHandler($currentHandler); + } + if (!$writeOnlyHandler instanceof \SessionUpdateTimestampHandlerInterface) { + $writeOnlyHandler = new StrictSessionHandler($writeOnlyHandler); + } + + $this->currentHandler = $currentHandler; + $this->writeOnlyHandler = $writeOnlyHandler; + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function close() + { + $result = $this->currentHandler->close(); + $this->writeOnlyHandler->close(); + + return $result; + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function destroy($sessionId) + { + $result = $this->currentHandler->destroy($sessionId); + $this->writeOnlyHandler->destroy($sessionId); + + return $result; + } + + /** + * @return int|false + */ + #[\ReturnTypeWillChange] + public function gc($maxlifetime) + { + $result = $this->currentHandler->gc($maxlifetime); + $this->writeOnlyHandler->gc($maxlifetime); + + return $result; + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function open($savePath, $sessionName) + { + $result = $this->currentHandler->open($savePath, $sessionName); + $this->writeOnlyHandler->open($savePath, $sessionName); + + return $result; + } + + /** + * @return string + */ + #[\ReturnTypeWillChange] + public function read($sessionId) + { + // No reading from new handler until switch-over + return $this->currentHandler->read($sessionId); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function write($sessionId, $sessionData) + { + $result = $this->currentHandler->write($sessionId, $sessionData); + $this->writeOnlyHandler->write($sessionId, $sessionData); + + return $result; + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function validateId($sessionId) + { + // No reading from new handler until switch-over + return $this->currentHandler->validateId($sessionId); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function updateTimestamp($sessionId, $sessionData) + { + $result = $this->currentHandler->updateTimestamp($sessionId, $sessionData); + $this->writeOnlyHandler->updateTimestamp($sessionId, $sessionData); + + return $result; + } +} diff --git a/src/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php b/src/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php new file mode 100644 index 0000000..ef8f719 --- /dev/null +++ b/src/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php @@ -0,0 +1,193 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +use MongoDB\BSON\Binary; +use MongoDB\BSON\UTCDateTime; +use MongoDB\Client; +use MongoDB\Collection; + +/** + * Session handler using the mongodb/mongodb package and MongoDB driver extension. + * + * @author Markus Bachmann + * + * @see https://packagist.org/packages/mongodb/mongodb + * @see https://php.net/mongodb + */ +class MongoDbSessionHandler extends AbstractSessionHandler +{ + private $mongo; + + /** + * @var Collection + */ + private $collection; + + /** + * @var array + */ + private $options; + + /** + * Constructor. + * + * List of available options: + * * database: The name of the database [required] + * * collection: The name of the collection [required] + * * id_field: The field name for storing the session id [default: _id] + * * data_field: The field name for storing the session data [default: data] + * * time_field: The field name for storing the timestamp [default: time] + * * expiry_field: The field name for storing the expiry-timestamp [default: expires_at]. + * + * It is strongly recommended to put an index on the `expiry_field` for + * garbage-collection. Alternatively it's possible to automatically expire + * the sessions in the database as described below: + * + * A TTL collections can be used on MongoDB 2.2+ to cleanup expired sessions + * automatically. Such an index can for example look like this: + * + * db..createIndex( + * { "": 1 }, + * { "expireAfterSeconds": 0 } + * ) + * + * More details on: https://docs.mongodb.org/manual/tutorial/expire-data/ + * + * If you use such an index, you can drop `gc_probability` to 0 since + * no garbage-collection is required. + * + * @throws \InvalidArgumentException When "database" or "collection" not provided + */ + public function __construct(Client $mongo, array $options) + { + if (!isset($options['database']) || !isset($options['collection'])) { + throw new \InvalidArgumentException('You must provide the "database" and "collection" option for MongoDBSessionHandler.'); + } + + $this->mongo = $mongo; + + $this->options = array_merge([ + 'id_field' => '_id', + 'data_field' => 'data', + 'time_field' => 'time', + 'expiry_field' => 'expires_at', + ], $options); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function close() + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function doDestroy(string $sessionId) + { + $this->getCollection()->deleteOne([ + $this->options['id_field'] => $sessionId, + ]); + + return true; + } + + /** + * @return int|false + */ + #[\ReturnTypeWillChange] + public function gc($maxlifetime) + { + return $this->getCollection()->deleteMany([ + $this->options['expiry_field'] => ['$lt' => new UTCDateTime()], + ])->getDeletedCount(); + } + + /** + * {@inheritdoc} + */ + protected function doWrite(string $sessionId, string $data) + { + $expiry = new UTCDateTime((time() + (int) \ini_get('session.gc_maxlifetime')) * 1000); + + $fields = [ + $this->options['time_field'] => new UTCDateTime(), + $this->options['expiry_field'] => $expiry, + $this->options['data_field'] => new Binary($data, Binary::TYPE_OLD_BINARY), + ]; + + $this->getCollection()->updateOne( + [$this->options['id_field'] => $sessionId], + ['$set' => $fields], + ['upsert' => true] + ); + + return true; + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function updateTimestamp($sessionId, $data) + { + $expiry = new UTCDateTime((time() + (int) \ini_get('session.gc_maxlifetime')) * 1000); + + $this->getCollection()->updateOne( + [$this->options['id_field'] => $sessionId], + ['$set' => [ + $this->options['time_field'] => new UTCDateTime(), + $this->options['expiry_field'] => $expiry, + ]] + ); + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doRead(string $sessionId) + { + $dbData = $this->getCollection()->findOne([ + $this->options['id_field'] => $sessionId, + $this->options['expiry_field'] => ['$gte' => new UTCDateTime()], + ]); + + if (null === $dbData) { + return ''; + } + + return $dbData[$this->options['data_field']]->getData(); + } + + private function getCollection(): Collection + { + if (null === $this->collection) { + $this->collection = $this->mongo->selectCollection($this->options['database'], $this->options['collection']); + } + + return $this->collection; + } + + /** + * @return Client + */ + protected function getMongo() + { + return $this->mongo; + } +} diff --git a/src/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php b/src/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php new file mode 100644 index 0000000..52a1038 --- /dev/null +++ b/src/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * Native session handler using PHP's built in file storage. + * + * @author Drak + */ +class NativeFileSessionHandler extends \SessionHandler +{ + /** + * @param string|null $savePath Path of directory to save session files + * Default null will leave setting as defined by PHP. + * '/path', 'N;/path', or 'N;octal-mode;/path + * + * @see https://php.net/session.configuration#ini.session.save-path for further details. + * + * @throws \InvalidArgumentException On invalid $savePath + * @throws \RuntimeException When failing to create the save directory + */ + public function __construct(string $savePath = null) + { + if (null === $savePath) { + $savePath = \ini_get('session.save_path'); + } + + $baseDir = $savePath; + + if ($count = substr_count($savePath, ';')) { + if ($count > 2) { + throw new \InvalidArgumentException(sprintf('Invalid argument $savePath \'%s\'.', $savePath)); + } + + // characters after last ';' are the path + $baseDir = ltrim(strrchr($savePath, ';'), ';'); + } + + if ($baseDir && !is_dir($baseDir) && !@mkdir($baseDir, 0777, true) && !is_dir($baseDir)) { + throw new \RuntimeException(sprintf('Session Storage was not able to create directory "%s".', $baseDir)); + } + + if ($savePath !== \ini_get('session.save_path')) { + ini_set('session.save_path', $savePath); + } + if ('files' !== \ini_get('session.save_handler')) { + ini_set('session.save_handler', 'files'); + } + } +} diff --git a/src/vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php b/src/vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php new file mode 100644 index 0000000..4331dbe --- /dev/null +++ b/src/vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * Can be used in unit testing or in a situations where persisted sessions are not desired. + * + * @author Drak + */ +class NullSessionHandler extends AbstractSessionHandler +{ + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function close() + { + return true; + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function validateId($sessionId) + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function doRead(string $sessionId) + { + return ''; + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function updateTimestamp($sessionId, $data) + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function doWrite(string $sessionId, string $data) + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function doDestroy(string $sessionId) + { + return true; + } + + /** + * @return int|false + */ + #[\ReturnTypeWillChange] + public function gc($maxlifetime) + { + return 0; + } +} diff --git a/src/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php b/src/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php new file mode 100644 index 0000000..cad7e0a --- /dev/null +++ b/src/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php @@ -0,0 +1,943 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * Session handler using a PDO connection to read and write data. + * + * It works with MySQL, PostgreSQL, Oracle, SQL Server and SQLite and implements + * different locking strategies to handle concurrent access to the same session. + * Locking is necessary to prevent loss of data due to race conditions and to keep + * the session data consistent between read() and write(). With locking, requests + * for the same session will wait until the other one finished writing. For this + * reason it's best practice to close a session as early as possible to improve + * concurrency. PHPs internal files session handler also implements locking. + * + * Attention: Since SQLite does not support row level locks but locks the whole database, + * it means only one session can be accessed at a time. Even different sessions would wait + * for another to finish. So saving session in SQLite should only be considered for + * development or prototypes. + * + * Session data is a binary string that can contain non-printable characters like the null byte. + * For this reason it must be saved in a binary column in the database like BLOB in MySQL. + * Saving it in a character column could corrupt the data. You can use createTable() + * to initialize a correctly defined table. + * + * @see https://php.net/sessionhandlerinterface + * + * @author Fabien Potencier + * @author Michael Williams + * @author Tobias Schultze + */ +class PdoSessionHandler extends AbstractSessionHandler +{ + /** + * No locking is done. This means sessions are prone to loss of data due to + * race conditions of concurrent requests to the same session. The last session + * write will win in this case. It might be useful when you implement your own + * logic to deal with this like an optimistic approach. + */ + public const LOCK_NONE = 0; + + /** + * Creates an application-level lock on a session. The disadvantage is that the + * lock is not enforced by the database and thus other, unaware parts of the + * application could still concurrently modify the session. The advantage is it + * does not require a transaction. + * This mode is not available for SQLite and not yet implemented for oci and sqlsrv. + */ + public const LOCK_ADVISORY = 1; + + /** + * Issues a real row lock. Since it uses a transaction between opening and + * closing a session, you have to be careful when you use same database connection + * that you also use for your application logic. This mode is the default because + * it's the only reliable solution across DBMSs. + */ + public const LOCK_TRANSACTIONAL = 2; + + private const MAX_LIFETIME = 315576000; + + /** + * @var \PDO|null PDO instance or null when not connected yet + */ + private $pdo; + + /** + * DSN string or null for session.save_path or false when lazy connection disabled. + * + * @var string|false|null + */ + private $dsn = false; + + /** + * @var string|null + */ + private $driver; + + /** + * @var string + */ + private $table = 'sessions'; + + /** + * @var string + */ + private $idCol = 'sess_id'; + + /** + * @var string + */ + private $dataCol = 'sess_data'; + + /** + * @var string + */ + private $lifetimeCol = 'sess_lifetime'; + + /** + * @var string + */ + private $timeCol = 'sess_time'; + + /** + * Username when lazy-connect. + * + * @var string + */ + private $username = ''; + + /** + * Password when lazy-connect. + * + * @var string + */ + private $password = ''; + + /** + * Connection options when lazy-connect. + * + * @var array + */ + private $connectionOptions = []; + + /** + * The strategy for locking, see constants. + * + * @var int + */ + private $lockMode = self::LOCK_TRANSACTIONAL; + + /** + * It's an array to support multiple reads before closing which is manual, non-standard usage. + * + * @var \PDOStatement[] An array of statements to release advisory locks + */ + private $unlockStatements = []; + + /** + * True when the current session exists but expired according to session.gc_maxlifetime. + * + * @var bool + */ + private $sessionExpired = false; + + /** + * Whether a transaction is active. + * + * @var bool + */ + private $inTransaction = false; + + /** + * Whether gc() has been called. + * + * @var bool + */ + private $gcCalled = false; + + /** + * You can either pass an existing database connection as PDO instance or + * pass a DSN string that will be used to lazy-connect to the database + * when the session is actually used. Furthermore it's possible to pass null + * which will then use the session.save_path ini setting as PDO DSN parameter. + * + * List of available options: + * * db_table: The name of the table [default: sessions] + * * db_id_col: The column where to store the session id [default: sess_id] + * * db_data_col: The column where to store the session data [default: sess_data] + * * db_lifetime_col: The column where to store the lifetime [default: sess_lifetime] + * * db_time_col: The column where to store the timestamp [default: sess_time] + * * db_username: The username when lazy-connect [default: ''] + * * db_password: The password when lazy-connect [default: ''] + * * db_connection_options: An array of driver-specific connection options [default: []] + * * lock_mode: The strategy for locking, see constants [default: LOCK_TRANSACTIONAL] + * + * @param \PDO|string|null $pdoOrDsn A \PDO instance or DSN string or URL string or null + * + * @throws \InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION + */ + public function __construct($pdoOrDsn = null, array $options = []) + { + if ($pdoOrDsn instanceof \PDO) { + if (\PDO::ERRMODE_EXCEPTION !== $pdoOrDsn->getAttribute(\PDO::ATTR_ERRMODE)) { + throw new \InvalidArgumentException(sprintf('"%s" requires PDO error mode attribute be set to throw Exceptions (i.e. $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)).', __CLASS__)); + } + + $this->pdo = $pdoOrDsn; + $this->driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME); + } elseif (\is_string($pdoOrDsn) && str_contains($pdoOrDsn, '://')) { + $this->dsn = $this->buildDsnFromUrl($pdoOrDsn); + } else { + $this->dsn = $pdoOrDsn; + } + + $this->table = $options['db_table'] ?? $this->table; + $this->idCol = $options['db_id_col'] ?? $this->idCol; + $this->dataCol = $options['db_data_col'] ?? $this->dataCol; + $this->lifetimeCol = $options['db_lifetime_col'] ?? $this->lifetimeCol; + $this->timeCol = $options['db_time_col'] ?? $this->timeCol; + $this->username = $options['db_username'] ?? $this->username; + $this->password = $options['db_password'] ?? $this->password; + $this->connectionOptions = $options['db_connection_options'] ?? $this->connectionOptions; + $this->lockMode = $options['lock_mode'] ?? $this->lockMode; + } + + /** + * Creates the table to store sessions which can be called once for setup. + * + * Session ID is saved in a column of maximum length 128 because that is enough even + * for a 512 bit configured session.hash_function like Whirlpool. Session data is + * saved in a BLOB. One could also use a shorter inlined varbinary column + * if one was sure the data fits into it. + * + * @throws \PDOException When the table already exists + * @throws \DomainException When an unsupported PDO driver is used + */ + public function createTable() + { + // connect if we are not yet + $this->getConnection(); + + switch ($this->driver) { + case 'mysql': + // We use varbinary for the ID column because it prevents unwanted conversions: + // - character set conversions between server and client + // - trailing space removal + // - case-insensitivity + // - language processing like é == e + $sql = "CREATE TABLE $this->table ($this->idCol VARBINARY(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER UNSIGNED NOT NULL, $this->timeCol INTEGER UNSIGNED NOT NULL) COLLATE utf8mb4_bin, ENGINE = InnoDB"; + break; + case 'sqlite': + $sql = "CREATE TABLE $this->table ($this->idCol TEXT NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)"; + break; + case 'pgsql': + $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(128) NOT NULL PRIMARY KEY, $this->dataCol BYTEA NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)"; + break; + case 'oci': + $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR2(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)"; + break; + case 'sqlsrv': + $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(128) NOT NULL PRIMARY KEY, $this->dataCol VARBINARY(MAX) NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)"; + break; + default: + throw new \DomainException(sprintf('Creating the session table is currently not implemented for PDO driver "%s".', $this->driver)); + } + + try { + $this->pdo->exec($sql); + $this->pdo->exec("CREATE INDEX EXPIRY ON $this->table ($this->lifetimeCol)"); + } catch (\PDOException $e) { + $this->rollback(); + + throw $e; + } + } + + /** + * Returns true when the current session exists but expired according to session.gc_maxlifetime. + * + * Can be used to distinguish between a new session and one that expired due to inactivity. + * + * @return bool + */ + public function isSessionExpired() + { + return $this->sessionExpired; + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function open($savePath, $sessionName) + { + $this->sessionExpired = false; + + if (null === $this->pdo) { + $this->connect($this->dsn ?: $savePath); + } + + return parent::open($savePath, $sessionName); + } + + /** + * @return string + */ + #[\ReturnTypeWillChange] + public function read($sessionId) + { + try { + return parent::read($sessionId); + } catch (\PDOException $e) { + $this->rollback(); + + throw $e; + } + } + + /** + * @return int|false + */ + #[\ReturnTypeWillChange] + public function gc($maxlifetime) + { + // We delay gc() to close() so that it is executed outside the transactional and blocking read-write process. + // This way, pruning expired sessions does not block them from being started while the current session is used. + $this->gcCalled = true; + + return 0; + } + + /** + * {@inheritdoc} + */ + protected function doDestroy(string $sessionId) + { + // delete the record associated with this id + $sql = "DELETE FROM $this->table WHERE $this->idCol = :id"; + + try { + $stmt = $this->pdo->prepare($sql); + $stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $stmt->execute(); + } catch (\PDOException $e) { + $this->rollback(); + + throw $e; + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doWrite(string $sessionId, string $data) + { + $maxlifetime = (int) \ini_get('session.gc_maxlifetime'); + + try { + // We use a single MERGE SQL query when supported by the database. + $mergeStmt = $this->getMergeStatement($sessionId, $data, $maxlifetime); + if (null !== $mergeStmt) { + $mergeStmt->execute(); + + return true; + } + + $updateStmt = $this->getUpdateStatement($sessionId, $data, $maxlifetime); + $updateStmt->execute(); + + // When MERGE is not supported, like in Postgres < 9.5, we have to use this approach that can result in + // duplicate key errors when the same session is written simultaneously (given the LOCK_NONE behavior). + // We can just catch such an error and re-execute the update. This is similar to a serializable + // transaction with retry logic on serialization failures but without the overhead and without possible + // false positives due to longer gap locking. + if (!$updateStmt->rowCount()) { + try { + $insertStmt = $this->getInsertStatement($sessionId, $data, $maxlifetime); + $insertStmt->execute(); + } catch (\PDOException $e) { + // Handle integrity violation SQLSTATE 23000 (or a subclass like 23505 in Postgres) for duplicate keys + if (str_starts_with($e->getCode(), '23')) { + $updateStmt->execute(); + } else { + throw $e; + } + } + } + } catch (\PDOException $e) { + $this->rollback(); + + throw $e; + } + + return true; + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function updateTimestamp($sessionId, $data) + { + $expiry = time() + (int) \ini_get('session.gc_maxlifetime'); + + try { + $updateStmt = $this->pdo->prepare( + "UPDATE $this->table SET $this->lifetimeCol = :expiry, $this->timeCol = :time WHERE $this->idCol = :id" + ); + $updateStmt->bindValue(':id', $sessionId, \PDO::PARAM_STR); + $updateStmt->bindValue(':expiry', $expiry, \PDO::PARAM_INT); + $updateStmt->bindValue(':time', time(), \PDO::PARAM_INT); + $updateStmt->execute(); + } catch (\PDOException $e) { + $this->rollback(); + + throw $e; + } + + return true; + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function close() + { + $this->commit(); + + while ($unlockStmt = array_shift($this->unlockStatements)) { + $unlockStmt->execute(); + } + + if ($this->gcCalled) { + $this->gcCalled = false; + + // delete the session records that have expired + $sql = "DELETE FROM $this->table WHERE $this->lifetimeCol < :time AND $this->lifetimeCol > :min"; + $stmt = $this->pdo->prepare($sql); + $stmt->bindValue(':time', time(), \PDO::PARAM_INT); + $stmt->bindValue(':min', self::MAX_LIFETIME, \PDO::PARAM_INT); + $stmt->execute(); + // to be removed in 6.0 + if ('mysql' === $this->driver) { + $legacySql = "DELETE FROM $this->table WHERE $this->lifetimeCol <= :min AND $this->lifetimeCol + $this->timeCol < :time"; + } else { + $legacySql = "DELETE FROM $this->table WHERE $this->lifetimeCol <= :min AND $this->lifetimeCol < :time - $this->timeCol"; + } + + $stmt = $this->pdo->prepare($legacySql); + $stmt->bindValue(':time', time(), \PDO::PARAM_INT); + $stmt->bindValue(':min', self::MAX_LIFETIME, \PDO::PARAM_INT); + $stmt->execute(); + } + + if (false !== $this->dsn) { + $this->pdo = null; // only close lazy-connection + $this->driver = null; + } + + return true; + } + + /** + * Lazy-connects to the database. + */ + private function connect(string $dsn): void + { + $this->pdo = new \PDO($dsn, $this->username, $this->password, $this->connectionOptions); + $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + $this->driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME); + } + + /** + * Builds a PDO DSN from a URL-like connection string. + * + * @todo implement missing support for oci DSN (which look totally different from other PDO ones) + */ + private function buildDsnFromUrl(string $dsnOrUrl): string + { + // (pdo_)?sqlite3?:///... => (pdo_)?sqlite3?://localhost/... or else the URL will be invalid + $url = preg_replace('#^((?:pdo_)?sqlite3?):///#', '$1://localhost/', $dsnOrUrl); + + $params = parse_url($url); + + if (false === $params) { + return $dsnOrUrl; // If the URL is not valid, let's assume it might be a DSN already. + } + + $params = array_map('rawurldecode', $params); + + // Override the default username and password. Values passed through options will still win over these in the constructor. + if (isset($params['user'])) { + $this->username = $params['user']; + } + + if (isset($params['pass'])) { + $this->password = $params['pass']; + } + + if (!isset($params['scheme'])) { + throw new \InvalidArgumentException('URLs without scheme are not supported to configure the PdoSessionHandler.'); + } + + $driverAliasMap = [ + 'mssql' => 'sqlsrv', + 'mysql2' => 'mysql', // Amazon RDS, for some weird reason + 'postgres' => 'pgsql', + 'postgresql' => 'pgsql', + 'sqlite3' => 'sqlite', + ]; + + $driver = $driverAliasMap[$params['scheme']] ?? $params['scheme']; + + // Doctrine DBAL supports passing its internal pdo_* driver names directly too (allowing both dashes and underscores). This allows supporting the same here. + if (str_starts_with($driver, 'pdo_') || str_starts_with($driver, 'pdo-')) { + $driver = substr($driver, 4); + } + + $dsn = null; + switch ($driver) { + case 'mysql': + $dsn = 'mysql:'; + if ('' !== ($params['query'] ?? '')) { + $queryParams = []; + parse_str($params['query'], $queryParams); + if ('' !== ($queryParams['charset'] ?? '')) { + $dsn .= 'charset='.$queryParams['charset'].';'; + } + + if ('' !== ($queryParams['unix_socket'] ?? '')) { + $dsn .= 'unix_socket='.$queryParams['unix_socket'].';'; + + if (isset($params['path'])) { + $dbName = substr($params['path'], 1); // Remove the leading slash + $dsn .= 'dbname='.$dbName.';'; + } + + return $dsn; + } + } + // If "unix_socket" is not in the query, we continue with the same process as pgsql + // no break + case 'pgsql': + $dsn ?? $dsn = 'pgsql:'; + + if (isset($params['host']) && '' !== $params['host']) { + $dsn .= 'host='.$params['host'].';'; + } + + if (isset($params['port']) && '' !== $params['port']) { + $dsn .= 'port='.$params['port'].';'; + } + + if (isset($params['path'])) { + $dbName = substr($params['path'], 1); // Remove the leading slash + $dsn .= 'dbname='.$dbName.';'; + } + + return $dsn; + + case 'sqlite': + return 'sqlite:'.substr($params['path'], 1); + + case 'sqlsrv': + $dsn = 'sqlsrv:server='; + + if (isset($params['host'])) { + $dsn .= $params['host']; + } + + if (isset($params['port']) && '' !== $params['port']) { + $dsn .= ','.$params['port']; + } + + if (isset($params['path'])) { + $dbName = substr($params['path'], 1); // Remove the leading slash + $dsn .= ';Database='.$dbName; + } + + return $dsn; + + default: + throw new \InvalidArgumentException(sprintf('The scheme "%s" is not supported by the PdoSessionHandler URL configuration. Pass a PDO DSN directly.', $params['scheme'])); + } + } + + /** + * Helper method to begin a transaction. + * + * Since SQLite does not support row level locks, we have to acquire a reserved lock + * on the database immediately. Because of https://bugs.php.net/42766 we have to create + * such a transaction manually which also means we cannot use PDO::commit or + * PDO::rollback or PDO::inTransaction for SQLite. + * + * Also MySQLs default isolation, REPEATABLE READ, causes deadlock for different sessions + * due to https://percona.com/blog/2013/12/12/one-more-innodb-gap-lock-to-avoid/ . + * So we change it to READ COMMITTED. + */ + private function beginTransaction(): void + { + if (!$this->inTransaction) { + if ('sqlite' === $this->driver) { + $this->pdo->exec('BEGIN IMMEDIATE TRANSACTION'); + } else { + if ('mysql' === $this->driver) { + $this->pdo->exec('SET TRANSACTION ISOLATION LEVEL READ COMMITTED'); + } + $this->pdo->beginTransaction(); + } + $this->inTransaction = true; + } + } + + /** + * Helper method to commit a transaction. + */ + private function commit(): void + { + if ($this->inTransaction) { + try { + // commit read-write transaction which also releases the lock + if ('sqlite' === $this->driver) { + $this->pdo->exec('COMMIT'); + } else { + $this->pdo->commit(); + } + $this->inTransaction = false; + } catch (\PDOException $e) { + $this->rollback(); + + throw $e; + } + } + } + + /** + * Helper method to rollback a transaction. + */ + private function rollback(): void + { + // We only need to rollback if we are in a transaction. Otherwise the resulting + // error would hide the real problem why rollback was called. We might not be + // in a transaction when not using the transactional locking behavior or when + // two callbacks (e.g. destroy and write) are invoked that both fail. + if ($this->inTransaction) { + if ('sqlite' === $this->driver) { + $this->pdo->exec('ROLLBACK'); + } else { + $this->pdo->rollBack(); + } + $this->inTransaction = false; + } + } + + /** + * Reads the session data in respect to the different locking strategies. + * + * We need to make sure we do not return session data that is already considered garbage according + * to the session.gc_maxlifetime setting because gc() is called after read() and only sometimes. + * + * @return string + */ + protected function doRead(string $sessionId) + { + if (self::LOCK_ADVISORY === $this->lockMode) { + $this->unlockStatements[] = $this->doAdvisoryLock($sessionId); + } + + $selectSql = $this->getSelectSql(); + $selectStmt = $this->pdo->prepare($selectSql); + $selectStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $insertStmt = null; + + while (true) { + $selectStmt->execute(); + $sessionRows = $selectStmt->fetchAll(\PDO::FETCH_NUM); + + if ($sessionRows) { + $expiry = (int) $sessionRows[0][1]; + if ($expiry <= self::MAX_LIFETIME) { + $expiry += $sessionRows[0][2]; + } + + if ($expiry < time()) { + $this->sessionExpired = true; + + return ''; + } + + return \is_resource($sessionRows[0][0]) ? stream_get_contents($sessionRows[0][0]) : $sessionRows[0][0]; + } + + if (null !== $insertStmt) { + $this->rollback(); + throw new \RuntimeException('Failed to read session: INSERT reported a duplicate id but next SELECT did not return any data.'); + } + + if (!filter_var(\ini_get('session.use_strict_mode'), \FILTER_VALIDATE_BOOLEAN) && self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) { + // In strict mode, session fixation is not possible: new sessions always start with a unique + // random id, so that concurrency is not possible and this code path can be skipped. + // Exclusive-reading of non-existent rows does not block, so we need to do an insert to block + // until other connections to the session are committed. + try { + $insertStmt = $this->getInsertStatement($sessionId, '', 0); + $insertStmt->execute(); + } catch (\PDOException $e) { + // Catch duplicate key error because other connection created the session already. + // It would only not be the case when the other connection destroyed the session. + if (str_starts_with($e->getCode(), '23')) { + // Retrieve finished session data written by concurrent connection by restarting the loop. + // We have to start a new transaction as a failed query will mark the current transaction as + // aborted in PostgreSQL and disallow further queries within it. + $this->rollback(); + $this->beginTransaction(); + continue; + } + + throw $e; + } + } + + return ''; + } + } + + /** + * Executes an application-level lock on the database. + * + * @return \PDOStatement The statement that needs to be executed later to release the lock + * + * @throws \DomainException When an unsupported PDO driver is used + * + * @todo implement missing advisory locks + * - for oci using DBMS_LOCK.REQUEST + * - for sqlsrv using sp_getapplock with LockOwner = Session + */ + private function doAdvisoryLock(string $sessionId): \PDOStatement + { + switch ($this->driver) { + case 'mysql': + // MySQL 5.7.5 and later enforces a maximum length on lock names of 64 characters. Previously, no limit was enforced. + $lockId = substr($sessionId, 0, 64); + // should we handle the return value? 0 on timeout, null on error + // we use a timeout of 50 seconds which is also the default for innodb_lock_wait_timeout + $stmt = $this->pdo->prepare('SELECT GET_LOCK(:key, 50)'); + $stmt->bindValue(':key', $lockId, \PDO::PARAM_STR); + $stmt->execute(); + + $releaseStmt = $this->pdo->prepare('DO RELEASE_LOCK(:key)'); + $releaseStmt->bindValue(':key', $lockId, \PDO::PARAM_STR); + + return $releaseStmt; + case 'pgsql': + // Obtaining an exclusive session level advisory lock requires an integer key. + // When session.sid_bits_per_character > 4, the session id can contain non-hex-characters. + // So we cannot just use hexdec(). + if (4 === \PHP_INT_SIZE) { + $sessionInt1 = $this->convertStringToInt($sessionId); + $sessionInt2 = $this->convertStringToInt(substr($sessionId, 4, 4)); + + $stmt = $this->pdo->prepare('SELECT pg_advisory_lock(:key1, :key2)'); + $stmt->bindValue(':key1', $sessionInt1, \PDO::PARAM_INT); + $stmt->bindValue(':key2', $sessionInt2, \PDO::PARAM_INT); + $stmt->execute(); + + $releaseStmt = $this->pdo->prepare('SELECT pg_advisory_unlock(:key1, :key2)'); + $releaseStmt->bindValue(':key1', $sessionInt1, \PDO::PARAM_INT); + $releaseStmt->bindValue(':key2', $sessionInt2, \PDO::PARAM_INT); + } else { + $sessionBigInt = $this->convertStringToInt($sessionId); + + $stmt = $this->pdo->prepare('SELECT pg_advisory_lock(:key)'); + $stmt->bindValue(':key', $sessionBigInt, \PDO::PARAM_INT); + $stmt->execute(); + + $releaseStmt = $this->pdo->prepare('SELECT pg_advisory_unlock(:key)'); + $releaseStmt->bindValue(':key', $sessionBigInt, \PDO::PARAM_INT); + } + + return $releaseStmt; + case 'sqlite': + throw new \DomainException('SQLite does not support advisory locks.'); + default: + throw new \DomainException(sprintf('Advisory locks are currently not implemented for PDO driver "%s".', $this->driver)); + } + } + + /** + * Encodes the first 4 (when PHP_INT_SIZE == 4) or 8 characters of the string as an integer. + * + * Keep in mind, PHP integers are signed. + */ + private function convertStringToInt(string $string): int + { + if (4 === \PHP_INT_SIZE) { + return (\ord($string[3]) << 24) + (\ord($string[2]) << 16) + (\ord($string[1]) << 8) + \ord($string[0]); + } + + $int1 = (\ord($string[7]) << 24) + (\ord($string[6]) << 16) + (\ord($string[5]) << 8) + \ord($string[4]); + $int2 = (\ord($string[3]) << 24) + (\ord($string[2]) << 16) + (\ord($string[1]) << 8) + \ord($string[0]); + + return $int2 + ($int1 << 32); + } + + /** + * Return a locking or nonlocking SQL query to read session information. + * + * @throws \DomainException When an unsupported PDO driver is used + */ + private function getSelectSql(): string + { + if (self::LOCK_TRANSACTIONAL === $this->lockMode) { + $this->beginTransaction(); + + // selecting the time column should be removed in 6.0 + switch ($this->driver) { + case 'mysql': + case 'oci': + case 'pgsql': + return "SELECT $this->dataCol, $this->lifetimeCol, $this->timeCol FROM $this->table WHERE $this->idCol = :id FOR UPDATE"; + case 'sqlsrv': + return "SELECT $this->dataCol, $this->lifetimeCol, $this->timeCol FROM $this->table WITH (UPDLOCK, ROWLOCK) WHERE $this->idCol = :id"; + case 'sqlite': + // we already locked when starting transaction + break; + default: + throw new \DomainException(sprintf('Transactional locks are currently not implemented for PDO driver "%s".', $this->driver)); + } + } + + return "SELECT $this->dataCol, $this->lifetimeCol, $this->timeCol FROM $this->table WHERE $this->idCol = :id"; + } + + /** + * Returns an insert statement supported by the database for writing session data. + */ + private function getInsertStatement(string $sessionId, string $sessionData, int $maxlifetime): \PDOStatement + { + switch ($this->driver) { + case 'oci': + $data = fopen('php://memory', 'r+'); + fwrite($data, $sessionData); + rewind($data); + $sql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, EMPTY_BLOB(), :expiry, :time) RETURNING $this->dataCol into :data"; + break; + default: + $data = $sessionData; + $sql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :expiry, :time)"; + break; + } + + $stmt = $this->pdo->prepare($sql); + $stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $stmt->bindParam(':data', $data, \PDO::PARAM_LOB); + $stmt->bindValue(':expiry', time() + $maxlifetime, \PDO::PARAM_INT); + $stmt->bindValue(':time', time(), \PDO::PARAM_INT); + + return $stmt; + } + + /** + * Returns an update statement supported by the database for writing session data. + */ + private function getUpdateStatement(string $sessionId, string $sessionData, int $maxlifetime): \PDOStatement + { + switch ($this->driver) { + case 'oci': + $data = fopen('php://memory', 'r+'); + fwrite($data, $sessionData); + rewind($data); + $sql = "UPDATE $this->table SET $this->dataCol = EMPTY_BLOB(), $this->lifetimeCol = :expiry, $this->timeCol = :time WHERE $this->idCol = :id RETURNING $this->dataCol into :data"; + break; + default: + $data = $sessionData; + $sql = "UPDATE $this->table SET $this->dataCol = :data, $this->lifetimeCol = :expiry, $this->timeCol = :time WHERE $this->idCol = :id"; + break; + } + + $stmt = $this->pdo->prepare($sql); + $stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $stmt->bindParam(':data', $data, \PDO::PARAM_LOB); + $stmt->bindValue(':expiry', time() + $maxlifetime, \PDO::PARAM_INT); + $stmt->bindValue(':time', time(), \PDO::PARAM_INT); + + return $stmt; + } + + /** + * Returns a merge/upsert (i.e. insert or update) statement when supported by the database for writing session data. + */ + private function getMergeStatement(string $sessionId, string $data, int $maxlifetime): ?\PDOStatement + { + switch (true) { + case 'mysql' === $this->driver: + $mergeSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :expiry, :time) ". + "ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->lifetimeCol = VALUES($this->lifetimeCol), $this->timeCol = VALUES($this->timeCol)"; + break; + case 'sqlsrv' === $this->driver && version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '10', '>='): + // MERGE is only available since SQL Server 2008 and must be terminated by semicolon + // It also requires HOLDLOCK according to https://weblogs.sqlteam.com/dang/2009/01/31/upsert-race-condition-with-merge/ + $mergeSql = "MERGE INTO $this->table WITH (HOLDLOCK) USING (SELECT 1 AS dummy) AS src ON ($this->idCol = ?) ". + "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ". + "WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?;"; + break; + case 'sqlite' === $this->driver: + $mergeSql = "INSERT OR REPLACE INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :expiry, :time)"; + break; + case 'pgsql' === $this->driver && version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '9.5', '>='): + $mergeSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :expiry, :time) ". + "ON CONFLICT ($this->idCol) DO UPDATE SET ($this->dataCol, $this->lifetimeCol, $this->timeCol) = (EXCLUDED.$this->dataCol, EXCLUDED.$this->lifetimeCol, EXCLUDED.$this->timeCol)"; + break; + default: + // MERGE is not supported with LOBs: https://oracle.com/technetwork/articles/fuecks-lobs-095315.html + return null; + } + + $mergeStmt = $this->pdo->prepare($mergeSql); + + if ('sqlsrv' === $this->driver) { + $mergeStmt->bindParam(1, $sessionId, \PDO::PARAM_STR); + $mergeStmt->bindParam(2, $sessionId, \PDO::PARAM_STR); + $mergeStmt->bindParam(3, $data, \PDO::PARAM_LOB); + $mergeStmt->bindValue(4, time() + $maxlifetime, \PDO::PARAM_INT); + $mergeStmt->bindValue(5, time(), \PDO::PARAM_INT); + $mergeStmt->bindParam(6, $data, \PDO::PARAM_LOB); + $mergeStmt->bindValue(7, time() + $maxlifetime, \PDO::PARAM_INT); + $mergeStmt->bindValue(8, time(), \PDO::PARAM_INT); + } else { + $mergeStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $mergeStmt->bindParam(':data', $data, \PDO::PARAM_LOB); + $mergeStmt->bindValue(':expiry', time() + $maxlifetime, \PDO::PARAM_INT); + $mergeStmt->bindValue(':time', time(), \PDO::PARAM_INT); + } + + return $mergeStmt; + } + + /** + * Return a PDO instance. + * + * @return \PDO + */ + protected function getConnection() + { + if (null === $this->pdo) { + $this->connect($this->dsn ?: \ini_get('session.save_path')); + } + + return $this->pdo; + } +} diff --git a/src/vendor/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php b/src/vendor/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php new file mode 100644 index 0000000..31954e6 --- /dev/null +++ b/src/vendor/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php @@ -0,0 +1,137 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +use Predis\Response\ErrorInterface; +use Symfony\Component\Cache\Traits\RedisClusterProxy; +use Symfony\Component\Cache\Traits\RedisProxy; + +/** + * Redis based session storage handler based on the Redis class + * provided by the PHP redis extension. + * + * @author Dalibor Karlović + */ +class RedisSessionHandler extends AbstractSessionHandler +{ + private $redis; + + /** + * @var string Key prefix for shared environments + */ + private $prefix; + + /** + * @var int Time to live in seconds + */ + private $ttl; + + /** + * List of available options: + * * prefix: The prefix to use for the keys in order to avoid collision on the Redis server + * * ttl: The time to live in seconds. + * + * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|RedisProxy|RedisClusterProxy $redis + * + * @throws \InvalidArgumentException When unsupported client or options are passed + */ + public function __construct($redis, array $options = []) + { + if ( + !$redis instanceof \Redis && + !$redis instanceof \RedisArray && + !$redis instanceof \RedisCluster && + !$redis instanceof \Predis\ClientInterface && + !$redis instanceof RedisProxy && + !$redis instanceof RedisClusterProxy + ) { + throw new \InvalidArgumentException(sprintf('"%s()" expects parameter 1 to be Redis, RedisArray, RedisCluster or Predis\ClientInterface, "%s" given.', __METHOD__, get_debug_type($redis))); + } + + if ($diff = array_diff(array_keys($options), ['prefix', 'ttl'])) { + throw new \InvalidArgumentException(sprintf('The following options are not supported "%s".', implode(', ', $diff))); + } + + $this->redis = $redis; + $this->prefix = $options['prefix'] ?? 'sf_s'; + $this->ttl = $options['ttl'] ?? null; + } + + /** + * {@inheritdoc} + */ + protected function doRead(string $sessionId): string + { + return $this->redis->get($this->prefix.$sessionId) ?: ''; + } + + /** + * {@inheritdoc} + */ + protected function doWrite(string $sessionId, string $data): bool + { + $result = $this->redis->setEx($this->prefix.$sessionId, (int) ($this->ttl ?? \ini_get('session.gc_maxlifetime')), $data); + + return $result && !$result instanceof ErrorInterface; + } + + /** + * {@inheritdoc} + */ + protected function doDestroy(string $sessionId): bool + { + static $unlink = true; + + if ($unlink) { + try { + $unlink = false !== $this->redis->unlink($this->prefix.$sessionId); + } catch (\Throwable $e) { + $unlink = false; + } + } + + if (!$unlink) { + $this->redis->del($this->prefix.$sessionId); + } + + return true; + } + + /** + * {@inheritdoc} + */ + #[\ReturnTypeWillChange] + public function close(): bool + { + return true; + } + + /** + * {@inheritdoc} + * + * @return int|false + */ + #[\ReturnTypeWillChange] + public function gc($maxlifetime) + { + return 0; + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function updateTimestamp($sessionId, $data) + { + return (bool) $this->redis->expire($this->prefix.$sessionId, (int) ($this->ttl ?? \ini_get('session.gc_maxlifetime'))); + } +} diff --git a/src/vendor/symfony/http-foundation/Session/Storage/Handler/SessionHandlerFactory.php b/src/vendor/symfony/http-foundation/Session/Storage/Handler/SessionHandlerFactory.php new file mode 100644 index 0000000..39dc30c --- /dev/null +++ b/src/vendor/symfony/http-foundation/Session/Storage/Handler/SessionHandlerFactory.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +use Doctrine\DBAL\Configuration; +use Doctrine\DBAL\DriverManager; +use Doctrine\DBAL\Schema\DefaultSchemaManagerFactory; +use Doctrine\DBAL\Tools\DsnParser; +use Symfony\Component\Cache\Adapter\AbstractAdapter; +use Symfony\Component\Cache\Traits\RedisClusterProxy; +use Symfony\Component\Cache\Traits\RedisProxy; + +/** + * @author Nicolas Grekas + */ +class SessionHandlerFactory +{ + /** + * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|RedisProxy|RedisClusterProxy|\Memcached|\PDO|string $connection Connection or DSN + */ + public static function createHandler($connection): AbstractSessionHandler + { + if (!\is_string($connection) && !\is_object($connection)) { + throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be a string or a connection object, "%s" given.', __METHOD__, get_debug_type($connection))); + } + + if ($options = \is_string($connection) ? parse_url($connection) : false) { + parse_str($options['query'] ?? '', $options); + } + + switch (true) { + case $connection instanceof \Redis: + case $connection instanceof \RedisArray: + case $connection instanceof \RedisCluster: + case $connection instanceof \Predis\ClientInterface: + case $connection instanceof RedisProxy: + case $connection instanceof RedisClusterProxy: + return new RedisSessionHandler($connection); + + case $connection instanceof \Memcached: + return new MemcachedSessionHandler($connection); + + case $connection instanceof \PDO: + return new PdoSessionHandler($connection); + + case !\is_string($connection): + throw new \InvalidArgumentException(sprintf('Unsupported Connection: "%s".', get_debug_type($connection))); + case str_starts_with($connection, 'file://'): + $savePath = substr($connection, 7); + + return new StrictSessionHandler(new NativeFileSessionHandler('' === $savePath ? null : $savePath)); + + case str_starts_with($connection, 'redis:'): + case str_starts_with($connection, 'rediss:'): + case str_starts_with($connection, 'memcached:'): + if (!class_exists(AbstractAdapter::class)) { + throw new \InvalidArgumentException(sprintf('Unsupported DSN "%s". Try running "composer require symfony/cache".', $connection)); + } + $handlerClass = str_starts_with($connection, 'memcached:') ? MemcachedSessionHandler::class : RedisSessionHandler::class; + $connection = AbstractAdapter::createConnection($connection, ['lazy' => true]); + + return new $handlerClass($connection, array_intersect_key($options ?: [], ['prefix' => 1, 'ttl' => 1])); + + case str_starts_with($connection, 'pdo_oci://'): + if (!class_exists(DriverManager::class)) { + throw new \InvalidArgumentException(sprintf('Unsupported DSN "%s". Try running "composer require doctrine/dbal".', $connection)); + } + $connection[3] = '-'; + $params = class_exists(DsnParser::class) ? (new DsnParser())->parse($connection) : ['url' => $connection]; + $config = new Configuration(); + if (class_exists(DefaultSchemaManagerFactory::class)) { + $config->setSchemaManagerFactory(new DefaultSchemaManagerFactory()); + } + + $connection = DriverManager::getConnection($params, $config); + $connection = method_exists($connection, 'getNativeConnection') ? $connection->getNativeConnection() : $connection->getWrappedConnection(); + // no break; + + case str_starts_with($connection, 'mssql://'): + case str_starts_with($connection, 'mysql://'): + case str_starts_with($connection, 'mysql2://'): + case str_starts_with($connection, 'pgsql://'): + case str_starts_with($connection, 'postgres://'): + case str_starts_with($connection, 'postgresql://'): + case str_starts_with($connection, 'sqlsrv://'): + case str_starts_with($connection, 'sqlite://'): + case str_starts_with($connection, 'sqlite3://'): + return new PdoSessionHandler($connection, $options ?: []); + } + + throw new \InvalidArgumentException(sprintf('Unsupported Connection: "%s".', $connection)); + } +} diff --git a/src/vendor/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php b/src/vendor/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php new file mode 100644 index 0000000..f7c385f --- /dev/null +++ b/src/vendor/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * Adds basic `SessionUpdateTimestampHandlerInterface` behaviors to another `SessionHandlerInterface`. + * + * @author Nicolas Grekas + */ +class StrictSessionHandler extends AbstractSessionHandler +{ + private $handler; + private $doDestroy; + + public function __construct(\SessionHandlerInterface $handler) + { + if ($handler instanceof \SessionUpdateTimestampHandlerInterface) { + throw new \LogicException(sprintf('"%s" is already an instance of "SessionUpdateTimestampHandlerInterface", you cannot wrap it with "%s".', get_debug_type($handler), self::class)); + } + + $this->handler = $handler; + } + + /** + * Returns true if this handler wraps an internal PHP session save handler using \SessionHandler. + * + * @internal + */ + public function isWrapper(): bool + { + return $this->handler instanceof \SessionHandler; + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function open($savePath, $sessionName) + { + parent::open($savePath, $sessionName); + + return $this->handler->open($savePath, $sessionName); + } + + /** + * {@inheritdoc} + */ + protected function doRead(string $sessionId) + { + return $this->handler->read($sessionId); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function updateTimestamp($sessionId, $data) + { + return $this->write($sessionId, $data); + } + + /** + * {@inheritdoc} + */ + protected function doWrite(string $sessionId, string $data) + { + return $this->handler->write($sessionId, $data); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function destroy($sessionId) + { + $this->doDestroy = true; + $destroyed = parent::destroy($sessionId); + + return $this->doDestroy ? $this->doDestroy($sessionId) : $destroyed; + } + + /** + * {@inheritdoc} + */ + protected function doDestroy(string $sessionId) + { + $this->doDestroy = false; + + return $this->handler->destroy($sessionId); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function close() + { + return $this->handler->close(); + } + + /** + * @return int|false + */ + #[\ReturnTypeWillChange] + public function gc($maxlifetime) + { + return $this->handler->gc($maxlifetime); + } +} diff --git a/src/vendor/symfony/http-foundation/Session/Storage/MetadataBag.php b/src/vendor/symfony/http-foundation/Session/Storage/MetadataBag.php new file mode 100644 index 0000000..52d3320 --- /dev/null +++ b/src/vendor/symfony/http-foundation/Session/Storage/MetadataBag.php @@ -0,0 +1,167 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * Metadata container. + * + * Adds metadata to the session. + * + * @author Drak + */ +class MetadataBag implements SessionBagInterface +{ + public const CREATED = 'c'; + public const UPDATED = 'u'; + public const LIFETIME = 'l'; + + /** + * @var string + */ + private $name = '__metadata'; + + /** + * @var string + */ + private $storageKey; + + /** + * @var array + */ + protected $meta = [self::CREATED => 0, self::UPDATED => 0, self::LIFETIME => 0]; + + /** + * Unix timestamp. + * + * @var int + */ + private $lastUsed; + + /** + * @var int + */ + private $updateThreshold; + + /** + * @param string $storageKey The key used to store bag in the session + * @param int $updateThreshold The time to wait between two UPDATED updates + */ + public function __construct(string $storageKey = '_sf2_meta', int $updateThreshold = 0) + { + $this->storageKey = $storageKey; + $this->updateThreshold = $updateThreshold; + } + + /** + * {@inheritdoc} + */ + public function initialize(array &$array) + { + $this->meta = &$array; + + if (isset($array[self::CREATED])) { + $this->lastUsed = $this->meta[self::UPDATED]; + + $timeStamp = time(); + if ($timeStamp - $array[self::UPDATED] >= $this->updateThreshold) { + $this->meta[self::UPDATED] = $timeStamp; + } + } else { + $this->stampCreated(); + } + } + + /** + * Gets the lifetime that the session cookie was set with. + * + * @return int + */ + public function getLifetime() + { + return $this->meta[self::LIFETIME]; + } + + /** + * Stamps a new session's metadata. + * + * @param int|null $lifetime Sets the cookie lifetime for the session cookie. A null value + * will leave the system settings unchanged, 0 sets the cookie + * to expire with browser session. Time is in seconds, and is + * not a Unix timestamp. + */ + public function stampNew(int $lifetime = null) + { + $this->stampCreated($lifetime); + } + + /** + * {@inheritdoc} + */ + public function getStorageKey() + { + return $this->storageKey; + } + + /** + * Gets the created timestamp metadata. + * + * @return int Unix timestamp + */ + public function getCreated() + { + return $this->meta[self::CREATED]; + } + + /** + * Gets the last used metadata. + * + * @return int Unix timestamp + */ + public function getLastUsed() + { + return $this->lastUsed; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + // nothing to do + return null; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + /** + * Sets name. + */ + public function setName(string $name) + { + $this->name = $name; + } + + private function stampCreated(int $lifetime = null): void + { + $timeStamp = time(); + $this->meta[self::CREATED] = $this->meta[self::UPDATED] = $this->lastUsed = $timeStamp; + $this->meta[self::LIFETIME] = $lifetime ?? (int) \ini_get('session.cookie_lifetime'); + } +} diff --git a/src/vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php b/src/vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php new file mode 100644 index 0000000..c5c2bb0 --- /dev/null +++ b/src/vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php @@ -0,0 +1,252 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * MockArraySessionStorage mocks the session for unit tests. + * + * No PHP session is actually started since a session can be initialized + * and shutdown only once per PHP execution cycle. + * + * When doing functional testing, you should use MockFileSessionStorage instead. + * + * @author Fabien Potencier + * @author Bulat Shakirzyanov + * @author Drak + */ +class MockArraySessionStorage implements SessionStorageInterface +{ + /** + * @var string + */ + protected $id = ''; + + /** + * @var string + */ + protected $name; + + /** + * @var bool + */ + protected $started = false; + + /** + * @var bool + */ + protected $closed = false; + + /** + * @var array + */ + protected $data = []; + + /** + * @var MetadataBag + */ + protected $metadataBag; + + /** + * @var array|SessionBagInterface[] + */ + protected $bags = []; + + public function __construct(string $name = 'MOCKSESSID', MetadataBag $metaBag = null) + { + $this->name = $name; + $this->setMetadataBag($metaBag); + } + + public function setSessionData(array $array) + { + $this->data = $array; + } + + /** + * {@inheritdoc} + */ + public function start() + { + if ($this->started) { + return true; + } + + if (empty($this->id)) { + $this->id = $this->generateId(); + } + + $this->loadSession(); + + return true; + } + + /** + * {@inheritdoc} + */ + public function regenerate(bool $destroy = false, int $lifetime = null) + { + if (!$this->started) { + $this->start(); + } + + $this->metadataBag->stampNew($lifetime); + $this->id = $this->generateId(); + + return true; + } + + /** + * {@inheritdoc} + */ + public function getId() + { + return $this->id; + } + + /** + * {@inheritdoc} + */ + public function setId(string $id) + { + if ($this->started) { + throw new \LogicException('Cannot set session ID after the session has started.'); + } + + $this->id = $id; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function setName(string $name) + { + $this->name = $name; + } + + /** + * {@inheritdoc} + */ + public function save() + { + if (!$this->started || $this->closed) { + throw new \RuntimeException('Trying to save a session that was not started yet or was already closed.'); + } + // nothing to do since we don't persist the session data + $this->closed = false; + $this->started = false; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + // clear out the bags + foreach ($this->bags as $bag) { + $bag->clear(); + } + + // clear out the session + $this->data = []; + + // reconnect the bags to the session + $this->loadSession(); + } + + /** + * {@inheritdoc} + */ + public function registerBag(SessionBagInterface $bag) + { + $this->bags[$bag->getName()] = $bag; + } + + /** + * {@inheritdoc} + */ + public function getBag(string $name) + { + if (!isset($this->bags[$name])) { + throw new \InvalidArgumentException(sprintf('The SessionBagInterface "%s" is not registered.', $name)); + } + + if (!$this->started) { + $this->start(); + } + + return $this->bags[$name]; + } + + /** + * {@inheritdoc} + */ + public function isStarted() + { + return $this->started; + } + + public function setMetadataBag(MetadataBag $bag = null) + { + if (null === $bag) { + $bag = new MetadataBag(); + } + + $this->metadataBag = $bag; + } + + /** + * Gets the MetadataBag. + * + * @return MetadataBag + */ + public function getMetadataBag() + { + return $this->metadataBag; + } + + /** + * Generates a session ID. + * + * This doesn't need to be particularly cryptographically secure since this is just + * a mock. + * + * @return string + */ + protected function generateId() + { + return hash('sha256', uniqid('ss_mock_', true)); + } + + protected function loadSession() + { + $bags = array_merge($this->bags, [$this->metadataBag]); + + foreach ($bags as $bag) { + $key = $bag->getStorageKey(); + $this->data[$key] = $this->data[$key] ?? []; + $bag->initialize($this->data[$key]); + } + + $this->started = true; + $this->closed = false; + } +} diff --git a/src/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php b/src/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php new file mode 100644 index 0000000..8e32a45 --- /dev/null +++ b/src/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php @@ -0,0 +1,160 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +/** + * MockFileSessionStorage is used to mock sessions for + * functional testing where you may need to persist session data + * across separate PHP processes. + * + * No PHP session is actually started since a session can be initialized + * and shutdown only once per PHP execution cycle and this class does + * not pollute any session related globals, including session_*() functions + * or session.* PHP ini directives. + * + * @author Drak + */ +class MockFileSessionStorage extends MockArraySessionStorage +{ + private $savePath; + + /** + * @param string|null $savePath Path of directory to save session files + */ + public function __construct(string $savePath = null, string $name = 'MOCKSESSID', MetadataBag $metaBag = null) + { + if (null === $savePath) { + $savePath = sys_get_temp_dir(); + } + + if (!is_dir($savePath) && !@mkdir($savePath, 0777, true) && !is_dir($savePath)) { + throw new \RuntimeException(sprintf('Session Storage was not able to create directory "%s".', $savePath)); + } + + $this->savePath = $savePath; + + parent::__construct($name, $metaBag); + } + + /** + * {@inheritdoc} + */ + public function start() + { + if ($this->started) { + return true; + } + + if (!$this->id) { + $this->id = $this->generateId(); + } + + $this->read(); + + $this->started = true; + + return true; + } + + /** + * {@inheritdoc} + */ + public function regenerate(bool $destroy = false, int $lifetime = null) + { + if (!$this->started) { + $this->start(); + } + + if ($destroy) { + $this->destroy(); + } + + return parent::regenerate($destroy, $lifetime); + } + + /** + * {@inheritdoc} + */ + public function save() + { + if (!$this->started) { + throw new \RuntimeException('Trying to save a session that was not started yet or was already closed.'); + } + + $data = $this->data; + + foreach ($this->bags as $bag) { + if (empty($data[$key = $bag->getStorageKey()])) { + unset($data[$key]); + } + } + if ([$key = $this->metadataBag->getStorageKey()] === array_keys($data)) { + unset($data[$key]); + } + + try { + if ($data) { + $path = $this->getFilePath(); + $tmp = $path.bin2hex(random_bytes(6)); + file_put_contents($tmp, serialize($data)); + rename($tmp, $path); + } else { + $this->destroy(); + } + } finally { + $this->data = $data; + } + + // this is needed when the session object is re-used across multiple requests + // in functional tests. + $this->started = false; + } + + /** + * Deletes a session from persistent storage. + * Deliberately leaves session data in memory intact. + */ + private function destroy(): void + { + set_error_handler(static function () {}); + try { + unlink($this->getFilePath()); + } finally { + restore_error_handler(); + } + } + + /** + * Calculate path to file. + */ + private function getFilePath(): string + { + return $this->savePath.'/'.$this->id.'.mocksess'; + } + + /** + * Reads session from storage and loads session. + */ + private function read(): void + { + set_error_handler(static function () {}); + try { + $data = file_get_contents($this->getFilePath()); + } finally { + restore_error_handler(); + } + + $this->data = $data ? unserialize($data) : []; + + $this->loadSession(); + } +} diff --git a/src/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorageFactory.php b/src/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorageFactory.php new file mode 100644 index 0000000..d0da1e1 --- /dev/null +++ b/src/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorageFactory.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Request; + +// Help opcache.preload discover always-needed symbols +class_exists(MockFileSessionStorage::class); + +/** + * @author Jérémy Derussé + */ +class MockFileSessionStorageFactory implements SessionStorageFactoryInterface +{ + private $savePath; + private $name; + private $metaBag; + + /** + * @see MockFileSessionStorage constructor. + */ + public function __construct(string $savePath = null, string $name = 'MOCKSESSID', MetadataBag $metaBag = null) + { + $this->savePath = $savePath; + $this->name = $name; + $this->metaBag = $metaBag; + } + + public function createStorage(?Request $request): SessionStorageInterface + { + return new MockFileSessionStorage($this->savePath, $this->name, $this->metaBag); + } +} diff --git a/src/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php b/src/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php new file mode 100644 index 0000000..242478c --- /dev/null +++ b/src/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php @@ -0,0 +1,507 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; +use Symfony\Component\HttpFoundation\Session\SessionUtils; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler; +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy; +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy; + +// Help opcache.preload discover always-needed symbols +class_exists(MetadataBag::class); +class_exists(StrictSessionHandler::class); +class_exists(SessionHandlerProxy::class); + +/** + * This provides a base class for session attribute storage. + * + * @author Drak + */ +class NativeSessionStorage implements SessionStorageInterface +{ + /** + * @var SessionBagInterface[] + */ + protected $bags = []; + + /** + * @var bool + */ + protected $started = false; + + /** + * @var bool + */ + protected $closed = false; + + /** + * @var AbstractProxy|\SessionHandlerInterface + */ + protected $saveHandler; + + /** + * @var MetadataBag + */ + protected $metadataBag; + + /** + * @var string|null + */ + private $emulateSameSite; + + /** + * Depending on how you want the storage driver to behave you probably + * want to override this constructor entirely. + * + * List of options for $options array with their defaults. + * + * @see https://php.net/session.configuration for options + * but we omit 'session.' from the beginning of the keys for convenience. + * + * ("auto_start", is not supported as it tells PHP to start a session before + * PHP starts to execute user-land code. Setting during runtime has no effect). + * + * cache_limiter, "" (use "0" to prevent headers from being sent entirely). + * cache_expire, "0" + * cookie_domain, "" + * cookie_httponly, "" + * cookie_lifetime, "0" + * cookie_path, "/" + * cookie_secure, "" + * cookie_samesite, null + * gc_divisor, "100" + * gc_maxlifetime, "1440" + * gc_probability, "1" + * lazy_write, "1" + * name, "PHPSESSID" + * referer_check, "" + * serialize_handler, "php" + * use_strict_mode, "1" + * use_cookies, "1" + * use_only_cookies, "1" + * use_trans_sid, "0" + * sid_length, "32" + * sid_bits_per_character, "5" + * trans_sid_hosts, $_SERVER['HTTP_HOST'] + * trans_sid_tags, "a=href,area=href,frame=src,form=" + * + * @param AbstractProxy|\SessionHandlerInterface|null $handler + */ + public function __construct(array $options = [], $handler = null, MetadataBag $metaBag = null) + { + if (!\extension_loaded('session')) { + throw new \LogicException('PHP extension "session" is required.'); + } + + $options += [ + 'cache_limiter' => '', + 'cache_expire' => 0, + 'use_cookies' => 1, + 'lazy_write' => 1, + 'use_strict_mode' => 1, + ]; + + session_register_shutdown(); + + $this->setMetadataBag($metaBag); + $this->setOptions($options); + $this->setSaveHandler($handler); + } + + /** + * Gets the save handler instance. + * + * @return AbstractProxy|\SessionHandlerInterface + */ + public function getSaveHandler() + { + return $this->saveHandler; + } + + /** + * {@inheritdoc} + */ + public function start() + { + if ($this->started) { + return true; + } + + if (\PHP_SESSION_ACTIVE === session_status()) { + throw new \RuntimeException('Failed to start the session: already started by PHP.'); + } + + if (filter_var(\ini_get('session.use_cookies'), \FILTER_VALIDATE_BOOLEAN) && headers_sent($file, $line)) { + throw new \RuntimeException(sprintf('Failed to start the session because headers have already been sent by "%s" at line %d.', $file, $line)); + } + + $sessionId = $_COOKIE[session_name()] ?? null; + /* + * Explanation of the session ID regular expression: `/^[a-zA-Z0-9,-]{22,250}$/`. + * + * ---------- Part 1 + * + * The part `[a-zA-Z0-9,-]` is related to the PHP ini directive `session.sid_bits_per_character` defined as 6. + * See https://www.php.net/manual/en/session.configuration.php#ini.session.sid-bits-per-character. + * Allowed values are integers such as: + * - 4 for range `a-f0-9` + * - 5 for range `a-v0-9` + * - 6 for range `a-zA-Z0-9,-` + * + * ---------- Part 2 + * + * The part `{22,250}` is related to the PHP ini directive `session.sid_length`. + * See https://www.php.net/manual/en/session.configuration.php#ini.session.sid-length. + * Allowed values are integers between 22 and 256, but we use 250 for the max. + * + * Where does the 250 come from? + * - The length of Windows and Linux filenames is limited to 255 bytes. Then the max must not exceed 255. + * - The session filename prefix is `sess_`, a 5 bytes string. Then the max must not exceed 255 - 5 = 250. + * + * ---------- Conclusion + * + * The parts 1 and 2 prevent the warning below: + * `PHP Warning: SessionHandler::read(): Session ID is too long or contains illegal characters. Only the A-Z, a-z, 0-9, "-", and "," characters are allowed.` + * + * The part 2 prevents the warning below: + * `PHP Warning: SessionHandler::read(): open(filepath, O_RDWR) failed: No such file or directory (2).` + */ + if ($sessionId && $this->saveHandler instanceof AbstractProxy && 'files' === $this->saveHandler->getSaveHandlerName() && !preg_match('/^[a-zA-Z0-9,-]{22,250}$/', $sessionId)) { + // the session ID in the header is invalid, create a new one + session_id(session_create_id()); + } + + // ok to try and start the session + if (!session_start()) { + throw new \RuntimeException('Failed to start the session.'); + } + + if (null !== $this->emulateSameSite) { + $originalCookie = SessionUtils::popSessionCookie(session_name(), session_id()); + if (null !== $originalCookie) { + header(sprintf('%s; SameSite=%s', $originalCookie, $this->emulateSameSite), false); + } + } + + $this->loadSession(); + + return true; + } + + /** + * {@inheritdoc} + */ + public function getId() + { + return $this->saveHandler->getId(); + } + + /** + * {@inheritdoc} + */ + public function setId(string $id) + { + $this->saveHandler->setId($id); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->saveHandler->getName(); + } + + /** + * {@inheritdoc} + */ + public function setName(string $name) + { + $this->saveHandler->setName($name); + } + + /** + * {@inheritdoc} + */ + public function regenerate(bool $destroy = false, int $lifetime = null) + { + // Cannot regenerate the session ID for non-active sessions. + if (\PHP_SESSION_ACTIVE !== session_status()) { + return false; + } + + if (headers_sent()) { + return false; + } + + if (null !== $lifetime && $lifetime != \ini_get('session.cookie_lifetime')) { + $this->save(); + ini_set('session.cookie_lifetime', $lifetime); + $this->start(); + } + + if ($destroy) { + $this->metadataBag->stampNew(); + } + + $isRegenerated = session_regenerate_id($destroy); + + if (null !== $this->emulateSameSite) { + $originalCookie = SessionUtils::popSessionCookie(session_name(), session_id()); + if (null !== $originalCookie) { + header(sprintf('%s; SameSite=%s', $originalCookie, $this->emulateSameSite), false); + } + } + + return $isRegenerated; + } + + /** + * {@inheritdoc} + */ + public function save() + { + // Store a copy so we can restore the bags in case the session was not left empty + $session = $_SESSION; + + foreach ($this->bags as $bag) { + if (empty($_SESSION[$key = $bag->getStorageKey()])) { + unset($_SESSION[$key]); + } + } + if ($_SESSION && [$key = $this->metadataBag->getStorageKey()] === array_keys($_SESSION)) { + unset($_SESSION[$key]); + } + + // Register error handler to add information about the current save handler + $previousHandler = set_error_handler(function ($type, $msg, $file, $line) use (&$previousHandler) { + if (\E_WARNING === $type && str_starts_with($msg, 'session_write_close():')) { + $handler = $this->saveHandler instanceof SessionHandlerProxy ? $this->saveHandler->getHandler() : $this->saveHandler; + $msg = sprintf('session_write_close(): Failed to write session data with "%s" handler', \get_class($handler)); + } + + return $previousHandler ? $previousHandler($type, $msg, $file, $line) : false; + }); + + try { + session_write_close(); + } finally { + restore_error_handler(); + + // Restore only if not empty + if ($_SESSION) { + $_SESSION = $session; + } + } + + $this->closed = true; + $this->started = false; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + // clear out the bags + foreach ($this->bags as $bag) { + $bag->clear(); + } + + // clear out the session + $_SESSION = []; + + // reconnect the bags to the session + $this->loadSession(); + } + + /** + * {@inheritdoc} + */ + public function registerBag(SessionBagInterface $bag) + { + if ($this->started) { + throw new \LogicException('Cannot register a bag when the session is already started.'); + } + + $this->bags[$bag->getName()] = $bag; + } + + /** + * {@inheritdoc} + */ + public function getBag(string $name) + { + if (!isset($this->bags[$name])) { + throw new \InvalidArgumentException(sprintf('The SessionBagInterface "%s" is not registered.', $name)); + } + + if (!$this->started && $this->saveHandler->isActive()) { + $this->loadSession(); + } elseif (!$this->started) { + $this->start(); + } + + return $this->bags[$name]; + } + + public function setMetadataBag(MetadataBag $metaBag = null) + { + if (null === $metaBag) { + $metaBag = new MetadataBag(); + } + + $this->metadataBag = $metaBag; + } + + /** + * Gets the MetadataBag. + * + * @return MetadataBag + */ + public function getMetadataBag() + { + return $this->metadataBag; + } + + /** + * {@inheritdoc} + */ + public function isStarted() + { + return $this->started; + } + + /** + * Sets session.* ini variables. + * + * For convenience we omit 'session.' from the beginning of the keys. + * Explicitly ignores other ini keys. + * + * @param array $options Session ini directives [key => value] + * + * @see https://php.net/session.configuration + */ + public function setOptions(array $options) + { + if (headers_sent() || \PHP_SESSION_ACTIVE === session_status()) { + return; + } + + $validOptions = array_flip([ + 'cache_expire', 'cache_limiter', 'cookie_domain', 'cookie_httponly', + 'cookie_lifetime', 'cookie_path', 'cookie_secure', 'cookie_samesite', + 'gc_divisor', 'gc_maxlifetime', 'gc_probability', + 'lazy_write', 'name', 'referer_check', + 'serialize_handler', 'use_strict_mode', 'use_cookies', + 'use_only_cookies', 'use_trans_sid', 'upload_progress.enabled', + 'upload_progress.cleanup', 'upload_progress.prefix', 'upload_progress.name', + 'upload_progress.freq', 'upload_progress.min_freq', 'url_rewriter.tags', + 'sid_length', 'sid_bits_per_character', 'trans_sid_hosts', 'trans_sid_tags', + ]); + + foreach ($options as $key => $value) { + if (isset($validOptions[$key])) { + if (str_starts_with($key, 'upload_progress.')) { + trigger_deprecation('symfony/http-foundation', '5.4', 'Support for the "%s" session option is deprecated. The settings prefixed with "session.upload_progress." can not be changed at runtime.', $key); + continue; + } + if ('url_rewriter.tags' === $key) { + trigger_deprecation('symfony/http-foundation', '5.4', 'Support for the "%s" session option is deprecated. Use "trans_sid_tags" instead.', $key); + } + if ('cookie_samesite' === $key && \PHP_VERSION_ID < 70300) { + // PHP < 7.3 does not support same_site cookies. We will emulate it in + // the start() method instead. + $this->emulateSameSite = $value; + continue; + } + if ('cookie_secure' === $key && 'auto' === $value) { + continue; + } + ini_set('url_rewriter.tags' !== $key ? 'session.'.$key : $key, $value); + } + } + } + + /** + * Registers session save handler as a PHP session handler. + * + * To use internal PHP session save handlers, override this method using ini_set with + * session.save_handler and session.save_path e.g. + * + * ini_set('session.save_handler', 'files'); + * ini_set('session.save_path', '/tmp'); + * + * or pass in a \SessionHandler instance which configures session.save_handler in the + * constructor, for a template see NativeFileSessionHandler. + * + * @see https://php.net/session-set-save-handler + * @see https://php.net/sessionhandlerinterface + * @see https://php.net/sessionhandler + * + * @param AbstractProxy|\SessionHandlerInterface|null $saveHandler + * + * @throws \InvalidArgumentException + */ + public function setSaveHandler($saveHandler = null) + { + if (!$saveHandler instanceof AbstractProxy + && !$saveHandler instanceof \SessionHandlerInterface + && null !== $saveHandler + ) { + throw new \InvalidArgumentException('Must be instance of AbstractProxy; implement \SessionHandlerInterface; or be null.'); + } + + // Wrap $saveHandler in proxy and prevent double wrapping of proxy + if (!$saveHandler instanceof AbstractProxy && $saveHandler instanceof \SessionHandlerInterface) { + $saveHandler = new SessionHandlerProxy($saveHandler); + } elseif (!$saveHandler instanceof AbstractProxy) { + $saveHandler = new SessionHandlerProxy(new StrictSessionHandler(new \SessionHandler())); + } + $this->saveHandler = $saveHandler; + + if (headers_sent() || \PHP_SESSION_ACTIVE === session_status()) { + return; + } + + if ($this->saveHandler instanceof SessionHandlerProxy) { + session_set_save_handler($this->saveHandler, false); + } + } + + /** + * Load the session with attributes. + * + * After starting the session, PHP retrieves the session from whatever handlers + * are set to (either PHP's internal, or a custom save handler set with session_set_save_handler()). + * PHP takes the return value from the read() handler, unserializes it + * and populates $_SESSION with the result automatically. + */ + protected function loadSession(array &$session = null) + { + if (null === $session) { + $session = &$_SESSION; + } + + $bags = array_merge($this->bags, [$this->metadataBag]); + + foreach ($bags as $bag) { + $key = $bag->getStorageKey(); + $session[$key] = isset($session[$key]) && \is_array($session[$key]) ? $session[$key] : []; + $bag->initialize($session[$key]); + } + + $this->started = true; + $this->closed = false; + } +} diff --git a/src/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorageFactory.php b/src/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorageFactory.php new file mode 100644 index 0000000..a7d7411 --- /dev/null +++ b/src/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorageFactory.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Request; + +// Help opcache.preload discover always-needed symbols +class_exists(NativeSessionStorage::class); + +/** + * @author Jérémy Derussé + */ +class NativeSessionStorageFactory implements SessionStorageFactoryInterface +{ + private $options; + private $handler; + private $metaBag; + private $secure; + + /** + * @see NativeSessionStorage constructor. + */ + public function __construct(array $options = [], $handler = null, MetadataBag $metaBag = null, bool $secure = false) + { + $this->options = $options; + $this->handler = $handler; + $this->metaBag = $metaBag; + $this->secure = $secure; + } + + public function createStorage(?Request $request): SessionStorageInterface + { + $storage = new NativeSessionStorage($this->options, $this->handler, $this->metaBag); + if ($this->secure && $request && $request->isSecure()) { + $storage->setOptions(['cookie_secure' => true]); + } + + return $storage; + } +} diff --git a/src/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php b/src/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php new file mode 100644 index 0000000..72dbef1 --- /dev/null +++ b/src/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy; + +/** + * Allows session to be started by PHP and managed by Symfony. + * + * @author Drak + */ +class PhpBridgeSessionStorage extends NativeSessionStorage +{ + /** + * @param AbstractProxy|\SessionHandlerInterface|null $handler + */ + public function __construct($handler = null, MetadataBag $metaBag = null) + { + if (!\extension_loaded('session')) { + throw new \LogicException('PHP extension "session" is required.'); + } + + $this->setMetadataBag($metaBag); + $this->setSaveHandler($handler); + } + + /** + * {@inheritdoc} + */ + public function start() + { + if ($this->started) { + return true; + } + + $this->loadSession(); + + return true; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + // clear out the bags and nothing else that may be set + // since the purpose of this driver is to share a handler + foreach ($this->bags as $bag) { + $bag->clear(); + } + + // reconnect the bags to the session + $this->loadSession(); + } +} diff --git a/src/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorageFactory.php b/src/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorageFactory.php new file mode 100644 index 0000000..173ef71 --- /dev/null +++ b/src/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorageFactory.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Request; + +// Help opcache.preload discover always-needed symbols +class_exists(PhpBridgeSessionStorage::class); + +/** + * @author Jérémy Derussé + */ +class PhpBridgeSessionStorageFactory implements SessionStorageFactoryInterface +{ + private $handler; + private $metaBag; + private $secure; + + /** + * @see PhpBridgeSessionStorage constructor. + */ + public function __construct($handler = null, MetadataBag $metaBag = null, bool $secure = false) + { + $this->handler = $handler; + $this->metaBag = $metaBag; + $this->secure = $secure; + } + + public function createStorage(?Request $request): SessionStorageInterface + { + $storage = new PhpBridgeSessionStorage($this->handler, $this->metaBag); + if ($this->secure && $request && $request->isSecure()) { + $storage->setOptions(['cookie_secure' => true]); + } + + return $storage; + } +} diff --git a/src/vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php b/src/vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php new file mode 100644 index 0000000..edd04df --- /dev/null +++ b/src/vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy; + +/** + * @author Drak + */ +abstract class AbstractProxy +{ + /** + * Flag if handler wraps an internal PHP session handler (using \SessionHandler). + * + * @var bool + */ + protected $wrapper = false; + + /** + * @var string + */ + protected $saveHandlerName; + + /** + * Gets the session.save_handler name. + * + * @return string|null + */ + public function getSaveHandlerName() + { + return $this->saveHandlerName; + } + + /** + * Is this proxy handler and instance of \SessionHandlerInterface. + * + * @return bool + */ + public function isSessionHandlerInterface() + { + return $this instanceof \SessionHandlerInterface; + } + + /** + * Returns true if this handler wraps an internal PHP session save handler using \SessionHandler. + * + * @return bool + */ + public function isWrapper() + { + return $this->wrapper; + } + + /** + * Has a session started? + * + * @return bool + */ + public function isActive() + { + return \PHP_SESSION_ACTIVE === session_status(); + } + + /** + * Gets the session ID. + * + * @return string + */ + public function getId() + { + return session_id(); + } + + /** + * Sets the session ID. + * + * @throws \LogicException + */ + public function setId(string $id) + { + if ($this->isActive()) { + throw new \LogicException('Cannot change the ID of an active session.'); + } + + session_id($id); + } + + /** + * Gets the session name. + * + * @return string + */ + public function getName() + { + return session_name(); + } + + /** + * Sets the session name. + * + * @throws \LogicException + */ + public function setName(string $name) + { + if ($this->isActive()) { + throw new \LogicException('Cannot change the name of an active session.'); + } + + session_name($name); + } +} diff --git a/src/vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php b/src/vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php new file mode 100644 index 0000000..0defa4a --- /dev/null +++ b/src/vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy; + +use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler; + +/** + * @author Drak + */ +class SessionHandlerProxy extends AbstractProxy implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface +{ + protected $handler; + + public function __construct(\SessionHandlerInterface $handler) + { + $this->handler = $handler; + $this->wrapper = $handler instanceof \SessionHandler; + $this->saveHandlerName = $this->wrapper || ($handler instanceof StrictSessionHandler && $handler->isWrapper()) ? \ini_get('session.save_handler') : 'user'; + } + + /** + * @return \SessionHandlerInterface + */ + public function getHandler() + { + return $this->handler; + } + + // \SessionHandlerInterface + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function open($savePath, $sessionName) + { + return $this->handler->open($savePath, $sessionName); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function close() + { + return $this->handler->close(); + } + + /** + * @return string|false + */ + #[\ReturnTypeWillChange] + public function read($sessionId) + { + return $this->handler->read($sessionId); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function write($sessionId, $data) + { + return $this->handler->write($sessionId, $data); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function destroy($sessionId) + { + return $this->handler->destroy($sessionId); + } + + /** + * @return int|false + */ + #[\ReturnTypeWillChange] + public function gc($maxlifetime) + { + return $this->handler->gc($maxlifetime); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function validateId($sessionId) + { + return !$this->handler instanceof \SessionUpdateTimestampHandlerInterface || $this->handler->validateId($sessionId); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function updateTimestamp($sessionId, $data) + { + return $this->handler instanceof \SessionUpdateTimestampHandlerInterface ? $this->handler->updateTimestamp($sessionId, $data) : $this->write($sessionId, $data); + } +} diff --git a/src/vendor/symfony/http-foundation/Session/Storage/ServiceSessionFactory.php b/src/vendor/symfony/http-foundation/Session/Storage/ServiceSessionFactory.php new file mode 100644 index 0000000..d17c60a --- /dev/null +++ b/src/vendor/symfony/http-foundation/Session/Storage/ServiceSessionFactory.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Request; + +/** + * @author Jérémy Derussé + * + * @internal to be removed in Symfony 6 + */ +final class ServiceSessionFactory implements SessionStorageFactoryInterface +{ + private $storage; + + public function __construct(SessionStorageInterface $storage) + { + $this->storage = $storage; + } + + public function createStorage(?Request $request): SessionStorageInterface + { + if ($this->storage instanceof NativeSessionStorage && $request && $request->isSecure()) { + $this->storage->setOptions(['cookie_secure' => true]); + } + + return $this->storage; + } +} diff --git a/src/vendor/symfony/http-foundation/Session/Storage/SessionStorageFactoryInterface.php b/src/vendor/symfony/http-foundation/Session/Storage/SessionStorageFactoryInterface.php new file mode 100644 index 0000000..d03f0da --- /dev/null +++ b/src/vendor/symfony/http-foundation/Session/Storage/SessionStorageFactoryInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Request; + +/** + * @author Jérémy Derussé + */ +interface SessionStorageFactoryInterface +{ + /** + * Creates a new instance of SessionStorageInterface. + */ + public function createStorage(?Request $request): SessionStorageInterface; +} diff --git a/src/vendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php b/src/vendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php new file mode 100644 index 0000000..7053745 --- /dev/null +++ b/src/vendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php @@ -0,0 +1,131 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * StorageInterface. + * + * @author Fabien Potencier + * @author Drak + */ +interface SessionStorageInterface +{ + /** + * Starts the session. + * + * @return bool + * + * @throws \RuntimeException if something goes wrong starting the session + */ + public function start(); + + /** + * Checks if the session is started. + * + * @return bool + */ + public function isStarted(); + + /** + * Returns the session ID. + * + * @return string + */ + public function getId(); + + /** + * Sets the session ID. + */ + public function setId(string $id); + + /** + * Returns the session name. + * + * @return string + */ + public function getName(); + + /** + * Sets the session name. + */ + public function setName(string $name); + + /** + * Regenerates id that represents this storage. + * + * This method must invoke session_regenerate_id($destroy) unless + * this interface is used for a storage object designed for unit + * or functional testing where a real PHP session would interfere + * with testing. + * + * Note regenerate+destroy should not clear the session data in memory + * only delete the session data from persistent storage. + * + * Care: When regenerating the session ID no locking is involved in PHP's + * session design. See https://bugs.php.net/61470 for a discussion. + * So you must make sure the regenerated session is saved BEFORE sending the + * headers with the new ID. Symfony's HttpKernel offers a listener for this. + * See Symfony\Component\HttpKernel\EventListener\SaveSessionListener. + * Otherwise session data could get lost again for concurrent requests with the + * new ID. One result could be that you get logged out after just logging in. + * + * @param bool $destroy Destroy session when regenerating? + * @param int|null $lifetime Sets the cookie lifetime for the session cookie. A null value + * will leave the system settings unchanged, 0 sets the cookie + * to expire with browser session. Time is in seconds, and is + * not a Unix timestamp. + * + * @return bool + * + * @throws \RuntimeException If an error occurs while regenerating this storage + */ + public function regenerate(bool $destroy = false, int $lifetime = null); + + /** + * Force the session to be saved and closed. + * + * This method must invoke session_write_close() unless this interface is + * used for a storage object design for unit or functional testing where + * a real PHP session would interfere with testing, in which case + * it should actually persist the session data if required. + * + * @throws \RuntimeException if the session is saved without being started, or if the session + * is already closed + */ + public function save(); + + /** + * Clear all session data in memory. + */ + public function clear(); + + /** + * Gets a SessionBagInterface by name. + * + * @return SessionBagInterface + * + * @throws \InvalidArgumentException If the bag does not exist + */ + public function getBag(string $name); + + /** + * Registers a SessionBagInterface for use. + */ + public function registerBag(SessionBagInterface $bag); + + /** + * @return MetadataBag + */ + public function getMetadataBag(); +} diff --git a/src/vendor/symfony/http-foundation/StreamedResponse.php b/src/vendor/symfony/http-foundation/StreamedResponse.php new file mode 100644 index 0000000..0599bd1 --- /dev/null +++ b/src/vendor/symfony/http-foundation/StreamedResponse.php @@ -0,0 +1,139 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * StreamedResponse represents a streamed HTTP response. + * + * A StreamedResponse uses a callback for its content. + * + * The callback should use the standard PHP functions like echo + * to stream the response back to the client. The flush() function + * can also be used if needed. + * + * @see flush() + * + * @author Fabien Potencier + */ +class StreamedResponse extends Response +{ + protected $callback; + protected $streamed; + private $headersSent; + + public function __construct(callable $callback = null, int $status = 200, array $headers = []) + { + parent::__construct(null, $status, $headers); + + if (null !== $callback) { + $this->setCallback($callback); + } + $this->streamed = false; + $this->headersSent = false; + } + + /** + * Factory method for chainability. + * + * @param callable|null $callback A valid PHP callback or null to set it later + * + * @return static + * + * @deprecated since Symfony 5.1, use __construct() instead. + */ + public static function create($callback = null, int $status = 200, array $headers = []) + { + trigger_deprecation('symfony/http-foundation', '5.1', 'The "%s()" method is deprecated, use "new %s()" instead.', __METHOD__, static::class); + + return new static($callback, $status, $headers); + } + + /** + * Sets the PHP callback associated with this Response. + * + * @return $this + */ + public function setCallback(callable $callback) + { + $this->callback = $callback; + + return $this; + } + + /** + * {@inheritdoc} + * + * This method only sends the headers once. + * + * @return $this + */ + public function sendHeaders() + { + if ($this->headersSent) { + return $this; + } + + $this->headersSent = true; + + return parent::sendHeaders(); + } + + /** + * {@inheritdoc} + * + * This method only sends the content once. + * + * @return $this + */ + public function sendContent() + { + if ($this->streamed) { + return $this; + } + + $this->streamed = true; + + if (null === $this->callback) { + throw new \LogicException('The Response callback must not be null.'); + } + + ($this->callback)(); + + return $this; + } + + /** + * {@inheritdoc} + * + * @return $this + * + * @throws \LogicException when the content is not null + */ + public function setContent(?string $content) + { + if (null !== $content) { + throw new \LogicException('The content cannot be set on a StreamedResponse instance.'); + } + + $this->streamed = true; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getContent() + { + return false; + } +} diff --git a/src/vendor/symfony/http-foundation/Test/Constraint/RequestAttributeValueSame.php b/src/vendor/symfony/http-foundation/Test/Constraint/RequestAttributeValueSame.php new file mode 100644 index 0000000..cb216ea --- /dev/null +++ b/src/vendor/symfony/http-foundation/Test/Constraint/RequestAttributeValueSame.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\HttpFoundation\Request; + +final class RequestAttributeValueSame extends Constraint +{ + private $name; + private $value; + + public function __construct(string $name, string $value) + { + $this->name = $name; + $this->value = $value; + } + + /** + * {@inheritdoc} + */ + public function toString(): string + { + return sprintf('has attribute "%s" with value "%s"', $this->name, $this->value); + } + + /** + * @param Request $request + * + * {@inheritdoc} + */ + protected function matches($request): bool + { + return $this->value === $request->attributes->get($this->name); + } + + /** + * @param Request $request + * + * {@inheritdoc} + */ + protected function failureDescription($request): string + { + return 'the Request '.$this->toString(); + } +} diff --git a/src/vendor/symfony/http-foundation/Test/Constraint/ResponseCookieValueSame.php b/src/vendor/symfony/http-foundation/Test/Constraint/ResponseCookieValueSame.php new file mode 100644 index 0000000..eb9c26a --- /dev/null +++ b/src/vendor/symfony/http-foundation/Test/Constraint/ResponseCookieValueSame.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpFoundation\Response; + +final class ResponseCookieValueSame extends Constraint +{ + private $name; + private $value; + private $path; + private $domain; + + public function __construct(string $name, string $value, string $path = '/', string $domain = null) + { + $this->name = $name; + $this->value = $value; + $this->path = $path; + $this->domain = $domain; + } + + /** + * {@inheritdoc} + */ + public function toString(): string + { + $str = sprintf('has cookie "%s"', $this->name); + if ('/' !== $this->path) { + $str .= sprintf(' with path "%s"', $this->path); + } + if ($this->domain) { + $str .= sprintf(' for domain "%s"', $this->domain); + } + $str .= sprintf(' with value "%s"', $this->value); + + return $str; + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function matches($response): bool + { + $cookie = $this->getCookie($response); + if (!$cookie) { + return false; + } + + return $this->value === (string) $cookie->getValue(); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function failureDescription($response): string + { + return 'the Response '.$this->toString(); + } + + protected function getCookie(Response $response): ?Cookie + { + $cookies = $response->headers->getCookies(); + + $filteredCookies = array_filter($cookies, function (Cookie $cookie) { + return $cookie->getName() === $this->name && $cookie->getPath() === $this->path && $cookie->getDomain() === $this->domain; + }); + + return reset($filteredCookies) ?: null; + } +} diff --git a/src/vendor/symfony/http-foundation/Test/Constraint/ResponseFormatSame.php b/src/vendor/symfony/http-foundation/Test/Constraint/ResponseFormatSame.php new file mode 100644 index 0000000..f73aedf --- /dev/null +++ b/src/vendor/symfony/http-foundation/Test/Constraint/ResponseFormatSame.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Asserts that the response is in the given format. + * + * @author Kévin Dunglas + */ +final class ResponseFormatSame extends Constraint +{ + private $request; + private $format; + + public function __construct(Request $request, ?string $format) + { + $this->request = $request; + $this->format = $format; + } + + /** + * {@inheritdoc} + */ + public function toString(): string + { + return 'format is '.($this->format ?? 'null'); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function matches($response): bool + { + return $this->format === $this->request->getFormat($response->headers->get('Content-Type')); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function failureDescription($response): string + { + return 'the Response '.$this->toString(); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function additionalFailureDescription($response): string + { + return (string) $response; + } +} diff --git a/src/vendor/symfony/http-foundation/Test/Constraint/ResponseHasCookie.php b/src/vendor/symfony/http-foundation/Test/Constraint/ResponseHasCookie.php new file mode 100644 index 0000000..eae9e27 --- /dev/null +++ b/src/vendor/symfony/http-foundation/Test/Constraint/ResponseHasCookie.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpFoundation\Response; + +final class ResponseHasCookie extends Constraint +{ + private $name; + private $path; + private $domain; + + public function __construct(string $name, string $path = '/', string $domain = null) + { + $this->name = $name; + $this->path = $path; + $this->domain = $domain; + } + + /** + * {@inheritdoc} + */ + public function toString(): string + { + $str = sprintf('has cookie "%s"', $this->name); + if ('/' !== $this->path) { + $str .= sprintf(' with path "%s"', $this->path); + } + if ($this->domain) { + $str .= sprintf(' for domain "%s"', $this->domain); + } + + return $str; + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function matches($response): bool + { + return null !== $this->getCookie($response); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function failureDescription($response): string + { + return 'the Response '.$this->toString(); + } + + private function getCookie(Response $response): ?Cookie + { + $cookies = $response->headers->getCookies(); + + $filteredCookies = array_filter($cookies, function (Cookie $cookie) { + return $cookie->getName() === $this->name && $cookie->getPath() === $this->path && $cookie->getDomain() === $this->domain; + }); + + return reset($filteredCookies) ?: null; + } +} diff --git a/src/vendor/symfony/http-foundation/Test/Constraint/ResponseHasHeader.php b/src/vendor/symfony/http-foundation/Test/Constraint/ResponseHasHeader.php new file mode 100644 index 0000000..68ad827 --- /dev/null +++ b/src/vendor/symfony/http-foundation/Test/Constraint/ResponseHasHeader.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\HttpFoundation\Response; + +final class ResponseHasHeader extends Constraint +{ + private $headerName; + + public function __construct(string $headerName) + { + $this->headerName = $headerName; + } + + /** + * {@inheritdoc} + */ + public function toString(): string + { + return sprintf('has header "%s"', $this->headerName); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function matches($response): bool + { + return $response->headers->has($this->headerName); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function failureDescription($response): string + { + return 'the Response '.$this->toString(); + } +} diff --git a/src/vendor/symfony/http-foundation/Test/Constraint/ResponseHeaderSame.php b/src/vendor/symfony/http-foundation/Test/Constraint/ResponseHeaderSame.php new file mode 100644 index 0000000..a27d0c7 --- /dev/null +++ b/src/vendor/symfony/http-foundation/Test/Constraint/ResponseHeaderSame.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\HttpFoundation\Response; + +final class ResponseHeaderSame extends Constraint +{ + private $headerName; + private $expectedValue; + + public function __construct(string $headerName, string $expectedValue) + { + $this->headerName = $headerName; + $this->expectedValue = $expectedValue; + } + + /** + * {@inheritdoc} + */ + public function toString(): string + { + return sprintf('has header "%s" with value "%s"', $this->headerName, $this->expectedValue); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function matches($response): bool + { + return $this->expectedValue === $response->headers->get($this->headerName, null); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function failureDescription($response): string + { + return 'the Response '.$this->toString(); + } +} diff --git a/src/vendor/symfony/http-foundation/Test/Constraint/ResponseIsRedirected.php b/src/vendor/symfony/http-foundation/Test/Constraint/ResponseIsRedirected.php new file mode 100644 index 0000000..8c4b883 --- /dev/null +++ b/src/vendor/symfony/http-foundation/Test/Constraint/ResponseIsRedirected.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\HttpFoundation\Response; + +final class ResponseIsRedirected extends Constraint +{ + /** + * {@inheritdoc} + */ + public function toString(): string + { + return 'is redirected'; + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function matches($response): bool + { + return $response->isRedirect(); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function failureDescription($response): string + { + return 'the Response '.$this->toString(); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function additionalFailureDescription($response): string + { + return (string) $response; + } +} diff --git a/src/vendor/symfony/http-foundation/Test/Constraint/ResponseIsSuccessful.php b/src/vendor/symfony/http-foundation/Test/Constraint/ResponseIsSuccessful.php new file mode 100644 index 0000000..9c66558 --- /dev/null +++ b/src/vendor/symfony/http-foundation/Test/Constraint/ResponseIsSuccessful.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\HttpFoundation\Response; + +final class ResponseIsSuccessful extends Constraint +{ + /** + * {@inheritdoc} + */ + public function toString(): string + { + return 'is successful'; + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function matches($response): bool + { + return $response->isSuccessful(); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function failureDescription($response): string + { + return 'the Response '.$this->toString(); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function additionalFailureDescription($response): string + { + return (string) $response; + } +} diff --git a/src/vendor/symfony/http-foundation/Test/Constraint/ResponseIsUnprocessable.php b/src/vendor/symfony/http-foundation/Test/Constraint/ResponseIsUnprocessable.php new file mode 100644 index 0000000..880c781 --- /dev/null +++ b/src/vendor/symfony/http-foundation/Test/Constraint/ResponseIsUnprocessable.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\HttpFoundation\Response; + +final class ResponseIsUnprocessable extends Constraint +{ + /** + * {@inheritdoc} + */ + public function toString(): string + { + return 'is unprocessable'; + } + + /** + * @param Response $other + * + * {@inheritdoc} + */ + protected function matches($other): bool + { + return Response::HTTP_UNPROCESSABLE_ENTITY === $other->getStatusCode(); + } + + /** + * @param Response $other + * + * {@inheritdoc} + */ + protected function failureDescription($other): string + { + return 'the Response '.$this->toString(); + } + + /** + * @param Response $other + * + * {@inheritdoc} + */ + protected function additionalFailureDescription($other): string + { + return (string) $other; + } +} diff --git a/src/vendor/symfony/http-foundation/Test/Constraint/ResponseStatusCodeSame.php b/src/vendor/symfony/http-foundation/Test/Constraint/ResponseStatusCodeSame.php new file mode 100644 index 0000000..72bb000 --- /dev/null +++ b/src/vendor/symfony/http-foundation/Test/Constraint/ResponseStatusCodeSame.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Test\Constraint; + +use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\HttpFoundation\Response; + +final class ResponseStatusCodeSame extends Constraint +{ + private $statusCode; + + public function __construct(int $statusCode) + { + $this->statusCode = $statusCode; + } + + /** + * {@inheritdoc} + */ + public function toString(): string + { + return 'status code is '.$this->statusCode; + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function matches($response): bool + { + return $this->statusCode === $response->getStatusCode(); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function failureDescription($response): string + { + return 'the Response '.$this->toString(); + } + + /** + * @param Response $response + * + * {@inheritdoc} + */ + protected function additionalFailureDescription($response): string + { + return (string) $response; + } +} diff --git a/src/vendor/symfony/http-foundation/UrlHelper.php b/src/vendor/symfony/http-foundation/UrlHelper.php new file mode 100644 index 0000000..9065994 --- /dev/null +++ b/src/vendor/symfony/http-foundation/UrlHelper.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\RequestContextAwareInterface; + +/** + * A helper service for manipulating URLs within and outside the request scope. + * + * @author Valentin Udaltsov + */ +final class UrlHelper +{ + private $requestStack; + private $requestContext; + + /** + * @param RequestContextAwareInterface|RequestContext|null $requestContext + */ + public function __construct(RequestStack $requestStack, $requestContext = null) + { + if (null !== $requestContext && !$requestContext instanceof RequestContext && !$requestContext instanceof RequestContextAwareInterface) { + throw new \TypeError(__METHOD__.': Argument #2 ($requestContext) must of type Symfony\Component\Routing\RequestContextAwareInterface|Symfony\Component\Routing\RequestContext|null, '.get_debug_type($requestContext).' given.'); + } + + $this->requestStack = $requestStack; + $this->requestContext = $requestContext; + } + + public function getAbsoluteUrl(string $path): string + { + if (str_contains($path, '://') || '//' === substr($path, 0, 2)) { + return $path; + } + + if (null === $request = $this->requestStack->getMainRequest()) { + return $this->getAbsoluteUrlFromContext($path); + } + + if ('#' === $path[0]) { + $path = $request->getRequestUri().$path; + } elseif ('?' === $path[0]) { + $path = $request->getPathInfo().$path; + } + + if (!$path || '/' !== $path[0]) { + $prefix = $request->getPathInfo(); + $last = \strlen($prefix) - 1; + if ($last !== $pos = strrpos($prefix, '/')) { + $prefix = substr($prefix, 0, $pos).'/'; + } + + return $request->getUriForPath($prefix.$path); + } + + return $request->getSchemeAndHttpHost().$path; + } + + public function getRelativePath(string $path): string + { + if (str_contains($path, '://') || '//' === substr($path, 0, 2)) { + return $path; + } + + if (null === $request = $this->requestStack->getMainRequest()) { + return $path; + } + + return $request->getRelativeUriForPath($path); + } + + private function getAbsoluteUrlFromContext(string $path): string + { + if (null === $context = $this->requestContext) { + return $path; + } + + if ($context instanceof RequestContextAwareInterface) { + $context = $context->getContext(); + } + + if ('' === $host = $context->getHost()) { + return $path; + } + + $scheme = $context->getScheme(); + $port = ''; + + if ('http' === $scheme && 80 !== $context->getHttpPort()) { + $port = ':'.$context->getHttpPort(); + } elseif ('https' === $scheme && 443 !== $context->getHttpsPort()) { + $port = ':'.$context->getHttpsPort(); + } + + if ('#' === $path[0]) { + $queryString = $context->getQueryString(); + $path = $context->getPathInfo().($queryString ? '?'.$queryString : '').$path; + } elseif ('?' === $path[0]) { + $path = $context->getPathInfo().$path; + } + + if ('/' !== $path[0]) { + $path = rtrim($context->getBaseUrl(), '/').'/'.$path; + } + + return $scheme.'://'.$host.$port.$path; + } +} diff --git a/src/vendor/symfony/http-foundation/composer.json b/src/vendor/symfony/http-foundation/composer.json new file mode 100644 index 0000000..cb8d59f --- /dev/null +++ b/src/vendor/symfony/http-foundation/composer.json @@ -0,0 +1,43 @@ +{ + "name": "symfony/http-foundation", + "type": "library", + "description": "Defines an object-oriented layer for the HTTP specification", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "predis/predis": "~1.0", + "symfony/cache": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4", + "symfony/mime": "^4.4|^5.0|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/rate-limiter": "^5.2|^6.0" + }, + "suggest" : { + "symfony/mime": "To use the file extension guesser" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\HttpFoundation\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/src/vendor/symfony/polyfill-ctype/Ctype.php b/src/vendor/symfony/polyfill-ctype/Ctype.php new file mode 100644 index 0000000..ba75a2c --- /dev/null +++ b/src/vendor/symfony/polyfill-ctype/Ctype.php @@ -0,0 +1,232 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Ctype; + +/** + * Ctype implementation through regex. + * + * @internal + * + * @author Gert de Pagter + */ +final class Ctype +{ + /** + * Returns TRUE if every character in text is either a letter or a digit, FALSE otherwise. + * + * @see https://php.net/ctype-alnum + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_alnum($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z0-9]/', $text); + } + + /** + * Returns TRUE if every character in text is a letter, FALSE otherwise. + * + * @see https://php.net/ctype-alpha + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_alpha($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z]/', $text); + } + + /** + * Returns TRUE if every character in text is a control character from the current locale, FALSE otherwise. + * + * @see https://php.net/ctype-cntrl + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_cntrl($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^\x00-\x1f\x7f]/', $text); + } + + /** + * Returns TRUE if every character in the string text is a decimal digit, FALSE otherwise. + * + * @see https://php.net/ctype-digit + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_digit($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^0-9]/', $text); + } + + /** + * Returns TRUE if every character in text is printable and actually creates visible output (no white space), FALSE otherwise. + * + * @see https://php.net/ctype-graph + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_graph($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^!-~]/', $text); + } + + /** + * Returns TRUE if every character in text is a lowercase letter. + * + * @see https://php.net/ctype-lower + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_lower($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^a-z]/', $text); + } + + /** + * Returns TRUE if every character in text will actually create output (including blanks). Returns FALSE if text contains control characters or characters that do not have any output or control function at all. + * + * @see https://php.net/ctype-print + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_print($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^ -~]/', $text); + } + + /** + * Returns TRUE if every character in text is printable, but neither letter, digit or blank, FALSE otherwise. + * + * @see https://php.net/ctype-punct + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_punct($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^!-\/\:-@\[-`\{-~]/', $text); + } + + /** + * Returns TRUE if every character in text creates some sort of white space, FALSE otherwise. Besides the blank character this also includes tab, vertical tab, line feed, carriage return and form feed characters. + * + * @see https://php.net/ctype-space + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_space($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^\s]/', $text); + } + + /** + * Returns TRUE if every character in text is an uppercase letter. + * + * @see https://php.net/ctype-upper + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_upper($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Z]/', $text); + } + + /** + * Returns TRUE if every character in text is a hexadecimal 'digit', that is a decimal digit or a character from [A-Fa-f] , FALSE otherwise. + * + * @see https://php.net/ctype-xdigit + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_xdigit($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Fa-f0-9]/', $text); + } + + /** + * Converts integers to their char versions according to normal ctype behaviour, if needed. + * + * If an integer between -128 and 255 inclusive is provided, + * it is interpreted as the ASCII value of a single character + * (negative values have 256 added in order to allow characters in the Extended ASCII range). + * Any other integer is interpreted as a string containing the decimal digits of the integer. + * + * @param mixed $int + * @param string $function + * + * @return mixed + */ + private static function convert_int_to_char_for_ctype($int, $function) + { + if (!\is_int($int)) { + return $int; + } + + if ($int < -128 || $int > 255) { + return (string) $int; + } + + if (\PHP_VERSION_ID >= 80100) { + @trigger_error($function.'(): Argument of type int will be interpreted as string in the future', \E_USER_DEPRECATED); + } + + if ($int < 0) { + $int += 256; + } + + return \chr($int); + } +} diff --git a/src/vendor/symfony/polyfill-ctype/LICENSE b/src/vendor/symfony/polyfill-ctype/LICENSE new file mode 100644 index 0000000..7536cae --- /dev/null +++ b/src/vendor/symfony/polyfill-ctype/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/vendor/symfony/polyfill-ctype/README.md b/src/vendor/symfony/polyfill-ctype/README.md new file mode 100644 index 0000000..b144d03 --- /dev/null +++ b/src/vendor/symfony/polyfill-ctype/README.md @@ -0,0 +1,12 @@ +Symfony Polyfill / Ctype +======================== + +This component provides `ctype_*` functions to users who run php versions without the ctype extension. + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/src/vendor/symfony/polyfill-ctype/bootstrap.php b/src/vendor/symfony/polyfill-ctype/bootstrap.php new file mode 100644 index 0000000..d54524b --- /dev/null +++ b/src/vendor/symfony/polyfill-ctype/bootstrap.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Ctype as p; + +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + +if (!function_exists('ctype_alnum')) { + function ctype_alnum($text) { return p\Ctype::ctype_alnum($text); } +} +if (!function_exists('ctype_alpha')) { + function ctype_alpha($text) { return p\Ctype::ctype_alpha($text); } +} +if (!function_exists('ctype_cntrl')) { + function ctype_cntrl($text) { return p\Ctype::ctype_cntrl($text); } +} +if (!function_exists('ctype_digit')) { + function ctype_digit($text) { return p\Ctype::ctype_digit($text); } +} +if (!function_exists('ctype_graph')) { + function ctype_graph($text) { return p\Ctype::ctype_graph($text); } +} +if (!function_exists('ctype_lower')) { + function ctype_lower($text) { return p\Ctype::ctype_lower($text); } +} +if (!function_exists('ctype_print')) { + function ctype_print($text) { return p\Ctype::ctype_print($text); } +} +if (!function_exists('ctype_punct')) { + function ctype_punct($text) { return p\Ctype::ctype_punct($text); } +} +if (!function_exists('ctype_space')) { + function ctype_space($text) { return p\Ctype::ctype_space($text); } +} +if (!function_exists('ctype_upper')) { + function ctype_upper($text) { return p\Ctype::ctype_upper($text); } +} +if (!function_exists('ctype_xdigit')) { + function ctype_xdigit($text) { return p\Ctype::ctype_xdigit($text); } +} diff --git a/src/vendor/symfony/polyfill-ctype/bootstrap80.php b/src/vendor/symfony/polyfill-ctype/bootstrap80.php new file mode 100644 index 0000000..ab2f861 --- /dev/null +++ b/src/vendor/symfony/polyfill-ctype/bootstrap80.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Ctype as p; + +if (!function_exists('ctype_alnum')) { + function ctype_alnum(mixed $text): bool { return p\Ctype::ctype_alnum($text); } +} +if (!function_exists('ctype_alpha')) { + function ctype_alpha(mixed $text): bool { return p\Ctype::ctype_alpha($text); } +} +if (!function_exists('ctype_cntrl')) { + function ctype_cntrl(mixed $text): bool { return p\Ctype::ctype_cntrl($text); } +} +if (!function_exists('ctype_digit')) { + function ctype_digit(mixed $text): bool { return p\Ctype::ctype_digit($text); } +} +if (!function_exists('ctype_graph')) { + function ctype_graph(mixed $text): bool { return p\Ctype::ctype_graph($text); } +} +if (!function_exists('ctype_lower')) { + function ctype_lower(mixed $text): bool { return p\Ctype::ctype_lower($text); } +} +if (!function_exists('ctype_print')) { + function ctype_print(mixed $text): bool { return p\Ctype::ctype_print($text); } +} +if (!function_exists('ctype_punct')) { + function ctype_punct(mixed $text): bool { return p\Ctype::ctype_punct($text); } +} +if (!function_exists('ctype_space')) { + function ctype_space(mixed $text): bool { return p\Ctype::ctype_space($text); } +} +if (!function_exists('ctype_upper')) { + function ctype_upper(mixed $text): bool { return p\Ctype::ctype_upper($text); } +} +if (!function_exists('ctype_xdigit')) { + function ctype_xdigit(mixed $text): bool { return p\Ctype::ctype_xdigit($text); } +} diff --git a/src/vendor/symfony/polyfill-ctype/composer.json b/src/vendor/symfony/polyfill-ctype/composer.json new file mode 100644 index 0000000..e5c978f --- /dev/null +++ b/src/vendor/symfony/polyfill-ctype/composer.json @@ -0,0 +1,41 @@ +{ + "name": "symfony/polyfill-ctype", + "type": "library", + "description": "Symfony polyfill for ctype functions", + "keywords": ["polyfill", "compatibility", "portable", "ctype"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Ctype\\": "" }, + "files": [ "bootstrap.php" ] + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/src/vendor/symfony/polyfill-mbstring/LICENSE b/src/vendor/symfony/polyfill-mbstring/LICENSE new file mode 100644 index 0000000..6e3afce --- /dev/null +++ b/src/vendor/symfony/polyfill-mbstring/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/vendor/symfony/polyfill-mbstring/Mbstring.php b/src/vendor/symfony/polyfill-mbstring/Mbstring.php new file mode 100644 index 0000000..2e0b969 --- /dev/null +++ b/src/vendor/symfony/polyfill-mbstring/Mbstring.php @@ -0,0 +1,947 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Mbstring; + +/** + * Partial mbstring implementation in PHP, iconv based, UTF-8 centric. + * + * Implemented: + * - mb_chr - Returns a specific character from its Unicode code point + * - mb_convert_encoding - Convert character encoding + * - mb_convert_variables - Convert character code in variable(s) + * - mb_decode_mimeheader - Decode string in MIME header field + * - mb_encode_mimeheader - Encode string for MIME header XXX NATIVE IMPLEMENTATION IS REALLY BUGGED + * - mb_decode_numericentity - Decode HTML numeric string reference to character + * - mb_encode_numericentity - Encode character to HTML numeric string reference + * - mb_convert_case - Perform case folding on a string + * - mb_detect_encoding - Detect character encoding + * - mb_get_info - Get internal settings of mbstring + * - mb_http_input - Detect HTTP input character encoding + * - mb_http_output - Set/Get HTTP output character encoding + * - mb_internal_encoding - Set/Get internal character encoding + * - mb_list_encodings - Returns an array of all supported encodings + * - mb_ord - Returns the Unicode code point of a character + * - mb_output_handler - Callback function converts character encoding in output buffer + * - mb_scrub - Replaces ill-formed byte sequences with substitute characters + * - mb_strlen - Get string length + * - mb_strpos - Find position of first occurrence of string in a string + * - mb_strrpos - Find position of last occurrence of a string in a string + * - mb_str_split - Convert a string to an array + * - mb_strtolower - Make a string lowercase + * - mb_strtoupper - Make a string uppercase + * - mb_substitute_character - Set/Get substitution character + * - mb_substr - Get part of string + * - mb_stripos - Finds position of first occurrence of a string within another, case insensitive + * - mb_stristr - Finds first occurrence of a string within another, case insensitive + * - mb_strrchr - Finds the last occurrence of a character in a string within another + * - mb_strrichr - Finds the last occurrence of a character in a string within another, case insensitive + * - mb_strripos - Finds position of last occurrence of a string within another, case insensitive + * - mb_strstr - Finds first occurrence of a string within another + * - mb_strwidth - Return width of string + * - mb_substr_count - Count the number of substring occurrences + * + * Not implemented: + * - mb_convert_kana - Convert "kana" one from another ("zen-kaku", "han-kaku" and more) + * - mb_ereg_* - Regular expression with multibyte support + * - mb_parse_str - Parse GET/POST/COOKIE data and set global variable + * - mb_preferred_mime_name - Get MIME charset string + * - mb_regex_encoding - Returns current encoding for multibyte regex as string + * - mb_regex_set_options - Set/Get the default options for mbregex functions + * - mb_send_mail - Send encoded mail + * - mb_split - Split multibyte string using regular expression + * - mb_strcut - Get part of string + * - mb_strimwidth - Get truncated string with specified width + * + * @author Nicolas Grekas + * + * @internal + */ +final class Mbstring +{ + public const MB_CASE_FOLD = \PHP_INT_MAX; + + private const SIMPLE_CASE_FOLD = [ + ['µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"], + ['μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1", 'ι'], + ]; + + private static $encodingList = ['ASCII', 'UTF-8']; + private static $language = 'neutral'; + private static $internalEncoding = 'UTF-8'; + + public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null) + { + if (\is_array($fromEncoding) || (null !== $fromEncoding && false !== strpos($fromEncoding, ','))) { + $fromEncoding = self::mb_detect_encoding($s, $fromEncoding); + } else { + $fromEncoding = self::getEncoding($fromEncoding); + } + + $toEncoding = self::getEncoding($toEncoding); + + if ('BASE64' === $fromEncoding) { + $s = base64_decode($s); + $fromEncoding = $toEncoding; + } + + if ('BASE64' === $toEncoding) { + return base64_encode($s); + } + + if ('HTML-ENTITIES' === $toEncoding || 'HTML' === $toEncoding) { + if ('HTML-ENTITIES' === $fromEncoding || 'HTML' === $fromEncoding) { + $fromEncoding = 'Windows-1252'; + } + if ('UTF-8' !== $fromEncoding) { + $s = iconv($fromEncoding, 'UTF-8//IGNORE', $s); + } + + return preg_replace_callback('/[\x80-\xFF]+/', [__CLASS__, 'html_encoding_callback'], $s); + } + + if ('HTML-ENTITIES' === $fromEncoding) { + $s = html_entity_decode($s, \ENT_COMPAT, 'UTF-8'); + $fromEncoding = 'UTF-8'; + } + + return iconv($fromEncoding, $toEncoding.'//IGNORE', $s); + } + + public static function mb_convert_variables($toEncoding, $fromEncoding, &...$vars) + { + $ok = true; + array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) { + if (false === $v = self::mb_convert_encoding($v, $toEncoding, $fromEncoding)) { + $ok = false; + } + }); + + return $ok ? $fromEncoding : false; + } + + public static function mb_decode_mimeheader($s) + { + return iconv_mime_decode($s, 2, self::$internalEncoding); + } + + public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null) + { + trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', \E_USER_WARNING); + } + + public static function mb_decode_numericentity($s, $convmap, $encoding = null) + { + if (null !== $s && !\is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) { + trigger_error('mb_decode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return null; + } + + if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) { + return false; + } + + if (null !== $encoding && !\is_scalar($encoding)) { + trigger_error('mb_decode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return ''; // Instead of null (cf. mb_encode_numericentity). + } + + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + $cnt = floor(\count($convmap) / 4) * 4; + + for ($i = 0; $i < $cnt; $i += 4) { + // collector_decode_htmlnumericentity ignores $convmap[$i + 3] + $convmap[$i] += $convmap[$i + 2]; + $convmap[$i + 1] += $convmap[$i + 2]; + } + + $s = preg_replace_callback('/&#(?:0*([0-9]+)|x0*([0-9a-fA-F]+))(?!&);?/', function (array $m) use ($cnt, $convmap) { + $c = isset($m[2]) ? (int) hexdec($m[2]) : $m[1]; + for ($i = 0; $i < $cnt; $i += 4) { + if ($c >= $convmap[$i] && $c <= $convmap[$i + 1]) { + return self::mb_chr($c - $convmap[$i + 2]); + } + } + + return $m[0]; + }, $s); + + if (null === $encoding) { + return $s; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $s); + } + + public static function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = false) + { + if (null !== $s && !\is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) { + trigger_error('mb_encode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return null; + } + + if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) { + return false; + } + + if (null !== $encoding && !\is_scalar($encoding)) { + trigger_error('mb_encode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return null; // Instead of '' (cf. mb_decode_numericentity). + } + + if (null !== $is_hex && !\is_scalar($is_hex)) { + trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, '.\gettype($s).' given', \E_USER_WARNING); + + return null; + } + + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; + + $cnt = floor(\count($convmap) / 4) * 4; + $i = 0; + $len = \strlen($s); + $result = ''; + + while ($i < $len) { + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + $c = self::mb_ord($uchr); + + for ($j = 0; $j < $cnt; $j += 4) { + if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) { + $cOffset = ($c + $convmap[$j + 2]) & $convmap[$j + 3]; + $result .= $is_hex ? sprintf('&#x%X;', $cOffset) : '&#'.$cOffset.';'; + continue 2; + } + } + $result .= $uchr; + } + + if (null === $encoding) { + return $result; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $result); + } + + public static function mb_convert_case($s, $mode, $encoding = null) + { + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + if (\MB_CASE_TITLE == $mode) { + static $titleRegexp = null; + if (null === $titleRegexp) { + $titleRegexp = self::getData('titleCaseRegexp'); + } + $s = preg_replace_callback($titleRegexp, [__CLASS__, 'title_case'], $s); + } else { + if (\MB_CASE_UPPER == $mode) { + static $upper = null; + if (null === $upper) { + $upper = self::getData('upperCase'); + } + $map = $upper; + } else { + if (self::MB_CASE_FOLD === $mode) { + static $caseFolding = null; + if (null === $caseFolding) { + $caseFolding = self::getData('caseFolding'); + } + $s = strtr($s, $caseFolding); + } + + static $lower = null; + if (null === $lower) { + $lower = self::getData('lowerCase'); + } + $map = $lower; + } + + static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; + + $i = 0; + $len = \strlen($s); + + while ($i < $len) { + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + + if (isset($map[$uchr])) { + $uchr = $map[$uchr]; + $nlen = \strlen($uchr); + + if ($nlen == $ulen) { + $nlen = $i; + do { + $s[--$nlen] = $uchr[--$ulen]; + } while ($ulen); + } else { + $s = substr_replace($s, $uchr, $i - $ulen, $ulen); + $len += $nlen - $ulen; + $i += $nlen - $ulen; + } + } + } + } + + if (null === $encoding) { + return $s; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $s); + } + + public static function mb_internal_encoding($encoding = null) + { + if (null === $encoding) { + return self::$internalEncoding; + } + + $normalizedEncoding = self::getEncoding($encoding); + + if ('UTF-8' === $normalizedEncoding || false !== @iconv($normalizedEncoding, $normalizedEncoding, ' ')) { + self::$internalEncoding = $normalizedEncoding; + + return true; + } + + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError(sprintf('Argument #1 ($encoding) must be a valid encoding, "%s" given', $encoding)); + } + + public static function mb_language($lang = null) + { + if (null === $lang) { + return self::$language; + } + + switch ($normalizedLang = strtolower($lang)) { + case 'uni': + case 'neutral': + self::$language = $normalizedLang; + + return true; + } + + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError(sprintf('Argument #1 ($language) must be a valid language, "%s" given', $lang)); + } + + public static function mb_list_encodings() + { + return ['UTF-8']; + } + + public static function mb_encoding_aliases($encoding) + { + switch (strtoupper($encoding)) { + case 'UTF8': + case 'UTF-8': + return ['utf8']; + } + + return false; + } + + public static function mb_check_encoding($var = null, $encoding = null) + { + if (PHP_VERSION_ID < 70200 && \is_array($var)) { + trigger_error('mb_check_encoding() expects parameter 1 to be string, array given', \E_USER_WARNING); + + return null; + } + + if (null === $encoding) { + if (null === $var) { + return false; + } + $encoding = self::$internalEncoding; + } + + if (!\is_array($var)) { + return self::mb_detect_encoding($var, [$encoding]) || false !== @iconv($encoding, $encoding, $var); + } + + foreach ($var as $key => $value) { + if (!self::mb_check_encoding($key, $encoding)) { + return false; + } + if (!self::mb_check_encoding($value, $encoding)) { + return false; + } + } + + return true; + + } + + public static function mb_detect_encoding($str, $encodingList = null, $strict = false) + { + if (null === $encodingList) { + $encodingList = self::$encodingList; + } else { + if (!\is_array($encodingList)) { + $encodingList = array_map('trim', explode(',', $encodingList)); + } + $encodingList = array_map('strtoupper', $encodingList); + } + + foreach ($encodingList as $enc) { + switch ($enc) { + case 'ASCII': + if (!preg_match('/[\x80-\xFF]/', $str)) { + return $enc; + } + break; + + case 'UTF8': + case 'UTF-8': + if (preg_match('//u', $str)) { + return 'UTF-8'; + } + break; + + default: + if (0 === strncmp($enc, 'ISO-8859-', 9)) { + return $enc; + } + } + } + + return false; + } + + public static function mb_detect_order($encodingList = null) + { + if (null === $encodingList) { + return self::$encodingList; + } + + if (!\is_array($encodingList)) { + $encodingList = array_map('trim', explode(',', $encodingList)); + } + $encodingList = array_map('strtoupper', $encodingList); + + foreach ($encodingList as $enc) { + switch ($enc) { + default: + if (strncmp($enc, 'ISO-8859-', 9)) { + return false; + } + // no break + case 'ASCII': + case 'UTF8': + case 'UTF-8': + } + } + + self::$encodingList = $encodingList; + + return true; + } + + public static function mb_strlen($s, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return \strlen($s); + } + + return @iconv_strlen($s, $encoding); + } + + public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strpos($haystack, $needle, $offset); + } + + $needle = (string) $needle; + if ('' === $needle) { + if (80000 > \PHP_VERSION_ID) { + trigger_error(__METHOD__.': Empty delimiter', \E_USER_WARNING); + + return false; + } + + return 0; + } + + return iconv_strpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strrpos($haystack, $needle, $offset); + } + + if ($offset != (int) $offset) { + $offset = 0; + } elseif ($offset = (int) $offset) { + if ($offset < 0) { + if (0 > $offset += self::mb_strlen($needle)) { + $haystack = self::mb_substr($haystack, 0, $offset, $encoding); + } + $offset = 0; + } else { + $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding); + } + } + + $pos = '' !== $needle || 80000 > \PHP_VERSION_ID + ? iconv_strrpos($haystack, $needle, $encoding) + : self::mb_strlen($haystack, $encoding); + + return false !== $pos ? $offset + $pos : false; + } + + public static function mb_str_split($string, $split_length = 1, $encoding = null) + { + if (null !== $string && !\is_scalar($string) && !(\is_object($string) && method_exists($string, '__toString'))) { + trigger_error('mb_str_split() expects parameter 1 to be string, '.\gettype($string).' given', \E_USER_WARNING); + + return null; + } + + if (1 > $split_length = (int) $split_length) { + if (80000 > \PHP_VERSION_ID) { + trigger_error('The length of each segment must be greater than zero', \E_USER_WARNING); + + return false; + } + + throw new \ValueError('Argument #2 ($length) must be greater than 0'); + } + + if (null === $encoding) { + $encoding = mb_internal_encoding(); + } + + if ('UTF-8' === $encoding = self::getEncoding($encoding)) { + $rx = '/('; + while (65535 < $split_length) { + $rx .= '.{65535}'; + $split_length -= 65535; + } + $rx .= '.{'.$split_length.'})/us'; + + return preg_split($rx, $string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY); + } + + $result = []; + $length = mb_strlen($string, $encoding); + + for ($i = 0; $i < $length; $i += $split_length) { + $result[] = mb_substr($string, $i, $split_length, $encoding); + } + + return $result; + } + + public static function mb_strtolower($s, $encoding = null) + { + return self::mb_convert_case($s, \MB_CASE_LOWER, $encoding); + } + + public static function mb_strtoupper($s, $encoding = null) + { + return self::mb_convert_case($s, \MB_CASE_UPPER, $encoding); + } + + public static function mb_substitute_character($c = null) + { + if (null === $c) { + return 'none'; + } + if (0 === strcasecmp($c, 'none')) { + return true; + } + if (80000 > \PHP_VERSION_ID) { + return false; + } + if (\is_int($c) || 'long' === $c || 'entity' === $c) { + return false; + } + + throw new \ValueError('Argument #1 ($substitute_character) must be "none", "long", "entity" or a valid codepoint'); + } + + public static function mb_substr($s, $start, $length = null, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return (string) substr($s, $start, null === $length ? 2147483647 : $length); + } + + if ($start < 0) { + $start = iconv_strlen($s, $encoding) + $start; + if ($start < 0) { + $start = 0; + } + } + + if (null === $length) { + $length = 2147483647; + } elseif ($length < 0) { + $length = iconv_strlen($s, $encoding) + $length - $start; + if ($length < 0) { + return ''; + } + } + + return (string) iconv_substr($s, $start, $length, $encoding); + } + + public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) + { + [$haystack, $needle] = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], [ + self::mb_convert_case($haystack, \MB_CASE_LOWER, $encoding), + self::mb_convert_case($needle, \MB_CASE_LOWER, $encoding), + ]); + + return self::mb_strpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_stristr($haystack, $needle, $part = false, $encoding = null) + { + $pos = self::mb_stripos($haystack, $needle, 0, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + $pos = strrpos($haystack, $needle); + } else { + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = iconv_strrpos($haystack, $needle, $encoding); + } + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null) + { + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = self::mb_strripos($haystack, $needle, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) + { + $haystack = self::mb_convert_case($haystack, \MB_CASE_LOWER, $encoding); + $needle = self::mb_convert_case($needle, \MB_CASE_LOWER, $encoding); + + $haystack = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], $haystack); + $needle = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], $needle); + + return self::mb_strrpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_strstr($haystack, $needle, $part = false, $encoding = null) + { + $pos = strpos($haystack, $needle); + if (false === $pos) { + return false; + } + if ($part) { + return substr($haystack, 0, $pos); + } + + return substr($haystack, $pos); + } + + public static function mb_get_info($type = 'all') + { + $info = [ + 'internal_encoding' => self::$internalEncoding, + 'http_output' => 'pass', + 'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)', + 'func_overload' => 0, + 'func_overload_list' => 'no overload', + 'mail_charset' => 'UTF-8', + 'mail_header_encoding' => 'BASE64', + 'mail_body_encoding' => 'BASE64', + 'illegal_chars' => 0, + 'encoding_translation' => 'Off', + 'language' => self::$language, + 'detect_order' => self::$encodingList, + 'substitute_character' => 'none', + 'strict_detection' => 'Off', + ]; + + if ('all' === $type) { + return $info; + } + if (isset($info[$type])) { + return $info[$type]; + } + + return false; + } + + public static function mb_http_input($type = '') + { + return false; + } + + public static function mb_http_output($encoding = null) + { + return null !== $encoding ? 'pass' === $encoding : 'pass'; + } + + public static function mb_strwidth($s, $encoding = null) + { + $encoding = self::getEncoding($encoding); + + if ('UTF-8' !== $encoding) { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + $s = preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide); + + return ($wide << 1) + iconv_strlen($s, 'UTF-8'); + } + + public static function mb_substr_count($haystack, $needle, $encoding = null) + { + return substr_count($haystack, $needle); + } + + public static function mb_output_handler($contents, $status) + { + return $contents; + } + + public static function mb_chr($code, $encoding = null) + { + if (0x80 > $code %= 0x200000) { + $s = \chr($code); + } elseif (0x800 > $code) { + $s = \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F); + } elseif (0x10000 > $code) { + $s = \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } else { + $s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } + + if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { + $s = mb_convert_encoding($s, $encoding, 'UTF-8'); + } + + return $s; + } + + public static function mb_ord($s, $encoding = null) + { + if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { + $s = mb_convert_encoding($s, 'UTF-8', $encoding); + } + + if (1 === \strlen($s)) { + return \ord($s); + } + + $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0; + if (0xF0 <= $code) { + return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80; + } + if (0xE0 <= $code) { + return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80; + } + if (0xC0 <= $code) { + return (($code - 0xC0) << 6) + $s[2] - 0x80; + } + + return $code; + } + + public static function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = \STR_PAD_RIGHT, string $encoding = null): string + { + if (!\in_array($pad_type, [\STR_PAD_RIGHT, \STR_PAD_LEFT, \STR_PAD_BOTH], true)) { + throw new \ValueError('mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH'); + } + + if (null === $encoding) { + $encoding = self::mb_internal_encoding(); + } + + try { + $validEncoding = @self::mb_check_encoding('', $encoding); + } catch (\ValueError $e) { + throw new \ValueError(sprintf('mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given', $encoding)); + } + + // BC for PHP 7.3 and lower + if (!$validEncoding) { + throw new \ValueError(sprintf('mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given', $encoding)); + } + + if (self::mb_strlen($pad_string, $encoding) <= 0) { + throw new \ValueError('mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string'); + } + + $paddingRequired = $length - self::mb_strlen($string, $encoding); + + if ($paddingRequired < 1) { + return $string; + } + + switch ($pad_type) { + case \STR_PAD_LEFT: + return self::mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding).$string; + case \STR_PAD_RIGHT: + return $string.self::mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding); + default: + $leftPaddingLength = floor($paddingRequired / 2); + $rightPaddingLength = $paddingRequired - $leftPaddingLength; + + return self::mb_substr(str_repeat($pad_string, $leftPaddingLength), 0, $leftPaddingLength, $encoding).$string.self::mb_substr(str_repeat($pad_string, $rightPaddingLength), 0, $rightPaddingLength, $encoding); + } + } + + private static function getSubpart($pos, $part, $haystack, $encoding) + { + if (false === $pos) { + return false; + } + if ($part) { + return self::mb_substr($haystack, 0, $pos, $encoding); + } + + return self::mb_substr($haystack, $pos, null, $encoding); + } + + private static function html_encoding_callback(array $m) + { + $i = 1; + $entities = ''; + $m = unpack('C*', htmlentities($m[0], \ENT_COMPAT, 'UTF-8')); + + while (isset($m[$i])) { + if (0x80 > $m[$i]) { + $entities .= \chr($m[$i++]); + continue; + } + if (0xF0 <= $m[$i]) { + $c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } elseif (0xE0 <= $m[$i]) { + $c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } else { + $c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80; + } + + $entities .= '&#'.$c.';'; + } + + return $entities; + } + + private static function title_case(array $s) + { + return self::mb_convert_case($s[1], \MB_CASE_UPPER, 'UTF-8').self::mb_convert_case($s[2], \MB_CASE_LOWER, 'UTF-8'); + } + + private static function getData($file) + { + if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) { + return require $file; + } + + return false; + } + + private static function getEncoding($encoding) + { + if (null === $encoding) { + return self::$internalEncoding; + } + + if ('UTF-8' === $encoding) { + return 'UTF-8'; + } + + $encoding = strtoupper($encoding); + + if ('8BIT' === $encoding || 'BINARY' === $encoding) { + return 'CP850'; + } + + if ('UTF8' === $encoding) { + return 'UTF-8'; + } + + return $encoding; + } +} diff --git a/src/vendor/symfony/polyfill-mbstring/README.md b/src/vendor/symfony/polyfill-mbstring/README.md new file mode 100644 index 0000000..478b40d --- /dev/null +++ b/src/vendor/symfony/polyfill-mbstring/README.md @@ -0,0 +1,13 @@ +Symfony Polyfill / Mbstring +=========================== + +This component provides a partial, native PHP implementation for the +[Mbstring](https://php.net/mbstring) extension. + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/src/vendor/symfony/polyfill-mbstring/Resources/unidata/caseFolding.php b/src/vendor/symfony/polyfill-mbstring/Resources/unidata/caseFolding.php new file mode 100644 index 0000000..512bba0 --- /dev/null +++ b/src/vendor/symfony/polyfill-mbstring/Resources/unidata/caseFolding.php @@ -0,0 +1,119 @@ + 'i̇', + 'µ' => 'μ', + 'ſ' => 's', + 'ͅ' => 'ι', + 'ς' => 'σ', + 'ϐ' => 'β', + 'ϑ' => 'θ', + 'ϕ' => 'φ', + 'ϖ' => 'π', + 'ϰ' => 'κ', + 'ϱ' => 'ρ', + 'ϵ' => 'ε', + 'ẛ' => 'ṡ', + 'ι' => 'ι', + 'ß' => 'ss', + 'ʼn' => 'ʼn', + 'ǰ' => 'ǰ', + 'ΐ' => 'ΐ', + 'ΰ' => 'ΰ', + 'և' => 'եւ', + 'ẖ' => 'ẖ', + 'ẗ' => 'ẗ', + 'ẘ' => 'ẘ', + 'ẙ' => 'ẙ', + 'ẚ' => 'aʾ', + 'ẞ' => 'ss', + 'ὐ' => 'ὐ', + 'ὒ' => 'ὒ', + 'ὔ' => 'ὔ', + 'ὖ' => 'ὖ', + 'ᾀ' => 'ἀι', + 'ᾁ' => 'ἁι', + 'ᾂ' => 'ἂι', + 'ᾃ' => 'ἃι', + 'ᾄ' => 'ἄι', + 'ᾅ' => 'ἅι', + 'ᾆ' => 'ἆι', + 'ᾇ' => 'ἇι', + 'ᾈ' => 'ἀι', + 'ᾉ' => 'ἁι', + 'ᾊ' => 'ἂι', + 'ᾋ' => 'ἃι', + 'ᾌ' => 'ἄι', + 'ᾍ' => 'ἅι', + 'ᾎ' => 'ἆι', + 'ᾏ' => 'ἇι', + 'ᾐ' => 'ἠι', + 'ᾑ' => 'ἡι', + 'ᾒ' => 'ἢι', + 'ᾓ' => 'ἣι', + 'ᾔ' => 'ἤι', + 'ᾕ' => 'ἥι', + 'ᾖ' => 'ἦι', + 'ᾗ' => 'ἧι', + 'ᾘ' => 'ἠι', + 'ᾙ' => 'ἡι', + 'ᾚ' => 'ἢι', + 'ᾛ' => 'ἣι', + 'ᾜ' => 'ἤι', + 'ᾝ' => 'ἥι', + 'ᾞ' => 'ἦι', + 'ᾟ' => 'ἧι', + 'ᾠ' => 'ὠι', + 'ᾡ' => 'ὡι', + 'ᾢ' => 'ὢι', + 'ᾣ' => 'ὣι', + 'ᾤ' => 'ὤι', + 'ᾥ' => 'ὥι', + 'ᾦ' => 'ὦι', + 'ᾧ' => 'ὧι', + 'ᾨ' => 'ὠι', + 'ᾩ' => 'ὡι', + 'ᾪ' => 'ὢι', + 'ᾫ' => 'ὣι', + 'ᾬ' => 'ὤι', + 'ᾭ' => 'ὥι', + 'ᾮ' => 'ὦι', + 'ᾯ' => 'ὧι', + 'ᾲ' => 'ὰι', + 'ᾳ' => 'αι', + 'ᾴ' => 'άι', + 'ᾶ' => 'ᾶ', + 'ᾷ' => 'ᾶι', + 'ᾼ' => 'αι', + 'ῂ' => 'ὴι', + 'ῃ' => 'ηι', + 'ῄ' => 'ήι', + 'ῆ' => 'ῆ', + 'ῇ' => 'ῆι', + 'ῌ' => 'ηι', + 'ῒ' => 'ῒ', + 'ῖ' => 'ῖ', + 'ῗ' => 'ῗ', + 'ῢ' => 'ῢ', + 'ῤ' => 'ῤ', + 'ῦ' => 'ῦ', + 'ῧ' => 'ῧ', + 'ῲ' => 'ὼι', + 'ῳ' => 'ωι', + 'ῴ' => 'ώι', + 'ῶ' => 'ῶ', + 'ῷ' => 'ῶι', + 'ῼ' => 'ωι', + 'ff' => 'ff', + 'fi' => 'fi', + 'fl' => 'fl', + 'ffi' => 'ffi', + 'ffl' => 'ffl', + 'ſt' => 'st', + 'st' => 'st', + 'ﬓ' => 'մն', + 'ﬔ' => 'մե', + 'ﬕ' => 'մի', + 'ﬖ' => 'վն', + 'ﬗ' => 'մխ', +]; diff --git a/src/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php b/src/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php new file mode 100644 index 0000000..fac60b0 --- /dev/null +++ b/src/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php @@ -0,0 +1,1397 @@ + 'a', + 'B' => 'b', + 'C' => 'c', + 'D' => 'd', + 'E' => 'e', + 'F' => 'f', + 'G' => 'g', + 'H' => 'h', + 'I' => 'i', + 'J' => 'j', + 'K' => 'k', + 'L' => 'l', + 'M' => 'm', + 'N' => 'n', + 'O' => 'o', + 'P' => 'p', + 'Q' => 'q', + 'R' => 'r', + 'S' => 's', + 'T' => 't', + 'U' => 'u', + 'V' => 'v', + 'W' => 'w', + 'X' => 'x', + 'Y' => 'y', + 'Z' => 'z', + 'À' => 'à', + 'Á' => 'á', + 'Â' => 'â', + 'Ã' => 'ã', + 'Ä' => 'ä', + 'Å' => 'å', + 'Æ' => 'æ', + 'Ç' => 'ç', + 'È' => 'è', + 'É' => 'é', + 'Ê' => 'ê', + 'Ë' => 'ë', + 'Ì' => 'ì', + 'Í' => 'í', + 'Î' => 'î', + 'Ï' => 'ï', + 'Ð' => 'ð', + 'Ñ' => 'ñ', + 'Ò' => 'ò', + 'Ó' => 'ó', + 'Ô' => 'ô', + 'Õ' => 'õ', + 'Ö' => 'ö', + 'Ø' => 'ø', + 'Ù' => 'ù', + 'Ú' => 'ú', + 'Û' => 'û', + 'Ü' => 'ü', + 'Ý' => 'ý', + 'Þ' => 'þ', + 'Ā' => 'ā', + 'Ă' => 'ă', + 'Ą' => 'ą', + 'Ć' => 'ć', + 'Ĉ' => 'ĉ', + 'Ċ' => 'ċ', + 'Č' => 'č', + 'Ď' => 'ď', + 'Đ' => 'đ', + 'Ē' => 'ē', + 'Ĕ' => 'ĕ', + 'Ė' => 'ė', + 'Ę' => 'ę', + 'Ě' => 'ě', + 'Ĝ' => 'ĝ', + 'Ğ' => 'ğ', + 'Ġ' => 'ġ', + 'Ģ' => 'ģ', + 'Ĥ' => 'ĥ', + 'Ħ' => 'ħ', + 'Ĩ' => 'ĩ', + 'Ī' => 'ī', + 'Ĭ' => 'ĭ', + 'Į' => 'į', + 'İ' => 'i̇', + 'IJ' => 'ij', + 'Ĵ' => 'ĵ', + 'Ķ' => 'ķ', + 'Ĺ' => 'ĺ', + 'Ļ' => 'ļ', + 'Ľ' => 'ľ', + 'Ŀ' => 'ŀ', + 'Ł' => 'ł', + 'Ń' => 'ń', + 'Ņ' => 'ņ', + 'Ň' => 'ň', + 'Ŋ' => 'ŋ', + 'Ō' => 'ō', + 'Ŏ' => 'ŏ', + 'Ő' => 'ő', + 'Œ' => 'œ', + 'Ŕ' => 'ŕ', + 'Ŗ' => 'ŗ', + 'Ř' => 'ř', + 'Ś' => 'ś', + 'Ŝ' => 'ŝ', + 'Ş' => 'ş', + 'Š' => 'š', + 'Ţ' => 'ţ', + 'Ť' => 'ť', + 'Ŧ' => 'ŧ', + 'Ũ' => 'ũ', + 'Ū' => 'ū', + 'Ŭ' => 'ŭ', + 'Ů' => 'ů', + 'Ű' => 'ű', + 'Ų' => 'ų', + 'Ŵ' => 'ŵ', + 'Ŷ' => 'ŷ', + 'Ÿ' => 'ÿ', + 'Ź' => 'ź', + 'Ż' => 'ż', + 'Ž' => 'ž', + 'Ɓ' => 'ɓ', + 'Ƃ' => 'ƃ', + 'Ƅ' => 'ƅ', + 'Ɔ' => 'ɔ', + 'Ƈ' => 'ƈ', + 'Ɖ' => 'ɖ', + 'Ɗ' => 'ɗ', + 'Ƌ' => 'ƌ', + 'Ǝ' => 'ǝ', + 'Ə' => 'ə', + 'Ɛ' => 'ɛ', + 'Ƒ' => 'ƒ', + 'Ɠ' => 'ɠ', + 'Ɣ' => 'ɣ', + 'Ɩ' => 'ɩ', + 'Ɨ' => 'ɨ', + 'Ƙ' => 'ƙ', + 'Ɯ' => 'ɯ', + 'Ɲ' => 'ɲ', + 'Ɵ' => 'ɵ', + 'Ơ' => 'ơ', + 'Ƣ' => 'ƣ', + 'Ƥ' => 'ƥ', + 'Ʀ' => 'ʀ', + 'Ƨ' => 'ƨ', + 'Ʃ' => 'ʃ', + 'Ƭ' => 'ƭ', + 'Ʈ' => 'ʈ', + 'Ư' => 'ư', + 'Ʊ' => 'ʊ', + 'Ʋ' => 'ʋ', + 'Ƴ' => 'ƴ', + 'Ƶ' => 'ƶ', + 'Ʒ' => 'ʒ', + 'Ƹ' => 'ƹ', + 'Ƽ' => 'ƽ', + 'DŽ' => 'dž', + 'Dž' => 'dž', + 'LJ' => 'lj', + 'Lj' => 'lj', + 'NJ' => 'nj', + 'Nj' => 'nj', + 'Ǎ' => 'ǎ', + 'Ǐ' => 'ǐ', + 'Ǒ' => 'ǒ', + 'Ǔ' => 'ǔ', + 'Ǖ' => 'ǖ', + 'Ǘ' => 'ǘ', + 'Ǚ' => 'ǚ', + 'Ǜ' => 'ǜ', + 'Ǟ' => 'ǟ', + 'Ǡ' => 'ǡ', + 'Ǣ' => 'ǣ', + 'Ǥ' => 'ǥ', + 'Ǧ' => 'ǧ', + 'Ǩ' => 'ǩ', + 'Ǫ' => 'ǫ', + 'Ǭ' => 'ǭ', + 'Ǯ' => 'ǯ', + 'DZ' => 'dz', + 'Dz' => 'dz', + 'Ǵ' => 'ǵ', + 'Ƕ' => 'ƕ', + 'Ƿ' => 'ƿ', + 'Ǹ' => 'ǹ', + 'Ǻ' => 'ǻ', + 'Ǽ' => 'ǽ', + 'Ǿ' => 'ǿ', + 'Ȁ' => 'ȁ', + 'Ȃ' => 'ȃ', + 'Ȅ' => 'ȅ', + 'Ȇ' => 'ȇ', + 'Ȉ' => 'ȉ', + 'Ȋ' => 'ȋ', + 'Ȍ' => 'ȍ', + 'Ȏ' => 'ȏ', + 'Ȑ' => 'ȑ', + 'Ȓ' => 'ȓ', + 'Ȕ' => 'ȕ', + 'Ȗ' => 'ȗ', + 'Ș' => 'ș', + 'Ț' => 'ț', + 'Ȝ' => 'ȝ', + 'Ȟ' => 'ȟ', + 'Ƞ' => 'ƞ', + 'Ȣ' => 'ȣ', + 'Ȥ' => 'ȥ', + 'Ȧ' => 'ȧ', + 'Ȩ' => 'ȩ', + 'Ȫ' => 'ȫ', + 'Ȭ' => 'ȭ', + 'Ȯ' => 'ȯ', + 'Ȱ' => 'ȱ', + 'Ȳ' => 'ȳ', + 'Ⱥ' => 'ⱥ', + 'Ȼ' => 'ȼ', + 'Ƚ' => 'ƚ', + 'Ⱦ' => 'ⱦ', + 'Ɂ' => 'ɂ', + 'Ƀ' => 'ƀ', + 'Ʉ' => 'ʉ', + 'Ʌ' => 'ʌ', + 'Ɇ' => 'ɇ', + 'Ɉ' => 'ɉ', + 'Ɋ' => 'ɋ', + 'Ɍ' => 'ɍ', + 'Ɏ' => 'ɏ', + 'Ͱ' => 'ͱ', + 'Ͳ' => 'ͳ', + 'Ͷ' => 'ͷ', + 'Ϳ' => 'ϳ', + 'Ά' => 'ά', + 'Έ' => 'έ', + 'Ή' => 'ή', + 'Ί' => 'ί', + 'Ό' => 'ό', + 'Ύ' => 'ύ', + 'Ώ' => 'ώ', + 'Α' => 'α', + 'Β' => 'β', + 'Γ' => 'γ', + 'Δ' => 'δ', + 'Ε' => 'ε', + 'Ζ' => 'ζ', + 'Η' => 'η', + 'Θ' => 'θ', + 'Ι' => 'ι', + 'Κ' => 'κ', + 'Λ' => 'λ', + 'Μ' => 'μ', + 'Ν' => 'ν', + 'Ξ' => 'ξ', + 'Ο' => 'ο', + 'Π' => 'π', + 'Ρ' => 'ρ', + 'Σ' => 'σ', + 'Τ' => 'τ', + 'Υ' => 'υ', + 'Φ' => 'φ', + 'Χ' => 'χ', + 'Ψ' => 'ψ', + 'Ω' => 'ω', + 'Ϊ' => 'ϊ', + 'Ϋ' => 'ϋ', + 'Ϗ' => 'ϗ', + 'Ϙ' => 'ϙ', + 'Ϛ' => 'ϛ', + 'Ϝ' => 'ϝ', + 'Ϟ' => 'ϟ', + 'Ϡ' => 'ϡ', + 'Ϣ' => 'ϣ', + 'Ϥ' => 'ϥ', + 'Ϧ' => 'ϧ', + 'Ϩ' => 'ϩ', + 'Ϫ' => 'ϫ', + 'Ϭ' => 'ϭ', + 'Ϯ' => 'ϯ', + 'ϴ' => 'θ', + 'Ϸ' => 'ϸ', + 'Ϲ' => 'ϲ', + 'Ϻ' => 'ϻ', + 'Ͻ' => 'ͻ', + 'Ͼ' => 'ͼ', + 'Ͽ' => 'ͽ', + 'Ѐ' => 'ѐ', + 'Ё' => 'ё', + 'Ђ' => 'ђ', + 'Ѓ' => 'ѓ', + 'Є' => 'є', + 'Ѕ' => 'ѕ', + 'І' => 'і', + 'Ї' => 'ї', + 'Ј' => 'ј', + 'Љ' => 'љ', + 'Њ' => 'њ', + 'Ћ' => 'ћ', + 'Ќ' => 'ќ', + 'Ѝ' => 'ѝ', + 'Ў' => 'ў', + 'Џ' => 'џ', + 'А' => 'а', + 'Б' => 'б', + 'В' => 'в', + 'Г' => 'г', + 'Д' => 'д', + 'Е' => 'е', + 'Ж' => 'ж', + 'З' => 'з', + 'И' => 'и', + 'Й' => 'й', + 'К' => 'к', + 'Л' => 'л', + 'М' => 'м', + 'Н' => 'н', + 'О' => 'о', + 'П' => 'п', + 'Р' => 'р', + 'С' => 'с', + 'Т' => 'т', + 'У' => 'у', + 'Ф' => 'ф', + 'Х' => 'х', + 'Ц' => 'ц', + 'Ч' => 'ч', + 'Ш' => 'ш', + 'Щ' => 'щ', + 'Ъ' => 'ъ', + 'Ы' => 'ы', + 'Ь' => 'ь', + 'Э' => 'э', + 'Ю' => 'ю', + 'Я' => 'я', + 'Ѡ' => 'ѡ', + 'Ѣ' => 'ѣ', + 'Ѥ' => 'ѥ', + 'Ѧ' => 'ѧ', + 'Ѩ' => 'ѩ', + 'Ѫ' => 'ѫ', + 'Ѭ' => 'ѭ', + 'Ѯ' => 'ѯ', + 'Ѱ' => 'ѱ', + 'Ѳ' => 'ѳ', + 'Ѵ' => 'ѵ', + 'Ѷ' => 'ѷ', + 'Ѹ' => 'ѹ', + 'Ѻ' => 'ѻ', + 'Ѽ' => 'ѽ', + 'Ѿ' => 'ѿ', + 'Ҁ' => 'ҁ', + 'Ҋ' => 'ҋ', + 'Ҍ' => 'ҍ', + 'Ҏ' => 'ҏ', + 'Ґ' => 'ґ', + 'Ғ' => 'ғ', + 'Ҕ' => 'ҕ', + 'Җ' => 'җ', + 'Ҙ' => 'ҙ', + 'Қ' => 'қ', + 'Ҝ' => 'ҝ', + 'Ҟ' => 'ҟ', + 'Ҡ' => 'ҡ', + 'Ң' => 'ң', + 'Ҥ' => 'ҥ', + 'Ҧ' => 'ҧ', + 'Ҩ' => 'ҩ', + 'Ҫ' => 'ҫ', + 'Ҭ' => 'ҭ', + 'Ү' => 'ү', + 'Ұ' => 'ұ', + 'Ҳ' => 'ҳ', + 'Ҵ' => 'ҵ', + 'Ҷ' => 'ҷ', + 'Ҹ' => 'ҹ', + 'Һ' => 'һ', + 'Ҽ' => 'ҽ', + 'Ҿ' => 'ҿ', + 'Ӏ' => 'ӏ', + 'Ӂ' => 'ӂ', + 'Ӄ' => 'ӄ', + 'Ӆ' => 'ӆ', + 'Ӈ' => 'ӈ', + 'Ӊ' => 'ӊ', + 'Ӌ' => 'ӌ', + 'Ӎ' => 'ӎ', + 'Ӑ' => 'ӑ', + 'Ӓ' => 'ӓ', + 'Ӕ' => 'ӕ', + 'Ӗ' => 'ӗ', + 'Ә' => 'ә', + 'Ӛ' => 'ӛ', + 'Ӝ' => 'ӝ', + 'Ӟ' => 'ӟ', + 'Ӡ' => 'ӡ', + 'Ӣ' => 'ӣ', + 'Ӥ' => 'ӥ', + 'Ӧ' => 'ӧ', + 'Ө' => 'ө', + 'Ӫ' => 'ӫ', + 'Ӭ' => 'ӭ', + 'Ӯ' => 'ӯ', + 'Ӱ' => 'ӱ', + 'Ӳ' => 'ӳ', + 'Ӵ' => 'ӵ', + 'Ӷ' => 'ӷ', + 'Ӹ' => 'ӹ', + 'Ӻ' => 'ӻ', + 'Ӽ' => 'ӽ', + 'Ӿ' => 'ӿ', + 'Ԁ' => 'ԁ', + 'Ԃ' => 'ԃ', + 'Ԅ' => 'ԅ', + 'Ԇ' => 'ԇ', + 'Ԉ' => 'ԉ', + 'Ԋ' => 'ԋ', + 'Ԍ' => 'ԍ', + 'Ԏ' => 'ԏ', + 'Ԑ' => 'ԑ', + 'Ԓ' => 'ԓ', + 'Ԕ' => 'ԕ', + 'Ԗ' => 'ԗ', + 'Ԙ' => 'ԙ', + 'Ԛ' => 'ԛ', + 'Ԝ' => 'ԝ', + 'Ԟ' => 'ԟ', + 'Ԡ' => 'ԡ', + 'Ԣ' => 'ԣ', + 'Ԥ' => 'ԥ', + 'Ԧ' => 'ԧ', + 'Ԩ' => 'ԩ', + 'Ԫ' => 'ԫ', + 'Ԭ' => 'ԭ', + 'Ԯ' => 'ԯ', + 'Ա' => 'ա', + 'Բ' => 'բ', + 'Գ' => 'գ', + 'Դ' => 'դ', + 'Ե' => 'ե', + 'Զ' => 'զ', + 'Է' => 'է', + 'Ը' => 'ը', + 'Թ' => 'թ', + 'Ժ' => 'ժ', + 'Ի' => 'ի', + 'Լ' => 'լ', + 'Խ' => 'խ', + 'Ծ' => 'ծ', + 'Կ' => 'կ', + 'Հ' => 'հ', + 'Ձ' => 'ձ', + 'Ղ' => 'ղ', + 'Ճ' => 'ճ', + 'Մ' => 'մ', + 'Յ' => 'յ', + 'Ն' => 'ն', + 'Շ' => 'շ', + 'Ո' => 'ո', + 'Չ' => 'չ', + 'Պ' => 'պ', + 'Ջ' => 'ջ', + 'Ռ' => 'ռ', + 'Ս' => 'ս', + 'Վ' => 'վ', + 'Տ' => 'տ', + 'Ր' => 'ր', + 'Ց' => 'ց', + 'Ւ' => 'ւ', + 'Փ' => 'փ', + 'Ք' => 'ք', + 'Օ' => 'օ', + 'Ֆ' => 'ֆ', + 'Ⴀ' => 'ⴀ', + 'Ⴁ' => 'ⴁ', + 'Ⴂ' => 'ⴂ', + 'Ⴃ' => 'ⴃ', + 'Ⴄ' => 'ⴄ', + 'Ⴅ' => 'ⴅ', + 'Ⴆ' => 'ⴆ', + 'Ⴇ' => 'ⴇ', + 'Ⴈ' => 'ⴈ', + 'Ⴉ' => 'ⴉ', + 'Ⴊ' => 'ⴊ', + 'Ⴋ' => 'ⴋ', + 'Ⴌ' => 'ⴌ', + 'Ⴍ' => 'ⴍ', + 'Ⴎ' => 'ⴎ', + 'Ⴏ' => 'ⴏ', + 'Ⴐ' => 'ⴐ', + 'Ⴑ' => 'ⴑ', + 'Ⴒ' => 'ⴒ', + 'Ⴓ' => 'ⴓ', + 'Ⴔ' => 'ⴔ', + 'Ⴕ' => 'ⴕ', + 'Ⴖ' => 'ⴖ', + 'Ⴗ' => 'ⴗ', + 'Ⴘ' => 'ⴘ', + 'Ⴙ' => 'ⴙ', + 'Ⴚ' => 'ⴚ', + 'Ⴛ' => 'ⴛ', + 'Ⴜ' => 'ⴜ', + 'Ⴝ' => 'ⴝ', + 'Ⴞ' => 'ⴞ', + 'Ⴟ' => 'ⴟ', + 'Ⴠ' => 'ⴠ', + 'Ⴡ' => 'ⴡ', + 'Ⴢ' => 'ⴢ', + 'Ⴣ' => 'ⴣ', + 'Ⴤ' => 'ⴤ', + 'Ⴥ' => 'ⴥ', + 'Ⴧ' => 'ⴧ', + 'Ⴭ' => 'ⴭ', + 'Ꭰ' => 'ꭰ', + 'Ꭱ' => 'ꭱ', + 'Ꭲ' => 'ꭲ', + 'Ꭳ' => 'ꭳ', + 'Ꭴ' => 'ꭴ', + 'Ꭵ' => 'ꭵ', + 'Ꭶ' => 'ꭶ', + 'Ꭷ' => 'ꭷ', + 'Ꭸ' => 'ꭸ', + 'Ꭹ' => 'ꭹ', + 'Ꭺ' => 'ꭺ', + 'Ꭻ' => 'ꭻ', + 'Ꭼ' => 'ꭼ', + 'Ꭽ' => 'ꭽ', + 'Ꭾ' => 'ꭾ', + 'Ꭿ' => 'ꭿ', + 'Ꮀ' => 'ꮀ', + 'Ꮁ' => 'ꮁ', + 'Ꮂ' => 'ꮂ', + 'Ꮃ' => 'ꮃ', + 'Ꮄ' => 'ꮄ', + 'Ꮅ' => 'ꮅ', + 'Ꮆ' => 'ꮆ', + 'Ꮇ' => 'ꮇ', + 'Ꮈ' => 'ꮈ', + 'Ꮉ' => 'ꮉ', + 'Ꮊ' => 'ꮊ', + 'Ꮋ' => 'ꮋ', + 'Ꮌ' => 'ꮌ', + 'Ꮍ' => 'ꮍ', + 'Ꮎ' => 'ꮎ', + 'Ꮏ' => 'ꮏ', + 'Ꮐ' => 'ꮐ', + 'Ꮑ' => 'ꮑ', + 'Ꮒ' => 'ꮒ', + 'Ꮓ' => 'ꮓ', + 'Ꮔ' => 'ꮔ', + 'Ꮕ' => 'ꮕ', + 'Ꮖ' => 'ꮖ', + 'Ꮗ' => 'ꮗ', + 'Ꮘ' => 'ꮘ', + 'Ꮙ' => 'ꮙ', + 'Ꮚ' => 'ꮚ', + 'Ꮛ' => 'ꮛ', + 'Ꮜ' => 'ꮜ', + 'Ꮝ' => 'ꮝ', + 'Ꮞ' => 'ꮞ', + 'Ꮟ' => 'ꮟ', + 'Ꮠ' => 'ꮠ', + 'Ꮡ' => 'ꮡ', + 'Ꮢ' => 'ꮢ', + 'Ꮣ' => 'ꮣ', + 'Ꮤ' => 'ꮤ', + 'Ꮥ' => 'ꮥ', + 'Ꮦ' => 'ꮦ', + 'Ꮧ' => 'ꮧ', + 'Ꮨ' => 'ꮨ', + 'Ꮩ' => 'ꮩ', + 'Ꮪ' => 'ꮪ', + 'Ꮫ' => 'ꮫ', + 'Ꮬ' => 'ꮬ', + 'Ꮭ' => 'ꮭ', + 'Ꮮ' => 'ꮮ', + 'Ꮯ' => 'ꮯ', + 'Ꮰ' => 'ꮰ', + 'Ꮱ' => 'ꮱ', + 'Ꮲ' => 'ꮲ', + 'Ꮳ' => 'ꮳ', + 'Ꮴ' => 'ꮴ', + 'Ꮵ' => 'ꮵ', + 'Ꮶ' => 'ꮶ', + 'Ꮷ' => 'ꮷ', + 'Ꮸ' => 'ꮸ', + 'Ꮹ' => 'ꮹ', + 'Ꮺ' => 'ꮺ', + 'Ꮻ' => 'ꮻ', + 'Ꮼ' => 'ꮼ', + 'Ꮽ' => 'ꮽ', + 'Ꮾ' => 'ꮾ', + 'Ꮿ' => 'ꮿ', + 'Ᏸ' => 'ᏸ', + 'Ᏹ' => 'ᏹ', + 'Ᏺ' => 'ᏺ', + 'Ᏻ' => 'ᏻ', + 'Ᏼ' => 'ᏼ', + 'Ᏽ' => 'ᏽ', + 'Ა' => 'ა', + 'Ბ' => 'ბ', + 'Გ' => 'გ', + 'Დ' => 'დ', + 'Ე' => 'ე', + 'Ვ' => 'ვ', + 'Ზ' => 'ზ', + 'Თ' => 'თ', + 'Ი' => 'ი', + 'Კ' => 'კ', + 'Ლ' => 'ლ', + 'Მ' => 'მ', + 'Ნ' => 'ნ', + 'Ო' => 'ო', + 'Პ' => 'პ', + 'Ჟ' => 'ჟ', + 'Რ' => 'რ', + 'Ს' => 'ს', + 'Ტ' => 'ტ', + 'Უ' => 'უ', + 'Ფ' => 'ფ', + 'Ქ' => 'ქ', + 'Ღ' => 'ღ', + 'Ყ' => 'ყ', + 'Შ' => 'შ', + 'Ჩ' => 'ჩ', + 'Ც' => 'ც', + 'Ძ' => 'ძ', + 'Წ' => 'წ', + 'Ჭ' => 'ჭ', + 'Ხ' => 'ხ', + 'Ჯ' => 'ჯ', + 'Ჰ' => 'ჰ', + 'Ჱ' => 'ჱ', + 'Ჲ' => 'ჲ', + 'Ჳ' => 'ჳ', + 'Ჴ' => 'ჴ', + 'Ჵ' => 'ჵ', + 'Ჶ' => 'ჶ', + 'Ჷ' => 'ჷ', + 'Ჸ' => 'ჸ', + 'Ჹ' => 'ჹ', + 'Ჺ' => 'ჺ', + 'Ჽ' => 'ჽ', + 'Ჾ' => 'ჾ', + 'Ჿ' => 'ჿ', + 'Ḁ' => 'ḁ', + 'Ḃ' => 'ḃ', + 'Ḅ' => 'ḅ', + 'Ḇ' => 'ḇ', + 'Ḉ' => 'ḉ', + 'Ḋ' => 'ḋ', + 'Ḍ' => 'ḍ', + 'Ḏ' => 'ḏ', + 'Ḑ' => 'ḑ', + 'Ḓ' => 'ḓ', + 'Ḕ' => 'ḕ', + 'Ḗ' => 'ḗ', + 'Ḙ' => 'ḙ', + 'Ḛ' => 'ḛ', + 'Ḝ' => 'ḝ', + 'Ḟ' => 'ḟ', + 'Ḡ' => 'ḡ', + 'Ḣ' => 'ḣ', + 'Ḥ' => 'ḥ', + 'Ḧ' => 'ḧ', + 'Ḩ' => 'ḩ', + 'Ḫ' => 'ḫ', + 'Ḭ' => 'ḭ', + 'Ḯ' => 'ḯ', + 'Ḱ' => 'ḱ', + 'Ḳ' => 'ḳ', + 'Ḵ' => 'ḵ', + 'Ḷ' => 'ḷ', + 'Ḹ' => 'ḹ', + 'Ḻ' => 'ḻ', + 'Ḽ' => 'ḽ', + 'Ḿ' => 'ḿ', + 'Ṁ' => 'ṁ', + 'Ṃ' => 'ṃ', + 'Ṅ' => 'ṅ', + 'Ṇ' => 'ṇ', + 'Ṉ' => 'ṉ', + 'Ṋ' => 'ṋ', + 'Ṍ' => 'ṍ', + 'Ṏ' => 'ṏ', + 'Ṑ' => 'ṑ', + 'Ṓ' => 'ṓ', + 'Ṕ' => 'ṕ', + 'Ṗ' => 'ṗ', + 'Ṙ' => 'ṙ', + 'Ṛ' => 'ṛ', + 'Ṝ' => 'ṝ', + 'Ṟ' => 'ṟ', + 'Ṡ' => 'ṡ', + 'Ṣ' => 'ṣ', + 'Ṥ' => 'ṥ', + 'Ṧ' => 'ṧ', + 'Ṩ' => 'ṩ', + 'Ṫ' => 'ṫ', + 'Ṭ' => 'ṭ', + 'Ṯ' => 'ṯ', + 'Ṱ' => 'ṱ', + 'Ṳ' => 'ṳ', + 'Ṵ' => 'ṵ', + 'Ṷ' => 'ṷ', + 'Ṹ' => 'ṹ', + 'Ṻ' => 'ṻ', + 'Ṽ' => 'ṽ', + 'Ṿ' => 'ṿ', + 'Ẁ' => 'ẁ', + 'Ẃ' => 'ẃ', + 'Ẅ' => 'ẅ', + 'Ẇ' => 'ẇ', + 'Ẉ' => 'ẉ', + 'Ẋ' => 'ẋ', + 'Ẍ' => 'ẍ', + 'Ẏ' => 'ẏ', + 'Ẑ' => 'ẑ', + 'Ẓ' => 'ẓ', + 'Ẕ' => 'ẕ', + 'ẞ' => 'ß', + 'Ạ' => 'ạ', + 'Ả' => 'ả', + 'Ấ' => 'ấ', + 'Ầ' => 'ầ', + 'Ẩ' => 'ẩ', + 'Ẫ' => 'ẫ', + 'Ậ' => 'ậ', + 'Ắ' => 'ắ', + 'Ằ' => 'ằ', + 'Ẳ' => 'ẳ', + 'Ẵ' => 'ẵ', + 'Ặ' => 'ặ', + 'Ẹ' => 'ẹ', + 'Ẻ' => 'ẻ', + 'Ẽ' => 'ẽ', + 'Ế' => 'ế', + 'Ề' => 'ề', + 'Ể' => 'ể', + 'Ễ' => 'ễ', + 'Ệ' => 'ệ', + 'Ỉ' => 'ỉ', + 'Ị' => 'ị', + 'Ọ' => 'ọ', + 'Ỏ' => 'ỏ', + 'Ố' => 'ố', + 'Ồ' => 'ồ', + 'Ổ' => 'ổ', + 'Ỗ' => 'ỗ', + 'Ộ' => 'ộ', + 'Ớ' => 'ớ', + 'Ờ' => 'ờ', + 'Ở' => 'ở', + 'Ỡ' => 'ỡ', + 'Ợ' => 'ợ', + 'Ụ' => 'ụ', + 'Ủ' => 'ủ', + 'Ứ' => 'ứ', + 'Ừ' => 'ừ', + 'Ử' => 'ử', + 'Ữ' => 'ữ', + 'Ự' => 'ự', + 'Ỳ' => 'ỳ', + 'Ỵ' => 'ỵ', + 'Ỷ' => 'ỷ', + 'Ỹ' => 'ỹ', + 'Ỻ' => 'ỻ', + 'Ỽ' => 'ỽ', + 'Ỿ' => 'ỿ', + 'Ἀ' => 'ἀ', + 'Ἁ' => 'ἁ', + 'Ἂ' => 'ἂ', + 'Ἃ' => 'ἃ', + 'Ἄ' => 'ἄ', + 'Ἅ' => 'ἅ', + 'Ἆ' => 'ἆ', + 'Ἇ' => 'ἇ', + 'Ἐ' => 'ἐ', + 'Ἑ' => 'ἑ', + 'Ἒ' => 'ἒ', + 'Ἓ' => 'ἓ', + 'Ἔ' => 'ἔ', + 'Ἕ' => 'ἕ', + 'Ἠ' => 'ἠ', + 'Ἡ' => 'ἡ', + 'Ἢ' => 'ἢ', + 'Ἣ' => 'ἣ', + 'Ἤ' => 'ἤ', + 'Ἥ' => 'ἥ', + 'Ἦ' => 'ἦ', + 'Ἧ' => 'ἧ', + 'Ἰ' => 'ἰ', + 'Ἱ' => 'ἱ', + 'Ἲ' => 'ἲ', + 'Ἳ' => 'ἳ', + 'Ἴ' => 'ἴ', + 'Ἵ' => 'ἵ', + 'Ἶ' => 'ἶ', + 'Ἷ' => 'ἷ', + 'Ὀ' => 'ὀ', + 'Ὁ' => 'ὁ', + 'Ὂ' => 'ὂ', + 'Ὃ' => 'ὃ', + 'Ὄ' => 'ὄ', + 'Ὅ' => 'ὅ', + 'Ὑ' => 'ὑ', + 'Ὓ' => 'ὓ', + 'Ὕ' => 'ὕ', + 'Ὗ' => 'ὗ', + 'Ὠ' => 'ὠ', + 'Ὡ' => 'ὡ', + 'Ὢ' => 'ὢ', + 'Ὣ' => 'ὣ', + 'Ὤ' => 'ὤ', + 'Ὥ' => 'ὥ', + 'Ὦ' => 'ὦ', + 'Ὧ' => 'ὧ', + 'ᾈ' => 'ᾀ', + 'ᾉ' => 'ᾁ', + 'ᾊ' => 'ᾂ', + 'ᾋ' => 'ᾃ', + 'ᾌ' => 'ᾄ', + 'ᾍ' => 'ᾅ', + 'ᾎ' => 'ᾆ', + 'ᾏ' => 'ᾇ', + 'ᾘ' => 'ᾐ', + 'ᾙ' => 'ᾑ', + 'ᾚ' => 'ᾒ', + 'ᾛ' => 'ᾓ', + 'ᾜ' => 'ᾔ', + 'ᾝ' => 'ᾕ', + 'ᾞ' => 'ᾖ', + 'ᾟ' => 'ᾗ', + 'ᾨ' => 'ᾠ', + 'ᾩ' => 'ᾡ', + 'ᾪ' => 'ᾢ', + 'ᾫ' => 'ᾣ', + 'ᾬ' => 'ᾤ', + 'ᾭ' => 'ᾥ', + 'ᾮ' => 'ᾦ', + 'ᾯ' => 'ᾧ', + 'Ᾰ' => 'ᾰ', + 'Ᾱ' => 'ᾱ', + 'Ὰ' => 'ὰ', + 'Ά' => 'ά', + 'ᾼ' => 'ᾳ', + 'Ὲ' => 'ὲ', + 'Έ' => 'έ', + 'Ὴ' => 'ὴ', + 'Ή' => 'ή', + 'ῌ' => 'ῃ', + 'Ῐ' => 'ῐ', + 'Ῑ' => 'ῑ', + 'Ὶ' => 'ὶ', + 'Ί' => 'ί', + 'Ῠ' => 'ῠ', + 'Ῡ' => 'ῡ', + 'Ὺ' => 'ὺ', + 'Ύ' => 'ύ', + 'Ῥ' => 'ῥ', + 'Ὸ' => 'ὸ', + 'Ό' => 'ό', + 'Ὼ' => 'ὼ', + 'Ώ' => 'ώ', + 'ῼ' => 'ῳ', + 'Ω' => 'ω', + 'K' => 'k', + 'Å' => 'å', + 'Ⅎ' => 'ⅎ', + 'Ⅰ' => 'ⅰ', + 'Ⅱ' => 'ⅱ', + 'Ⅲ' => 'ⅲ', + 'Ⅳ' => 'ⅳ', + 'Ⅴ' => 'ⅴ', + 'Ⅵ' => 'ⅵ', + 'Ⅶ' => 'ⅶ', + 'Ⅷ' => 'ⅷ', + 'Ⅸ' => 'ⅸ', + 'Ⅹ' => 'ⅹ', + 'Ⅺ' => 'ⅺ', + 'Ⅻ' => 'ⅻ', + 'Ⅼ' => 'ⅼ', + 'Ⅽ' => 'ⅽ', + 'Ⅾ' => 'ⅾ', + 'Ⅿ' => 'ⅿ', + 'Ↄ' => 'ↄ', + 'Ⓐ' => 'ⓐ', + 'Ⓑ' => 'ⓑ', + 'Ⓒ' => 'ⓒ', + 'Ⓓ' => 'ⓓ', + 'Ⓔ' => 'ⓔ', + 'Ⓕ' => 'ⓕ', + 'Ⓖ' => 'ⓖ', + 'Ⓗ' => 'ⓗ', + 'Ⓘ' => 'ⓘ', + 'Ⓙ' => 'ⓙ', + 'Ⓚ' => 'ⓚ', + 'Ⓛ' => 'ⓛ', + 'Ⓜ' => 'ⓜ', + 'Ⓝ' => 'ⓝ', + 'Ⓞ' => 'ⓞ', + 'Ⓟ' => 'ⓟ', + 'Ⓠ' => 'ⓠ', + 'Ⓡ' => 'ⓡ', + 'Ⓢ' => 'ⓢ', + 'Ⓣ' => 'ⓣ', + 'Ⓤ' => 'ⓤ', + 'Ⓥ' => 'ⓥ', + 'Ⓦ' => 'ⓦ', + 'Ⓧ' => 'ⓧ', + 'Ⓨ' => 'ⓨ', + 'Ⓩ' => 'ⓩ', + 'Ⰰ' => 'ⰰ', + 'Ⰱ' => 'ⰱ', + 'Ⰲ' => 'ⰲ', + 'Ⰳ' => 'ⰳ', + 'Ⰴ' => 'ⰴ', + 'Ⰵ' => 'ⰵ', + 'Ⰶ' => 'ⰶ', + 'Ⰷ' => 'ⰷ', + 'Ⰸ' => 'ⰸ', + 'Ⰹ' => 'ⰹ', + 'Ⰺ' => 'ⰺ', + 'Ⰻ' => 'ⰻ', + 'Ⰼ' => 'ⰼ', + 'Ⰽ' => 'ⰽ', + 'Ⰾ' => 'ⰾ', + 'Ⰿ' => 'ⰿ', + 'Ⱀ' => 'ⱀ', + 'Ⱁ' => 'ⱁ', + 'Ⱂ' => 'ⱂ', + 'Ⱃ' => 'ⱃ', + 'Ⱄ' => 'ⱄ', + 'Ⱅ' => 'ⱅ', + 'Ⱆ' => 'ⱆ', + 'Ⱇ' => 'ⱇ', + 'Ⱈ' => 'ⱈ', + 'Ⱉ' => 'ⱉ', + 'Ⱊ' => 'ⱊ', + 'Ⱋ' => 'ⱋ', + 'Ⱌ' => 'ⱌ', + 'Ⱍ' => 'ⱍ', + 'Ⱎ' => 'ⱎ', + 'Ⱏ' => 'ⱏ', + 'Ⱐ' => 'ⱐ', + 'Ⱑ' => 'ⱑ', + 'Ⱒ' => 'ⱒ', + 'Ⱓ' => 'ⱓ', + 'Ⱔ' => 'ⱔ', + 'Ⱕ' => 'ⱕ', + 'Ⱖ' => 'ⱖ', + 'Ⱗ' => 'ⱗ', + 'Ⱘ' => 'ⱘ', + 'Ⱙ' => 'ⱙ', + 'Ⱚ' => 'ⱚ', + 'Ⱛ' => 'ⱛ', + 'Ⱜ' => 'ⱜ', + 'Ⱝ' => 'ⱝ', + 'Ⱞ' => 'ⱞ', + 'Ⱡ' => 'ⱡ', + 'Ɫ' => 'ɫ', + 'Ᵽ' => 'ᵽ', + 'Ɽ' => 'ɽ', + 'Ⱨ' => 'ⱨ', + 'Ⱪ' => 'ⱪ', + 'Ⱬ' => 'ⱬ', + 'Ɑ' => 'ɑ', + 'Ɱ' => 'ɱ', + 'Ɐ' => 'ɐ', + 'Ɒ' => 'ɒ', + 'Ⱳ' => 'ⱳ', + 'Ⱶ' => 'ⱶ', + 'Ȿ' => 'ȿ', + 'Ɀ' => 'ɀ', + 'Ⲁ' => 'ⲁ', + 'Ⲃ' => 'ⲃ', + 'Ⲅ' => 'ⲅ', + 'Ⲇ' => 'ⲇ', + 'Ⲉ' => 'ⲉ', + 'Ⲋ' => 'ⲋ', + 'Ⲍ' => 'ⲍ', + 'Ⲏ' => 'ⲏ', + 'Ⲑ' => 'ⲑ', + 'Ⲓ' => 'ⲓ', + 'Ⲕ' => 'ⲕ', + 'Ⲗ' => 'ⲗ', + 'Ⲙ' => 'ⲙ', + 'Ⲛ' => 'ⲛ', + 'Ⲝ' => 'ⲝ', + 'Ⲟ' => 'ⲟ', + 'Ⲡ' => 'ⲡ', + 'Ⲣ' => 'ⲣ', + 'Ⲥ' => 'ⲥ', + 'Ⲧ' => 'ⲧ', + 'Ⲩ' => 'ⲩ', + 'Ⲫ' => 'ⲫ', + 'Ⲭ' => 'ⲭ', + 'Ⲯ' => 'ⲯ', + 'Ⲱ' => 'ⲱ', + 'Ⲳ' => 'ⲳ', + 'Ⲵ' => 'ⲵ', + 'Ⲷ' => 'ⲷ', + 'Ⲹ' => 'ⲹ', + 'Ⲻ' => 'ⲻ', + 'Ⲽ' => 'ⲽ', + 'Ⲿ' => 'ⲿ', + 'Ⳁ' => 'ⳁ', + 'Ⳃ' => 'ⳃ', + 'Ⳅ' => 'ⳅ', + 'Ⳇ' => 'ⳇ', + 'Ⳉ' => 'ⳉ', + 'Ⳋ' => 'ⳋ', + 'Ⳍ' => 'ⳍ', + 'Ⳏ' => 'ⳏ', + 'Ⳑ' => 'ⳑ', + 'Ⳓ' => 'ⳓ', + 'Ⳕ' => 'ⳕ', + 'Ⳗ' => 'ⳗ', + 'Ⳙ' => 'ⳙ', + 'Ⳛ' => 'ⳛ', + 'Ⳝ' => 'ⳝ', + 'Ⳟ' => 'ⳟ', + 'Ⳡ' => 'ⳡ', + 'Ⳣ' => 'ⳣ', + 'Ⳬ' => 'ⳬ', + 'Ⳮ' => 'ⳮ', + 'Ⳳ' => 'ⳳ', + 'Ꙁ' => 'ꙁ', + 'Ꙃ' => 'ꙃ', + 'Ꙅ' => 'ꙅ', + 'Ꙇ' => 'ꙇ', + 'Ꙉ' => 'ꙉ', + 'Ꙋ' => 'ꙋ', + 'Ꙍ' => 'ꙍ', + 'Ꙏ' => 'ꙏ', + 'Ꙑ' => 'ꙑ', + 'Ꙓ' => 'ꙓ', + 'Ꙕ' => 'ꙕ', + 'Ꙗ' => 'ꙗ', + 'Ꙙ' => 'ꙙ', + 'Ꙛ' => 'ꙛ', + 'Ꙝ' => 'ꙝ', + 'Ꙟ' => 'ꙟ', + 'Ꙡ' => 'ꙡ', + 'Ꙣ' => 'ꙣ', + 'Ꙥ' => 'ꙥ', + 'Ꙧ' => 'ꙧ', + 'Ꙩ' => 'ꙩ', + 'Ꙫ' => 'ꙫ', + 'Ꙭ' => 'ꙭ', + 'Ꚁ' => 'ꚁ', + 'Ꚃ' => 'ꚃ', + 'Ꚅ' => 'ꚅ', + 'Ꚇ' => 'ꚇ', + 'Ꚉ' => 'ꚉ', + 'Ꚋ' => 'ꚋ', + 'Ꚍ' => 'ꚍ', + 'Ꚏ' => 'ꚏ', + 'Ꚑ' => 'ꚑ', + 'Ꚓ' => 'ꚓ', + 'Ꚕ' => 'ꚕ', + 'Ꚗ' => 'ꚗ', + 'Ꚙ' => 'ꚙ', + 'Ꚛ' => 'ꚛ', + 'Ꜣ' => 'ꜣ', + 'Ꜥ' => 'ꜥ', + 'Ꜧ' => 'ꜧ', + 'Ꜩ' => 'ꜩ', + 'Ꜫ' => 'ꜫ', + 'Ꜭ' => 'ꜭ', + 'Ꜯ' => 'ꜯ', + 'Ꜳ' => 'ꜳ', + 'Ꜵ' => 'ꜵ', + 'Ꜷ' => 'ꜷ', + 'Ꜹ' => 'ꜹ', + 'Ꜻ' => 'ꜻ', + 'Ꜽ' => 'ꜽ', + 'Ꜿ' => 'ꜿ', + 'Ꝁ' => 'ꝁ', + 'Ꝃ' => 'ꝃ', + 'Ꝅ' => 'ꝅ', + 'Ꝇ' => 'ꝇ', + 'Ꝉ' => 'ꝉ', + 'Ꝋ' => 'ꝋ', + 'Ꝍ' => 'ꝍ', + 'Ꝏ' => 'ꝏ', + 'Ꝑ' => 'ꝑ', + 'Ꝓ' => 'ꝓ', + 'Ꝕ' => 'ꝕ', + 'Ꝗ' => 'ꝗ', + 'Ꝙ' => 'ꝙ', + 'Ꝛ' => 'ꝛ', + 'Ꝝ' => 'ꝝ', + 'Ꝟ' => 'ꝟ', + 'Ꝡ' => 'ꝡ', + 'Ꝣ' => 'ꝣ', + 'Ꝥ' => 'ꝥ', + 'Ꝧ' => 'ꝧ', + 'Ꝩ' => 'ꝩ', + 'Ꝫ' => 'ꝫ', + 'Ꝭ' => 'ꝭ', + 'Ꝯ' => 'ꝯ', + 'Ꝺ' => 'ꝺ', + 'Ꝼ' => 'ꝼ', + 'Ᵹ' => 'ᵹ', + 'Ꝿ' => 'ꝿ', + 'Ꞁ' => 'ꞁ', + 'Ꞃ' => 'ꞃ', + 'Ꞅ' => 'ꞅ', + 'Ꞇ' => 'ꞇ', + 'Ꞌ' => 'ꞌ', + 'Ɥ' => 'ɥ', + 'Ꞑ' => 'ꞑ', + 'Ꞓ' => 'ꞓ', + 'Ꞗ' => 'ꞗ', + 'Ꞙ' => 'ꞙ', + 'Ꞛ' => 'ꞛ', + 'Ꞝ' => 'ꞝ', + 'Ꞟ' => 'ꞟ', + 'Ꞡ' => 'ꞡ', + 'Ꞣ' => 'ꞣ', + 'Ꞥ' => 'ꞥ', + 'Ꞧ' => 'ꞧ', + 'Ꞩ' => 'ꞩ', + 'Ɦ' => 'ɦ', + 'Ɜ' => 'ɜ', + 'Ɡ' => 'ɡ', + 'Ɬ' => 'ɬ', + 'Ɪ' => 'ɪ', + 'Ʞ' => 'ʞ', + 'Ʇ' => 'ʇ', + 'Ʝ' => 'ʝ', + 'Ꭓ' => 'ꭓ', + 'Ꞵ' => 'ꞵ', + 'Ꞷ' => 'ꞷ', + 'Ꞹ' => 'ꞹ', + 'Ꞻ' => 'ꞻ', + 'Ꞽ' => 'ꞽ', + 'Ꞿ' => 'ꞿ', + 'Ꟃ' => 'ꟃ', + 'Ꞔ' => 'ꞔ', + 'Ʂ' => 'ʂ', + 'Ᶎ' => 'ᶎ', + 'Ꟈ' => 'ꟈ', + 'Ꟊ' => 'ꟊ', + 'Ꟶ' => 'ꟶ', + 'A' => 'a', + 'B' => 'b', + 'C' => 'c', + 'D' => 'd', + 'E' => 'e', + 'F' => 'f', + 'G' => 'g', + 'H' => 'h', + 'I' => 'i', + 'J' => 'j', + 'K' => 'k', + 'L' => 'l', + 'M' => 'm', + 'N' => 'n', + 'O' => 'o', + 'P' => 'p', + 'Q' => 'q', + 'R' => 'r', + 'S' => 's', + 'T' => 't', + 'U' => 'u', + 'V' => 'v', + 'W' => 'w', + 'X' => 'x', + 'Y' => 'y', + 'Z' => 'z', + '𐐀' => '𐐨', + '𐐁' => '𐐩', + '𐐂' => '𐐪', + '𐐃' => '𐐫', + '𐐄' => '𐐬', + '𐐅' => '𐐭', + '𐐆' => '𐐮', + '𐐇' => '𐐯', + '𐐈' => '𐐰', + '𐐉' => '𐐱', + '𐐊' => '𐐲', + '𐐋' => '𐐳', + '𐐌' => '𐐴', + '𐐍' => '𐐵', + '𐐎' => '𐐶', + '𐐏' => '𐐷', + '𐐐' => '𐐸', + '𐐑' => '𐐹', + '𐐒' => '𐐺', + '𐐓' => '𐐻', + '𐐔' => '𐐼', + '𐐕' => '𐐽', + '𐐖' => '𐐾', + '𐐗' => '𐐿', + '𐐘' => '𐑀', + '𐐙' => '𐑁', + '𐐚' => '𐑂', + '𐐛' => '𐑃', + '𐐜' => '𐑄', + '𐐝' => '𐑅', + '𐐞' => '𐑆', + '𐐟' => '𐑇', + '𐐠' => '𐑈', + '𐐡' => '𐑉', + '𐐢' => '𐑊', + '𐐣' => '𐑋', + '𐐤' => '𐑌', + '𐐥' => '𐑍', + '𐐦' => '𐑎', + '𐐧' => '𐑏', + '𐒰' => '𐓘', + '𐒱' => '𐓙', + '𐒲' => '𐓚', + '𐒳' => '𐓛', + '𐒴' => '𐓜', + '𐒵' => '𐓝', + '𐒶' => '𐓞', + '𐒷' => '𐓟', + '𐒸' => '𐓠', + '𐒹' => '𐓡', + '𐒺' => '𐓢', + '𐒻' => '𐓣', + '𐒼' => '𐓤', + '𐒽' => '𐓥', + '𐒾' => '𐓦', + '𐒿' => '𐓧', + '𐓀' => '𐓨', + '𐓁' => '𐓩', + '𐓂' => '𐓪', + '𐓃' => '𐓫', + '𐓄' => '𐓬', + '𐓅' => '𐓭', + '𐓆' => '𐓮', + '𐓇' => '𐓯', + '𐓈' => '𐓰', + '𐓉' => '𐓱', + '𐓊' => '𐓲', + '𐓋' => '𐓳', + '𐓌' => '𐓴', + '𐓍' => '𐓵', + '𐓎' => '𐓶', + '𐓏' => '𐓷', + '𐓐' => '𐓸', + '𐓑' => '𐓹', + '𐓒' => '𐓺', + '𐓓' => '𐓻', + '𐲀' => '𐳀', + '𐲁' => '𐳁', + '𐲂' => '𐳂', + '𐲃' => '𐳃', + '𐲄' => '𐳄', + '𐲅' => '𐳅', + '𐲆' => '𐳆', + '𐲇' => '𐳇', + '𐲈' => '𐳈', + '𐲉' => '𐳉', + '𐲊' => '𐳊', + '𐲋' => '𐳋', + '𐲌' => '𐳌', + '𐲍' => '𐳍', + '𐲎' => '𐳎', + '𐲏' => '𐳏', + '𐲐' => '𐳐', + '𐲑' => '𐳑', + '𐲒' => '𐳒', + '𐲓' => '𐳓', + '𐲔' => '𐳔', + '𐲕' => '𐳕', + '𐲖' => '𐳖', + '𐲗' => '𐳗', + '𐲘' => '𐳘', + '𐲙' => '𐳙', + '𐲚' => '𐳚', + '𐲛' => '𐳛', + '𐲜' => '𐳜', + '𐲝' => '𐳝', + '𐲞' => '𐳞', + '𐲟' => '𐳟', + '𐲠' => '𐳠', + '𐲡' => '𐳡', + '𐲢' => '𐳢', + '𐲣' => '𐳣', + '𐲤' => '𐳤', + '𐲥' => '𐳥', + '𐲦' => '𐳦', + '𐲧' => '𐳧', + '𐲨' => '𐳨', + '𐲩' => '𐳩', + '𐲪' => '𐳪', + '𐲫' => '𐳫', + '𐲬' => '𐳬', + '𐲭' => '𐳭', + '𐲮' => '𐳮', + '𐲯' => '𐳯', + '𐲰' => '𐳰', + '𐲱' => '𐳱', + '𐲲' => '𐳲', + '𑢠' => '𑣀', + '𑢡' => '𑣁', + '𑢢' => '𑣂', + '𑢣' => '𑣃', + '𑢤' => '𑣄', + '𑢥' => '𑣅', + '𑢦' => '𑣆', + '𑢧' => '𑣇', + '𑢨' => '𑣈', + '𑢩' => '𑣉', + '𑢪' => '𑣊', + '𑢫' => '𑣋', + '𑢬' => '𑣌', + '𑢭' => '𑣍', + '𑢮' => '𑣎', + '𑢯' => '𑣏', + '𑢰' => '𑣐', + '𑢱' => '𑣑', + '𑢲' => '𑣒', + '𑢳' => '𑣓', + '𑢴' => '𑣔', + '𑢵' => '𑣕', + '𑢶' => '𑣖', + '𑢷' => '𑣗', + '𑢸' => '𑣘', + '𑢹' => '𑣙', + '𑢺' => '𑣚', + '𑢻' => '𑣛', + '𑢼' => '𑣜', + '𑢽' => '𑣝', + '𑢾' => '𑣞', + '𑢿' => '𑣟', + '𖹀' => '𖹠', + '𖹁' => '𖹡', + '𖹂' => '𖹢', + '𖹃' => '𖹣', + '𖹄' => '𖹤', + '𖹅' => '𖹥', + '𖹆' => '𖹦', + '𖹇' => '𖹧', + '𖹈' => '𖹨', + '𖹉' => '𖹩', + '𖹊' => '𖹪', + '𖹋' => '𖹫', + '𖹌' => '𖹬', + '𖹍' => '𖹭', + '𖹎' => '𖹮', + '𖹏' => '𖹯', + '𖹐' => '𖹰', + '𖹑' => '𖹱', + '𖹒' => '𖹲', + '𖹓' => '𖹳', + '𖹔' => '𖹴', + '𖹕' => '𖹵', + '𖹖' => '𖹶', + '𖹗' => '𖹷', + '𖹘' => '𖹸', + '𖹙' => '𖹹', + '𖹚' => '𖹺', + '𖹛' => '𖹻', + '𖹜' => '𖹼', + '𖹝' => '𖹽', + '𖹞' => '𖹾', + '𖹟' => '𖹿', + '𞤀' => '𞤢', + '𞤁' => '𞤣', + '𞤂' => '𞤤', + '𞤃' => '𞤥', + '𞤄' => '𞤦', + '𞤅' => '𞤧', + '𞤆' => '𞤨', + '𞤇' => '𞤩', + '𞤈' => '𞤪', + '𞤉' => '𞤫', + '𞤊' => '𞤬', + '𞤋' => '𞤭', + '𞤌' => '𞤮', + '𞤍' => '𞤯', + '𞤎' => '𞤰', + '𞤏' => '𞤱', + '𞤐' => '𞤲', + '𞤑' => '𞤳', + '𞤒' => '𞤴', + '𞤓' => '𞤵', + '𞤔' => '𞤶', + '𞤕' => '𞤷', + '𞤖' => '𞤸', + '𞤗' => '𞤹', + '𞤘' => '𞤺', + '𞤙' => '𞤻', + '𞤚' => '𞤼', + '𞤛' => '𞤽', + '𞤜' => '𞤾', + '𞤝' => '𞤿', + '𞤞' => '𞥀', + '𞤟' => '𞥁', + '𞤠' => '𞥂', + '𞤡' => '𞥃', +); diff --git a/src/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php b/src/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php new file mode 100644 index 0000000..2a8f6e7 --- /dev/null +++ b/src/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php @@ -0,0 +1,5 @@ + 'A', + 'b' => 'B', + 'c' => 'C', + 'd' => 'D', + 'e' => 'E', + 'f' => 'F', + 'g' => 'G', + 'h' => 'H', + 'i' => 'I', + 'j' => 'J', + 'k' => 'K', + 'l' => 'L', + 'm' => 'M', + 'n' => 'N', + 'o' => 'O', + 'p' => 'P', + 'q' => 'Q', + 'r' => 'R', + 's' => 'S', + 't' => 'T', + 'u' => 'U', + 'v' => 'V', + 'w' => 'W', + 'x' => 'X', + 'y' => 'Y', + 'z' => 'Z', + 'µ' => 'Μ', + 'à' => 'À', + 'á' => 'Á', + 'â' => 'Â', + 'ã' => 'Ã', + 'ä' => 'Ä', + 'å' => 'Å', + 'æ' => 'Æ', + 'ç' => 'Ç', + 'è' => 'È', + 'é' => 'É', + 'ê' => 'Ê', + 'ë' => 'Ë', + 'ì' => 'Ì', + 'í' => 'Í', + 'î' => 'Î', + 'ï' => 'Ï', + 'ð' => 'Ð', + 'ñ' => 'Ñ', + 'ò' => 'Ò', + 'ó' => 'Ó', + 'ô' => 'Ô', + 'õ' => 'Õ', + 'ö' => 'Ö', + 'ø' => 'Ø', + 'ù' => 'Ù', + 'ú' => 'Ú', + 'û' => 'Û', + 'ü' => 'Ü', + 'ý' => 'Ý', + 'þ' => 'Þ', + 'ÿ' => 'Ÿ', + 'ā' => 'Ā', + 'ă' => 'Ă', + 'ą' => 'Ą', + 'ć' => 'Ć', + 'ĉ' => 'Ĉ', + 'ċ' => 'Ċ', + 'č' => 'Č', + 'ď' => 'Ď', + 'đ' => 'Đ', + 'ē' => 'Ē', + 'ĕ' => 'Ĕ', + 'ė' => 'Ė', + 'ę' => 'Ę', + 'ě' => 'Ě', + 'ĝ' => 'Ĝ', + 'ğ' => 'Ğ', + 'ġ' => 'Ġ', + 'ģ' => 'Ģ', + 'ĥ' => 'Ĥ', + 'ħ' => 'Ħ', + 'ĩ' => 'Ĩ', + 'ī' => 'Ī', + 'ĭ' => 'Ĭ', + 'į' => 'Į', + 'ı' => 'I', + 'ij' => 'IJ', + 'ĵ' => 'Ĵ', + 'ķ' => 'Ķ', + 'ĺ' => 'Ĺ', + 'ļ' => 'Ļ', + 'ľ' => 'Ľ', + 'ŀ' => 'Ŀ', + 'ł' => 'Ł', + 'ń' => 'Ń', + 'ņ' => 'Ņ', + 'ň' => 'Ň', + 'ŋ' => 'Ŋ', + 'ō' => 'Ō', + 'ŏ' => 'Ŏ', + 'ő' => 'Ő', + 'œ' => 'Œ', + 'ŕ' => 'Ŕ', + 'ŗ' => 'Ŗ', + 'ř' => 'Ř', + 'ś' => 'Ś', + 'ŝ' => 'Ŝ', + 'ş' => 'Ş', + 'š' => 'Š', + 'ţ' => 'Ţ', + 'ť' => 'Ť', + 'ŧ' => 'Ŧ', + 'ũ' => 'Ũ', + 'ū' => 'Ū', + 'ŭ' => 'Ŭ', + 'ů' => 'Ů', + 'ű' => 'Ű', + 'ų' => 'Ų', + 'ŵ' => 'Ŵ', + 'ŷ' => 'Ŷ', + 'ź' => 'Ź', + 'ż' => 'Ż', + 'ž' => 'Ž', + 'ſ' => 'S', + 'ƀ' => 'Ƀ', + 'ƃ' => 'Ƃ', + 'ƅ' => 'Ƅ', + 'ƈ' => 'Ƈ', + 'ƌ' => 'Ƌ', + 'ƒ' => 'Ƒ', + 'ƕ' => 'Ƕ', + 'ƙ' => 'Ƙ', + 'ƚ' => 'Ƚ', + 'ƞ' => 'Ƞ', + 'ơ' => 'Ơ', + 'ƣ' => 'Ƣ', + 'ƥ' => 'Ƥ', + 'ƨ' => 'Ƨ', + 'ƭ' => 'Ƭ', + 'ư' => 'Ư', + 'ƴ' => 'Ƴ', + 'ƶ' => 'Ƶ', + 'ƹ' => 'Ƹ', + 'ƽ' => 'Ƽ', + 'ƿ' => 'Ƿ', + 'Dž' => 'DŽ', + 'dž' => 'DŽ', + 'Lj' => 'LJ', + 'lj' => 'LJ', + 'Nj' => 'NJ', + 'nj' => 'NJ', + 'ǎ' => 'Ǎ', + 'ǐ' => 'Ǐ', + 'ǒ' => 'Ǒ', + 'ǔ' => 'Ǔ', + 'ǖ' => 'Ǖ', + 'ǘ' => 'Ǘ', + 'ǚ' => 'Ǚ', + 'ǜ' => 'Ǜ', + 'ǝ' => 'Ǝ', + 'ǟ' => 'Ǟ', + 'ǡ' => 'Ǡ', + 'ǣ' => 'Ǣ', + 'ǥ' => 'Ǥ', + 'ǧ' => 'Ǧ', + 'ǩ' => 'Ǩ', + 'ǫ' => 'Ǫ', + 'ǭ' => 'Ǭ', + 'ǯ' => 'Ǯ', + 'Dz' => 'DZ', + 'dz' => 'DZ', + 'ǵ' => 'Ǵ', + 'ǹ' => 'Ǹ', + 'ǻ' => 'Ǻ', + 'ǽ' => 'Ǽ', + 'ǿ' => 'Ǿ', + 'ȁ' => 'Ȁ', + 'ȃ' => 'Ȃ', + 'ȅ' => 'Ȅ', + 'ȇ' => 'Ȇ', + 'ȉ' => 'Ȉ', + 'ȋ' => 'Ȋ', + 'ȍ' => 'Ȍ', + 'ȏ' => 'Ȏ', + 'ȑ' => 'Ȑ', + 'ȓ' => 'Ȓ', + 'ȕ' => 'Ȕ', + 'ȗ' => 'Ȗ', + 'ș' => 'Ș', + 'ț' => 'Ț', + 'ȝ' => 'Ȝ', + 'ȟ' => 'Ȟ', + 'ȣ' => 'Ȣ', + 'ȥ' => 'Ȥ', + 'ȧ' => 'Ȧ', + 'ȩ' => 'Ȩ', + 'ȫ' => 'Ȫ', + 'ȭ' => 'Ȭ', + 'ȯ' => 'Ȯ', + 'ȱ' => 'Ȱ', + 'ȳ' => 'Ȳ', + 'ȼ' => 'Ȼ', + 'ȿ' => 'Ȿ', + 'ɀ' => 'Ɀ', + 'ɂ' => 'Ɂ', + 'ɇ' => 'Ɇ', + 'ɉ' => 'Ɉ', + 'ɋ' => 'Ɋ', + 'ɍ' => 'Ɍ', + 'ɏ' => 'Ɏ', + 'ɐ' => 'Ɐ', + 'ɑ' => 'Ɑ', + 'ɒ' => 'Ɒ', + 'ɓ' => 'Ɓ', + 'ɔ' => 'Ɔ', + 'ɖ' => 'Ɖ', + 'ɗ' => 'Ɗ', + 'ə' => 'Ə', + 'ɛ' => 'Ɛ', + 'ɜ' => 'Ɜ', + 'ɠ' => 'Ɠ', + 'ɡ' => 'Ɡ', + 'ɣ' => 'Ɣ', + 'ɥ' => 'Ɥ', + 'ɦ' => 'Ɦ', + 'ɨ' => 'Ɨ', + 'ɩ' => 'Ɩ', + 'ɪ' => 'Ɪ', + 'ɫ' => 'Ɫ', + 'ɬ' => 'Ɬ', + 'ɯ' => 'Ɯ', + 'ɱ' => 'Ɱ', + 'ɲ' => 'Ɲ', + 'ɵ' => 'Ɵ', + 'ɽ' => 'Ɽ', + 'ʀ' => 'Ʀ', + 'ʂ' => 'Ʂ', + 'ʃ' => 'Ʃ', + 'ʇ' => 'Ʇ', + 'ʈ' => 'Ʈ', + 'ʉ' => 'Ʉ', + 'ʊ' => 'Ʊ', + 'ʋ' => 'Ʋ', + 'ʌ' => 'Ʌ', + 'ʒ' => 'Ʒ', + 'ʝ' => 'Ʝ', + 'ʞ' => 'Ʞ', + 'ͅ' => 'Ι', + 'ͱ' => 'Ͱ', + 'ͳ' => 'Ͳ', + 'ͷ' => 'Ͷ', + 'ͻ' => 'Ͻ', + 'ͼ' => 'Ͼ', + 'ͽ' => 'Ͽ', + 'ά' => 'Ά', + 'έ' => 'Έ', + 'ή' => 'Ή', + 'ί' => 'Ί', + 'α' => 'Α', + 'β' => 'Β', + 'γ' => 'Γ', + 'δ' => 'Δ', + 'ε' => 'Ε', + 'ζ' => 'Ζ', + 'η' => 'Η', + 'θ' => 'Θ', + 'ι' => 'Ι', + 'κ' => 'Κ', + 'λ' => 'Λ', + 'μ' => 'Μ', + 'ν' => 'Ν', + 'ξ' => 'Ξ', + 'ο' => 'Ο', + 'π' => 'Π', + 'ρ' => 'Ρ', + 'ς' => 'Σ', + 'σ' => 'Σ', + 'τ' => 'Τ', + 'υ' => 'Υ', + 'φ' => 'Φ', + 'χ' => 'Χ', + 'ψ' => 'Ψ', + 'ω' => 'Ω', + 'ϊ' => 'Ϊ', + 'ϋ' => 'Ϋ', + 'ό' => 'Ό', + 'ύ' => 'Ύ', + 'ώ' => 'Ώ', + 'ϐ' => 'Β', + 'ϑ' => 'Θ', + 'ϕ' => 'Φ', + 'ϖ' => 'Π', + 'ϗ' => 'Ϗ', + 'ϙ' => 'Ϙ', + 'ϛ' => 'Ϛ', + 'ϝ' => 'Ϝ', + 'ϟ' => 'Ϟ', + 'ϡ' => 'Ϡ', + 'ϣ' => 'Ϣ', + 'ϥ' => 'Ϥ', + 'ϧ' => 'Ϧ', + 'ϩ' => 'Ϩ', + 'ϫ' => 'Ϫ', + 'ϭ' => 'Ϭ', + 'ϯ' => 'Ϯ', + 'ϰ' => 'Κ', + 'ϱ' => 'Ρ', + 'ϲ' => 'Ϲ', + 'ϳ' => 'Ϳ', + 'ϵ' => 'Ε', + 'ϸ' => 'Ϸ', + 'ϻ' => 'Ϻ', + 'а' => 'А', + 'б' => 'Б', + 'в' => 'В', + 'г' => 'Г', + 'д' => 'Д', + 'е' => 'Е', + 'ж' => 'Ж', + 'з' => 'З', + 'и' => 'И', + 'й' => 'Й', + 'к' => 'К', + 'л' => 'Л', + 'м' => 'М', + 'н' => 'Н', + 'о' => 'О', + 'п' => 'П', + 'р' => 'Р', + 'с' => 'С', + 'т' => 'Т', + 'у' => 'У', + 'ф' => 'Ф', + 'х' => 'Х', + 'ц' => 'Ц', + 'ч' => 'Ч', + 'ш' => 'Ш', + 'щ' => 'Щ', + 'ъ' => 'Ъ', + 'ы' => 'Ы', + 'ь' => 'Ь', + 'э' => 'Э', + 'ю' => 'Ю', + 'я' => 'Я', + 'ѐ' => 'Ѐ', + 'ё' => 'Ё', + 'ђ' => 'Ђ', + 'ѓ' => 'Ѓ', + 'є' => 'Є', + 'ѕ' => 'Ѕ', + 'і' => 'І', + 'ї' => 'Ї', + 'ј' => 'Ј', + 'љ' => 'Љ', + 'њ' => 'Њ', + 'ћ' => 'Ћ', + 'ќ' => 'Ќ', + 'ѝ' => 'Ѝ', + 'ў' => 'Ў', + 'џ' => 'Џ', + 'ѡ' => 'Ѡ', + 'ѣ' => 'Ѣ', + 'ѥ' => 'Ѥ', + 'ѧ' => 'Ѧ', + 'ѩ' => 'Ѩ', + 'ѫ' => 'Ѫ', + 'ѭ' => 'Ѭ', + 'ѯ' => 'Ѯ', + 'ѱ' => 'Ѱ', + 'ѳ' => 'Ѳ', + 'ѵ' => 'Ѵ', + 'ѷ' => 'Ѷ', + 'ѹ' => 'Ѹ', + 'ѻ' => 'Ѻ', + 'ѽ' => 'Ѽ', + 'ѿ' => 'Ѿ', + 'ҁ' => 'Ҁ', + 'ҋ' => 'Ҋ', + 'ҍ' => 'Ҍ', + 'ҏ' => 'Ҏ', + 'ґ' => 'Ґ', + 'ғ' => 'Ғ', + 'ҕ' => 'Ҕ', + 'җ' => 'Җ', + 'ҙ' => 'Ҙ', + 'қ' => 'Қ', + 'ҝ' => 'Ҝ', + 'ҟ' => 'Ҟ', + 'ҡ' => 'Ҡ', + 'ң' => 'Ң', + 'ҥ' => 'Ҥ', + 'ҧ' => 'Ҧ', + 'ҩ' => 'Ҩ', + 'ҫ' => 'Ҫ', + 'ҭ' => 'Ҭ', + 'ү' => 'Ү', + 'ұ' => 'Ұ', + 'ҳ' => 'Ҳ', + 'ҵ' => 'Ҵ', + 'ҷ' => 'Ҷ', + 'ҹ' => 'Ҹ', + 'һ' => 'Һ', + 'ҽ' => 'Ҽ', + 'ҿ' => 'Ҿ', + 'ӂ' => 'Ӂ', + 'ӄ' => 'Ӄ', + 'ӆ' => 'Ӆ', + 'ӈ' => 'Ӈ', + 'ӊ' => 'Ӊ', + 'ӌ' => 'Ӌ', + 'ӎ' => 'Ӎ', + 'ӏ' => 'Ӏ', + 'ӑ' => 'Ӑ', + 'ӓ' => 'Ӓ', + 'ӕ' => 'Ӕ', + 'ӗ' => 'Ӗ', + 'ә' => 'Ә', + 'ӛ' => 'Ӛ', + 'ӝ' => 'Ӝ', + 'ӟ' => 'Ӟ', + 'ӡ' => 'Ӡ', + 'ӣ' => 'Ӣ', + 'ӥ' => 'Ӥ', + 'ӧ' => 'Ӧ', + 'ө' => 'Ө', + 'ӫ' => 'Ӫ', + 'ӭ' => 'Ӭ', + 'ӯ' => 'Ӯ', + 'ӱ' => 'Ӱ', + 'ӳ' => 'Ӳ', + 'ӵ' => 'Ӵ', + 'ӷ' => 'Ӷ', + 'ӹ' => 'Ӹ', + 'ӻ' => 'Ӻ', + 'ӽ' => 'Ӽ', + 'ӿ' => 'Ӿ', + 'ԁ' => 'Ԁ', + 'ԃ' => 'Ԃ', + 'ԅ' => 'Ԅ', + 'ԇ' => 'Ԇ', + 'ԉ' => 'Ԉ', + 'ԋ' => 'Ԋ', + 'ԍ' => 'Ԍ', + 'ԏ' => 'Ԏ', + 'ԑ' => 'Ԑ', + 'ԓ' => 'Ԓ', + 'ԕ' => 'Ԕ', + 'ԗ' => 'Ԗ', + 'ԙ' => 'Ԙ', + 'ԛ' => 'Ԛ', + 'ԝ' => 'Ԝ', + 'ԟ' => 'Ԟ', + 'ԡ' => 'Ԡ', + 'ԣ' => 'Ԣ', + 'ԥ' => 'Ԥ', + 'ԧ' => 'Ԧ', + 'ԩ' => 'Ԩ', + 'ԫ' => 'Ԫ', + 'ԭ' => 'Ԭ', + 'ԯ' => 'Ԯ', + 'ա' => 'Ա', + 'բ' => 'Բ', + 'գ' => 'Գ', + 'դ' => 'Դ', + 'ե' => 'Ե', + 'զ' => 'Զ', + 'է' => 'Է', + 'ը' => 'Ը', + 'թ' => 'Թ', + 'ժ' => 'Ժ', + 'ի' => 'Ի', + 'լ' => 'Լ', + 'խ' => 'Խ', + 'ծ' => 'Ծ', + 'կ' => 'Կ', + 'հ' => 'Հ', + 'ձ' => 'Ձ', + 'ղ' => 'Ղ', + 'ճ' => 'Ճ', + 'մ' => 'Մ', + 'յ' => 'Յ', + 'ն' => 'Ն', + 'շ' => 'Շ', + 'ո' => 'Ո', + 'չ' => 'Չ', + 'պ' => 'Պ', + 'ջ' => 'Ջ', + 'ռ' => 'Ռ', + 'ս' => 'Ս', + 'վ' => 'Վ', + 'տ' => 'Տ', + 'ր' => 'Ր', + 'ց' => 'Ց', + 'ւ' => 'Ւ', + 'փ' => 'Փ', + 'ք' => 'Ք', + 'օ' => 'Օ', + 'ֆ' => 'Ֆ', + 'ა' => 'Ა', + 'ბ' => 'Ბ', + 'გ' => 'Გ', + 'დ' => 'Დ', + 'ე' => 'Ე', + 'ვ' => 'Ვ', + 'ზ' => 'Ზ', + 'თ' => 'Თ', + 'ი' => 'Ი', + 'კ' => 'Კ', + 'ლ' => 'Ლ', + 'მ' => 'Მ', + 'ნ' => 'Ნ', + 'ო' => 'Ო', + 'პ' => 'Პ', + 'ჟ' => 'Ჟ', + 'რ' => 'Რ', + 'ს' => 'Ს', + 'ტ' => 'Ტ', + 'უ' => 'Უ', + 'ფ' => 'Ფ', + 'ქ' => 'Ქ', + 'ღ' => 'Ღ', + 'ყ' => 'Ყ', + 'შ' => 'Შ', + 'ჩ' => 'Ჩ', + 'ც' => 'Ც', + 'ძ' => 'Ძ', + 'წ' => 'Წ', + 'ჭ' => 'Ჭ', + 'ხ' => 'Ხ', + 'ჯ' => 'Ჯ', + 'ჰ' => 'Ჰ', + 'ჱ' => 'Ჱ', + 'ჲ' => 'Ჲ', + 'ჳ' => 'Ჳ', + 'ჴ' => 'Ჴ', + 'ჵ' => 'Ჵ', + 'ჶ' => 'Ჶ', + 'ჷ' => 'Ჷ', + 'ჸ' => 'Ჸ', + 'ჹ' => 'Ჹ', + 'ჺ' => 'Ჺ', + 'ჽ' => 'Ჽ', + 'ჾ' => 'Ჾ', + 'ჿ' => 'Ჿ', + 'ᏸ' => 'Ᏸ', + 'ᏹ' => 'Ᏹ', + 'ᏺ' => 'Ᏺ', + 'ᏻ' => 'Ᏻ', + 'ᏼ' => 'Ᏼ', + 'ᏽ' => 'Ᏽ', + 'ᲀ' => 'В', + 'ᲁ' => 'Д', + 'ᲂ' => 'О', + 'ᲃ' => 'С', + 'ᲄ' => 'Т', + 'ᲅ' => 'Т', + 'ᲆ' => 'Ъ', + 'ᲇ' => 'Ѣ', + 'ᲈ' => 'Ꙋ', + 'ᵹ' => 'Ᵹ', + 'ᵽ' => 'Ᵽ', + 'ᶎ' => 'Ᶎ', + 'ḁ' => 'Ḁ', + 'ḃ' => 'Ḃ', + 'ḅ' => 'Ḅ', + 'ḇ' => 'Ḇ', + 'ḉ' => 'Ḉ', + 'ḋ' => 'Ḋ', + 'ḍ' => 'Ḍ', + 'ḏ' => 'Ḏ', + 'ḑ' => 'Ḑ', + 'ḓ' => 'Ḓ', + 'ḕ' => 'Ḕ', + 'ḗ' => 'Ḗ', + 'ḙ' => 'Ḙ', + 'ḛ' => 'Ḛ', + 'ḝ' => 'Ḝ', + 'ḟ' => 'Ḟ', + 'ḡ' => 'Ḡ', + 'ḣ' => 'Ḣ', + 'ḥ' => 'Ḥ', + 'ḧ' => 'Ḧ', + 'ḩ' => 'Ḩ', + 'ḫ' => 'Ḫ', + 'ḭ' => 'Ḭ', + 'ḯ' => 'Ḯ', + 'ḱ' => 'Ḱ', + 'ḳ' => 'Ḳ', + 'ḵ' => 'Ḵ', + 'ḷ' => 'Ḷ', + 'ḹ' => 'Ḹ', + 'ḻ' => 'Ḻ', + 'ḽ' => 'Ḽ', + 'ḿ' => 'Ḿ', + 'ṁ' => 'Ṁ', + 'ṃ' => 'Ṃ', + 'ṅ' => 'Ṅ', + 'ṇ' => 'Ṇ', + 'ṉ' => 'Ṉ', + 'ṋ' => 'Ṋ', + 'ṍ' => 'Ṍ', + 'ṏ' => 'Ṏ', + 'ṑ' => 'Ṑ', + 'ṓ' => 'Ṓ', + 'ṕ' => 'Ṕ', + 'ṗ' => 'Ṗ', + 'ṙ' => 'Ṙ', + 'ṛ' => 'Ṛ', + 'ṝ' => 'Ṝ', + 'ṟ' => 'Ṟ', + 'ṡ' => 'Ṡ', + 'ṣ' => 'Ṣ', + 'ṥ' => 'Ṥ', + 'ṧ' => 'Ṧ', + 'ṩ' => 'Ṩ', + 'ṫ' => 'Ṫ', + 'ṭ' => 'Ṭ', + 'ṯ' => 'Ṯ', + 'ṱ' => 'Ṱ', + 'ṳ' => 'Ṳ', + 'ṵ' => 'Ṵ', + 'ṷ' => 'Ṷ', + 'ṹ' => 'Ṹ', + 'ṻ' => 'Ṻ', + 'ṽ' => 'Ṽ', + 'ṿ' => 'Ṿ', + 'ẁ' => 'Ẁ', + 'ẃ' => 'Ẃ', + 'ẅ' => 'Ẅ', + 'ẇ' => 'Ẇ', + 'ẉ' => 'Ẉ', + 'ẋ' => 'Ẋ', + 'ẍ' => 'Ẍ', + 'ẏ' => 'Ẏ', + 'ẑ' => 'Ẑ', + 'ẓ' => 'Ẓ', + 'ẕ' => 'Ẕ', + 'ẛ' => 'Ṡ', + 'ạ' => 'Ạ', + 'ả' => 'Ả', + 'ấ' => 'Ấ', + 'ầ' => 'Ầ', + 'ẩ' => 'Ẩ', + 'ẫ' => 'Ẫ', + 'ậ' => 'Ậ', + 'ắ' => 'Ắ', + 'ằ' => 'Ằ', + 'ẳ' => 'Ẳ', + 'ẵ' => 'Ẵ', + 'ặ' => 'Ặ', + 'ẹ' => 'Ẹ', + 'ẻ' => 'Ẻ', + 'ẽ' => 'Ẽ', + 'ế' => 'Ế', + 'ề' => 'Ề', + 'ể' => 'Ể', + 'ễ' => 'Ễ', + 'ệ' => 'Ệ', + 'ỉ' => 'Ỉ', + 'ị' => 'Ị', + 'ọ' => 'Ọ', + 'ỏ' => 'Ỏ', + 'ố' => 'Ố', + 'ồ' => 'Ồ', + 'ổ' => 'Ổ', + 'ỗ' => 'Ỗ', + 'ộ' => 'Ộ', + 'ớ' => 'Ớ', + 'ờ' => 'Ờ', + 'ở' => 'Ở', + 'ỡ' => 'Ỡ', + 'ợ' => 'Ợ', + 'ụ' => 'Ụ', + 'ủ' => 'Ủ', + 'ứ' => 'Ứ', + 'ừ' => 'Ừ', + 'ử' => 'Ử', + 'ữ' => 'Ữ', + 'ự' => 'Ự', + 'ỳ' => 'Ỳ', + 'ỵ' => 'Ỵ', + 'ỷ' => 'Ỷ', + 'ỹ' => 'Ỹ', + 'ỻ' => 'Ỻ', + 'ỽ' => 'Ỽ', + 'ỿ' => 'Ỿ', + 'ἀ' => 'Ἀ', + 'ἁ' => 'Ἁ', + 'ἂ' => 'Ἂ', + 'ἃ' => 'Ἃ', + 'ἄ' => 'Ἄ', + 'ἅ' => 'Ἅ', + 'ἆ' => 'Ἆ', + 'ἇ' => 'Ἇ', + 'ἐ' => 'Ἐ', + 'ἑ' => 'Ἑ', + 'ἒ' => 'Ἒ', + 'ἓ' => 'Ἓ', + 'ἔ' => 'Ἔ', + 'ἕ' => 'Ἕ', + 'ἠ' => 'Ἠ', + 'ἡ' => 'Ἡ', + 'ἢ' => 'Ἢ', + 'ἣ' => 'Ἣ', + 'ἤ' => 'Ἤ', + 'ἥ' => 'Ἥ', + 'ἦ' => 'Ἦ', + 'ἧ' => 'Ἧ', + 'ἰ' => 'Ἰ', + 'ἱ' => 'Ἱ', + 'ἲ' => 'Ἲ', + 'ἳ' => 'Ἳ', + 'ἴ' => 'Ἴ', + 'ἵ' => 'Ἵ', + 'ἶ' => 'Ἶ', + 'ἷ' => 'Ἷ', + 'ὀ' => 'Ὀ', + 'ὁ' => 'Ὁ', + 'ὂ' => 'Ὂ', + 'ὃ' => 'Ὃ', + 'ὄ' => 'Ὄ', + 'ὅ' => 'Ὅ', + 'ὑ' => 'Ὑ', + 'ὓ' => 'Ὓ', + 'ὕ' => 'Ὕ', + 'ὗ' => 'Ὗ', + 'ὠ' => 'Ὠ', + 'ὡ' => 'Ὡ', + 'ὢ' => 'Ὢ', + 'ὣ' => 'Ὣ', + 'ὤ' => 'Ὤ', + 'ὥ' => 'Ὥ', + 'ὦ' => 'Ὦ', + 'ὧ' => 'Ὧ', + 'ὰ' => 'Ὰ', + 'ά' => 'Ά', + 'ὲ' => 'Ὲ', + 'έ' => 'Έ', + 'ὴ' => 'Ὴ', + 'ή' => 'Ή', + 'ὶ' => 'Ὶ', + 'ί' => 'Ί', + 'ὸ' => 'Ὸ', + 'ό' => 'Ό', + 'ὺ' => 'Ὺ', + 'ύ' => 'Ύ', + 'ὼ' => 'Ὼ', + 'ώ' => 'Ώ', + 'ᾀ' => 'ἈΙ', + 'ᾁ' => 'ἉΙ', + 'ᾂ' => 'ἊΙ', + 'ᾃ' => 'ἋΙ', + 'ᾄ' => 'ἌΙ', + 'ᾅ' => 'ἍΙ', + 'ᾆ' => 'ἎΙ', + 'ᾇ' => 'ἏΙ', + 'ᾐ' => 'ἨΙ', + 'ᾑ' => 'ἩΙ', + 'ᾒ' => 'ἪΙ', + 'ᾓ' => 'ἫΙ', + 'ᾔ' => 'ἬΙ', + 'ᾕ' => 'ἭΙ', + 'ᾖ' => 'ἮΙ', + 'ᾗ' => 'ἯΙ', + 'ᾠ' => 'ὨΙ', + 'ᾡ' => 'ὩΙ', + 'ᾢ' => 'ὪΙ', + 'ᾣ' => 'ὫΙ', + 'ᾤ' => 'ὬΙ', + 'ᾥ' => 'ὭΙ', + 'ᾦ' => 'ὮΙ', + 'ᾧ' => 'ὯΙ', + 'ᾰ' => 'Ᾰ', + 'ᾱ' => 'Ᾱ', + 'ᾳ' => 'ΑΙ', + 'ι' => 'Ι', + 'ῃ' => 'ΗΙ', + 'ῐ' => 'Ῐ', + 'ῑ' => 'Ῑ', + 'ῠ' => 'Ῠ', + 'ῡ' => 'Ῡ', + 'ῥ' => 'Ῥ', + 'ῳ' => 'ΩΙ', + 'ⅎ' => 'Ⅎ', + 'ⅰ' => 'Ⅰ', + 'ⅱ' => 'Ⅱ', + 'ⅲ' => 'Ⅲ', + 'ⅳ' => 'Ⅳ', + 'ⅴ' => 'Ⅴ', + 'ⅵ' => 'Ⅵ', + 'ⅶ' => 'Ⅶ', + 'ⅷ' => 'Ⅷ', + 'ⅸ' => 'Ⅸ', + 'ⅹ' => 'Ⅹ', + 'ⅺ' => 'Ⅺ', + 'ⅻ' => 'Ⅻ', + 'ⅼ' => 'Ⅼ', + 'ⅽ' => 'Ⅽ', + 'ⅾ' => 'Ⅾ', + 'ⅿ' => 'Ⅿ', + 'ↄ' => 'Ↄ', + 'ⓐ' => 'Ⓐ', + 'ⓑ' => 'Ⓑ', + 'ⓒ' => 'Ⓒ', + 'ⓓ' => 'Ⓓ', + 'ⓔ' => 'Ⓔ', + 'ⓕ' => 'Ⓕ', + 'ⓖ' => 'Ⓖ', + 'ⓗ' => 'Ⓗ', + 'ⓘ' => 'Ⓘ', + 'ⓙ' => 'Ⓙ', + 'ⓚ' => 'Ⓚ', + 'ⓛ' => 'Ⓛ', + 'ⓜ' => 'Ⓜ', + 'ⓝ' => 'Ⓝ', + 'ⓞ' => 'Ⓞ', + 'ⓟ' => 'Ⓟ', + 'ⓠ' => 'Ⓠ', + 'ⓡ' => 'Ⓡ', + 'ⓢ' => 'Ⓢ', + 'ⓣ' => 'Ⓣ', + 'ⓤ' => 'Ⓤ', + 'ⓥ' => 'Ⓥ', + 'ⓦ' => 'Ⓦ', + 'ⓧ' => 'Ⓧ', + 'ⓨ' => 'Ⓨ', + 'ⓩ' => 'Ⓩ', + 'ⰰ' => 'Ⰰ', + 'ⰱ' => 'Ⰱ', + 'ⰲ' => 'Ⰲ', + 'ⰳ' => 'Ⰳ', + 'ⰴ' => 'Ⰴ', + 'ⰵ' => 'Ⰵ', + 'ⰶ' => 'Ⰶ', + 'ⰷ' => 'Ⰷ', + 'ⰸ' => 'Ⰸ', + 'ⰹ' => 'Ⰹ', + 'ⰺ' => 'Ⰺ', + 'ⰻ' => 'Ⰻ', + 'ⰼ' => 'Ⰼ', + 'ⰽ' => 'Ⰽ', + 'ⰾ' => 'Ⰾ', + 'ⰿ' => 'Ⰿ', + 'ⱀ' => 'Ⱀ', + 'ⱁ' => 'Ⱁ', + 'ⱂ' => 'Ⱂ', + 'ⱃ' => 'Ⱃ', + 'ⱄ' => 'Ⱄ', + 'ⱅ' => 'Ⱅ', + 'ⱆ' => 'Ⱆ', + 'ⱇ' => 'Ⱇ', + 'ⱈ' => 'Ⱈ', + 'ⱉ' => 'Ⱉ', + 'ⱊ' => 'Ⱊ', + 'ⱋ' => 'Ⱋ', + 'ⱌ' => 'Ⱌ', + 'ⱍ' => 'Ⱍ', + 'ⱎ' => 'Ⱎ', + 'ⱏ' => 'Ⱏ', + 'ⱐ' => 'Ⱐ', + 'ⱑ' => 'Ⱑ', + 'ⱒ' => 'Ⱒ', + 'ⱓ' => 'Ⱓ', + 'ⱔ' => 'Ⱔ', + 'ⱕ' => 'Ⱕ', + 'ⱖ' => 'Ⱖ', + 'ⱗ' => 'Ⱗ', + 'ⱘ' => 'Ⱘ', + 'ⱙ' => 'Ⱙ', + 'ⱚ' => 'Ⱚ', + 'ⱛ' => 'Ⱛ', + 'ⱜ' => 'Ⱜ', + 'ⱝ' => 'Ⱝ', + 'ⱞ' => 'Ⱞ', + 'ⱡ' => 'Ⱡ', + 'ⱥ' => 'Ⱥ', + 'ⱦ' => 'Ⱦ', + 'ⱨ' => 'Ⱨ', + 'ⱪ' => 'Ⱪ', + 'ⱬ' => 'Ⱬ', + 'ⱳ' => 'Ⱳ', + 'ⱶ' => 'Ⱶ', + 'ⲁ' => 'Ⲁ', + 'ⲃ' => 'Ⲃ', + 'ⲅ' => 'Ⲅ', + 'ⲇ' => 'Ⲇ', + 'ⲉ' => 'Ⲉ', + 'ⲋ' => 'Ⲋ', + 'ⲍ' => 'Ⲍ', + 'ⲏ' => 'Ⲏ', + 'ⲑ' => 'Ⲑ', + 'ⲓ' => 'Ⲓ', + 'ⲕ' => 'Ⲕ', + 'ⲗ' => 'Ⲗ', + 'ⲙ' => 'Ⲙ', + 'ⲛ' => 'Ⲛ', + 'ⲝ' => 'Ⲝ', + 'ⲟ' => 'Ⲟ', + 'ⲡ' => 'Ⲡ', + 'ⲣ' => 'Ⲣ', + 'ⲥ' => 'Ⲥ', + 'ⲧ' => 'Ⲧ', + 'ⲩ' => 'Ⲩ', + 'ⲫ' => 'Ⲫ', + 'ⲭ' => 'Ⲭ', + 'ⲯ' => 'Ⲯ', + 'ⲱ' => 'Ⲱ', + 'ⲳ' => 'Ⲳ', + 'ⲵ' => 'Ⲵ', + 'ⲷ' => 'Ⲷ', + 'ⲹ' => 'Ⲹ', + 'ⲻ' => 'Ⲻ', + 'ⲽ' => 'Ⲽ', + 'ⲿ' => 'Ⲿ', + 'ⳁ' => 'Ⳁ', + 'ⳃ' => 'Ⳃ', + 'ⳅ' => 'Ⳅ', + 'ⳇ' => 'Ⳇ', + 'ⳉ' => 'Ⳉ', + 'ⳋ' => 'Ⳋ', + 'ⳍ' => 'Ⳍ', + 'ⳏ' => 'Ⳏ', + 'ⳑ' => 'Ⳑ', + 'ⳓ' => 'Ⳓ', + 'ⳕ' => 'Ⳕ', + 'ⳗ' => 'Ⳗ', + 'ⳙ' => 'Ⳙ', + 'ⳛ' => 'Ⳛ', + 'ⳝ' => 'Ⳝ', + 'ⳟ' => 'Ⳟ', + 'ⳡ' => 'Ⳡ', + 'ⳣ' => 'Ⳣ', + 'ⳬ' => 'Ⳬ', + 'ⳮ' => 'Ⳮ', + 'ⳳ' => 'Ⳳ', + 'ⴀ' => 'Ⴀ', + 'ⴁ' => 'Ⴁ', + 'ⴂ' => 'Ⴂ', + 'ⴃ' => 'Ⴃ', + 'ⴄ' => 'Ⴄ', + 'ⴅ' => 'Ⴅ', + 'ⴆ' => 'Ⴆ', + 'ⴇ' => 'Ⴇ', + 'ⴈ' => 'Ⴈ', + 'ⴉ' => 'Ⴉ', + 'ⴊ' => 'Ⴊ', + 'ⴋ' => 'Ⴋ', + 'ⴌ' => 'Ⴌ', + 'ⴍ' => 'Ⴍ', + 'ⴎ' => 'Ⴎ', + 'ⴏ' => 'Ⴏ', + 'ⴐ' => 'Ⴐ', + 'ⴑ' => 'Ⴑ', + 'ⴒ' => 'Ⴒ', + 'ⴓ' => 'Ⴓ', + 'ⴔ' => 'Ⴔ', + 'ⴕ' => 'Ⴕ', + 'ⴖ' => 'Ⴖ', + 'ⴗ' => 'Ⴗ', + 'ⴘ' => 'Ⴘ', + 'ⴙ' => 'Ⴙ', + 'ⴚ' => 'Ⴚ', + 'ⴛ' => 'Ⴛ', + 'ⴜ' => 'Ⴜ', + 'ⴝ' => 'Ⴝ', + 'ⴞ' => 'Ⴞ', + 'ⴟ' => 'Ⴟ', + 'ⴠ' => 'Ⴠ', + 'ⴡ' => 'Ⴡ', + 'ⴢ' => 'Ⴢ', + 'ⴣ' => 'Ⴣ', + 'ⴤ' => 'Ⴤ', + 'ⴥ' => 'Ⴥ', + 'ⴧ' => 'Ⴧ', + 'ⴭ' => 'Ⴭ', + 'ꙁ' => 'Ꙁ', + 'ꙃ' => 'Ꙃ', + 'ꙅ' => 'Ꙅ', + 'ꙇ' => 'Ꙇ', + 'ꙉ' => 'Ꙉ', + 'ꙋ' => 'Ꙋ', + 'ꙍ' => 'Ꙍ', + 'ꙏ' => 'Ꙏ', + 'ꙑ' => 'Ꙑ', + 'ꙓ' => 'Ꙓ', + 'ꙕ' => 'Ꙕ', + 'ꙗ' => 'Ꙗ', + 'ꙙ' => 'Ꙙ', + 'ꙛ' => 'Ꙛ', + 'ꙝ' => 'Ꙝ', + 'ꙟ' => 'Ꙟ', + 'ꙡ' => 'Ꙡ', + 'ꙣ' => 'Ꙣ', + 'ꙥ' => 'Ꙥ', + 'ꙧ' => 'Ꙧ', + 'ꙩ' => 'Ꙩ', + 'ꙫ' => 'Ꙫ', + 'ꙭ' => 'Ꙭ', + 'ꚁ' => 'Ꚁ', + 'ꚃ' => 'Ꚃ', + 'ꚅ' => 'Ꚅ', + 'ꚇ' => 'Ꚇ', + 'ꚉ' => 'Ꚉ', + 'ꚋ' => 'Ꚋ', + 'ꚍ' => 'Ꚍ', + 'ꚏ' => 'Ꚏ', + 'ꚑ' => 'Ꚑ', + 'ꚓ' => 'Ꚓ', + 'ꚕ' => 'Ꚕ', + 'ꚗ' => 'Ꚗ', + 'ꚙ' => 'Ꚙ', + 'ꚛ' => 'Ꚛ', + 'ꜣ' => 'Ꜣ', + 'ꜥ' => 'Ꜥ', + 'ꜧ' => 'Ꜧ', + 'ꜩ' => 'Ꜩ', + 'ꜫ' => 'Ꜫ', + 'ꜭ' => 'Ꜭ', + 'ꜯ' => 'Ꜯ', + 'ꜳ' => 'Ꜳ', + 'ꜵ' => 'Ꜵ', + 'ꜷ' => 'Ꜷ', + 'ꜹ' => 'Ꜹ', + 'ꜻ' => 'Ꜻ', + 'ꜽ' => 'Ꜽ', + 'ꜿ' => 'Ꜿ', + 'ꝁ' => 'Ꝁ', + 'ꝃ' => 'Ꝃ', + 'ꝅ' => 'Ꝅ', + 'ꝇ' => 'Ꝇ', + 'ꝉ' => 'Ꝉ', + 'ꝋ' => 'Ꝋ', + 'ꝍ' => 'Ꝍ', + 'ꝏ' => 'Ꝏ', + 'ꝑ' => 'Ꝑ', + 'ꝓ' => 'Ꝓ', + 'ꝕ' => 'Ꝕ', + 'ꝗ' => 'Ꝗ', + 'ꝙ' => 'Ꝙ', + 'ꝛ' => 'Ꝛ', + 'ꝝ' => 'Ꝝ', + 'ꝟ' => 'Ꝟ', + 'ꝡ' => 'Ꝡ', + 'ꝣ' => 'Ꝣ', + 'ꝥ' => 'Ꝥ', + 'ꝧ' => 'Ꝧ', + 'ꝩ' => 'Ꝩ', + 'ꝫ' => 'Ꝫ', + 'ꝭ' => 'Ꝭ', + 'ꝯ' => 'Ꝯ', + 'ꝺ' => 'Ꝺ', + 'ꝼ' => 'Ꝼ', + 'ꝿ' => 'Ꝿ', + 'ꞁ' => 'Ꞁ', + 'ꞃ' => 'Ꞃ', + 'ꞅ' => 'Ꞅ', + 'ꞇ' => 'Ꞇ', + 'ꞌ' => 'Ꞌ', + 'ꞑ' => 'Ꞑ', + 'ꞓ' => 'Ꞓ', + 'ꞔ' => 'Ꞔ', + 'ꞗ' => 'Ꞗ', + 'ꞙ' => 'Ꞙ', + 'ꞛ' => 'Ꞛ', + 'ꞝ' => 'Ꞝ', + 'ꞟ' => 'Ꞟ', + 'ꞡ' => 'Ꞡ', + 'ꞣ' => 'Ꞣ', + 'ꞥ' => 'Ꞥ', + 'ꞧ' => 'Ꞧ', + 'ꞩ' => 'Ꞩ', + 'ꞵ' => 'Ꞵ', + 'ꞷ' => 'Ꞷ', + 'ꞹ' => 'Ꞹ', + 'ꞻ' => 'Ꞻ', + 'ꞽ' => 'Ꞽ', + 'ꞿ' => 'Ꞿ', + 'ꟃ' => 'Ꟃ', + 'ꟈ' => 'Ꟈ', + 'ꟊ' => 'Ꟊ', + 'ꟶ' => 'Ꟶ', + 'ꭓ' => 'Ꭓ', + 'ꭰ' => 'Ꭰ', + 'ꭱ' => 'Ꭱ', + 'ꭲ' => 'Ꭲ', + 'ꭳ' => 'Ꭳ', + 'ꭴ' => 'Ꭴ', + 'ꭵ' => 'Ꭵ', + 'ꭶ' => 'Ꭶ', + 'ꭷ' => 'Ꭷ', + 'ꭸ' => 'Ꭸ', + 'ꭹ' => 'Ꭹ', + 'ꭺ' => 'Ꭺ', + 'ꭻ' => 'Ꭻ', + 'ꭼ' => 'Ꭼ', + 'ꭽ' => 'Ꭽ', + 'ꭾ' => 'Ꭾ', + 'ꭿ' => 'Ꭿ', + 'ꮀ' => 'Ꮀ', + 'ꮁ' => 'Ꮁ', + 'ꮂ' => 'Ꮂ', + 'ꮃ' => 'Ꮃ', + 'ꮄ' => 'Ꮄ', + 'ꮅ' => 'Ꮅ', + 'ꮆ' => 'Ꮆ', + 'ꮇ' => 'Ꮇ', + 'ꮈ' => 'Ꮈ', + 'ꮉ' => 'Ꮉ', + 'ꮊ' => 'Ꮊ', + 'ꮋ' => 'Ꮋ', + 'ꮌ' => 'Ꮌ', + 'ꮍ' => 'Ꮍ', + 'ꮎ' => 'Ꮎ', + 'ꮏ' => 'Ꮏ', + 'ꮐ' => 'Ꮐ', + 'ꮑ' => 'Ꮑ', + 'ꮒ' => 'Ꮒ', + 'ꮓ' => 'Ꮓ', + 'ꮔ' => 'Ꮔ', + 'ꮕ' => 'Ꮕ', + 'ꮖ' => 'Ꮖ', + 'ꮗ' => 'Ꮗ', + 'ꮘ' => 'Ꮘ', + 'ꮙ' => 'Ꮙ', + 'ꮚ' => 'Ꮚ', + 'ꮛ' => 'Ꮛ', + 'ꮜ' => 'Ꮜ', + 'ꮝ' => 'Ꮝ', + 'ꮞ' => 'Ꮞ', + 'ꮟ' => 'Ꮟ', + 'ꮠ' => 'Ꮠ', + 'ꮡ' => 'Ꮡ', + 'ꮢ' => 'Ꮢ', + 'ꮣ' => 'Ꮣ', + 'ꮤ' => 'Ꮤ', + 'ꮥ' => 'Ꮥ', + 'ꮦ' => 'Ꮦ', + 'ꮧ' => 'Ꮧ', + 'ꮨ' => 'Ꮨ', + 'ꮩ' => 'Ꮩ', + 'ꮪ' => 'Ꮪ', + 'ꮫ' => 'Ꮫ', + 'ꮬ' => 'Ꮬ', + 'ꮭ' => 'Ꮭ', + 'ꮮ' => 'Ꮮ', + 'ꮯ' => 'Ꮯ', + 'ꮰ' => 'Ꮰ', + 'ꮱ' => 'Ꮱ', + 'ꮲ' => 'Ꮲ', + 'ꮳ' => 'Ꮳ', + 'ꮴ' => 'Ꮴ', + 'ꮵ' => 'Ꮵ', + 'ꮶ' => 'Ꮶ', + 'ꮷ' => 'Ꮷ', + 'ꮸ' => 'Ꮸ', + 'ꮹ' => 'Ꮹ', + 'ꮺ' => 'Ꮺ', + 'ꮻ' => 'Ꮻ', + 'ꮼ' => 'Ꮼ', + 'ꮽ' => 'Ꮽ', + 'ꮾ' => 'Ꮾ', + 'ꮿ' => 'Ꮿ', + 'a' => 'A', + 'b' => 'B', + 'c' => 'C', + 'd' => 'D', + 'e' => 'E', + 'f' => 'F', + 'g' => 'G', + 'h' => 'H', + 'i' => 'I', + 'j' => 'J', + 'k' => 'K', + 'l' => 'L', + 'm' => 'M', + 'n' => 'N', + 'o' => 'O', + 'p' => 'P', + 'q' => 'Q', + 'r' => 'R', + 's' => 'S', + 't' => 'T', + 'u' => 'U', + 'v' => 'V', + 'w' => 'W', + 'x' => 'X', + 'y' => 'Y', + 'z' => 'Z', + '𐐨' => '𐐀', + '𐐩' => '𐐁', + '𐐪' => '𐐂', + '𐐫' => '𐐃', + '𐐬' => '𐐄', + '𐐭' => '𐐅', + '𐐮' => '𐐆', + '𐐯' => '𐐇', + '𐐰' => '𐐈', + '𐐱' => '𐐉', + '𐐲' => '𐐊', + '𐐳' => '𐐋', + '𐐴' => '𐐌', + '𐐵' => '𐐍', + '𐐶' => '𐐎', + '𐐷' => '𐐏', + '𐐸' => '𐐐', + '𐐹' => '𐐑', + '𐐺' => '𐐒', + '𐐻' => '𐐓', + '𐐼' => '𐐔', + '𐐽' => '𐐕', + '𐐾' => '𐐖', + '𐐿' => '𐐗', + '𐑀' => '𐐘', + '𐑁' => '𐐙', + '𐑂' => '𐐚', + '𐑃' => '𐐛', + '𐑄' => '𐐜', + '𐑅' => '𐐝', + '𐑆' => '𐐞', + '𐑇' => '𐐟', + '𐑈' => '𐐠', + '𐑉' => '𐐡', + '𐑊' => '𐐢', + '𐑋' => '𐐣', + '𐑌' => '𐐤', + '𐑍' => '𐐥', + '𐑎' => '𐐦', + '𐑏' => '𐐧', + '𐓘' => '𐒰', + '𐓙' => '𐒱', + '𐓚' => '𐒲', + '𐓛' => '𐒳', + '𐓜' => '𐒴', + '𐓝' => '𐒵', + '𐓞' => '𐒶', + '𐓟' => '𐒷', + '𐓠' => '𐒸', + '𐓡' => '𐒹', + '𐓢' => '𐒺', + '𐓣' => '𐒻', + '𐓤' => '𐒼', + '𐓥' => '𐒽', + '𐓦' => '𐒾', + '𐓧' => '𐒿', + '𐓨' => '𐓀', + '𐓩' => '𐓁', + '𐓪' => '𐓂', + '𐓫' => '𐓃', + '𐓬' => '𐓄', + '𐓭' => '𐓅', + '𐓮' => '𐓆', + '𐓯' => '𐓇', + '𐓰' => '𐓈', + '𐓱' => '𐓉', + '𐓲' => '𐓊', + '𐓳' => '𐓋', + '𐓴' => '𐓌', + '𐓵' => '𐓍', + '𐓶' => '𐓎', + '𐓷' => '𐓏', + '𐓸' => '𐓐', + '𐓹' => '𐓑', + '𐓺' => '𐓒', + '𐓻' => '𐓓', + '𐳀' => '𐲀', + '𐳁' => '𐲁', + '𐳂' => '𐲂', + '𐳃' => '𐲃', + '𐳄' => '𐲄', + '𐳅' => '𐲅', + '𐳆' => '𐲆', + '𐳇' => '𐲇', + '𐳈' => '𐲈', + '𐳉' => '𐲉', + '𐳊' => '𐲊', + '𐳋' => '𐲋', + '𐳌' => '𐲌', + '𐳍' => '𐲍', + '𐳎' => '𐲎', + '𐳏' => '𐲏', + '𐳐' => '𐲐', + '𐳑' => '𐲑', + '𐳒' => '𐲒', + '𐳓' => '𐲓', + '𐳔' => '𐲔', + '𐳕' => '𐲕', + '𐳖' => '𐲖', + '𐳗' => '𐲗', + '𐳘' => '𐲘', + '𐳙' => '𐲙', + '𐳚' => '𐲚', + '𐳛' => '𐲛', + '𐳜' => '𐲜', + '𐳝' => '𐲝', + '𐳞' => '𐲞', + '𐳟' => '𐲟', + '𐳠' => '𐲠', + '𐳡' => '𐲡', + '𐳢' => '𐲢', + '𐳣' => '𐲣', + '𐳤' => '𐲤', + '𐳥' => '𐲥', + '𐳦' => '𐲦', + '𐳧' => '𐲧', + '𐳨' => '𐲨', + '𐳩' => '𐲩', + '𐳪' => '𐲪', + '𐳫' => '𐲫', + '𐳬' => '𐲬', + '𐳭' => '𐲭', + '𐳮' => '𐲮', + '𐳯' => '𐲯', + '𐳰' => '𐲰', + '𐳱' => '𐲱', + '𐳲' => '𐲲', + '𑣀' => '𑢠', + '𑣁' => '𑢡', + '𑣂' => '𑢢', + '𑣃' => '𑢣', + '𑣄' => '𑢤', + '𑣅' => '𑢥', + '𑣆' => '𑢦', + '𑣇' => '𑢧', + '𑣈' => '𑢨', + '𑣉' => '𑢩', + '𑣊' => '𑢪', + '𑣋' => '𑢫', + '𑣌' => '𑢬', + '𑣍' => '𑢭', + '𑣎' => '𑢮', + '𑣏' => '𑢯', + '𑣐' => '𑢰', + '𑣑' => '𑢱', + '𑣒' => '𑢲', + '𑣓' => '𑢳', + '𑣔' => '𑢴', + '𑣕' => '𑢵', + '𑣖' => '𑢶', + '𑣗' => '𑢷', + '𑣘' => '𑢸', + '𑣙' => '𑢹', + '𑣚' => '𑢺', + '𑣛' => '𑢻', + '𑣜' => '𑢼', + '𑣝' => '𑢽', + '𑣞' => '𑢾', + '𑣟' => '𑢿', + '𖹠' => '𖹀', + '𖹡' => '𖹁', + '𖹢' => '𖹂', + '𖹣' => '𖹃', + '𖹤' => '𖹄', + '𖹥' => '𖹅', + '𖹦' => '𖹆', + '𖹧' => '𖹇', + '𖹨' => '𖹈', + '𖹩' => '𖹉', + '𖹪' => '𖹊', + '𖹫' => '𖹋', + '𖹬' => '𖹌', + '𖹭' => '𖹍', + '𖹮' => '𖹎', + '𖹯' => '𖹏', + '𖹰' => '𖹐', + '𖹱' => '𖹑', + '𖹲' => '𖹒', + '𖹳' => '𖹓', + '𖹴' => '𖹔', + '𖹵' => '𖹕', + '𖹶' => '𖹖', + '𖹷' => '𖹗', + '𖹸' => '𖹘', + '𖹹' => '𖹙', + '𖹺' => '𖹚', + '𖹻' => '𖹛', + '𖹼' => '𖹜', + '𖹽' => '𖹝', + '𖹾' => '𖹞', + '𖹿' => '𖹟', + '𞤢' => '𞤀', + '𞤣' => '𞤁', + '𞤤' => '𞤂', + '𞤥' => '𞤃', + '𞤦' => '𞤄', + '𞤧' => '𞤅', + '𞤨' => '𞤆', + '𞤩' => '𞤇', + '𞤪' => '𞤈', + '𞤫' => '𞤉', + '𞤬' => '𞤊', + '𞤭' => '𞤋', + '𞤮' => '𞤌', + '𞤯' => '𞤍', + '𞤰' => '𞤎', + '𞤱' => '𞤏', + '𞤲' => '𞤐', + '𞤳' => '𞤑', + '𞤴' => '𞤒', + '𞤵' => '𞤓', + '𞤶' => '𞤔', + '𞤷' => '𞤕', + '𞤸' => '𞤖', + '𞤹' => '𞤗', + '𞤺' => '𞤘', + '𞤻' => '𞤙', + '𞤼' => '𞤚', + '𞤽' => '𞤛', + '𞤾' => '𞤜', + '𞤿' => '𞤝', + '𞥀' => '𞤞', + '𞥁' => '𞤟', + '𞥂' => '𞤠', + '𞥃' => '𞤡', + 'ß' => 'SS', + 'ff' => 'FF', + 'fi' => 'FI', + 'fl' => 'FL', + 'ffi' => 'FFI', + 'ffl' => 'FFL', + 'ſt' => 'ST', + 'st' => 'ST', + 'և' => 'ԵՒ', + 'ﬓ' => 'ՄՆ', + 'ﬔ' => 'ՄԵ', + 'ﬕ' => 'ՄԻ', + 'ﬖ' => 'ՎՆ', + 'ﬗ' => 'ՄԽ', + 'ʼn' => 'ʼN', + 'ΐ' => 'Ϊ́', + 'ΰ' => 'Ϋ́', + 'ǰ' => 'J̌', + 'ẖ' => 'H̱', + 'ẗ' => 'T̈', + 'ẘ' => 'W̊', + 'ẙ' => 'Y̊', + 'ẚ' => 'Aʾ', + 'ὐ' => 'Υ̓', + 'ὒ' => 'Υ̓̀', + 'ὔ' => 'Υ̓́', + 'ὖ' => 'Υ̓͂', + 'ᾶ' => 'Α͂', + 'ῆ' => 'Η͂', + 'ῒ' => 'Ϊ̀', + 'ΐ' => 'Ϊ́', + 'ῖ' => 'Ι͂', + 'ῗ' => 'Ϊ͂', + 'ῢ' => 'Ϋ̀', + 'ΰ' => 'Ϋ́', + 'ῤ' => 'Ρ̓', + 'ῦ' => 'Υ͂', + 'ῧ' => 'Ϋ͂', + 'ῶ' => 'Ω͂', + 'ᾈ' => 'ἈΙ', + 'ᾉ' => 'ἉΙ', + 'ᾊ' => 'ἊΙ', + 'ᾋ' => 'ἋΙ', + 'ᾌ' => 'ἌΙ', + 'ᾍ' => 'ἍΙ', + 'ᾎ' => 'ἎΙ', + 'ᾏ' => 'ἏΙ', + 'ᾘ' => 'ἨΙ', + 'ᾙ' => 'ἩΙ', + 'ᾚ' => 'ἪΙ', + 'ᾛ' => 'ἫΙ', + 'ᾜ' => 'ἬΙ', + 'ᾝ' => 'ἭΙ', + 'ᾞ' => 'ἮΙ', + 'ᾟ' => 'ἯΙ', + 'ᾨ' => 'ὨΙ', + 'ᾩ' => 'ὩΙ', + 'ᾪ' => 'ὪΙ', + 'ᾫ' => 'ὫΙ', + 'ᾬ' => 'ὬΙ', + 'ᾭ' => 'ὭΙ', + 'ᾮ' => 'ὮΙ', + 'ᾯ' => 'ὯΙ', + 'ᾼ' => 'ΑΙ', + 'ῌ' => 'ΗΙ', + 'ῼ' => 'ΩΙ', + 'ᾲ' => 'ᾺΙ', + 'ᾴ' => 'ΆΙ', + 'ῂ' => 'ῊΙ', + 'ῄ' => 'ΉΙ', + 'ῲ' => 'ῺΙ', + 'ῴ' => 'ΏΙ', + 'ᾷ' => 'Α͂Ι', + 'ῇ' => 'Η͂Ι', + 'ῷ' => 'Ω͂Ι', +); diff --git a/src/vendor/symfony/polyfill-mbstring/bootstrap.php b/src/vendor/symfony/polyfill-mbstring/bootstrap.php new file mode 100644 index 0000000..ecf1a03 --- /dev/null +++ b/src/vendor/symfony/polyfill-mbstring/bootstrap.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Mbstring as p; + +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + +if (!function_exists('mb_convert_encoding')) { + function mb_convert_encoding($string, $to_encoding, $from_encoding = null) { return p\Mbstring::mb_convert_encoding($string, $to_encoding, $from_encoding); } +} +if (!function_exists('mb_decode_mimeheader')) { + function mb_decode_mimeheader($string) { return p\Mbstring::mb_decode_mimeheader($string); } +} +if (!function_exists('mb_encode_mimeheader')) { + function mb_encode_mimeheader($string, $charset = null, $transfer_encoding = null, $newline = "\r\n", $indent = 0) { return p\Mbstring::mb_encode_mimeheader($string, $charset, $transfer_encoding, $newline, $indent); } +} +if (!function_exists('mb_decode_numericentity')) { + function mb_decode_numericentity($string, $map, $encoding = null) { return p\Mbstring::mb_decode_numericentity($string, $map, $encoding); } +} +if (!function_exists('mb_encode_numericentity')) { + function mb_encode_numericentity($string, $map, $encoding = null, $hex = false) { return p\Mbstring::mb_encode_numericentity($string, $map, $encoding, $hex); } +} +if (!function_exists('mb_convert_case')) { + function mb_convert_case($string, $mode, $encoding = null) { return p\Mbstring::mb_convert_case($string, $mode, $encoding); } +} +if (!function_exists('mb_internal_encoding')) { + function mb_internal_encoding($encoding = null) { return p\Mbstring::mb_internal_encoding($encoding); } +} +if (!function_exists('mb_language')) { + function mb_language($language = null) { return p\Mbstring::mb_language($language); } +} +if (!function_exists('mb_list_encodings')) { + function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); } +} +if (!function_exists('mb_encoding_aliases')) { + function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); } +} +if (!function_exists('mb_check_encoding')) { + function mb_check_encoding($value = null, $encoding = null) { return p\Mbstring::mb_check_encoding($value, $encoding); } +} +if (!function_exists('mb_detect_encoding')) { + function mb_detect_encoding($string, $encodings = null, $strict = false) { return p\Mbstring::mb_detect_encoding($string, $encodings, $strict); } +} +if (!function_exists('mb_detect_order')) { + function mb_detect_order($encoding = null) { return p\Mbstring::mb_detect_order($encoding); } +} +if (!function_exists('mb_parse_str')) { + function mb_parse_str($string, &$result = []) { parse_str($string, $result); return (bool) $result; } +} +if (!function_exists('mb_strlen')) { + function mb_strlen($string, $encoding = null) { return p\Mbstring::mb_strlen($string, $encoding); } +} +if (!function_exists('mb_strpos')) { + function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strpos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strtolower')) { + function mb_strtolower($string, $encoding = null) { return p\Mbstring::mb_strtolower($string, $encoding); } +} +if (!function_exists('mb_strtoupper')) { + function mb_strtoupper($string, $encoding = null) { return p\Mbstring::mb_strtoupper($string, $encoding); } +} +if (!function_exists('mb_substitute_character')) { + function mb_substitute_character($substitute_character = null) { return p\Mbstring::mb_substitute_character($substitute_character); } +} +if (!function_exists('mb_substr')) { + function mb_substr($string, $start, $length = 2147483647, $encoding = null) { return p\Mbstring::mb_substr($string, $start, $length, $encoding); } +} +if (!function_exists('mb_stripos')) { + function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_stripos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_stristr')) { + function mb_stristr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_stristr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strrchr')) { + function mb_strrchr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrchr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strrichr')) { + function mb_strrichr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrichr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strripos')) { + function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strripos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strrpos')) { + function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strrpos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strstr')) { + function mb_strstr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strstr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_get_info')) { + function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); } +} +if (!function_exists('mb_http_output')) { + function mb_http_output($encoding = null) { return p\Mbstring::mb_http_output($encoding); } +} +if (!function_exists('mb_strwidth')) { + function mb_strwidth($string, $encoding = null) { return p\Mbstring::mb_strwidth($string, $encoding); } +} +if (!function_exists('mb_substr_count')) { + function mb_substr_count($haystack, $needle, $encoding = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $encoding); } +} +if (!function_exists('mb_output_handler')) { + function mb_output_handler($string, $status) { return p\Mbstring::mb_output_handler($string, $status); } +} +if (!function_exists('mb_http_input')) { + function mb_http_input($type = null) { return p\Mbstring::mb_http_input($type); } +} + +if (!function_exists('mb_convert_variables')) { + function mb_convert_variables($to_encoding, $from_encoding, &...$vars) { return p\Mbstring::mb_convert_variables($to_encoding, $from_encoding, ...$vars); } +} + +if (!function_exists('mb_ord')) { + function mb_ord($string, $encoding = null) { return p\Mbstring::mb_ord($string, $encoding); } +} +if (!function_exists('mb_chr')) { + function mb_chr($codepoint, $encoding = null) { return p\Mbstring::mb_chr($codepoint, $encoding); } +} +if (!function_exists('mb_scrub')) { + function mb_scrub($string, $encoding = null) { $encoding = null === $encoding ? mb_internal_encoding() : $encoding; return mb_convert_encoding($string, $encoding, $encoding); } +} +if (!function_exists('mb_str_split')) { + function mb_str_split($string, $length = 1, $encoding = null) { return p\Mbstring::mb_str_split($string, $length, $encoding); } +} + +if (!function_exists('mb_str_pad')) { + function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Mbstring::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); } +} + +if (extension_loaded('mbstring')) { + return; +} + +if (!defined('MB_CASE_UPPER')) { + define('MB_CASE_UPPER', 0); +} +if (!defined('MB_CASE_LOWER')) { + define('MB_CASE_LOWER', 1); +} +if (!defined('MB_CASE_TITLE')) { + define('MB_CASE_TITLE', 2); +} diff --git a/src/vendor/symfony/polyfill-mbstring/bootstrap80.php b/src/vendor/symfony/polyfill-mbstring/bootstrap80.php new file mode 100644 index 0000000..2f9fb5b --- /dev/null +++ b/src/vendor/symfony/polyfill-mbstring/bootstrap80.php @@ -0,0 +1,147 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Mbstring as p; + +if (!function_exists('mb_convert_encoding')) { + function mb_convert_encoding(array|string|null $string, ?string $to_encoding, array|string|null $from_encoding = null): array|string|false { return p\Mbstring::mb_convert_encoding($string ?? '', (string) $to_encoding, $from_encoding); } +} +if (!function_exists('mb_decode_mimeheader')) { + function mb_decode_mimeheader(?string $string): string { return p\Mbstring::mb_decode_mimeheader((string) $string); } +} +if (!function_exists('mb_encode_mimeheader')) { + function mb_encode_mimeheader(?string $string, ?string $charset = null, ?string $transfer_encoding = null, ?string $newline = "\r\n", ?int $indent = 0): string { return p\Mbstring::mb_encode_mimeheader((string) $string, $charset, $transfer_encoding, (string) $newline, (int) $indent); } +} +if (!function_exists('mb_decode_numericentity')) { + function mb_decode_numericentity(?string $string, array $map, ?string $encoding = null): string { return p\Mbstring::mb_decode_numericentity((string) $string, $map, $encoding); } +} +if (!function_exists('mb_encode_numericentity')) { + function mb_encode_numericentity(?string $string, array $map, ?string $encoding = null, ?bool $hex = false): string { return p\Mbstring::mb_encode_numericentity((string) $string, $map, $encoding, (bool) $hex); } +} +if (!function_exists('mb_convert_case')) { + function mb_convert_case(?string $string, ?int $mode, ?string $encoding = null): string { return p\Mbstring::mb_convert_case((string) $string, (int) $mode, $encoding); } +} +if (!function_exists('mb_internal_encoding')) { + function mb_internal_encoding(?string $encoding = null): string|bool { return p\Mbstring::mb_internal_encoding($encoding); } +} +if (!function_exists('mb_language')) { + function mb_language(?string $language = null): string|bool { return p\Mbstring::mb_language($language); } +} +if (!function_exists('mb_list_encodings')) { + function mb_list_encodings(): array { return p\Mbstring::mb_list_encodings(); } +} +if (!function_exists('mb_encoding_aliases')) { + function mb_encoding_aliases(?string $encoding): array { return p\Mbstring::mb_encoding_aliases((string) $encoding); } +} +if (!function_exists('mb_check_encoding')) { + function mb_check_encoding(array|string|null $value = null, ?string $encoding = null): bool { return p\Mbstring::mb_check_encoding($value, $encoding); } +} +if (!function_exists('mb_detect_encoding')) { + function mb_detect_encoding(?string $string, array|string|null $encodings = null, ?bool $strict = false): string|false { return p\Mbstring::mb_detect_encoding((string) $string, $encodings, (bool) $strict); } +} +if (!function_exists('mb_detect_order')) { + function mb_detect_order(array|string|null $encoding = null): array|bool { return p\Mbstring::mb_detect_order($encoding); } +} +if (!function_exists('mb_parse_str')) { + function mb_parse_str(?string $string, &$result = []): bool { parse_str((string) $string, $result); return (bool) $result; } +} +if (!function_exists('mb_strlen')) { + function mb_strlen(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strlen((string) $string, $encoding); } +} +if (!function_exists('mb_strpos')) { + function mb_strpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strpos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strtolower')) { + function mb_strtolower(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtolower((string) $string, $encoding); } +} +if (!function_exists('mb_strtoupper')) { + function mb_strtoupper(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtoupper((string) $string, $encoding); } +} +if (!function_exists('mb_substitute_character')) { + function mb_substitute_character(string|int|null $substitute_character = null): string|int|bool { return p\Mbstring::mb_substitute_character($substitute_character); } +} +if (!function_exists('mb_substr')) { + function mb_substr(?string $string, ?int $start, ?int $length = null, ?string $encoding = null): string { return p\Mbstring::mb_substr((string) $string, (int) $start, $length, $encoding); } +} +if (!function_exists('mb_stripos')) { + function mb_stripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_stripos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_stristr')) { + function mb_stristr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_stristr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strrchr')) { + function mb_strrchr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrchr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strrichr')) { + function mb_strrichr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrichr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strripos')) { + function mb_strripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strripos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strrpos')) { + function mb_strrpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strrpos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strstr')) { + function mb_strstr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strstr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_get_info')) { + function mb_get_info(?string $type = 'all'): array|string|int|false { return p\Mbstring::mb_get_info((string) $type); } +} +if (!function_exists('mb_http_output')) { + function mb_http_output(?string $encoding = null): string|bool { return p\Mbstring::mb_http_output($encoding); } +} +if (!function_exists('mb_strwidth')) { + function mb_strwidth(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strwidth((string) $string, $encoding); } +} +if (!function_exists('mb_substr_count')) { + function mb_substr_count(?string $haystack, ?string $needle, ?string $encoding = null): int { return p\Mbstring::mb_substr_count((string) $haystack, (string) $needle, $encoding); } +} +if (!function_exists('mb_output_handler')) { + function mb_output_handler(?string $string, ?int $status): string { return p\Mbstring::mb_output_handler((string) $string, (int) $status); } +} +if (!function_exists('mb_http_input')) { + function mb_http_input(?string $type = null): array|string|false { return p\Mbstring::mb_http_input($type); } +} + +if (!function_exists('mb_convert_variables')) { + function mb_convert_variables(?string $to_encoding, array|string|null $from_encoding, mixed &$var, mixed &...$vars): string|false { return p\Mbstring::mb_convert_variables((string) $to_encoding, $from_encoding ?? '', $var, ...$vars); } +} + +if (!function_exists('mb_ord')) { + function mb_ord(?string $string, ?string $encoding = null): int|false { return p\Mbstring::mb_ord((string) $string, $encoding); } +} +if (!function_exists('mb_chr')) { + function mb_chr(?int $codepoint, ?string $encoding = null): string|false { return p\Mbstring::mb_chr((int) $codepoint, $encoding); } +} +if (!function_exists('mb_scrub')) { + function mb_scrub(?string $string, ?string $encoding = null): string { $encoding ??= mb_internal_encoding(); return mb_convert_encoding((string) $string, $encoding, $encoding); } +} +if (!function_exists('mb_str_split')) { + function mb_str_split(?string $string, ?int $length = 1, ?string $encoding = null): array { return p\Mbstring::mb_str_split((string) $string, (int) $length, $encoding); } +} + +if (!function_exists('mb_str_pad')) { + function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Mbstring::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); } +} + +if (extension_loaded('mbstring')) { + return; +} + +if (!defined('MB_CASE_UPPER')) { + define('MB_CASE_UPPER', 0); +} +if (!defined('MB_CASE_LOWER')) { + define('MB_CASE_LOWER', 1); +} +if (!defined('MB_CASE_TITLE')) { + define('MB_CASE_TITLE', 2); +} diff --git a/src/vendor/symfony/polyfill-mbstring/composer.json b/src/vendor/symfony/polyfill-mbstring/composer.json new file mode 100644 index 0000000..943e502 --- /dev/null +++ b/src/vendor/symfony/polyfill-mbstring/composer.json @@ -0,0 +1,41 @@ +{ + "name": "symfony/polyfill-mbstring", + "type": "library", + "description": "Symfony polyfill for the Mbstring extension", + "keywords": ["polyfill", "shim", "compatibility", "portable", "mbstring"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Mbstring\\": "" }, + "files": [ "bootstrap.php" ] + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/src/vendor/symfony/polyfill-php73/LICENSE b/src/vendor/symfony/polyfill-php73/LICENSE new file mode 100644 index 0000000..7536cae --- /dev/null +++ b/src/vendor/symfony/polyfill-php73/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/vendor/symfony/polyfill-php73/Php73.php b/src/vendor/symfony/polyfill-php73/Php73.php new file mode 100644 index 0000000..65c35a6 --- /dev/null +++ b/src/vendor/symfony/polyfill-php73/Php73.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php73; + +/** + * @author Gabriel Caruso + * @author Ion Bazan + * + * @internal + */ +final class Php73 +{ + public static $startAt = 1533462603; + + /** + * @param bool $asNum + * + * @return array|float|int + */ + public static function hrtime($asNum = false) + { + $ns = microtime(false); + $s = substr($ns, 11) - self::$startAt; + $ns = 1E9 * (float) $ns; + + if ($asNum) { + $ns += $s * 1E9; + + return \PHP_INT_SIZE === 4 ? $ns : (int) $ns; + } + + return [$s, (int) $ns]; + } +} diff --git a/src/vendor/symfony/polyfill-php73/README.md b/src/vendor/symfony/polyfill-php73/README.md new file mode 100644 index 0000000..032fafb --- /dev/null +++ b/src/vendor/symfony/polyfill-php73/README.md @@ -0,0 +1,18 @@ +Symfony Polyfill / Php73 +======================== + +This component provides functions added to PHP 7.3 core: + +- [`array_key_first`](https://php.net/array_key_first) +- [`array_key_last`](https://php.net/array_key_last) +- [`hrtime`](https://php.net/function.hrtime) +- [`is_countable`](https://php.net/is_countable) +- [`JsonException`](https://php.net/JsonException) + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/src/vendor/symfony/polyfill-php73/Resources/stubs/JsonException.php b/src/vendor/symfony/polyfill-php73/Resources/stubs/JsonException.php new file mode 100644 index 0000000..f06d6c2 --- /dev/null +++ b/src/vendor/symfony/polyfill-php73/Resources/stubs/JsonException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 70300) { + class JsonException extends Exception + { + } +} diff --git a/src/vendor/symfony/polyfill-php73/bootstrap.php b/src/vendor/symfony/polyfill-php73/bootstrap.php new file mode 100644 index 0000000..d6b2153 --- /dev/null +++ b/src/vendor/symfony/polyfill-php73/bootstrap.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php73 as p; + +if (\PHP_VERSION_ID >= 70300) { + return; +} + +if (!function_exists('is_countable')) { + function is_countable($value) { return is_array($value) || $value instanceof Countable || $value instanceof ResourceBundle || $value instanceof SimpleXmlElement; } +} +if (!function_exists('hrtime')) { + require_once __DIR__.'/Php73.php'; + p\Php73::$startAt = (int) microtime(true); + function hrtime($as_number = false) { return p\Php73::hrtime($as_number); } +} +if (!function_exists('array_key_first')) { + function array_key_first(array $array) { foreach ($array as $key => $value) { return $key; } } +} +if (!function_exists('array_key_last')) { + function array_key_last(array $array) { return key(array_slice($array, -1, 1, true)); } +} diff --git a/src/vendor/symfony/polyfill-php73/composer.json b/src/vendor/symfony/polyfill-php73/composer.json new file mode 100644 index 0000000..48295ef --- /dev/null +++ b/src/vendor/symfony/polyfill-php73/composer.json @@ -0,0 +1,36 @@ +{ + "name": "symfony/polyfill-php73", + "type": "library", + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "keywords": ["polyfill", "shim", "compatibility", "portable"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php73\\": "" }, + "files": [ "bootstrap.php" ], + "classmap": [ "Resources/stubs" ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/src/vendor/symfony/polyfill-php80/LICENSE b/src/vendor/symfony/polyfill-php80/LICENSE new file mode 100644 index 0000000..0ed3a24 --- /dev/null +++ b/src/vendor/symfony/polyfill-php80/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/vendor/symfony/polyfill-php80/Php80.php b/src/vendor/symfony/polyfill-php80/Php80.php new file mode 100644 index 0000000..362dd1a --- /dev/null +++ b/src/vendor/symfony/polyfill-php80/Php80.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php80; + +/** + * @author Ion Bazan + * @author Nico Oelgart + * @author Nicolas Grekas + * + * @internal + */ +final class Php80 +{ + public static function fdiv(float $dividend, float $divisor): float + { + return @($dividend / $divisor); + } + + public static function get_debug_type($value): string + { + switch (true) { + case null === $value: return 'null'; + case \is_bool($value): return 'bool'; + case \is_string($value): return 'string'; + case \is_array($value): return 'array'; + case \is_int($value): return 'int'; + case \is_float($value): return 'float'; + case \is_object($value): break; + case $value instanceof \__PHP_Incomplete_Class: return '__PHP_Incomplete_Class'; + default: + if (null === $type = @get_resource_type($value)) { + return 'unknown'; + } + + if ('Unknown' === $type) { + $type = 'closed'; + } + + return "resource ($type)"; + } + + $class = \get_class($value); + + if (false === strpos($class, '@')) { + return $class; + } + + return (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous'; + } + + public static function get_resource_id($res): int + { + if (!\is_resource($res) && null === @get_resource_type($res)) { + throw new \TypeError(sprintf('Argument 1 passed to get_resource_id() must be of the type resource, %s given', get_debug_type($res))); + } + + return (int) $res; + } + + public static function preg_last_error_msg(): string + { + switch (preg_last_error()) { + case \PREG_INTERNAL_ERROR: + return 'Internal error'; + case \PREG_BAD_UTF8_ERROR: + return 'Malformed UTF-8 characters, possibly incorrectly encoded'; + case \PREG_BAD_UTF8_OFFSET_ERROR: + return 'The offset did not correspond to the beginning of a valid UTF-8 code point'; + case \PREG_BACKTRACK_LIMIT_ERROR: + return 'Backtrack limit exhausted'; + case \PREG_RECURSION_LIMIT_ERROR: + return 'Recursion limit exhausted'; + case \PREG_JIT_STACKLIMIT_ERROR: + return 'JIT stack limit exhausted'; + case \PREG_NO_ERROR: + return 'No error'; + default: + return 'Unknown error'; + } + } + + public static function str_contains(string $haystack, string $needle): bool + { + return '' === $needle || false !== strpos($haystack, $needle); + } + + public static function str_starts_with(string $haystack, string $needle): bool + { + return 0 === strncmp($haystack, $needle, \strlen($needle)); + } + + public static function str_ends_with(string $haystack, string $needle): bool + { + if ('' === $needle || $needle === $haystack) { + return true; + } + + if ('' === $haystack) { + return false; + } + + $needleLength = \strlen($needle); + + return $needleLength <= \strlen($haystack) && 0 === substr_compare($haystack, $needle, -$needleLength); + } +} diff --git a/src/vendor/symfony/polyfill-php80/PhpToken.php b/src/vendor/symfony/polyfill-php80/PhpToken.php new file mode 100644 index 0000000..fe6e691 --- /dev/null +++ b/src/vendor/symfony/polyfill-php80/PhpToken.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php80; + +/** + * @author Fedonyuk Anton + * + * @internal + */ +class PhpToken implements \Stringable +{ + /** + * @var int + */ + public $id; + + /** + * @var string + */ + public $text; + + /** + * @var int + */ + public $line; + + /** + * @var int + */ + public $pos; + + public function __construct(int $id, string $text, int $line = -1, int $position = -1) + { + $this->id = $id; + $this->text = $text; + $this->line = $line; + $this->pos = $position; + } + + public function getTokenName(): ?string + { + if ('UNKNOWN' === $name = token_name($this->id)) { + $name = \strlen($this->text) > 1 || \ord($this->text) < 32 ? null : $this->text; + } + + return $name; + } + + /** + * @param int|string|array $kind + */ + public function is($kind): bool + { + foreach ((array) $kind as $value) { + if (\in_array($value, [$this->id, $this->text], true)) { + return true; + } + } + + return false; + } + + public function isIgnorable(): bool + { + return \in_array($this->id, [\T_WHITESPACE, \T_COMMENT, \T_DOC_COMMENT, \T_OPEN_TAG], true); + } + + public function __toString(): string + { + return (string) $this->text; + } + + /** + * @return static[] + */ + public static function tokenize(string $code, int $flags = 0): array + { + $line = 1; + $position = 0; + $tokens = token_get_all($code, $flags); + foreach ($tokens as $index => $token) { + if (\is_string($token)) { + $id = \ord($token); + $text = $token; + } else { + [$id, $text, $line] = $token; + } + $tokens[$index] = new static($id, $text, $line, $position); + $position += \strlen($text); + } + + return $tokens; + } +} diff --git a/src/vendor/symfony/polyfill-php80/README.md b/src/vendor/symfony/polyfill-php80/README.md new file mode 100644 index 0000000..3816c55 --- /dev/null +++ b/src/vendor/symfony/polyfill-php80/README.md @@ -0,0 +1,25 @@ +Symfony Polyfill / Php80 +======================== + +This component provides features added to PHP 8.0 core: + +- [`Stringable`](https://php.net/stringable) interface +- [`fdiv`](https://php.net/fdiv) +- [`ValueError`](https://php.net/valueerror) class +- [`UnhandledMatchError`](https://php.net/unhandledmatcherror) class +- `FILTER_VALIDATE_BOOL` constant +- [`get_debug_type`](https://php.net/get_debug_type) +- [`PhpToken`](https://php.net/phptoken) class +- [`preg_last_error_msg`](https://php.net/preg_last_error_msg) +- [`str_contains`](https://php.net/str_contains) +- [`str_starts_with`](https://php.net/str_starts_with) +- [`str_ends_with`](https://php.net/str_ends_with) +- [`get_resource_id`](https://php.net/get_resource_id) + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/src/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php b/src/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php new file mode 100644 index 0000000..2b95542 --- /dev/null +++ b/src/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#[Attribute(Attribute::TARGET_CLASS)] +final class Attribute +{ + public const TARGET_CLASS = 1; + public const TARGET_FUNCTION = 2; + public const TARGET_METHOD = 4; + public const TARGET_PROPERTY = 8; + public const TARGET_CLASS_CONSTANT = 16; + public const TARGET_PARAMETER = 32; + public const TARGET_ALL = 63; + public const IS_REPEATABLE = 64; + + /** @var int */ + public $flags; + + public function __construct(int $flags = self::TARGET_ALL) + { + $this->flags = $flags; + } +} diff --git a/src/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php b/src/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php new file mode 100644 index 0000000..bd1212f --- /dev/null +++ b/src/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80000 && extension_loaded('tokenizer')) { + class PhpToken extends Symfony\Polyfill\Php80\PhpToken + { + } +} diff --git a/src/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php b/src/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php new file mode 100644 index 0000000..7c62d75 --- /dev/null +++ b/src/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80000) { + interface Stringable + { + /** + * @return string + */ + public function __toString(); + } +} diff --git a/src/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php b/src/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php new file mode 100644 index 0000000..01c6c6c --- /dev/null +++ b/src/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80000) { + class UnhandledMatchError extends Error + { + } +} diff --git a/src/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php b/src/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php new file mode 100644 index 0000000..783dbc2 --- /dev/null +++ b/src/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80000) { + class ValueError extends Error + { + } +} diff --git a/src/vendor/symfony/polyfill-php80/bootstrap.php b/src/vendor/symfony/polyfill-php80/bootstrap.php new file mode 100644 index 0000000..e5f7dbc --- /dev/null +++ b/src/vendor/symfony/polyfill-php80/bootstrap.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php80 as p; + +if (\PHP_VERSION_ID >= 80000) { + return; +} + +if (!defined('FILTER_VALIDATE_BOOL') && defined('FILTER_VALIDATE_BOOLEAN')) { + define('FILTER_VALIDATE_BOOL', \FILTER_VALIDATE_BOOLEAN); +} + +if (!function_exists('fdiv')) { + function fdiv(float $num1, float $num2): float { return p\Php80::fdiv($num1, $num2); } +} +if (!function_exists('preg_last_error_msg')) { + function preg_last_error_msg(): string { return p\Php80::preg_last_error_msg(); } +} +if (!function_exists('str_contains')) { + function str_contains(?string $haystack, ?string $needle): bool { return p\Php80::str_contains($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('str_starts_with')) { + function str_starts_with(?string $haystack, ?string $needle): bool { return p\Php80::str_starts_with($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('str_ends_with')) { + function str_ends_with(?string $haystack, ?string $needle): bool { return p\Php80::str_ends_with($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('get_debug_type')) { + function get_debug_type($value): string { return p\Php80::get_debug_type($value); } +} +if (!function_exists('get_resource_id')) { + function get_resource_id($resource): int { return p\Php80::get_resource_id($resource); } +} diff --git a/src/vendor/symfony/polyfill-php80/composer.json b/src/vendor/symfony/polyfill-php80/composer.json new file mode 100644 index 0000000..f1801f4 --- /dev/null +++ b/src/vendor/symfony/polyfill-php80/composer.json @@ -0,0 +1,40 @@ +{ + "name": "symfony/polyfill-php80", + "type": "library", + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "keywords": ["polyfill", "shim", "compatibility", "portable"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php80\\": "" }, + "files": [ "bootstrap.php" ], + "classmap": [ "Resources/stubs" ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/src/vendor/symfony/polyfill-php81/LICENSE b/src/vendor/symfony/polyfill-php81/LICENSE new file mode 100644 index 0000000..99c6bdf --- /dev/null +++ b/src/vendor/symfony/polyfill-php81/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2021-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/vendor/symfony/polyfill-php81/Php81.php b/src/vendor/symfony/polyfill-php81/Php81.php new file mode 100644 index 0000000..f0507b7 --- /dev/null +++ b/src/vendor/symfony/polyfill-php81/Php81.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php81; + +/** + * @author Nicolas Grekas + * + * @internal + */ +final class Php81 +{ + public static function array_is_list(array $array): bool + { + if ([] === $array || $array === array_values($array)) { + return true; + } + + $nextKey = -1; + + foreach ($array as $k => $v) { + if ($k !== ++$nextKey) { + return false; + } + } + + return true; + } +} diff --git a/src/vendor/symfony/polyfill-php81/README.md b/src/vendor/symfony/polyfill-php81/README.md new file mode 100644 index 0000000..c07ef78 --- /dev/null +++ b/src/vendor/symfony/polyfill-php81/README.md @@ -0,0 +1,18 @@ +Symfony Polyfill / Php81 +======================== + +This component provides features added to PHP 8.1 core: + +- [`array_is_list`](https://php.net/array_is_list) +- [`enum_exists`](https://php.net/enum-exists) +- [`MYSQLI_REFRESH_REPLICA`](https://php.net/mysqli.constants#constantmysqli-refresh-replica) constant +- [`ReturnTypeWillChange`](https://wiki.php.net/rfc/internal_method_return_types) +- [`CURLStringFile`](https://php.net/CURLStringFile) (but only if PHP >= 7.4 is used) + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/src/vendor/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php b/src/vendor/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php new file mode 100644 index 0000000..eb5952e --- /dev/null +++ b/src/vendor/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID >= 70400 && extension_loaded('curl')) { + /** + * @property string $data + */ + class CURLStringFile extends CURLFile + { + private $data; + + public function __construct(string $data, string $postname, string $mime = 'application/octet-stream') + { + $this->data = $data; + parent::__construct('data://application/octet-stream;base64,'.base64_encode($data), $mime, $postname); + } + + public function __set(string $name, $value): void + { + if ('data' !== $name) { + $this->$name = $value; + + return; + } + + if (is_object($value) ? !method_exists($value, '__toString') : !is_scalar($value)) { + throw new \TypeError('Cannot assign '.gettype($value).' to property CURLStringFile::$data of type string'); + } + + $this->name = 'data://application/octet-stream;base64,'.base64_encode($value); + } + + public function __isset(string $name): bool + { + return isset($this->$name); + } + + public function &__get(string $name) + { + return $this->$name; + } + } +} diff --git a/src/vendor/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php b/src/vendor/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php new file mode 100644 index 0000000..cb7720a --- /dev/null +++ b/src/vendor/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80100) { + #[Attribute(Attribute::TARGET_METHOD)] + final class ReturnTypeWillChange + { + public function __construct() + { + } + } +} diff --git a/src/vendor/symfony/polyfill-php81/bootstrap.php b/src/vendor/symfony/polyfill-php81/bootstrap.php new file mode 100644 index 0000000..9f872e0 --- /dev/null +++ b/src/vendor/symfony/polyfill-php81/bootstrap.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php81 as p; + +if (\PHP_VERSION_ID >= 80100) { + return; +} + +if (defined('MYSQLI_REFRESH_SLAVE') && !defined('MYSQLI_REFRESH_REPLICA')) { + define('MYSQLI_REFRESH_REPLICA', 64); +} + +if (!function_exists('array_is_list')) { + function array_is_list(array $array): bool { return p\Php81::array_is_list($array); } +} + +if (!function_exists('enum_exists')) { + function enum_exists(string $enum, bool $autoload = true): bool { return $autoload && class_exists($enum) && false; } +} diff --git a/src/vendor/symfony/polyfill-php81/composer.json b/src/vendor/symfony/polyfill-php81/composer.json new file mode 100644 index 0000000..e02d673 --- /dev/null +++ b/src/vendor/symfony/polyfill-php81/composer.json @@ -0,0 +1,36 @@ +{ + "name": "symfony/polyfill-php81", + "type": "library", + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "keywords": ["polyfill", "shim", "compatibility", "portable"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php81\\": "" }, + "files": [ "bootstrap.php" ], + "classmap": [ "Resources/stubs" ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/src/vendor/symfony/service-contracts/Attribute/Required.php b/src/vendor/symfony/service-contracts/Attribute/Required.php new file mode 100644 index 0000000..9df8511 --- /dev/null +++ b/src/vendor/symfony/service-contracts/Attribute/Required.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service\Attribute; + +/** + * A required dependency. + * + * This attribute indicates that a property holds a required dependency. The annotated property or method should be + * considered during the instantiation process of the containing class. + * + * @author Alexander M. Turek + */ +#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] +final class Required +{ +} diff --git a/src/vendor/symfony/service-contracts/Attribute/SubscribedService.php b/src/vendor/symfony/service-contracts/Attribute/SubscribedService.php new file mode 100644 index 0000000..d98e1df --- /dev/null +++ b/src/vendor/symfony/service-contracts/Attribute/SubscribedService.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service\Attribute; + +use Symfony\Contracts\Service\ServiceSubscriberInterface; +use Symfony\Contracts\Service\ServiceSubscriberTrait; + +/** + * For use as the return value for {@see ServiceSubscriberInterface}. + * + * @example new SubscribedService('http_client', HttpClientInterface::class, false, new Target('githubApi')) + * + * Use with {@see ServiceSubscriberTrait} to mark a method's return type + * as a subscribed service. + * + * @author Kevin Bond + */ +#[\Attribute(\Attribute::TARGET_METHOD)] +final class SubscribedService +{ + /** @var object[] */ + public array $attributes; + + /** + * @param string|null $key The key to use for the service + * @param class-string|null $type The service class + * @param bool $nullable Whether the service is optional + * @param object|object[] $attributes One or more dependency injection attributes to use + */ + public function __construct( + public ?string $key = null, + public ?string $type = null, + public bool $nullable = false, + array|object $attributes = [], + ) { + $this->attributes = \is_array($attributes) ? $attributes : [$attributes]; + } +} diff --git a/src/vendor/symfony/service-contracts/CHANGELOG.md b/src/vendor/symfony/service-contracts/CHANGELOG.md new file mode 100644 index 0000000..7932e26 --- /dev/null +++ b/src/vendor/symfony/service-contracts/CHANGELOG.md @@ -0,0 +1,5 @@ +CHANGELOG +========= + +The changelog is maintained for all Symfony contracts at the following URL: +https://github.com/symfony/contracts/blob/main/CHANGELOG.md diff --git a/src/vendor/symfony/service-contracts/LICENSE b/src/vendor/symfony/service-contracts/LICENSE new file mode 100644 index 0000000..7536cae --- /dev/null +++ b/src/vendor/symfony/service-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/vendor/symfony/service-contracts/README.md b/src/vendor/symfony/service-contracts/README.md new file mode 100644 index 0000000..42841a5 --- /dev/null +++ b/src/vendor/symfony/service-contracts/README.md @@ -0,0 +1,9 @@ +Symfony Service Contracts +========================= + +A set of abstractions extracted out of the Symfony components. + +Can be used to build on semantics that the Symfony components proved useful and +that already have battle tested implementations. + +See https://github.com/symfony/contracts/blob/main/README.md for more information. diff --git a/src/vendor/symfony/service-contracts/ResetInterface.php b/src/vendor/symfony/service-contracts/ResetInterface.php new file mode 100644 index 0000000..a4f389b --- /dev/null +++ b/src/vendor/symfony/service-contracts/ResetInterface.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +/** + * Provides a way to reset an object to its initial state. + * + * When calling the "reset()" method on an object, it should be put back to its + * initial state. This usually means clearing any internal buffers and forwarding + * the call to internal dependencies. All properties of the object should be put + * back to the same state it had when it was first ready to use. + * + * This method could be called, for example, to recycle objects that are used as + * services, so that they can be used to handle several requests in the same + * process loop (note that we advise making your services stateless instead of + * implementing this interface when possible.) + */ +interface ResetInterface +{ + /** + * @return void + */ + public function reset(); +} diff --git a/src/vendor/symfony/service-contracts/ServiceLocatorTrait.php b/src/vendor/symfony/service-contracts/ServiceLocatorTrait.php new file mode 100644 index 0000000..45c8d91 --- /dev/null +++ b/src/vendor/symfony/service-contracts/ServiceLocatorTrait.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\NotFoundExceptionInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(ContainerExceptionInterface::class); +class_exists(NotFoundExceptionInterface::class); + +/** + * A trait to help implement ServiceProviderInterface. + * + * @author Robin Chalas + * @author Nicolas Grekas + */ +trait ServiceLocatorTrait +{ + private array $factories; + private array $loading = []; + private array $providedTypes; + + /** + * @param callable[] $factories + */ + public function __construct(array $factories) + { + $this->factories = $factories; + } + + public function has(string $id): bool + { + return isset($this->factories[$id]); + } + + public function get(string $id): mixed + { + if (!isset($this->factories[$id])) { + throw $this->createNotFoundException($id); + } + + if (isset($this->loading[$id])) { + $ids = array_values($this->loading); + $ids = \array_slice($this->loading, array_search($id, $ids)); + $ids[] = $id; + + throw $this->createCircularReferenceException($id, $ids); + } + + $this->loading[$id] = $id; + try { + return $this->factories[$id]($this); + } finally { + unset($this->loading[$id]); + } + } + + public function getProvidedServices(): array + { + if (!isset($this->providedTypes)) { + $this->providedTypes = []; + + foreach ($this->factories as $name => $factory) { + if (!\is_callable($factory)) { + $this->providedTypes[$name] = '?'; + } else { + $type = (new \ReflectionFunction($factory))->getReturnType(); + + $this->providedTypes[$name] = $type ? ($type->allowsNull() ? '?' : '').($type instanceof \ReflectionNamedType ? $type->getName() : $type) : '?'; + } + } + } + + return $this->providedTypes; + } + + private function createNotFoundException(string $id): NotFoundExceptionInterface + { + if (!$alternatives = array_keys($this->factories)) { + $message = 'is empty...'; + } else { + $last = array_pop($alternatives); + if ($alternatives) { + $message = sprintf('only knows about the "%s" and "%s" services.', implode('", "', $alternatives), $last); + } else { + $message = sprintf('only knows about the "%s" service.', $last); + } + } + + if ($this->loading) { + $message = sprintf('The service "%s" has a dependency on a non-existent service "%s". This locator %s', end($this->loading), $id, $message); + } else { + $message = sprintf('Service "%s" not found: the current service locator %s', $id, $message); + } + + return new class($message) extends \InvalidArgumentException implements NotFoundExceptionInterface { + }; + } + + private function createCircularReferenceException(string $id, array $path): ContainerExceptionInterface + { + return new class(sprintf('Circular reference detected for service "%s", path: "%s".', $id, implode(' -> ', $path))) extends \RuntimeException implements ContainerExceptionInterface { + }; + } +} diff --git a/src/vendor/symfony/service-contracts/ServiceProviderInterface.php b/src/vendor/symfony/service-contracts/ServiceProviderInterface.php new file mode 100644 index 0000000..c05e4bf --- /dev/null +++ b/src/vendor/symfony/service-contracts/ServiceProviderInterface.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +use Psr\Container\ContainerInterface; + +/** + * A ServiceProviderInterface exposes the identifiers and the types of services provided by a container. + * + * @author Nicolas Grekas + * @author Mateusz Sip + * + * @template-covariant T of mixed + */ +interface ServiceProviderInterface extends ContainerInterface +{ + /** + * @return T + */ + public function get(string $id): mixed; + + public function has(string $id): bool; + + /** + * Returns an associative array of service types keyed by the identifiers provided by the current container. + * + * Examples: + * + * * ['logger' => 'Psr\Log\LoggerInterface'] means the object provides a service named "logger" that implements Psr\Log\LoggerInterface + * * ['foo' => '?'] means the container provides service name "foo" of unspecified type + * * ['bar' => '?Bar\Baz'] means the container provides a service "bar" of type Bar\Baz|null + * + * @return string[] The provided service types, keyed by service names + */ + public function getProvidedServices(): array; +} diff --git a/src/vendor/symfony/service-contracts/ServiceSubscriberInterface.php b/src/vendor/symfony/service-contracts/ServiceSubscriberInterface.php new file mode 100644 index 0000000..3da1916 --- /dev/null +++ b/src/vendor/symfony/service-contracts/ServiceSubscriberInterface.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +use Symfony\Contracts\Service\Attribute\SubscribedService; + +/** + * A ServiceSubscriber exposes its dependencies via the static {@link getSubscribedServices} method. + * + * The getSubscribedServices method returns an array of service types required by such instances, + * optionally keyed by the service names used internally. Service types that start with an interrogation + * mark "?" are optional, while the other ones are mandatory service dependencies. + * + * The injected service locators SHOULD NOT allow access to any other services not specified by the method. + * + * It is expected that ServiceSubscriber instances consume PSR-11-based service locators internally. + * This interface does not dictate any injection method for these service locators, although constructor + * injection is recommended. + * + * @author Nicolas Grekas + */ +interface ServiceSubscriberInterface +{ + /** + * Returns an array of service types (or {@see SubscribedService} objects) required + * by such instances, optionally keyed by the service names used internally. + * + * For mandatory dependencies: + * + * * ['logger' => 'Psr\Log\LoggerInterface'] means the objects use the "logger" name + * internally to fetch a service which must implement Psr\Log\LoggerInterface. + * * ['loggers' => 'Psr\Log\LoggerInterface[]'] means the objects use the "loggers" name + * internally to fetch an iterable of Psr\Log\LoggerInterface instances. + * * ['Psr\Log\LoggerInterface'] is a shortcut for + * * ['Psr\Log\LoggerInterface' => 'Psr\Log\LoggerInterface'] + * + * otherwise: + * + * * ['logger' => '?Psr\Log\LoggerInterface'] denotes an optional dependency + * * ['loggers' => '?Psr\Log\LoggerInterface[]'] denotes an optional iterable dependency + * * ['?Psr\Log\LoggerInterface'] is a shortcut for + * * ['Psr\Log\LoggerInterface' => '?Psr\Log\LoggerInterface'] + * + * additionally, an array of {@see SubscribedService}'s can be returned: + * + * * [new SubscribedService('logger', Psr\Log\LoggerInterface::class)] + * * [new SubscribedService(type: Psr\Log\LoggerInterface::class, nullable: true)] + * * [new SubscribedService('http_client', HttpClientInterface::class, attributes: new Target('githubApi'))] + * + * @return string[]|SubscribedService[] The required service types, optionally keyed by service names + */ + public static function getSubscribedServices(): array; +} diff --git a/src/vendor/symfony/service-contracts/ServiceSubscriberTrait.php b/src/vendor/symfony/service-contracts/ServiceSubscriberTrait.php new file mode 100644 index 0000000..f3b450c --- /dev/null +++ b/src/vendor/symfony/service-contracts/ServiceSubscriberTrait.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +use Psr\Container\ContainerInterface; +use Symfony\Contracts\Service\Attribute\Required; +use Symfony\Contracts\Service\Attribute\SubscribedService; + +/** + * Implementation of ServiceSubscriberInterface that determines subscribed services from + * method return types. Service ids are available as "ClassName::methodName". + * + * @author Kevin Bond + */ +trait ServiceSubscriberTrait +{ + /** @var ContainerInterface */ + protected $container; + + public static function getSubscribedServices(): array + { + $services = method_exists(get_parent_class(self::class) ?: '', __FUNCTION__) ? parent::getSubscribedServices() : []; + + foreach ((new \ReflectionClass(self::class))->getMethods() as $method) { + if (self::class !== $method->getDeclaringClass()->name) { + continue; + } + + if (!$attribute = $method->getAttributes(SubscribedService::class)[0] ?? null) { + continue; + } + + if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) { + throw new \LogicException(sprintf('Cannot use "%s" on method "%s::%s()" (can only be used on non-static, non-abstract methods with no parameters).', SubscribedService::class, self::class, $method->name)); + } + + if (!$returnType = $method->getReturnType()) { + throw new \LogicException(sprintf('Cannot use "%s" on methods without a return type in "%s::%s()".', SubscribedService::class, $method->name, self::class)); + } + + /* @var SubscribedService $attribute */ + $attribute = $attribute->newInstance(); + $attribute->key ??= self::class.'::'.$method->name; + $attribute->type ??= $returnType instanceof \ReflectionNamedType ? $returnType->getName() : (string) $returnType; + $attribute->nullable = $returnType->allowsNull(); + + if ($attribute->attributes) { + $services[] = $attribute; + } else { + $services[$attribute->key] = ($attribute->nullable ? '?' : '').$attribute->type; + } + } + + return $services; + } + + #[Required] + public function setContainer(ContainerInterface $container): ?ContainerInterface + { + $ret = null; + if (method_exists(get_parent_class(self::class) ?: '', __FUNCTION__)) { + $ret = parent::setContainer($container); + } + + $this->container = $container; + + return $ret; + } +} diff --git a/src/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php b/src/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php new file mode 100644 index 0000000..07d12b4 --- /dev/null +++ b/src/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service\Test; + +class_alias(ServiceLocatorTestCase::class, ServiceLocatorTest::class); + +if (false) { + /** + * @deprecated since PHPUnit 9.6 + */ + class ServiceLocatorTest + { + } +} diff --git a/src/vendor/symfony/service-contracts/Test/ServiceLocatorTestCase.php b/src/vendor/symfony/service-contracts/Test/ServiceLocatorTestCase.php new file mode 100644 index 0000000..a0f20a6 --- /dev/null +++ b/src/vendor/symfony/service-contracts/Test/ServiceLocatorTestCase.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service\Test; + +use PHPUnit\Framework\TestCase; +use Psr\Container\ContainerInterface; +use Symfony\Contracts\Service\ServiceLocatorTrait; + +abstract class ServiceLocatorTestCase extends TestCase +{ + protected function getServiceLocator(array $factories): ContainerInterface + { + return new class($factories) implements ContainerInterface { + use ServiceLocatorTrait; + }; + } + + public function testHas() + { + $locator = $this->getServiceLocator([ + 'foo' => function () { return 'bar'; }, + 'bar' => function () { return 'baz'; }, + function () { return 'dummy'; }, + ]); + + $this->assertTrue($locator->has('foo')); + $this->assertTrue($locator->has('bar')); + $this->assertFalse($locator->has('dummy')); + } + + public function testGet() + { + $locator = $this->getServiceLocator([ + 'foo' => function () { return 'bar'; }, + 'bar' => function () { return 'baz'; }, + ]); + + $this->assertSame('bar', $locator->get('foo')); + $this->assertSame('baz', $locator->get('bar')); + } + + public function testGetDoesNotMemoize() + { + $i = 0; + $locator = $this->getServiceLocator([ + 'foo' => function () use (&$i) { + ++$i; + + return 'bar'; + }, + ]); + + $this->assertSame('bar', $locator->get('foo')); + $this->assertSame('bar', $locator->get('foo')); + $this->assertSame(2, $i); + } + + public function testThrowsOnUndefinedInternalService() + { + if (!$this->getExpectedException()) { + $this->expectException(\Psr\Container\NotFoundExceptionInterface::class); + $this->expectExceptionMessage('The service "foo" has a dependency on a non-existent service "bar". This locator only knows about the "foo" service.'); + } + $locator = $this->getServiceLocator([ + 'foo' => function () use (&$locator) { return $locator->get('bar'); }, + ]); + + $locator->get('foo'); + } + + public function testThrowsOnCircularReference() + { + $this->expectException(\Psr\Container\ContainerExceptionInterface::class); + $this->expectExceptionMessage('Circular reference detected for service "bar", path: "bar -> baz -> bar".'); + $locator = $this->getServiceLocator([ + 'foo' => function () use (&$locator) { return $locator->get('bar'); }, + 'bar' => function () use (&$locator) { return $locator->get('baz'); }, + 'baz' => function () use (&$locator) { return $locator->get('bar'); }, + ]); + + $locator->get('foo'); + } +} diff --git a/src/vendor/symfony/service-contracts/composer.json b/src/vendor/symfony/service-contracts/composer.json new file mode 100644 index 0000000..a64188b --- /dev/null +++ b/src/vendor/symfony/service-contracts/composer.json @@ -0,0 +1,41 @@ +{ + "name": "symfony/service-contracts", + "type": "library", + "description": "Generic abstractions related to writing services", + "keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.1", + "psr/container": "^2.0" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "autoload": { + "psr-4": { "Symfony\\Contracts\\Service\\": "" }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + } +} diff --git a/src/vendor/symfony/translation-contracts/CHANGELOG.md b/src/vendor/symfony/translation-contracts/CHANGELOG.md new file mode 100644 index 0000000..7932e26 --- /dev/null +++ b/src/vendor/symfony/translation-contracts/CHANGELOG.md @@ -0,0 +1,5 @@ +CHANGELOG +========= + +The changelog is maintained for all Symfony contracts at the following URL: +https://github.com/symfony/contracts/blob/main/CHANGELOG.md diff --git a/src/vendor/symfony/translation-contracts/LICENSE b/src/vendor/symfony/translation-contracts/LICENSE new file mode 100644 index 0000000..7536cae --- /dev/null +++ b/src/vendor/symfony/translation-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/vendor/symfony/translation-contracts/LocaleAwareInterface.php b/src/vendor/symfony/translation-contracts/LocaleAwareInterface.php new file mode 100644 index 0000000..db40ba1 --- /dev/null +++ b/src/vendor/symfony/translation-contracts/LocaleAwareInterface.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Translation; + +interface LocaleAwareInterface +{ + /** + * Sets the current locale. + * + * @return void + * + * @throws \InvalidArgumentException If the locale contains invalid characters + */ + public function setLocale(string $locale); + + /** + * Returns the current locale. + */ + public function getLocale(): string; +} diff --git a/src/vendor/symfony/translation-contracts/README.md b/src/vendor/symfony/translation-contracts/README.md new file mode 100644 index 0000000..b211d58 --- /dev/null +++ b/src/vendor/symfony/translation-contracts/README.md @@ -0,0 +1,9 @@ +Symfony Translation Contracts +============================= + +A set of abstractions extracted out of the Symfony components. + +Can be used to build on semantics that the Symfony components proved useful and +that already have battle tested implementations. + +See https://github.com/symfony/contracts/blob/main/README.md for more information. diff --git a/src/vendor/symfony/translation-contracts/Test/TranslatorTest.php b/src/vendor/symfony/translation-contracts/Test/TranslatorTest.php new file mode 100644 index 0000000..674b78b --- /dev/null +++ b/src/vendor/symfony/translation-contracts/Test/TranslatorTest.php @@ -0,0 +1,384 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Translation\Test; + +use PHPUnit\Framework\TestCase; +use Symfony\Contracts\Translation\TranslatorInterface; +use Symfony\Contracts\Translation\TranslatorTrait; + +/** + * Test should cover all languages mentioned on http://translate.sourceforge.net/wiki/l10n/pluralforms + * and Plural forms mentioned on http://www.gnu.org/software/gettext/manual/gettext.html#Plural-forms. + * + * See also https://developer.mozilla.org/en/Localization_and_Plurals which mentions 15 rules having a maximum of 6 forms. + * The mozilla code is also interesting to check for. + * + * As mentioned by chx http://drupal.org/node/1273968 we can cover all by testing number from 0 to 199 + * + * The goal to cover all languages is to far fetched so this test case is smaller. + * + * @author Clemens Tolboom clemens@build2be.nl + */ +class TranslatorTest extends TestCase +{ + private $defaultLocale; + + protected function setUp(): void + { + $this->defaultLocale = \Locale::getDefault(); + \Locale::setDefault('en'); + } + + protected function tearDown(): void + { + \Locale::setDefault($this->defaultLocale); + } + + public function getTranslator(): TranslatorInterface + { + return new class() implements TranslatorInterface { + use TranslatorTrait; + }; + } + + /** + * @dataProvider getTransTests + */ + public function testTrans($expected, $id, $parameters) + { + $translator = $this->getTranslator(); + + $this->assertEquals($expected, $translator->trans($id, $parameters)); + } + + /** + * @dataProvider getTransChoiceTests + */ + public function testTransChoiceWithExplicitLocale($expected, $id, $number) + { + $translator = $this->getTranslator(); + + $this->assertEquals($expected, $translator->trans($id, ['%count%' => $number])); + } + + /** + * @requires extension intl + * + * @dataProvider getTransChoiceTests + */ + public function testTransChoiceWithDefaultLocale($expected, $id, $number) + { + $translator = $this->getTranslator(); + + $this->assertEquals($expected, $translator->trans($id, ['%count%' => $number])); + } + + /** + * @dataProvider getTransChoiceTests + */ + public function testTransChoiceWithEnUsPosix($expected, $id, $number) + { + $translator = $this->getTranslator(); + $translator->setLocale('en_US_POSIX'); + + $this->assertEquals($expected, $translator->trans($id, ['%count%' => $number])); + } + + public function testGetSetLocale() + { + $translator = $this->getTranslator(); + + $this->assertEquals('en', $translator->getLocale()); + } + + /** + * @requires extension intl + */ + public function testGetLocaleReturnsDefaultLocaleIfNotSet() + { + $translator = $this->getTranslator(); + + \Locale::setDefault('pt_BR'); + $this->assertEquals('pt_BR', $translator->getLocale()); + + \Locale::setDefault('en'); + $this->assertEquals('en', $translator->getLocale()); + } + + public static function getTransTests() + { + return [ + ['Symfony is great!', 'Symfony is great!', []], + ['Symfony is awesome!', 'Symfony is %what%!', ['%what%' => 'awesome']], + ]; + } + + public static function getTransChoiceTests() + { + return [ + ['There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0], + ['There is one apple', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 1], + ['There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10], + ['There are 0 apples', 'There is 1 apple|There are %count% apples', 0], + ['There is 1 apple', 'There is 1 apple|There are %count% apples', 1], + ['There are 10 apples', 'There is 1 apple|There are %count% apples', 10], + // custom validation messages may be coded with a fixed value + ['There are 2 apples', 'There are 2 apples', 2], + ]; + } + + /** + * @dataProvider getInterval + */ + public function testInterval($expected, $number, $interval) + { + $translator = $this->getTranslator(); + + $this->assertEquals($expected, $translator->trans($interval.' foo|[1,Inf[ bar', ['%count%' => $number])); + } + + public static function getInterval() + { + return [ + ['foo', 3, '{1,2, 3 ,4}'], + ['bar', 10, '{1,2, 3 ,4}'], + ['bar', 3, '[1,2]'], + ['foo', 1, '[1,2]'], + ['foo', 2, '[1,2]'], + ['bar', 1, ']1,2['], + ['bar', 2, ']1,2['], + ['foo', log(0), '[-Inf,2['], + ['foo', -log(0), '[-2,+Inf]'], + ]; + } + + /** + * @dataProvider getChooseTests + */ + public function testChoose($expected, $id, $number, $locale = null) + { + $translator = $this->getTranslator(); + + $this->assertEquals($expected, $translator->trans($id, ['%count%' => $number], null, $locale)); + } + + public function testReturnMessageIfExactlyOneStandardRuleIsGiven() + { + $translator = $this->getTranslator(); + + $this->assertEquals('There are two apples', $translator->trans('There are two apples', ['%count%' => 2])); + } + + /** + * @dataProvider getNonMatchingMessages + */ + public function testThrowExceptionIfMatchingMessageCannotBeFound($id, $number) + { + $this->expectException(\InvalidArgumentException::class); + $translator = $this->getTranslator(); + + $translator->trans($id, ['%count%' => $number]); + } + + public static function getNonMatchingMessages() + { + return [ + ['{0} There are no apples|{1} There is one apple', 2], + ['{1} There is one apple|]1,Inf] There are %count% apples', 0], + ['{1} There is one apple|]2,Inf] There are %count% apples', 2], + ['{0} There are no apples|There is one apple', 2], + ]; + } + + public static function getChooseTests() + { + return [ + ['There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0], + ['There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0], + ['There are no apples', '{0}There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0], + + ['There is one apple', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 1], + + ['There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10], + ['There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf]There are %count% apples', 10], + ['There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10], + + ['There are 0 apples', 'There is one apple|There are %count% apples', 0], + ['There is one apple', 'There is one apple|There are %count% apples', 1], + ['There are 10 apples', 'There is one apple|There are %count% apples', 10], + + ['There are 0 apples', 'one: There is one apple|more: There are %count% apples', 0], + ['There is one apple', 'one: There is one apple|more: There are %count% apples', 1], + ['There are 10 apples', 'one: There is one apple|more: There are %count% apples', 10], + + ['There are no apples', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 0], + ['There is one apple', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 1], + ['There are 10 apples', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 10], + + ['', '{0}|{1} There is one apple|]1,Inf] There are %count% apples', 0], + ['', '{0} There are no apples|{1}|]1,Inf] There are %count% apples', 1], + + // Indexed only tests which are Gettext PoFile* compatible strings. + ['There are 0 apples', 'There is one apple|There are %count% apples', 0], + ['There is one apple', 'There is one apple|There are %count% apples', 1], + ['There are 2 apples', 'There is one apple|There are %count% apples', 2], + + // Tests for float numbers + ['There is almost one apple', '{0} There are no apples|]0,1[ There is almost one apple|{1} There is one apple|[1,Inf] There is more than one apple', 0.7], + ['There is one apple', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 1], + ['There is more than one apple', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 1.7], + ['There are no apples', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0], + ['There are no apples', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0.0], + ['There are no apples', '{0.0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0], + + // Test texts with new-lines + // with double-quotes and \n in id & double-quotes and actual newlines in text + ["This is a text with a\n new-line in it. Selector = 0.", '{0}This is a text with a + new-line in it. Selector = 0.|{1}This is a text with a + new-line in it. Selector = 1.|[1,Inf]This is a text with a + new-line in it. Selector > 1.', 0], + // with double-quotes and \n in id and single-quotes and actual newlines in text + ["This is a text with a\n new-line in it. Selector = 1.", '{0}This is a text with a + new-line in it. Selector = 0.|{1}This is a text with a + new-line in it. Selector = 1.|[1,Inf]This is a text with a + new-line in it. Selector > 1.', 1], + ["This is a text with a\n new-line in it. Selector > 1.", '{0}This is a text with a + new-line in it. Selector = 0.|{1}This is a text with a + new-line in it. Selector = 1.|[1,Inf]This is a text with a + new-line in it. Selector > 1.', 5], + // with double-quotes and id split across lines + ['This is a text with a + new-line in it. Selector = 1.', '{0}This is a text with a + new-line in it. Selector = 0.|{1}This is a text with a + new-line in it. Selector = 1.|[1,Inf]This is a text with a + new-line in it. Selector > 1.', 1], + // with single-quotes and id split across lines + ['This is a text with a + new-line in it. Selector > 1.', '{0}This is a text with a + new-line in it. Selector = 0.|{1}This is a text with a + new-line in it. Selector = 1.|[1,Inf]This is a text with a + new-line in it. Selector > 1.', 5], + // with single-quotes and \n in text + ['This is a text with a\nnew-line in it. Selector = 0.', '{0}This is a text with a\nnew-line in it. Selector = 0.|{1}This is a text with a\nnew-line in it. Selector = 1.|[1,Inf]This is a text with a\nnew-line in it. Selector > 1.', 0], + // with double-quotes and id split across lines + ["This is a text with a\nnew-line in it. Selector = 1.", "{0}This is a text with a\nnew-line in it. Selector = 0.|{1}This is a text with a\nnew-line in it. Selector = 1.|[1,Inf]This is a text with a\nnew-line in it. Selector > 1.", 1], + // escape pipe + ['This is a text with | in it. Selector = 0.', '{0}This is a text with || in it. Selector = 0.|{1}This is a text with || in it. Selector = 1.', 0], + // Empty plural set (2 plural forms) from a .PO file + ['', '|', 1], + // Empty plural set (3 plural forms) from a .PO file + ['', '||', 1], + + // Floating values + ['1.5 liters', '%count% liter|%count% liters', 1.5], + ['1.5 litre', '%count% litre|%count% litres', 1.5, 'fr'], + + // Negative values + ['-1 degree', '%count% degree|%count% degrees', -1], + ['-1 degré', '%count% degré|%count% degrés', -1], + ['-1.5 degrees', '%count% degree|%count% degrees', -1.5], + ['-1.5 degré', '%count% degré|%count% degrés', -1.5, 'fr'], + ['-2 degrees', '%count% degree|%count% degrees', -2], + ['-2 degrés', '%count% degré|%count% degrés', -2], + ]; + } + + /** + * @dataProvider failingLangcodes + */ + public function testFailedLangcodes($nplural, $langCodes) + { + $matrix = $this->generateTestData($langCodes); + $this->validateMatrix($nplural, $matrix, false); + } + + /** + * @dataProvider successLangcodes + */ + public function testLangcodes($nplural, $langCodes) + { + $matrix = $this->generateTestData($langCodes); + $this->validateMatrix($nplural, $matrix); + } + + /** + * This array should contain all currently known langcodes. + * + * As it is impossible to have this ever complete we should try as hard as possible to have it almost complete. + */ + public static function successLangcodes(): array + { + return [ + ['1', ['ay', 'bo', 'cgg', 'dz', 'id', 'ja', 'jbo', 'ka', 'kk', 'km', 'ko', 'ky']], + ['2', ['nl', 'fr', 'en', 'de', 'de_GE', 'hy', 'hy_AM', 'en_US_POSIX']], + ['3', ['be', 'bs', 'cs', 'hr']], + ['4', ['cy', 'mt', 'sl']], + ['6', ['ar']], + ]; + } + + /** + * This array should be at least empty within the near future. + * + * This both depends on a complete list trying to add above as understanding + * the plural rules of the current failing languages. + * + * @return array with nplural together with langcodes + */ + public static function failingLangcodes(): array + { + return [ + ['1', ['fa']], + ['2', ['jbo']], + ['3', ['cbs']], + ['4', ['gd', 'kw']], + ['5', ['ga']], + ]; + } + + /** + * We validate only on the plural coverage. Thus the real rules is not tested. + * + * @param string $nplural Plural expected + * @param array $matrix Containing langcodes and their plural index values + */ + protected function validateMatrix(string $nplural, array $matrix, bool $expectSuccess = true) + { + foreach ($matrix as $langCode => $data) { + $indexes = array_flip($data); + if ($expectSuccess) { + $this->assertCount($nplural, $indexes, "Langcode '$langCode' has '$nplural' plural forms."); + } else { + $this->assertNotEquals((int) $nplural, \count($indexes), "Langcode '$langCode' has '$nplural' plural forms."); + } + } + } + + protected function generateTestData($langCodes) + { + $translator = new class() { + use TranslatorTrait { + getPluralizationRule as public; + } + }; + + $matrix = []; + foreach ($langCodes as $langCode) { + for ($count = 0; $count < 200; ++$count) { + $plural = $translator->getPluralizationRule($count, $langCode); + $matrix[$langCode][$count] = $plural; + } + } + + return $matrix; + } +} diff --git a/src/vendor/symfony/translation-contracts/TranslatableInterface.php b/src/vendor/symfony/translation-contracts/TranslatableInterface.php new file mode 100644 index 0000000..47fd6fa --- /dev/null +++ b/src/vendor/symfony/translation-contracts/TranslatableInterface.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Translation; + +/** + * @author Nicolas Grekas + */ +interface TranslatableInterface +{ + public function trans(TranslatorInterface $translator, string $locale = null): string; +} diff --git a/src/vendor/symfony/translation-contracts/TranslatorInterface.php b/src/vendor/symfony/translation-contracts/TranslatorInterface.php new file mode 100644 index 0000000..018db07 --- /dev/null +++ b/src/vendor/symfony/translation-contracts/TranslatorInterface.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Translation; + +/** + * @author Fabien Potencier + */ +interface TranslatorInterface +{ + /** + * Translates the given message. + * + * When a number is provided as a parameter named "%count%", the message is parsed for plural + * forms and a translation is chosen according to this number using the following rules: + * + * Given a message with different plural translations separated by a + * pipe (|), this method returns the correct portion of the message based + * on the given number, locale and the pluralization rules in the message + * itself. + * + * The message supports two different types of pluralization rules: + * + * interval: {0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples + * indexed: There is one apple|There are %count% apples + * + * The indexed solution can also contain labels (e.g. one: There is one apple). + * This is purely for making the translations more clear - it does not + * affect the functionality. + * + * The two methods can also be mixed: + * {0} There are no apples|one: There is one apple|more: There are %count% apples + * + * An interval can represent a finite set of numbers: + * {1,2,3,4} + * + * An interval can represent numbers between two numbers: + * [1, +Inf] + * ]-1,2[ + * + * The left delimiter can be [ (inclusive) or ] (exclusive). + * The right delimiter can be [ (exclusive) or ] (inclusive). + * Beside numbers, you can use -Inf and +Inf for the infinite. + * + * @see https://en.wikipedia.org/wiki/ISO_31-11 + * + * @param string $id The message id (may also be an object that can be cast to string) + * @param array $parameters An array of parameters for the message + * @param string|null $domain The domain for the message or null to use the default + * @param string|null $locale The locale or null to use the default + * + * @throws \InvalidArgumentException If the locale contains invalid characters + */ + public function trans(string $id, array $parameters = [], string $domain = null, string $locale = null): string; + + /** + * Returns the default locale. + */ + public function getLocale(): string; +} diff --git a/src/vendor/symfony/translation-contracts/TranslatorTrait.php b/src/vendor/symfony/translation-contracts/TranslatorTrait.php new file mode 100644 index 0000000..e3b0adf --- /dev/null +++ b/src/vendor/symfony/translation-contracts/TranslatorTrait.php @@ -0,0 +1,225 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Translation; + +use Symfony\Component\Translation\Exception\InvalidArgumentException; + +/** + * A trait to help implement TranslatorInterface and LocaleAwareInterface. + * + * @author Fabien Potencier + */ +trait TranslatorTrait +{ + private ?string $locale = null; + + /** + * @return void + */ + public function setLocale(string $locale) + { + $this->locale = $locale; + } + + public function getLocale(): string + { + return $this->locale ?: (class_exists(\Locale::class) ? \Locale::getDefault() : 'en'); + } + + public function trans(?string $id, array $parameters = [], string $domain = null, string $locale = null): string + { + if (null === $id || '' === $id) { + return ''; + } + + if (!isset($parameters['%count%']) || !is_numeric($parameters['%count%'])) { + return strtr($id, $parameters); + } + + $number = (float) $parameters['%count%']; + $locale = $locale ?: $this->getLocale(); + + $parts = []; + if (preg_match('/^\|++$/', $id)) { + $parts = explode('|', $id); + } elseif (preg_match_all('/(?:\|\||[^\|])++/', $id, $matches)) { + $parts = $matches[0]; + } + + $intervalRegexp = <<<'EOF' +/^(?P + ({\s* + (\-?\d+(\.\d+)?[\s*,\s*\-?\d+(\.\d+)?]*) + \s*}) + + | + + (?P[\[\]]) + \s* + (?P-Inf|\-?\d+(\.\d+)?) + \s*,\s* + (?P\+?Inf|\-?\d+(\.\d+)?) + \s* + (?P[\[\]]) +)\s*(?P.*?)$/xs +EOF; + + $standardRules = []; + foreach ($parts as $part) { + $part = trim(str_replace('||', '|', $part)); + + // try to match an explicit rule, then fallback to the standard ones + if (preg_match($intervalRegexp, $part, $matches)) { + if ($matches[2]) { + foreach (explode(',', $matches[3]) as $n) { + if ($number == $n) { + return strtr($matches['message'], $parameters); + } + } + } else { + $leftNumber = '-Inf' === $matches['left'] ? -\INF : (float) $matches['left']; + $rightNumber = is_numeric($matches['right']) ? (float) $matches['right'] : \INF; + + if (('[' === $matches['left_delimiter'] ? $number >= $leftNumber : $number > $leftNumber) + && (']' === $matches['right_delimiter'] ? $number <= $rightNumber : $number < $rightNumber) + ) { + return strtr($matches['message'], $parameters); + } + } + } elseif (preg_match('/^\w+\:\s*(.*?)$/', $part, $matches)) { + $standardRules[] = $matches[1]; + } else { + $standardRules[] = $part; + } + } + + $position = $this->getPluralizationRule($number, $locale); + + if (!isset($standardRules[$position])) { + // when there's exactly one rule given, and that rule is a standard + // rule, use this rule + if (1 === \count($parts) && isset($standardRules[0])) { + return strtr($standardRules[0], $parameters); + } + + $message = sprintf('Unable to choose a translation for "%s" with locale "%s" for value "%d". Double check that this translation has the correct plural options (e.g. "There is one apple|There are %%count%% apples").', $id, $locale, $number); + + if (class_exists(InvalidArgumentException::class)) { + throw new InvalidArgumentException($message); + } + + throw new \InvalidArgumentException($message); + } + + return strtr($standardRules[$position], $parameters); + } + + /** + * Returns the plural position to use for the given locale and number. + * + * The plural rules are derived from code of the Zend Framework (2010-09-25), + * which is subject to the new BSD license (http://framework.zend.com/license/new-bsd). + * Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) + */ + private function getPluralizationRule(float $number, string $locale): int + { + $number = abs($number); + + return match ('pt_BR' !== $locale && 'en_US_POSIX' !== $locale && \strlen($locale) > 3 ? substr($locale, 0, strrpos($locale, '_')) : $locale) { + 'af', + 'bn', + 'bg', + 'ca', + 'da', + 'de', + 'el', + 'en', + 'en_US_POSIX', + 'eo', + 'es', + 'et', + 'eu', + 'fa', + 'fi', + 'fo', + 'fur', + 'fy', + 'gl', + 'gu', + 'ha', + 'he', + 'hu', + 'is', + 'it', + 'ku', + 'lb', + 'ml', + 'mn', + 'mr', + 'nah', + 'nb', + 'ne', + 'nl', + 'nn', + 'no', + 'oc', + 'om', + 'or', + 'pa', + 'pap', + 'ps', + 'pt', + 'so', + 'sq', + 'sv', + 'sw', + 'ta', + 'te', + 'tk', + 'ur', + 'zu' => (1 == $number) ? 0 : 1, + 'am', + 'bh', + 'fil', + 'fr', + 'gun', + 'hi', + 'hy', + 'ln', + 'mg', + 'nso', + 'pt_BR', + 'ti', + 'wa' => ($number < 2) ? 0 : 1, + 'be', + 'bs', + 'hr', + 'ru', + 'sh', + 'sr', + 'uk' => ((1 == $number % 10) && (11 != $number % 100)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2), + 'cs', + 'sk' => (1 == $number) ? 0 : ((($number >= 2) && ($number <= 4)) ? 1 : 2), + 'ga' => (1 == $number) ? 0 : ((2 == $number) ? 1 : 2), + 'lt' => ((1 == $number % 10) && (11 != $number % 100)) ? 0 : ((($number % 10 >= 2) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2), + 'sl' => (1 == $number % 100) ? 0 : ((2 == $number % 100) ? 1 : (((3 == $number % 100) || (4 == $number % 100)) ? 2 : 3)), + 'mk' => (1 == $number % 10) ? 0 : 1, + 'mt' => (1 == $number) ? 0 : (((0 == $number) || (($number % 100 > 1) && ($number % 100 < 11))) ? 1 : ((($number % 100 > 10) && ($number % 100 < 20)) ? 2 : 3)), + 'lv' => (0 == $number) ? 0 : (((1 == $number % 10) && (11 != $number % 100)) ? 1 : 2), + 'pl' => (1 == $number) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 12) || ($number % 100 > 14))) ? 1 : 2), + 'cy' => (1 == $number) ? 0 : ((2 == $number) ? 1 : (((8 == $number) || (11 == $number)) ? 2 : 3)), + 'ro' => (1 == $number) ? 0 : (((0 == $number) || (($number % 100 > 0) && ($number % 100 < 20))) ? 1 : 2), + 'ar' => (0 == $number) ? 0 : ((1 == $number) ? 1 : ((2 == $number) ? 2 : ((($number % 100 >= 3) && ($number % 100 <= 10)) ? 3 : ((($number % 100 >= 11) && ($number % 100 <= 99)) ? 4 : 5)))), + default => 0, + }; + } +} diff --git a/src/vendor/symfony/translation-contracts/composer.json b/src/vendor/symfony/translation-contracts/composer.json new file mode 100644 index 0000000..213b5cd --- /dev/null +++ b/src/vendor/symfony/translation-contracts/composer.json @@ -0,0 +1,37 @@ +{ + "name": "symfony/translation-contracts", + "type": "library", + "description": "Generic abstractions related to translation", + "keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.1" + }, + "autoload": { + "psr-4": { "Symfony\\Contracts\\Translation\\": "" }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + } +} diff --git a/src/vendor/symfony/validator/CHANGELOG.md b/src/vendor/symfony/validator/CHANGELOG.md new file mode 100644 index 0000000..f15d18b --- /dev/null +++ b/src/vendor/symfony/validator/CHANGELOG.md @@ -0,0 +1,356 @@ +CHANGELOG +========= + +5.4 +--- + + * Add a `Cidr` constraint to validate CIDR notations + * Add a `CssColor` constraint to validate CSS colors + * Add support for `ConstraintViolationList::createFromMessage()` + * Add error's uid to `Count` and `Length` constraints with "exactly" option enabled + +5.3 +--- + + * Add the `normalizer` option to the `Unique` constraint + * Add `Validation::createIsValidCallable()` that returns true/false instead of throwing exceptions + +5.2.0 +----- + + * added a `Cascade` constraint to ease validating nested typed object properties + * deprecated the `allowEmptyString` option of the `Length` constraint + + Before: + + ```php + use Symfony\Component\Validator\Constraints as Assert; + + /** + * @Assert\Length(min=5, allowEmptyString=true) + */ + ``` + + After: + + ```php + use Symfony\Component\Validator\Constraints as Assert; + + /** + * @Assert\AtLeastOneOf({ + * @Assert\Blank(), + * @Assert\Length(min=5) + * }) + */ + ``` + * added the `Isin` constraint and validator + * added the `ULID` constraint and validator + * added support for UUIDv6 in `Uuid` constraint + * enabled the validator to load constraints from PHP attributes + * deprecated the `NumberConstraintTrait` trait + * deprecated setting or creating a Doctrine annotation reader via `ValidatorBuilder::enableAnnotationMapping()`, pass `true` as first parameter and additionally call `setDoctrineAnnotationReader()` or `addDefaultDoctrineAnnotationReader()` to set up the annotation reader + +5.1.0 +----- + + * Add `AtLeastOneOf` constraint that is considered to be valid if at least one of the nested constraints is valid + * added the `Hostname` constraint and validator + * added the `alpha3` option to the `Country` and `Language` constraints + * allow to define a reusable set of constraints by extending the `Compound` constraint + * added `Sequentially` constraint, to sequentially validate a set of constraints (any violation raised will prevent further validation of the nested constraints) + * added the `divisibleBy` option to the `Count` constraint + * added the `ExpressionLanguageSyntax` constraint + +5.0.0 +----- + + * an `ExpressionLanguage` instance or null must be passed as the first argument of `ExpressionValidator::__construct()` + * removed the `checkDNS` and `dnsMessage` options of the `Url` constraint + * removed the `checkMX`, `checkHost` and `strict` options of the `Email` constraint + * removed support for validating instances of `\DateTimeInterface` in `DateTimeValidator`, `DateValidator` and `TimeValidator` + * removed support for using the `Bic`, `Country`, `Currency`, `Language` and `Locale` constraints without `symfony/intl` + * removed support for using the `Email` constraint without `egulias/email-validator` + * removed support for using the `Expression` constraint without `symfony/expression-language` + * changed default value of `canonicalize` option of `Locale` constraint to `true` + * removed `ValidatorBuilderInterface` + * passing a null message when instantiating a `ConstraintViolation` is not allowed + * changed the default value of `Length::$allowEmptyString` to `false` and made it optional + * removed `Symfony\Component\Validator\Mapping\Cache\CacheInterface` in favor of PSR-6. + * removed `ValidatorBuilder::setMetadataCache`, use `ValidatorBuilder::setMappingCache` instead. + +4.4.0 +----- + + * [BC BREAK] using null as `$classValidatorRegexp` value in `PropertyInfoLoader::__construct` will not enable auto-mapping for all classes anymore, use `'{.*}'` instead. + * added `EnableAutoMapping` and `DisableAutoMapping` constraints to enable or disable auto mapping for class or a property + * using anything else than a `string` as the code of a `ConstraintViolation` is deprecated, a `string` type-hint will + be added to the constructor of the `ConstraintViolation` class and to the `ConstraintViolationBuilder::setCode()` + method in 5.0 + * deprecated passing an `ExpressionLanguage` instance as the second argument of `ExpressionValidator::__construct()`. Pass it as the first argument instead. + * added the `compared_value_path` parameter in violations when using any + comparison constraint with the `propertyPath` option. + * added support for checking an array of types in `TypeValidator` + * added a new `allowEmptyString` option to the `Length` constraint to allow rejecting empty strings when `min` is set, by setting it to `false`. + * Added new `minPropertyPath` and `maxPropertyPath` options + to `Range` constraint in order to get the value to compare + from an array or object + * added the `min_limit_path` and `max_limit_path` parameters in violations when using + `Range` constraint with respectively the `minPropertyPath` and + `maxPropertyPath` options + * added a new `notInRangeMessage` option to the `Range` constraint that will + be used in the violation builder when both `min` and `max` are not null + * added ability to use stringable objects as violation messages + * Overriding the methods `ConstraintValidatorTestCase::setUp()` and `ConstraintValidatorTestCase::tearDown()` without the `void` return-type is deprecated. + * deprecated `Symfony\Component\Validator\Mapping\Cache\CacheInterface` in favor of PSR-6. + * deprecated `ValidatorBuilder::setMetadataCache`, use `ValidatorBuilder::setMappingCache` instead. + * Marked the `ValidatorDataCollector` class as `@final`. + +4.3.0 +----- + + * added `Timezone` constraint + * added `NotCompromisedPassword` constraint + * added options `iban` and `ibanPropertyPath` to Bic constraint + * added UATP cards support to `CardSchemeValidator` + * added option `allowNull` to NotBlank constraint + * added `Json` constraint + * added `Unique` constraint + * added a new `normalizer` option to the string constraints and to the `NotBlank` constraint + * added `Positive` constraint + * added `PositiveOrZero` constraint + * added `Negative` constraint + * added `NegativeOrZero` constraint + +4.2.0 +----- + + * added a new `UnexpectedValueException` that can be thrown by constraint validators, these exceptions are caught by + the validator and are converted into constraint violations + * added `DivisibleBy` constraint + * decoupled from `symfony/translation` by using `Symfony\Contracts\Translation\TranslatorInterface` + * deprecated `ValidatorBuilderInterface` + * made `ValidatorBuilder::setTranslator()` final + * marked `format` the default option in `DateTime` constraint + * deprecated validating instances of `\DateTimeInterface` in `DateTimeValidator`, `DateValidator` and `TimeValidator`. + * deprecated using the `Bic`, `Country`, `Currency`, `Language` and `Locale` constraints without `symfony/intl` + * deprecated using the `Email` constraint without `egulias/email-validator` + * deprecated using the `Expression` constraint without `symfony/expression-language` + +4.1.0 +----- + + * Deprecated the `checkDNS` and `dnsMessage` options of the `Url` constraint. + * added a `values` option to the `Expression` constraint + * Deprecated use of `Locale` constraint without setting `true` at "canonicalize" option, which will be the default value in 5.0 + +4.0.0 +----- + + * Setting the `strict` option of the `Choice` constraint to anything but `true` + is not supported anymore. + * removed the `DateTimeValidator::PATTERN` constant + * removed the `AbstractConstraintValidatorTest` class + * removed support for setting the `checkDNS` option of the `Url` constraint to `true` + +3.4.0 +----- + + * added support for validation groups to the `Valid` constraint + * not setting the `strict` option of the `Choice` constraint to `true` is + deprecated and will throw an exception in Symfony 4.0 + * setting the `checkDNS` option of the `Url` constraint to `true` is deprecated in favor of + the `Url::CHECK_DNS_TYPE_*` constants values and will throw an exception in Symfony 4.0 + * added min/max amount of pixels check to `Image` constraint via `minPixels` and `maxPixels` + * added a new "propertyPath" option to comparison constraints in order to get the value to compare from an array or object + +3.3.0 +----- + + * added `AddValidatorInitializersPass` + * added `AddConstraintValidatorsPass` + * added `ContainerConstraintValidatorFactory` + +3.2.0 +----- + + * deprecated `Tests\Constraints\AbstractConstraintValidatorTest` in favor of `Test\ConstraintValidatorTestCase` + * added support for PHP constants in YAML configuration files + +3.1.0 +----- + + * deprecated `DateTimeValidator::PATTERN` constant + * added a `format` option to the `DateTime` constraint + +2.8.0 +----- + + * added the BIC (SWIFT-Code) validator + +2.7.0 +----- + + * deprecated `DefaultTranslator` in favor of `Symfony\Component\Translation\IdentityTranslator` + * deprecated PHP7-incompatible constraints (Null, True, False) and related validators (NullValidator, TrueValidator, FalseValidator) in favor of their `Is`-prefixed equivalent + +2.6.0 +----- + + * [BC BREAK] `FileValidator` disallow empty files + * [BC BREAK] `UserPasswordValidator` source message change + * [BC BREAK] added internal `ExecutionContextInterface::setConstraint()` + * added `ConstraintViolation::getConstraint()` + * [BC BREAK] The `ExpressionValidator` will now evaluate the Expression even when the property value is null or an empty string + * deprecated `ClassMetadata::hasMemberMetadatas()` + * deprecated `ClassMetadata::getMemberMetadatas()` + * deprecated `ClassMetadata::addMemberMetadata()` + * [BC BREAK] added `Mapping\MetadataInterface::getConstraints()` + * added generic "payload" option to all constraints for attaching domain-specific data + * [BC BREAK] added `ConstraintViolationBuilderInterface::setCause()` + +2.5.0 +----- + + * deprecated `ApcCache` in favor of `DoctrineCache` + * added `DoctrineCache` to adapt any Doctrine cache + * `GroupSequence` now implements `ArrayAccess`, `Countable` and `Traversable` + * [BC BREAK] changed `ClassMetadata::getGroupSequence()` to return a `GroupSequence` instance instead of an array + * `Callback` can now be put onto properties (useful when you pass a closure to the constraint) + * deprecated `ClassBasedInterface` + * deprecated `MetadataInterface` + * deprecated `PropertyMetadataInterface` + * deprecated `PropertyMetadataContainerInterface` + * deprecated `Mapping\ElementMetadata` + * added `Mapping\MetadataInterface` + * added `Mapping\ClassMetadataInterface` + * added `Mapping\PropertyMetadataInterface` + * added `Mapping\GenericMetadata` + * added `Mapping\CascadingStrategy` + * added `Mapping\TraversalStrategy` + * deprecated `Mapping\ClassMetadata::accept()` + * deprecated `Mapping\MemberMetadata::accept()` + * removed array type hint of `Mapping\ClassMetadata::setGroupSequence()` + * deprecated `MetadataFactoryInterface` + * deprecated `Mapping\BlackholeMetadataFactory` + * deprecated `Mapping\ClassMetadataFactory` + * added `Mapping\Factory\MetadataFactoryInterface` + * added `Mapping\Factory\BlackHoleMetadataFactory` + * added `Mapping\Factory\LazyLoadingMetadataFactory` + * deprecated `ExecutionContextInterface` + * deprecated `ExecutionContext` + * deprecated `GlobalExecutionContextInterface` + * added `Context\ExecutionContextInterface` + * added `Context\ExecutionContext` + * added `Context\ExecutionContextFactoryInterface` + * added `Context\ExecutionContextFactory` + * deprecated `ValidatorInterface` + * deprecated `Validator` + * deprecated `ValidationVisitorInterface` + * deprecated `ValidationVisitor` + * added `Validator\ValidatorInterface` + * added `Validator\RecursiveValidator` + * added `Validator\ContextualValidatorInterface` + * added `Validator\RecursiveContextualValidator` + * added `Violation\ConstraintViolationBuilderInterface` + * added `Violation\ConstraintViolationBuilder` + * added `ConstraintViolation::getParameters()` + * added `ConstraintViolation::getPlural()` + * added `Constraints\Traverse` + * deprecated `$deep` property in `Constraints\Valid` + * added `ValidatorBuilderInterface::setApiVersion()` + * added `Validation::API_VERSION_2_4` + * added `Validation::API_VERSION_2_5` + * added `Exception\OutOfBoundsException` + * added `Exception\UnsupportedMetadataException` + * made `Exception\ValidatorException` extend `Exception\RuntimeException` + * added `Util\PropertyPath` + * made the PropertyAccess component an optional dependency + * deprecated `ValidatorBuilder::setPropertyAccessor()` + * deprecated `validate` and `validateValue` on `Validator\Context\ExecutionContext` use `getValidator()` together with `inContext()` instead + +2.4.0 +----- + + * added a constraint the uses the expression language + * added `minRatio`, `maxRatio`, `allowSquare`, `allowLandscape`, and `allowPortrait` to Image validator + +2.3.29 +------ + + * fixed compatibility with PHP7 and up by introducing new constraints (IsNull, IsTrue, IsFalse) and related validators (IsNullValidator, IsTrueValidator, IsFalseValidator) + +2.3.0 +----- + + * added the ISBN, ISSN, and IBAN validators + * copied the constraints `Optional` and `Required` to the + `Symfony\Component\Validator\Constraints\` namespace and deprecated the original + classes. + * added comparison validators (EqualTo, NotEqualTo, LessThan, LessThanOrEqualTo, GreaterThan, GreaterThanOrEqualTo, IdenticalTo, NotIdenticalTo) + +2.2.0 +----- + + * added a CardScheme validator + * added a Luhn validator + * moved @api-tags from `Validator` to `ValidatorInterface` + * moved @api-tags from `ConstraintViolation` to the new `ConstraintViolationInterface` + * moved @api-tags from `ConstraintViolationList` to the new `ConstraintViolationListInterface` + * moved @api-tags from `ExecutionContext` to the new `ExecutionContextInterface` + * [BC BREAK] `ConstraintValidatorInterface::initialize` is now type hinted against `ExecutionContextInterface` instead of `ExecutionContext` + * [BC BREAK] changed the visibility of the properties in `Validator` from protected to private + * deprecated `ClassMetadataFactoryInterface` in favor of the new `MetadataFactoryInterface` + * deprecated `ClassMetadataFactory::getClassMetadata` in favor of `getMetadataFor` + * created `MetadataInterface`, `PropertyMetadataInterface`, `ClassBasedInterface` and `PropertyMetadataContainerInterface` + * deprecated `GraphWalker` in favor of the new `ValidationVisitorInterface` + * deprecated `ExecutionContext::addViolationAtPath` + * deprecated `ExecutionContext::addViolationAtSubPath` in favor of `ExecutionContextInterface::addViolationAt` + * deprecated `ExecutionContext::getCurrentClass` in favor of `ExecutionContextInterface::getClassName` + * deprecated `ExecutionContext::getCurrentProperty` in favor of `ExecutionContextInterface::getPropertyName` + * deprecated `ExecutionContext::getCurrentValue` in favor of `ExecutionContextInterface::getValue` + * deprecated `ExecutionContext::getGraphWalker` in favor of `ExecutionContextInterface::validate` and `ExecutionContextInterface::validateValue` + * improved `ValidatorInterface::validateValue` to accept arrays of constraints + * changed `ValidatorInterface::getMetadataFactory` to return a `MetadataFactoryInterface` instead of a `ClassMetadataFactoryInterface` + * removed `ClassMetadataFactoryInterface` type hint from `ValidatorBuilderInterface::setMetadataFactory`. + As of Symfony 2.3, this method will be typed against `MetadataFactoryInterface` instead. + * [BC BREAK] the switches `traverse` and `deep` in the `Valid` constraint and in `GraphWalker::walkReference` + are ignored for arrays now. Arrays are always traversed recursively. + * added dependency to Translation component + * violation messages are now translated with a TranslatorInterface implementation + * [BC BREAK] inserted argument `$message` in the constructor of `ConstraintViolation` + * [BC BREAK] inserted arguments `$translator` and `$translationDomain` in the constructor of `ExecutionContext` + * [BC BREAK] inserted arguments `$translator` and `$translationDomain` in the constructor of `GraphWalker` + * [BC BREAK] inserted arguments `$translator` and `$translationDomain` in the constructor of `ValidationVisitor` + * [BC BREAK] inserted arguments `$translator` and `$translationDomain` in the constructor of `Validator` + * [BC BREAK] added `setTranslator()` and `setTranslationDomain()` to `ValidatorBuilderInterface` + * improved the Validator to support pluralized messages by default + * [BC BREAK] changed the source of all pluralized messages in the translation files to the pluralized version + * added ExceptionInterface, BadMethodCallException and InvalidArgumentException + +2.1.0 +----- + + * added support for `ctype_*` assertions in `TypeValidator` + * improved the ImageValidator with min width, max width, min height, and max height constraints + * added support for MIME with wildcard in FileValidator + * changed Collection validator to add "missing" and "extra" errors to + individual fields + * changed default value for `extraFieldsMessage` and `missingFieldsMessage` + in Collection constraint + * made ExecutionContext immutable + * deprecated Constraint methods `setMessage`, `getMessageTemplate` and + `getMessageParameters` + * added support for dynamic group sequences with the GroupSequenceProvider pattern + * [BC BREAK] ConstraintValidatorInterface method `isValid` has been renamed to + `validate`, its return value was dropped. ConstraintValidator still contains + `isValid` for BC + * [BC BREAK] collections in fields annotated with `Valid` are not traversed + recursively anymore by default. `Valid` contains a new property `deep` + which enables the BC behavior. + * added Count constraint + * added Length constraint + * added Range constraint + * deprecated the Min and Max constraints + * deprecated the MinLength and MaxLength constraints + * added Validation and ValidatorBuilderInterface + * deprecated ValidatorContext, ValidatorContextInterface and ValidatorFactory diff --git a/src/vendor/symfony/validator/Command/DebugCommand.php b/src/vendor/symfony/validator/Command/DebugCommand.php new file mode 100644 index 0000000..bd892c5 --- /dev/null +++ b/src/vendor/symfony/validator/Command/DebugCommand.php @@ -0,0 +1,252 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\Dumper; +use Symfony\Component\Console\Helper\Table; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Finder\Exception\DirectoryNotFoundException; +use Symfony\Component\Finder\Finder; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Mapping\AutoMappingStrategy; +use Symfony\Component\Validator\Mapping\CascadingStrategy; +use Symfony\Component\Validator\Mapping\ClassMetadataInterface; +use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface; +use Symfony\Component\Validator\Mapping\GenericMetadata; +use Symfony\Component\Validator\Mapping\TraversalStrategy; + +/** + * A console command to debug Validators information. + * + * @author Loïc Frémont + */ +class DebugCommand extends Command +{ + protected static $defaultName = 'debug:validator'; + protected static $defaultDescription = 'Display validation constraints for classes'; + + private $validator; + + public function __construct(MetadataFactoryInterface $validator) + { + parent::__construct(); + + $this->validator = $validator; + } + + protected function configure() + { + $this + ->addArgument('class', InputArgument::REQUIRED, 'A fully qualified class name or a path') + ->addOption('show-all', null, InputOption::VALUE_NONE, 'Show all classes even if they have no validation constraints') + ->setDescription(self::$defaultDescription) + ->setHelp(<<<'EOF' +The %command.name% 'App\Entity\Dummy' command dumps the validators for the dummy class. + +The %command.name% src/ command dumps the validators for the `src` directory. +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $class = $input->getArgument('class'); + + if (class_exists($class)) { + $this->dumpValidatorsForClass($input, $output, $class); + + return 0; + } + + try { + foreach ($this->getResourcesByPath($class) as $class) { + $this->dumpValidatorsForClass($input, $output, $class); + } + } catch (DirectoryNotFoundException $exception) { + $io = new SymfonyStyle($input, $output); + $io->error(sprintf('Neither class nor path were found with "%s" argument.', $input->getArgument('class'))); + + return 1; + } + + return 0; + } + + private function dumpValidatorsForClass(InputInterface $input, OutputInterface $output, string $class): void + { + $io = new SymfonyStyle($input, $output); + $title = sprintf('%s', $class); + $rows = []; + $dump = new Dumper($output); + + /** @var ClassMetadataInterface $classMetadata */ + $classMetadata = $this->validator->getMetadataFor($class); + + foreach ($this->getClassConstraintsData($classMetadata) as $data) { + $rows[] = [ + '-', + $data['class'], + implode(', ', $data['groups']), + $dump($data['options']), + ]; + } + + foreach ($this->getConstrainedPropertiesData($classMetadata) as $propertyName => $constraintsData) { + foreach ($constraintsData as $data) { + $rows[] = [ + $propertyName, + $data['class'], + implode(', ', $data['groups']), + $dump($data['options']), + ]; + } + } + + if (!$rows) { + if (false === $input->getOption('show-all')) { + return; + } + + $io->section($title); + $io->text('No validators were found for this class.'); + + return; + } + + $io->section($title); + + $table = new Table($output); + $table->setHeaders(['Property', 'Name', 'Groups', 'Options']); + $table->setRows($rows); + $table->setColumnMaxWidth(3, 80); + $table->render(); + } + + private function getClassConstraintsData(ClassMetadataInterface $classMetadata): iterable + { + foreach ($classMetadata->getConstraints() as $constraint) { + yield [ + 'class' => \get_class($constraint), + 'groups' => $constraint->groups, + 'options' => $this->getConstraintOptions($constraint), + ]; + } + } + + private function getConstrainedPropertiesData(ClassMetadataInterface $classMetadata): array + { + $data = []; + + foreach ($classMetadata->getConstrainedProperties() as $constrainedProperty) { + $data[$constrainedProperty] = $this->getPropertyData($classMetadata, $constrainedProperty); + } + + return $data; + } + + private function getPropertyData(ClassMetadataInterface $classMetadata, string $constrainedProperty): array + { + $data = []; + + $propertyMetadata = $classMetadata->getPropertyMetadata($constrainedProperty); + foreach ($propertyMetadata as $metadata) { + $autoMapingStrategy = 'Not supported'; + if ($metadata instanceof GenericMetadata) { + switch ($metadata->getAutoMappingStrategy()) { + case AutoMappingStrategy::ENABLED: $autoMapingStrategy = 'Enabled'; break; + case AutoMappingStrategy::DISABLED: $autoMapingStrategy = 'Disabled'; break; + case AutoMappingStrategy::NONE: $autoMapingStrategy = 'None'; break; + } + } + $traversalStrategy = 'None'; + if (TraversalStrategy::TRAVERSE === $metadata->getTraversalStrategy()) { + $traversalStrategy = 'Traverse'; + } + if (TraversalStrategy::IMPLICIT === $metadata->getTraversalStrategy()) { + $traversalStrategy = 'Implicit'; + } + + $data[] = [ + 'class' => 'property options', + 'groups' => [], + 'options' => [ + 'cascadeStrategy' => CascadingStrategy::CASCADE === $metadata->getCascadingStrategy() ? 'Cascade' : 'None', + 'autoMappingStrategy' => $autoMapingStrategy, + 'traversalStrategy' => $traversalStrategy, + ], + ]; + foreach ($metadata->getConstraints() as $constraint) { + $data[] = [ + 'class' => \get_class($constraint), + 'groups' => $constraint->groups, + 'options' => $this->getConstraintOptions($constraint), + ]; + } + } + + return $data; + } + + private function getConstraintOptions(Constraint $constraint): array + { + $options = []; + + foreach (array_keys(get_object_vars($constraint)) as $propertyName) { + // Groups are dumped on a specific column. + if ('groups' === $propertyName) { + continue; + } + + $options[$propertyName] = $constraint->$propertyName; + } + + ksort($options); + + return $options; + } + + private function getResourcesByPath(string $path): array + { + $finder = new Finder(); + $finder->files()->in($path)->name('*.php')->sortByName(true); + $classes = []; + + foreach ($finder as $file) { + $fileContent = file_get_contents($file->getRealPath()); + + preg_match('/namespace (.+);/', $fileContent, $matches); + + $namespace = $matches[1] ?? null; + + if (!preg_match('/class +([^{ ]+)/', $fileContent, $matches)) { + // no class found + continue; + } + + $className = trim($matches[1]); + + if (null !== $namespace) { + $classes[] = $namespace.'\\'.$className; + } else { + $classes[] = $className; + } + } + + return $classes; + } +} diff --git a/src/vendor/symfony/validator/Constraint.php b/src/vendor/symfony/validator/Constraint.php new file mode 100644 index 0000000..27ddcb8 --- /dev/null +++ b/src/vendor/symfony/validator/Constraint.php @@ -0,0 +1,313 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator; + +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; +use Symfony\Component\Validator\Exception\InvalidArgumentException; +use Symfony\Component\Validator\Exception\InvalidOptionsException; +use Symfony\Component\Validator\Exception\MissingOptionsException; + +/** + * Contains the properties of a constraint definition. + * + * A constraint can be defined on a class, a property or a getter method. + * The Constraint class encapsulates all the configuration required for + * validating this class, property or getter result successfully. + * + * Constraint instances are immutable and serializable. + * + * @author Bernhard Schussek + */ +abstract class Constraint +{ + /** + * The name of the group given to all constraints with no explicit group. + */ + public const DEFAULT_GROUP = 'Default'; + + /** + * Marks a constraint that can be put onto classes. + */ + public const CLASS_CONSTRAINT = 'class'; + + /** + * Marks a constraint that can be put onto properties. + */ + public const PROPERTY_CONSTRAINT = 'property'; + + /** + * Maps error codes to the names of their constants. + */ + protected static $errorNames = []; + + /** + * Domain-specific data attached to a constraint. + * + * @var mixed + */ + public $payload; + + /** + * The groups that the constraint belongs to. + * + * @var string[] + */ + public $groups; + + /** + * Returns the name of the given error code. + * + * @return string + * + * @throws InvalidArgumentException If the error code does not exist + */ + public static function getErrorName(string $errorCode) + { + if (!isset(static::$errorNames[$errorCode])) { + throw new InvalidArgumentException(sprintf('The error code "%s" does not exist for constraint of type "%s".', $errorCode, static::class)); + } + + return static::$errorNames[$errorCode]; + } + + /** + * Initializes the constraint with options. + * + * You should pass an associative array. The keys should be the names of + * existing properties in this class. The values should be the value for these + * properties. + * + * Alternatively you can override the method getDefaultOption() to return the + * name of an existing property. If no associative array is passed, this + * property is set instead. + * + * You can force that certain options are set by overriding + * getRequiredOptions() to return the names of these options. If any + * option is not set here, an exception is thrown. + * + * @param mixed $options The options (as associative array) + * or the value for the default + * option (any other type) + * @param string[] $groups An array of validation groups + * @param mixed $payload Domain-specific data attached to a constraint + * + * @throws InvalidOptionsException When you pass the names of non-existing + * options + * @throws MissingOptionsException When you don't pass any of the options + * returned by getRequiredOptions() + * @throws ConstraintDefinitionException When you don't pass an associative + * array, but getDefaultOption() returns + * null + */ + public function __construct($options = null, array $groups = null, $payload = null) + { + unset($this->groups); // enable lazy initialization + + $options = $this->normalizeOptions($options); + if (null !== $groups) { + $options['groups'] = $groups; + } + $options['payload'] = $payload ?? $options['payload'] ?? null; + + foreach ($options as $name => $value) { + $this->$name = $value; + } + } + + protected function normalizeOptions($options): array + { + $normalizedOptions = []; + $defaultOption = $this->getDefaultOption(); + $invalidOptions = []; + $missingOptions = array_flip((array) $this->getRequiredOptions()); + $knownOptions = get_class_vars(static::class); + + if (\is_array($options) && isset($options['value']) && !property_exists($this, 'value')) { + if (null === $defaultOption) { + throw new ConstraintDefinitionException(sprintf('No default option is configured for constraint "%s".', static::class)); + } + + $options[$defaultOption] = $options['value']; + unset($options['value']); + } + + if (\is_array($options)) { + reset($options); + } + if ($options && \is_array($options) && \is_string(key($options))) { + foreach ($options as $option => $value) { + if (\array_key_exists($option, $knownOptions)) { + $normalizedOptions[$option] = $value; + unset($missingOptions[$option]); + } else { + $invalidOptions[] = $option; + } + } + } elseif (null !== $options && !(\is_array($options) && 0 === \count($options))) { + if (null === $defaultOption) { + throw new ConstraintDefinitionException(sprintf('No default option is configured for constraint "%s".', static::class)); + } + + if (\array_key_exists($defaultOption, $knownOptions)) { + $normalizedOptions[$defaultOption] = $options; + unset($missingOptions[$defaultOption]); + } else { + $invalidOptions[] = $defaultOption; + } + } + + if (\count($invalidOptions) > 0) { + throw new InvalidOptionsException(sprintf('The options "%s" do not exist in constraint "%s".', implode('", "', $invalidOptions), static::class), $invalidOptions); + } + + if (\count($missingOptions) > 0) { + throw new MissingOptionsException(sprintf('The options "%s" must be set for constraint "%s".', implode('", "', array_keys($missingOptions)), static::class), array_keys($missingOptions)); + } + + return $normalizedOptions; + } + + /** + * Sets the value of a lazily initialized option. + * + * Corresponding properties are added to the object on first access. Hence + * this method will be called at most once per constraint instance and + * option name. + * + * @param mixed $value The value to set + * + * @throws InvalidOptionsException If an invalid option name is given + */ + public function __set(string $option, $value) + { + if ('groups' === $option) { + $this->groups = (array) $value; + + return; + } + + throw new InvalidOptionsException(sprintf('The option "%s" does not exist in constraint "%s".', $option, static::class), [$option]); + } + + /** + * Returns the value of a lazily initialized option. + * + * Corresponding properties are added to the object on first access. Hence + * this method will be called at most once per constraint instance and + * option name. + * + * @return mixed + * + * @throws InvalidOptionsException If an invalid option name is given + */ + public function __get(string $option) + { + if ('groups' === $option) { + $this->groups = [self::DEFAULT_GROUP]; + + return $this->groups; + } + + throw new InvalidOptionsException(sprintf('The option "%s" does not exist in constraint "%s".', $option, static::class), [$option]); + } + + /** + * @return bool + */ + public function __isset(string $option) + { + return 'groups' === $option; + } + + /** + * Adds the given group if this constraint is in the Default group. + */ + public function addImplicitGroupName(string $group) + { + if (null === $this->groups && \array_key_exists('groups', (array) $this)) { + throw new \LogicException(sprintf('"%s::$groups" is set to null. Did you forget to call "%s::__construct()"?', static::class, self::class)); + } + + if (\in_array(self::DEFAULT_GROUP, $this->groups) && !\in_array($group, $this->groups)) { + $this->groups[] = $group; + } + } + + /** + * Returns the name of the default option. + * + * Override this method to define a default option. + * + * @return string|null + * + * @see __construct() + */ + public function getDefaultOption() + { + return null; + } + + /** + * Returns the name of the required options. + * + * Override this method if you want to define required options. + * + * @return string[] + * + * @see __construct() + */ + public function getRequiredOptions() + { + return []; + } + + /** + * Returns the name of the class that validates this constraint. + * + * By default, this is the fully qualified name of the constraint class + * suffixed with "Validator". You can override this method to change that + * behavior. + * + * @return string + */ + public function validatedBy() + { + return static::class.'Validator'; + } + + /** + * Returns whether the constraint can be put onto classes, properties or + * both. + * + * This method should return one or more of the constants + * Constraint::CLASS_CONSTRAINT and Constraint::PROPERTY_CONSTRAINT. + * + * @return string|string[] One or more constant values + */ + public function getTargets() + { + return self::PROPERTY_CONSTRAINT; + } + + /** + * Optimizes the serialized value to minimize storage space. + * + * @internal + */ + public function __sleep(): array + { + // Initialize "groups" option if it is not set + $this->groups; + + return array_keys(get_object_vars($this)); + } +} diff --git a/src/vendor/symfony/validator/ConstraintValidator.php b/src/vendor/symfony/validator/ConstraintValidator.php new file mode 100644 index 0000000..4a22dc1 --- /dev/null +++ b/src/vendor/symfony/validator/ConstraintValidator.php @@ -0,0 +1,163 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator; + +use Symfony\Component\Validator\Context\ExecutionContextInterface; + +/** + * Base class for constraint validators. + * + * @author Bernhard Schussek + */ +abstract class ConstraintValidator implements ConstraintValidatorInterface +{ + /** + * Whether to format {@link \DateTime} objects, either with the {@link \IntlDateFormatter} + * (if it is available) or as RFC-3339 dates ("Y-m-d H:i:s"). + */ + public const PRETTY_DATE = 1; + + /** + * Whether to cast objects with a "__toString()" method to strings. + */ + public const OBJECT_TO_STRING = 2; + + /** + * @var ExecutionContextInterface + */ + protected $context; + + /** + * {@inheritdoc} + */ + public function initialize(ExecutionContextInterface $context) + { + $this->context = $context; + } + + /** + * Returns a string representation of the type of the value. + * + * This method should be used if you pass the type of a value as + * message parameter to a constraint violation. Note that such + * parameters should usually not be included in messages aimed at + * non-technical people. + * + * @param mixed $value The value to return the type of + * + * @return string + */ + protected function formatTypeOf($value) + { + return get_debug_type($value); + } + + /** + * Returns a string representation of the value. + * + * This method returns the equivalent PHP tokens for most scalar types + * (i.e. "false" for false, "1" for 1 etc.). Strings are always wrapped + * in double quotes ("). Objects, arrays and resources are formatted as + * "object", "array" and "resource". If the $format bitmask contains + * the PRETTY_DATE bit, then {@link \DateTime} objects will be formatted + * with the {@link \IntlDateFormatter}. If it is not available, they will be + * formatted as RFC-3339 dates ("Y-m-d H:i:s"). + * + * Be careful when passing message parameters to a constraint violation + * that (may) contain objects, arrays or resources. These parameters + * should only be displayed for technical users. Non-technical users + * won't know what an "object", "array" or "resource" is and will be + * confused by the violation message. + * + * @param mixed $value The value to format as string + * @param int $format A bitwise combination of the format + * constants in this class + * + * @return string + */ + protected function formatValue($value, int $format = 0) + { + if (($format & self::PRETTY_DATE) && $value instanceof \DateTimeInterface) { + if (class_exists(\IntlDateFormatter::class)) { + $formatter = new \IntlDateFormatter(\Locale::getDefault(), \IntlDateFormatter::MEDIUM, \IntlDateFormatter::SHORT, 'UTC'); + + return $formatter->format(new \DateTime( + $value->format('Y-m-d H:i:s.u'), + new \DateTimeZone('UTC') + )); + } + + return $value->format('Y-m-d H:i:s'); + } + + if ($value instanceof \UnitEnum) { + return $value->name; + } + + if (\is_object($value)) { + if (($format & self::OBJECT_TO_STRING) && method_exists($value, '__toString')) { + return $value->__toString(); + } + + return 'object'; + } + + if (\is_array($value)) { + return 'array'; + } + + if (\is_string($value)) { + return '"'.$value.'"'; + } + + if (\is_resource($value)) { + return 'resource'; + } + + if (null === $value) { + return 'null'; + } + + if (false === $value) { + return 'false'; + } + + if (true === $value) { + return 'true'; + } + + return (string) $value; + } + + /** + * Returns a string representation of a list of values. + * + * Each of the values is converted to a string using + * {@link formatValue()}. The values are then concatenated with commas. + * + * @param array $values A list of values + * @param int $format A bitwise combination of the format + * constants in this class + * + * @return string + * + * @see formatValue() + */ + protected function formatValues(array $values, int $format = 0) + { + foreach ($values as $key => $value) { + $values[$key] = $this->formatValue($value, $format); + } + + return implode(', ', $values); + } +} diff --git a/src/vendor/symfony/validator/ConstraintValidatorFactory.php b/src/vendor/symfony/validator/ConstraintValidatorFactory.php new file mode 100644 index 0000000..45f3ca9 --- /dev/null +++ b/src/vendor/symfony/validator/ConstraintValidatorFactory.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator; + +use Symfony\Component\Validator\Constraints\ExpressionValidator; + +/** + * Default implementation of the ConstraintValidatorFactoryInterface. + * + * This enforces the convention that the validatedBy() method on any + * Constraint will return the class name of the ConstraintValidator that + * should validate the Constraint. + * + * @author Bernhard Schussek + */ +class ConstraintValidatorFactory implements ConstraintValidatorFactoryInterface +{ + protected $validators = []; + + public function __construct() + { + } + + /** + * {@inheritdoc} + */ + public function getInstance(Constraint $constraint) + { + $className = $constraint->validatedBy(); + + if (!isset($this->validators[$className])) { + $this->validators[$className] = 'validator.expression' === $className + ? new ExpressionValidator() + : new $className(); + } + + return $this->validators[$className]; + } +} diff --git a/src/vendor/symfony/validator/ConstraintValidatorFactoryInterface.php b/src/vendor/symfony/validator/ConstraintValidatorFactoryInterface.php new file mode 100644 index 0000000..b647645 --- /dev/null +++ b/src/vendor/symfony/validator/ConstraintValidatorFactoryInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator; + +/** + * Specifies an object able to return the correct ConstraintValidatorInterface + * instance given a Constraint object. + */ +interface ConstraintValidatorFactoryInterface +{ + /** + * Given a Constraint, this returns the ConstraintValidatorInterface + * object that should be used to verify its validity. + * + * @return ConstraintValidatorInterface + */ + public function getInstance(Constraint $constraint); +} diff --git a/src/vendor/symfony/validator/ConstraintValidatorInterface.php b/src/vendor/symfony/validator/ConstraintValidatorInterface.php new file mode 100644 index 0000000..308c1e6 --- /dev/null +++ b/src/vendor/symfony/validator/ConstraintValidatorInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator; + +use Symfony\Component\Validator\Context\ExecutionContextInterface; + +/** + * @author Bernhard Schussek + */ +interface ConstraintValidatorInterface +{ + /** + * Initializes the constraint validator. + */ + public function initialize(ExecutionContextInterface $context); + + /** + * Checks if the passed value is valid. + * + * @param mixed $value The value that should be validated + */ + public function validate($value, Constraint $constraint); +} diff --git a/src/vendor/symfony/validator/ConstraintViolation.php b/src/vendor/symfony/validator/ConstraintViolation.php new file mode 100644 index 0000000..33aa42a --- /dev/null +++ b/src/vendor/symfony/validator/ConstraintViolation.php @@ -0,0 +1,181 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator; + +/** + * Default implementation of {@ConstraintViolationInterface}. + * + * @author Bernhard Schussek + */ +class ConstraintViolation implements ConstraintViolationInterface +{ + private $message; + private $messageTemplate; + private $parameters; + private $plural; + private $root; + private $propertyPath; + private $invalidValue; + private $constraint; + private $code; + private $cause; + + /** + * Creates a new constraint violation. + * + * @param string|\Stringable $message The violation message as a string or a stringable object + * @param string|null $messageTemplate The raw violation message + * @param array $parameters The parameters to substitute in the + * raw violation message + * @param mixed $root The value originally passed to the + * validator + * @param string|null $propertyPath The property path from the root + * value to the invalid value + * @param mixed $invalidValue The invalid value that caused this + * violation + * @param int|null $plural The number for determining the plural + * form when translating the message + * @param string|null $code The error code of the violation + * @param Constraint|null $constraint The constraint whose validation + * caused the violation + * @param mixed $cause The cause of the violation + */ + public function __construct($message, ?string $messageTemplate, array $parameters, $root, ?string $propertyPath, $invalidValue, int $plural = null, string $code = null, Constraint $constraint = null, $cause = null) + { + if (!\is_string($message) && !(\is_object($message) && method_exists($message, '__toString'))) { + throw new \TypeError('Constraint violation message should be a string or an object which implements the __toString() method.'); + } + + $this->message = $message; + $this->messageTemplate = $messageTemplate; + $this->parameters = $parameters; + $this->plural = $plural; + $this->root = $root; + $this->propertyPath = $propertyPath; + $this->invalidValue = $invalidValue; + $this->constraint = $constraint; + $this->code = $code; + $this->cause = $cause; + } + + /** + * Converts the violation into a string for debugging purposes. + * + * @return string + */ + public function __toString() + { + if (\is_object($this->root)) { + $class = 'Object('.\get_class($this->root).')'; + } elseif (\is_array($this->root)) { + $class = 'Array'; + } else { + $class = (string) $this->root; + } + + $propertyPath = (string) $this->propertyPath; + + if ('' !== $propertyPath && '[' !== $propertyPath[0] && '' !== $class) { + $class .= '.'; + } + + if (null !== ($code = $this->code) && '' !== $code) { + $code = ' (code '.$code.')'; + } + + return $class.$propertyPath.":\n ".$this->getMessage().$code; + } + + /** + * {@inheritdoc} + */ + public function getMessageTemplate() + { + return (string) $this->messageTemplate; + } + + /** + * {@inheritdoc} + */ + public function getParameters() + { + return $this->parameters; + } + + /** + * {@inheritdoc} + */ + public function getPlural() + { + return $this->plural; + } + + /** + * {@inheritdoc} + */ + public function getMessage() + { + return $this->message; + } + + /** + * {@inheritdoc} + */ + public function getRoot() + { + return $this->root; + } + + /** + * {@inheritdoc} + */ + public function getPropertyPath() + { + return (string) $this->propertyPath; + } + + /** + * {@inheritdoc} + */ + public function getInvalidValue() + { + return $this->invalidValue; + } + + /** + * Returns the constraint whose validation caused the violation. + * + * @return Constraint|null + */ + public function getConstraint() + { + return $this->constraint; + } + + /** + * Returns the cause of the violation. + * + * @return mixed + */ + public function getCause() + { + return $this->cause; + } + + /** + * {@inheritdoc} + */ + public function getCode() + { + return $this->code; + } +} diff --git a/src/vendor/symfony/validator/ConstraintViolationInterface.php b/src/vendor/symfony/validator/ConstraintViolationInterface.php new file mode 100644 index 0000000..fd7d414 --- /dev/null +++ b/src/vendor/symfony/validator/ConstraintViolationInterface.php @@ -0,0 +1,120 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator; + +/** + * A violation of a constraint that happened during validation. + * + * For each constraint that fails during validation one or more violations are + * created. The violations store the violation message, the path to the failing + * element in the validation graph and the root element that was originally + * passed to the validator. For example, take the following graph: + * + * (Person)---(firstName: string) + * \ + * (address: Address)---(street: string) + * + * If the Person object is validated and validation fails for the + * "firstName" property, the generated violation has the Person + * instance as root and the property path "firstName". If validation fails + * for the "street" property of the related Address instance, the root + * element is still the person, but the property path is "address.street". + * + * @author Bernhard Schussek + */ +interface ConstraintViolationInterface +{ + /** + * Returns the violation message. + * + * @return string|\Stringable + */ + public function getMessage(); + + /** + * Returns the raw violation message. + * + * The raw violation message contains placeholders for the parameters + * returned by {@link getParameters}. Typically you'll pass the + * message template and parameters to a translation engine. + * + * @return string The raw violation message + */ + public function getMessageTemplate(); + + /** + * Returns the parameters to be inserted into the raw violation message. + * + * @return array a possibly empty list of parameters indexed by the names + * that appear in the message template + * + * @see getMessageTemplate() + */ + public function getParameters(); + + /** + * Returns a number for pluralizing the violation message. + * + * For example, the message template could have different translation based + * on a parameter "choices": + * + *
    + *
  • Please select exactly one entry. (choices=1)
  • + *
  • Please select two entries. (choices=2)
  • + *
+ * + * This method returns the value of the parameter for choosing the right + * pluralization form (in this case "choices"). + * + * @return int|null The number to use to pluralize of the message + */ + public function getPlural(); + + /** + * Returns the root element of the validation. + * + * @return mixed The value that was passed originally to the validator when + * the validation was started. Because the validator traverses + * the object graph, the value at which the violation occurs + * is not necessarily the value that was originally validated. + */ + public function getRoot(); + + /** + * Returns the property path from the root element to the violation. + * + * @return string The property path indicates how the validator reached + * the invalid value from the root element. If the root + * element is a Person instance with a property + * "address" that contains an Address instance + * with an invalid property "street", the generated property + * path is "address.street". Property access is denoted by + * dots, while array access is denoted by square brackets, + * for example "addresses[1].street". + */ + public function getPropertyPath(); + + /** + * Returns the value that caused the violation. + * + * @return mixed the invalid value that caused the validated constraint to + * fail + */ + public function getInvalidValue(); + + /** + * Returns a machine-digestible error code for the violation. + * + * @return string|null + */ + public function getCode(); +} diff --git a/src/vendor/symfony/validator/ConstraintViolationList.php b/src/vendor/symfony/validator/ConstraintViolationList.php new file mode 100644 index 0000000..3d459b2 --- /dev/null +++ b/src/vendor/symfony/validator/ConstraintViolationList.php @@ -0,0 +1,203 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator; + +/** + * Default implementation of {@ConstraintViolationListInterface}. + * + * @author Bernhard Schussek + * + * @implements \IteratorAggregate + */ +class ConstraintViolationList implements \IteratorAggregate, ConstraintViolationListInterface +{ + /** + * @var list + */ + private $violations = []; + + /** + * Creates a new constraint violation list. + * + * @param iterable $violations The constraint violations to add to the list + */ + public function __construct(iterable $violations = []) + { + foreach ($violations as $violation) { + $this->add($violation); + } + } + + public static function createFromMessage(string $message): self + { + $self = new self(); + $self->add(new ConstraintViolation($message, '', [], null, '', null)); + + return $self; + } + + /** + * Converts the violation into a string for debugging purposes. + * + * @return string + */ + public function __toString() + { + $string = ''; + + foreach ($this->violations as $violation) { + $string .= $violation."\n"; + } + + return $string; + } + + /** + * {@inheritdoc} + */ + public function add(ConstraintViolationInterface $violation) + { + $this->violations[] = $violation; + } + + /** + * {@inheritdoc} + */ + public function addAll(ConstraintViolationListInterface $otherList) + { + foreach ($otherList as $violation) { + $this->violations[] = $violation; + } + } + + /** + * {@inheritdoc} + */ + public function get(int $offset) + { + if (!isset($this->violations[$offset])) { + throw new \OutOfBoundsException(sprintf('The offset "%s" does not exist.', $offset)); + } + + return $this->violations[$offset]; + } + + /** + * {@inheritdoc} + */ + public function has(int $offset) + { + return isset($this->violations[$offset]); + } + + /** + * {@inheritdoc} + */ + public function set(int $offset, ConstraintViolationInterface $violation) + { + $this->violations[$offset] = $violation; + } + + /** + * {@inheritdoc} + */ + public function remove(int $offset) + { + unset($this->violations[$offset]); + } + + /** + * {@inheritdoc} + * + * @return \ArrayIterator + */ + #[\ReturnTypeWillChange] + public function getIterator() + { + return new \ArrayIterator($this->violations); + } + + /** + * @return int + */ + #[\ReturnTypeWillChange] + public function count() + { + return \count($this->violations); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function offsetExists($offset) + { + return $this->has($offset); + } + + /** + * {@inheritdoc} + * + * @return ConstraintViolationInterface + */ + #[\ReturnTypeWillChange] + public function offsetGet($offset) + { + return $this->get($offset); + } + + /** + * {@inheritdoc} + * + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetSet($offset, $violation) + { + if (null === $offset) { + $this->add($violation); + } else { + $this->set($offset, $violation); + } + } + + /** + * {@inheritdoc} + * + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetUnset($offset) + { + $this->remove($offset); + } + + /** + * Creates iterator for errors with specific codes. + * + * @param string|string[] $codes The codes to find + * + * @return static + */ + public function findByCodes($codes) + { + $codes = (array) $codes; + $violations = []; + foreach ($this as $violation) { + if (\in_array($violation->getCode(), $codes, true)) { + $violations[] = $violation; + } + } + + return new static($violations); + } +} diff --git a/src/vendor/symfony/validator/ConstraintViolationListInterface.php b/src/vendor/symfony/validator/ConstraintViolationListInterface.php new file mode 100644 index 0000000..f994668 --- /dev/null +++ b/src/vendor/symfony/validator/ConstraintViolationListInterface.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator; + +/** + * A list of constraint violations. + * + * @author Bernhard Schussek + * + * @extends \ArrayAccess + * @extends \Traversable + */ +interface ConstraintViolationListInterface extends \Traversable, \Countable, \ArrayAccess +{ + /** + * Adds a constraint violation to this list. + */ + public function add(ConstraintViolationInterface $violation); + + /** + * Merges an existing violation list into this list. + */ + public function addAll(self $otherList); + + /** + * Returns the violation at a given offset. + * + * @param int $offset The offset of the violation + * + * @return ConstraintViolationInterface + * + * @throws \OutOfBoundsException if the offset does not exist + */ + public function get(int $offset); + + /** + * Returns whether the given offset exists. + * + * @param int $offset The violation offset + * + * @return bool + */ + public function has(int $offset); + + /** + * Sets a violation at a given offset. + * + * @param int $offset The violation offset + */ + public function set(int $offset, ConstraintViolationInterface $violation); + + /** + * Removes a violation at a given offset. + * + * @param int $offset The offset to remove + */ + public function remove(int $offset); +} diff --git a/src/vendor/symfony/validator/Constraints/AbstractComparison.php b/src/vendor/symfony/validator/Constraints/AbstractComparison.php new file mode 100644 index 0000000..d492655 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/AbstractComparison.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\PropertyAccess\PropertyAccess; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; +use Symfony\Component\Validator\Exception\LogicException; + +/** + * Used for the comparison of values. + * + * @author Daniel Holmes + * @author Bernhard Schussek + */ +abstract class AbstractComparison extends Constraint +{ + public $message; + public $value; + public $propertyPath; + + /** + * {@inheritdoc} + * + * @param mixed $value the value to compare or a set of options + */ + public function __construct($value = null, $propertyPath = null, string $message = null, array $groups = null, $payload = null, array $options = []) + { + if (\is_array($value)) { + $options = array_merge($value, $options); + } elseif (null !== $value) { + $options['value'] = $value; + } + + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + $this->propertyPath = $propertyPath ?? $this->propertyPath; + + if (null === $this->value && null === $this->propertyPath) { + throw new ConstraintDefinitionException(sprintf('The "%s" constraint requires either the "value" or "propertyPath" option to be set.', static::class)); + } + + if (null !== $this->value && null !== $this->propertyPath) { + throw new ConstraintDefinitionException(sprintf('The "%s" constraint requires only one of the "value" or "propertyPath" options to be set, not both.', static::class)); + } + + if (null !== $this->propertyPath && !class_exists(PropertyAccess::class)) { + throw new LogicException(sprintf('The "%s" constraint requires the Symfony PropertyAccess component to use the "propertyPath" option.', static::class)); + } + } + + /** + * {@inheritdoc} + */ + public function getDefaultOption() + { + return 'value'; + } +} diff --git a/src/vendor/symfony/validator/Constraints/AbstractComparisonValidator.php b/src/vendor/symfony/validator/Constraints/AbstractComparisonValidator.php new file mode 100644 index 0000000..c3a117d --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/AbstractComparisonValidator.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException; +use Symfony\Component\PropertyAccess\PropertyAccess; +use Symfony\Component\PropertyAccess\PropertyAccessorInterface; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; + +/** + * Provides a base class for the validation of property comparisons. + * + * @author Daniel Holmes + * @author Bernhard Schussek + */ +abstract class AbstractComparisonValidator extends ConstraintValidator +{ + private $propertyAccessor; + + public function __construct(PropertyAccessorInterface $propertyAccessor = null) + { + $this->propertyAccessor = $propertyAccessor; + } + + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof AbstractComparison) { + throw new UnexpectedTypeException($constraint, AbstractComparison::class); + } + + if (null === $value) { + return; + } + + if ($path = $constraint->propertyPath) { + if (null === $object = $this->context->getObject()) { + return; + } + + try { + $comparedValue = $this->getPropertyAccessor()->getValue($object, $path); + } catch (NoSuchPropertyException $e) { + throw new ConstraintDefinitionException(sprintf('Invalid property path "%s" provided to "%s" constraint: ', $path, get_debug_type($constraint)).$e->getMessage(), 0, $e); + } + } else { + $comparedValue = $constraint->value; + } + + // Convert strings to DateTimes if comparing another DateTime + // This allows to compare with any date/time value supported by + // the DateTime constructor: + // https://php.net/datetime.formats + if (\is_string($comparedValue) && $value instanceof \DateTimeInterface) { + // If $value is immutable, convert the compared value to a DateTimeImmutable too, otherwise use DateTime + $dateTimeClass = $value instanceof \DateTimeImmutable ? \DateTimeImmutable::class : \DateTime::class; + + try { + $comparedValue = new $dateTimeClass($comparedValue); + } catch (\Exception $e) { + throw new ConstraintDefinitionException(sprintf('The compared value "%s" could not be converted to a "%s" instance in the "%s" constraint.', $comparedValue, $dateTimeClass, get_debug_type($constraint))); + } + } + + if (!$this->compareValues($value, $comparedValue)) { + $violationBuilder = $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value, self::OBJECT_TO_STRING | self::PRETTY_DATE)) + ->setParameter('{{ compared_value }}', $this->formatValue($comparedValue, self::OBJECT_TO_STRING | self::PRETTY_DATE)) + ->setParameter('{{ compared_value_type }}', $this->formatTypeOf($comparedValue)) + ->setCode($this->getErrorCode()); + + if (null !== $path) { + $violationBuilder->setParameter('{{ compared_value_path }}', $path); + } + + $violationBuilder->addViolation(); + } + } + + private function getPropertyAccessor(): PropertyAccessorInterface + { + if (null === $this->propertyAccessor) { + $this->propertyAccessor = PropertyAccess::createPropertyAccessor(); + } + + return $this->propertyAccessor; + } + + /** + * Compares the two given values to find if their relationship is valid. + * + * @param mixed $value1 The first value to compare + * @param mixed $value2 The second value to compare + * + * @return bool + */ + abstract protected function compareValues($value1, $value2); + + /** + * Returns the error code used if the comparison fails. + * + * @return string|null + */ + protected function getErrorCode() + { + return null; + } +} diff --git a/src/vendor/symfony/validator/Constraints/All.php b/src/vendor/symfony/validator/Constraints/All.php new file mode 100644 index 0000000..5b42976 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/All.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class All extends Composite +{ + public $constraints = []; + + public function __construct($constraints = null, array $groups = null, $payload = null) + { + parent::__construct($constraints ?? [], $groups, $payload); + } + + public function getDefaultOption() + { + return 'constraints'; + } + + public function getRequiredOptions() + { + return ['constraints']; + } + + protected function getCompositeOption() + { + return 'constraints'; + } +} diff --git a/src/vendor/symfony/validator/Constraints/AllValidator.php b/src/vendor/symfony/validator/Constraints/AllValidator.php new file mode 100644 index 0000000..611ac8d --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/AllValidator.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; +use Symfony\Component\Validator\Exception\UnexpectedValueException; + +/** + * @author Bernhard Schussek + */ +class AllValidator extends ConstraintValidator +{ + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof All) { + throw new UnexpectedTypeException($constraint, All::class); + } + + if (null === $value) { + return; + } + + if (!\is_array($value) && !$value instanceof \Traversable) { + throw new UnexpectedValueException($value, 'iterable'); + } + + $context = $this->context; + + $validator = $context->getValidator()->inContext($context); + + foreach ($value as $key => $element) { + $validator->atPath('['.$key.']')->validate($element, $constraint->constraints); + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/AtLeastOneOf.php b/src/vendor/symfony/validator/Constraints/AtLeastOneOf.php new file mode 100644 index 0000000..f01ed9c --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/AtLeastOneOf.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Przemysław Bogusz + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class AtLeastOneOf extends Composite +{ + public const AT_LEAST_ONE_OF_ERROR = 'f27e6d6c-261a-4056-b391-6673a623531c'; + + protected static $errorNames = [ + self::AT_LEAST_ONE_OF_ERROR => 'AT_LEAST_ONE_OF_ERROR', + ]; + + public $constraints = []; + public $message = 'This value should satisfy at least one of the following constraints:'; + public $messageCollection = 'Each element of this collection should satisfy its own set of constraints.'; + public $includeInternalMessages = true; + + public function __construct($constraints = null, array $groups = null, $payload = null, string $message = null, string $messageCollection = null, bool $includeInternalMessages = null) + { + parent::__construct($constraints ?? [], $groups, $payload); + + $this->message = $message ?? $this->message; + $this->messageCollection = $messageCollection ?? $this->messageCollection; + $this->includeInternalMessages = $includeInternalMessages ?? $this->includeInternalMessages; + } + + public function getDefaultOption() + { + return 'constraints'; + } + + public function getRequiredOptions() + { + return ['constraints']; + } + + protected function getCompositeOption() + { + return 'constraints'; + } +} diff --git a/src/vendor/symfony/validator/Constraints/AtLeastOneOfValidator.php b/src/vendor/symfony/validator/Constraints/AtLeastOneOfValidator.php new file mode 100644 index 0000000..692b117 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/AtLeastOneOfValidator.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; + +/** + * @author Przemysław Bogusz + */ +class AtLeastOneOfValidator extends ConstraintValidator +{ + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof AtLeastOneOf) { + throw new UnexpectedTypeException($constraint, AtLeastOneOf::class); + } + + $validator = $this->context->getValidator(); + + // Build a first violation to have the base message of the constraint translated + $baseMessageContext = clone $this->context; + $baseMessageContext->buildViolation($constraint->message)->addViolation(); + $baseViolations = $baseMessageContext->getViolations(); + $messages = [(string) $baseViolations->get(\count($baseViolations) - 1)->getMessage()]; + + foreach ($constraint->constraints as $key => $item) { + if (!\in_array($this->context->getGroup(), $item->groups, true)) { + continue; + } + + $executionContext = clone $this->context; + $executionContext->setNode($value, $this->context->getObject(), $this->context->getMetadata(), $this->context->getPropertyPath()); + $violations = $validator->inContext($executionContext)->validate($value, $item, $this->context->getGroup())->getViolations(); + + if (\count($this->context->getViolations()) === \count($violations)) { + return; + } + + if ($constraint->includeInternalMessages) { + $message = ' ['.($key + 1).'] '; + + if ($item instanceof All || $item instanceof Collection) { + $message .= $constraint->messageCollection; + } else { + $message .= $violations->get(\count($violations) - 1)->getMessage(); + } + + $messages[] = $message; + } + } + + $this->context->buildViolation(implode('', $messages)) + ->setCode(AtLeastOneOf::AT_LEAST_ONE_OF_ERROR) + ->addViolation() + ; + } +} diff --git a/src/vendor/symfony/validator/Constraints/Bic.php b/src/vendor/symfony/validator/Constraints/Bic.php new file mode 100644 index 0000000..1cd98b4 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Bic.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Intl\Countries; +use Symfony\Component\PropertyAccess\PropertyAccess; +use Symfony\Component\PropertyAccess\PropertyPathInterface; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; +use Symfony\Component\Validator\Exception\LogicException; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Michael Hirschler + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class Bic extends Constraint +{ + public const INVALID_LENGTH_ERROR = '66dad313-af0b-4214-8566-6c799be9789c'; + public const INVALID_CHARACTERS_ERROR = 'f424c529-7add-4417-8f2d-4b656e4833e2'; + public const INVALID_BANK_CODE_ERROR = '00559357-6170-4f29-aebd-d19330aa19cf'; + public const INVALID_COUNTRY_CODE_ERROR = '1ce76f8d-3c1f-451c-9e62-fe9c3ed486ae'; + public const INVALID_CASE_ERROR = '11884038-3312-4ae5-9d04-699f782130c7'; + public const INVALID_IBAN_COUNTRY_CODE_ERROR = '29a2c3bb-587b-4996-b6f5-53081364cea5'; + + protected static $errorNames = [ + self::INVALID_LENGTH_ERROR => 'INVALID_LENGTH_ERROR', + self::INVALID_CHARACTERS_ERROR => 'INVALID_CHARACTERS_ERROR', + self::INVALID_BANK_CODE_ERROR => 'INVALID_BANK_CODE_ERROR', + self::INVALID_COUNTRY_CODE_ERROR => 'INVALID_COUNTRY_CODE_ERROR', + self::INVALID_CASE_ERROR => 'INVALID_CASE_ERROR', + ]; + + public $message = 'This is not a valid Business Identifier Code (BIC).'; + public $ibanMessage = 'This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}.'; + public $iban; + public $ibanPropertyPath; + + /** + * {@inheritdoc} + * + * @param string|PropertyPathInterface|null $ibanPropertyPath + */ + public function __construct(array $options = null, string $message = null, string $iban = null, $ibanPropertyPath = null, string $ibanMessage = null, array $groups = null, $payload = null) + { + if (!class_exists(Countries::class)) { + throw new LogicException('The Intl component is required to use the Bic constraint. Try running "composer require symfony/intl".'); + } + if (null !== $ibanPropertyPath && !\is_string($ibanPropertyPath) && !$ibanPropertyPath instanceof PropertyPathInterface) { + throw new \TypeError(sprintf('"%s": Expected argument $ibanPropertyPath to be either null, a string or an instance of "%s", got "%s".', __METHOD__, PropertyPathInterface::class, get_debug_type($ibanPropertyPath))); + } + + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + $this->ibanMessage = $ibanMessage ?? $this->ibanMessage; + $this->iban = $iban ?? $this->iban; + $this->ibanPropertyPath = $ibanPropertyPath ?? $this->ibanPropertyPath; + + if (null !== $this->iban && null !== $this->ibanPropertyPath) { + throw new ConstraintDefinitionException('The "iban" and "ibanPropertyPath" options of the Iban constraint cannot be used at the same time.'); + } + + if (null !== $this->ibanPropertyPath && !class_exists(PropertyAccess::class)) { + throw new LogicException(sprintf('The "symfony/property-access" component is required to use the "%s" constraint with the "ibanPropertyPath" option.', self::class)); + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/BicValidator.php b/src/vendor/symfony/validator/Constraints/BicValidator.php new file mode 100644 index 0000000..6c03806 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/BicValidator.php @@ -0,0 +1,174 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Intl\Countries; +use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException; +use Symfony\Component\PropertyAccess\PropertyAccess; +use Symfony\Component\PropertyAccess\PropertyAccessor; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; +use Symfony\Component\Validator\Exception\LogicException; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; +use Symfony\Component\Validator\Exception\UnexpectedValueException; + +/** + * @author Michael Hirschler + * + * @see https://en.wikipedia.org/wiki/ISO_9362#Structure + */ +class BicValidator extends ConstraintValidator +{ + // Reference: https://www.iban.com/structure + private const BIC_COUNTRY_TO_IBAN_COUNTRY_MAP = [ + // FR includes: + 'GF' => 'FR', // French Guiana + 'PF' => 'FR', // French Polynesia + 'TF' => 'FR', // French Southern Territories + 'GP' => 'FR', // Guadeloupe + 'MQ' => 'FR', // Martinique + 'YT' => 'FR', // Mayotte + 'NC' => 'FR', // New Caledonia + 'RE' => 'FR', // Reunion + 'BL' => 'FR', // Saint Barthelemy + 'MF' => 'FR', // Saint Martin (French part) + 'PM' => 'FR', // Saint Pierre and Miquelon + 'WF' => 'FR', // Wallis and Futuna Islands + // GB includes: + 'JE' => 'GB', // Jersey + 'IM' => 'GB', // Isle of Man + 'GG' => 'GB', // Guernsey + 'VG' => 'GB', // British Virgin Islands + // FI includes: + 'AX' => 'FI', // Aland Islands + // ES includes: + 'IC' => 'ES', // Canary Islands + 'EA' => 'ES', // Ceuta and Melilla + ]; + + private $propertyAccessor; + + public function __construct(PropertyAccessor $propertyAccessor = null) + { + $this->propertyAccessor = $propertyAccessor; + } + + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Bic) { + throw new UnexpectedTypeException($constraint, Bic::class); + } + + if (null === $value || '' === $value) { + return; + } + + if (!\is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { + throw new UnexpectedValueException($value, 'string'); + } + + $canonicalize = str_replace(' ', '', $value); + + // the bic must be either 8 or 11 characters long + if (!\in_array(\strlen($canonicalize), [8, 11])) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Bic::INVALID_LENGTH_ERROR) + ->addViolation(); + + return; + } + + // must contain alphanumeric values only + if (!ctype_alnum($canonicalize)) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Bic::INVALID_CHARACTERS_ERROR) + ->addViolation(); + + return; + } + + // first 4 letters must be alphabetic (bank code) + if (!ctype_alpha(substr($canonicalize, 0, 4))) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Bic::INVALID_BANK_CODE_ERROR) + ->addViolation(); + + return; + } + + $bicCountryCode = substr($canonicalize, 4, 2); + if (!isset(self::BIC_COUNTRY_TO_IBAN_COUNTRY_MAP[$bicCountryCode]) && !Countries::exists($bicCountryCode)) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Bic::INVALID_COUNTRY_CODE_ERROR) + ->addViolation(); + + return; + } + + // should contain uppercase characters only + if (strtoupper($canonicalize) !== $canonicalize) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Bic::INVALID_CASE_ERROR) + ->addViolation(); + + return; + } + + // check against an IBAN + $iban = $constraint->iban; + $path = $constraint->ibanPropertyPath; + if ($path && null !== $object = $this->context->getObject()) { + try { + $iban = $this->getPropertyAccessor()->getValue($object, $path); + } catch (NoSuchPropertyException $e) { + throw new ConstraintDefinitionException(sprintf('Invalid property path "%s" provided to "%s" constraint: ', $path, get_debug_type($constraint)).$e->getMessage(), 0, $e); + } + } + if (!$iban) { + return; + } + $ibanCountryCode = substr($iban, 0, 2); + if (ctype_alpha($ibanCountryCode) && !$this->bicAndIbanCountriesMatch($bicCountryCode, $ibanCountryCode)) { + $this->context->buildViolation($constraint->ibanMessage) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setParameter('{{ iban }}', $iban) + ->setCode(Bic::INVALID_IBAN_COUNTRY_CODE_ERROR) + ->addViolation(); + } + } + + private function getPropertyAccessor(): PropertyAccessor + { + if (null === $this->propertyAccessor) { + if (!class_exists(PropertyAccess::class)) { + throw new LogicException('Unable to use property path as the Symfony PropertyAccess component is not installed.'); + } + $this->propertyAccessor = PropertyAccess::createPropertyAccessor(); + } + + return $this->propertyAccessor; + } + + private function bicAndIbanCountriesMatch(string $bicCountryCode, string $ibanCountryCode): bool + { + return $ibanCountryCode === $bicCountryCode || $ibanCountryCode === (self::BIC_COUNTRY_TO_IBAN_COUNTRY_MAP[$bicCountryCode] ?? null); + } +} diff --git a/src/vendor/symfony/validator/Constraints/Blank.php b/src/vendor/symfony/validator/Constraints/Blank.php new file mode 100644 index 0000000..a9ee525 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Blank.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class Blank extends Constraint +{ + public const NOT_BLANK_ERROR = '183ad2de-533d-4796-a439-6d3c3852b549'; + + protected static $errorNames = [ + self::NOT_BLANK_ERROR => 'NOT_BLANK_ERROR', + ]; + + public $message = 'This value should be blank.'; + + public function __construct(array $options = null, string $message = null, array $groups = null, $payload = null) + { + parent::__construct($options ?? [], $groups, $payload); + + $this->message = $message ?? $this->message; + } +} diff --git a/src/vendor/symfony/validator/Constraints/BlankValidator.php b/src/vendor/symfony/validator/Constraints/BlankValidator.php new file mode 100644 index 0000000..3fd7fcc --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/BlankValidator.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; + +/** + * @author Bernhard Schussek + */ +class BlankValidator extends ConstraintValidator +{ + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Blank) { + throw new UnexpectedTypeException($constraint, Blank::class); + } + + if ('' !== $value && null !== $value) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Blank::NOT_BLANK_ERROR) + ->addViolation(); + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/Callback.php b/src/vendor/symfony/validator/Constraints/Callback.php new file mode 100644 index 0000000..7b9c3c3 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Callback.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; + +/** + * @Annotation + * @Target({"CLASS", "PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class Callback extends Constraint +{ + /** + * @var string|callable + */ + public $callback; + + /** + * {@inheritdoc} + * + * @param array|string|callable $callback The callback or a set of options + */ + public function __construct($callback = null, array $groups = null, $payload = null, array $options = []) + { + // Invocation through annotations with an array parameter only + if (\is_array($callback) && 1 === \count($callback) && isset($callback['value'])) { + $callback = $callback['value']; + } + + if (!\is_array($callback) || (!isset($callback['callback']) && !isset($callback['groups']) && !isset($callback['payload']))) { + $options['callback'] = $callback; + } else { + $options = array_merge($callback, $options); + } + + parent::__construct($options, $groups, $payload); + } + + /** + * {@inheritdoc} + */ + public function getDefaultOption() + { + return 'callback'; + } + + /** + * {@inheritdoc} + */ + public function getTargets() + { + return [self::CLASS_CONSTRAINT, self::PROPERTY_CONSTRAINT]; + } +} diff --git a/src/vendor/symfony/validator/Constraints/CallbackValidator.php b/src/vendor/symfony/validator/Constraints/CallbackValidator.php new file mode 100644 index 0000000..71cff2a --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/CallbackValidator.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; + +/** + * Validator for Callback constraint. + * + * @author Bernhard Schussek + */ +class CallbackValidator extends ConstraintValidator +{ + /** + * {@inheritdoc} + */ + public function validate($object, Constraint $constraint) + { + if (!$constraint instanceof Callback) { + throw new UnexpectedTypeException($constraint, Callback::class); + } + + $method = $constraint->callback; + if ($method instanceof \Closure) { + $method($object, $this->context, $constraint->payload); + } elseif (\is_array($method)) { + if (!\is_callable($method)) { + if (isset($method[0]) && \is_object($method[0])) { + $method[0] = \get_class($method[0]); + } + throw new ConstraintDefinitionException(json_encode($method).' targeted by Callback constraint is not a valid callable.'); + } + + $method($object, $this->context, $constraint->payload); + } elseif (null !== $object) { + if (!method_exists($object, $method)) { + throw new ConstraintDefinitionException(sprintf('Method "%s" targeted by Callback constraint does not exist in class "%s".', $method, get_debug_type($object))); + } + + $reflMethod = new \ReflectionMethod($object, $method); + + if ($reflMethod->isStatic()) { + $reflMethod->invoke(null, $object, $this->context, $constraint->payload); + } else { + $reflMethod->invoke($object, $this->context, $constraint->payload); + } + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/CardScheme.php b/src/vendor/symfony/validator/Constraints/CardScheme.php new file mode 100644 index 0000000..e9d66b9 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/CardScheme.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; + +/** + * Metadata for the CardSchemeValidator. + * + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Tim Nagel + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class CardScheme extends Constraint +{ + public const AMEX = 'AMEX'; + public const CHINA_UNIONPAY = 'CHINA_UNIONPAY'; + public const DINERS = 'DINERS'; + public const DISCOVER = 'DISCOVER'; + public const INSTAPAYMENT = 'INSTAPAYMENT'; + public const JCB = 'JCB'; + public const LASER = 'LASER'; + public const MAESTRO = 'MAESTRO'; + public const MASTERCARD = 'MASTERCARD'; + public const MIR = 'MIR'; + public const UATP = 'UATP'; + public const VISA = 'VISA'; + + public const NOT_NUMERIC_ERROR = 'a2ad9231-e827-485f-8a1e-ef4d9a6d5c2e'; + public const INVALID_FORMAT_ERROR = 'a8faedbf-1c2f-4695-8d22-55783be8efed'; + + protected static $errorNames = [ + self::NOT_NUMERIC_ERROR => 'NOT_NUMERIC_ERROR', + self::INVALID_FORMAT_ERROR => 'INVALID_FORMAT_ERROR', + ]; + + public $message = 'Unsupported card type or invalid card number.'; + public $schemes; + + /** + * {@inheritdoc} + * + * @param array|string $schemes The schemes to validate against or a set of options + */ + public function __construct($schemes, string $message = null, array $groups = null, $payload = null, array $options = []) + { + if (\is_array($schemes) && \is_string(key($schemes))) { + $options = array_merge($schemes, $options); + } else { + $options['value'] = $schemes; + } + + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + } + + public function getDefaultOption() + { + return 'schemes'; + } + + public function getRequiredOptions() + { + return ['schemes']; + } +} diff --git a/src/vendor/symfony/validator/Constraints/CardSchemeValidator.php b/src/vendor/symfony/validator/Constraints/CardSchemeValidator.php new file mode 100644 index 0000000..faef7ec --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/CardSchemeValidator.php @@ -0,0 +1,134 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; + +/** + * Validates that a card number belongs to a specified scheme. + * + * @author Tim Nagel + * @author Bernhard Schussek + * + * @see https://en.wikipedia.org/wiki/Payment_card_number + * @see https://www.regular-expressions.info/creditcard.html + */ +class CardSchemeValidator extends ConstraintValidator +{ + protected $schemes = [ + // American Express card numbers start with 34 or 37 and have 15 digits. + CardScheme::AMEX => [ + '/^3[47][0-9]{13}$/', + ], + // China UnionPay cards start with 62 and have between 16 and 19 digits. + // Please note that these cards do not follow Luhn Algorithm as a checksum. + CardScheme::CHINA_UNIONPAY => [ + '/^62[0-9]{14,17}$/', + ], + // Diners Club card numbers begin with 300 through 305, 36 or 38. All have 14 digits. + // There are Diners Club cards that begin with 5 and have 16 digits. + // These are a joint venture between Diners Club and MasterCard, and should be processed like a MasterCard. + CardScheme::DINERS => [ + '/^3(?:0[0-5]|[68][0-9])[0-9]{11}$/', + ], + // Discover card numbers begin with 6011, 622126 through 622925, 644 through 649 or 65. + // All have 16 digits. + CardScheme::DISCOVER => [ + '/^6011[0-9]{12}$/', + '/^64[4-9][0-9]{13}$/', + '/^65[0-9]{14}$/', + '/^622(12[6-9]|1[3-9][0-9]|[2-8][0-9][0-9]|91[0-9]|92[0-5])[0-9]{10}$/', + ], + // InstaPayment cards begin with 637 through 639 and have 16 digits. + CardScheme::INSTAPAYMENT => [ + '/^63[7-9][0-9]{13}$/', + ], + // JCB cards beginning with 2131 or 1800 have 15 digits. + // JCB cards beginning with 35 have 16 digits. + CardScheme::JCB => [ + '/^(?:2131|1800|35[0-9]{3})[0-9]{11}$/', + ], + // Laser cards begin with either 6304, 6706, 6709 or 6771 and have between 16 and 19 digits. + CardScheme::LASER => [ + '/^(6304|670[69]|6771)[0-9]{12,15}$/', + ], + // Maestro international cards begin with 675900..675999 and have between 12 and 19 digits. + // Maestro UK cards begin with either 500000..509999 or 560000..699999 and have between 12 and 19 digits. + CardScheme::MAESTRO => [ + '/^(6759[0-9]{2})[0-9]{6,13}$/', + '/^(50[0-9]{4})[0-9]{6,13}$/', + '/^5[6-9][0-9]{10,17}$/', + '/^6[0-9]{11,18}$/', + ], + // All MasterCard numbers start with the numbers 51 through 55. All have 16 digits. + // October 2016 MasterCard numbers can also start with 222100 through 272099. + CardScheme::MASTERCARD => [ + '/^5[1-5][0-9]{14}$/', + '/^2(22[1-9][0-9]{12}|2[3-9][0-9]{13}|[3-6][0-9]{14}|7[0-1][0-9]{13}|720[0-9]{12})$/', + ], + // Payment system MIR numbers start with 220, then 1 digit from 0 to 4, then between 12 and 15 digits + CardScheme::MIR => [ + '/^220[0-4][0-9]{12,15}$/', + ], + // All UATP card numbers start with a 1 and have a length of 15 digits. + CardScheme::UATP => [ + '/^1[0-9]{14}$/', + ], + // All Visa card numbers start with a 4 and have a length of 13, 16, or 19 digits. + CardScheme::VISA => [ + '/^4([0-9]{12}|[0-9]{15}|[0-9]{18})$/', + ], + ]; + + /** + * Validates a creditcard belongs to a specified scheme. + * + * @param mixed $value + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof CardScheme) { + throw new UnexpectedTypeException($constraint, CardScheme::class); + } + + if (null === $value || '' === $value) { + return; + } + + if (!is_numeric($value)) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(CardScheme::NOT_NUMERIC_ERROR) + ->addViolation(); + + return; + } + + $schemes = array_flip((array) $constraint->schemes); + $schemeRegexes = array_intersect_key($this->schemes, $schemes); + + foreach ($schemeRegexes as $regexes) { + foreach ($regexes as $regex) { + if (preg_match($regex, $value)) { + return; + } + } + } + + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(CardScheme::INVALID_FORMAT_ERROR) + ->addViolation(); + } +} diff --git a/src/vendor/symfony/validator/Constraints/Cascade.php b/src/vendor/symfony/validator/Constraints/Cascade.php new file mode 100644 index 0000000..2458d5c --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Cascade.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; + +/** + * @Annotation + * @Target({"CLASS"}) + * + * @author Jules Pietri + */ +#[\Attribute(\Attribute::TARGET_CLASS)] +class Cascade extends Constraint +{ + public function __construct(array $options = null) + { + if (\is_array($options) && \array_key_exists('groups', $options)) { + throw new ConstraintDefinitionException(sprintf('The option "groups" is not supported by the constraint "%s".', __CLASS__)); + } + + parent::__construct($options); + } + + /** + * {@inheritdoc} + */ + public function getTargets() + { + return self::CLASS_CONSTRAINT; + } +} diff --git a/src/vendor/symfony/validator/Constraints/Choice.php b/src/vendor/symfony/validator/Constraints/Choice.php new file mode 100644 index 0000000..b5bcc6d --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Choice.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class Choice extends Constraint +{ + public const NO_SUCH_CHOICE_ERROR = '8e179f1b-97aa-4560-a02f-2a8b42e49df7'; + public const TOO_FEW_ERROR = '11edd7eb-5872-4b6e-9f12-89923999fd0e'; + public const TOO_MANY_ERROR = '9bd98e49-211c-433f-8630-fd1c2d0f08c3'; + + protected static $errorNames = [ + self::NO_SUCH_CHOICE_ERROR => 'NO_SUCH_CHOICE_ERROR', + self::TOO_FEW_ERROR => 'TOO_FEW_ERROR', + self::TOO_MANY_ERROR => 'TOO_MANY_ERROR', + ]; + + public $choices; + public $callback; + public $multiple = false; + public $strict = true; + public $min; + public $max; + public $message = 'The value you selected is not a valid choice.'; + public $multipleMessage = 'One or more of the given values is invalid.'; + public $minMessage = 'You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices.'; + public $maxMessage = 'You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices.'; + + /** + * {@inheritdoc} + */ + public function getDefaultOption() + { + return 'choices'; + } + + public function __construct( + $options = [], + array $choices = null, + $callback = null, + bool $multiple = null, + bool $strict = null, + int $min = null, + int $max = null, + string $message = null, + string $multipleMessage = null, + string $minMessage = null, + string $maxMessage = null, + $groups = null, + $payload = null + ) { + if (\is_array($options) && $options && array_is_list($options)) { + $choices = $choices ?? $options; + $options = []; + } + if (null !== $choices) { + $options['value'] = $choices; + } + + parent::__construct($options, $groups, $payload); + + $this->callback = $callback ?? $this->callback; + $this->multiple = $multiple ?? $this->multiple; + $this->strict = $strict ?? $this->strict; + $this->min = $min ?? $this->min; + $this->max = $max ?? $this->max; + $this->message = $message ?? $this->message; + $this->multipleMessage = $multipleMessage ?? $this->multipleMessage; + $this->minMessage = $minMessage ?? $this->minMessage; + $this->maxMessage = $maxMessage ?? $this->maxMessage; + } +} diff --git a/src/vendor/symfony/validator/Constraints/ChoiceValidator.php b/src/vendor/symfony/validator/Constraints/ChoiceValidator.php new file mode 100644 index 0000000..a1c47a6 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/ChoiceValidator.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; +use Symfony\Component\Validator\Exception\UnexpectedValueException; + +/** + * ChoiceValidator validates that the value is one of the expected values. + * + * @author Fabien Potencier + * @author Florian Eckerstorfer + * @author Bernhard Schussek + */ +class ChoiceValidator extends ConstraintValidator +{ + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Choice) { + throw new UnexpectedTypeException($constraint, Choice::class); + } + + if (!\is_array($constraint->choices) && !$constraint->callback) { + throw new ConstraintDefinitionException('Either "choices" or "callback" must be specified on constraint Choice.'); + } + + if (null === $value) { + return; + } + + if ($constraint->multiple && !\is_array($value)) { + throw new UnexpectedValueException($value, 'array'); + } + + if ($constraint->callback) { + if (!\is_callable($choices = [$this->context->getObject(), $constraint->callback]) + && !\is_callable($choices = [$this->context->getClassName(), $constraint->callback]) + && !\is_callable($choices = $constraint->callback) + ) { + throw new ConstraintDefinitionException('The Choice constraint expects a valid callback.'); + } + $choices = $choices(); + } else { + $choices = $constraint->choices; + } + + if (true !== $constraint->strict) { + throw new \RuntimeException('The "strict" option of the Choice constraint should not be used.'); + } + + if ($constraint->multiple) { + foreach ($value as $_value) { + if (!\in_array($_value, $choices, true)) { + $this->context->buildViolation($constraint->multipleMessage) + ->setParameter('{{ value }}', $this->formatValue($_value)) + ->setParameter('{{ choices }}', $this->formatValues($choices)) + ->setCode(Choice::NO_SUCH_CHOICE_ERROR) + ->setInvalidValue($_value) + ->addViolation(); + + return; + } + } + + $count = \count($value); + + if (null !== $constraint->min && $count < $constraint->min) { + $this->context->buildViolation($constraint->minMessage) + ->setParameter('{{ limit }}', $constraint->min) + ->setPlural((int) $constraint->min) + ->setCode(Choice::TOO_FEW_ERROR) + ->addViolation(); + + return; + } + + if (null !== $constraint->max && $count > $constraint->max) { + $this->context->buildViolation($constraint->maxMessage) + ->setParameter('{{ limit }}', $constraint->max) + ->setPlural((int) $constraint->max) + ->setCode(Choice::TOO_MANY_ERROR) + ->addViolation(); + + return; + } + } elseif (!\in_array($value, $choices, true)) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setParameter('{{ choices }}', $this->formatValues($choices)) + ->setCode(Choice::NO_SUCH_CHOICE_ERROR) + ->addViolation(); + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/Cidr.php b/src/vendor/symfony/validator/Constraints/Cidr.php new file mode 100644 index 0000000..387c599 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Cidr.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; + +/** + * Validates that a value is a valid CIDR notation. + * + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Sorin Pop + * @author Calin Bolea + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class Cidr extends Constraint +{ + public const INVALID_CIDR_ERROR = '5649e53a-5afb-47c5-a360-ffbab3be8567'; + public const OUT_OF_RANGE_ERROR = 'b9f14a51-acbd-401a-a078-8c6b204ab32f'; + + protected static $errorNames = [ + self::INVALID_CIDR_ERROR => 'INVALID_CIDR_ERROR', + self::OUT_OF_RANGE_ERROR => 'OUT_OF_RANGE_VIOLATION', + ]; + + private const NET_MAXES = [ + Ip::ALL => 128, + Ip::V4 => 32, + Ip::V6 => 128, + ]; + + public $version = Ip::ALL; + + public $message = 'This value is not a valid CIDR notation.'; + + public $netmaskRangeViolationMessage = 'The value of the netmask should be between {{ min }} and {{ max }}.'; + + public $netmaskMin = 0; + + public $netmaskMax; + + public function __construct( + array $options = null, + string $version = null, + int $netmaskMin = null, + int $netmaskMax = null, + string $message = null, + array $groups = null, + $payload = null + ) { + $this->version = $version ?? $options['version'] ?? $this->version; + + if (!\in_array($this->version, array_keys(self::NET_MAXES))) { + throw new ConstraintDefinitionException(sprintf('The option "version" must be one of "%s".', implode('", "', array_keys(self::NET_MAXES)))); + } + + $this->netmaskMin = $netmaskMin ?? $options['netmaskMin'] ?? $this->netmaskMin; + $this->netmaskMax = $netmaskMax ?? $options['netmaskMax'] ?? self::NET_MAXES[$this->version]; + $this->message = $message ?? $this->message; + + unset($options['netmaskMin'], $options['netmaskMax'], $options['version']); + + if ($this->netmaskMin < 0 || $this->netmaskMax > self::NET_MAXES[$this->version] || $this->netmaskMin > $this->netmaskMax) { + throw new ConstraintDefinitionException(sprintf('The netmask range must be between 0 and %d.', self::NET_MAXES[$this->version])); + } + + parent::__construct($options, $groups, $payload); + } +} diff --git a/src/vendor/symfony/validator/Constraints/CidrValidator.php b/src/vendor/symfony/validator/Constraints/CidrValidator.php new file mode 100644 index 0000000..c90ebcf --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/CidrValidator.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; +use Symfony\Component\Validator\Exception\UnexpectedValueException; + +class CidrValidator extends ConstraintValidator +{ + public function validate($value, Constraint $constraint): void + { + if (!$constraint instanceof Cidr) { + throw new UnexpectedTypeException($constraint, Cidr::class); + } + + if (null === $value || '' === $value) { + return; + } + + if (!\is_string($value)) { + throw new UnexpectedValueException($value, 'string'); + } + + $cidrParts = explode('/', $value, 2); + + if (!isset($cidrParts[1]) + || !ctype_digit($cidrParts[1]) + || '' === $cidrParts[0] + ) { + $this->context + ->buildViolation($constraint->message) + ->setCode(Cidr::INVALID_CIDR_ERROR) + ->addViolation(); + + return; + } + + $ipAddress = $cidrParts[0]; + $netmask = (int) $cidrParts[1]; + + $validV4 = Ip::V6 !== $constraint->version + && filter_var($ipAddress, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4) + && $netmask <= 32; + + $validV6 = Ip::V4 !== $constraint->version + && filter_var($ipAddress, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6); + + if (!$validV4 && !$validV6) { + $this->context + ->buildViolation($constraint->message) + ->setCode(Cidr::INVALID_CIDR_ERROR) + ->addViolation(); + + return; + } + + if ($netmask < $constraint->netmaskMin || $netmask > $constraint->netmaskMax) { + $this->context + ->buildViolation($constraint->netmaskRangeViolationMessage) + ->setParameter('{{ min }}', $constraint->netmaskMin) + ->setParameter('{{ max }}', $constraint->netmaskMax) + ->setCode(Cidr::OUT_OF_RANGE_ERROR) + ->addViolation(); + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/Collection.php b/src/vendor/symfony/validator/Constraints/Collection.php new file mode 100644 index 0000000..3f4adb5 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Collection.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class Collection extends Composite +{ + public const MISSING_FIELD_ERROR = '2fa2158c-2a7f-484b-98aa-975522539ff8'; + public const NO_SUCH_FIELD_ERROR = '7703c766-b5d5-4cef-ace7-ae0dd82304e9'; + + protected static $errorNames = [ + self::MISSING_FIELD_ERROR => 'MISSING_FIELD_ERROR', + self::NO_SUCH_FIELD_ERROR => 'NO_SUCH_FIELD_ERROR', + ]; + + public $fields = []; + public $allowExtraFields = false; + public $allowMissingFields = false; + public $extraFieldsMessage = 'This field was not expected.'; + public $missingFieldsMessage = 'This field is missing.'; + + /** + * {@inheritdoc} + */ + public function __construct($fields = null, array $groups = null, $payload = null, bool $allowExtraFields = null, bool $allowMissingFields = null, string $extraFieldsMessage = null, string $missingFieldsMessage = null) + { + // no known options set? $fields is the fields array + if (\is_array($fields) + && !array_intersect(array_keys($fields), ['groups', 'fields', 'allowExtraFields', 'allowMissingFields', 'extraFieldsMessage', 'missingFieldsMessage'])) { + $fields = ['fields' => $fields]; + } + + parent::__construct($fields, $groups, $payload); + + $this->allowExtraFields = $allowExtraFields ?? $this->allowExtraFields; + $this->allowMissingFields = $allowMissingFields ?? $this->allowMissingFields; + $this->extraFieldsMessage = $extraFieldsMessage ?? $this->extraFieldsMessage; + $this->missingFieldsMessage = $missingFieldsMessage ?? $this->missingFieldsMessage; + } + + /** + * {@inheritdoc} + */ + protected function initializeNestedConstraints() + { + parent::initializeNestedConstraints(); + + if (!\is_array($this->fields)) { + throw new ConstraintDefinitionException(sprintf('The option "fields" is expected to be an array in constraint "%s".', __CLASS__)); + } + + foreach ($this->fields as $fieldName => $field) { + // the XmlFileLoader and YamlFileLoader pass the field Optional + // and Required constraint as an array with exactly one element + if (\is_array($field) && 1 == \count($field)) { + $this->fields[$fieldName] = $field = $field[0]; + } + + if (!$field instanceof Optional && !$field instanceof Required) { + $this->fields[$fieldName] = new Required($field); + } + } + } + + public function getRequiredOptions() + { + return ['fields']; + } + + protected function getCompositeOption() + { + return 'fields'; + } +} diff --git a/src/vendor/symfony/validator/Constraints/CollectionValidator.php b/src/vendor/symfony/validator/Constraints/CollectionValidator.php new file mode 100644 index 0000000..7cea7d0 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/CollectionValidator.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; +use Symfony\Component\Validator\Exception\UnexpectedValueException; + +/** + * @author Bernhard Schussek + */ +class CollectionValidator extends ConstraintValidator +{ + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Collection) { + throw new UnexpectedTypeException($constraint, Collection::class); + } + + if (null === $value) { + return; + } + + if (!\is_array($value) && !($value instanceof \Traversable && $value instanceof \ArrayAccess)) { + throw new UnexpectedValueException($value, 'array|(Traversable&ArrayAccess)'); + } + + // We need to keep the initialized context when CollectionValidator + // calls itself recursively (Collection constraints can be nested). + // Since the context of the validator is overwritten when initialize() + // is called for the nested constraint, the outer validator is + // acting on the wrong context when the nested validation terminates. + // + // A better solution - which should be approached in Symfony 3.0 - is to + // remove the initialize() method and pass the context as last argument + // to validate() instead. + $context = $this->context; + + foreach ($constraint->fields as $field => $fieldConstraint) { + // bug fix issue #2779 + $existsInArray = \is_array($value) && \array_key_exists($field, $value); + $existsInArrayAccess = $value instanceof \ArrayAccess && $value->offsetExists($field); + + if ($existsInArray || $existsInArrayAccess) { + if (\count($fieldConstraint->constraints) > 0) { + $context->getValidator() + ->inContext($context) + ->atPath('['.$field.']') + ->validate($value[$field], $fieldConstraint->constraints); + } + } elseif (!$fieldConstraint instanceof Optional && !$constraint->allowMissingFields) { + $context->buildViolation($constraint->missingFieldsMessage) + ->atPath('['.$field.']') + ->setParameter('{{ field }}', $this->formatValue($field)) + ->setInvalidValue(null) + ->setCode(Collection::MISSING_FIELD_ERROR) + ->addViolation(); + } + } + + if (!$constraint->allowExtraFields) { + foreach ($value as $field => $fieldValue) { + if (!isset($constraint->fields[$field])) { + $context->buildViolation($constraint->extraFieldsMessage) + ->atPath('['.$field.']') + ->setParameter('{{ field }}', $this->formatValue($field)) + ->setInvalidValue($fieldValue) + ->setCode(Collection::NO_SUCH_FIELD_ERROR) + ->addViolation(); + } + } + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/Composite.php b/src/vendor/symfony/validator/Constraints/Composite.php new file mode 100644 index 0000000..2f6eadf --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Composite.php @@ -0,0 +1,159 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; + +/** + * A constraint that is composed of other constraints. + * + * You should never use the nested constraint instances anywhere else, because + * their groups are adapted when passed to the constructor of this class. + * + * If you want to create your own composite constraint, extend this class and + * let {@link getCompositeOption()} return the name of the property which + * contains the nested constraints. + * + * @author Bernhard Schussek + */ +abstract class Composite extends Constraint +{ + /** + * {@inheritdoc} + * + * The groups of the composite and its nested constraints are made + * consistent using the following strategy: + * + * - If groups are passed explicitly to the composite constraint, but + * not to the nested constraints, the options of the composite + * constraint are copied to the nested constraints; + * + * - If groups are passed explicitly to the nested constraints, but not + * to the composite constraint, the groups of all nested constraints + * are merged and used as groups for the composite constraint; + * + * - If groups are passed explicitly to both the composite and its nested + * constraints, the groups of the nested constraints must be a subset + * of the groups of the composite constraint. If not, a + * {@link ConstraintDefinitionException} is thrown. + * + * All this is done in the constructor, because constraints can then be + * cached. When constraints are loaded from the cache, no more group + * checks need to be done. + */ + public function __construct($options = null, array $groups = null, $payload = null) + { + parent::__construct($options, $groups, $payload); + + $this->initializeNestedConstraints(); + + /* @var Constraint[] $nestedConstraints */ + $compositeOption = $this->getCompositeOption(); + $nestedConstraints = $this->$compositeOption; + + if (!\is_array($nestedConstraints)) { + $nestedConstraints = [$nestedConstraints]; + } + + foreach ($nestedConstraints as $constraint) { + if (!$constraint instanceof Constraint) { + if (\is_object($constraint)) { + $constraint = \get_class($constraint); + } + + throw new ConstraintDefinitionException(sprintf('The value "%s" is not an instance of Constraint in constraint "%s".', $constraint, static::class)); + } + + if ($constraint instanceof Valid) { + throw new ConstraintDefinitionException(sprintf('The constraint Valid cannot be nested inside constraint "%s". You can only declare the Valid constraint directly on a field or method.', static::class)); + } + } + + if (!isset(((array) $this)['groups'])) { + $mergedGroups = []; + + foreach ($nestedConstraints as $constraint) { + foreach ($constraint->groups as $group) { + $mergedGroups[$group] = true; + } + } + + // prevent empty composite constraint to have empty groups + $this->groups = array_keys($mergedGroups) ?: [self::DEFAULT_GROUP]; + $this->$compositeOption = $nestedConstraints; + + return; + } + + foreach ($nestedConstraints as $constraint) { + if (isset(((array) $constraint)['groups'])) { + $excessGroups = array_diff($constraint->groups, $this->groups); + + if (\count($excessGroups) > 0) { + throw new ConstraintDefinitionException(sprintf('The group(s) "%s" passed to the constraint "%s" should also be passed to its containing constraint "%s".', implode('", "', $excessGroups), get_debug_type($constraint), static::class)); + } + } else { + $constraint->groups = $this->groups; + } + } + + $this->$compositeOption = $nestedConstraints; + } + + /** + * {@inheritdoc} + * + * Implicit group names are forwarded to nested constraints. + */ + public function addImplicitGroupName(string $group) + { + parent::addImplicitGroupName($group); + + /** @var Constraint[] $nestedConstraints */ + $nestedConstraints = $this->{$this->getCompositeOption()}; + + foreach ($nestedConstraints as $constraint) { + $constraint->addImplicitGroupName($group); + } + } + + /** + * Returns the name of the property that contains the nested constraints. + * + * @return string + */ + abstract protected function getCompositeOption(); + + /** + * @internal Used by metadata + * + * @return Constraint[] + */ + public function getNestedConstraints() + { + /* @var Constraint[] $nestedConstraints */ + return $this->{$this->getCompositeOption()}; + } + + /** + * Initializes the nested constraints. + * + * This method can be overwritten in subclasses to clean up the nested + * constraints passed to the constructor. + * + * @see Collection::initializeNestedConstraints() + */ + protected function initializeNestedConstraints() + { + } +} diff --git a/src/vendor/symfony/validator/Constraints/Compound.php b/src/vendor/symfony/validator/Constraints/Compound.php new file mode 100644 index 0000000..54dcd6c --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Compound.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; + +/** + * Extend this class to create a reusable set of constraints. + * + * @author Maxime Steinhausser + */ +abstract class Compound extends Composite +{ + /** @var Constraint[] */ + public $constraints = []; + + public function __construct($options = null) + { + if (isset($options[$this->getCompositeOption()])) { + throw new ConstraintDefinitionException(sprintf('You can\'t redefine the "%s" option. Use the "%s::getConstraints()" method instead.', $this->getCompositeOption(), __CLASS__)); + } + + $this->constraints = $this->getConstraints($this->normalizeOptions($options)); + + parent::__construct($options); + } + + final protected function getCompositeOption(): string + { + return 'constraints'; + } + + final public function validatedBy(): string + { + return CompoundValidator::class; + } + + /** + * @return Constraint[] + */ + abstract protected function getConstraints(array $options): array; +} diff --git a/src/vendor/symfony/validator/Constraints/CompoundValidator.php b/src/vendor/symfony/validator/Constraints/CompoundValidator.php new file mode 100644 index 0000000..2ba993f --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/CompoundValidator.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; + +/** + * @author Maxime Steinhausser + */ +class CompoundValidator extends ConstraintValidator +{ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Compound) { + throw new UnexpectedTypeException($constraint, Compound::class); + } + + $context = $this->context; + + $validator = $context->getValidator()->inContext($context); + + $validator->validate($value, $constraint->constraints); + } +} diff --git a/src/vendor/symfony/validator/Constraints/Count.php b/src/vendor/symfony/validator/Constraints/Count.php new file mode 100644 index 0000000..0d75903 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Count.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Exception\MissingOptionsException; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class Count extends Constraint +{ + public const TOO_FEW_ERROR = 'bef8e338-6ae5-4caf-b8e2-50e7b0579e69'; + public const TOO_MANY_ERROR = '756b1212-697c-468d-a9ad-50dd783bb169'; + public const NOT_EQUAL_COUNT_ERROR = '9fe5d43f-3784-4ece-a0e1-473fc02dadbc'; + public const NOT_DIVISIBLE_BY_ERROR = DivisibleBy::NOT_DIVISIBLE_BY; + + protected static $errorNames = [ + self::TOO_FEW_ERROR => 'TOO_FEW_ERROR', + self::TOO_MANY_ERROR => 'TOO_MANY_ERROR', + self::NOT_EQUAL_COUNT_ERROR => 'NOT_EQUAL_COUNT_ERROR', + self::NOT_DIVISIBLE_BY_ERROR => 'NOT_DIVISIBLE_BY_ERROR', + ]; + + public $minMessage = 'This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more.'; + public $maxMessage = 'This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less.'; + public $exactMessage = 'This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements.'; + public $divisibleByMessage = 'The number of elements in this collection should be a multiple of {{ compared_value }}.'; + public $min; + public $max; + public $divisibleBy; + + /** + * {@inheritdoc} + * + * @param int|array|null $exactly The expected exact count or a set of options + */ + public function __construct( + $exactly = null, + int $min = null, + int $max = null, + int $divisibleBy = null, + string $exactMessage = null, + string $minMessage = null, + string $maxMessage = null, + string $divisibleByMessage = null, + array $groups = null, + $payload = null, + array $options = [] + ) { + if (\is_array($exactly)) { + $options = array_merge($exactly, $options); + $exactly = $options['value'] ?? null; + } + + $min = $min ?? $options['min'] ?? null; + $max = $max ?? $options['max'] ?? null; + + unset($options['value'], $options['min'], $options['max']); + + if (null !== $exactly && null === $min && null === $max) { + $min = $max = $exactly; + } + + parent::__construct($options, $groups, $payload); + + $this->min = $min; + $this->max = $max; + $this->divisibleBy = $divisibleBy ?? $this->divisibleBy; + $this->exactMessage = $exactMessage ?? $this->exactMessage; + $this->minMessage = $minMessage ?? $this->minMessage; + $this->maxMessage = $maxMessage ?? $this->maxMessage; + $this->divisibleByMessage = $divisibleByMessage ?? $this->divisibleByMessage; + + if (null === $this->min && null === $this->max && null === $this->divisibleBy) { + throw new MissingOptionsException(sprintf('Either option "min", "max" or "divisibleBy" must be given for constraint "%s".', __CLASS__), ['min', 'max', 'divisibleBy']); + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/CountValidator.php b/src/vendor/symfony/validator/Constraints/CountValidator.php new file mode 100644 index 0000000..ff991ac --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/CountValidator.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; +use Symfony\Component\Validator\Exception\UnexpectedValueException; + +/** + * @author Bernhard Schussek + */ +class CountValidator extends ConstraintValidator +{ + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Count) { + throw new UnexpectedTypeException($constraint, Count::class); + } + + if (null === $value) { + return; + } + + if (!\is_array($value) && !$value instanceof \Countable) { + throw new UnexpectedValueException($value, 'array|\Countable'); + } + + $count = \count($value); + + if (null !== $constraint->max && $count > $constraint->max) { + $exactlyOptionEnabled = $constraint->min == $constraint->max; + + $this->context->buildViolation($exactlyOptionEnabled ? $constraint->exactMessage : $constraint->maxMessage) + ->setParameter('{{ count }}', $count) + ->setParameter('{{ limit }}', $constraint->max) + ->setInvalidValue($value) + ->setPlural((int) $constraint->max) + ->setCode($exactlyOptionEnabled ? Count::NOT_EQUAL_COUNT_ERROR : Count::TOO_MANY_ERROR) + ->addViolation(); + + return; + } + + if (null !== $constraint->min && $count < $constraint->min) { + $exactlyOptionEnabled = $constraint->min == $constraint->max; + + $this->context->buildViolation($exactlyOptionEnabled ? $constraint->exactMessage : $constraint->minMessage) + ->setParameter('{{ count }}', $count) + ->setParameter('{{ limit }}', $constraint->min) + ->setInvalidValue($value) + ->setPlural((int) $constraint->min) + ->setCode($exactlyOptionEnabled ? Count::NOT_EQUAL_COUNT_ERROR : Count::TOO_FEW_ERROR) + ->addViolation(); + + return; + } + + if (null !== $constraint->divisibleBy) { + $this->context + ->getValidator() + ->inContext($this->context) + ->validate($count, [ + new DivisibleBy([ + 'value' => $constraint->divisibleBy, + 'message' => $constraint->divisibleByMessage, + ]), + ], $this->context->getGroup()); + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/Country.php b/src/vendor/symfony/validator/Constraints/Country.php new file mode 100644 index 0000000..ccd815c --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Country.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Intl\Countries; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Exception\LogicException; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class Country extends Constraint +{ + public const NO_SUCH_COUNTRY_ERROR = '8f900c12-61bd-455d-9398-996cd040f7f0'; + + protected static $errorNames = [ + self::NO_SUCH_COUNTRY_ERROR => 'NO_SUCH_COUNTRY_ERROR', + ]; + + public $message = 'This value is not a valid country.'; + public $alpha3 = false; + + public function __construct( + array $options = null, + string $message = null, + bool $alpha3 = null, + array $groups = null, + $payload = null + ) { + if (!class_exists(Countries::class)) { + throw new LogicException('The Intl component is required to use the Country constraint. Try running "composer require symfony/intl".'); + } + + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + $this->alpha3 = $alpha3 ?? $this->alpha3; + } +} diff --git a/src/vendor/symfony/validator/Constraints/CountryValidator.php b/src/vendor/symfony/validator/Constraints/CountryValidator.php new file mode 100644 index 0000000..a96809c --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/CountryValidator.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Intl\Countries; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; +use Symfony\Component\Validator\Exception\UnexpectedValueException; + +/** + * Validates whether a value is a valid country code. + * + * @author Bernhard Schussek + */ +class CountryValidator extends ConstraintValidator +{ + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Country) { + throw new UnexpectedTypeException($constraint, Country::class); + } + + if (null === $value || '' === $value) { + return; + } + + if (!\is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { + throw new UnexpectedValueException($value, 'string'); + } + + $value = (string) $value; + + if ($constraint->alpha3 ? !Countries::alpha3CodeExists($value) : !Countries::exists($value)) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Country::NO_SUCH_COUNTRY_ERROR) + ->addViolation(); + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/CssColor.php b/src/vendor/symfony/validator/Constraints/CssColor.php new file mode 100644 index 0000000..e1510da --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/CssColor.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Exception\InvalidArgumentException; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Mathieu Santostefano + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class CssColor extends Constraint +{ + public const HEX_LONG = 'hex_long'; + public const HEX_LONG_WITH_ALPHA = 'hex_long_with_alpha'; + public const HEX_SHORT = 'hex_short'; + public const HEX_SHORT_WITH_ALPHA = 'hex_short_with_alpha'; + public const BASIC_NAMED_COLORS = 'basic_named_colors'; + public const EXTENDED_NAMED_COLORS = 'extended_named_colors'; + public const SYSTEM_COLORS = 'system_colors'; + public const KEYWORDS = 'keywords'; + public const RGB = 'rgb'; + public const RGBA = 'rgba'; + public const HSL = 'hsl'; + public const HSLA = 'hsla'; + public const INVALID_FORMAT_ERROR = '454ab47b-aacf-4059-8f26-184b2dc9d48d'; + + protected static $errorNames = [ + self::INVALID_FORMAT_ERROR => 'INVALID_FORMAT_ERROR', + ]; + + /** + * @var string[] + */ + private static $validationModes = [ + self::HEX_LONG, + self::HEX_LONG_WITH_ALPHA, + self::HEX_SHORT, + self::HEX_SHORT_WITH_ALPHA, + self::BASIC_NAMED_COLORS, + self::EXTENDED_NAMED_COLORS, + self::SYSTEM_COLORS, + self::KEYWORDS, + self::RGB, + self::RGBA, + self::HSL, + self::HSLA, + ]; + + public $message = 'This value is not a valid CSS color.'; + public $formats; + + /** + * @param array|string $formats The types of CSS colors allowed (e.g. hexadecimal only, RGB and HSL only, etc.). + */ + public function __construct($formats = [], string $message = null, array $groups = null, $payload = null, array $options = null) + { + $validationModesAsString = implode(', ', self::$validationModes); + + if (!$formats) { + $options['value'] = self::$validationModes; + } elseif (\is_array($formats) && \is_string(key($formats))) { + $options = array_merge($formats, $options ?? []); + } elseif (\is_array($formats)) { + if ([] === array_intersect(self::$validationModes, $formats)) { + throw new InvalidArgumentException(sprintf('The "formats" parameter value is not valid. It must contain one or more of the following values: "%s".', $validationModesAsString)); + } + + $options['value'] = $formats; + } elseif (\is_string($formats)) { + if (!\in_array($formats, self::$validationModes)) { + throw new InvalidArgumentException(sprintf('The "formats" parameter value is not valid. It must contain one or more of the following values: "%s".', $validationModesAsString)); + } + + $options['value'] = [$formats]; + } else { + throw new InvalidArgumentException('The "formats" parameter type is not valid. It should be a string or an array.'); + } + + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + } + + public function getDefaultOption(): string + { + return 'formats'; + } + + public function getRequiredOptions(): array + { + return ['formats']; + } +} diff --git a/src/vendor/symfony/validator/Constraints/CssColorValidator.php b/src/vendor/symfony/validator/Constraints/CssColorValidator.php new file mode 100644 index 0000000..b34ef93 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/CssColorValidator.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; +use Symfony\Component\Validator\Exception\UnexpectedValueException; + +/** + * @author Mathieu Santostefano + */ +class CssColorValidator extends ConstraintValidator +{ + private const PATTERN_HEX_LONG = '/^#[0-9a-f]{6}$/i'; + private const PATTERN_HEX_LONG_WITH_ALPHA = '/^#[0-9a-f]{8}$/i'; + private const PATTERN_HEX_SHORT = '/^#[0-9a-f]{3}$/i'; + private const PATTERN_HEX_SHORT_WITH_ALPHA = '/^#[0-9a-f]{4}$/i'; + // List comes from https://www.w3.org/wiki/CSS/Properties/color/keywords#Basic_Colors + private const PATTERN_BASIC_NAMED_COLORS = '/^(black|silver|gray|white|maroon|red|purple|fuchsia|green|lime|olive|yellow|navy|blue|teal|aqua)$/i'; + // List comes from https://www.w3.org/wiki/CSS/Properties/color/keywords#Extended_colors + private const PATTERN_EXTENDED_NAMED_COLORS = '/^(aliceblue|antiquewhite|aqua|aquamarine|azure|beige|bisque|black|blanchedalmond|blue|blueviolet|brown|burlywood|cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|cyan|darkblue|darkcyan|darkgoldenrod|darkgray|darkgreen|darkgrey|darkkhaki|darkmagenta|darkolivegreen|darkorange|darkorchid|darkred|darksalmon|darkseagreen|darkslateblue|darkslategray|darkslategrey|darkturquoise|darkviolet|deeppink|deepskyblue|dimgray|dimgrey|dodgerblue|firebrick|floralwhite|forestgreen|fuchsia|gainsboro|ghostwhite|gold|goldenrod|gray|green|greenyellow|grey|honeydew|hotpink|indianred|indigo|ivory|khaki|lavender|lavenderblush|lawngreen|lemonchiffon|lightblue|lightcoral|lightcyan|lightgoldenrodyellow|lightgray|lightgreen|lightgrey|lightpink|lightsalmon|lightseagreen|lightskyblue|lightslategray|lightslategrey|lightsteelblue|lightyellow|lime|limegreen|linen|magenta|maroon|mediumaquamarine|mediumblue|mediumorchid|mediumpurple|mediumseagreen|mediumslateblue|mediumspringgreen|mediumturquoise|mediumvioletred|midnightblue|mintcream|mistyrose|moccasin|navajowhite|navy|oldlace|olive|olivedrab|orange|orangered|orchid|palegoldenrod|palegreen|paleturquoise|palevioletred|papayawhip|peachpuff|peru|pink|plum|powderblue|purple|red|rosybrown|royalblue|saddlebrown|salmon|sandybrown|seagreen|seashell|sienna|silver|skyblue|slateblue|slategray|slategrey|snow|springgreen|steelblue|tan|teal|thistle|tomato|turquoise|violet|wheat|white|whitesmoke|yellow|yellowgreen)$/i'; + // List comes from https://drafts.csswg.org/css-color/#css-system-colors + private const PATTERN_SYSTEM_COLORS = '/^(Canvas|CanvasText|LinkText|VisitedText|ActiveText|ButtonFace|ButtonText|ButtonBorder|Field|FieldText|Highlight|HighlightText|SelectedItem|SelectedItemText|Mark|MarkText|GrayText)$/i'; + private const PATTERN_KEYWORDS = '/^(transparent|currentColor)$/i'; + private const PATTERN_RGB = '/^rgb\(\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d),\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d),\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d)\s*\)$/i'; + private const PATTERN_RGBA = '/^rgba\(\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d),\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d),\s*(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d),\s*(0|0?\.\d+|1(\.0)?)\s*\)$/i'; + private const PATTERN_HSL = '/^hsl\(\s*(0|360|35\d|3[0-4]\d|[12]\d\d|0?\d?\d),\s*(0|100|\d{1,2})%,\s*(0|100|\d{1,2})%\s*\)$/i'; + private const PATTERN_HSLA = '/^hsla\(\s*(0|360|35\d|3[0-4]\d|[12]\d\d|0?\d?\d),\s*(0|100|\d{1,2})%,\s*(0|100|\d{1,2})%,\s*(0|0?\.\d+|1(\.0)?)\s*\)$/i'; + + private const COLOR_PATTERNS = [ + CssColor::HEX_LONG => self::PATTERN_HEX_LONG, + CssColor::HEX_LONG_WITH_ALPHA => self::PATTERN_HEX_LONG_WITH_ALPHA, + CssColor::HEX_SHORT => self::PATTERN_HEX_SHORT, + CssColor::HEX_SHORT_WITH_ALPHA => self::PATTERN_HEX_SHORT_WITH_ALPHA, + CssColor::BASIC_NAMED_COLORS => self::PATTERN_BASIC_NAMED_COLORS, + CssColor::EXTENDED_NAMED_COLORS => self::PATTERN_EXTENDED_NAMED_COLORS, + CssColor::SYSTEM_COLORS => self::PATTERN_SYSTEM_COLORS, + CssColor::KEYWORDS => self::PATTERN_KEYWORDS, + CssColor::RGB => self::PATTERN_RGB, + CssColor::RGBA => self::PATTERN_RGBA, + CssColor::HSL => self::PATTERN_HSL, + CssColor::HSLA => self::PATTERN_HSLA, + ]; + + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint): void + { + if (!$constraint instanceof CssColor) { + throw new UnexpectedTypeException($constraint, CssColor::class); + } + + if (null === $value || '' === $value) { + return; + } + + if (!\is_string($value)) { + throw new UnexpectedValueException($value, 'string'); + } + + $formats = array_flip((array) $constraint->formats); + $formatRegexes = array_intersect_key(self::COLOR_PATTERNS, $formats); + + foreach ($formatRegexes as $regex) { + if (preg_match($regex, (string) $value)) { + return; + } + } + + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(CssColor::INVALID_FORMAT_ERROR) + ->addViolation(); + } +} diff --git a/src/vendor/symfony/validator/Constraints/Currency.php b/src/vendor/symfony/validator/Constraints/Currency.php new file mode 100644 index 0000000..cac2dff --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Currency.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Intl\Currencies; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Exception\LogicException; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Miha Vrhovnik + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class Currency extends Constraint +{ + public const NO_SUCH_CURRENCY_ERROR = '69945ac1-2db4-405f-bec7-d2772f73df52'; + + protected static $errorNames = [ + self::NO_SUCH_CURRENCY_ERROR => 'NO_SUCH_CURRENCY_ERROR', + ]; + + public $message = 'This value is not a valid currency.'; + + public function __construct(array $options = null, string $message = null, array $groups = null, $payload = null) + { + if (!class_exists(Currencies::class)) { + throw new LogicException('The Intl component is required to use the Currency constraint. Try running "composer require symfony/intl".'); + } + + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + } +} diff --git a/src/vendor/symfony/validator/Constraints/CurrencyValidator.php b/src/vendor/symfony/validator/Constraints/CurrencyValidator.php new file mode 100644 index 0000000..64dd66f --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/CurrencyValidator.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Intl\Currencies; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; +use Symfony\Component\Validator\Exception\UnexpectedValueException; + +/** + * Validates whether a value is a valid currency. + * + * @author Miha Vrhovnik + * @author Bernhard Schussek + */ +class CurrencyValidator extends ConstraintValidator +{ + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Currency) { + throw new UnexpectedTypeException($constraint, Currency::class); + } + + if (null === $value || '' === $value) { + return; + } + + if (!\is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { + throw new UnexpectedValueException($value, 'string'); + } + + $value = (string) $value; + + if (!Currencies::exists($value)) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Currency::NO_SUCH_CURRENCY_ERROR) + ->addViolation(); + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/Date.php b/src/vendor/symfony/validator/Constraints/Date.php new file mode 100644 index 0000000..7c9666f --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Date.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class Date extends Constraint +{ + public const INVALID_FORMAT_ERROR = '69819696-02ac-4a99-9ff0-14e127c4d1bc'; + public const INVALID_DATE_ERROR = '3c184ce5-b31d-4de7-8b76-326da7b2be93'; + + protected static $errorNames = [ + self::INVALID_FORMAT_ERROR => 'INVALID_FORMAT_ERROR', + self::INVALID_DATE_ERROR => 'INVALID_DATE_ERROR', + ]; + + public $message = 'This value is not a valid date.'; + + public function __construct(array $options = null, string $message = null, array $groups = null, $payload = null) + { + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + } +} diff --git a/src/vendor/symfony/validator/Constraints/DateTime.php b/src/vendor/symfony/validator/Constraints/DateTime.php new file mode 100644 index 0000000..94c3dd6 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/DateTime.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class DateTime extends Constraint +{ + public const INVALID_FORMAT_ERROR = '1a9da513-2640-4f84-9b6a-4d99dcddc628'; + public const INVALID_DATE_ERROR = 'd52afa47-620d-4d99-9f08-f4d85b36e33c'; + public const INVALID_TIME_ERROR = '5e797c9d-74f7-4098-baa3-94390c447b27'; + + protected static $errorNames = [ + self::INVALID_FORMAT_ERROR => 'INVALID_FORMAT_ERROR', + self::INVALID_DATE_ERROR => 'INVALID_DATE_ERROR', + self::INVALID_TIME_ERROR => 'INVALID_TIME_ERROR', + ]; + + public $format = 'Y-m-d H:i:s'; + public $message = 'This value is not a valid datetime.'; + + /** + * {@inheritdoc} + * + * @param string|array|null $format + */ + public function __construct($format = null, string $message = null, array $groups = null, $payload = null, array $options = []) + { + if (\is_array($format)) { + $options = array_merge($format, $options); + } elseif (null !== $format) { + $options['value'] = $format; + } + + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + } + + public function getDefaultOption() + { + return 'format'; + } +} diff --git a/src/vendor/symfony/validator/Constraints/DateTimeValidator.php b/src/vendor/symfony/validator/Constraints/DateTimeValidator.php new file mode 100644 index 0000000..6ecefe6 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/DateTimeValidator.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; +use Symfony\Component\Validator\Exception\UnexpectedValueException; + +/** + * @author Bernhard Schussek + * @author Diego Saint Esteben + */ +class DateTimeValidator extends DateValidator +{ + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof DateTime) { + throw new UnexpectedTypeException($constraint, DateTime::class); + } + + if (null === $value || '' === $value) { + return; + } + + if (!\is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { + throw new UnexpectedValueException($value, 'string'); + } + + $value = (string) $value; + + \DateTime::createFromFormat($constraint->format, $value); + + $errors = \DateTime::getLastErrors() ?: ['error_count' => 0, 'warnings' => []]; + + if (0 < $errors['error_count']) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(DateTime::INVALID_FORMAT_ERROR) + ->addViolation(); + + return; + } + + if (str_ends_with($constraint->format, '+')) { + $errors['warnings'] = array_filter($errors['warnings'], function ($warning) { + return 'Trailing data' !== $warning; + }); + } + + foreach ($errors['warnings'] as $warning) { + if ('The parsed date was invalid' === $warning) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(DateTime::INVALID_DATE_ERROR) + ->addViolation(); + } elseif ('The parsed time was invalid' === $warning) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(DateTime::INVALID_TIME_ERROR) + ->addViolation(); + } else { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(DateTime::INVALID_FORMAT_ERROR) + ->addViolation(); + } + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/DateValidator.php b/src/vendor/symfony/validator/Constraints/DateValidator.php new file mode 100644 index 0000000..5a5f22e --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/DateValidator.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; +use Symfony\Component\Validator\Exception\UnexpectedValueException; + +/** + * @author Bernhard Schussek + */ +class DateValidator extends ConstraintValidator +{ + public const PATTERN = '/^(?\d{4})-(?\d{2})-(?\d{2})$/'; + + /** + * Checks whether a date is valid. + * + * @internal + */ + public static function checkDate(int $year, int $month, int $day): bool + { + return checkdate($month, $day, $year); + } + + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Date) { + throw new UnexpectedTypeException($constraint, Date::class); + } + + if (null === $value || '' === $value) { + return; + } + + if (!\is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { + throw new UnexpectedValueException($value, 'string'); + } + + $value = (string) $value; + + if (!preg_match(static::PATTERN, $value, $matches)) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Date::INVALID_FORMAT_ERROR) + ->addViolation(); + + return; + } + + if (!self::checkDate( + $matches['year'] ?? $matches[1], + $matches['month'] ?? $matches[2], + $matches['day'] ?? $matches[3] + )) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Date::INVALID_DATE_ERROR) + ->addViolation(); + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/DisableAutoMapping.php b/src/vendor/symfony/validator/Constraints/DisableAutoMapping.php new file mode 100644 index 0000000..9a91f00 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/DisableAutoMapping.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; + +/** + * Disables auto mapping. + * + * Using the annotations on a property has higher precedence than using it on a class, + * which has higher precedence than any configuration that might be defined outside the class. + * + * @Annotation + * + * @author Kévin Dunglas + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::TARGET_CLASS)] +class DisableAutoMapping extends Constraint +{ + public function __construct(array $options = null) + { + if (\is_array($options) && \array_key_exists('groups', $options)) { + throw new ConstraintDefinitionException(sprintf('The option "groups" is not supported by the constraint "%s".', __CLASS__)); + } + + parent::__construct($options); + } + + /** + * {@inheritdoc} + */ + public function getTargets() + { + return [self::PROPERTY_CONSTRAINT, self::CLASS_CONSTRAINT]; + } +} diff --git a/src/vendor/symfony/validator/Constraints/DivisibleBy.php b/src/vendor/symfony/validator/Constraints/DivisibleBy.php new file mode 100644 index 0000000..d3f5cd7 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/DivisibleBy.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Colin O'Dell + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class DivisibleBy extends AbstractComparison +{ + public const NOT_DIVISIBLE_BY = '6d99d6c3-1464-4ccf-bdc7-14d083cf455c'; + + protected static $errorNames = [ + self::NOT_DIVISIBLE_BY => 'NOT_DIVISIBLE_BY', + ]; + + public $message = 'This value should be a multiple of {{ compared_value }}.'; +} diff --git a/src/vendor/symfony/validator/Constraints/DivisibleByValidator.php b/src/vendor/symfony/validator/Constraints/DivisibleByValidator.php new file mode 100644 index 0000000..de77430 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/DivisibleByValidator.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Exception\UnexpectedValueException; + +/** + * Validates that values are a multiple of the given number. + * + * @author Colin O'Dell + */ +class DivisibleByValidator extends AbstractComparisonValidator +{ + /** + * {@inheritdoc} + */ + protected function compareValues($value1, $value2) + { + if (!is_numeric($value1)) { + throw new UnexpectedValueException($value1, 'numeric'); + } + + if (!is_numeric($value2)) { + throw new UnexpectedValueException($value2, 'numeric'); + } + + if (!$value2 = abs($value2)) { + return false; + } + if (\is_int($value1 = abs($value1)) && \is_int($value2)) { + return 0 === ($value1 % $value2); + } + if (!$remainder = fmod($value1, $value2)) { + return true; + } + if (\is_float($value2) && \INF !== $value2) { + $quotient = $value1 / $value2; + $rounded = round($quotient); + + return sprintf('%.12e', $quotient) === sprintf('%.12e', $rounded); + } + + return sprintf('%.12e', $value2) === sprintf('%.12e', $remainder); + } + + /** + * {@inheritdoc} + */ + protected function getErrorCode() + { + return DivisibleBy::NOT_DIVISIBLE_BY; + } +} diff --git a/src/vendor/symfony/validator/Constraints/Email.php b/src/vendor/symfony/validator/Constraints/Email.php new file mode 100644 index 0000000..7976cc4 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Email.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Egulias\EmailValidator\EmailValidator as StrictEmailValidator; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Exception\InvalidArgumentException; +use Symfony\Component\Validator\Exception\LogicException; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class Email extends Constraint +{ + public const VALIDATION_MODE_HTML5 = 'html5'; + public const VALIDATION_MODE_STRICT = 'strict'; + public const VALIDATION_MODE_LOOSE = 'loose'; + + public const INVALID_FORMAT_ERROR = 'bd79c0ab-ddba-46cc-a703-a7a4b08de310'; + + protected static $errorNames = [ + self::INVALID_FORMAT_ERROR => 'STRICT_CHECK_FAILED_ERROR', + ]; + + /** + * @var string[] + * + * @internal + */ + public static $validationModes = [ + self::VALIDATION_MODE_HTML5, + self::VALIDATION_MODE_STRICT, + self::VALIDATION_MODE_LOOSE, + ]; + + public $message = 'This value is not a valid email address.'; + public $mode; + public $normalizer; + + public function __construct( + array $options = null, + string $message = null, + string $mode = null, + callable $normalizer = null, + array $groups = null, + $payload = null + ) { + if (\is_array($options) && \array_key_exists('mode', $options) && !\in_array($options['mode'], self::$validationModes, true)) { + throw new InvalidArgumentException('The "mode" parameter value is not valid.'); + } + + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + $this->mode = $mode ?? $this->mode; + $this->normalizer = $normalizer ?? $this->normalizer; + + if (self::VALIDATION_MODE_STRICT === $this->mode && !class_exists(StrictEmailValidator::class)) { + throw new LogicException(sprintf('The "egulias/email-validator" component is required to use the "%s" constraint in strict mode.', __CLASS__)); + } + + if (null !== $this->normalizer && !\is_callable($this->normalizer)) { + throw new InvalidArgumentException(sprintf('The "normalizer" option must be a valid callable ("%s" given).', get_debug_type($this->normalizer))); + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/EmailValidator.php b/src/vendor/symfony/validator/Constraints/EmailValidator.php new file mode 100644 index 0000000..11fc7be --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/EmailValidator.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Egulias\EmailValidator\EmailValidator as EguliasEmailValidator; +use Egulias\EmailValidator\Validation\EmailValidation; +use Egulias\EmailValidator\Validation\NoRFCWarningsValidation; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\LogicException; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; +use Symfony\Component\Validator\Exception\UnexpectedValueException; + +/** + * @author Bernhard Schussek + */ +class EmailValidator extends ConstraintValidator +{ + private const PATTERN_HTML5 = '/^[a-zA-Z0-9.!#$%&\'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/'; + private const PATTERN_LOOSE = '/^.+\@\S+\.\S+$/'; + + private const EMAIL_PATTERNS = [ + Email::VALIDATION_MODE_LOOSE => self::PATTERN_LOOSE, + Email::VALIDATION_MODE_HTML5 => self::PATTERN_HTML5, + ]; + + private $defaultMode; + + public function __construct(string $defaultMode = Email::VALIDATION_MODE_LOOSE) + { + if (!\in_array($defaultMode, Email::$validationModes, true)) { + throw new \InvalidArgumentException('The "defaultMode" parameter value is not valid.'); + } + + $this->defaultMode = $defaultMode; + } + + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Email) { + throw new UnexpectedTypeException($constraint, Email::class); + } + + if (null === $value || '' === $value) { + return; + } + + if (!\is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { + throw new UnexpectedValueException($value, 'string'); + } + + $value = (string) $value; + if ('' === $value) { + return; + } + + if (null !== $constraint->normalizer) { + $value = ($constraint->normalizer)($value); + } + + if (null === $constraint->mode) { + if (Email::VALIDATION_MODE_STRICT === $this->defaultMode && !class_exists(EguliasEmailValidator::class)) { + throw new LogicException(sprintf('The "egulias/email-validator" component is required to make the "%s" constraint default to strict mode.', Email::class)); + } + + $constraint->mode = $this->defaultMode; + } + + if (!\in_array($constraint->mode, Email::$validationModes, true)) { + throw new \InvalidArgumentException(sprintf('The "%s::$mode" parameter value is not valid.', get_debug_type($constraint))); + } + + if (Email::VALIDATION_MODE_STRICT === $constraint->mode) { + $strictValidator = new EguliasEmailValidator(); + + if (interface_exists(EmailValidation::class) && !$strictValidator->isValid($value, new NoRFCWarningsValidation())) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Email::INVALID_FORMAT_ERROR) + ->addViolation(); + + return; + } elseif (!interface_exists(EmailValidation::class) && !$strictValidator->isValid($value, false, true)) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Email::INVALID_FORMAT_ERROR) + ->addViolation(); + + return; + } + } elseif (!preg_match(self::EMAIL_PATTERNS[$constraint->mode], $value)) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Email::INVALID_FORMAT_ERROR) + ->addViolation(); + + return; + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/EnableAutoMapping.php b/src/vendor/symfony/validator/Constraints/EnableAutoMapping.php new file mode 100644 index 0000000..3136fd3 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/EnableAutoMapping.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; + +/** + * Enables auto mapping. + * + * Using the annotations on a property has higher precedence than using it on a class, + * which has higher precedence than any configuration that might be defined outside the class. + * + * @Annotation + * + * @author Kévin Dunglas + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::TARGET_CLASS)] +class EnableAutoMapping extends Constraint +{ + public function __construct(array $options = null) + { + if (\is_array($options) && \array_key_exists('groups', $options)) { + throw new ConstraintDefinitionException(sprintf('The option "groups" is not supported by the constraint "%s".', __CLASS__)); + } + + parent::__construct($options); + } + + /** + * {@inheritdoc} + */ + public function getTargets() + { + return [self::PROPERTY_CONSTRAINT, self::CLASS_CONSTRAINT]; + } +} diff --git a/src/vendor/symfony/validator/Constraints/EqualTo.php b/src/vendor/symfony/validator/Constraints/EqualTo.php new file mode 100644 index 0000000..09ab4f0 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/EqualTo.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Daniel Holmes + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class EqualTo extends AbstractComparison +{ + public const NOT_EQUAL_ERROR = '478618a7-95ba-473d-9101-cabd45e49115'; + + protected static $errorNames = [ + self::NOT_EQUAL_ERROR => 'NOT_EQUAL_ERROR', + ]; + + public $message = 'This value should be equal to {{ compared_value }}.'; +} diff --git a/src/vendor/symfony/validator/Constraints/EqualToValidator.php b/src/vendor/symfony/validator/Constraints/EqualToValidator.php new file mode 100644 index 0000000..fe1f362 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/EqualToValidator.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +/** + * Validates values are equal (==). + * + * @author Daniel Holmes + * @author Bernhard Schussek + */ +class EqualToValidator extends AbstractComparisonValidator +{ + /** + * {@inheritdoc} + */ + protected function compareValues($value1, $value2) + { + return $value1 == $value2; + } + + /** + * {@inheritdoc} + */ + protected function getErrorCode() + { + return EqualTo::NOT_EQUAL_ERROR; + } +} diff --git a/src/vendor/symfony/validator/Constraints/Existence.php b/src/vendor/symfony/validator/Constraints/Existence.php new file mode 100644 index 0000000..903cf63 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Existence.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +/** + * @author Bernhard Schussek + */ +abstract class Existence extends Composite +{ + public $constraints = []; + + public function getDefaultOption() + { + return 'constraints'; + } + + protected function getCompositeOption() + { + return 'constraints'; + } +} diff --git a/src/vendor/symfony/validator/Constraints/Expression.php b/src/vendor/symfony/validator/Constraints/Expression.php new file mode 100644 index 0000000..01cf429 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Expression.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\ExpressionLanguage\Expression as ExpressionObject; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Exception\LogicException; + +/** + * @Annotation + * @Target({"CLASS", "PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Fabien Potencier + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] +class Expression extends Constraint +{ + public const EXPRESSION_FAILED_ERROR = '6b3befbc-2f01-4ddf-be21-b57898905284'; + + protected static $errorNames = [ + self::EXPRESSION_FAILED_ERROR => 'EXPRESSION_FAILED_ERROR', + ]; + + public $message = 'This value is not valid.'; + public $expression; + public $values = []; + + /** + * {@inheritdoc} + * + * @param string|ExpressionObject|array $expression The expression to evaluate or an array of options + */ + public function __construct( + $expression, + string $message = null, + array $values = null, + array $groups = null, + $payload = null, + array $options = [] + ) { + if (!class_exists(ExpressionLanguage::class)) { + throw new LogicException(sprintf('The "symfony/expression-language" component is required to use the "%s" constraint.', __CLASS__)); + } + + if (\is_array($expression)) { + $options = array_merge($expression, $options); + } elseif (!\is_string($expression) && !$expression instanceof ExpressionObject) { + throw new \TypeError(sprintf('"%s": Expected argument $expression to be either a string, an instance of "%s" or an array, got "%s".', __METHOD__, ExpressionObject::class, get_debug_type($expression))); + } else { + $options['value'] = $expression; + } + + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + $this->values = $values ?? $this->values; + } + + /** + * {@inheritdoc} + */ + public function getDefaultOption() + { + return 'expression'; + } + + /** + * {@inheritdoc} + */ + public function getRequiredOptions() + { + return ['expression']; + } + + /** + * {@inheritdoc} + */ + public function getTargets() + { + return [self::CLASS_CONSTRAINT, self::PROPERTY_CONSTRAINT]; + } + + /** + * {@inheritdoc} + */ + public function validatedBy() + { + return 'validator.expression'; + } +} diff --git a/src/vendor/symfony/validator/Constraints/ExpressionLanguageSyntax.php b/src/vendor/symfony/validator/Constraints/ExpressionLanguageSyntax.php new file mode 100644 index 0000000..d5c1f6f --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/ExpressionLanguageSyntax.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Andrey Sevastianov + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class ExpressionLanguageSyntax extends Constraint +{ + public const EXPRESSION_LANGUAGE_SYNTAX_ERROR = '1766a3f3-ff03-40eb-b053-ab7aa23d988a'; + + protected static $errorNames = [ + self::EXPRESSION_LANGUAGE_SYNTAX_ERROR => 'EXPRESSION_LANGUAGE_SYNTAX_ERROR', + ]; + + public $message = 'This value should be a valid expression.'; + public $service; + public $allowedVariables; + + public function __construct(array $options = null, string $message = null, string $service = null, array $allowedVariables = null, array $groups = null, $payload = null) + { + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + $this->service = $service ?? $this->service; + $this->allowedVariables = $allowedVariables ?? $this->allowedVariables; + } + + /** + * {@inheritdoc} + */ + public function validatedBy() + { + return $this->service ?? static::class.'Validator'; + } +} diff --git a/src/vendor/symfony/validator/Constraints/ExpressionLanguageSyntaxValidator.php b/src/vendor/symfony/validator/Constraints/ExpressionLanguageSyntaxValidator.php new file mode 100644 index 0000000..4b67da2 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/ExpressionLanguageSyntaxValidator.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use Symfony\Component\ExpressionLanguage\SyntaxError; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; +use Symfony\Component\Validator\Exception\UnexpectedValueException; + +/** + * @author Andrey Sevastianov + */ +class ExpressionLanguageSyntaxValidator extends ConstraintValidator +{ + private $expressionLanguage; + + public function __construct(ExpressionLanguage $expressionLanguage = null) + { + $this->expressionLanguage = $expressionLanguage; + } + + /** + * {@inheritdoc} + */ + public function validate($expression, Constraint $constraint): void + { + if (!$constraint instanceof ExpressionLanguageSyntax) { + throw new UnexpectedTypeException($constraint, ExpressionLanguageSyntax::class); + } + + if (!\is_string($expression)) { + throw new UnexpectedValueException($expression, 'string'); + } + + if (null === $this->expressionLanguage) { + $this->expressionLanguage = new ExpressionLanguage(); + } + + try { + $this->expressionLanguage->lint($expression, $constraint->allowedVariables); + } catch (SyntaxError $exception) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ syntax_error }}', $this->formatValue($exception->getMessage())) + ->setInvalidValue((string) $expression) + ->setCode(ExpressionLanguageSyntax::EXPRESSION_LANGUAGE_SYNTAX_ERROR) + ->addViolation(); + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/ExpressionValidator.php b/src/vendor/symfony/validator/Constraints/ExpressionValidator.php new file mode 100644 index 0000000..3ae47f4 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/ExpressionValidator.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; + +/** + * @author Fabien Potencier + * @author Bernhard Schussek + */ +class ExpressionValidator extends ConstraintValidator +{ + private $expressionLanguage; + + public function __construct(ExpressionLanguage $expressionLanguage = null) + { + $this->expressionLanguage = $expressionLanguage; + } + + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Expression) { + throw new UnexpectedTypeException($constraint, Expression::class); + } + + $variables = $constraint->values; + $variables['value'] = $value; + $variables['this'] = $this->context->getObject(); + + if (!$this->getExpressionLanguage()->evaluate($constraint->expression, $variables)) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value, self::OBJECT_TO_STRING)) + ->setCode(Expression::EXPRESSION_FAILED_ERROR) + ->addViolation(); + } + } + + private function getExpressionLanguage(): ExpressionLanguage + { + if (null === $this->expressionLanguage) { + $this->expressionLanguage = new ExpressionLanguage(); + } + + return $this->expressionLanguage; + } +} diff --git a/src/vendor/symfony/validator/Constraints/File.php b/src/vendor/symfony/validator/Constraints/File.php new file mode 100644 index 0000000..b5a446e --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/File.php @@ -0,0 +1,174 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @property int $maxSize + * + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class File extends Constraint +{ + // Check the Image constraint for clashes if adding new constants here + + public const NOT_FOUND_ERROR = 'd2a3fb6e-7ddc-4210-8fbf-2ab345ce1998'; + public const NOT_READABLE_ERROR = 'c20c92a4-5bfa-4202-9477-28e800e0f6ff'; + public const EMPTY_ERROR = '5d743385-9775-4aa5-8ff5-495fb1e60137'; + public const TOO_LARGE_ERROR = 'df8637af-d466-48c6-a59d-e7126250a654'; + public const INVALID_MIME_TYPE_ERROR = '744f00bc-4389-4c74-92de-9a43cde55534'; + + protected static $errorNames = [ + self::NOT_FOUND_ERROR => 'NOT_FOUND_ERROR', + self::NOT_READABLE_ERROR => 'NOT_READABLE_ERROR', + self::EMPTY_ERROR => 'EMPTY_ERROR', + self::TOO_LARGE_ERROR => 'TOO_LARGE_ERROR', + self::INVALID_MIME_TYPE_ERROR => 'INVALID_MIME_TYPE_ERROR', + ]; + + public $binaryFormat; + public $mimeTypes = []; + public $notFoundMessage = 'The file could not be found.'; + public $notReadableMessage = 'The file is not readable.'; + public $maxSizeMessage = 'The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}.'; + public $mimeTypesMessage = 'The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}.'; + public $disallowEmptyMessage = 'An empty file is not allowed.'; + + public $uploadIniSizeErrorMessage = 'The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}.'; + public $uploadFormSizeErrorMessage = 'The file is too large.'; + public $uploadPartialErrorMessage = 'The file was only partially uploaded.'; + public $uploadNoFileErrorMessage = 'No file was uploaded.'; + public $uploadNoTmpDirErrorMessage = 'No temporary folder was configured in php.ini.'; + public $uploadCantWriteErrorMessage = 'Cannot write temporary file to disk.'; + public $uploadExtensionErrorMessage = 'A PHP extension caused the upload to fail.'; + public $uploadErrorMessage = 'The file could not be uploaded.'; + + protected $maxSize; + + /** + * {@inheritdoc} + * + * @param int|string|null $maxSize + * @param string[]|string|null $mimeTypes + */ + public function __construct( + array $options = null, + $maxSize = null, + bool $binaryFormat = null, + $mimeTypes = null, + string $notFoundMessage = null, + string $notReadableMessage = null, + string $maxSizeMessage = null, + string $mimeTypesMessage = null, + string $disallowEmptyMessage = null, + + string $uploadIniSizeErrorMessage = null, + string $uploadFormSizeErrorMessage = null, + string $uploadPartialErrorMessage = null, + string $uploadNoFileErrorMessage = null, + string $uploadNoTmpDirErrorMessage = null, + string $uploadCantWriteErrorMessage = null, + string $uploadExtensionErrorMessage = null, + string $uploadErrorMessage = null, + array $groups = null, + $payload = null + ) { + if (null !== $maxSize && !\is_int($maxSize) && !\is_string($maxSize)) { + throw new \TypeError(sprintf('"%s": Expected argument $maxSize to be either null, an integer or a string, got "%s".', __METHOD__, get_debug_type($maxSize))); + } + if (null !== $mimeTypes && !\is_array($mimeTypes) && !\is_string($mimeTypes)) { + throw new \TypeError(sprintf('"%s": Expected argument $mimeTypes to be either null, an array or a string, got "%s".', __METHOD__, get_debug_type($mimeTypes))); + } + + parent::__construct($options, $groups, $payload); + + $this->maxSize = $maxSize ?? $this->maxSize; + $this->binaryFormat = $binaryFormat ?? $this->binaryFormat; + $this->mimeTypes = $mimeTypes ?? $this->mimeTypes; + $this->notFoundMessage = $notFoundMessage ?? $this->notFoundMessage; + $this->notReadableMessage = $notReadableMessage ?? $this->notReadableMessage; + $this->maxSizeMessage = $maxSizeMessage ?? $this->maxSizeMessage; + $this->mimeTypesMessage = $mimeTypesMessage ?? $this->mimeTypesMessage; + $this->disallowEmptyMessage = $disallowEmptyMessage ?? $this->disallowEmptyMessage; + $this->uploadIniSizeErrorMessage = $uploadIniSizeErrorMessage ?? $this->uploadIniSizeErrorMessage; + $this->uploadFormSizeErrorMessage = $uploadFormSizeErrorMessage ?? $this->uploadFormSizeErrorMessage; + $this->uploadPartialErrorMessage = $uploadPartialErrorMessage ?? $this->uploadPartialErrorMessage; + $this->uploadNoFileErrorMessage = $uploadNoFileErrorMessage ?? $this->uploadNoFileErrorMessage; + $this->uploadNoTmpDirErrorMessage = $uploadNoTmpDirErrorMessage ?? $this->uploadNoTmpDirErrorMessage; + $this->uploadCantWriteErrorMessage = $uploadCantWriteErrorMessage ?? $this->uploadCantWriteErrorMessage; + $this->uploadExtensionErrorMessage = $uploadExtensionErrorMessage ?? $this->uploadExtensionErrorMessage; + $this->uploadErrorMessage = $uploadErrorMessage ?? $this->uploadErrorMessage; + + if (null !== $this->maxSize) { + $this->normalizeBinaryFormat($this->maxSize); + } + } + + public function __set(string $option, $value) + { + if ('maxSize' === $option) { + $this->normalizeBinaryFormat($value); + + return; + } + + parent::__set($option, $value); + } + + public function __get(string $option) + { + if ('maxSize' === $option) { + return $this->maxSize; + } + + return parent::__get($option); + } + + public function __isset(string $option) + { + if ('maxSize' === $option) { + return true; + } + + return parent::__isset($option); + } + + /** + * @param int|string $maxSize + */ + private function normalizeBinaryFormat($maxSize) + { + $factors = [ + 'k' => 1000, + 'ki' => 1 << 10, + 'm' => 1000 * 1000, + 'mi' => 1 << 20, + 'g' => 1000 * 1000 * 1000, + 'gi' => 1 << 30, + ]; + if (ctype_digit((string) $maxSize)) { + $this->maxSize = (int) $maxSize; + $this->binaryFormat = $this->binaryFormat ?? false; + } elseif (preg_match('/^(\d++)('.implode('|', array_keys($factors)).')$/i', $maxSize, $matches)) { + $this->maxSize = $matches[1] * $factors[$unit = strtolower($matches[2])]; + $this->binaryFormat = $this->binaryFormat ?? (2 === \strlen($unit)); + } else { + throw new ConstraintDefinitionException(sprintf('"%s" is not a valid maximum size.', $maxSize)); + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/FileValidator.php b/src/vendor/symfony/validator/Constraints/FileValidator.php new file mode 100644 index 0000000..101330f --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/FileValidator.php @@ -0,0 +1,252 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\HttpFoundation\File\File as FileObject; +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\Mime\MimeTypes; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\LogicException; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; +use Symfony\Component\Validator\Exception\UnexpectedValueException; + +/** + * @author Bernhard Schussek + */ +class FileValidator extends ConstraintValidator +{ + public const KB_BYTES = 1000; + public const MB_BYTES = 1000000; + public const KIB_BYTES = 1024; + public const MIB_BYTES = 1048576; + + private const SUFFICES = [ + 1 => 'bytes', + self::KB_BYTES => 'kB', + self::MB_BYTES => 'MB', + self::KIB_BYTES => 'KiB', + self::MIB_BYTES => 'MiB', + ]; + + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof File) { + throw new UnexpectedTypeException($constraint, File::class); + } + + if (null === $value || '' === $value) { + return; + } + + if ($value instanceof UploadedFile && !$value->isValid()) { + switch ($value->getError()) { + case \UPLOAD_ERR_INI_SIZE: + $iniLimitSize = UploadedFile::getMaxFilesize(); + if ($constraint->maxSize && $constraint->maxSize < $iniLimitSize) { + $limitInBytes = $constraint->maxSize; + $binaryFormat = $constraint->binaryFormat; + } else { + $limitInBytes = $iniLimitSize; + $binaryFormat = $constraint->binaryFormat ?? true; + } + + [, $limitAsString, $suffix] = $this->factorizeSizes(0, $limitInBytes, $binaryFormat); + $this->context->buildViolation($constraint->uploadIniSizeErrorMessage) + ->setParameter('{{ limit }}', $limitAsString) + ->setParameter('{{ suffix }}', $suffix) + ->setCode((string) \UPLOAD_ERR_INI_SIZE) + ->addViolation(); + + return; + case \UPLOAD_ERR_FORM_SIZE: + $this->context->buildViolation($constraint->uploadFormSizeErrorMessage) + ->setCode((string) \UPLOAD_ERR_FORM_SIZE) + ->addViolation(); + + return; + case \UPLOAD_ERR_PARTIAL: + $this->context->buildViolation($constraint->uploadPartialErrorMessage) + ->setCode((string) \UPLOAD_ERR_PARTIAL) + ->addViolation(); + + return; + case \UPLOAD_ERR_NO_FILE: + $this->context->buildViolation($constraint->uploadNoFileErrorMessage) + ->setCode((string) \UPLOAD_ERR_NO_FILE) + ->addViolation(); + + return; + case \UPLOAD_ERR_NO_TMP_DIR: + $this->context->buildViolation($constraint->uploadNoTmpDirErrorMessage) + ->setCode((string) \UPLOAD_ERR_NO_TMP_DIR) + ->addViolation(); + + return; + case \UPLOAD_ERR_CANT_WRITE: + $this->context->buildViolation($constraint->uploadCantWriteErrorMessage) + ->setCode((string) \UPLOAD_ERR_CANT_WRITE) + ->addViolation(); + + return; + case \UPLOAD_ERR_EXTENSION: + $this->context->buildViolation($constraint->uploadExtensionErrorMessage) + ->setCode((string) \UPLOAD_ERR_EXTENSION) + ->addViolation(); + + return; + default: + $this->context->buildViolation($constraint->uploadErrorMessage) + ->setCode((string) $value->getError()) + ->addViolation(); + + return; + } + } + + if (!\is_scalar($value) && !$value instanceof FileObject && !(\is_object($value) && method_exists($value, '__toString'))) { + throw new UnexpectedValueException($value, 'string'); + } + + $path = $value instanceof FileObject ? $value->getPathname() : (string) $value; + + if (!is_file($path)) { + $this->context->buildViolation($constraint->notFoundMessage) + ->setParameter('{{ file }}', $this->formatValue($path)) + ->setCode(File::NOT_FOUND_ERROR) + ->addViolation(); + + return; + } + + if (!is_readable($path)) { + $this->context->buildViolation($constraint->notReadableMessage) + ->setParameter('{{ file }}', $this->formatValue($path)) + ->setCode(File::NOT_READABLE_ERROR) + ->addViolation(); + + return; + } + + $sizeInBytes = filesize($path); + $basename = $value instanceof UploadedFile ? $value->getClientOriginalName() : basename($path); + + if (0 === $sizeInBytes) { + $this->context->buildViolation($constraint->disallowEmptyMessage) + ->setParameter('{{ file }}', $this->formatValue($path)) + ->setParameter('{{ name }}', $this->formatValue($basename)) + ->setCode(File::EMPTY_ERROR) + ->addViolation(); + + return; + } + + if ($constraint->maxSize) { + $limitInBytes = $constraint->maxSize; + + if ($sizeInBytes > $limitInBytes) { + [$sizeAsString, $limitAsString, $suffix] = $this->factorizeSizes($sizeInBytes, $limitInBytes, $constraint->binaryFormat); + $this->context->buildViolation($constraint->maxSizeMessage) + ->setParameter('{{ file }}', $this->formatValue($path)) + ->setParameter('{{ size }}', $sizeAsString) + ->setParameter('{{ limit }}', $limitAsString) + ->setParameter('{{ suffix }}', $suffix) + ->setParameter('{{ name }}', $this->formatValue($basename)) + ->setCode(File::TOO_LARGE_ERROR) + ->addViolation(); + + return; + } + } + + if ($constraint->mimeTypes) { + if ($value instanceof FileObject) { + $mime = $value->getMimeType(); + } elseif (class_exists(MimeTypes::class)) { + $mime = MimeTypes::getDefault()->guessMimeType($path); + } elseif (!class_exists(FileObject::class)) { + throw new LogicException('You cannot validate the mime-type of files as the Mime component is not installed. Try running "composer require symfony/mime".'); + } else { + $mime = (new FileObject($value))->getMimeType(); + } + + $mimeTypes = (array) $constraint->mimeTypes; + + foreach ($mimeTypes as $mimeType) { + if ($mimeType === $mime) { + return; + } + + if ($discrete = strstr($mimeType, '/*', true)) { + if (strstr($mime, '/', true) === $discrete) { + return; + } + } + } + + $this->context->buildViolation($constraint->mimeTypesMessage) + ->setParameter('{{ file }}', $this->formatValue($path)) + ->setParameter('{{ type }}', $this->formatValue($mime)) + ->setParameter('{{ types }}', $this->formatValues($mimeTypes)) + ->setParameter('{{ name }}', $this->formatValue($basename)) + ->setCode(File::INVALID_MIME_TYPE_ERROR) + ->addViolation(); + } + } + + private static function moreDecimalsThan(string $double, int $numberOfDecimals): bool + { + return \strlen($double) > \strlen(round($double, $numberOfDecimals)); + } + + /** + * Convert the limit to the smallest possible number + * (i.e. try "MB", then "kB", then "bytes"). + * + * @param int|float $limit + */ + private function factorizeSizes(int $size, $limit, bool $binaryFormat): array + { + if ($binaryFormat) { + $coef = self::MIB_BYTES; + $coefFactor = self::KIB_BYTES; + } else { + $coef = self::MB_BYTES; + $coefFactor = self::KB_BYTES; + } + + $limitAsString = (string) ($limit / $coef); + + // Restrict the limit to 2 decimals (without rounding! we + // need the precise value) + while (self::moreDecimalsThan($limitAsString, 2)) { + $coef /= $coefFactor; + $limitAsString = (string) ($limit / $coef); + } + + // Convert size to the same measure, but round to 2 decimals + $sizeAsString = (string) round($size / $coef, 2); + + // If the size and limit produce the same string output + // (due to rounding), reduce the coefficient + while ($sizeAsString === $limitAsString) { + $coef /= $coefFactor; + $limitAsString = (string) ($limit / $coef); + $sizeAsString = (string) round($size / $coef, 2); + } + + return [$sizeAsString, $limitAsString, self::SUFFICES[$coef]]; + } +} diff --git a/src/vendor/symfony/validator/Constraints/GreaterThan.php b/src/vendor/symfony/validator/Constraints/GreaterThan.php new file mode 100644 index 0000000..4164875 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/GreaterThan.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Daniel Holmes + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class GreaterThan extends AbstractComparison +{ + public const TOO_LOW_ERROR = '778b7ae0-84d3-481a-9dec-35fdb64b1d78'; + + protected static $errorNames = [ + self::TOO_LOW_ERROR => 'TOO_LOW_ERROR', + ]; + + public $message = 'This value should be greater than {{ compared_value }}.'; +} diff --git a/src/vendor/symfony/validator/Constraints/GreaterThanOrEqual.php b/src/vendor/symfony/validator/Constraints/GreaterThanOrEqual.php new file mode 100644 index 0000000..86ff44e --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/GreaterThanOrEqual.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Daniel Holmes + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class GreaterThanOrEqual extends AbstractComparison +{ + public const TOO_LOW_ERROR = 'ea4e51d1-3342-48bd-87f1-9e672cd90cad'; + + protected static $errorNames = [ + self::TOO_LOW_ERROR => 'TOO_LOW_ERROR', + ]; + + public $message = 'This value should be greater than or equal to {{ compared_value }}.'; +} diff --git a/src/vendor/symfony/validator/Constraints/GreaterThanOrEqualValidator.php b/src/vendor/symfony/validator/Constraints/GreaterThanOrEqualValidator.php new file mode 100644 index 0000000..290408a --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/GreaterThanOrEqualValidator.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +/** + * Validates values are greater than or equal to the previous (>=). + * + * @author Daniel Holmes + * @author Bernhard Schussek + */ +class GreaterThanOrEqualValidator extends AbstractComparisonValidator +{ + /** + * {@inheritdoc} + */ + protected function compareValues($value1, $value2) + { + return null === $value2 || $value1 >= $value2; + } + + /** + * {@inheritdoc} + */ + protected function getErrorCode() + { + return GreaterThanOrEqual::TOO_LOW_ERROR; + } +} diff --git a/src/vendor/symfony/validator/Constraints/GreaterThanValidator.php b/src/vendor/symfony/validator/Constraints/GreaterThanValidator.php new file mode 100644 index 0000000..062503a --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/GreaterThanValidator.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +/** + * Validates values are greater than the previous (>). + * + * @author Daniel Holmes + * @author Bernhard Schussek + */ +class GreaterThanValidator extends AbstractComparisonValidator +{ + /** + * {@inheritdoc} + */ + protected function compareValues($value1, $value2) + { + return null === $value2 || $value1 > $value2; + } + + /** + * {@inheritdoc} + */ + protected function getErrorCode() + { + return GreaterThan::TOO_LOW_ERROR; + } +} diff --git a/src/vendor/symfony/validator/Constraints/GroupSequence.php b/src/vendor/symfony/validator/Constraints/GroupSequence.php new file mode 100644 index 0000000..522c5fd --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/GroupSequence.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +/** + * A sequence of validation groups. + * + * When validating a group sequence, each group will only be validated if all + * of the previous groups in the sequence succeeded. For example: + * + * $validator->validate($address, null, new GroupSequence(['Basic', 'Strict'])); + * + * In the first step, all constraints that belong to the group "Basic" will be + * validated. If none of the constraints fail, the validator will then validate + * the constraints in group "Strict". This is useful, for example, if "Strict" + * contains expensive checks that require a lot of CPU or slow, external + * services. You usually don't want to run expensive checks if any of the cheap + * checks fail. + * + * When adding metadata to a class, you can override the "Default" group of + * that class with a group sequence: + * /** + * * @GroupSequence({"Address", "Strict"}) + * *\/ + * class Address + * { + * // ... + * } + * + * Whenever you validate that object in the "Default" group, the group sequence + * will be validated: + * + * $validator->validate($address); + * + * If you want to execute the constraints of the "Default" group for a class + * with an overridden default group, pass the class name as group name instead: + * + * $validator->validate($address, null, "Address") + * + * @Annotation + * @Target({"CLASS", "ANNOTATION"}) + * + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_CLASS)] +class GroupSequence +{ + /** + * The groups in the sequence. + * + * @var array + */ + public $groups; + + /** + * The group in which cascaded objects are validated when validating + * this sequence. + * + * By default, cascaded objects are validated in each of the groups of + * the sequence. + * + * If a class has a group sequence attached, that sequence replaces the + * "Default" group. When validating that class in the "Default" group, the + * group sequence is used instead, but still the "Default" group should be + * cascaded to other objects. + * + * @var string|GroupSequence + */ + public $cascadedGroup; + + /** + * Creates a new group sequence. + * + * @param array $groups The groups in the sequence + */ + public function __construct(array $groups) + { + // Support for Doctrine annotations + $this->groups = $groups['value'] ?? $groups; + } +} diff --git a/src/vendor/symfony/validator/Constraints/GroupSequenceProvider.php b/src/vendor/symfony/validator/Constraints/GroupSequenceProvider.php new file mode 100644 index 0000000..489a449 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/GroupSequenceProvider.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +/** + * Annotation to define a group sequence provider. + * + * @Annotation + * @Target({"CLASS", "ANNOTATION"}) + * + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_CLASS)] +class GroupSequenceProvider +{ +} diff --git a/src/vendor/symfony/validator/Constraints/Hostname.php b/src/vendor/symfony/validator/Constraints/Hostname.php new file mode 100644 index 0000000..d0d02d1 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Hostname.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Dmitrii Poddubnyi + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class Hostname extends Constraint +{ + public const INVALID_HOSTNAME_ERROR = '7057ffdb-0af4-4f7e-bd5e-e9acfa6d7a2d'; + + protected static $errorNames = [ + self::INVALID_HOSTNAME_ERROR => 'INVALID_HOSTNAME_ERROR', + ]; + + public $message = 'This value is not a valid hostname.'; + public $requireTld = true; + + public function __construct( + array $options = null, + string $message = null, + bool $requireTld = null, + array $groups = null, + $payload = null + ) { + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + $this->requireTld = $requireTld ?? $this->requireTld; + } +} diff --git a/src/vendor/symfony/validator/Constraints/HostnameValidator.php b/src/vendor/symfony/validator/Constraints/HostnameValidator.php new file mode 100644 index 0000000..69f7bd0 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/HostnameValidator.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; +use Symfony\Component\Validator\Exception\UnexpectedValueException; + +/** + * @author Dmitrii Poddubnyi + */ +class HostnameValidator extends ConstraintValidator +{ + /** + * https://tools.ietf.org/html/rfc2606. + */ + private const RESERVED_TLDS = [ + 'example', + 'invalid', + 'localhost', + 'test', + ]; + + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Hostname) { + throw new UnexpectedTypeException($constraint, Hostname::class); + } + + if (null === $value || '' === $value) { + return; + } + + if (!\is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { + throw new UnexpectedValueException($value, 'string'); + } + + $value = (string) $value; + if ('' === $value) { + return; + } + if (!$this->isValid($value) || ($constraint->requireTld && !$this->hasValidTld($value))) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Hostname::INVALID_HOSTNAME_ERROR) + ->addViolation(); + } + } + + private function isValid(string $domain): bool + { + return false !== filter_var($domain, \FILTER_VALIDATE_DOMAIN, \FILTER_FLAG_HOSTNAME); + } + + private function hasValidTld(string $domain): bool + { + return false !== strpos($domain, '.') && !\in_array(substr($domain, strrpos($domain, '.') + 1), self::RESERVED_TLDS, true); + } +} diff --git a/src/vendor/symfony/validator/Constraints/Iban.php b/src/vendor/symfony/validator/Constraints/Iban.php new file mode 100644 index 0000000..2f7a61e --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Iban.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Manuel Reinhard + * @author Michael Schummel + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class Iban extends Constraint +{ + public const INVALID_COUNTRY_CODE_ERROR = 'de78ee2c-bd50-44e2-aec8-3d8228aeadb9'; + public const INVALID_CHARACTERS_ERROR = '8d3d85e4-784f-4719-a5bc-d9e40d45a3a5'; + public const CHECKSUM_FAILED_ERROR = 'b9401321-f9bf-4dcb-83c1-f31094440795'; + public const INVALID_FORMAT_ERROR = 'c8d318f1-2ecc-41ba-b983-df70d225cf5a'; + public const NOT_SUPPORTED_COUNTRY_CODE_ERROR = 'e2c259f3-4b46-48e6-b72e-891658158ec8'; + + protected static $errorNames = [ + self::INVALID_COUNTRY_CODE_ERROR => 'INVALID_COUNTRY_CODE_ERROR', + self::INVALID_CHARACTERS_ERROR => 'INVALID_CHARACTERS_ERROR', + self::CHECKSUM_FAILED_ERROR => 'CHECKSUM_FAILED_ERROR', + self::INVALID_FORMAT_ERROR => 'INVALID_FORMAT_ERROR', + self::NOT_SUPPORTED_COUNTRY_CODE_ERROR => 'NOT_SUPPORTED_COUNTRY_CODE_ERROR', + ]; + + public $message = 'This is not a valid International Bank Account Number (IBAN).'; + + public function __construct(array $options = null, string $message = null, array $groups = null, $payload = null) + { + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + } +} diff --git a/src/vendor/symfony/validator/Constraints/IbanValidator.php b/src/vendor/symfony/validator/Constraints/IbanValidator.php new file mode 100644 index 0000000..173cb66 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/IbanValidator.php @@ -0,0 +1,285 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; +use Symfony\Component\Validator\Exception\UnexpectedValueException; + +/** + * @author Manuel Reinhard + * @author Michael Schummel + * @author Bernhard Schussek + */ +class IbanValidator extends ConstraintValidator +{ + /** + * IBAN country specific formats. + * + * The first 2 characters from an IBAN format are the two-character ISO country code. + * The following 2 characters represent the check digits calculated from the rest of the IBAN characters. + * The rest are up to thirty alphanumeric characters for + * a BBAN (Basic Bank Account Number) which has a fixed length per country and, + * included within it, a bank identifier with a fixed position and a fixed length per country + * + * @see Resources/bin/sync-iban-formats.php + * @see https://www.swift.com/swift-resource/11971/download?language=en + * @see https://en.wikipedia.org/wiki/International_Bank_Account_Number + */ + private const FORMATS = [ + // auto-generated + 'AD' => 'AD\d{2}\d{4}\d{4}[\dA-Z]{12}', // Andorra + 'AE' => 'AE\d{2}\d{3}\d{16}', // United Arab Emirates (The) + 'AL' => 'AL\d{2}\d{8}[\dA-Z]{16}', // Albania + 'AO' => 'AO\d{2}\d{21}', // Angola + 'AT' => 'AT\d{2}\d{5}\d{11}', // Austria + 'AX' => 'FI\d{2}\d{3}\d{11}', // Finland + 'AZ' => 'AZ\d{2}[A-Z]{4}[\dA-Z]{20}', // Azerbaijan + 'BA' => 'BA\d{2}\d{3}\d{3}\d{8}\d{2}', // Bosnia and Herzegovina + 'BE' => 'BE\d{2}\d{3}\d{7}\d{2}', // Belgium + 'BF' => 'BF\d{2}[\dA-Z]{2}\d{22}', // Burkina Faso + 'BG' => 'BG\d{2}[A-Z]{4}\d{4}\d{2}[\dA-Z]{8}', // Bulgaria + 'BH' => 'BH\d{2}[A-Z]{4}[\dA-Z]{14}', // Bahrain + 'BI' => 'BI\d{2}\d{5}\d{5}\d{11}\d{2}', // Burundi + 'BJ' => 'BJ\d{2}[\dA-Z]{2}\d{22}', // Benin + 'BL' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // France + 'BR' => 'BR\d{2}\d{8}\d{5}\d{10}[A-Z]{1}[\dA-Z]{1}', // Brazil + 'BY' => 'BY\d{2}[\dA-Z]{4}\d{4}[\dA-Z]{16}', // Republic of Belarus + 'CF' => 'CF\d{2}\d{23}', // Central African Republic + 'CG' => 'CG\d{2}\d{23}', // Congo, Republic of the + 'CH' => 'CH\d{2}\d{5}[\dA-Z]{12}', // Switzerland + 'CI' => 'CI\d{2}[A-Z]{1}\d{23}', // Côte d'Ivoire + 'CM' => 'CM\d{2}\d{23}', // Cameroon + 'CR' => 'CR\d{2}\d{4}\d{14}', // Costa Rica + 'CV' => 'CV\d{2}\d{21}', // Cabo Verde + 'CY' => 'CY\d{2}\d{3}\d{5}[\dA-Z]{16}', // Cyprus + 'CZ' => 'CZ\d{2}\d{4}\d{6}\d{10}', // Czechia + 'DE' => 'DE\d{2}\d{8}\d{10}', // Germany + 'DJ' => 'DJ\d{2}\d{5}\d{5}\d{11}\d{2}', // Djibouti + 'DK' => 'DK\d{2}\d{4}\d{9}\d{1}', // Denmark + 'DO' => 'DO\d{2}[\dA-Z]{4}\d{20}', // Dominican Republic + 'DZ' => 'DZ\d{2}\d{22}', // Algeria + 'EE' => 'EE\d{2}\d{2}\d{2}\d{11}\d{1}', // Estonia + 'EG' => 'EG\d{2}\d{4}\d{4}\d{17}', // Egypt + 'ES' => 'ES\d{2}\d{4}\d{4}\d{1}\d{1}\d{10}', // Spain + 'FI' => 'FI\d{2}\d{3}\d{11}', // Finland + 'FO' => 'FO\d{2}\d{4}\d{9}\d{1}', // Faroe Islands + 'FR' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // France + 'GA' => 'GA\d{2}\d{23}', // Gabon + 'GB' => 'GB\d{2}[A-Z]{4}\d{6}\d{8}', // United Kingdom + 'GE' => 'GE\d{2}[A-Z]{2}\d{16}', // Georgia + 'GF' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // France + 'GG' => 'GB\d{2}[A-Z]{4}\d{6}\d{8}', // United Kingdom + 'GI' => 'GI\d{2}[A-Z]{4}[\dA-Z]{15}', // Gibraltar + 'GL' => 'GL\d{2}\d{4}\d{9}\d{1}', // Greenland + 'GP' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // France + 'GQ' => 'GQ\d{2}\d{23}', // Equatorial Guinea + 'GR' => 'GR\d{2}\d{3}\d{4}[\dA-Z]{16}', // Greece + 'GT' => 'GT\d{2}[\dA-Z]{4}[\dA-Z]{20}', // Guatemala + 'GW' => 'GW\d{2}[\dA-Z]{2}\d{19}', // Guinea-Bissau + 'HN' => 'HN\d{2}[A-Z]{4}\d{20}', // Honduras + 'HR' => 'HR\d{2}\d{7}\d{10}', // Croatia + 'HU' => 'HU\d{2}\d{3}\d{4}\d{1}\d{15}\d{1}', // Hungary + 'IE' => 'IE\d{2}[A-Z]{4}\d{6}\d{8}', // Ireland + 'IL' => 'IL\d{2}\d{3}\d{3}\d{13}', // Israel + 'IM' => 'GB\d{2}[A-Z]{4}\d{6}\d{8}', // United Kingdom + 'IQ' => 'IQ\d{2}[A-Z]{4}\d{3}\d{12}', // Iraq + 'IR' => 'IR\d{2}\d{22}', // Iran + 'IS' => 'IS\d{2}\d{4}\d{2}\d{6}\d{10}', // Iceland + 'IT' => 'IT\d{2}[A-Z]{1}\d{5}\d{5}[\dA-Z]{12}', // Italy + 'JE' => 'GB\d{2}[A-Z]{4}\d{6}\d{8}', // United Kingdom + 'JO' => 'JO\d{2}[A-Z]{4}\d{4}[\dA-Z]{18}', // Jordan + 'KM' => 'KM\d{2}\d{23}', // Comoros + 'KW' => 'KW\d{2}[A-Z]{4}[\dA-Z]{22}', // Kuwait + 'KZ' => 'KZ\d{2}\d{3}[\dA-Z]{13}', // Kazakhstan + 'LB' => 'LB\d{2}\d{4}[\dA-Z]{20}', // Lebanon + 'LC' => 'LC\d{2}[A-Z]{4}[\dA-Z]{24}', // Saint Lucia + 'LI' => 'LI\d{2}\d{5}[\dA-Z]{12}', // Liechtenstein + 'LT' => 'LT\d{2}\d{5}\d{11}', // Lithuania + 'LU' => 'LU\d{2}\d{3}[\dA-Z]{13}', // Luxembourg + 'LV' => 'LV\d{2}[A-Z]{4}[\dA-Z]{13}', // Latvia + 'LY' => 'LY\d{2}\d{3}\d{3}\d{15}', // Libya + 'MA' => 'MA\d{2}\d{24}', // Morocco + 'MC' => 'MC\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // Monaco + 'MD' => 'MD\d{2}[\dA-Z]{2}[\dA-Z]{18}', // Moldova + 'ME' => 'ME\d{2}\d{3}\d{13}\d{2}', // Montenegro + 'MF' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // France + 'MG' => 'MG\d{2}\d{23}', // Madagascar + 'MK' => 'MK\d{2}\d{3}[\dA-Z]{10}\d{2}', // Macedonia + 'ML' => 'ML\d{2}[\dA-Z]{2}\d{22}', // Mali + 'MQ' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // France + 'MR' => 'MR\d{2}\d{5}\d{5}\d{11}\d{2}', // Mauritania + 'MT' => 'MT\d{2}[A-Z]{4}\d{5}[\dA-Z]{18}', // Malta + 'MU' => 'MU\d{2}[A-Z]{4}\d{2}\d{2}\d{12}\d{3}[A-Z]{3}', // Mauritius + 'MZ' => 'MZ\d{2}\d{21}', // Mozambique + 'NC' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // France + 'NE' => 'NE\d{2}[A-Z]{2}\d{22}', // Niger + 'NI' => 'NI\d{2}[A-Z]{4}\d{24}', // Nicaragua + 'NL' => 'NL\d{2}[A-Z]{4}\d{10}', // Netherlands (The) + 'NO' => 'NO\d{2}\d{4}\d{6}\d{1}', // Norway + 'PF' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // France + 'PK' => 'PK\d{2}[A-Z]{4}[\dA-Z]{16}', // Pakistan + 'PL' => 'PL\d{2}\d{8}\d{16}', // Poland + 'PM' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // France + 'PS' => 'PS\d{2}[A-Z]{4}[\dA-Z]{21}', // Palestine, State of + 'PT' => 'PT\d{2}\d{4}\d{4}\d{11}\d{2}', // Portugal + 'QA' => 'QA\d{2}[A-Z]{4}[\dA-Z]{21}', // Qatar + 'RE' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // France + 'RO' => 'RO\d{2}[A-Z]{4}[\dA-Z]{16}', // Romania + 'RS' => 'RS\d{2}\d{3}\d{13}\d{2}', // Serbia + 'RU' => 'RU\d{2}\d{9}\d{5}[\dA-Z]{15}', // Russia + 'SA' => 'SA\d{2}\d{2}[\dA-Z]{18}', // Saudi Arabia + 'SC' => 'SC\d{2}[A-Z]{4}\d{2}\d{2}\d{16}[A-Z]{3}', // Seychelles + 'SD' => 'SD\d{2}\d{2}\d{12}', // Sudan + 'SE' => 'SE\d{2}\d{3}\d{16}\d{1}', // Sweden + 'SI' => 'SI\d{2}\d{5}\d{8}\d{2}', // Slovenia + 'SK' => 'SK\d{2}\d{4}\d{6}\d{10}', // Slovakia + 'SM' => 'SM\d{2}[A-Z]{1}\d{5}\d{5}[\dA-Z]{12}', // San Marino + 'SN' => 'SN\d{2}[A-Z]{2}\d{22}', // Senegal + 'SO' => 'SO\d{2}\d{4}\d{3}\d{12}', // Somalia + 'ST' => 'ST\d{2}\d{4}\d{4}\d{11}\d{2}', // Sao Tome and Principe + 'SV' => 'SV\d{2}[A-Z]{4}\d{20}', // El Salvador + 'TD' => 'TD\d{2}\d{23}', // Chad + 'TF' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // France + 'TG' => 'TG\d{2}[A-Z]{2}\d{22}', // Togo + 'TL' => 'TL\d{2}\d{3}\d{14}\d{2}', // Timor-Leste + 'TN' => 'TN\d{2}\d{2}\d{3}\d{13}\d{2}', // Tunisia + 'TR' => 'TR\d{2}\d{5}\d{1}[\dA-Z]{16}', // Turkey + 'UA' => 'UA\d{2}\d{6}[\dA-Z]{19}', // Ukraine + 'VA' => 'VA\d{2}\d{3}\d{15}', // Vatican City State + 'VG' => 'VG\d{2}[A-Z]{4}\d{16}', // Virgin Islands + 'WF' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // France + 'XK' => 'XK\d{2}\d{4}\d{10}\d{2}', // Kosovo + 'YT' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // France + ]; + + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Iban) { + throw new UnexpectedTypeException($constraint, Iban::class); + } + + if (null === $value || '' === $value) { + return; + } + + if (!\is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { + throw new UnexpectedValueException($value, 'string'); + } + + $value = (string) $value; + + // Remove spaces and convert to uppercase + $canonicalized = str_replace(' ', '', strtoupper($value)); + + // The IBAN must contain only digits and characters... + if (!ctype_alnum($canonicalized)) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Iban::INVALID_CHARACTERS_ERROR) + ->addViolation(); + + return; + } + + // ...start with a two-letter country code + $countryCode = substr($canonicalized, 0, 2); + + if (!ctype_alpha($countryCode)) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Iban::INVALID_COUNTRY_CODE_ERROR) + ->addViolation(); + + return; + } + + // ...have a format available + if (!\array_key_exists($countryCode, self::FORMATS)) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Iban::NOT_SUPPORTED_COUNTRY_CODE_ERROR) + ->addViolation(); + + return; + } + + // ...and have a valid format + if (!preg_match('/^'.self::FORMATS[$countryCode].'$/', $canonicalized) + ) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Iban::INVALID_FORMAT_ERROR) + ->addViolation(); + + return; + } + + // Move the first four characters to the end + // e.g. CH93 0076 2011 6238 5295 7 + // -> 0076 2011 6238 5295 7 CH93 + $canonicalized = substr($canonicalized, 4).substr($canonicalized, 0, 4); + + // Convert all remaining letters to their ordinals + // The result is an integer, which is too large for PHP's int + // data type, so we store it in a string instead. + // e.g. 0076 2011 6238 5295 7 CH93 + // -> 0076 2011 6238 5295 7 121893 + $checkSum = self::toBigInt($canonicalized); + + // Do a modulo-97 operation on the large integer + // We cannot use PHP's modulo operator, so we calculate the + // modulo step-wisely instead + if (1 !== self::bigModulo97($checkSum)) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Iban::CHECKSUM_FAILED_ERROR) + ->addViolation(); + } + } + + private static function toBigInt(string $string): string + { + $chars = str_split($string); + $bigInt = ''; + + foreach ($chars as $char) { + // Convert uppercase characters to ordinals, starting with 10 for "A" + if (ctype_upper($char)) { + $bigInt .= (\ord($char) - 55); + + continue; + } + + // Simply append digits + $bigInt .= $char; + } + + return $bigInt; + } + + private static function bigModulo97(string $bigInt): int + { + $parts = str_split($bigInt, 7); + $rest = 0; + + foreach ($parts as $part) { + $rest = ($rest.$part) % 97; + } + + return $rest; + } +} diff --git a/src/vendor/symfony/validator/Constraints/IdenticalTo.php b/src/vendor/symfony/validator/Constraints/IdenticalTo.php new file mode 100644 index 0000000..b3d6b92 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/IdenticalTo.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Daniel Holmes + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class IdenticalTo extends AbstractComparison +{ + public const NOT_IDENTICAL_ERROR = '2a8cc50f-58a2-4536-875e-060a2ce69ed5'; + + protected static $errorNames = [ + self::NOT_IDENTICAL_ERROR => 'NOT_IDENTICAL_ERROR', + ]; + + public $message = 'This value should be identical to {{ compared_value_type }} {{ compared_value }}.'; +} diff --git a/src/vendor/symfony/validator/Constraints/IdenticalToValidator.php b/src/vendor/symfony/validator/Constraints/IdenticalToValidator.php new file mode 100644 index 0000000..304f71f --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/IdenticalToValidator.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +/** + * Validates values are identical (===). + * + * @author Daniel Holmes + * @author Bernhard Schussek + */ +class IdenticalToValidator extends AbstractComparisonValidator +{ + /** + * {@inheritdoc} + */ + protected function compareValues($value1, $value2) + { + return $value1 === $value2; + } + + /** + * {@inheritdoc} + */ + protected function getErrorCode() + { + return IdenticalTo::NOT_IDENTICAL_ERROR; + } +} diff --git a/src/vendor/symfony/validator/Constraints/Image.php b/src/vendor/symfony/validator/Constraints/Image.php new file mode 100644 index 0000000..83fc9f9 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Image.php @@ -0,0 +1,193 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Benjamin Dulau + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class Image extends File +{ + public const SIZE_NOT_DETECTED_ERROR = '6d55c3f4-e58e-4fe3-91ee-74b492199956'; + public const TOO_WIDE_ERROR = '7f87163d-878f-47f5-99ba-a8eb723a1ab2'; + public const TOO_NARROW_ERROR = '9afbd561-4f90-4a27-be62-1780fc43604a'; + public const TOO_HIGH_ERROR = '7efae81c-4877-47ba-aa65-d01ccb0d4645'; + public const TOO_LOW_ERROR = 'aef0cb6a-c07f-4894-bc08-1781420d7b4c'; + public const TOO_FEW_PIXEL_ERROR = '1b06b97d-ae48-474e-978f-038a74854c43'; + public const TOO_MANY_PIXEL_ERROR = 'ee0804e8-44db-4eac-9775-be91aaf72ce1'; + public const RATIO_TOO_BIG_ERROR = '70cafca6-168f-41c9-8c8c-4e47a52be643'; + public const RATIO_TOO_SMALL_ERROR = '59b8c6ef-bcf2-4ceb-afff-4642ed92f12e'; + public const SQUARE_NOT_ALLOWED_ERROR = '5d41425b-facb-47f7-a55a-de9fbe45cb46'; + public const LANDSCAPE_NOT_ALLOWED_ERROR = '6f895685-7cf2-4d65-b3da-9029c5581d88'; + public const PORTRAIT_NOT_ALLOWED_ERROR = '65608156-77da-4c79-a88c-02ef6d18c782'; + public const CORRUPTED_IMAGE_ERROR = '5d4163f3-648f-4e39-87fd-cc5ea7aad2d1'; + + // Include the mapping from the base class + + protected static $errorNames = [ + self::NOT_FOUND_ERROR => 'NOT_FOUND_ERROR', + self::NOT_READABLE_ERROR => 'NOT_READABLE_ERROR', + self::EMPTY_ERROR => 'EMPTY_ERROR', + self::TOO_LARGE_ERROR => 'TOO_LARGE_ERROR', + self::INVALID_MIME_TYPE_ERROR => 'INVALID_MIME_TYPE_ERROR', + self::SIZE_NOT_DETECTED_ERROR => 'SIZE_NOT_DETECTED_ERROR', + self::TOO_WIDE_ERROR => 'TOO_WIDE_ERROR', + self::TOO_NARROW_ERROR => 'TOO_NARROW_ERROR', + self::TOO_HIGH_ERROR => 'TOO_HIGH_ERROR', + self::TOO_LOW_ERROR => 'TOO_LOW_ERROR', + self::TOO_FEW_PIXEL_ERROR => 'TOO_FEW_PIXEL_ERROR', + self::TOO_MANY_PIXEL_ERROR => 'TOO_MANY_PIXEL_ERROR', + self::RATIO_TOO_BIG_ERROR => 'RATIO_TOO_BIG_ERROR', + self::RATIO_TOO_SMALL_ERROR => 'RATIO_TOO_SMALL_ERROR', + self::SQUARE_NOT_ALLOWED_ERROR => 'SQUARE_NOT_ALLOWED_ERROR', + self::LANDSCAPE_NOT_ALLOWED_ERROR => 'LANDSCAPE_NOT_ALLOWED_ERROR', + self::PORTRAIT_NOT_ALLOWED_ERROR => 'PORTRAIT_NOT_ALLOWED_ERROR', + self::CORRUPTED_IMAGE_ERROR => 'CORRUPTED_IMAGE_ERROR', + ]; + + public $mimeTypes = 'image/*'; + public $minWidth; + public $maxWidth; + public $maxHeight; + public $minHeight; + public $maxRatio; + public $minRatio; + public $minPixels; + public $maxPixels; + public $allowSquare = true; + public $allowLandscape = true; + public $allowPortrait = true; + public $detectCorrupted = false; + + // The constant for a wrong MIME type is taken from the parent class. + public $mimeTypesMessage = 'This file is not a valid image.'; + public $sizeNotDetectedMessage = 'The size of the image could not be detected.'; + public $maxWidthMessage = 'The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px.'; + public $minWidthMessage = 'The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px.'; + public $maxHeightMessage = 'The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px.'; + public $minHeightMessage = 'The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px.'; + public $minPixelsMessage = 'The image has too few pixels ({{ pixels }} pixels). Minimum amount expected is {{ min_pixels }} pixels.'; + public $maxPixelsMessage = 'The image has too many pixels ({{ pixels }} pixels). Maximum amount expected is {{ max_pixels }} pixels.'; + public $maxRatioMessage = 'The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}.'; + public $minRatioMessage = 'The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}.'; + public $allowSquareMessage = 'The image is square ({{ width }}x{{ height }}px). Square images are not allowed.'; + public $allowLandscapeMessage = 'The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed.'; + public $allowPortraitMessage = 'The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed.'; + public $corruptedMessage = 'The image file is corrupted.'; + + /** + * {@inheritdoc} + * + * @param int|float $maxRatio + * @param int|float $minRatio + * @param int|float $minPixels + * @param int|float $maxPixels + */ + public function __construct( + array $options = null, + $maxSize = null, + bool $binaryFormat = null, + array $mimeTypes = null, + int $minWidth = null, + int $maxWidth = null, + int $maxHeight = null, + int $minHeight = null, + $maxRatio = null, + $minRatio = null, + $minPixels = null, + $maxPixels = null, + bool $allowSquare = null, + bool $allowLandscape = null, + bool $allowPortrait = null, + bool $detectCorrupted = null, + string $notFoundMessage = null, + string $notReadableMessage = null, + string $maxSizeMessage = null, + string $mimeTypesMessage = null, + string $disallowEmptyMessage = null, + string $uploadIniSizeErrorMessage = null, + string $uploadFormSizeErrorMessage = null, + string $uploadPartialErrorMessage = null, + string $uploadNoFileErrorMessage = null, + string $uploadNoTmpDirErrorMessage = null, + string $uploadCantWriteErrorMessage = null, + string $uploadExtensionErrorMessage = null, + string $uploadErrorMessage = null, + string $sizeNotDetectedMessage = null, + string $maxWidthMessage = null, + string $minWidthMessage = null, + string $maxHeightMessage = null, + string $minHeightMessage = null, + string $minPixelsMessage = null, + string $maxPixelsMessage = null, + string $maxRatioMessage = null, + string $minRatioMessage = null, + string $allowSquareMessage = null, + string $allowLandscapeMessage = null, + string $allowPortraitMessage = null, + string $corruptedMessage = null, + array $groups = null, + $payload = null + ) { + parent::__construct( + $options, + $maxSize, + $binaryFormat, + $mimeTypes, + $notFoundMessage, + $notReadableMessage, + $maxSizeMessage, + $mimeTypesMessage, + $disallowEmptyMessage, + $uploadIniSizeErrorMessage, + $uploadFormSizeErrorMessage, + $uploadPartialErrorMessage, + $uploadNoFileErrorMessage, + $uploadNoTmpDirErrorMessage, + $uploadCantWriteErrorMessage, + $uploadExtensionErrorMessage, + $uploadErrorMessage, + $groups, + $payload + ); + + $this->minWidth = $minWidth ?? $this->minWidth; + $this->maxWidth = $maxWidth ?? $this->maxWidth; + $this->maxHeight = $maxHeight ?? $this->maxHeight; + $this->minHeight = $minHeight ?? $this->minHeight; + $this->maxRatio = $maxRatio ?? $this->maxRatio; + $this->minRatio = $minRatio ?? $this->minRatio; + $this->minPixels = $minPixels ?? $this->minPixels; + $this->maxPixels = $maxPixels ?? $this->maxPixels; + $this->allowSquare = $allowSquare ?? $this->allowSquare; + $this->allowLandscape = $allowLandscape ?? $this->allowLandscape; + $this->allowPortrait = $allowPortrait ?? $this->allowPortrait; + $this->detectCorrupted = $detectCorrupted ?? $this->detectCorrupted; + $this->sizeNotDetectedMessage = $sizeNotDetectedMessage ?? $this->sizeNotDetectedMessage; + $this->maxWidthMessage = $maxWidthMessage ?? $this->maxWidthMessage; + $this->minWidthMessage = $minWidthMessage ?? $this->minWidthMessage; + $this->maxHeightMessage = $maxHeightMessage ?? $this->maxHeightMessage; + $this->minHeightMessage = $minHeightMessage ?? $this->minHeightMessage; + $this->minPixelsMessage = $minPixelsMessage ?? $this->minPixelsMessage; + $this->maxPixelsMessage = $maxPixelsMessage ?? $this->maxPixelsMessage; + $this->maxRatioMessage = $maxRatioMessage ?? $this->maxRatioMessage; + $this->minRatioMessage = $minRatioMessage ?? $this->minRatioMessage; + $this->allowSquareMessage = $allowSquareMessage ?? $this->allowSquareMessage; + $this->allowLandscapeMessage = $allowLandscapeMessage ?? $this->allowLandscapeMessage; + $this->allowPortraitMessage = $allowPortraitMessage ?? $this->allowPortraitMessage; + $this->corruptedMessage = $corruptedMessage ?? $this->corruptedMessage; + } +} diff --git a/src/vendor/symfony/validator/Constraints/ImageValidator.php b/src/vendor/symfony/validator/Constraints/ImageValidator.php new file mode 100644 index 0000000..f199f1d --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/ImageValidator.php @@ -0,0 +1,237 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; +use Symfony\Component\Validator\Exception\LogicException; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; + +/** + * Validates whether a value is a valid image file and is valid + * against minWidth, maxWidth, minHeight and maxHeight constraints. + * + * @author Benjamin Dulau + * @author Bernhard Schussek + */ +class ImageValidator extends FileValidator +{ + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Image) { + throw new UnexpectedTypeException($constraint, Image::class); + } + + $violations = \count($this->context->getViolations()); + + parent::validate($value, $constraint); + + $failed = \count($this->context->getViolations()) !== $violations; + + if ($failed || null === $value || '' === $value) { + return; + } + + if (null === $constraint->minWidth && null === $constraint->maxWidth + && null === $constraint->minHeight && null === $constraint->maxHeight + && null === $constraint->minPixels && null === $constraint->maxPixels + && null === $constraint->minRatio && null === $constraint->maxRatio + && $constraint->allowSquare && $constraint->allowLandscape && $constraint->allowPortrait + && !$constraint->detectCorrupted) { + return; + } + + $size = @getimagesize($value); + + if (empty($size) || (0 === $size[0]) || (0 === $size[1])) { + $this->context->buildViolation($constraint->sizeNotDetectedMessage) + ->setCode(Image::SIZE_NOT_DETECTED_ERROR) + ->addViolation(); + + return; + } + + $width = $size[0]; + $height = $size[1]; + + if ($constraint->minWidth) { + if (!ctype_digit((string) $constraint->minWidth)) { + throw new ConstraintDefinitionException(sprintf('"%s" is not a valid minimum width.', $constraint->minWidth)); + } + + if ($width < $constraint->minWidth) { + $this->context->buildViolation($constraint->minWidthMessage) + ->setParameter('{{ width }}', $width) + ->setParameter('{{ min_width }}', $constraint->minWidth) + ->setCode(Image::TOO_NARROW_ERROR) + ->addViolation(); + + return; + } + } + + if ($constraint->maxWidth) { + if (!ctype_digit((string) $constraint->maxWidth)) { + throw new ConstraintDefinitionException(sprintf('"%s" is not a valid maximum width.', $constraint->maxWidth)); + } + + if ($width > $constraint->maxWidth) { + $this->context->buildViolation($constraint->maxWidthMessage) + ->setParameter('{{ width }}', $width) + ->setParameter('{{ max_width }}', $constraint->maxWidth) + ->setCode(Image::TOO_WIDE_ERROR) + ->addViolation(); + + return; + } + } + + if ($constraint->minHeight) { + if (!ctype_digit((string) $constraint->minHeight)) { + throw new ConstraintDefinitionException(sprintf('"%s" is not a valid minimum height.', $constraint->minHeight)); + } + + if ($height < $constraint->minHeight) { + $this->context->buildViolation($constraint->minHeightMessage) + ->setParameter('{{ height }}', $height) + ->setParameter('{{ min_height }}', $constraint->minHeight) + ->setCode(Image::TOO_LOW_ERROR) + ->addViolation(); + + return; + } + } + + if ($constraint->maxHeight) { + if (!ctype_digit((string) $constraint->maxHeight)) { + throw new ConstraintDefinitionException(sprintf('"%s" is not a valid maximum height.', $constraint->maxHeight)); + } + + if ($height > $constraint->maxHeight) { + $this->context->buildViolation($constraint->maxHeightMessage) + ->setParameter('{{ height }}', $height) + ->setParameter('{{ max_height }}', $constraint->maxHeight) + ->setCode(Image::TOO_HIGH_ERROR) + ->addViolation(); + } + } + + $pixels = $width * $height; + + if (null !== $constraint->minPixels) { + if (!ctype_digit((string) $constraint->minPixels)) { + throw new ConstraintDefinitionException(sprintf('"%s" is not a valid minimum amount of pixels.', $constraint->minPixels)); + } + + if ($pixels < $constraint->minPixels) { + $this->context->buildViolation($constraint->minPixelsMessage) + ->setParameter('{{ pixels }}', $pixels) + ->setParameter('{{ min_pixels }}', $constraint->minPixels) + ->setParameter('{{ height }}', $height) + ->setParameter('{{ width }}', $width) + ->setCode(Image::TOO_FEW_PIXEL_ERROR) + ->addViolation(); + } + } + + if (null !== $constraint->maxPixels) { + if (!ctype_digit((string) $constraint->maxPixels)) { + throw new ConstraintDefinitionException(sprintf('"%s" is not a valid maximum amount of pixels.', $constraint->maxPixels)); + } + + if ($pixels > $constraint->maxPixels) { + $this->context->buildViolation($constraint->maxPixelsMessage) + ->setParameter('{{ pixels }}', $pixels) + ->setParameter('{{ max_pixels }}', $constraint->maxPixels) + ->setParameter('{{ height }}', $height) + ->setParameter('{{ width }}', $width) + ->setCode(Image::TOO_MANY_PIXEL_ERROR) + ->addViolation(); + } + } + + $ratio = round($width / $height, 2); + + if (null !== $constraint->minRatio) { + if (!is_numeric((string) $constraint->minRatio)) { + throw new ConstraintDefinitionException(sprintf('"%s" is not a valid minimum ratio.', $constraint->minRatio)); + } + + if ($ratio < round($constraint->minRatio, 2)) { + $this->context->buildViolation($constraint->minRatioMessage) + ->setParameter('{{ ratio }}', $ratio) + ->setParameter('{{ min_ratio }}', round($constraint->minRatio, 2)) + ->setCode(Image::RATIO_TOO_SMALL_ERROR) + ->addViolation(); + } + } + + if (null !== $constraint->maxRatio) { + if (!is_numeric((string) $constraint->maxRatio)) { + throw new ConstraintDefinitionException(sprintf('"%s" is not a valid maximum ratio.', $constraint->maxRatio)); + } + + if ($ratio > round($constraint->maxRatio, 2)) { + $this->context->buildViolation($constraint->maxRatioMessage) + ->setParameter('{{ ratio }}', $ratio) + ->setParameter('{{ max_ratio }}', round($constraint->maxRatio, 2)) + ->setCode(Image::RATIO_TOO_BIG_ERROR) + ->addViolation(); + } + } + + if (!$constraint->allowSquare && $width == $height) { + $this->context->buildViolation($constraint->allowSquareMessage) + ->setParameter('{{ width }}', $width) + ->setParameter('{{ height }}', $height) + ->setCode(Image::SQUARE_NOT_ALLOWED_ERROR) + ->addViolation(); + } + + if (!$constraint->allowLandscape && $width > $height) { + $this->context->buildViolation($constraint->allowLandscapeMessage) + ->setParameter('{{ width }}', $width) + ->setParameter('{{ height }}', $height) + ->setCode(Image::LANDSCAPE_NOT_ALLOWED_ERROR) + ->addViolation(); + } + + if (!$constraint->allowPortrait && $width < $height) { + $this->context->buildViolation($constraint->allowPortraitMessage) + ->setParameter('{{ width }}', $width) + ->setParameter('{{ height }}', $height) + ->setCode(Image::PORTRAIT_NOT_ALLOWED_ERROR) + ->addViolation(); + } + + if ($constraint->detectCorrupted) { + if (!\function_exists('imagecreatefromstring')) { + throw new LogicException('Corrupted images detection requires installed and enabled GD extension.'); + } + + $resource = @imagecreatefromstring(file_get_contents($value)); + + if (false === $resource) { + $this->context->buildViolation($constraint->corruptedMessage) + ->setCode(Image::CORRUPTED_IMAGE_ERROR) + ->addViolation(); + + return; + } + + imagedestroy($resource); + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/Ip.php b/src/vendor/symfony/validator/Constraints/Ip.php new file mode 100644 index 0000000..0e41240 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Ip.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; +use Symfony\Component\Validator\Exception\InvalidArgumentException; + +/** + * Validates that a value is a valid IP address. + * + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Bernhard Schussek + * @author Joseph Bielawski + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class Ip extends Constraint +{ + public const V4 = '4'; + public const V6 = '6'; + public const ALL = 'all'; + + // adds FILTER_FLAG_NO_PRIV_RANGE flag (skip private ranges) + public const V4_NO_PRIV = '4_no_priv'; + public const V6_NO_PRIV = '6_no_priv'; + public const ALL_NO_PRIV = 'all_no_priv'; + + // adds FILTER_FLAG_NO_RES_RANGE flag (skip reserved ranges) + public const V4_NO_RES = '4_no_res'; + public const V6_NO_RES = '6_no_res'; + public const ALL_NO_RES = 'all_no_res'; + + // adds FILTER_FLAG_NO_PRIV_RANGE and FILTER_FLAG_NO_RES_RANGE flags (skip both) + public const V4_ONLY_PUBLIC = '4_public'; + public const V6_ONLY_PUBLIC = '6_public'; + public const ALL_ONLY_PUBLIC = 'all_public'; + + public const INVALID_IP_ERROR = 'b1b427ae-9f6f-41b0-aa9b-84511fbb3c5b'; + + protected static $versions = [ + self::V4, + self::V6, + self::ALL, + + self::V4_NO_PRIV, + self::V6_NO_PRIV, + self::ALL_NO_PRIV, + + self::V4_NO_RES, + self::V6_NO_RES, + self::ALL_NO_RES, + + self::V4_ONLY_PUBLIC, + self::V6_ONLY_PUBLIC, + self::ALL_ONLY_PUBLIC, + ]; + + protected static $errorNames = [ + self::INVALID_IP_ERROR => 'INVALID_IP_ERROR', + ]; + + public $version = self::V4; + + public $message = 'This is not a valid IP address.'; + + public $normalizer; + + /** + * {@inheritdoc} + */ + public function __construct( + array $options = null, + string $version = null, + string $message = null, + callable $normalizer = null, + array $groups = null, + $payload = null + ) { + parent::__construct($options, $groups, $payload); + + $this->version = $version ?? $this->version; + $this->message = $message ?? $this->message; + $this->normalizer = $normalizer ?? $this->normalizer; + + if (!\in_array($this->version, self::$versions)) { + throw new ConstraintDefinitionException(sprintf('The option "version" must be one of "%s".', implode('", "', self::$versions))); + } + + if (null !== $this->normalizer && !\is_callable($this->normalizer)) { + throw new InvalidArgumentException(sprintf('The "normalizer" option must be a valid callable ("%s" given).', get_debug_type($this->normalizer))); + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/IpValidator.php b/src/vendor/symfony/validator/Constraints/IpValidator.php new file mode 100644 index 0000000..9db41f9 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/IpValidator.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; +use Symfony\Component\Validator\Exception\UnexpectedValueException; + +/** + * Validates whether a value is a valid IP address. + * + * @author Bernhard Schussek + * @author Joseph Bielawski + */ +class IpValidator extends ConstraintValidator +{ + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Ip) { + throw new UnexpectedTypeException($constraint, Ip::class); + } + + if (null === $value || '' === $value) { + return; + } + + if (!\is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { + throw new UnexpectedValueException($value, 'string'); + } + + $value = (string) $value; + + if (null !== $constraint->normalizer) { + $value = ($constraint->normalizer)($value); + } + + switch ($constraint->version) { + case Ip::V4: + $flag = \FILTER_FLAG_IPV4; + break; + + case Ip::V6: + $flag = \FILTER_FLAG_IPV6; + break; + + case Ip::V4_NO_PRIV: + $flag = \FILTER_FLAG_IPV4 | \FILTER_FLAG_NO_PRIV_RANGE; + break; + + case Ip::V6_NO_PRIV: + $flag = \FILTER_FLAG_IPV6 | \FILTER_FLAG_NO_PRIV_RANGE; + break; + + case Ip::ALL_NO_PRIV: + $flag = \FILTER_FLAG_NO_PRIV_RANGE; + break; + + case Ip::V4_NO_RES: + $flag = \FILTER_FLAG_IPV4 | \FILTER_FLAG_NO_RES_RANGE; + break; + + case Ip::V6_NO_RES: + $flag = \FILTER_FLAG_IPV6 | \FILTER_FLAG_NO_RES_RANGE; + break; + + case Ip::ALL_NO_RES: + $flag = \FILTER_FLAG_NO_RES_RANGE; + break; + + case Ip::V4_ONLY_PUBLIC: + $flag = \FILTER_FLAG_IPV4 | \FILTER_FLAG_NO_PRIV_RANGE | \FILTER_FLAG_NO_RES_RANGE; + break; + + case Ip::V6_ONLY_PUBLIC: + $flag = \FILTER_FLAG_IPV6 | \FILTER_FLAG_NO_PRIV_RANGE | \FILTER_FLAG_NO_RES_RANGE; + break; + + case Ip::ALL_ONLY_PUBLIC: + $flag = \FILTER_FLAG_NO_PRIV_RANGE | \FILTER_FLAG_NO_RES_RANGE; + break; + + default: + $flag = 0; + break; + } + + if (!filter_var($value, \FILTER_VALIDATE_IP, $flag)) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Ip::INVALID_IP_ERROR) + ->addViolation(); + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/IsFalse.php b/src/vendor/symfony/validator/Constraints/IsFalse.php new file mode 100644 index 0000000..460aafc --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/IsFalse.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class IsFalse extends Constraint +{ + public const NOT_FALSE_ERROR = 'd53a91b0-def3-426a-83d7-269da7ab4200'; + + protected static $errorNames = [ + self::NOT_FALSE_ERROR => 'NOT_FALSE_ERROR', + ]; + + public $message = 'This value should be false.'; + + public function __construct(array $options = null, string $message = null, array $groups = null, $payload = null) + { + parent::__construct($options ?? [], $groups, $payload); + + $this->message = $message ?? $this->message; + } +} diff --git a/src/vendor/symfony/validator/Constraints/IsFalseValidator.php b/src/vendor/symfony/validator/Constraints/IsFalseValidator.php new file mode 100644 index 0000000..79c4234 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/IsFalseValidator.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; + +/** + * @author Bernhard Schussek + */ +class IsFalseValidator extends ConstraintValidator +{ + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof IsFalse) { + throw new UnexpectedTypeException($constraint, IsFalse::class); + } + + if (null === $value || false === $value || 0 === $value || '0' === $value) { + return; + } + + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(IsFalse::NOT_FALSE_ERROR) + ->addViolation(); + } +} diff --git a/src/vendor/symfony/validator/Constraints/IsNull.php b/src/vendor/symfony/validator/Constraints/IsNull.php new file mode 100644 index 0000000..2a8439f --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/IsNull.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class IsNull extends Constraint +{ + public const NOT_NULL_ERROR = '60d2f30b-8cfa-4372-b155-9656634de120'; + + protected static $errorNames = [ + self::NOT_NULL_ERROR => 'NOT_NULL_ERROR', + ]; + + public $message = 'This value should be null.'; + + public function __construct(array $options = null, string $message = null, array $groups = null, $payload = null) + { + parent::__construct($options ?? [], $groups, $payload); + + $this->message = $message ?? $this->message; + } +} diff --git a/src/vendor/symfony/validator/Constraints/IsNullValidator.php b/src/vendor/symfony/validator/Constraints/IsNullValidator.php new file mode 100644 index 0000000..b6e7817 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/IsNullValidator.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; + +/** + * @author Bernhard Schussek + */ +class IsNullValidator extends ConstraintValidator +{ + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof IsNull) { + throw new UnexpectedTypeException($constraint, IsNull::class); + } + + if (null !== $value) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(IsNull::NOT_NULL_ERROR) + ->addViolation(); + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/IsTrue.php b/src/vendor/symfony/validator/Constraints/IsTrue.php new file mode 100644 index 0000000..7b95475 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/IsTrue.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class IsTrue extends Constraint +{ + public const NOT_TRUE_ERROR = '2beabf1c-54c0-4882-a928-05249b26e23b'; + + protected static $errorNames = [ + self::NOT_TRUE_ERROR => 'NOT_TRUE_ERROR', + ]; + + public $message = 'This value should be true.'; + + public function __construct(array $options = null, string $message = null, array $groups = null, $payload = null) + { + parent::__construct($options ?? [], $groups, $payload); + + $this->message = $message ?? $this->message; + } +} diff --git a/src/vendor/symfony/validator/Constraints/IsTrueValidator.php b/src/vendor/symfony/validator/Constraints/IsTrueValidator.php new file mode 100644 index 0000000..6088f6d --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/IsTrueValidator.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; + +/** + * @author Bernhard Schussek + */ +class IsTrueValidator extends ConstraintValidator +{ + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof IsTrue) { + throw new UnexpectedTypeException($constraint, IsTrue::class); + } + + if (null === $value) { + return; + } + + if (true !== $value && 1 !== $value && '1' !== $value) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(IsTrue::NOT_TRUE_ERROR) + ->addViolation(); + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/Isbn.php b/src/vendor/symfony/validator/Constraints/Isbn.php new file mode 100644 index 0000000..b95dfeb --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Isbn.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author The Whole Life To Learn + * @author Manuel Reinhard + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class Isbn extends Constraint +{ + public const ISBN_10 = 'isbn10'; + public const ISBN_13 = 'isbn13'; + + public const TOO_SHORT_ERROR = '949acbb0-8ef5-43ed-a0e9-032dfd08ae45'; + public const TOO_LONG_ERROR = '3171387d-f80a-47b3-bd6e-60598545316a'; + public const INVALID_CHARACTERS_ERROR = '23d21cea-da99-453d-98b1-a7d916fbb339'; + public const CHECKSUM_FAILED_ERROR = '2881c032-660f-46b6-8153-d352d9706640'; + public const TYPE_NOT_RECOGNIZED_ERROR = 'fa54a457-f042-441f-89c4-066ee5bdd3e1'; + + protected static $errorNames = [ + self::TOO_SHORT_ERROR => 'TOO_SHORT_ERROR', + self::TOO_LONG_ERROR => 'TOO_LONG_ERROR', + self::INVALID_CHARACTERS_ERROR => 'INVALID_CHARACTERS_ERROR', + self::CHECKSUM_FAILED_ERROR => 'CHECKSUM_FAILED_ERROR', + self::TYPE_NOT_RECOGNIZED_ERROR => 'TYPE_NOT_RECOGNIZED_ERROR', + ]; + + public $isbn10Message = 'This value is not a valid ISBN-10.'; + public $isbn13Message = 'This value is not a valid ISBN-13.'; + public $bothIsbnMessage = 'This value is neither a valid ISBN-10 nor a valid ISBN-13.'; + public $type; + public $message; + + /** + * {@inheritdoc} + * + * @param string|array|null $type The ISBN standard to validate or a set of options + */ + public function __construct( + $type = null, + string $message = null, + string $isbn10Message = null, + string $isbn13Message = null, + string $bothIsbnMessage = null, + array $groups = null, + $payload = null, + array $options = [] + ) { + if (\is_array($type)) { + $options = array_merge($type, $options); + } elseif (null !== $type) { + $options['value'] = $type; + } + + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + $this->isbn10Message = $isbn10Message ?? $this->isbn10Message; + $this->isbn13Message = $isbn13Message ?? $this->isbn13Message; + $this->bothIsbnMessage = $bothIsbnMessage ?? $this->bothIsbnMessage; + } + + /** + * {@inheritdoc} + */ + public function getDefaultOption() + { + return 'type'; + } +} diff --git a/src/vendor/symfony/validator/Constraints/IsbnValidator.php b/src/vendor/symfony/validator/Constraints/IsbnValidator.php new file mode 100644 index 0000000..37aa873 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/IsbnValidator.php @@ -0,0 +1,184 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; +use Symfony\Component\Validator\Exception\UnexpectedValueException; + +/** + * Validates whether the value is a valid ISBN-10 or ISBN-13. + * + * @author The Whole Life To Learn + * @author Manuel Reinhard + * @author Bernhard Schussek + * + * @see https://en.wikipedia.org/wiki/Isbn + */ +class IsbnValidator extends ConstraintValidator +{ + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Isbn) { + throw new UnexpectedTypeException($constraint, Isbn::class); + } + + if (null === $value || '' === $value) { + return; + } + + if (!\is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { + throw new UnexpectedValueException($value, 'string'); + } + + $value = (string) $value; + $canonical = str_replace('-', '', $value); + + // Explicitly validate against ISBN-10 + if (Isbn::ISBN_10 === $constraint->type) { + if (true !== ($code = $this->validateIsbn10($canonical))) { + $this->context->buildViolation($this->getMessage($constraint, $constraint->type)) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode($code) + ->addViolation(); + } + + return; + } + + // Explicitly validate against ISBN-13 + if (Isbn::ISBN_13 === $constraint->type) { + if (true !== ($code = $this->validateIsbn13($canonical))) { + $this->context->buildViolation($this->getMessage($constraint, $constraint->type)) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode($code) + ->addViolation(); + } + + return; + } + + // Try both ISBNs + + // First, try ISBN-10 + $code = $this->validateIsbn10($canonical); + + // The ISBN can only be an ISBN-13 if the value was too long for ISBN-10 + if (Isbn::TOO_LONG_ERROR === $code) { + // Try ISBN-13 now + $code = $this->validateIsbn13($canonical); + + // If too short, this means we have 11 or 12 digits + if (Isbn::TOO_SHORT_ERROR === $code) { + $code = Isbn::TYPE_NOT_RECOGNIZED_ERROR; + } + } + + if (true !== $code) { + $this->context->buildViolation($this->getMessage($constraint)) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode($code) + ->addViolation(); + } + } + + protected function validateIsbn10(string $isbn) + { + // Choose an algorithm so that ERROR_INVALID_CHARACTERS is preferred + // over ERROR_TOO_SHORT/ERROR_TOO_LONG + // Otherwise "0-45122-5244" passes, but "0-45122_5244" reports + // "too long" + + // Error priority: + // 1. ERROR_INVALID_CHARACTERS + // 2. ERROR_TOO_SHORT/ERROR_TOO_LONG + // 3. ERROR_CHECKSUM_FAILED + + $checkSum = 0; + + for ($i = 0; $i < 10; ++$i) { + // If we test the length before the loop, we get an ERROR_TOO_SHORT + // when actually an ERROR_INVALID_CHARACTERS is wanted, e.g. for + // "0-45122_5244" (typo) + if (!isset($isbn[$i])) { + return Isbn::TOO_SHORT_ERROR; + } + + if ('X' === $isbn[$i]) { + $digit = 10; + } elseif (ctype_digit($isbn[$i])) { + $digit = $isbn[$i]; + } else { + return Isbn::INVALID_CHARACTERS_ERROR; + } + + $checkSum += $digit * (10 - $i); + } + + if (isset($isbn[$i])) { + return Isbn::TOO_LONG_ERROR; + } + + return 0 === $checkSum % 11 ? true : Isbn::CHECKSUM_FAILED_ERROR; + } + + protected function validateIsbn13(string $isbn) + { + // Error priority: + // 1. ERROR_INVALID_CHARACTERS + // 2. ERROR_TOO_SHORT/ERROR_TOO_LONG + // 3. ERROR_CHECKSUM_FAILED + + if (!ctype_digit($isbn)) { + return Isbn::INVALID_CHARACTERS_ERROR; + } + + $length = \strlen($isbn); + + if ($length < 13) { + return Isbn::TOO_SHORT_ERROR; + } + + if ($length > 13) { + return Isbn::TOO_LONG_ERROR; + } + + $checkSum = 0; + + for ($i = 0; $i < 13; $i += 2) { + $checkSum += $isbn[$i]; + } + + for ($i = 1; $i < 12; $i += 2) { + $checkSum += $isbn[$i] * 3; + } + + return 0 === $checkSum % 10 ? true : Isbn::CHECKSUM_FAILED_ERROR; + } + + protected function getMessage(Isbn $constraint, string $type = null) + { + if (null !== $constraint->message) { + return $constraint->message; + } elseif (Isbn::ISBN_10 === $type) { + return $constraint->isbn10Message; + } elseif (Isbn::ISBN_13 === $type) { + return $constraint->isbn13Message; + } + + return $constraint->bothIsbnMessage; + } +} diff --git a/src/vendor/symfony/validator/Constraints/Isin.php b/src/vendor/symfony/validator/Constraints/Isin.php new file mode 100644 index 0000000..08fa60d --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Isin.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Laurent Masforné + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class Isin extends Constraint +{ + public const VALIDATION_LENGTH = 12; + public const VALIDATION_PATTERN = '/[A-Z]{2}[A-Z0-9]{9}[0-9]{1}/'; + + public const INVALID_LENGTH_ERROR = '88738dfc-9ed5-ba1e-aebe-402a2a9bf58e'; + public const INVALID_PATTERN_ERROR = '3d08ce0-ded9-a93d-9216-17ac21265b65e'; + public const INVALID_CHECKSUM_ERROR = '32089b-0ee1-93ba-399e-aa232e62f2d29d'; + + protected static $errorNames = [ + self::INVALID_LENGTH_ERROR => 'INVALID_LENGTH_ERROR', + self::INVALID_PATTERN_ERROR => 'INVALID_PATTERN_ERROR', + self::INVALID_CHECKSUM_ERROR => 'INVALID_CHECKSUM_ERROR', + ]; + + public $message = 'This value is not a valid International Securities Identification Number (ISIN).'; + + public function __construct(array $options = null, string $message = null, array $groups = null, $payload = null) + { + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + } +} diff --git a/src/vendor/symfony/validator/Constraints/IsinValidator.php b/src/vendor/symfony/validator/Constraints/IsinValidator.php new file mode 100644 index 0000000..81c815e --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/IsinValidator.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; +use Symfony\Component\Validator\Exception\UnexpectedValueException; + +/** + * @author Laurent Masforné + * + * @see https://en.wikipedia.org/wiki/International_Securities_Identification_Number + */ +class IsinValidator extends ConstraintValidator +{ + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Isin) { + throw new UnexpectedTypeException($constraint, Isin::class); + } + + if (null === $value || '' === $value) { + return; + } + + if (!\is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { + throw new UnexpectedValueException($value, 'string'); + } + + $value = strtoupper($value); + + if (Isin::VALIDATION_LENGTH !== \strlen($value)) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Isin::INVALID_LENGTH_ERROR) + ->addViolation(); + + return; + } + + if (!preg_match(Isin::VALIDATION_PATTERN, $value)) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Isin::INVALID_PATTERN_ERROR) + ->addViolation(); + + return; + } + + if (!$this->isCorrectChecksum($value)) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Isin::INVALID_CHECKSUM_ERROR) + ->addViolation(); + } + } + + private function isCorrectChecksum(string $input): bool + { + $characters = str_split($input); + foreach ($characters as $i => $char) { + $characters[$i] = \intval($char, 36); + } + $number = implode('', $characters); + + return 0 === $this->context->getValidator()->validate($number, new Luhn())->count(); + } +} diff --git a/src/vendor/symfony/validator/Constraints/Issn.php b/src/vendor/symfony/validator/Constraints/Issn.php new file mode 100644 index 0000000..b3b7b21 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Issn.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Antonio J. García Lagar + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class Issn extends Constraint +{ + public const TOO_SHORT_ERROR = '6a20dd3d-f463-4460-8e7b-18a1b98abbfb'; + public const TOO_LONG_ERROR = '37cef893-5871-464e-8b12-7fb79324833c'; + public const MISSING_HYPHEN_ERROR = '2983286f-8134-4693-957a-1ec4ef887b15'; + public const INVALID_CHARACTERS_ERROR = 'a663d266-37c2-4ece-a914-ae891940c588'; + public const INVALID_CASE_ERROR = '7b6dd393-7523-4a6c-b84d-72b91bba5e1a'; + public const CHECKSUM_FAILED_ERROR = 'b0f92dbc-667c-48de-b526-ad9586d43e85'; + + protected static $errorNames = [ + self::TOO_SHORT_ERROR => 'TOO_SHORT_ERROR', + self::TOO_LONG_ERROR => 'TOO_LONG_ERROR', + self::MISSING_HYPHEN_ERROR => 'MISSING_HYPHEN_ERROR', + self::INVALID_CHARACTERS_ERROR => 'INVALID_CHARACTERS_ERROR', + self::INVALID_CASE_ERROR => 'INVALID_CASE_ERROR', + self::CHECKSUM_FAILED_ERROR => 'CHECKSUM_FAILED_ERROR', + ]; + + public $message = 'This value is not a valid ISSN.'; + public $caseSensitive = false; + public $requireHyphen = false; + + public function __construct( + array $options = null, + string $message = null, + bool $caseSensitive = null, + bool $requireHyphen = null, + array $groups = null, + $payload = null + ) { + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + $this->caseSensitive = $caseSensitive ?? $this->caseSensitive; + $this->requireHyphen = $requireHyphen ?? $this->requireHyphen; + } +} diff --git a/src/vendor/symfony/validator/Constraints/IssnValidator.php b/src/vendor/symfony/validator/Constraints/IssnValidator.php new file mode 100644 index 0000000..66f44af --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/IssnValidator.php @@ -0,0 +1,131 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; +use Symfony\Component\Validator\Exception\UnexpectedValueException; + +/** + * Validates whether the value is a valid ISSN. + * + * @author Antonio J. García Lagar + * @author Bernhard Schussek + * + * @see https://en.wikipedia.org/wiki/Issn + */ +class IssnValidator extends ConstraintValidator +{ + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Issn) { + throw new UnexpectedTypeException($constraint, Issn::class); + } + + if (null === $value || '' === $value) { + return; + } + + if (!\is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { + throw new UnexpectedValueException($value, 'string'); + } + + $value = (string) $value; + $canonical = $value; + + // 1234-567X + // ^ + if (isset($canonical[4]) && '-' === $canonical[4]) { + // remove hyphen + $canonical = substr($canonical, 0, 4).substr($canonical, 5); + } elseif ($constraint->requireHyphen) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Issn::MISSING_HYPHEN_ERROR) + ->addViolation(); + + return; + } + + $length = \strlen($canonical); + + if ($length < 8) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Issn::TOO_SHORT_ERROR) + ->addViolation(); + + return; + } + + if ($length > 8) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Issn::TOO_LONG_ERROR) + ->addViolation(); + + return; + } + + // 1234567X + // ^^^^^^^ digits only + if (!ctype_digit(substr($canonical, 0, 7))) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Issn::INVALID_CHARACTERS_ERROR) + ->addViolation(); + + return; + } + + // 1234567X + // ^ digit, x or X + if (!ctype_digit($canonical[7]) && 'x' !== $canonical[7] && 'X' !== $canonical[7]) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Issn::INVALID_CHARACTERS_ERROR) + ->addViolation(); + + return; + } + + // 1234567X + // ^ case-sensitive? + if ($constraint->caseSensitive && 'x' === $canonical[7]) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Issn::INVALID_CASE_ERROR) + ->addViolation(); + + return; + } + + // Calculate a checksum. "X" equals 10. + $checkSum = 'X' === $canonical[7] || 'x' === $canonical[7] ? 10 : $canonical[7]; + + for ($i = 0; $i < 7; ++$i) { + // Multiply the first digit by 8, the second by 7, etc. + $checkSum += (8 - $i) * (int) $canonical[$i]; + } + + if (0 !== $checkSum % 11) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Issn::CHECKSUM_FAILED_ERROR) + ->addViolation(); + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/Json.php b/src/vendor/symfony/validator/Constraints/Json.php new file mode 100644 index 0000000..4388858 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Json.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Imad ZAIRIG + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class Json extends Constraint +{ + public const INVALID_JSON_ERROR = '0789c8ad-2d2b-49a4-8356-e2ce63998504'; + + protected static $errorNames = [ + self::INVALID_JSON_ERROR => 'INVALID_JSON_ERROR', + ]; + + public $message = 'This value should be valid JSON.'; + + public function __construct(array $options = null, string $message = null, array $groups = null, $payload = null) + { + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + } +} diff --git a/src/vendor/symfony/validator/Constraints/JsonValidator.php b/src/vendor/symfony/validator/Constraints/JsonValidator.php new file mode 100644 index 0000000..176331f --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/JsonValidator.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; + +/** + * @author Imad ZAIRIG + */ +class JsonValidator extends ConstraintValidator +{ + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Json) { + throw new UnexpectedTypeException($constraint, Json::class); + } + + if (null === $value || '' === $value) { + return; + } + + if (!\is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { + throw new UnexpectedTypeException($value, 'string'); + } + + $value = (string) $value; + + json_decode($value); + + if (\JSON_ERROR_NONE !== json_last_error()) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Json::INVALID_JSON_ERROR) + ->addViolation(); + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/Language.php b/src/vendor/symfony/validator/Constraints/Language.php new file mode 100644 index 0000000..a8204da --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Language.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Intl\Languages; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Exception\LogicException; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class Language extends Constraint +{ + public const NO_SUCH_LANGUAGE_ERROR = 'ee65fec4-9a20-4202-9f39-ca558cd7bdf7'; + + protected static $errorNames = [ + self::NO_SUCH_LANGUAGE_ERROR => 'NO_SUCH_LANGUAGE_ERROR', + ]; + + public $message = 'This value is not a valid language.'; + public $alpha3 = false; + + public function __construct( + array $options = null, + string $message = null, + bool $alpha3 = null, + array $groups = null, + $payload = null + ) { + if (!class_exists(Languages::class)) { + throw new LogicException('The Intl component is required to use the Language constraint. Try running "composer require symfony/intl".'); + } + + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + $this->alpha3 = $alpha3 ?? $this->alpha3; + } +} diff --git a/src/vendor/symfony/validator/Constraints/LanguageValidator.php b/src/vendor/symfony/validator/Constraints/LanguageValidator.php new file mode 100644 index 0000000..8becc18 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/LanguageValidator.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Intl\Languages; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; +use Symfony\Component\Validator\Exception\UnexpectedValueException; + +/** + * Validates whether a value is a valid language code. + * + * @author Bernhard Schussek + */ +class LanguageValidator extends ConstraintValidator +{ + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Language) { + throw new UnexpectedTypeException($constraint, Language::class); + } + + if (null === $value || '' === $value) { + return; + } + + if (!\is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { + throw new UnexpectedValueException($value, 'string'); + } + + $value = (string) $value; + + if ($constraint->alpha3 ? !Languages::alpha3CodeExists($value) : !Languages::exists($value)) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Language::NO_SUCH_LANGUAGE_ERROR) + ->addViolation(); + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/Length.php b/src/vendor/symfony/validator/Constraints/Length.php new file mode 100644 index 0000000..29a89a3 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Length.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Exception\InvalidArgumentException; +use Symfony\Component\Validator\Exception\MissingOptionsException; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class Length extends Constraint +{ + public const TOO_SHORT_ERROR = '9ff3fdc4-b214-49db-8718-39c315e33d45'; + public const TOO_LONG_ERROR = 'd94b19cc-114f-4f44-9cc4-4138e80a87b9'; + public const NOT_EQUAL_LENGTH_ERROR = '4b6f5c76-22b4-409d-af16-fbe823ba9332'; + public const INVALID_CHARACTERS_ERROR = '35e6a710-aa2e-4719-b58e-24b35749b767'; + + protected static $errorNames = [ + self::TOO_SHORT_ERROR => 'TOO_SHORT_ERROR', + self::TOO_LONG_ERROR => 'TOO_LONG_ERROR', + self::NOT_EQUAL_LENGTH_ERROR => 'NOT_EQUAL_LENGTH_ERROR', + self::INVALID_CHARACTERS_ERROR => 'INVALID_CHARACTERS_ERROR', + ]; + + public $maxMessage = 'This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less.'; + public $minMessage = 'This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more.'; + public $exactMessage = 'This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters.'; + public $charsetMessage = 'This value does not match the expected {{ charset }} charset.'; + public $max; + public $min; + public $charset = 'UTF-8'; + public $normalizer; + public $allowEmptyString = false; + + /** + * {@inheritdoc} + * + * @param int|array|null $exactly The expected exact length or a set of options + */ + public function __construct( + $exactly = null, + int $min = null, + int $max = null, + string $charset = null, + callable $normalizer = null, + string $exactMessage = null, + string $minMessage = null, + string $maxMessage = null, + string $charsetMessage = null, + array $groups = null, + $payload = null, + array $options = [] + ) { + if (\is_array($exactly)) { + $options = array_merge($exactly, $options); + $exactly = $options['value'] ?? null; + } + + $min = $min ?? $options['min'] ?? null; + $max = $max ?? $options['max'] ?? null; + + unset($options['value'], $options['min'], $options['max']); + + if (null !== $exactly && null === $min && null === $max) { + $min = $max = $exactly; + } + + parent::__construct($options, $groups, $payload); + + $this->min = $min; + $this->max = $max; + $this->charset = $charset ?? $this->charset; + $this->normalizer = $normalizer ?? $this->normalizer; + $this->exactMessage = $exactMessage ?? $this->exactMessage; + $this->minMessage = $minMessage ?? $this->minMessage; + $this->maxMessage = $maxMessage ?? $this->maxMessage; + $this->charsetMessage = $charsetMessage ?? $this->charsetMessage; + + if (null === $this->min && null === $this->max) { + throw new MissingOptionsException(sprintf('Either option "min" or "max" must be given for constraint "%s".', __CLASS__), ['min', 'max']); + } + + if (null !== $this->normalizer && !\is_callable($this->normalizer)) { + throw new InvalidArgumentException(sprintf('The "normalizer" option must be a valid callable ("%s" given).', get_debug_type($this->normalizer))); + } + + if (isset($options['allowEmptyString'])) { + trigger_deprecation('symfony/validator', '5.2', sprintf('The "allowEmptyString" option of the "%s" constraint is deprecated.', self::class)); + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/LengthValidator.php b/src/vendor/symfony/validator/Constraints/LengthValidator.php new file mode 100644 index 0000000..34e8360 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/LengthValidator.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; +use Symfony\Component\Validator\Exception\UnexpectedValueException; + +/** + * @author Bernhard Schussek + */ +class LengthValidator extends ConstraintValidator +{ + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Length) { + throw new UnexpectedTypeException($constraint, Length::class); + } + + if (null === $value || ('' === $value && $constraint->allowEmptyString)) { + return; + } + + if (!\is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { + throw new UnexpectedValueException($value, 'string'); + } + + $stringValue = (string) $value; + + if (null !== $constraint->normalizer) { + $stringValue = ($constraint->normalizer)($stringValue); + } + + try { + $invalidCharset = !@mb_check_encoding($stringValue, $constraint->charset); + } catch (\ValueError $e) { + if (!str_starts_with($e->getMessage(), 'mb_check_encoding(): Argument #2 ($encoding) must be a valid encoding')) { + throw $e; + } + + $invalidCharset = true; + } + + if ($invalidCharset) { + $this->context->buildViolation($constraint->charsetMessage) + ->setParameter('{{ value }}', $this->formatValue($stringValue)) + ->setParameter('{{ charset }}', $constraint->charset) + ->setInvalidValue($value) + ->setCode(Length::INVALID_CHARACTERS_ERROR) + ->addViolation(); + + return; + } + + $length = mb_strlen($stringValue, $constraint->charset); + + if (null !== $constraint->max && $length > $constraint->max) { + $exactlyOptionEnabled = $constraint->min == $constraint->max; + + $this->context->buildViolation($exactlyOptionEnabled ? $constraint->exactMessage : $constraint->maxMessage) + ->setParameter('{{ value }}', $this->formatValue($stringValue)) + ->setParameter('{{ limit }}', $constraint->max) + ->setInvalidValue($value) + ->setPlural((int) $constraint->max) + ->setCode($exactlyOptionEnabled ? Length::NOT_EQUAL_LENGTH_ERROR : Length::TOO_LONG_ERROR) + ->addViolation(); + + return; + } + + if (null !== $constraint->min && $length < $constraint->min) { + $exactlyOptionEnabled = $constraint->min == $constraint->max; + + $this->context->buildViolation($exactlyOptionEnabled ? $constraint->exactMessage : $constraint->minMessage) + ->setParameter('{{ value }}', $this->formatValue($stringValue)) + ->setParameter('{{ limit }}', $constraint->min) + ->setInvalidValue($value) + ->setPlural((int) $constraint->min) + ->setCode($exactlyOptionEnabled ? Length::NOT_EQUAL_LENGTH_ERROR : Length::TOO_SHORT_ERROR) + ->addViolation(); + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/LessThan.php b/src/vendor/symfony/validator/Constraints/LessThan.php new file mode 100644 index 0000000..acd6c9e --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/LessThan.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Daniel Holmes + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class LessThan extends AbstractComparison +{ + public const TOO_HIGH_ERROR = '079d7420-2d13-460c-8756-de810eeb37d2'; + + protected static $errorNames = [ + self::TOO_HIGH_ERROR => 'TOO_HIGH_ERROR', + ]; + + public $message = 'This value should be less than {{ compared_value }}.'; +} diff --git a/src/vendor/symfony/validator/Constraints/LessThanOrEqual.php b/src/vendor/symfony/validator/Constraints/LessThanOrEqual.php new file mode 100644 index 0000000..6f72845 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/LessThanOrEqual.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Daniel Holmes + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class LessThanOrEqual extends AbstractComparison +{ + public const TOO_HIGH_ERROR = '30fbb013-d015-4232-8b3b-8f3be97a7e14'; + + protected static $errorNames = [ + self::TOO_HIGH_ERROR => 'TOO_HIGH_ERROR', + ]; + + public $message = 'This value should be less than or equal to {{ compared_value }}.'; +} diff --git a/src/vendor/symfony/validator/Constraints/LessThanOrEqualValidator.php b/src/vendor/symfony/validator/Constraints/LessThanOrEqualValidator.php new file mode 100644 index 0000000..f7f4c8b --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/LessThanOrEqualValidator.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +/** + * Validates values are less than or equal to the previous (<=). + * + * @author Daniel Holmes + * @author Bernhard Schussek + */ +class LessThanOrEqualValidator extends AbstractComparisonValidator +{ + /** + * {@inheritdoc} + */ + protected function compareValues($value1, $value2) + { + return null === $value2 || $value1 <= $value2; + } + + /** + * {@inheritdoc} + */ + protected function getErrorCode() + { + return LessThanOrEqual::TOO_HIGH_ERROR; + } +} diff --git a/src/vendor/symfony/validator/Constraints/LessThanValidator.php b/src/vendor/symfony/validator/Constraints/LessThanValidator.php new file mode 100644 index 0000000..64e1075 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/LessThanValidator.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +/** + * Validates values are less than the previous (<). + * + * @author Daniel Holmes + * @author Bernhard Schussek + */ +class LessThanValidator extends AbstractComparisonValidator +{ + /** + * {@inheritdoc} + */ + protected function compareValues($value1, $value2) + { + return null === $value2 || $value1 < $value2; + } + + /** + * {@inheritdoc} + */ + protected function getErrorCode() + { + return LessThan::TOO_HIGH_ERROR; + } +} diff --git a/src/vendor/symfony/validator/Constraints/Locale.php b/src/vendor/symfony/validator/Constraints/Locale.php new file mode 100644 index 0000000..43c46cc --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Locale.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Intl\Locales; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Exception\LogicException; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class Locale extends Constraint +{ + public const NO_SUCH_LOCALE_ERROR = 'a0af4293-1f1a-4a1c-a328-979cba6182a2'; + + protected static $errorNames = [ + self::NO_SUCH_LOCALE_ERROR => 'NO_SUCH_LOCALE_ERROR', + ]; + + public $message = 'This value is not a valid locale.'; + public $canonicalize = true; + + public function __construct( + array $options = null, + string $message = null, + bool $canonicalize = null, + array $groups = null, + $payload = null + ) { + if (!class_exists(Locales::class)) { + throw new LogicException('The Intl component is required to use the Locale constraint. Try running "composer require symfony/intl".'); + } + + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + $this->canonicalize = $canonicalize ?? $this->canonicalize; + } +} diff --git a/src/vendor/symfony/validator/Constraints/LocaleValidator.php b/src/vendor/symfony/validator/Constraints/LocaleValidator.php new file mode 100644 index 0000000..0957e90 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/LocaleValidator.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Intl\Locales; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; +use Symfony\Component\Validator\Exception\UnexpectedValueException; + +/** + * Validates whether a value is a valid locale code. + * + * @author Bernhard Schussek + */ +class LocaleValidator extends ConstraintValidator +{ + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Locale) { + throw new UnexpectedTypeException($constraint, Locale::class); + } + + if (null === $value || '' === $value) { + return; + } + + if (!\is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { + throw new UnexpectedValueException($value, 'string'); + } + + $inputValue = (string) $value; + $value = $inputValue; + if ($constraint->canonicalize) { + $value = \Locale::canonicalize($value); + } + + if (!Locales::exists($value)) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($inputValue)) + ->setCode(Locale::NO_SUCH_LOCALE_ERROR) + ->addViolation(); + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/Luhn.php b/src/vendor/symfony/validator/Constraints/Luhn.php new file mode 100644 index 0000000..b2d2c29 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Luhn.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; + +/** + * Metadata for the LuhnValidator. + * + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Tim Nagel + * @author Greg Knapp http://gregk.me/2011/php-implementation-of-bank-card-luhn-algorithm/ + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class Luhn extends Constraint +{ + public const INVALID_CHARACTERS_ERROR = 'dfad6d23-1b74-4374-929b-5cbb56fc0d9e'; + public const CHECKSUM_FAILED_ERROR = '4d760774-3f50-4cd5-a6d5-b10a3299d8d3'; + + protected static $errorNames = [ + self::INVALID_CHARACTERS_ERROR => 'INVALID_CHARACTERS_ERROR', + self::CHECKSUM_FAILED_ERROR => 'CHECKSUM_FAILED_ERROR', + ]; + + public $message = 'Invalid card number.'; + + public function __construct( + array $options = null, + string $message = null, + array $groups = null, + $payload = null + ) { + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + } +} diff --git a/src/vendor/symfony/validator/Constraints/LuhnValidator.php b/src/vendor/symfony/validator/Constraints/LuhnValidator.php new file mode 100644 index 0000000..0f568fa --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/LuhnValidator.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; +use Symfony\Component\Validator\Exception\UnexpectedValueException; + +/** + * Validates a PAN using the LUHN Algorithm. + * + * For a list of example card numbers that are used to test this + * class, please see the LuhnValidatorTest class. + * + * @see http://en.wikipedia.org/wiki/Luhn_algorithm + * + * @author Tim Nagel + * @author Greg Knapp http://gregk.me/2011/php-implementation-of-bank-card-luhn-algorithm/ + * @author Bernhard Schussek + */ +class LuhnValidator extends ConstraintValidator +{ + /** + * Validates a credit card number with the Luhn algorithm. + * + * @param mixed $value + * + * @throws UnexpectedTypeException when the given credit card number is no string + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Luhn) { + throw new UnexpectedTypeException($constraint, Luhn::class); + } + + if (null === $value || '' === $value) { + return; + } + + // Work with strings only, because long numbers are represented as floats + // internally and don't work with strlen() + if (!\is_string($value) && !(\is_object($value) && method_exists($value, '__toString'))) { + throw new UnexpectedValueException($value, 'string'); + } + + $value = (string) $value; + + if (!ctype_digit($value)) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Luhn::INVALID_CHARACTERS_ERROR) + ->addViolation(); + + return; + } + + $checkSum = 0; + $length = \strlen($value); + + // Starting with the last digit and walking left, add every second + // digit to the check sum + // e.g. 7 9 9 2 7 3 9 8 7 1 3 + // ^ ^ ^ ^ ^ ^ + // = 7 + 9 + 7 + 9 + 7 + 3 + for ($i = $length - 1; $i >= 0; $i -= 2) { + $checkSum += $value[$i]; + } + + // Starting with the second last digit and walking left, double every + // second digit and add it to the check sum + // For doubles greater than 9, sum the individual digits + // e.g. 7 9 9 2 7 3 9 8 7 1 3 + // ^ ^ ^ ^ ^ + // = 1+8 + 4 + 6 + 1+6 + 2 + for ($i = $length - 2; $i >= 0; $i -= 2) { + $checkSum += array_sum(str_split((int) $value[$i] * 2)); + } + + if (0 === $checkSum || 0 !== $checkSum % 10) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Luhn::CHECKSUM_FAILED_ERROR) + ->addViolation(); + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/Negative.php b/src/vendor/symfony/validator/Constraints/Negative.php new file mode 100644 index 0000000..c13ebcb --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Negative.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Jan Schädlich + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class Negative extends LessThan +{ + use ZeroComparisonConstraintTrait; + + public $message = 'This value should be negative.'; +} diff --git a/src/vendor/symfony/validator/Constraints/NegativeOrZero.php b/src/vendor/symfony/validator/Constraints/NegativeOrZero.php new file mode 100644 index 0000000..5be735c --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/NegativeOrZero.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Jan Schädlich + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class NegativeOrZero extends LessThanOrEqual +{ + use ZeroComparisonConstraintTrait; + + public $message = 'This value should be either negative or zero.'; +} diff --git a/src/vendor/symfony/validator/Constraints/NotBlank.php b/src/vendor/symfony/validator/Constraints/NotBlank.php new file mode 100644 index 0000000..6f98d5a --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/NotBlank.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Exception\InvalidArgumentException; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Bernhard Schussek + * @author Kévin Dunglas + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class NotBlank extends Constraint +{ + public const IS_BLANK_ERROR = 'c1051bb4-d103-4f74-8988-acbcafc7fdc3'; + + protected static $errorNames = [ + self::IS_BLANK_ERROR => 'IS_BLANK_ERROR', + ]; + + public $message = 'This value should not be blank.'; + public $allowNull = false; + public $normalizer; + + public function __construct(array $options = null, string $message = null, bool $allowNull = null, callable $normalizer = null, array $groups = null, $payload = null) + { + parent::__construct($options ?? [], $groups, $payload); + + $this->message = $message ?? $this->message; + $this->allowNull = $allowNull ?? $this->allowNull; + $this->normalizer = $normalizer ?? $this->normalizer; + + if (null !== $this->normalizer && !\is_callable($this->normalizer)) { + throw new InvalidArgumentException(sprintf('The "normalizer" option must be a valid callable ("%s" given).', get_debug_type($this->normalizer))); + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/NotBlankValidator.php b/src/vendor/symfony/validator/Constraints/NotBlankValidator.php new file mode 100644 index 0000000..86af061 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/NotBlankValidator.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; + +/** + * @author Bernhard Schussek + * @author Kévin Dunglas + */ +class NotBlankValidator extends ConstraintValidator +{ + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof NotBlank) { + throw new UnexpectedTypeException($constraint, NotBlank::class); + } + + if ($constraint->allowNull && null === $value) { + return; + } + + if (\is_string($value) && null !== $constraint->normalizer) { + $value = ($constraint->normalizer)($value); + } + + if (false === $value || (empty($value) && '0' != $value)) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(NotBlank::IS_BLANK_ERROR) + ->addViolation(); + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/NotCompromisedPassword.php b/src/vendor/symfony/validator/Constraints/NotCompromisedPassword.php new file mode 100644 index 0000000..213bde2 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/NotCompromisedPassword.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; + +/** + * Checks if a password has been leaked in a data breach. + * + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Kévin Dunglas + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class NotCompromisedPassword extends Constraint +{ + public const COMPROMISED_PASSWORD_ERROR = 'd9bcdbfe-a9d6-4bfa-a8ff-da5fd93e0f6d'; + + protected static $errorNames = [self::COMPROMISED_PASSWORD_ERROR => 'COMPROMISED_PASSWORD_ERROR']; + + public $message = 'This password has been leaked in a data breach, it must not be used. Please use another password.'; + public $threshold = 1; + public $skipOnError = false; + + public function __construct( + array $options = null, + string $message = null, + int $threshold = null, + bool $skipOnError = null, + array $groups = null, + $payload = null + ) { + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + $this->threshold = $threshold ?? $this->threshold; + $this->skipOnError = $skipOnError ?? $this->skipOnError; + } +} diff --git a/src/vendor/symfony/validator/Constraints/NotCompromisedPasswordValidator.php b/src/vendor/symfony/validator/Constraints/NotCompromisedPasswordValidator.php new file mode 100644 index 0000000..148253d --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/NotCompromisedPasswordValidator.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\HttpClient\HttpClient; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; +use Symfony\Component\Validator\Exception\UnexpectedValueException; +use Symfony\Contracts\HttpClient\Exception\ExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * Checks if a password has been leaked in a data breach using haveibeenpwned.com's API. + * Use a k-anonymity model to protect the password being searched for. + * + * @see https://haveibeenpwned.com/API/v2#SearchingPwnedPasswordsByRange + * + * @author Kévin Dunglas + */ +class NotCompromisedPasswordValidator extends ConstraintValidator +{ + private const DEFAULT_API_ENDPOINT = 'https://api.pwnedpasswords.com/range/%s'; + + private $httpClient; + private $charset; + private $enabled; + private $endpoint; + + public function __construct(HttpClientInterface $httpClient = null, string $charset = 'UTF-8', bool $enabled = true, string $endpoint = null) + { + if (null === $httpClient && !class_exists(HttpClient::class)) { + throw new \LogicException(sprintf('The "%s" class requires the "HttpClient" component. Try running "composer require symfony/http-client".', self::class)); + } + + $this->httpClient = $httpClient ?? HttpClient::create(); + $this->charset = $charset; + $this->enabled = $enabled; + $this->endpoint = $endpoint ?? self::DEFAULT_API_ENDPOINT; + } + + /** + * {@inheritdoc} + * + * @throws ExceptionInterface + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof NotCompromisedPassword) { + throw new UnexpectedTypeException($constraint, NotCompromisedPassword::class); + } + + if (!$this->enabled) { + return; + } + + if (null !== $value && !\is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { + throw new UnexpectedValueException($value, 'string'); + } + + $value = (string) $value; + if ('' === $value) { + return; + } + + if ('UTF-8' !== $this->charset) { + $value = mb_convert_encoding($value, 'UTF-8', $this->charset); + } + + $hash = strtoupper(sha1($value)); + $hashPrefix = substr($hash, 0, 5); + $url = sprintf($this->endpoint, $hashPrefix); + + try { + $result = $this->httpClient->request('GET', $url)->getContent(); + } catch (ExceptionInterface $e) { + if ($constraint->skipOnError) { + return; + } + + throw $e; + } + + foreach (explode("\r\n", $result) as $line) { + if (!str_contains($line, ':')) { + continue; + } + + [$hashSuffix, $count] = explode(':', $line); + + if ($hashPrefix.$hashSuffix === $hash && $constraint->threshold <= (int) $count) { + $this->context->buildViolation($constraint->message) + ->setCode(NotCompromisedPassword::COMPROMISED_PASSWORD_ERROR) + ->addViolation(); + + return; + } + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/NotEqualTo.php b/src/vendor/symfony/validator/Constraints/NotEqualTo.php new file mode 100644 index 0000000..4b2accd --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/NotEqualTo.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Daniel Holmes + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class NotEqualTo extends AbstractComparison +{ + public const IS_EQUAL_ERROR = 'aa2e33da-25c8-4d76-8c6c-812f02ea89dd'; + + protected static $errorNames = [ + self::IS_EQUAL_ERROR => 'IS_EQUAL_ERROR', + ]; + + public $message = 'This value should not be equal to {{ compared_value }}.'; +} diff --git a/src/vendor/symfony/validator/Constraints/NotEqualToValidator.php b/src/vendor/symfony/validator/Constraints/NotEqualToValidator.php new file mode 100644 index 0000000..b80c5ea --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/NotEqualToValidator.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +/** + * Validates values are all unequal (!=). + * + * @author Daniel Holmes + * @author Bernhard Schussek + */ +class NotEqualToValidator extends AbstractComparisonValidator +{ + /** + * {@inheritdoc} + */ + protected function compareValues($value1, $value2) + { + return $value1 != $value2; + } + + /** + * {@inheritdoc} + */ + protected function getErrorCode() + { + return NotEqualTo::IS_EQUAL_ERROR; + } +} diff --git a/src/vendor/symfony/validator/Constraints/NotIdenticalTo.php b/src/vendor/symfony/validator/Constraints/NotIdenticalTo.php new file mode 100644 index 0000000..82ee014 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/NotIdenticalTo.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Daniel Holmes + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class NotIdenticalTo extends AbstractComparison +{ + public const IS_IDENTICAL_ERROR = '4aaac518-0dda-4129-a6d9-e216b9b454a0'; + + protected static $errorNames = [ + self::IS_IDENTICAL_ERROR => 'IS_IDENTICAL_ERROR', + ]; + + public $message = 'This value should not be identical to {{ compared_value_type }} {{ compared_value }}.'; +} diff --git a/src/vendor/symfony/validator/Constraints/NotIdenticalToValidator.php b/src/vendor/symfony/validator/Constraints/NotIdenticalToValidator.php new file mode 100644 index 0000000..3ea8b5a --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/NotIdenticalToValidator.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +/** + * Validates values aren't identical (!==). + * + * @author Daniel Holmes + * @author Bernhard Schussek + */ +class NotIdenticalToValidator extends AbstractComparisonValidator +{ + /** + * {@inheritdoc} + */ + protected function compareValues($value1, $value2) + { + return $value1 !== $value2; + } + + /** + * {@inheritdoc} + */ + protected function getErrorCode() + { + return NotIdenticalTo::IS_IDENTICAL_ERROR; + } +} diff --git a/src/vendor/symfony/validator/Constraints/NotNull.php b/src/vendor/symfony/validator/Constraints/NotNull.php new file mode 100644 index 0000000..85783c7 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/NotNull.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class NotNull extends Constraint +{ + public const IS_NULL_ERROR = 'ad32d13f-c3d4-423b-909a-857b961eb720'; + + protected static $errorNames = [ + self::IS_NULL_ERROR => 'IS_NULL_ERROR', + ]; + + public $message = 'This value should not be null.'; + + public function __construct(array $options = null, string $message = null, array $groups = null, $payload = null) + { + parent::__construct($options ?? [], $groups, $payload); + + $this->message = $message ?? $this->message; + } +} diff --git a/src/vendor/symfony/validator/Constraints/NotNullValidator.php b/src/vendor/symfony/validator/Constraints/NotNullValidator.php new file mode 100644 index 0000000..d02fcc4 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/NotNullValidator.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; + +/** + * @author Bernhard Schussek + */ +class NotNullValidator extends ConstraintValidator +{ + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof NotNull) { + throw new UnexpectedTypeException($constraint, NotNull::class); + } + + if (null === $value) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(NotNull::IS_NULL_ERROR) + ->addViolation(); + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/NumberConstraintTrait.php b/src/vendor/symfony/validator/Constraints/NumberConstraintTrait.php new file mode 100644 index 0000000..3229871 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/NumberConstraintTrait.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; + +trigger_deprecation('symfony/validator', '5.2', '%s is deprecated.', NumberConstraintTrait::class); + +/** + * @author Jan Schädlich + * + * @deprecated since Symfony 5.2 + */ +trait NumberConstraintTrait +{ + private function configureNumberConstraintOptions($options): array + { + if (null === $options) { + $options = []; + } elseif (!\is_array($options)) { + $options = [$this->getDefaultOption() => $options]; + } + + if (isset($options['propertyPath'])) { + throw new ConstraintDefinitionException(sprintf('The "propertyPath" option of the "%s" constraint cannot be set.', static::class)); + } + + if (isset($options['value'])) { + throw new ConstraintDefinitionException(sprintf('The "value" option of the "%s" constraint cannot be set.', static::class)); + } + + $options['value'] = 0; + + return $options; + } +} diff --git a/src/vendor/symfony/validator/Constraints/Optional.php b/src/vendor/symfony/validator/Constraints/Optional.php new file mode 100644 index 0000000..dab8b43 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Optional.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +/** + * @Annotation + * @Target({"ANNOTATION"}) + * + * @author Bernhard Schussek + */ +class Optional extends Existence +{ +} diff --git a/src/vendor/symfony/validator/Constraints/Positive.php b/src/vendor/symfony/validator/Constraints/Positive.php new file mode 100644 index 0000000..951e944 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Positive.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Jan Schädlich + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class Positive extends GreaterThan +{ + use ZeroComparisonConstraintTrait; + + public $message = 'This value should be positive.'; +} diff --git a/src/vendor/symfony/validator/Constraints/PositiveOrZero.php b/src/vendor/symfony/validator/Constraints/PositiveOrZero.php new file mode 100644 index 0000000..a7669c6 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/PositiveOrZero.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Jan Schädlich + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class PositiveOrZero extends GreaterThanOrEqual +{ + use ZeroComparisonConstraintTrait; + + public $message = 'This value should be either positive or zero.'; +} diff --git a/src/vendor/symfony/validator/Constraints/Range.php b/src/vendor/symfony/validator/Constraints/Range.php new file mode 100644 index 0000000..906057e --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Range.php @@ -0,0 +1,120 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\PropertyAccess\PropertyAccess; +use Symfony\Component\PropertyAccess\PropertyPathInterface; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; +use Symfony\Component\Validator\Exception\LogicException; +use Symfony\Component\Validator\Exception\MissingOptionsException; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class Range extends Constraint +{ + public const INVALID_CHARACTERS_ERROR = 'ad9a9798-7a99-4df7-8ce9-46e416a1e60b'; + public const NOT_IN_RANGE_ERROR = '04b91c99-a946-4221-afc5-e65ebac401eb'; + public const TOO_HIGH_ERROR = '2d28afcb-e32e-45fb-a815-01c431a86a69'; + public const TOO_LOW_ERROR = '76454e69-502c-46c5-9643-f447d837c4d5'; + + protected static $errorNames = [ + self::INVALID_CHARACTERS_ERROR => 'INVALID_CHARACTERS_ERROR', + self::NOT_IN_RANGE_ERROR => 'NOT_IN_RANGE_ERROR', + self::TOO_HIGH_ERROR => 'TOO_HIGH_ERROR', + self::TOO_LOW_ERROR => 'TOO_LOW_ERROR', + ]; + + public $notInRangeMessage = 'This value should be between {{ min }} and {{ max }}.'; + public $minMessage = 'This value should be {{ limit }} or more.'; + public $maxMessage = 'This value should be {{ limit }} or less.'; + public $invalidMessage = 'This value should be a valid number.'; + public $invalidDateTimeMessage = 'This value should be a valid datetime.'; + public $min; + public $minPropertyPath; + public $max; + public $maxPropertyPath; + + /** + * @internal + */ + public $deprecatedMinMessageSet = false; + + /** + * @internal + */ + public $deprecatedMaxMessageSet = false; + + /** + * {@inheritdoc} + * + * @param string|PropertyPathInterface|null $minPropertyPath + * @param string|PropertyPathInterface|null $maxPropertyPath + */ + public function __construct( + array $options = null, + string $notInRangeMessage = null, + string $minMessage = null, + string $maxMessage = null, + string $invalidMessage = null, + string $invalidDateTimeMessage = null, + $min = null, + $minPropertyPath = null, + $max = null, + $maxPropertyPath = null, + array $groups = null, + $payload = null + ) { + parent::__construct($options, $groups, $payload); + + $this->notInRangeMessage = $notInRangeMessage ?? $this->notInRangeMessage; + $this->minMessage = $minMessage ?? $this->minMessage; + $this->maxMessage = $maxMessage ?? $this->maxMessage; + $this->invalidMessage = $invalidMessage ?? $this->invalidMessage; + $this->invalidDateTimeMessage = $invalidDateTimeMessage ?? $this->invalidDateTimeMessage; + $this->min = $min ?? $this->min; + $this->minPropertyPath = $minPropertyPath ?? $this->minPropertyPath; + $this->max = $max ?? $this->max; + $this->maxPropertyPath = $maxPropertyPath ?? $this->maxPropertyPath; + + if (null === $this->min && null === $this->minPropertyPath && null === $this->max && null === $this->maxPropertyPath) { + throw new MissingOptionsException(sprintf('Either option "min", "minPropertyPath", "max" or "maxPropertyPath" must be given for constraint "%s".', __CLASS__), ['min', 'minPropertyPath', 'max', 'maxPropertyPath']); + } + + if (null !== $this->min && null !== $this->minPropertyPath) { + throw new ConstraintDefinitionException(sprintf('The "%s" constraint requires only one of the "min" or "minPropertyPath" options to be set, not both.', static::class)); + } + + if (null !== $this->max && null !== $this->maxPropertyPath) { + throw new ConstraintDefinitionException(sprintf('The "%s" constraint requires only one of the "max" or "maxPropertyPath" options to be set, not both.', static::class)); + } + + if ((null !== $this->minPropertyPath || null !== $this->maxPropertyPath) && !class_exists(PropertyAccess::class)) { + throw new LogicException(sprintf('The "%s" constraint requires the Symfony PropertyAccess component to use the "minPropertyPath" or "maxPropertyPath" option.', static::class)); + } + + if (null !== $this->min && null !== $this->max) { + $this->deprecatedMinMessageSet = isset($options['minMessage']) || null !== $minMessage; + $this->deprecatedMaxMessageSet = isset($options['maxMessage']) || null !== $maxMessage; + + // BC layer, should throw a ConstraintDefinitionException in 6.0 + if ($this->deprecatedMinMessageSet || $this->deprecatedMaxMessageSet) { + trigger_deprecation('symfony/validator', '4.4', '"minMessage" and "maxMessage" are deprecated when the "min" and "max" options are both set. Use "notInRangeMessage" instead.'); + } + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/RangeValidator.php b/src/vendor/symfony/validator/Constraints/RangeValidator.php new file mode 100644 index 0000000..e24cd87 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/RangeValidator.php @@ -0,0 +1,211 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException; +use Symfony\Component\PropertyAccess\PropertyAccess; +use Symfony\Component\PropertyAccess\PropertyAccessorInterface; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; + +/** + * @author Bernhard Schussek + */ +class RangeValidator extends ConstraintValidator +{ + private $propertyAccessor; + + public function __construct(PropertyAccessorInterface $propertyAccessor = null) + { + $this->propertyAccessor = $propertyAccessor; + } + + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Range) { + throw new UnexpectedTypeException($constraint, Range::class); + } + + if (null === $value) { + return; + } + + $min = $this->getLimit($constraint->minPropertyPath, $constraint->min, $constraint); + $max = $this->getLimit($constraint->maxPropertyPath, $constraint->max, $constraint); + + if (!is_numeric($value) && !$value instanceof \DateTimeInterface) { + if ($this->isParsableDatetimeString($min) && $this->isParsableDatetimeString($max)) { + $this->context->buildViolation($constraint->invalidDateTimeMessage) + ->setParameter('{{ value }}', $this->formatValue($value, self::PRETTY_DATE)) + ->setCode(Range::INVALID_CHARACTERS_ERROR) + ->addViolation(); + } else { + $this->context->buildViolation($constraint->invalidMessage) + ->setParameter('{{ value }}', $this->formatValue($value, self::PRETTY_DATE)) + ->setCode(Range::INVALID_CHARACTERS_ERROR) + ->addViolation(); + } + + return; + } + + // Convert strings to DateTimes if comparing another DateTime + // This allows to compare with any date/time value supported by + // the DateTime constructor: + // https://php.net/datetime.formats + if ($value instanceof \DateTimeInterface) { + $dateTimeClass = null; + + if (\is_string($min)) { + $dateTimeClass = $value instanceof \DateTimeImmutable ? \DateTimeImmutable::class : \DateTime::class; + + try { + $min = new $dateTimeClass($min); + } catch (\Exception $e) { + throw new ConstraintDefinitionException(sprintf('The min value "%s" could not be converted to a "%s" instance in the "%s" constraint.', $min, $dateTimeClass, get_debug_type($constraint))); + } + } + + if (\is_string($max)) { + $dateTimeClass = $dateTimeClass ?: ($value instanceof \DateTimeImmutable ? \DateTimeImmutable::class : \DateTime::class); + + try { + $max = new $dateTimeClass($max); + } catch (\Exception $e) { + throw new ConstraintDefinitionException(sprintf('The max value "%s" could not be converted to a "%s" instance in the "%s" constraint.', $max, $dateTimeClass, get_debug_type($constraint))); + } + } + } + + $hasLowerLimit = null !== $min; + $hasUpperLimit = null !== $max; + + if ($hasLowerLimit && $hasUpperLimit && ($value < $min || $value > $max)) { + $message = $constraint->notInRangeMessage; + $code = Range::NOT_IN_RANGE_ERROR; + + if ($value < $min && $constraint->deprecatedMinMessageSet) { + $message = $constraint->minMessage; + $code = Range::TOO_LOW_ERROR; + } + + if ($value > $max && $constraint->deprecatedMaxMessageSet) { + $message = $constraint->maxMessage; + $code = Range::TOO_HIGH_ERROR; + } + + $violationBuilder = $this->context->buildViolation($message) + ->setParameter('{{ value }}', $this->formatValue($value, self::PRETTY_DATE)) + ->setParameter('{{ min }}', $this->formatValue($min, self::PRETTY_DATE)) + ->setParameter('{{ max }}', $this->formatValue($max, self::PRETTY_DATE)) + ->setCode($code); + + if (null !== $constraint->maxPropertyPath) { + $violationBuilder->setParameter('{{ max_limit_path }}', $constraint->maxPropertyPath); + } + + if (null !== $constraint->minPropertyPath) { + $violationBuilder->setParameter('{{ min_limit_path }}', $constraint->minPropertyPath); + } + + $violationBuilder->addViolation(); + + return; + } + + if ($hasUpperLimit && $value > $max) { + $violationBuilder = $this->context->buildViolation($constraint->maxMessage) + ->setParameter('{{ value }}', $this->formatValue($value, self::PRETTY_DATE)) + ->setParameter('{{ limit }}', $this->formatValue($max, self::PRETTY_DATE)) + ->setCode(Range::TOO_HIGH_ERROR); + + if (null !== $constraint->maxPropertyPath) { + $violationBuilder->setParameter('{{ max_limit_path }}', $constraint->maxPropertyPath); + } + + if (null !== $constraint->minPropertyPath) { + $violationBuilder->setParameter('{{ min_limit_path }}', $constraint->minPropertyPath); + } + + $violationBuilder->addViolation(); + + return; + } + + if ($hasLowerLimit && $value < $min) { + $violationBuilder = $this->context->buildViolation($constraint->minMessage) + ->setParameter('{{ value }}', $this->formatValue($value, self::PRETTY_DATE)) + ->setParameter('{{ limit }}', $this->formatValue($min, self::PRETTY_DATE)) + ->setCode(Range::TOO_LOW_ERROR); + + if (null !== $constraint->maxPropertyPath) { + $violationBuilder->setParameter('{{ max_limit_path }}', $constraint->maxPropertyPath); + } + + if (null !== $constraint->minPropertyPath) { + $violationBuilder->setParameter('{{ min_limit_path }}', $constraint->minPropertyPath); + } + + $violationBuilder->addViolation(); + } + } + + private function getLimit(?string $propertyPath, $default, Constraint $constraint) + { + if (null === $propertyPath) { + return $default; + } + + if (null === $object = $this->context->getObject()) { + return $default; + } + + try { + return $this->getPropertyAccessor()->getValue($object, $propertyPath); + } catch (NoSuchPropertyException $e) { + throw new ConstraintDefinitionException(sprintf('Invalid property path "%s" provided to "%s" constraint: ', $propertyPath, get_debug_type($constraint)).$e->getMessage(), 0, $e); + } + } + + private function getPropertyAccessor(): PropertyAccessorInterface + { + if (null === $this->propertyAccessor) { + $this->propertyAccessor = PropertyAccess::createPropertyAccessor(); + } + + return $this->propertyAccessor; + } + + private function isParsableDatetimeString($boundary): bool + { + if (null === $boundary) { + return true; + } + + if (!\is_string($boundary)) { + return false; + } + + try { + new \DateTime($boundary); + } catch (\Exception $e) { + return false; + } + + return true; + } +} diff --git a/src/vendor/symfony/validator/Constraints/Regex.php b/src/vendor/symfony/validator/Constraints/Regex.php new file mode 100644 index 0000000..63bbd8d --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Regex.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Exception\InvalidArgumentException; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class Regex extends Constraint +{ + public const REGEX_FAILED_ERROR = 'de1e3db3-5ed4-4941-aae4-59f3667cc3a3'; + + protected static $errorNames = [ + self::REGEX_FAILED_ERROR => 'REGEX_FAILED_ERROR', + ]; + + public $message = 'This value is not valid.'; + public $pattern; + public $htmlPattern; + public $match = true; + public $normalizer; + + /** + * {@inheritdoc} + * + * @param string|array $pattern The pattern to evaluate or an array of options + */ + public function __construct( + $pattern, + string $message = null, + string $htmlPattern = null, + bool $match = null, + callable $normalizer = null, + array $groups = null, + $payload = null, + array $options = [] + ) { + if (\is_array($pattern)) { + $options = array_merge($pattern, $options); + } elseif (null !== $pattern) { + $options['value'] = $pattern; + } + + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + $this->htmlPattern = $htmlPattern ?? $this->htmlPattern; + $this->match = $match ?? $this->match; + $this->normalizer = $normalizer ?? $this->normalizer; + + if (null !== $this->normalizer && !\is_callable($this->normalizer)) { + throw new InvalidArgumentException(sprintf('The "normalizer" option must be a valid callable ("%s" given).', get_debug_type($this->normalizer))); + } + } + + /** + * {@inheritdoc} + */ + public function getDefaultOption() + { + return 'pattern'; + } + + /** + * {@inheritdoc} + */ + public function getRequiredOptions() + { + return ['pattern']; + } + + /** + * Converts the htmlPattern to a suitable format for HTML5 pattern. + * Example: /^[a-z]+$/ would be converted to [a-z]+ + * However, if options are specified, it cannot be converted. + * + * @see http://dev.w3.org/html5/spec/single-page.html#the-pattern-attribute + * + * @return string|null + */ + public function getHtmlPattern() + { + // If htmlPattern is specified, use it + if (null !== $this->htmlPattern) { + return empty($this->htmlPattern) + ? null + : $this->htmlPattern; + } + + // Quit if delimiters not at very beginning/end (e.g. when options are passed) + if ($this->pattern[0] !== $this->pattern[\strlen($this->pattern) - 1]) { + return null; + } + + $delimiter = $this->pattern[0]; + + // Unescape the delimiter + $pattern = str_replace('\\'.$delimiter, $delimiter, substr($this->pattern, 1, -1)); + + // If the pattern is inverted, we can wrap it in + // ((?!pattern).)* + if (!$this->match) { + return '((?!'.$pattern.').)*'; + } + + // If the pattern contains an or statement, wrap the pattern in + // .*(pattern).* and quit. Otherwise we'd need to parse the pattern + if (str_contains($pattern, '|')) { + return '.*('.$pattern.').*'; + } + + // Trim leading ^, otherwise prepend .* + $pattern = '^' === $pattern[0] ? substr($pattern, 1) : '.*'.$pattern; + + // Trim trailing $, otherwise append .* + $pattern = '$' === $pattern[\strlen($pattern) - 1] ? substr($pattern, 0, -1) : $pattern.'.*'; + + return $pattern; + } +} diff --git a/src/vendor/symfony/validator/Constraints/RegexValidator.php b/src/vendor/symfony/validator/Constraints/RegexValidator.php new file mode 100644 index 0000000..bf82816 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/RegexValidator.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; +use Symfony\Component\Validator\Exception\UnexpectedValueException; + +/** + * Validates whether a value match or not given regexp pattern. + * + * @author Bernhard Schussek + * @author Joseph Bielawski + */ +class RegexValidator extends ConstraintValidator +{ + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Regex) { + throw new UnexpectedTypeException($constraint, Regex::class); + } + + if (null === $value || '' === $value) { + return; + } + + if (!\is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { + throw new UnexpectedValueException($value, 'string'); + } + + $value = (string) $value; + + if (null !== $constraint->normalizer) { + $value = ($constraint->normalizer)($value); + } + + if ($constraint->match xor preg_match($constraint->pattern, $value)) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Regex::REGEX_FAILED_ERROR) + ->addViolation(); + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/Required.php b/src/vendor/symfony/validator/Constraints/Required.php new file mode 100644 index 0000000..bd77a90 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Required.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +/** + * @Annotation + * @Target({"ANNOTATION"}) + * + * @author Bernhard Schussek + */ +class Required extends Existence +{ +} diff --git a/src/vendor/symfony/validator/Constraints/Sequentially.php b/src/vendor/symfony/validator/Constraints/Sequentially.php new file mode 100644 index 0000000..36a801a --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Sequentially.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +/** + * Use this constraint to sequentially validate nested constraints. + * Validation for the nested constraints collection will stop at first violation. + * + * @Annotation + * @Target({"CLASS", "PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Maxime Steinhausser + */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class Sequentially extends Composite +{ + public $constraints = []; + + public function __construct($constraints = null, array $groups = null, $payload = null) + { + parent::__construct($constraints ?? [], $groups, $payload); + } + + public function getDefaultOption() + { + return 'constraints'; + } + + public function getRequiredOptions() + { + return ['constraints']; + } + + protected function getCompositeOption() + { + return 'constraints'; + } + + public function getTargets() + { + return [self::CLASS_CONSTRAINT, self::PROPERTY_CONSTRAINT]; + } +} diff --git a/src/vendor/symfony/validator/Constraints/SequentiallyValidator.php b/src/vendor/symfony/validator/Constraints/SequentiallyValidator.php new file mode 100644 index 0000000..434d2ab --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/SequentiallyValidator.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; + +/** + * @author Maxime Steinhausser + */ +class SequentiallyValidator extends ConstraintValidator +{ + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Sequentially) { + throw new UnexpectedTypeException($constraint, Sequentially::class); + } + + $context = $this->context; + + $validator = $context->getValidator()->inContext($context); + + $originalCount = $validator->getViolations()->count(); + + foreach ($constraint->constraints as $c) { + if ($originalCount !== $validator->validate($value, $c)->getViolations()->count()) { + break; + } + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/Time.php b/src/vendor/symfony/validator/Constraints/Time.php new file mode 100644 index 0000000..366d623 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Time.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class Time extends Constraint +{ + public const INVALID_FORMAT_ERROR = '9d27b2bb-f755-4fbf-b725-39b1edbdebdf'; + public const INVALID_TIME_ERROR = '8532f9e1-84b2-4d67-8989-0818bc38533b'; + + protected static $errorNames = [ + self::INVALID_FORMAT_ERROR => 'INVALID_FORMAT_ERROR', + self::INVALID_TIME_ERROR => 'INVALID_TIME_ERROR', + ]; + + public $message = 'This value is not a valid time.'; + + public function __construct( + array $options = null, + string $message = null, + array $groups = null, + $payload = null + ) { + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + } +} diff --git a/src/vendor/symfony/validator/Constraints/TimeValidator.php b/src/vendor/symfony/validator/Constraints/TimeValidator.php new file mode 100644 index 0000000..855f320 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/TimeValidator.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; +use Symfony\Component\Validator\Exception\UnexpectedValueException; + +/** + * @author Bernhard Schussek + */ +class TimeValidator extends ConstraintValidator +{ + public const PATTERN = '/^(\d{2}):(\d{2}):(\d{2})$/'; + + /** + * Checks whether a time is valid. + * + * @internal + */ + public static function checkTime(int $hour, int $minute, float $second): bool + { + return $hour >= 0 && $hour < 24 && $minute >= 0 && $minute < 60 && $second >= 0 && $second < 60; + } + + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Time) { + throw new UnexpectedTypeException($constraint, Time::class); + } + + if (null === $value || '' === $value) { + return; + } + + if (!\is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { + throw new UnexpectedValueException($value, 'string'); + } + + $value = (string) $value; + + if (!preg_match(static::PATTERN, $value, $matches)) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Time::INVALID_FORMAT_ERROR) + ->addViolation(); + + return; + } + + if (!self::checkTime($matches[1], $matches[2], $matches[3])) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Time::INVALID_TIME_ERROR) + ->addViolation(); + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/Timezone.php b/src/vendor/symfony/validator/Constraints/Timezone.php new file mode 100644 index 0000000..409fbc1 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Timezone.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Javier Spagnoletti + * @author Hugo Hamon + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class Timezone extends Constraint +{ + public const TIMEZONE_IDENTIFIER_ERROR = '5ce113e6-5e64-4ea2-90fe-d2233956db13'; + public const TIMEZONE_IDENTIFIER_IN_ZONE_ERROR = 'b57767b1-36c0-40ac-a3d7-629420c775b8'; + public const TIMEZONE_IDENTIFIER_IN_COUNTRY_ERROR = 'c4a22222-dc92-4fc0-abb0-d95b268c7d0b'; + public const TIMEZONE_IDENTIFIER_INTL_ERROR = '45863c26-88dc-41ba-bf53-c73bd1f7e90d'; + + public $zone = \DateTimeZone::ALL; + public $countryCode; + public $intlCompatible = false; + public $message = 'This value is not a valid timezone.'; + + protected static $errorNames = [ + self::TIMEZONE_IDENTIFIER_ERROR => 'TIMEZONE_IDENTIFIER_ERROR', + self::TIMEZONE_IDENTIFIER_IN_ZONE_ERROR => 'TIMEZONE_IDENTIFIER_IN_ZONE_ERROR', + self::TIMEZONE_IDENTIFIER_IN_COUNTRY_ERROR => 'TIMEZONE_IDENTIFIER_IN_COUNTRY_ERROR', + self::TIMEZONE_IDENTIFIER_INTL_ERROR => 'TIMEZONE_IDENTIFIER_INTL_ERROR', + ]; + + /** + * {@inheritdoc} + * + * @param int|array|null $zone A combination of {@see \DateTimeZone} class constants or a set of options + */ + public function __construct( + $zone = null, + string $message = null, + string $countryCode = null, + bool $intlCompatible = null, + array $groups = null, + $payload = null, + array $options = [] + ) { + if (\is_array($zone)) { + $options = array_merge($zone, $options); + } elseif (null !== $zone) { + $options['value'] = $zone; + } + + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + $this->countryCode = $countryCode ?? $this->countryCode; + $this->intlCompatible = $intlCompatible ?? $this->intlCompatible; + + if (null === $this->countryCode) { + if (0 >= $this->zone || \DateTimeZone::ALL_WITH_BC < $this->zone) { + throw new ConstraintDefinitionException('The option "zone" must be a valid range of "\DateTimeZone" constants.'); + } + } elseif (\DateTimeZone::PER_COUNTRY !== (\DateTimeZone::PER_COUNTRY & $this->zone)) { + throw new ConstraintDefinitionException('The option "countryCode" can only be used when the "zone" option is configured with "\DateTimeZone::PER_COUNTRY".'); + } + if ($this->intlCompatible && !class_exists(\IntlTimeZone::class)) { + throw new ConstraintDefinitionException('The option "intlCompatible" can only be used when the PHP intl extension is available.'); + } + } + + /** + * {@inheritdoc} + */ + public function getDefaultOption() + { + return 'zone'; + } +} diff --git a/src/vendor/symfony/validator/Constraints/TimezoneValidator.php b/src/vendor/symfony/validator/Constraints/TimezoneValidator.php new file mode 100644 index 0000000..a83d78c --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/TimezoneValidator.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Intl\Exception\MissingResourceException; +use Symfony\Component\Intl\Timezones; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; +use Symfony\Component\Validator\Exception\UnexpectedValueException; + +/** + * Validates whether a value is a valid timezone identifier. + * + * @author Javier Spagnoletti + * @author Hugo Hamon + */ +class TimezoneValidator extends ConstraintValidator +{ + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Timezone) { + throw new UnexpectedTypeException($constraint, Timezone::class); + } + + if (null === $value || '' === $value) { + return; + } + + if (!\is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { + throw new UnexpectedValueException($value, 'string'); + } + + $value = (string) $value; + + if ($constraint->intlCompatible && 'Etc/Unknown' === \IntlTimeZone::createTimeZone($value)->getID()) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Timezone::TIMEZONE_IDENTIFIER_INTL_ERROR) + ->addViolation(); + + return; + } + + if ( + \in_array($value, self::getPhpTimezones($constraint->zone, $constraint->countryCode), true) || + \in_array($value, self::getIntlTimezones($constraint->zone, $constraint->countryCode), true) + ) { + return; + } + + if ($constraint->countryCode) { + $code = Timezone::TIMEZONE_IDENTIFIER_IN_COUNTRY_ERROR; + } elseif (\DateTimeZone::ALL !== $constraint->zone) { + $code = Timezone::TIMEZONE_IDENTIFIER_IN_ZONE_ERROR; + } else { + $code = Timezone::TIMEZONE_IDENTIFIER_ERROR; + } + + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode($code) + ->addViolation(); + } + + private static function getPhpTimezones(int $zone, string $countryCode = null): array + { + if (null !== $countryCode) { + try { + return @\DateTimeZone::listIdentifiers($zone, $countryCode) ?: []; + } catch (\ValueError $e) { + return []; + } + } + + return \DateTimeZone::listIdentifiers($zone); + } + + private static function getIntlTimezones(int $zone, string $countryCode = null): array + { + if (!class_exists(Timezones::class)) { + return []; + } + + if (null !== $countryCode) { + try { + return Timezones::forCountryCode($countryCode); + } catch (MissingResourceException $e) { + return []; + } + } + + $timezones = Timezones::getIds(); + + if (\DateTimeZone::ALL === (\DateTimeZone::ALL & $zone)) { + return $timezones; + } + + $filtered = []; + foreach ((new \ReflectionClass(\DateTimeZone::class))->getConstants() as $const => $flag) { + if ($flag !== ($flag & $zone)) { + continue; + } + + $filtered[] = array_filter($timezones, static function ($id) use ($const) { + return 0 === stripos($id, $const.'/'); + }); + } + + return $filtered ? array_merge(...$filtered) : []; + } +} diff --git a/src/vendor/symfony/validator/Constraints/Traverse.php b/src/vendor/symfony/validator/Constraints/Traverse.php new file mode 100644 index 0000000..fe6527d --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Traverse.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; + +/** + * @Annotation + * + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_CLASS)] +class Traverse extends Constraint +{ + public $traverse = true; + + /** + * @param bool|array|null $traverse + */ + public function __construct($traverse = null) + { + if (\is_array($traverse) && \array_key_exists('groups', $traverse)) { + throw new ConstraintDefinitionException(sprintf('The option "groups" is not supported by the constraint "%s".', __CLASS__)); + } + + parent::__construct($traverse); + } + + /** + * {@inheritdoc} + */ + public function getDefaultOption() + { + return 'traverse'; + } + + /** + * {@inheritdoc} + */ + public function getTargets() + { + return self::CLASS_CONSTRAINT; + } +} diff --git a/src/vendor/symfony/validator/Constraints/Type.php b/src/vendor/symfony/validator/Constraints/Type.php new file mode 100644 index 0000000..220c219 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Type.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class Type extends Constraint +{ + public const INVALID_TYPE_ERROR = 'ba785a8c-82cb-4283-967c-3cf342181b40'; + + protected static $errorNames = [ + self::INVALID_TYPE_ERROR => 'INVALID_TYPE_ERROR', + ]; + + public $message = 'This value should be of type {{ type }}.'; + public $type; + + /** + * {@inheritdoc} + * + * @param string|array $type One ore multiple types to validate against or a set of options + */ + public function __construct($type, string $message = null, array $groups = null, $payload = null, array $options = []) + { + if (\is_array($type) && \is_string(key($type))) { + $options = array_merge($type, $options); + } elseif (null !== $type) { + $options['value'] = $type; + } + + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + } + + /** + * {@inheritdoc} + */ + public function getDefaultOption() + { + return 'type'; + } + + /** + * {@inheritdoc} + */ + public function getRequiredOptions() + { + return ['type']; + } +} diff --git a/src/vendor/symfony/validator/Constraints/TypeValidator.php b/src/vendor/symfony/validator/Constraints/TypeValidator.php new file mode 100644 index 0000000..0a938c6 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/TypeValidator.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; + +/** + * @author Bernhard Schussek + */ +class TypeValidator extends ConstraintValidator +{ + private const VALIDATION_FUNCTIONS = [ + 'bool' => 'is_bool', + 'boolean' => 'is_bool', + 'int' => 'is_int', + 'integer' => 'is_int', + 'long' => 'is_int', + 'float' => 'is_float', + 'double' => 'is_float', + 'real' => 'is_float', + 'numeric' => 'is_numeric', + 'string' => 'is_string', + 'scalar' => 'is_scalar', + 'array' => 'is_array', + 'iterable' => 'is_iterable', + 'countable' => 'is_countable', + 'callable' => 'is_callable', + 'object' => 'is_object', + 'resource' => 'is_resource', + 'null' => 'is_null', + 'alnum' => 'ctype_alnum', + 'alpha' => 'ctype_alpha', + 'cntrl' => 'ctype_cntrl', + 'digit' => 'ctype_digit', + 'graph' => 'ctype_graph', + 'lower' => 'ctype_lower', + 'print' => 'ctype_print', + 'punct' => 'ctype_punct', + 'space' => 'ctype_space', + 'upper' => 'ctype_upper', + 'xdigit' => 'ctype_xdigit', + ]; + + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Type) { + throw new UnexpectedTypeException($constraint, Type::class); + } + + if (null === $value) { + return; + } + + $types = (array) $constraint->type; + + foreach ($types as $type) { + $type = strtolower($type); + if (isset(self::VALIDATION_FUNCTIONS[$type]) && self::VALIDATION_FUNCTIONS[$type]($value)) { + return; + } + + if ($value instanceof $type) { + return; + } + } + + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setParameter('{{ type }}', implode('|', $types)) + ->setCode(Type::INVALID_TYPE_ERROR) + ->addViolation(); + } +} diff --git a/src/vendor/symfony/validator/Constraints/Ulid.php b/src/vendor/symfony/validator/Constraints/Ulid.php new file mode 100644 index 0000000..d1644b8 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Ulid.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; + +/** + * @Annotation + * + * @author Laurent Clouet + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class Ulid extends Constraint +{ + public const TOO_SHORT_ERROR = '7b44804e-37d5-4df4-9bdd-b738d4a45bb4'; + public const TOO_LONG_ERROR = '9608249f-6da1-4d53-889e-9864b58c4d37'; + public const INVALID_CHARACTERS_ERROR = 'e4155739-5135-4258-9c81-ae7b44b5311e'; + public const TOO_LARGE_ERROR = 'df8cfb9a-ce6d-4a69-ae5a-eea7ab6f278b'; + + protected static $errorNames = [ + self::TOO_SHORT_ERROR => 'TOO_SHORT_ERROR', + self::TOO_LONG_ERROR => 'TOO_LONG_ERROR', + self::INVALID_CHARACTERS_ERROR => 'INVALID_CHARACTERS_ERROR', + self::TOO_LARGE_ERROR => 'TOO_LARGE_ERROR', + ]; + + public $message = 'This is not a valid ULID.'; + + public function __construct( + array $options = null, + string $message = null, + array $groups = null, + $payload = null + ) { + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + } +} diff --git a/src/vendor/symfony/validator/Constraints/UlidValidator.php b/src/vendor/symfony/validator/Constraints/UlidValidator.php new file mode 100644 index 0000000..ad74124 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/UlidValidator.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; +use Symfony\Component\Validator\Exception\UnexpectedValueException; + +/** + * Validates whether the value is a valid ULID (Universally Unique Lexicographically Sortable Identifier). + * Cf https://github.com/ulid/spec for ULID specifications. + * + * @author Laurent Clouet + */ +class UlidValidator extends ConstraintValidator +{ + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Ulid) { + throw new UnexpectedTypeException($constraint, Ulid::class); + } + + if (null === $value || '' === $value) { + return; + } + + if (!\is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { + throw new UnexpectedValueException($value, 'string'); + } + + $value = (string) $value; + + if (26 !== \strlen($value)) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(26 > \strlen($value) ? Ulid::TOO_SHORT_ERROR : Ulid::TOO_LONG_ERROR) + ->addViolation(); + + return; + } + + if (\strlen($value) !== strspn($value, '0123456789ABCDEFGHJKMNPQRSTVWXYZabcdefghjkmnpqrstvwxyz')) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Ulid::INVALID_CHARACTERS_ERROR) + ->addViolation(); + + return; + } + + // Largest valid ULID is '7ZZZZZZZZZZZZZZZZZZZZZZZZZ' + // Cf https://github.com/ulid/spec#overflow-errors-when-parsing-base32-strings + if ($value[0] > '7') { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Ulid::TOO_LARGE_ERROR) + ->addViolation(); + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/Unique.php b/src/vendor/symfony/validator/Constraints/Unique.php new file mode 100644 index 0000000..6280e97 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Unique.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Exception\InvalidArgumentException; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Yevgeniy Zholkevskiy + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class Unique extends Constraint +{ + public const IS_NOT_UNIQUE = '7911c98d-b845-4da0-94b7-a8dac36bc55a'; + + protected static $errorNames = [ + self::IS_NOT_UNIQUE => 'IS_NOT_UNIQUE', + ]; + + public $message = 'This collection should contain only unique elements.'; + public $normalizer; + + public function __construct( + array $options = null, + string $message = null, + callable $normalizer = null, + array $groups = null, + $payload = null + ) { + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + $this->normalizer = $normalizer ?? $this->normalizer; + + if (null !== $this->normalizer && !\is_callable($this->normalizer)) { + throw new InvalidArgumentException(sprintf('The "normalizer" option must be a valid callable ("%s" given).', get_debug_type($this->normalizer))); + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/UniqueValidator.php b/src/vendor/symfony/validator/Constraints/UniqueValidator.php new file mode 100644 index 0000000..2758a3f --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/UniqueValidator.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; +use Symfony\Component\Validator\Exception\UnexpectedValueException; + +/** + * @author Yevgeniy Zholkevskiy + */ +class UniqueValidator extends ConstraintValidator +{ + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Unique) { + throw new UnexpectedTypeException($constraint, Unique::class); + } + + if (null === $value) { + return; + } + + if (!\is_array($value) && !$value instanceof \IteratorAggregate) { + throw new UnexpectedValueException($value, 'array|IteratorAggregate'); + } + + $collectionElements = []; + $normalizer = $this->getNormalizer($constraint); + foreach ($value as $element) { + $element = $normalizer($element); + + if (\in_array($element, $collectionElements, true)) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Unique::IS_NOT_UNIQUE) + ->addViolation(); + + return; + } + $collectionElements[] = $element; + } + } + + private function getNormalizer(Unique $unique): callable + { + if (null === $unique->normalizer) { + return static function ($value) { + return $value; + }; + } + + return $unique->normalizer; + } +} diff --git a/src/vendor/symfony/validator/Constraints/Url.php b/src/vendor/symfony/validator/Constraints/Url.php new file mode 100644 index 0000000..23cd77c --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Url.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Exception\InvalidArgumentException; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class Url extends Constraint +{ + public const INVALID_URL_ERROR = '57c2f299-1154-4870-89bb-ef3b1f5ad229'; + + protected static $errorNames = [ + self::INVALID_URL_ERROR => 'INVALID_URL_ERROR', + ]; + + public $message = 'This value is not a valid URL.'; + public $protocols = ['http', 'https']; + public $relativeProtocol = false; + public $normalizer; + + public function __construct( + array $options = null, + string $message = null, + array $protocols = null, + bool $relativeProtocol = null, + callable $normalizer = null, + array $groups = null, + $payload = null + ) { + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + $this->protocols = $protocols ?? $this->protocols; + $this->relativeProtocol = $relativeProtocol ?? $this->relativeProtocol; + $this->normalizer = $normalizer ?? $this->normalizer; + + if (null !== $this->normalizer && !\is_callable($this->normalizer)) { + throw new InvalidArgumentException(sprintf('The "normalizer" option must be a valid callable ("%s" given).', get_debug_type($this->normalizer))); + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/UrlValidator.php b/src/vendor/symfony/validator/Constraints/UrlValidator.php new file mode 100644 index 0000000..dff0a99 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/UrlValidator.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; +use Symfony\Component\Validator\Exception\UnexpectedValueException; + +/** + * @author Bernhard Schussek + */ +class UrlValidator extends ConstraintValidator +{ + public const PATTERN = '~^ + (%s):// # protocol + (((?:[\_\.\pL\pN-]|%%[0-9A-Fa-f]{2})+:)?((?:[\_\.\pL\pN-]|%%[0-9A-Fa-f]{2})+)@)? # basic auth + ( + (?: + (?:xn--[a-z0-9-]++\.)*+xn--[a-z0-9-]++ # a domain name using punycode + | + (?:[\pL\pN\pS\pM\-\_]++\.)+[\pL\pN\pM]++ # a multi-level domain name + | + [a-z0-9\-\_]++ # a single-level domain name + )\.? + | # or + \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} # an IP address + | # or + \[ + (?:(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){6})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:::(?:(?:(?:[0-9a-f]{1,4})):){5})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){4})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,1}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){3})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,2}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){2})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,3}(?:(?:[0-9a-f]{1,4})))?::(?:(?:[0-9a-f]{1,4})):)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,4}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,5}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,6}(?:(?:[0-9a-f]{1,4})))?::)))) + \] # an IPv6 address + ) + (:[0-9]+)? # a port (optional) + (?:/ (?:[\pL\pN\-._\~!$&\'()*+,;=:@]|%%[0-9A-Fa-f]{2})* )* # a path + (?:\? (?:[\pL\pN\-._\~!$&\'\[\]()*+,;=:@/?]|%%[0-9A-Fa-f]{2})* )? # a query (optional) + (?:\# (?:[\pL\pN\-._\~!$&\'()*+,;=:@/?]|%%[0-9A-Fa-f]{2})* )? # a fragment (optional) + $~ixu'; + + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Url) { + throw new UnexpectedTypeException($constraint, Url::class); + } + + if (null === $value || '' === $value) { + return; + } + + if (!\is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { + throw new UnexpectedValueException($value, 'string'); + } + + $value = (string) $value; + if ('' === $value) { + return; + } + + if (null !== $constraint->normalizer) { + $value = ($constraint->normalizer)($value); + } + + $pattern = $constraint->relativeProtocol ? str_replace('(%s):', '(?:(%s):)?', static::PATTERN) : static::PATTERN; + $pattern = sprintf($pattern, implode('|', $constraint->protocols)); + + if (!preg_match($pattern, $value)) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Url::INVALID_URL_ERROR) + ->addViolation(); + + return; + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/Uuid.php b/src/vendor/symfony/validator/Constraints/Uuid.php new file mode 100644 index 0000000..84f83f8 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Uuid.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Exception\InvalidArgumentException; + +/** + * @Annotation + * + * @author Colin O'Dell + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class Uuid extends Constraint +{ + public const TOO_SHORT_ERROR = 'aa314679-dac9-4f54-bf97-b2049df8f2a3'; + public const TOO_LONG_ERROR = '494897dd-36f8-4d31-8923-71a8d5f3000d'; + public const INVALID_CHARACTERS_ERROR = '51120b12-a2bc-41bf-aa53-cd73daf330d0'; + public const INVALID_HYPHEN_PLACEMENT_ERROR = '98469c83-0309-4f5d-bf95-a496dcaa869c'; + public const INVALID_VERSION_ERROR = '21ba13b4-b185-4882-ac6f-d147355987eb'; + public const INVALID_VARIANT_ERROR = '164ef693-2b9d-46de-ad7f-836201f0c2db'; + + protected static $errorNames = [ + self::TOO_SHORT_ERROR => 'TOO_SHORT_ERROR', + self::TOO_LONG_ERROR => 'TOO_LONG_ERROR', + self::INVALID_CHARACTERS_ERROR => 'INVALID_CHARACTERS_ERROR', + self::INVALID_HYPHEN_PLACEMENT_ERROR => 'INVALID_HYPHEN_PLACEMENT_ERROR', + self::INVALID_VERSION_ERROR => 'INVALID_VERSION_ERROR', + self::INVALID_VARIANT_ERROR => 'INVALID_VARIANT_ERROR', + ]; + + // Possible versions defined by RFC 4122 + public const V1_MAC = 1; + public const V2_DCE = 2; + public const V3_MD5 = 3; + public const V4_RANDOM = 4; + public const V5_SHA1 = 5; + public const V6_SORTABLE = 6; + + public const ALL_VERSIONS = [ + self::V1_MAC, + self::V2_DCE, + self::V3_MD5, + self::V4_RANDOM, + self::V5_SHA1, + self::V6_SORTABLE, + ]; + + /** + * Message to display when validation fails. + * + * @var string + */ + public $message = 'This is not a valid UUID.'; + + /** + * Strict mode only allows UUIDs that meet the formal definition and formatting per RFC 4122. + * + * Set this to `false` to allow legacy formats with different dash positioning or wrapping characters + * + * @var bool + */ + public $strict = true; + + /** + * Array of allowed versions (see version constants above). + * + * All UUID versions are allowed by default + * + * @var int[] + */ + public $versions = self::ALL_VERSIONS; + + public $normalizer; + + /** + * {@inheritdoc} + * + * @param int[]|null $versions + */ + public function __construct( + array $options = null, + string $message = null, + array $versions = null, + bool $strict = null, + callable $normalizer = null, + array $groups = null, + $payload = null + ) { + parent::__construct($options, $groups, $payload); + + $this->message = $message ?? $this->message; + $this->versions = $versions ?? $this->versions; + $this->strict = $strict ?? $this->strict; + $this->normalizer = $normalizer ?? $this->normalizer; + + if (null !== $this->normalizer && !\is_callable($this->normalizer)) { + throw new InvalidArgumentException(sprintf('The "normalizer" option must be a valid callable ("%s" given).', get_debug_type($this->normalizer))); + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/UuidValidator.php b/src/vendor/symfony/validator/Constraints/UuidValidator.php new file mode 100644 index 0000000..df530e9 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/UuidValidator.php @@ -0,0 +1,258 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; +use Symfony\Component\Validator\Exception\UnexpectedValueException; + +/** + * Validates whether the value is a valid UUID (also known as GUID). + * + * Strict validation will allow a UUID as specified per RFC 4122. + * Loose validation will allow any type of UUID. + * + * @author Colin O'Dell + * @author Bernhard Schussek + * + * @see http://tools.ietf.org/html/rfc4122 + * @see https://en.wikipedia.org/wiki/Universally_unique_identifier + */ +class UuidValidator extends ConstraintValidator +{ + // The strict pattern matches UUIDs like this: + // xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx + + // Roughly speaking: + // x = any hexadecimal character + // M = any allowed version {1..6} + // N = any allowed variant {8, 9, a, b} + + public const STRICT_LENGTH = 36; + public const STRICT_FIRST_HYPHEN_POSITION = 8; + public const STRICT_LAST_HYPHEN_POSITION = 23; + public const STRICT_VERSION_POSITION = 14; + public const STRICT_VARIANT_POSITION = 19; + + // The loose pattern validates similar yet non-compliant UUIDs. + // Hyphens are completely optional. If present, they should only appear + // between every fourth character: + // xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx + // xxxxxxxxxxxx-xxxx-xxxx-xxxx-xxxx-xxxx + // xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + + // The value can also be wrapped with characters like []{}: + // {xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx} + + // Neither the version nor the variant is validated by this pattern. + + public const LOOSE_MAX_LENGTH = 39; + public const LOOSE_FIRST_HYPHEN_POSITION = 4; + + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Uuid) { + throw new UnexpectedTypeException($constraint, Uuid::class); + } + + if (null === $value || '' === $value) { + return; + } + + if (!\is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { + throw new UnexpectedValueException($value, 'string'); + } + + $value = (string) $value; + + if (null !== $constraint->normalizer) { + $value = ($constraint->normalizer)($value); + } + + if ($constraint->strict) { + $this->validateStrict($value, $constraint); + + return; + } + + $this->validateLoose($value, $constraint); + } + + private function validateLoose(string $value, Uuid $constraint) + { + // Error priority: + // 1. ERROR_INVALID_CHARACTERS + // 2. ERROR_INVALID_HYPHEN_PLACEMENT + // 3. ERROR_TOO_SHORT/ERROR_TOO_LONG + + // Trim any wrapping characters like [] or {} used by some legacy systems + $trimmed = trim($value, '[]{}'); + + // Position of the next expected hyphen + $h = self::LOOSE_FIRST_HYPHEN_POSITION; + + // Expected length + $l = self::LOOSE_MAX_LENGTH; + + for ($i = 0; $i < $l; ++$i) { + // Check length + if (!isset($trimmed[$i])) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Uuid::TOO_SHORT_ERROR) + ->addViolation(); + + return; + } + + // Hyphens must occur every fifth position + // xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx + // ^ ^ ^ ^ ^ ^ ^ + if ('-' === $trimmed[$i]) { + if ($i !== $h) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Uuid::INVALID_HYPHEN_PLACEMENT_ERROR) + ->addViolation(); + + return; + } + + $h += 5; + + continue; + } + + // Missing hyphens are ignored + if ($i === $h) { + $h += 4; + --$l; + } + + // Check characters + if (!ctype_xdigit($trimmed[$i])) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Uuid::INVALID_CHARACTERS_ERROR) + ->addViolation(); + + return; + } + } + + // Check length again + if (isset($trimmed[$i])) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Uuid::TOO_LONG_ERROR) + ->addViolation(); + } + } + + private function validateStrict(string $value, Uuid $constraint) + { + // Error priority: + // 1. ERROR_INVALID_CHARACTERS + // 2. ERROR_INVALID_HYPHEN_PLACEMENT + // 3. ERROR_TOO_SHORT/ERROR_TOO_LONG + // 4. ERROR_INVALID_VERSION + // 5. ERROR_INVALID_VARIANT + + // Position of the next expected hyphen + $h = self::STRICT_FIRST_HYPHEN_POSITION; + + for ($i = 0; $i < self::STRICT_LENGTH; ++$i) { + // Check length + if (!isset($value[$i])) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Uuid::TOO_SHORT_ERROR) + ->addViolation(); + + return; + } + + // Check hyphen placement + // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + // ^ ^ ^ ^ + if ('-' === $value[$i]) { + if ($i !== $h) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Uuid::INVALID_HYPHEN_PLACEMENT_ERROR) + ->addViolation(); + + return; + } + + // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + // ^ + if ($h < self::STRICT_LAST_HYPHEN_POSITION) { + $h += 5; + } + + continue; + } + + // Check characters + if (!ctype_xdigit($value[$i])) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Uuid::INVALID_CHARACTERS_ERROR) + ->addViolation(); + + return; + } + + // Missing hyphen + if ($i === $h) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Uuid::INVALID_HYPHEN_PLACEMENT_ERROR) + ->addViolation(); + + return; + } + } + + // Check length again + if (isset($value[$i])) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Uuid::TOO_LONG_ERROR) + ->addViolation(); + } + + // Check version + if (!\in_array($value[self::STRICT_VERSION_POSITION], $constraint->versions)) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Uuid::INVALID_VERSION_ERROR) + ->addViolation(); + } + + // Check variant - first two bits must equal "10" + // 0b10xx + // & 0b1100 (12) + // = 0b1000 (8) + if (8 !== (hexdec($value[self::STRICT_VARIANT_POSITION]) & 12)) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Uuid::INVALID_VARIANT_ERROR) + ->addViolation(); + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/Valid.php b/src/vendor/symfony/validator/Constraints/Valid.php new file mode 100644 index 0000000..e000063 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/Valid.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Bernhard Schussek + */ +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class Valid extends Constraint +{ + public $traverse = true; + + public function __construct(array $options = null, array $groups = null, $payload = null, bool $traverse = null) + { + parent::__construct($options ?? [], $groups, $payload); + + $this->traverse = $traverse ?? $this->traverse; + } + + public function __get(string $option) + { + if ('groups' === $option) { + // when this is reached, no groups have been configured + return null; + } + + return parent::__get($option); + } + + /** + * {@inheritdoc} + */ + public function addImplicitGroupName(string $group) + { + if (null !== $this->groups) { + parent::addImplicitGroupName($group); + } + } +} diff --git a/src/vendor/symfony/validator/Constraints/ValidValidator.php b/src/vendor/symfony/validator/Constraints/ValidValidator.php new file mode 100644 index 0000000..85eabb8 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/ValidValidator.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; + +/** + * @author Christian Flothmann + */ +class ValidValidator extends ConstraintValidator +{ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Valid) { + throw new UnexpectedTypeException($constraint, Valid::class); + } + + if (null === $value) { + return; + } + + $this->context + ->getValidator() + ->inContext($this->context) + ->validate($value, null, $this->context->getGroup()); + } +} diff --git a/src/vendor/symfony/validator/Constraints/ZeroComparisonConstraintTrait.php b/src/vendor/symfony/validator/Constraints/ZeroComparisonConstraintTrait.php new file mode 100644 index 0000000..b65fcf2 --- /dev/null +++ b/src/vendor/symfony/validator/Constraints/ZeroComparisonConstraintTrait.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; + +/** + * @internal + * + * @author Jan Schädlich + * @author Alexander M. Turek + */ +trait ZeroComparisonConstraintTrait +{ + public function __construct(array $options = null, string $message = null, array $groups = null, $payload = null) + { + if (null === $options) { + $options = []; + } + + if (isset($options['propertyPath'])) { + throw new ConstraintDefinitionException(sprintf('The "propertyPath" option of the "%s" constraint cannot be set.', static::class)); + } + + if (isset($options['value'])) { + throw new ConstraintDefinitionException(sprintf('The "value" option of the "%s" constraint cannot be set.', static::class)); + } + + parent::__construct(0, null, $message, $groups, $payload, $options); + } + + public function validatedBy(): string + { + return parent::class.'Validator'; + } +} diff --git a/src/vendor/symfony/validator/ContainerConstraintValidatorFactory.php b/src/vendor/symfony/validator/ContainerConstraintValidatorFactory.php new file mode 100644 index 0000000..0b5baed --- /dev/null +++ b/src/vendor/symfony/validator/ContainerConstraintValidatorFactory.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator; + +use Psr\Container\ContainerInterface; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; +use Symfony\Component\Validator\Exception\ValidatorException; + +/** + * Uses a service container to create constraint validators. + * + * @author Kris Wallsmith + */ +class ContainerConstraintValidatorFactory implements ConstraintValidatorFactoryInterface +{ + private $container; + private $validators; + + public function __construct(ContainerInterface $container) + { + $this->container = $container; + $this->validators = []; + } + + /** + * {@inheritdoc} + * + * @throws ValidatorException When the validator class does not exist + * @throws UnexpectedTypeException When the validator is not an instance of ConstraintValidatorInterface + */ + public function getInstance(Constraint $constraint) + { + $name = $constraint->validatedBy(); + + if (!isset($this->validators[$name])) { + if ($this->container->has($name)) { + $this->validators[$name] = $this->container->get($name); + } else { + if (!class_exists($name)) { + throw new ValidatorException(sprintf('Constraint validator "%s" does not exist or is not enabled. Check the "validatedBy" method in your constraint class "%s".', $name, get_debug_type($constraint))); + } + + $this->validators[$name] = new $name(); + } + } + + if (!$this->validators[$name] instanceof ConstraintValidatorInterface) { + throw new UnexpectedTypeException($this->validators[$name], ConstraintValidatorInterface::class); + } + + return $this->validators[$name]; + } +} diff --git a/src/vendor/symfony/validator/Context/ExecutionContext.php b/src/vendor/symfony/validator/Context/ExecutionContext.php new file mode 100644 index 0000000..c640da3 --- /dev/null +++ b/src/vendor/symfony/validator/Context/ExecutionContext.php @@ -0,0 +1,372 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Context; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintViolation; +use Symfony\Component\Validator\ConstraintViolationList; +use Symfony\Component\Validator\ConstraintViolationListInterface; +use Symfony\Component\Validator\Mapping\ClassMetadataInterface; +use Symfony\Component\Validator\Mapping\MemberMetadata; +use Symfony\Component\Validator\Mapping\MetadataInterface; +use Symfony\Component\Validator\Mapping\PropertyMetadataInterface; +use Symfony\Component\Validator\Util\PropertyPath; +use Symfony\Component\Validator\Validator\LazyProperty; +use Symfony\Component\Validator\Validator\ValidatorInterface; +use Symfony\Component\Validator\Violation\ConstraintViolationBuilder; +use Symfony\Component\Validator\Violation\ConstraintViolationBuilderInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * The context used and created by {@link ExecutionContextFactory}. + * + * @author Bernhard Schussek + * + * @see ExecutionContextInterface + * + * @internal since version 2.5. Code against ExecutionContextInterface instead. + */ +class ExecutionContext implements ExecutionContextInterface +{ + /** + * @var ValidatorInterface + */ + private $validator; + + /** + * The root value of the validated object graph. + * + * @var mixed + */ + private $root; + + /** + * @var TranslatorInterface + */ + private $translator; + + /** + * @var string|null + */ + private $translationDomain; + + /** + * The violations generated in the current context. + * + * @var ConstraintViolationList + */ + private $violations; + + /** + * The currently validated value. + * + * @var mixed + */ + private $value; + + /** + * The currently validated object. + * + * @var object|null + */ + private $object; + + /** + * The property path leading to the current value. + * + * @var string + */ + private $propertyPath = ''; + + /** + * The current validation metadata. + * + * @var MetadataInterface|null + */ + private $metadata; + + /** + * The currently validated group. + * + * @var string|null + */ + private $group; + + /** + * The currently validated constraint. + * + * @var Constraint|null + */ + private $constraint; + + /** + * Stores which objects have been validated in which group. + * + * @var bool[][] + */ + private $validatedObjects = []; + + /** + * Stores which class constraint has been validated for which object. + * + * @var bool[] + */ + private $validatedConstraints = []; + + /** + * Stores which objects have been initialized. + * + * @var bool[] + */ + private $initializedObjects; + + /** + * @var \SplObjectStorage + */ + private $cachedObjectsRefs; + + /** + * @param mixed $root The root value of the validated object graph + * + * @internal Called by {@link ExecutionContextFactory}. Should not be used in user code. + */ + public function __construct(ValidatorInterface $validator, $root, TranslatorInterface $translator, string $translationDomain = null) + { + $this->validator = $validator; + $this->root = $root; + $this->translator = $translator; + $this->translationDomain = $translationDomain; + $this->violations = new ConstraintViolationList(); + $this->cachedObjectsRefs = new \SplObjectStorage(); + } + + /** + * {@inheritdoc} + */ + public function setNode($value, ?object $object, MetadataInterface $metadata = null, string $propertyPath) + { + $this->value = $value; + $this->object = $object; + $this->metadata = $metadata; + $this->propertyPath = $propertyPath; + } + + /** + * {@inheritdoc} + */ + public function setGroup(?string $group) + { + $this->group = $group; + } + + /** + * {@inheritdoc} + */ + public function setConstraint(Constraint $constraint) + { + $this->constraint = $constraint; + } + + /** + * {@inheritdoc} + */ + public function addViolation(string $message, array $parameters = []) + { + $this->violations->add(new ConstraintViolation( + $this->translator->trans($message, $parameters, $this->translationDomain), + $message, + $parameters, + $this->root, + $this->propertyPath, + $this->getValue(), + null, + null, + $this->constraint + )); + } + + /** + * {@inheritdoc} + */ + public function buildViolation(string $message, array $parameters = []): ConstraintViolationBuilderInterface + { + return new ConstraintViolationBuilder( + $this->violations, + $this->constraint, + $message, + $parameters, + $this->root, + $this->propertyPath, + $this->getValue(), + $this->translator, + $this->translationDomain + ); + } + + /** + * {@inheritdoc} + */ + public function getViolations(): ConstraintViolationListInterface + { + return $this->violations; + } + + /** + * {@inheritdoc} + */ + public function getValidator(): ValidatorInterface + { + return $this->validator; + } + + /** + * {@inheritdoc} + */ + public function getRoot() + { + return $this->root; + } + + /** + * {@inheritdoc} + */ + public function getValue() + { + if ($this->value instanceof LazyProperty) { + return $this->value->getPropertyValue(); + } + + return $this->value; + } + + /** + * {@inheritdoc} + */ + public function getObject() + { + return $this->object; + } + + /** + * {@inheritdoc} + */ + public function getMetadata(): ?MetadataInterface + { + return $this->metadata; + } + + /** + * {@inheritdoc} + */ + public function getGroup(): ?string + { + return $this->group; + } + + public function getConstraint(): ?Constraint + { + return $this->constraint; + } + + /** + * {@inheritdoc} + */ + public function getClassName(): ?string + { + return $this->metadata instanceof MemberMetadata || $this->metadata instanceof ClassMetadataInterface ? $this->metadata->getClassName() : null; + } + + /** + * {@inheritdoc} + */ + public function getPropertyName(): ?string + { + return $this->metadata instanceof PropertyMetadataInterface ? $this->metadata->getPropertyName() : null; + } + + /** + * {@inheritdoc} + */ + public function getPropertyPath(string $subPath = ''): string + { + return PropertyPath::append($this->propertyPath, $subPath); + } + + /** + * {@inheritdoc} + */ + public function markGroupAsValidated(string $cacheKey, string $groupHash) + { + if (!isset($this->validatedObjects[$cacheKey])) { + $this->validatedObjects[$cacheKey] = []; + } + + $this->validatedObjects[$cacheKey][$groupHash] = true; + } + + /** + * {@inheritdoc} + */ + public function isGroupValidated(string $cacheKey, string $groupHash): bool + { + return isset($this->validatedObjects[$cacheKey][$groupHash]); + } + + /** + * {@inheritdoc} + */ + public function markConstraintAsValidated(string $cacheKey, string $constraintHash) + { + $this->validatedConstraints[$cacheKey.':'.$constraintHash] = true; + } + + /** + * {@inheritdoc} + */ + public function isConstraintValidated(string $cacheKey, string $constraintHash): bool + { + return isset($this->validatedConstraints[$cacheKey.':'.$constraintHash]); + } + + /** + * {@inheritdoc} + */ + public function markObjectAsInitialized(string $cacheKey) + { + $this->initializedObjects[$cacheKey] = true; + } + + /** + * {@inheritdoc} + */ + public function isObjectInitialized(string $cacheKey): bool + { + return isset($this->initializedObjects[$cacheKey]); + } + + /** + * @internal + */ + public function generateCacheKey(object $object): string + { + if (!isset($this->cachedObjectsRefs[$object])) { + $this->cachedObjectsRefs[$object] = spl_object_hash($object); + } + + return $this->cachedObjectsRefs[$object]; + } + + public function __clone() + { + $this->violations = clone $this->violations; + } +} diff --git a/src/vendor/symfony/validator/Context/ExecutionContextFactory.php b/src/vendor/symfony/validator/Context/ExecutionContextFactory.php new file mode 100644 index 0000000..623bd16 --- /dev/null +++ b/src/vendor/symfony/validator/Context/ExecutionContextFactory.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Context; + +use Symfony\Component\Validator\Validator\ValidatorInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * Creates new {@link ExecutionContext} instances. + * + * @author Bernhard Schussek + * + * @internal version 2.5. Code against ExecutionContextFactoryInterface instead. + */ +class ExecutionContextFactory implements ExecutionContextFactoryInterface +{ + private $translator; + private $translationDomain; + + public function __construct(TranslatorInterface $translator, string $translationDomain = null) + { + $this->translator = $translator; + $this->translationDomain = $translationDomain; + } + + /** + * {@inheritdoc} + */ + public function createContext(ValidatorInterface $validator, $root) + { + return new ExecutionContext( + $validator, + $root, + $this->translator, + $this->translationDomain + ); + } +} diff --git a/src/vendor/symfony/validator/Context/ExecutionContextFactoryInterface.php b/src/vendor/symfony/validator/Context/ExecutionContextFactoryInterface.php new file mode 100644 index 0000000..a300086 --- /dev/null +++ b/src/vendor/symfony/validator/Context/ExecutionContextFactoryInterface.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Context; + +use Symfony\Component\Validator\Validator\ValidatorInterface; + +/** + * Creates instances of {@link ExecutionContextInterface}. + * + * You can use a custom factory if you want to customize the execution context + * that is passed through the validation run. + * + * @author Bernhard Schussek + */ +interface ExecutionContextFactoryInterface +{ + /** + * Creates a new execution context. + * + * @param mixed $root The root value of the validated + * object graph + * + * @return ExecutionContextInterface + */ + public function createContext(ValidatorInterface $validator, $root); +} diff --git a/src/vendor/symfony/validator/Context/ExecutionContextInterface.php b/src/vendor/symfony/validator/Context/ExecutionContextInterface.php new file mode 100644 index 0000000..039ef74 --- /dev/null +++ b/src/vendor/symfony/validator/Context/ExecutionContextInterface.php @@ -0,0 +1,312 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Context; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintViolationListInterface; +use Symfony\Component\Validator\Mapping; +use Symfony\Component\Validator\Mapping\MetadataInterface; +use Symfony\Component\Validator\Validator\ValidatorInterface; +use Symfony\Component\Validator\Violation\ConstraintViolationBuilderInterface; + +/** + * The context of a validation run. + * + * The context collects all violations generated during the validation. By + * default, validators execute all validations in a new context: + * + * $violations = $validator->validate($object); + * + * When you make another call to the validator, while the validation is in + * progress, the violations will be isolated from each other: + * + * public function validate($value, Constraint $constraint) + * { + * $validator = $this->context->getValidator(); + * + * // The violations are not added to $this->context + * $violations = $validator->validate($value); + * } + * + * However, if you want to add the violations to the current context, use the + * {@link ValidatorInterface::inContext()} method: + * + * public function validate($value, Constraint $constraint) + * { + * $validator = $this->context->getValidator(); + * + * // The violations are added to $this->context + * $validator + * ->inContext($this->context) + * ->validate($value) + * ; + * } + * + * Additionally, the context provides information about the current state of + * the validator, such as the currently validated class, the name of the + * currently validated property and more. These values change over time, so you + * cannot store a context and expect that the methods still return the same + * results later on. + * + * @author Bernhard Schussek + */ +interface ExecutionContextInterface +{ + /** + * Adds a violation at the current node of the validation graph. + * + * @param string|\Stringable $message The error message as a string or a stringable object + * @param array $params The parameters substituted in the error message + */ + public function addViolation(string $message, array $params = []); + + /** + * Returns a builder for adding a violation with extended information. + * + * Call {@link ConstraintViolationBuilderInterface::addViolation()} to + * add the violation when you're done with the configuration: + * + * $context->buildViolation('Please enter a number between %min% and %max%.') + * ->setParameter('%min%', '3') + * ->setParameter('%max%', '10') + * ->setTranslationDomain('number_validation') + * ->addViolation(); + * + * @param string|\Stringable $message The error message as a string or a stringable object + * @param array $parameters The parameters substituted in the error message + * + * @return ConstraintViolationBuilderInterface + */ + public function buildViolation(string $message, array $parameters = []); + + /** + * Returns the validator. + * + * Useful if you want to validate additional constraints: + * + * public function validate($value, Constraint $constraint) + * { + * $validator = $this->context->getValidator(); + * + * $violations = $validator->validate($value, new Length(['min' => 3])); + * + * if (count($violations) > 0) { + * // ... + * } + * } + * + * @return ValidatorInterface + */ + public function getValidator(); + + /** + * Returns the currently validated object. + * + * If the validator is currently validating a class constraint, the + * object of that class is returned. If it is validating a property or + * getter constraint, the object that the property/getter belongs to is + * returned. + * + * In other cases, null is returned. + * + * @return object|null + */ + public function getObject(); + + /** + * Warning: Should not be called by user code, to be used by the validator engine only. + * + * @param mixed $value The validated value + * @param object|null $object The currently validated object + * @param string $propertyPath The property path to the current value + */ + public function setNode($value, ?object $object, MetadataInterface $metadata = null, string $propertyPath); + + /** + * Warning: Should not be called by user code, to be used by the validator engine only. + * + * @param string|null $group The validated group + */ + public function setGroup(?string $group); + + /** + * Warning: Should not be called by user code, to be used by the validator engine only. + */ + public function setConstraint(Constraint $constraint); + + /** + * Warning: Should not be called by user code, to be used by the validator engine only. + * + * @param string $cacheKey The hash of the object + * @param string $groupHash The group's name or hash, if it is group + * sequence + */ + public function markGroupAsValidated(string $cacheKey, string $groupHash); + + /** + * Warning: Should not be called by user code, to be used by the validator engine only. + * + * @param string $cacheKey The hash of the object + * @param string $groupHash The group's name or hash, if it is group + * sequence + * + * @return bool + */ + public function isGroupValidated(string $cacheKey, string $groupHash); + + /** + * Warning: Should not be called by user code, to be used by the validator engine only. + * + * @param string $cacheKey The hash of the object + * @param string $constraintHash The hash of the constraint + */ + public function markConstraintAsValidated(string $cacheKey, string $constraintHash); + + /** + * Warning: Should not be called by user code, to be used by the validator engine only. + * + * @param string $cacheKey The hash of the object + * @param string $constraintHash The hash of the constraint + * + * @return bool + */ + public function isConstraintValidated(string $cacheKey, string $constraintHash); + + /** + * Warning: Should not be called by user code, to be used by the validator engine only. + * + * @param string $cacheKey The hash of the object + * + * @see ObjectInitializerInterface + */ + public function markObjectAsInitialized(string $cacheKey); + + /** + * Warning: Should not be called by user code, to be used by the validator engine only. + * + * @param string $cacheKey The hash of the object + * + * @return bool + * + * @see ObjectInitializerInterface + */ + public function isObjectInitialized(string $cacheKey); + + /** + * Returns the violations generated by the validator so far. + * + * @return ConstraintViolationListInterface + */ + public function getViolations(); + + /** + * Returns the value at which validation was started in the object graph. + * + * The validator, when given an object, traverses the properties and + * related objects and their properties. The root of the validation is the + * object from which the traversal started. + * + * The current value is returned by {@link getValue}. + * + * @return mixed + */ + public function getRoot(); + + /** + * Returns the value that the validator is currently validating. + * + * If you want to retrieve the object that was originally passed to the + * validator, use {@link getRoot}. + * + * @return mixed + */ + public function getValue(); + + /** + * Returns the metadata for the currently validated value. + * + * With the core implementation, this method returns a + * {@link Mapping\ClassMetadataInterface} instance if the current value is an object, + * a {@link Mapping\PropertyMetadata} instance if the current value is + * the value of a property and a {@link Mapping\GetterMetadata} instance if + * the validated value is the result of a getter method. + * + * If the validated value is neither of these, for example if the validator + * has been called with a plain value and constraint, this method returns + * null. + * + * @return MetadataInterface|null + */ + public function getMetadata(); + + /** + * Returns the validation group that is currently being validated. + * + * @return string|null + */ + public function getGroup(); + + /** + * Returns the class name of the current node. + * + * If the metadata of the current node does not implement + * {@link Mapping\ClassMetadataInterface} or if no metadata is available for the + * current node, this method returns null. + * + * @return string|null + */ + public function getClassName(); + + /** + * Returns the property name of the current node. + * + * If the metadata of the current node does not implement + * {@link PropertyMetadataInterface} or if no metadata is available for the + * current node, this method returns null. + * + * @return string|null + */ + public function getPropertyName(); + + /** + * Returns the property path to the value that the validator is currently + * validating. + * + * For example, take the following object graph: + * + *
+     * (Person)---($address: Address)---($street: string)
+     * 
+ * + * When the Person instance is passed to the validator, the + * property path is initially empty. When the $address property + * of that person is validated, the property path is "address". When + * the $street property of the related Address instance + * is validated, the property path is "address.street". + * + * Properties of objects are prefixed with a dot in the property path. + * Indices of arrays or objects implementing the {@link \ArrayAccess} + * interface are enclosed in brackets. For example, if the property in + * the previous example is $addresses and contains an array + * of Address instance, the property path generated for the + * $street property of one of these addresses is for example + * "addresses[0].street". + * + * @param string $subPath Optional. The suffix appended to the current + * property path. + * + * @return string The current property path. The result may be an empty + * string if the validator is currently validating the + * root value of the validation graph. + */ + public function getPropertyPath(string $subPath = ''); +} diff --git a/src/vendor/symfony/validator/DataCollector/ValidatorDataCollector.php b/src/vendor/symfony/validator/DataCollector/ValidatorDataCollector.php new file mode 100644 index 0000000..2b36267 --- /dev/null +++ b/src/vendor/symfony/validator/DataCollector/ValidatorDataCollector.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\DataCollector; + +use Symfony\Component\Form\FormInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\DataCollector\DataCollector; +use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface; +use Symfony\Component\Validator\Validator\TraceableValidator; +use Symfony\Component\VarDumper\Caster\Caster; +use Symfony\Component\VarDumper\Caster\ClassStub; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Maxime Steinhausser + * + * @final + */ +class ValidatorDataCollector extends DataCollector implements LateDataCollectorInterface +{ + private $validator; + + public function __construct(TraceableValidator $validator) + { + $this->validator = $validator; + $this->reset(); + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Throwable $exception = null) + { + // Everything is collected once, on kernel terminate. + } + + public function reset() + { + $this->data = [ + 'calls' => $this->cloneVar([]), + 'violations_count' => 0, + ]; + } + + /** + * {@inheritdoc} + */ + public function lateCollect() + { + $collected = $this->validator->getCollectedData(); + $this->data['calls'] = $this->cloneVar($collected); + $this->data['violations_count'] = array_reduce($collected, function ($previous, $item) { + return $previous + \count($item['violations']); + }, 0); + } + + public function getCalls(): Data + { + return $this->data['calls']; + } + + public function getViolationsCount(): int + { + return $this->data['violations_count']; + } + + /** + * {@inheritdoc} + */ + public function getName(): string + { + return 'validator'; + } + + protected function getCasters(): array + { + return parent::getCasters() + [ + \Exception::class => function (\Exception $e, array $a, Stub $s) { + foreach (["\0Exception\0previous", "\0Exception\0trace"] as $k) { + if (isset($a[$k])) { + unset($a[$k]); + ++$s->cut; + } + } + + return $a; + }, + FormInterface::class => function (FormInterface $f, array $a) { + return [ + Caster::PREFIX_VIRTUAL.'name' => $f->getName(), + Caster::PREFIX_VIRTUAL.'type_class' => new ClassStub(\get_class($f->getConfig()->getType()->getInnerType())), + Caster::PREFIX_VIRTUAL.'data' => $f->getData(), + ]; + }, + ]; + } +} diff --git a/src/vendor/symfony/validator/DependencyInjection/AddAutoMappingConfigurationPass.php b/src/vendor/symfony/validator/DependencyInjection/AddAutoMappingConfigurationPass.php new file mode 100644 index 0000000..dca06ee --- /dev/null +++ b/src/vendor/symfony/validator/DependencyInjection/AddAutoMappingConfigurationPass.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Injects the automapping configuration as last argument of loaders tagged with the "validator.auto_mapper" tag. + * + * @author Kévin Dunglas + */ +class AddAutoMappingConfigurationPass implements CompilerPassInterface +{ + private $validatorBuilderService; + private $tag; + + public function __construct(string $validatorBuilderService = 'validator.builder', string $tag = 'validator.auto_mapper') + { + if (0 < \func_num_args()) { + trigger_deprecation('symfony/validator', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); + } + + $this->validatorBuilderService = $validatorBuilderService; + $this->tag = $tag; + } + + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container) + { + if (!$container->hasParameter('validator.auto_mapping') || !$container->hasDefinition($this->validatorBuilderService)) { + return; + } + + $config = $container->getParameter('validator.auto_mapping'); + + $globalNamespaces = []; + $servicesToNamespaces = []; + foreach ($config as $namespace => $value) { + if ([] === $value['services']) { + $globalNamespaces[] = $namespace; + + continue; + } + + foreach ($value['services'] as $service) { + $servicesToNamespaces[$service][] = $namespace; + } + } + + $validatorBuilder = $container->getDefinition($this->validatorBuilderService); + foreach ($container->findTaggedServiceIds($this->tag) as $id => $tags) { + $regexp = $this->getRegexp(array_merge($globalNamespaces, $servicesToNamespaces[$id] ?? [])); + $validatorBuilder->addMethodCall('addLoader', [new Reference($id)]); + $container->getDefinition($id)->setArgument('$classValidatorRegexp', $regexp); + } + + $container->getParameterBag()->remove('validator.auto_mapping'); + } + + /** + * Builds a regexp to check if a class is auto-mapped. + */ + private function getRegexp(array $patterns): ?string + { + if (!$patterns) { + return null; + } + + $regexps = []; + foreach ($patterns as $pattern) { + // Escape namespace + $regex = preg_quote(ltrim($pattern, '\\')); + + // Wildcards * and ** + $regex = strtr($regex, ['\\*\\*' => '.*?', '\\*' => '[^\\\\]*?']); + + // If this class does not end by a slash, anchor the end + if (!str_ends_with($regex, '\\')) { + $regex .= '$'; + } + + $regexps[] = '^'.$regex; + } + + return sprintf('{%s}', implode('|', $regexps)); + } +} diff --git a/src/vendor/symfony/validator/DependencyInjection/AddConstraintValidatorsPass.php b/src/vendor/symfony/validator/DependencyInjection/AddConstraintValidatorsPass.php new file mode 100644 index 0000000..a3c6559 --- /dev/null +++ b/src/vendor/symfony/validator/DependencyInjection/AddConstraintValidatorsPass.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * @author Johannes M. Schmitt + * @author Robin Chalas + */ +class AddConstraintValidatorsPass implements CompilerPassInterface +{ + private $validatorFactoryServiceId; + private $constraintValidatorTag; + + public function __construct(string $validatorFactoryServiceId = 'validator.validator_factory', string $constraintValidatorTag = 'validator.constraint_validator') + { + if (0 < \func_num_args()) { + trigger_deprecation('symfony/validator', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); + } + + $this->validatorFactoryServiceId = $validatorFactoryServiceId; + $this->constraintValidatorTag = $constraintValidatorTag; + } + + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition($this->validatorFactoryServiceId)) { + return; + } + + $validators = []; + foreach ($container->findTaggedServiceIds($this->constraintValidatorTag, true) as $id => $attributes) { + $definition = $container->getDefinition($id); + + if (isset($attributes[0]['alias'])) { + $validators[$attributes[0]['alias']] = new Reference($id); + } + + $validators[$definition->getClass()] = new Reference($id); + } + + $container + ->getDefinition($this->validatorFactoryServiceId) + ->replaceArgument(0, ServiceLocatorTagPass::register($container, $validators)) + ; + } +} diff --git a/src/vendor/symfony/validator/DependencyInjection/AddValidatorInitializersPass.php b/src/vendor/symfony/validator/DependencyInjection/AddValidatorInitializersPass.php new file mode 100644 index 0000000..0ea8eef --- /dev/null +++ b/src/vendor/symfony/validator/DependencyInjection/AddValidatorInitializersPass.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * @author Fabien Potencier + * @author Robin Chalas + */ +class AddValidatorInitializersPass implements CompilerPassInterface +{ + private $builderService; + private $initializerTag; + + public function __construct(string $builderService = 'validator.builder', string $initializerTag = 'validator.initializer') + { + if (0 < \func_num_args()) { + trigger_deprecation('symfony/validator', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); + } + + $this->builderService = $builderService; + $this->initializerTag = $initializerTag; + } + + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition($this->builderService)) { + return; + } + + $initializers = []; + foreach ($container->findTaggedServiceIds($this->initializerTag, true) as $id => $attributes) { + $initializers[] = new Reference($id); + } + + $container->getDefinition($this->builderService)->addMethodCall('addObjectInitializers', [$initializers]); + } +} diff --git a/src/vendor/symfony/validator/Exception/BadMethodCallException.php b/src/vendor/symfony/validator/Exception/BadMethodCallException.php new file mode 100644 index 0000000..939161b --- /dev/null +++ b/src/vendor/symfony/validator/Exception/BadMethodCallException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Exception; + +/** + * Base BadMethodCallException for the Validator component. + * + * @author Bernhard Schussek + */ +class BadMethodCallException extends \BadMethodCallException implements ExceptionInterface +{ +} diff --git a/src/vendor/symfony/validator/Exception/ConstraintDefinitionException.php b/src/vendor/symfony/validator/Exception/ConstraintDefinitionException.php new file mode 100644 index 0000000..b24fdd6 --- /dev/null +++ b/src/vendor/symfony/validator/Exception/ConstraintDefinitionException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Exception; + +class ConstraintDefinitionException extends ValidatorException +{ +} diff --git a/src/vendor/symfony/validator/Exception/ExceptionInterface.php b/src/vendor/symfony/validator/Exception/ExceptionInterface.php new file mode 100644 index 0000000..390e8c0 --- /dev/null +++ b/src/vendor/symfony/validator/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Exception; + +/** + * Base ExceptionInterface for the Validator component. + * + * @author Bernhard Schussek + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/src/vendor/symfony/validator/Exception/GroupDefinitionException.php b/src/vendor/symfony/validator/Exception/GroupDefinitionException.php new file mode 100644 index 0000000..ab7e91d --- /dev/null +++ b/src/vendor/symfony/validator/Exception/GroupDefinitionException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Exception; + +class GroupDefinitionException extends ValidatorException +{ +} diff --git a/src/vendor/symfony/validator/Exception/InvalidArgumentException.php b/src/vendor/symfony/validator/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..22da39b --- /dev/null +++ b/src/vendor/symfony/validator/Exception/InvalidArgumentException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Exception; + +/** + * Base InvalidArgumentException for the Validator component. + * + * @author Bernhard Schussek + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/src/vendor/symfony/validator/Exception/InvalidOptionsException.php b/src/vendor/symfony/validator/Exception/InvalidOptionsException.php new file mode 100644 index 0000000..79d064f --- /dev/null +++ b/src/vendor/symfony/validator/Exception/InvalidOptionsException.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Exception; + +class InvalidOptionsException extends ValidatorException +{ + private $options; + + public function __construct(string $message, array $options) + { + parent::__construct($message); + + $this->options = $options; + } + + public function getOptions() + { + return $this->options; + } +} diff --git a/src/vendor/symfony/validator/Exception/LogicException.php b/src/vendor/symfony/validator/Exception/LogicException.php new file mode 100644 index 0000000..41d0975 --- /dev/null +++ b/src/vendor/symfony/validator/Exception/LogicException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Exception; + +class LogicException extends \LogicException implements ExceptionInterface +{ +} diff --git a/src/vendor/symfony/validator/Exception/MappingException.php b/src/vendor/symfony/validator/Exception/MappingException.php new file mode 100644 index 0000000..4c8c057 --- /dev/null +++ b/src/vendor/symfony/validator/Exception/MappingException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Exception; + +class MappingException extends ValidatorException +{ +} diff --git a/src/vendor/symfony/validator/Exception/MissingOptionsException.php b/src/vendor/symfony/validator/Exception/MissingOptionsException.php new file mode 100644 index 0000000..61de567 --- /dev/null +++ b/src/vendor/symfony/validator/Exception/MissingOptionsException.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Exception; + +class MissingOptionsException extends ValidatorException +{ + private $options; + + public function __construct(string $message, array $options) + { + parent::__construct($message); + + $this->options = $options; + } + + public function getOptions() + { + return $this->options; + } +} diff --git a/src/vendor/symfony/validator/Exception/NoSuchMetadataException.php b/src/vendor/symfony/validator/Exception/NoSuchMetadataException.php new file mode 100644 index 0000000..4cac74c --- /dev/null +++ b/src/vendor/symfony/validator/Exception/NoSuchMetadataException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Exception; + +/** + * @author Bernhard Schussek + */ +class NoSuchMetadataException extends ValidatorException +{ +} diff --git a/src/vendor/symfony/validator/Exception/OutOfBoundsException.php b/src/vendor/symfony/validator/Exception/OutOfBoundsException.php new file mode 100644 index 0000000..30906e8 --- /dev/null +++ b/src/vendor/symfony/validator/Exception/OutOfBoundsException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Exception; + +/** + * Base OutOfBoundsException for the Validator component. + * + * @author Bernhard Schussek + */ +class OutOfBoundsException extends \OutOfBoundsException implements ExceptionInterface +{ +} diff --git a/src/vendor/symfony/validator/Exception/RuntimeException.php b/src/vendor/symfony/validator/Exception/RuntimeException.php new file mode 100644 index 0000000..df4a50c --- /dev/null +++ b/src/vendor/symfony/validator/Exception/RuntimeException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Exception; + +/** + * Base RuntimeException for the Validator component. + * + * @author Bernhard Schussek + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/src/vendor/symfony/validator/Exception/UnexpectedTypeException.php b/src/vendor/symfony/validator/Exception/UnexpectedTypeException.php new file mode 100644 index 0000000..86a7c03 --- /dev/null +++ b/src/vendor/symfony/validator/Exception/UnexpectedTypeException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Exception; + +class UnexpectedTypeException extends ValidatorException +{ + public function __construct($value, string $expectedType) + { + parent::__construct(sprintf('Expected argument of type "%s", "%s" given', $expectedType, get_debug_type($value))); + } +} diff --git a/src/vendor/symfony/validator/Exception/UnexpectedValueException.php b/src/vendor/symfony/validator/Exception/UnexpectedValueException.php new file mode 100644 index 0000000..7a7f7f7 --- /dev/null +++ b/src/vendor/symfony/validator/Exception/UnexpectedValueException.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Exception; + +/** + * @author Christian Flothmann + */ +class UnexpectedValueException extends UnexpectedTypeException +{ + private $expectedType; + + public function __construct($value, string $expectedType) + { + parent::__construct($value, $expectedType); + + $this->expectedType = $expectedType; + } + + public function getExpectedType(): string + { + return $this->expectedType; + } +} diff --git a/src/vendor/symfony/validator/Exception/UnsupportedMetadataException.php b/src/vendor/symfony/validator/Exception/UnsupportedMetadataException.php new file mode 100644 index 0000000..aff569b --- /dev/null +++ b/src/vendor/symfony/validator/Exception/UnsupportedMetadataException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Exception; + +/** + * @author Bernhard Schussek + */ +class UnsupportedMetadataException extends InvalidArgumentException +{ +} diff --git a/src/vendor/symfony/validator/Exception/ValidationFailedException.php b/src/vendor/symfony/validator/Exception/ValidationFailedException.php new file mode 100644 index 0000000..ca0314c --- /dev/null +++ b/src/vendor/symfony/validator/Exception/ValidationFailedException.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Exception; + +use Symfony\Component\Validator\ConstraintViolationListInterface; + +/** + * @author Jan Vernieuwe + */ +class ValidationFailedException extends RuntimeException +{ + private $violations; + private $value; + + public function __construct($value, ConstraintViolationListInterface $violations) + { + $this->violations = $violations; + $this->value = $value; + parent::__construct($violations); + } + + public function getValue() + { + return $this->value; + } + + public function getViolations(): ConstraintViolationListInterface + { + return $this->violations; + } +} diff --git a/src/vendor/symfony/validator/Exception/ValidatorException.php b/src/vendor/symfony/validator/Exception/ValidatorException.php new file mode 100644 index 0000000..28bd470 --- /dev/null +++ b/src/vendor/symfony/validator/Exception/ValidatorException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Exception; + +class ValidatorException extends RuntimeException +{ +} diff --git a/src/vendor/symfony/validator/GroupSequenceProviderInterface.php b/src/vendor/symfony/validator/GroupSequenceProviderInterface.php new file mode 100644 index 0000000..0374a8b --- /dev/null +++ b/src/vendor/symfony/validator/GroupSequenceProviderInterface.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator; + +use Symfony\Component\Validator\Constraints\GroupSequence; + +/** + * Defines the interface for a group sequence provider. + */ +interface GroupSequenceProviderInterface +{ + /** + * Returns which validation groups should be used for a certain state + * of the object. + * + * @return string[]|string[][]|GroupSequence + */ + public function getGroupSequence(); +} diff --git a/src/vendor/symfony/validator/LICENSE b/src/vendor/symfony/validator/LICENSE new file mode 100644 index 0000000..0138f8f --- /dev/null +++ b/src/vendor/symfony/validator/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/vendor/symfony/validator/Mapping/AutoMappingStrategy.php b/src/vendor/symfony/validator/Mapping/AutoMappingStrategy.php new file mode 100644 index 0000000..4012ddc --- /dev/null +++ b/src/vendor/symfony/validator/Mapping/AutoMappingStrategy.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Mapping; + +/** + * Specifies how the auto-mapping feature should behave. + * + * @author Maxime Steinhausser + */ +final class AutoMappingStrategy +{ + /** + * Nothing explicitly set, rely on auto-mapping configured regex. + */ + public const NONE = 0; + + /** + * Explicitly enabled. + */ + public const ENABLED = 1; + + /** + * Explicitly disabled. + */ + public const DISABLED = 2; + + /** + * Not instantiable. + */ + private function __construct() + { + } +} diff --git a/src/vendor/symfony/validator/Mapping/CascadingStrategy.php b/src/vendor/symfony/validator/Mapping/CascadingStrategy.php new file mode 100644 index 0000000..6bab8ac --- /dev/null +++ b/src/vendor/symfony/validator/Mapping/CascadingStrategy.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Mapping; + +/** + * Specifies whether an object should be cascaded. + * + * Cascading is relevant for any node type but class nodes. If such a node + * contains an object of value, and if cascading is enabled, then the node + * traverser will try to find class metadata for that object and validate the + * object against that metadata. + * + * If no metadata is found for a cascaded object, and if that object implements + * {@link \Traversable}, the node traverser will iterate over the object and + * cascade each object or collection contained within, unless iteration is + * prohibited by the specified {@link TraversalStrategy}. + * + * Although the constants currently represent a boolean switch, they are + * implemented as bit mask in order to allow future extensions. + * + * @author Bernhard Schussek + * + * @see TraversalStrategy + */ +class CascadingStrategy +{ + /** + * Specifies that a node should not be cascaded. + */ + public const NONE = 1; + + /** + * Specifies that a node should be cascaded. + */ + public const CASCADE = 2; + + /** + * Not instantiable. + */ + private function __construct() + { + } +} diff --git a/src/vendor/symfony/validator/Mapping/ClassMetadata.php b/src/vendor/symfony/validator/Mapping/ClassMetadata.php new file mode 100644 index 0000000..a7209d5 --- /dev/null +++ b/src/vendor/symfony/validator/Mapping/ClassMetadata.php @@ -0,0 +1,514 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Mapping; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Constraints\Cascade; +use Symfony\Component\Validator\Constraints\Composite; +use Symfony\Component\Validator\Constraints\GroupSequence; +use Symfony\Component\Validator\Constraints\Traverse; +use Symfony\Component\Validator\Constraints\Valid; +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; +use Symfony\Component\Validator\Exception\GroupDefinitionException; + +/** + * Default implementation of {@link ClassMetadataInterface}. + * + * This class supports serialization and cloning. + * + * @author Bernhard Schussek + * @author Fabien Potencier + */ +class ClassMetadata extends GenericMetadata implements ClassMetadataInterface +{ + /** + * @var string + * + * @internal This property is public in order to reduce the size of the + * class' serialized representation. Do not access it. Use + * {@link getClassName()} instead. + */ + public $name; + + /** + * @var string + * + * @internal This property is public in order to reduce the size of the + * class' serialized representation. Do not access it. Use + * {@link getDefaultGroup()} instead. + */ + public $defaultGroup; + + /** + * @var MemberMetadata[][] + * + * @internal This property is public in order to reduce the size of the + * class' serialized representation. Do not access it. Use + * {@link getPropertyMetadata()} instead. + */ + public $members = []; + + /** + * @var PropertyMetadata[] + * + * @internal This property is public in order to reduce the size of the + * class' serialized representation. Do not access it. Use + * {@link getPropertyMetadata()} instead. + */ + public $properties = []; + + /** + * @var GetterMetadata[] + * + * @internal This property is public in order to reduce the size of the + * class' serialized representation. Do not access it. Use + * {@link getPropertyMetadata()} instead. + */ + public $getters = []; + + /** + * @var array + * + * @internal This property is public in order to reduce the size of the + * class' serialized representation. Do not access it. Use + * {@link getGroupSequence()} instead. + */ + public $groupSequence = []; + + /** + * @var bool + * + * @internal This property is public in order to reduce the size of the + * class' serialized representation. Do not access it. Use + * {@link isGroupSequenceProvider()} instead. + */ + public $groupSequenceProvider = false; + + /** + * The strategy for traversing traversable objects. + * + * By default, only instances of {@link \Traversable} are traversed. + * + * @var int + * + * @internal This property is public in order to reduce the size of the + * class' serialized representation. Do not access it. Use + * {@link getTraversalStrategy()} instead. + */ + public $traversalStrategy = TraversalStrategy::IMPLICIT; + + /** + * @var \ReflectionClass + */ + private $reflClass; + + public function __construct(string $class) + { + $this->name = $class; + // class name without namespace + if (false !== $nsSep = strrpos($class, '\\')) { + $this->defaultGroup = substr($class, $nsSep + 1); + } else { + $this->defaultGroup = $class; + } + } + + /** + * {@inheritdoc} + */ + public function __sleep() + { + $parentProperties = parent::__sleep(); + + // Don't store the cascading strategy. Classes never cascade. + unset($parentProperties[array_search('cascadingStrategy', $parentProperties)]); + + return array_merge($parentProperties, [ + 'getters', + 'groupSequence', + 'groupSequenceProvider', + 'members', + 'name', + 'properties', + 'defaultGroup', + ]); + } + + /** + * {@inheritdoc} + */ + public function getClassName() + { + return $this->name; + } + + /** + * Returns the name of the default group for this class. + * + * For each class, the group "Default" is an alias for the group + * "", where is the non-namespaced name of the + * class. All constraints implicitly or explicitly assigned to group + * "Default" belong to both of these groups, unless the class defines + * a group sequence. + * + * If a class defines a group sequence, validating the class in "Default" + * will validate the group sequence. The constraints assigned to "Default" + * can still be validated by validating the class in "". + * + * @return string + */ + public function getDefaultGroup() + { + return $this->defaultGroup; + } + + /** + * {@inheritdoc} + * + * If the constraint {@link Cascade} is added, the cascading strategy will be + * changed to {@link CascadingStrategy::CASCADE}. + * + * If the constraint {@link Traverse} is added, the traversal strategy will be + * changed. Depending on the $traverse property of that constraint, + * the traversal strategy will be set to one of the following: + * + * - {@link TraversalStrategy::IMPLICIT} by default + * - {@link TraversalStrategy::NONE} if $traverse is disabled + * - {@link TraversalStrategy::TRAVERSE} if $traverse is enabled + */ + public function addConstraint(Constraint $constraint) + { + $this->checkConstraint($constraint); + + if ($constraint instanceof Traverse) { + if ($constraint->traverse) { + // If traverse is true, traversal should be explicitly enabled + $this->traversalStrategy = TraversalStrategy::TRAVERSE; + } else { + // If traverse is false, traversal should be explicitly disabled + $this->traversalStrategy = TraversalStrategy::NONE; + } + + // The constraint is not added + return $this; + } + + if ($constraint instanceof Cascade) { + if (\PHP_VERSION_ID < 70400) { + throw new ConstraintDefinitionException(sprintf('The constraint "%s" requires PHP 7.4.', Cascade::class)); + } + + $this->cascadingStrategy = CascadingStrategy::CASCADE; + + foreach ($this->getReflectionClass()->getProperties() as $property) { + if ($property->hasType() && (('array' === $type = $property->getType()->getName()) || class_exists($type))) { + $this->addPropertyConstraint($property->getName(), new Valid()); + } + } + + // The constraint is not added + return $this; + } + + $constraint->addImplicitGroupName($this->getDefaultGroup()); + + parent::addConstraint($constraint); + + return $this; + } + + /** + * Adds a constraint to the given property. + * + * @return $this + */ + public function addPropertyConstraint(string $property, Constraint $constraint) + { + if (!isset($this->properties[$property])) { + $this->properties[$property] = new PropertyMetadata($this->getClassName(), $property); + + $this->addPropertyMetadata($this->properties[$property]); + } + + $constraint->addImplicitGroupName($this->getDefaultGroup()); + + $this->properties[$property]->addConstraint($constraint); + + return $this; + } + + /** + * @param Constraint[] $constraints + * + * @return $this + */ + public function addPropertyConstraints(string $property, array $constraints) + { + foreach ($constraints as $constraint) { + $this->addPropertyConstraint($property, $constraint); + } + + return $this; + } + + /** + * Adds a constraint to the getter of the given property. + * + * The name of the getter is assumed to be the name of the property with an + * uppercased first letter and the prefix "get", "is" or "has". + * + * @return $this + */ + public function addGetterConstraint(string $property, Constraint $constraint) + { + if (!isset($this->getters[$property])) { + $this->getters[$property] = new GetterMetadata($this->getClassName(), $property); + + $this->addPropertyMetadata($this->getters[$property]); + } + + $constraint->addImplicitGroupName($this->getDefaultGroup()); + + $this->getters[$property]->addConstraint($constraint); + + return $this; + } + + /** + * Adds a constraint to the getter of the given property. + * + * @return $this + */ + public function addGetterMethodConstraint(string $property, string $method, Constraint $constraint) + { + if (!isset($this->getters[$property])) { + $this->getters[$property] = new GetterMetadata($this->getClassName(), $property, $method); + + $this->addPropertyMetadata($this->getters[$property]); + } + + $constraint->addImplicitGroupName($this->getDefaultGroup()); + + $this->getters[$property]->addConstraint($constraint); + + return $this; + } + + /** + * @param Constraint[] $constraints + * + * @return $this + */ + public function addGetterConstraints(string $property, array $constraints) + { + foreach ($constraints as $constraint) { + $this->addGetterConstraint($property, $constraint); + } + + return $this; + } + + /** + * @param Constraint[] $constraints + * + * @return $this + */ + public function addGetterMethodConstraints(string $property, string $method, array $constraints) + { + foreach ($constraints as $constraint) { + $this->addGetterMethodConstraint($property, $method, $constraint); + } + + return $this; + } + + /** + * Merges the constraints of the given metadata into this object. + */ + public function mergeConstraints(self $source) + { + if ($source->isGroupSequenceProvider()) { + $this->setGroupSequenceProvider(true); + } + + foreach ($source->getConstraints() as $constraint) { + $this->addConstraint(clone $constraint); + } + + foreach ($source->getConstrainedProperties() as $property) { + foreach ($source->getPropertyMetadata($property) as $member) { + $member = clone $member; + + foreach ($member->getConstraints() as $constraint) { + if (\in_array($constraint::DEFAULT_GROUP, $constraint->groups, true)) { + $member->constraintsByGroup[$this->getDefaultGroup()][] = $constraint; + } + + $constraint->addImplicitGroupName($this->getDefaultGroup()); + } + + if ($member instanceof MemberMetadata && !$member->isPrivate($this->name)) { + $property = $member->getPropertyName(); + $this->members[$property][] = $member; + + if ($member instanceof PropertyMetadata && !isset($this->properties[$property])) { + $this->properties[$property] = $member; + } elseif ($member instanceof GetterMetadata && !isset($this->getters[$property])) { + $this->getters[$property] = $member; + } + } else { + $this->addPropertyMetadata($member); + } + } + } + } + + /** + * {@inheritdoc} + */ + public function hasPropertyMetadata(string $property) + { + return \array_key_exists($property, $this->members); + } + + /** + * {@inheritdoc} + */ + public function getPropertyMetadata(string $property) + { + return $this->members[$property] ?? []; + } + + /** + * {@inheritdoc} + */ + public function getConstrainedProperties() + { + return array_keys($this->members); + } + + /** + * Sets the default group sequence for this class. + * + * @param string[]|GroupSequence $groupSequence An array of group names + * + * @return $this + * + * @throws GroupDefinitionException + */ + public function setGroupSequence($groupSequence) + { + if ($this->isGroupSequenceProvider()) { + throw new GroupDefinitionException('Defining a static group sequence is not allowed with a group sequence provider.'); + } + + if (\is_array($groupSequence)) { + $groupSequence = new GroupSequence($groupSequence); + } + + if (\in_array(Constraint::DEFAULT_GROUP, $groupSequence->groups, true)) { + throw new GroupDefinitionException(sprintf('The group "%s" is not allowed in group sequences.', Constraint::DEFAULT_GROUP)); + } + + if (!\in_array($this->getDefaultGroup(), $groupSequence->groups, true)) { + throw new GroupDefinitionException(sprintf('The group "%s" is missing in the group sequence.', $this->getDefaultGroup())); + } + + $this->groupSequence = $groupSequence; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function hasGroupSequence() + { + return $this->groupSequence && \count($this->groupSequence->groups) > 0; + } + + /** + * {@inheritdoc} + */ + public function getGroupSequence() + { + return $this->groupSequence; + } + + /** + * Returns a ReflectionClass instance for this class. + * + * @return \ReflectionClass + */ + public function getReflectionClass() + { + if (!$this->reflClass) { + $this->reflClass = new \ReflectionClass($this->getClassName()); + } + + return $this->reflClass; + } + + /** + * Sets whether a group sequence provider should be used. + * + * @throws GroupDefinitionException + */ + public function setGroupSequenceProvider(bool $active) + { + if ($this->hasGroupSequence()) { + throw new GroupDefinitionException('Defining a group sequence provider is not allowed with a static group sequence.'); + } + + if (!$this->getReflectionClass()->implementsInterface('Symfony\Component\Validator\GroupSequenceProviderInterface')) { + throw new GroupDefinitionException(sprintf('Class "%s" must implement GroupSequenceProviderInterface.', $this->name)); + } + + $this->groupSequenceProvider = $active; + } + + /** + * {@inheritdoc} + */ + public function isGroupSequenceProvider() + { + return $this->groupSequenceProvider; + } + + /** + * {@inheritdoc} + */ + public function getCascadingStrategy() + { + return $this->cascadingStrategy; + } + + private function addPropertyMetadata(PropertyMetadataInterface $metadata) + { + $property = $metadata->getPropertyName(); + + $this->members[$property][] = $metadata; + } + + private function checkConstraint(Constraint $constraint) + { + if (!\in_array(Constraint::CLASS_CONSTRAINT, (array) $constraint->getTargets(), true)) { + throw new ConstraintDefinitionException(sprintf('The constraint "%s" cannot be put on classes.', get_debug_type($constraint))); + } + + if ($constraint instanceof Composite) { + foreach ($constraint->getNestedConstraints() as $nestedConstraint) { + $this->checkConstraint($nestedConstraint); + } + } + } +} diff --git a/src/vendor/symfony/validator/Mapping/ClassMetadataInterface.php b/src/vendor/symfony/validator/Mapping/ClassMetadataInterface.php new file mode 100644 index 0000000..99764c5 --- /dev/null +++ b/src/vendor/symfony/validator/Mapping/ClassMetadataInterface.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Mapping; + +use Symfony\Component\Validator\Constraints\GroupSequence; +use Symfony\Component\Validator\GroupSequenceProviderInterface; + +/** + * Stores all metadata needed for validating objects of specific class. + * + * Most importantly, the metadata stores the constraints against which an object + * and its properties should be validated. + * + * Additionally, the metadata stores whether the "Default" group is overridden + * by a group sequence for that class and whether instances of that class + * should be traversed or not. + * + * @author Bernhard Schussek + * + * @see MetadataInterface + * @see GroupSequence + * @see GroupSequenceProviderInterface + * @see TraversalStrategy + */ +interface ClassMetadataInterface extends MetadataInterface +{ + /** + * Returns the names of all constrained properties. + * + * @return string[] + */ + public function getConstrainedProperties(); + + /** + * Returns whether the "Default" group is overridden by a group sequence. + * + * If it is, you can access the group sequence with {@link getGroupSequence()}. + * + * @return bool + */ + public function hasGroupSequence(); + + /** + * Returns the group sequence that overrides the "Default" group for this + * class. + * + * @return GroupSequence|null + */ + public function getGroupSequence(); + + /** + * Returns whether the "Default" group is overridden by a dynamic group + * sequence obtained by the validated objects. + * + * If this method returns true, the class must implement + * {@link GroupSequenceProviderInterface}. + * This interface will be used to obtain the group sequence when an object + * of this class is validated. + * + * @return bool + */ + public function isGroupSequenceProvider(); + + /** + * Check if there's any metadata attached to the given named property. + * + * @param string $property The property name + * + * @return bool + */ + public function hasPropertyMetadata(string $property); + + /** + * Returns all metadata instances for the given named property. + * + * If your implementation does not support properties, throw an exception + * in this method (for example a BadMethodCallException). + * + * @param string $property The property name + * + * @return PropertyMetadataInterface[] + */ + public function getPropertyMetadata(string $property); + + /** + * Returns the name of the backing PHP class. + * + * @return string + */ + public function getClassName(); +} diff --git a/src/vendor/symfony/validator/Mapping/Factory/BlackHoleMetadataFactory.php b/src/vendor/symfony/validator/Mapping/Factory/BlackHoleMetadataFactory.php new file mode 100644 index 0000000..ec21d54 --- /dev/null +++ b/src/vendor/symfony/validator/Mapping/Factory/BlackHoleMetadataFactory.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Mapping\Factory; + +use Symfony\Component\Validator\Exception\LogicException; + +/** + * Metadata factory that does not store metadata. + * + * This implementation is useful if you want to validate values against + * constraints only and you don't need to add constraints to classes and + * properties. + * + * @author Fabien Potencier + */ +class BlackHoleMetadataFactory implements MetadataFactoryInterface +{ + /** + * {@inheritdoc} + */ + public function getMetadataFor($value) + { + throw new LogicException('This class does not support metadata.'); + } + + /** + * {@inheritdoc} + */ + public function hasMetadataFor($value) + { + return false; + } +} diff --git a/src/vendor/symfony/validator/Mapping/Factory/LazyLoadingMetadataFactory.php b/src/vendor/symfony/validator/Mapping/Factory/LazyLoadingMetadataFactory.php new file mode 100644 index 0000000..42ed050 --- /dev/null +++ b/src/vendor/symfony/validator/Mapping/Factory/LazyLoadingMetadataFactory.php @@ -0,0 +1,165 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Mapping\Factory; + +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Validator\Exception\NoSuchMetadataException; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\Loader\LoaderInterface; + +/** + * Creates new {@link ClassMetadataInterface} instances. + * + * Whenever {@link getMetadataFor()} is called for the first time with a given + * class name or object of that class, a new metadata instance is created and + * returned. On subsequent requests for the same class, the same metadata + * instance will be returned. + * + * You can optionally pass a {@link LoaderInterface} instance to the constructor. + * Whenever a new metadata instance is created, it is passed to the loader, + * which can configure the metadata based on configuration loaded from the + * filesystem or a database. If you want to use multiple loaders, wrap them in a + * {@link LoaderChain}. + * + * You can also optionally pass a {@link CacheInterface} instance to the + * constructor. This cache will be used for persisting the generated metadata + * between multiple PHP requests. + * + * @author Bernhard Schussek + */ +class LazyLoadingMetadataFactory implements MetadataFactoryInterface +{ + protected $loader; + protected $cache; + + /** + * The loaded metadata, indexed by class name. + * + * @var ClassMetadata[] + */ + protected $loadedClasses = []; + + public function __construct(LoaderInterface $loader = null, CacheItemPoolInterface $cache = null) + { + $this->loader = $loader; + $this->cache = $cache; + } + + /** + * {@inheritdoc} + * + * If the method was called with the same class name (or an object of that + * class) before, the same metadata instance is returned. + * + * If the factory was configured with a cache, this method will first look + * for an existing metadata instance in the cache. If an existing instance + * is found, it will be returned without further ado. + * + * Otherwise, a new metadata instance is created. If the factory was + * configured with a loader, the metadata is passed to the + * {@link LoaderInterface::loadClassMetadata()} method for further + * configuration. At last, the new object is returned. + */ + public function getMetadataFor($value) + { + if (!\is_object($value) && !\is_string($value)) { + throw new NoSuchMetadataException(sprintf('Cannot create metadata for non-objects. Got: "%s".', get_debug_type($value))); + } + + $class = ltrim(\is_object($value) ? \get_class($value) : $value, '\\'); + + if (isset($this->loadedClasses[$class])) { + return $this->loadedClasses[$class]; + } + + if (!class_exists($class) && !interface_exists($class, false)) { + throw new NoSuchMetadataException(sprintf('The class or interface "%s" does not exist.', $class)); + } + + $cacheItem = null === $this->cache ? null : $this->cache->getItem($this->escapeClassName($class)); + if ($cacheItem && $cacheItem->isHit()) { + $metadata = $cacheItem->get(); + + // Include constraints from the parent class + $this->mergeConstraints($metadata); + + return $this->loadedClasses[$class] = $metadata; + } + + $metadata = new ClassMetadata($class); + + if (null !== $this->loader) { + $this->loader->loadClassMetadata($metadata); + } + + if (null !== $cacheItem) { + $this->cache->save($cacheItem->set($metadata)); + } + + // Include constraints from the parent class + $this->mergeConstraints($metadata); + + return $this->loadedClasses[$class] = $metadata; + } + + private function mergeConstraints(ClassMetadata $metadata) + { + if ($metadata->getReflectionClass()->isInterface()) { + return; + } + + // Include constraints from the parent class + if ($parent = $metadata->getReflectionClass()->getParentClass()) { + $metadata->mergeConstraints($this->getMetadataFor($parent->name)); + } + + // Include constraints from all directly implemented interfaces + foreach ($metadata->getReflectionClass()->getInterfaces() as $interface) { + if ('Symfony\Component\Validator\GroupSequenceProviderInterface' === $interface->name) { + continue; + } + + if ($parent && \in_array($interface->getName(), $parent->getInterfaceNames(), true)) { + continue; + } + + $metadata->mergeConstraints($this->getMetadataFor($interface->name)); + } + } + + /** + * {@inheritdoc} + */ + public function hasMetadataFor($value) + { + if (!\is_object($value) && !\is_string($value)) { + return false; + } + + $class = ltrim(\is_object($value) ? \get_class($value) : $value, '\\'); + + return class_exists($class) || interface_exists($class, false); + } + + /** + * Replaces backslashes by dots in a class name. + */ + private function escapeClassName(string $class): string + { + if (str_contains($class, '@')) { + // anonymous class: replace all PSR6-reserved characters + return str_replace(["\0", '\\', '/', '@', ':', '{', '}', '(', ')'], '.', $class); + } + + return str_replace('\\', '.', $class); + } +} diff --git a/src/vendor/symfony/validator/Mapping/Factory/MetadataFactoryInterface.php b/src/vendor/symfony/validator/Mapping/Factory/MetadataFactoryInterface.php new file mode 100644 index 0000000..d3517c5 --- /dev/null +++ b/src/vendor/symfony/validator/Mapping/Factory/MetadataFactoryInterface.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Mapping\Factory; + +use Symfony\Component\Validator\Exception\NoSuchMetadataException; +use Symfony\Component\Validator\Mapping\MetadataInterface; + +/** + * Returns {@link \Symfony\Component\Validator\Mapping\MetadataInterface} instances for values. + * + * @author Bernhard Schussek + */ +interface MetadataFactoryInterface +{ + /** + * Returns the metadata for the given value. + * + * @param mixed $value Some value + * + * @return MetadataInterface + * + * @throws NoSuchMetadataException If no metadata exists for the given value + */ + public function getMetadataFor($value); + + /** + * Returns whether the class is able to return metadata for the given value. + * + * @param mixed $value Some value + * + * @return bool + */ + public function hasMetadataFor($value); +} diff --git a/src/vendor/symfony/validator/Mapping/GenericMetadata.php b/src/vendor/symfony/validator/Mapping/GenericMetadata.php new file mode 100644 index 0000000..06971e8 --- /dev/null +++ b/src/vendor/symfony/validator/Mapping/GenericMetadata.php @@ -0,0 +1,240 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Mapping; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Constraints\Cascade; +use Symfony\Component\Validator\Constraints\DisableAutoMapping; +use Symfony\Component\Validator\Constraints\EnableAutoMapping; +use Symfony\Component\Validator\Constraints\Traverse; +use Symfony\Component\Validator\Constraints\Valid; +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; + +/** + * A generic container of {@link Constraint} objects. + * + * This class supports serialization and cloning. + * + * @author Bernhard Schussek + */ +class GenericMetadata implements MetadataInterface +{ + /** + * @var Constraint[] + * + * @internal This property is public in order to reduce the size of the + * class' serialized representation. Do not access it. Use + * {@link getConstraints()} and {@link findConstraints()} instead. + */ + public $constraints = []; + + /** + * @var array + * + * @internal This property is public in order to reduce the size of the + * class' serialized representation. Do not access it. Use + * {@link findConstraints()} instead. + */ + public $constraintsByGroup = []; + + /** + * The strategy for cascading objects. + * + * By default, objects are not cascaded. + * + * @var int + * + * @see CascadingStrategy + * + * @internal This property is public in order to reduce the size of the + * class' serialized representation. Do not access it. Use + * {@link getCascadingStrategy()} instead. + */ + public $cascadingStrategy = CascadingStrategy::NONE; + + /** + * The strategy for traversing traversable objects. + * + * By default, traversable objects are not traversed. + * + * @var int + * + * @see TraversalStrategy + * + * @internal This property is public in order to reduce the size of the + * class' serialized representation. Do not access it. Use + * {@link getTraversalStrategy()} instead. + */ + public $traversalStrategy = TraversalStrategy::NONE; + + /** + * Is auto-mapping enabled? + * + * @var int + * + * @see AutoMappingStrategy + * + * @internal This property is public in order to reduce the size of the + * class' serialized representation. Do not access it. Use + * {@link getAutoMappingStrategy()} instead. + */ + public $autoMappingStrategy = AutoMappingStrategy::NONE; + + /** + * Returns the names of the properties that should be serialized. + * + * @return string[] + */ + public function __sleep() + { + return [ + 'constraints', + 'constraintsByGroup', + 'cascadingStrategy', + 'traversalStrategy', + 'autoMappingStrategy', + ]; + } + + /** + * Clones this object. + */ + public function __clone() + { + $constraints = $this->constraints; + + $this->constraints = []; + $this->constraintsByGroup = []; + + foreach ($constraints as $constraint) { + $this->addConstraint(clone $constraint); + } + } + + /** + * Adds a constraint. + * + * If the constraint {@link Valid} is added, the cascading strategy will be + * changed to {@link CascadingStrategy::CASCADE}. Depending on the + * $traverse property of that constraint, the traversal strategy + * will be set to one of the following: + * + * - {@link TraversalStrategy::IMPLICIT} if $traverse is enabled + * - {@link TraversalStrategy::NONE} if $traverse is disabled + * + * @return $this + * + * @throws ConstraintDefinitionException When trying to add the {@link Cascade} + * or {@link Traverse} constraint + */ + public function addConstraint(Constraint $constraint) + { + if ($constraint instanceof Traverse || $constraint instanceof Cascade) { + throw new ConstraintDefinitionException(sprintf('The constraint "%s" can only be put on classes. Please use "Symfony\Component\Validator\Constraints\Valid" instead.', get_debug_type($constraint))); + } + + if ($constraint instanceof Valid && null === $constraint->groups) { + $this->cascadingStrategy = CascadingStrategy::CASCADE; + + if ($constraint->traverse) { + $this->traversalStrategy = TraversalStrategy::IMPLICIT; + } else { + $this->traversalStrategy = TraversalStrategy::NONE; + } + + return $this; + } + + if ($constraint instanceof DisableAutoMapping || $constraint instanceof EnableAutoMapping) { + $this->autoMappingStrategy = $constraint instanceof EnableAutoMapping ? AutoMappingStrategy::ENABLED : AutoMappingStrategy::DISABLED; + + // The constraint is not added + return $this; + } + + $this->constraints[] = $constraint; + + foreach ($constraint->groups as $group) { + $this->constraintsByGroup[$group][] = $constraint; + } + + return $this; + } + + /** + * Adds an list of constraints. + * + * @param Constraint[] $constraints The constraints to add + * + * @return $this + */ + public function addConstraints(array $constraints) + { + foreach ($constraints as $constraint) { + $this->addConstraint($constraint); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getConstraints() + { + return $this->constraints; + } + + /** + * Returns whether this element has any constraints. + * + * @return bool + */ + public function hasConstraints() + { + return \count($this->constraints) > 0; + } + + /** + * {@inheritdoc} + * + * Aware of the global group (* group). + */ + public function findConstraints(string $group) + { + return $this->constraintsByGroup[$group] ?? []; + } + + /** + * {@inheritdoc} + */ + public function getCascadingStrategy() + { + return $this->cascadingStrategy; + } + + /** + * {@inheritdoc} + */ + public function getTraversalStrategy() + { + return $this->traversalStrategy; + } + + /** + * @see AutoMappingStrategy + */ + public function getAutoMappingStrategy(): int + { + return $this->autoMappingStrategy; + } +} diff --git a/src/vendor/symfony/validator/Mapping/GetterMetadata.php b/src/vendor/symfony/validator/Mapping/GetterMetadata.php new file mode 100644 index 0000000..0be3329 --- /dev/null +++ b/src/vendor/symfony/validator/Mapping/GetterMetadata.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Mapping; + +use Symfony\Component\Validator\Exception\ValidatorException; + +/** + * Stores all metadata needed for validating a class property via its getter + * method. + * + * A property getter is any method that is equal to the property's name, + * prefixed with "get", "is" or "has". That method will be used to access the + * property's value. + * + * The getter will be invoked by reflection, so the access of private and + * protected getters is supported. + * + * This class supports serialization and cloning. + * + * @author Bernhard Schussek + * + * @see PropertyMetadataInterface + */ +class GetterMetadata extends MemberMetadata +{ + /** + * @param string $class The class the getter is defined on + * @param string $property The property which the getter returns + * @param string|null $method The method that is called to retrieve the value being validated (null for auto-detection) + * + * @throws ValidatorException + */ + public function __construct(string $class, string $property, string $method = null) + { + if (null === $method) { + $getMethod = 'get'.ucfirst($property); + $isMethod = 'is'.ucfirst($property); + $hasMethod = 'has'.ucfirst($property); + + if (method_exists($class, $getMethod)) { + $method = $getMethod; + } elseif (method_exists($class, $isMethod)) { + $method = $isMethod; + } elseif (method_exists($class, $hasMethod)) { + $method = $hasMethod; + } else { + throw new ValidatorException(sprintf('Neither of these methods exist in class "%s": "%s", "%s", "%s".', $class, $getMethod, $isMethod, $hasMethod)); + } + } elseif (!method_exists($class, $method)) { + throw new ValidatorException(sprintf('The "%s()" method does not exist in class "%s".', $method, $class)); + } + + parent::__construct($class, $method, $property); + } + + /** + * {@inheritdoc} + */ + public function getPropertyValue($object) + { + return $this->newReflectionMember($object)->invoke($object); + } + + /** + * {@inheritdoc} + */ + protected function newReflectionMember($objectOrClassName) + { + return new \ReflectionMethod($objectOrClassName, $this->getName()); + } +} diff --git a/src/vendor/symfony/validator/Mapping/Loader/AbstractLoader.php b/src/vendor/symfony/validator/Mapping/Loader/AbstractLoader.php new file mode 100644 index 0000000..9f6667f --- /dev/null +++ b/src/vendor/symfony/validator/Mapping/Loader/AbstractLoader.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Mapping\Loader; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Exception\MappingException; + +/** + * Base loader for validation metadata. + * + * This loader supports the loading of constraints from Symfony's default + * namespace (see {@link DEFAULT_NAMESPACE}) using the short class names of + * those constraints. Constraints can also be loaded using their fully + * qualified class names. At last, namespace aliases can be defined to load + * constraints with the syntax "alias:ShortName". + * + * @author Bernhard Schussek + */ +abstract class AbstractLoader implements LoaderInterface +{ + /** + * The namespace to load constraints from by default. + */ + public const DEFAULT_NAMESPACE = '\\Symfony\\Component\\Validator\\Constraints\\'; + + protected $namespaces = []; + + /** + * Adds a namespace alias. + * + * The namespace alias can be used to reference constraints from specific + * namespaces in {@link newConstraint()}: + * + * $this->addNamespaceAlias('mynamespace', '\\Acme\\Package\\Constraints\\'); + * + * $constraint = $this->newConstraint('mynamespace:NotNull'); + */ + protected function addNamespaceAlias(string $alias, string $namespace) + { + $this->namespaces[$alias] = $namespace; + } + + /** + * Creates a new constraint instance for the given constraint name. + * + * @param string $name The constraint name. Either a constraint relative + * to the default constraint namespace, or a fully + * qualified class name. Alternatively, the constraint + * may be preceded by a namespace alias and a colon. + * The namespace alias must have been defined using + * {@link addNamespaceAlias()}. + * @param mixed $options The constraint options + * + * @return Constraint + * + * @throws MappingException If the namespace prefix is undefined + */ + protected function newConstraint(string $name, $options = null) + { + if (str_contains($name, '\\') && class_exists($name)) { + $className = $name; + } elseif (str_contains($name, ':')) { + [$prefix, $className] = explode(':', $name, 2); + + if (!isset($this->namespaces[$prefix])) { + throw new MappingException(sprintf('Undefined namespace prefix "%s".', $prefix)); + } + + $className = $this->namespaces[$prefix].$className; + } else { + $className = self::DEFAULT_NAMESPACE.$name; + } + + return new $className($options); + } +} diff --git a/src/vendor/symfony/validator/Mapping/Loader/AnnotationLoader.php b/src/vendor/symfony/validator/Mapping/Loader/AnnotationLoader.php new file mode 100644 index 0000000..dfe0f83 --- /dev/null +++ b/src/vendor/symfony/validator/Mapping/Loader/AnnotationLoader.php @@ -0,0 +1,145 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Mapping\Loader; + +use Doctrine\Common\Annotations\Reader; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Constraints\Callback; +use Symfony\Component\Validator\Constraints\GroupSequence; +use Symfony\Component\Validator\Constraints\GroupSequenceProvider; +use Symfony\Component\Validator\Exception\MappingException; +use Symfony\Component\Validator\Mapping\ClassMetadata; + +/** + * Loads validation metadata using a Doctrine annotation {@link Reader} or using PHP 8 attributes. + * + * @author Bernhard Schussek + * @author Alexander M. Turek + */ +class AnnotationLoader implements LoaderInterface +{ + protected $reader; + + public function __construct(Reader $reader = null) + { + $this->reader = $reader; + } + + /** + * {@inheritdoc} + */ + public function loadClassMetadata(ClassMetadata $metadata) + { + $reflClass = $metadata->getReflectionClass(); + $className = $reflClass->name; + $success = false; + + foreach ($this->getAnnotations($reflClass) as $constraint) { + if ($constraint instanceof GroupSequence) { + $metadata->setGroupSequence($constraint->groups); + } elseif ($constraint instanceof GroupSequenceProvider) { + $metadata->setGroupSequenceProvider(true); + } elseif ($constraint instanceof Constraint) { + $metadata->addConstraint($constraint); + } + + $success = true; + } + + foreach ($reflClass->getProperties() as $property) { + if ($property->getDeclaringClass()->name === $className) { + foreach ($this->getAnnotations($property) as $constraint) { + if ($constraint instanceof Constraint) { + $metadata->addPropertyConstraint($property->name, $constraint); + } + + $success = true; + } + } + } + + foreach ($reflClass->getMethods() as $method) { + if ($method->getDeclaringClass()->name === $className) { + foreach ($this->getAnnotations($method) as $constraint) { + if ($constraint instanceof Callback) { + $constraint->callback = $method->getName(); + + $metadata->addConstraint($constraint); + } elseif ($constraint instanceof Constraint) { + if (preg_match('/^(get|is|has)(.+)$/i', $method->name, $matches)) { + $metadata->addGetterMethodConstraint(lcfirst($matches[2]), $matches[0], $constraint); + } else { + throw new MappingException(sprintf('The constraint on "%s::%s()" cannot be added. Constraints can only be added on methods beginning with "get", "is" or "has".', $className, $method->name)); + } + } + + $success = true; + } + } + } + + return $success; + } + + /** + * @param \ReflectionClass|\ReflectionMethod|\ReflectionProperty $reflection + */ + private function getAnnotations(object $reflection): iterable + { + $dedup = []; + + if (\PHP_VERSION_ID >= 80000) { + foreach ($reflection->getAttributes(GroupSequence::class) as $attribute) { + $dedup[] = $attribute->newInstance(); + yield $attribute->newInstance(); + } + foreach ($reflection->getAttributes(GroupSequenceProvider::class) as $attribute) { + $dedup[] = $attribute->newInstance(); + yield $attribute->newInstance(); + } + foreach ($reflection->getAttributes(Constraint::class, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) { + $dedup[] = $attribute->newInstance(); + yield $attribute->newInstance(); + } + } + if (!$this->reader) { + return; + } + + $annotations = []; + + if ($reflection instanceof \ReflectionClass) { + $annotations = $this->reader->getClassAnnotations($reflection); + } + if ($reflection instanceof \ReflectionMethod) { + $annotations = $this->reader->getMethodAnnotations($reflection); + } + if ($reflection instanceof \ReflectionProperty) { + $annotations = $this->reader->getPropertyAnnotations($reflection); + } + + foreach ($dedup as $annotation) { + if ($annotation instanceof Constraint) { + $annotation->groups; // trigger initialization of the "groups" property + } + } + + foreach ($annotations as $annotation) { + if ($annotation instanceof Constraint) { + $annotation->groups; // trigger initialization of the "groups" property + } + if (!\in_array($annotation, $dedup, false)) { + yield $annotation; + } + } + } +} diff --git a/src/vendor/symfony/validator/Mapping/Loader/AutoMappingTrait.php b/src/vendor/symfony/validator/Mapping/Loader/AutoMappingTrait.php new file mode 100644 index 0000000..f76442f --- /dev/null +++ b/src/vendor/symfony/validator/Mapping/Loader/AutoMappingTrait.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Mapping\Loader; + +use Symfony\Component\Validator\Mapping\AutoMappingStrategy; +use Symfony\Component\Validator\Mapping\ClassMetadata; + +/** + * Utility methods to create auto mapping loaders. + * + * @author Kévin Dunglas + */ +trait AutoMappingTrait +{ + private function isAutoMappingEnabledForClass(ClassMetadata $metadata, string $classValidatorRegexp = null): bool + { + // Check if AutoMapping constraint is set first + if (AutoMappingStrategy::NONE !== $strategy = $metadata->getAutoMappingStrategy()) { + return AutoMappingStrategy::ENABLED === $strategy; + } + + // Fallback on the config + return null !== $classValidatorRegexp && preg_match($classValidatorRegexp, $metadata->getClassName()); + } +} diff --git a/src/vendor/symfony/validator/Mapping/Loader/FileLoader.php b/src/vendor/symfony/validator/Mapping/Loader/FileLoader.php new file mode 100644 index 0000000..0fc0172 --- /dev/null +++ b/src/vendor/symfony/validator/Mapping/Loader/FileLoader.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Mapping\Loader; + +use Symfony\Component\Validator\Exception\MappingException; + +/** + * Base loader for loading validation metadata from a file. + * + * @author Bernhard Schussek + * + * @see YamlFileLoader + * @see XmlFileLoader + */ +abstract class FileLoader extends AbstractLoader +{ + protected $file; + + /** + * Creates a new loader. + * + * @param string $file The mapping file to load + * + * @throws MappingException If the file does not exist or is not readable + */ + public function __construct(string $file) + { + if (!is_file($file)) { + throw new MappingException(sprintf('The mapping file "%s" does not exist.', $file)); + } + + if (!is_readable($file)) { + throw new MappingException(sprintf('The mapping file "%s" is not readable.', $file)); + } + + if (!stream_is_local($this->file)) { + throw new MappingException(sprintf('The mapping file "%s" is not a local file.', $file)); + } + + $this->file = $file; + } +} diff --git a/src/vendor/symfony/validator/Mapping/Loader/FilesLoader.php b/src/vendor/symfony/validator/Mapping/Loader/FilesLoader.php new file mode 100644 index 0000000..f6574f4 --- /dev/null +++ b/src/vendor/symfony/validator/Mapping/Loader/FilesLoader.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Mapping\Loader; + +/** + * Base loader for loading validation metadata from a list of files. + * + * @author Bulat Shakirzyanov + * @author Bernhard Schussek + * + * @see YamlFilesLoader + * @see XmlFilesLoader + */ +abstract class FilesLoader extends LoaderChain +{ + /** + * Creates a new loader. + * + * @param array $paths An array of file paths + */ + public function __construct(array $paths) + { + parent::__construct($this->getFileLoaders($paths)); + } + + /** + * Returns an array of file loaders for the given file paths. + * + * @return LoaderInterface[] + */ + protected function getFileLoaders(array $paths) + { + $loaders = []; + + foreach ($paths as $path) { + $loaders[] = $this->getFileLoaderInstance($path); + } + + return $loaders; + } + + /** + * Creates a loader for the given file path. + * + * @return LoaderInterface + */ + abstract protected function getFileLoaderInstance(string $path); +} diff --git a/src/vendor/symfony/validator/Mapping/Loader/LoaderChain.php b/src/vendor/symfony/validator/Mapping/Loader/LoaderChain.php new file mode 100644 index 0000000..b3d6184 --- /dev/null +++ b/src/vendor/symfony/validator/Mapping/Loader/LoaderChain.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Mapping\Loader; + +use Symfony\Component\Validator\Exception\MappingException; +use Symfony\Component\Validator\Mapping\ClassMetadata; + +/** + * Loads validation metadata from multiple {@link LoaderInterface} instances. + * + * Pass the loaders when constructing the chain. Once + * {@link loadClassMetadata()} is called, that method will be called on all + * loaders in the chain. + * + * @author Bernhard Schussek + */ +class LoaderChain implements LoaderInterface +{ + protected $loaders; + + /** + * @param LoaderInterface[] $loaders The metadata loaders to use + * + * @throws MappingException If any of the loaders has an invalid type + */ + public function __construct(array $loaders) + { + foreach ($loaders as $loader) { + if (!$loader instanceof LoaderInterface) { + throw new MappingException(sprintf('Class "%s" is expected to implement LoaderInterface.', get_debug_type($loader))); + } + } + + $this->loaders = $loaders; + } + + /** + * {@inheritdoc} + */ + public function loadClassMetadata(ClassMetadata $metadata) + { + $success = false; + + foreach ($this->loaders as $loader) { + $success = $loader->loadClassMetadata($metadata) || $success; + } + + return $success; + } + + /** + * @return LoaderInterface[] + */ + public function getLoaders() + { + return $this->loaders; + } +} diff --git a/src/vendor/symfony/validator/Mapping/Loader/LoaderInterface.php b/src/vendor/symfony/validator/Mapping/Loader/LoaderInterface.php new file mode 100644 index 0000000..974214c --- /dev/null +++ b/src/vendor/symfony/validator/Mapping/Loader/LoaderInterface.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Mapping\Loader; + +use Symfony\Component\Validator\Mapping\ClassMetadata; + +/** + * Loads validation metadata into {@link ClassMetadata} instances. + * + * @author Bernhard Schussek + */ +interface LoaderInterface +{ + /** + * Loads validation metadata into a {@link ClassMetadata} instance. + * + * @return bool + */ + public function loadClassMetadata(ClassMetadata $metadata); +} diff --git a/src/vendor/symfony/validator/Mapping/Loader/PropertyInfoLoader.php b/src/vendor/symfony/validator/Mapping/Loader/PropertyInfoLoader.php new file mode 100644 index 0000000..118c159 --- /dev/null +++ b/src/vendor/symfony/validator/Mapping/Loader/PropertyInfoLoader.php @@ -0,0 +1,179 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Mapping\Loader; + +use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyListExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; +use Symfony\Component\PropertyInfo\Type as PropertyInfoType; +use Symfony\Component\Validator\Constraints\All; +use Symfony\Component\Validator\Constraints\NotBlank; +use Symfony\Component\Validator\Constraints\NotNull; +use Symfony\Component\Validator\Constraints\Type; +use Symfony\Component\Validator\Mapping\AutoMappingStrategy; +use Symfony\Component\Validator\Mapping\ClassMetadata; + +/** + * Guesses and loads the appropriate constraints using PropertyInfo. + * + * @author Kévin Dunglas + */ +final class PropertyInfoLoader implements LoaderInterface +{ + use AutoMappingTrait; + + private $listExtractor; + private $typeExtractor; + private $accessExtractor; + private $classValidatorRegexp; + + public function __construct(PropertyListExtractorInterface $listExtractor, PropertyTypeExtractorInterface $typeExtractor, PropertyAccessExtractorInterface $accessExtractor, string $classValidatorRegexp = null) + { + $this->listExtractor = $listExtractor; + $this->typeExtractor = $typeExtractor; + $this->accessExtractor = $accessExtractor; + $this->classValidatorRegexp = $classValidatorRegexp; + } + + /** + * {@inheritdoc} + */ + public function loadClassMetadata(ClassMetadata $metadata): bool + { + $className = $metadata->getClassName(); + if (!$properties = $this->listExtractor->getProperties($className)) { + return false; + } + + $loaded = false; + $enabledForClass = $this->isAutoMappingEnabledForClass($metadata, $this->classValidatorRegexp); + foreach ($properties as $property) { + if (false === $this->accessExtractor->isWritable($className, $property)) { + continue; + } + + if (!property_exists($className, $property)) { + continue; + } + + $types = $this->typeExtractor->getTypes($className, $property); + if (null === $types) { + continue; + } + + $enabledForProperty = $enabledForClass; + $hasTypeConstraint = false; + $hasNotNullConstraint = false; + $hasNotBlankConstraint = false; + $allConstraint = null; + foreach ($metadata->getPropertyMetadata($property) as $propertyMetadata) { + // Enabling or disabling auto-mapping explicitly always takes precedence + if (AutoMappingStrategy::DISABLED === $propertyMetadata->getAutoMappingStrategy()) { + continue 2; + } + + if (AutoMappingStrategy::ENABLED === $propertyMetadata->getAutoMappingStrategy()) { + $enabledForProperty = true; + } + + foreach ($propertyMetadata->getConstraints() as $constraint) { + if ($constraint instanceof Type) { + $hasTypeConstraint = true; + } elseif ($constraint instanceof NotNull) { + $hasNotNullConstraint = true; + } elseif ($constraint instanceof NotBlank) { + $hasNotBlankConstraint = true; + } elseif ($constraint instanceof All) { + $allConstraint = $constraint; + } + } + } + + if (!$enabledForProperty) { + continue; + } + + $loaded = true; + $builtinTypes = []; + $nullable = false; + $scalar = true; + foreach ($types as $type) { + $builtinTypes[] = $type->getBuiltinType(); + + if ($scalar && !\in_array($type->getBuiltinType(), [PropertyInfoType::BUILTIN_TYPE_INT, PropertyInfoType::BUILTIN_TYPE_FLOAT, PropertyInfoType::BUILTIN_TYPE_STRING, PropertyInfoType::BUILTIN_TYPE_BOOL], true)) { + $scalar = false; + } + + if (!$nullable && $type->isNullable()) { + $nullable = true; + } + } + if (!$hasTypeConstraint) { + if (1 === \count($builtinTypes)) { + if ($types[0]->isCollection() && \count($collectionValueType = $types[0]->getCollectionValueTypes()) > 0) { + [$collectionValueType] = $collectionValueType; + $this->handleAllConstraint($property, $allConstraint, $collectionValueType, $metadata); + } + + $metadata->addPropertyConstraint($property, $this->getTypeConstraint($builtinTypes[0], $types[0])); + } elseif ($scalar) { + $metadata->addPropertyConstraint($property, new Type(['type' => 'scalar'])); + } + } + + if (!$nullable && !$hasNotBlankConstraint && !$hasNotNullConstraint) { + $metadata->addPropertyConstraint($property, new NotNull()); + } + } + + return $loaded; + } + + private function getTypeConstraint(string $builtinType, PropertyInfoType $type): Type + { + if (PropertyInfoType::BUILTIN_TYPE_OBJECT === $builtinType && null !== $className = $type->getClassName()) { + return new Type(['type' => $className]); + } + + return new Type(['type' => $builtinType]); + } + + private function handleAllConstraint(string $property, ?All $allConstraint, PropertyInfoType $propertyInfoType, ClassMetadata $metadata) + { + $containsTypeConstraint = false; + $containsNotNullConstraint = false; + if (null !== $allConstraint) { + foreach ($allConstraint->constraints as $constraint) { + if ($constraint instanceof Type) { + $containsTypeConstraint = true; + } elseif ($constraint instanceof NotNull) { + $containsNotNullConstraint = true; + } + } + } + + $constraints = []; + if (!$containsNotNullConstraint && !$propertyInfoType->isNullable()) { + $constraints[] = new NotNull(); + } + + if (!$containsTypeConstraint) { + $constraints[] = $this->getTypeConstraint($propertyInfoType->getBuiltinType(), $propertyInfoType); + } + + if (null === $allConstraint) { + $metadata->addPropertyConstraint($property, new All(['constraints' => $constraints])); + } else { + $allConstraint->constraints = array_merge($allConstraint->constraints, $constraints); + } + } +} diff --git a/src/vendor/symfony/validator/Mapping/Loader/StaticMethodLoader.php b/src/vendor/symfony/validator/Mapping/Loader/StaticMethodLoader.php new file mode 100644 index 0000000..256700e --- /dev/null +++ b/src/vendor/symfony/validator/Mapping/Loader/StaticMethodLoader.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Mapping\Loader; + +use Symfony\Component\Validator\Exception\MappingException; +use Symfony\Component\Validator\Mapping\ClassMetadata; + +/** + * Loads validation metadata by calling a static method on the loaded class. + * + * @author Bernhard Schussek + */ +class StaticMethodLoader implements LoaderInterface +{ + protected $methodName; + + /** + * Creates a new loader. + * + * @param string $methodName The name of the static method to call + */ + public function __construct(string $methodName = 'loadValidatorMetadata') + { + $this->methodName = $methodName; + } + + /** + * {@inheritdoc} + */ + public function loadClassMetadata(ClassMetadata $metadata) + { + /** @var \ReflectionClass $reflClass */ + $reflClass = $metadata->getReflectionClass(); + + if (!$reflClass->isInterface() && $reflClass->hasMethod($this->methodName)) { + $reflMethod = $reflClass->getMethod($this->methodName); + + if ($reflMethod->isAbstract()) { + return false; + } + + if (!$reflMethod->isStatic()) { + throw new MappingException(sprintf('The method "%s::%s()" should be static.', $reflClass->name, $this->methodName)); + } + + if ($reflMethod->getDeclaringClass()->name != $reflClass->name) { + return false; + } + + $reflMethod->invoke(null, $metadata); + + return true; + } + + return false; + } +} diff --git a/src/vendor/symfony/validator/Mapping/Loader/XmlFileLoader.php b/src/vendor/symfony/validator/Mapping/Loader/XmlFileLoader.php new file mode 100644 index 0000000..66cfe8f --- /dev/null +++ b/src/vendor/symfony/validator/Mapping/Loader/XmlFileLoader.php @@ -0,0 +1,231 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Mapping\Loader; + +use Symfony\Component\Config\Util\XmlUtils; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Exception\MappingException; +use Symfony\Component\Validator\Mapping\ClassMetadata; + +/** + * Loads validation metadata from an XML file. + * + * @author Bernhard Schussek + */ +class XmlFileLoader extends FileLoader +{ + /** + * The XML nodes of the mapping file. + * + * @var \SimpleXMLElement[]|null + */ + protected $classes; + + /** + * {@inheritdoc} + */ + public function loadClassMetadata(ClassMetadata $metadata) + { + if (null === $this->classes) { + $this->loadClassesFromXml(); + } + + if (isset($this->classes[$metadata->getClassName()])) { + $classDescription = $this->classes[$metadata->getClassName()]; + + $this->loadClassMetadataFromXml($metadata, $classDescription); + + return true; + } + + return false; + } + + /** + * Return the names of the classes mapped in this file. + * + * @return string[] + */ + public function getMappedClasses() + { + if (null === $this->classes) { + $this->loadClassesFromXml(); + } + + return array_keys($this->classes); + } + + /** + * Parses a collection of "constraint" XML nodes. + * + * @param \SimpleXMLElement $nodes The XML nodes + * + * @return Constraint[] + */ + protected function parseConstraints(\SimpleXMLElement $nodes) + { + $constraints = []; + + foreach ($nodes as $node) { + if (\count($node) > 0) { + if (\count($node->value) > 0) { + $options = $this->parseValues($node->value); + } elseif (\count($node->constraint) > 0) { + $options = $this->parseConstraints($node->constraint); + } elseif (\count($node->option) > 0) { + $options = $this->parseOptions($node->option); + } else { + $options = []; + } + } elseif ('' !== (string) $node) { + $options = XmlUtils::phpize(trim($node)); + } else { + $options = null; + } + + $constraints[] = $this->newConstraint((string) $node['name'], $options); + } + + return $constraints; + } + + /** + * Parses a collection of "value" XML nodes. + * + * @param \SimpleXMLElement $nodes The XML nodes + * + * @return array + */ + protected function parseValues(\SimpleXMLElement $nodes) + { + $values = []; + + foreach ($nodes as $node) { + if (\count($node) > 0) { + if (\count($node->value) > 0) { + $value = $this->parseValues($node->value); + } elseif (\count($node->constraint) > 0) { + $value = $this->parseConstraints($node->constraint); + } else { + $value = []; + } + } else { + $value = trim($node); + } + + if (isset($node['key'])) { + $values[(string) $node['key']] = $value; + } else { + $values[] = $value; + } + } + + return $values; + } + + /** + * Parses a collection of "option" XML nodes. + * + * @param \SimpleXMLElement $nodes The XML nodes + * + * @return array + */ + protected function parseOptions(\SimpleXMLElement $nodes) + { + $options = []; + + foreach ($nodes as $node) { + if (\count($node) > 0) { + if (\count($node->value) > 0) { + $value = $this->parseValues($node->value); + } elseif (\count($node->constraint) > 0) { + $value = $this->parseConstraints($node->constraint); + } else { + $value = []; + } + } else { + $value = XmlUtils::phpize($node); + if (\is_string($value)) { + $value = trim($value); + } + } + + $options[(string) $node['name']] = $value; + } + + return $options; + } + + /** + * Loads the XML class descriptions from the given file. + * + * @return \SimpleXMLElement + * + * @throws MappingException If the file could not be loaded + */ + protected function parseFile(string $path) + { + try { + $dom = XmlUtils::loadFile($path, __DIR__.'/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd'); + } catch (\Exception $e) { + throw new MappingException($e->getMessage(), $e->getCode(), $e); + } + + return simplexml_import_dom($dom); + } + + private function loadClassesFromXml() + { + // This method may throw an exception. Do not modify the class' + // state before it completes + $xml = $this->parseFile($this->file); + + $this->classes = []; + + foreach ($xml->namespace as $namespace) { + $this->addNamespaceAlias((string) $namespace['prefix'], trim((string) $namespace)); + } + + foreach ($xml->class as $class) { + $this->classes[(string) $class['name']] = $class; + } + } + + private function loadClassMetadataFromXml(ClassMetadata $metadata, \SimpleXMLElement $classDescription) + { + if (\count($classDescription->{'group-sequence-provider'}) > 0) { + $metadata->setGroupSequenceProvider(true); + } + + foreach ($classDescription->{'group-sequence'} as $groupSequence) { + if (\count($groupSequence->value) > 0) { + $metadata->setGroupSequence($this->parseValues($groupSequence[0]->value)); + } + } + + foreach ($this->parseConstraints($classDescription->constraint) as $constraint) { + $metadata->addConstraint($constraint); + } + + foreach ($classDescription->property as $property) { + foreach ($this->parseConstraints($property->constraint) as $constraint) { + $metadata->addPropertyConstraint((string) $property['name'], $constraint); + } + } + + foreach ($classDescription->getter as $getter) { + foreach ($this->parseConstraints($getter->constraint) as $constraint) { + $metadata->addGetterConstraint((string) $getter['property'], $constraint); + } + } + } +} diff --git a/src/vendor/symfony/validator/Mapping/Loader/XmlFilesLoader.php b/src/vendor/symfony/validator/Mapping/Loader/XmlFilesLoader.php new file mode 100644 index 0000000..5a242b0 --- /dev/null +++ b/src/vendor/symfony/validator/Mapping/Loader/XmlFilesLoader.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Mapping\Loader; + +/** + * Loads validation metadata from a list of XML files. + * + * @author Bulat Shakirzyanov + * @author Bernhard Schussek + * + * @see FilesLoader + */ +class XmlFilesLoader extends FilesLoader +{ + /** + * {@inheritdoc} + */ + public function getFileLoaderInstance(string $file) + { + return new XmlFileLoader($file); + } +} diff --git a/src/vendor/symfony/validator/Mapping/Loader/YamlFileLoader.php b/src/vendor/symfony/validator/Mapping/Loader/YamlFileLoader.php new file mode 100644 index 0000000..8d44ad6 --- /dev/null +++ b/src/vendor/symfony/validator/Mapping/Loader/YamlFileLoader.php @@ -0,0 +1,189 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Mapping\Loader; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Parser as YamlParser; +use Symfony\Component\Yaml\Yaml; + +/** + * Loads validation metadata from a YAML file. + * + * @author Bernhard Schussek + */ +class YamlFileLoader extends FileLoader +{ + /** + * An array of YAML class descriptions. + * + * @var array + */ + protected $classes = null; + + /** + * Caches the used YAML parser. + * + * @var YamlParser + */ + private $yamlParser; + + /** + * {@inheritdoc} + */ + public function loadClassMetadata(ClassMetadata $metadata) + { + if (null === $this->classes) { + $this->loadClassesFromYaml(); + } + + if (isset($this->classes[$metadata->getClassName()])) { + $classDescription = $this->classes[$metadata->getClassName()]; + + $this->loadClassMetadataFromYaml($metadata, $classDescription); + + return true; + } + + return false; + } + + /** + * Return the names of the classes mapped in this file. + * + * @return string[] + */ + public function getMappedClasses() + { + if (null === $this->classes) { + $this->loadClassesFromYaml(); + } + + return array_keys($this->classes); + } + + /** + * Parses a collection of YAML nodes. + * + * @param array $nodes The YAML nodes + * + * @return array + */ + protected function parseNodes(array $nodes) + { + $values = []; + + foreach ($nodes as $name => $childNodes) { + if (is_numeric($name) && \is_array($childNodes) && 1 === \count($childNodes)) { + $options = current($childNodes); + + if (\is_array($options)) { + $options = $this->parseNodes($options); + } + + $values[] = $this->newConstraint(key($childNodes), $options); + } else { + if (\is_array($childNodes)) { + $childNodes = $this->parseNodes($childNodes); + } + + $values[$name] = $childNodes; + } + } + + return $values; + } + + /** + * Loads the YAML class descriptions from the given file. + * + * @throws \InvalidArgumentException If the file could not be loaded or did + * not contain a YAML array + */ + private function parseFile(string $path): array + { + try { + $classes = $this->yamlParser->parseFile($path, Yaml::PARSE_CONSTANT); + } catch (ParseException $e) { + throw new \InvalidArgumentException(sprintf('The file "%s" does not contain valid YAML: ', $path).$e->getMessage(), 0, $e); + } + + // empty file + if (null === $classes) { + return []; + } + + // not an array + if (!\is_array($classes)) { + throw new \InvalidArgumentException(sprintf('The file "%s" must contain a YAML array.', $this->file)); + } + + return $classes; + } + + private function loadClassesFromYaml() + { + if (null === $this->yamlParser) { + $this->yamlParser = new YamlParser(); + } + + $this->classes = $this->parseFile($this->file); + + if (isset($this->classes['namespaces'])) { + foreach ($this->classes['namespaces'] as $alias => $namespace) { + $this->addNamespaceAlias($alias, $namespace); + } + + unset($this->classes['namespaces']); + } + } + + private function loadClassMetadataFromYaml(ClassMetadata $metadata, array $classDescription) + { + if (isset($classDescription['group_sequence_provider'])) { + $metadata->setGroupSequenceProvider( + (bool) $classDescription['group_sequence_provider'] + ); + } + + if (isset($classDescription['group_sequence'])) { + $metadata->setGroupSequence($classDescription['group_sequence']); + } + + if (isset($classDescription['constraints']) && \is_array($classDescription['constraints'])) { + foreach ($this->parseNodes($classDescription['constraints']) as $constraint) { + $metadata->addConstraint($constraint); + } + } + + if (isset($classDescription['properties']) && \is_array($classDescription['properties'])) { + foreach ($classDescription['properties'] as $property => $constraints) { + if (null !== $constraints) { + foreach ($this->parseNodes($constraints) as $constraint) { + $metadata->addPropertyConstraint($property, $constraint); + } + } + } + } + + if (isset($classDescription['getters']) && \is_array($classDescription['getters'])) { + foreach ($classDescription['getters'] as $getter => $constraints) { + if (null !== $constraints) { + foreach ($this->parseNodes($constraints) as $constraint) { + $metadata->addGetterConstraint($getter, $constraint); + } + } + } + } + } +} diff --git a/src/vendor/symfony/validator/Mapping/Loader/YamlFilesLoader.php b/src/vendor/symfony/validator/Mapping/Loader/YamlFilesLoader.php new file mode 100644 index 0000000..f32c67c --- /dev/null +++ b/src/vendor/symfony/validator/Mapping/Loader/YamlFilesLoader.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Mapping\Loader; + +/** + * Loads validation metadata from a list of YAML files. + * + * @author Bulat Shakirzyanov + * @author Bernhard Schussek + * + * @see FilesLoader + */ +class YamlFilesLoader extends FilesLoader +{ + /** + * {@inheritdoc} + */ + public function getFileLoaderInstance(string $file) + { + return new YamlFileLoader($file); + } +} diff --git a/src/vendor/symfony/validator/Mapping/Loader/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd b/src/vendor/symfony/validator/Mapping/Loader/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd new file mode 100644 index 0000000..1ca840b --- /dev/null +++ b/src/vendor/symfony/validator/Mapping/Loader/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/vendor/symfony/validator/Mapping/MemberMetadata.php b/src/vendor/symfony/validator/Mapping/MemberMetadata.php new file mode 100644 index 0000000..33934c6 --- /dev/null +++ b/src/vendor/symfony/validator/Mapping/MemberMetadata.php @@ -0,0 +1,194 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Mapping; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Constraints\Composite; +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; + +/** + * Stores all metadata needed for validating a class property. + * + * The method of accessing the property's value must be specified by subclasses + * by implementing the {@link newReflectionMember()} method. + * + * This class supports serialization and cloning. + * + * @author Bernhard Schussek + * + * @see PropertyMetadataInterface + */ +abstract class MemberMetadata extends GenericMetadata implements PropertyMetadataInterface +{ + /** + * @internal This property is public in order to reduce the size of the + * class' serialized representation. Do not access it. Use + * {@link getClassName()} instead. + */ + public $class; + + /** + * @internal This property is public in order to reduce the size of the + * class' serialized representation. Do not access it. Use + * {@link getName()} instead. + */ + public $name; + + /** + * @internal This property is public in order to reduce the size of the + * class' serialized representation. Do not access it. Use + * {@link getPropertyName()} instead. + */ + public $property; + + /** + * @var \ReflectionMethod[]|\ReflectionProperty[] + */ + private $reflMember = []; + + /** + * @param string $class The name of the class this member is defined on + * @param string $name The name of the member + * @param string $property The property the member belongs to + */ + public function __construct(string $class, string $name, string $property) + { + $this->class = $class; + $this->name = $name; + $this->property = $property; + } + + /** + * {@inheritdoc} + */ + public function addConstraint(Constraint $constraint) + { + $this->checkConstraint($constraint); + + parent::addConstraint($constraint); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function __sleep() + { + return array_merge(parent::__sleep(), [ + 'class', + 'name', + 'property', + ]); + } + + /** + * Returns the name of the member. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function getClassName() + { + return $this->class; + } + + /** + * {@inheritdoc} + */ + public function getPropertyName() + { + return $this->property; + } + + /** + * Returns whether this member is public. + * + * @param object|string $objectOrClassName The object or the class name + * + * @return bool + */ + public function isPublic($objectOrClassName) + { + return $this->getReflectionMember($objectOrClassName)->isPublic(); + } + + /** + * Returns whether this member is protected. + * + * @param object|string $objectOrClassName The object or the class name + * + * @return bool + */ + public function isProtected($objectOrClassName) + { + return $this->getReflectionMember($objectOrClassName)->isProtected(); + } + + /** + * Returns whether this member is private. + * + * @param object|string $objectOrClassName The object or the class name + * + * @return bool + */ + public function isPrivate($objectOrClassName) + { + return $this->getReflectionMember($objectOrClassName)->isPrivate(); + } + + /** + * Returns the reflection instance for accessing the member's value. + * + * @param object|string $objectOrClassName The object or the class name + * + * @return \ReflectionMethod|\ReflectionProperty + */ + public function getReflectionMember($objectOrClassName) + { + $className = \is_string($objectOrClassName) ? $objectOrClassName : \get_class($objectOrClassName); + if (!isset($this->reflMember[$className])) { + $this->reflMember[$className] = $this->newReflectionMember($objectOrClassName); + } + + return $this->reflMember[$className]; + } + + /** + * Creates a new reflection instance for accessing the member's value. + * + * @param object|string $objectOrClassName The object or the class name + * + * @return \ReflectionMethod|\ReflectionProperty + */ + abstract protected function newReflectionMember($objectOrClassName); + + private function checkConstraint(Constraint $constraint) + { + if (!\in_array(Constraint::PROPERTY_CONSTRAINT, (array) $constraint->getTargets(), true)) { + throw new ConstraintDefinitionException(sprintf('The constraint "%s" cannot be put on properties or getters.', get_debug_type($constraint))); + } + + if ($constraint instanceof Composite) { + foreach ($constraint->getNestedConstraints() as $nestedConstraint) { + $this->checkConstraint($nestedConstraint); + } + } + } +} diff --git a/src/vendor/symfony/validator/Mapping/MetadataInterface.php b/src/vendor/symfony/validator/Mapping/MetadataInterface.php new file mode 100644 index 0000000..efb32ce --- /dev/null +++ b/src/vendor/symfony/validator/Mapping/MetadataInterface.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Mapping; + +use Symfony\Component\Validator\Constraint; + +/** + * A container for validation metadata. + * + * Most importantly, the metadata stores the constraints against which an object + * and its properties should be validated. + * + * Additionally, the metadata stores whether objects should be validated + * against their class' metadata and whether traversable objects should be + * traversed or not. + * + * @author Bernhard Schussek + * + * @see CascadingStrategy + * @see TraversalStrategy + */ +interface MetadataInterface +{ + /** + * Returns the strategy for cascading objects. + * + * @return int + * + * @see CascadingStrategy + */ + public function getCascadingStrategy(); + + /** + * Returns the strategy for traversing traversable objects. + * + * @return int + * + * @see TraversalStrategy + */ + public function getTraversalStrategy(); + + /** + * Returns all constraints of this element. + * + * @return Constraint[] + */ + public function getConstraints(); + + /** + * Returns all constraints for a given validation group. + * + * @param string $group The validation group + * + * @return Constraint[] + */ + public function findConstraints(string $group); +} diff --git a/src/vendor/symfony/validator/Mapping/PropertyMetadata.php b/src/vendor/symfony/validator/Mapping/PropertyMetadata.php new file mode 100644 index 0000000..8c18e5d --- /dev/null +++ b/src/vendor/symfony/validator/Mapping/PropertyMetadata.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Mapping; + +use Symfony\Component\Validator\Exception\ValidatorException; + +/** + * Stores all metadata needed for validating a class property. + * + * The value of the property is obtained by directly accessing the property. + * The property will be accessed by reflection, so the access of private and + * protected properties is supported. + * + * This class supports serialization and cloning. + * + * @author Bernhard Schussek + * + * @see PropertyMetadataInterface + */ +class PropertyMetadata extends MemberMetadata +{ + /** + * @param string $class The class this property is defined on + * @param string $name The name of this property + * + * @throws ValidatorException + */ + public function __construct(string $class, string $name) + { + if (!property_exists($class, $name)) { + throw new ValidatorException(sprintf('Property "%s" does not exist in class "%s".', $name, $class)); + } + + parent::__construct($class, $name, $name); + } + + /** + * {@inheritdoc} + */ + public function getPropertyValue($object) + { + $reflProperty = $this->getReflectionMember($object); + + if (\PHP_VERSION_ID >= 70400 && $reflProperty->hasType() && !$reflProperty->isInitialized($object)) { + // There is no way to check if a property has been unset or if it is uninitialized. + // When trying to access an uninitialized property, __get method is triggered. + + // If __get method is not present, no fallback is possible + // Otherwise we need to catch an Error in case we are trying to access an uninitialized but set property. + if (!method_exists($object, '__get')) { + return null; + } + + try { + return $reflProperty->getValue($object); + } catch (\Error $e) { + return null; + } + } + + return $reflProperty->getValue($object); + } + + /** + * {@inheritdoc} + */ + protected function newReflectionMember($objectOrClassName) + { + $originalClass = \is_string($objectOrClassName) ? $objectOrClassName : \get_class($objectOrClassName); + + while (!property_exists($objectOrClassName, $this->getName())) { + $objectOrClassName = get_parent_class($objectOrClassName); + + if (false === $objectOrClassName) { + throw new ValidatorException(sprintf('Property "%s" does not exist in class "%s".', $this->getName(), $originalClass)); + } + } + + $member = new \ReflectionProperty($objectOrClassName, $this->getName()); + $member->setAccessible(true); + + return $member; + } +} diff --git a/src/vendor/symfony/validator/Mapping/PropertyMetadataInterface.php b/src/vendor/symfony/validator/Mapping/PropertyMetadataInterface.php new file mode 100644 index 0000000..f4bbbb9 --- /dev/null +++ b/src/vendor/symfony/validator/Mapping/PropertyMetadataInterface.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Mapping; + +/** + * Stores all metadata needed for validating the value of a class property. + * + * Most importantly, the metadata stores the constraints against which the + * property's value should be validated. + * + * Additionally, the metadata stores whether objects stored in the property + * should be validated against their class' metadata and whether traversable + * objects should be traversed or not. + * + * @author Bernhard Schussek + * + * @see MetadataInterface + * @see CascadingStrategy + * @see TraversalStrategy + */ +interface PropertyMetadataInterface extends MetadataInterface +{ + /** + * Returns the name of the property. + * + * @return string + */ + public function getPropertyName(); + + /** + * Extracts the value of the property from the given container. + * + * @param mixed $containingValue The container to extract the property value from + * + * @return mixed + */ + public function getPropertyValue($containingValue); +} diff --git a/src/vendor/symfony/validator/Mapping/TraversalStrategy.php b/src/vendor/symfony/validator/Mapping/TraversalStrategy.php new file mode 100644 index 0000000..0df5a7e --- /dev/null +++ b/src/vendor/symfony/validator/Mapping/TraversalStrategy.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Mapping; + +/** + * Specifies whether and how a traversable object should be traversed. + * + * If the node traverser traverses a node whose value is an instance of + * {@link \Traversable}, and if that node is either a class node or if + * cascading is enabled, then the node's traversal strategy will be checked. + * Depending on the requested traversal strategy, the node traverser will + * iterate over the object and cascade each object or collection returned by + * the iterator. + * + * The traversal strategy is ignored for arrays. Arrays are always iterated. + * + * @author Bernhard Schussek + * + * @see CascadingStrategy + */ +class TraversalStrategy +{ + /** + * Specifies that a node's value should be iterated only if it is an + * instance of {@link \Traversable}. + */ + public const IMPLICIT = 1; + + /** + * Specifies that a node's value should never be iterated. + */ + public const NONE = 2; + + /** + * Specifies that a node's value should always be iterated. If the value is + * not an instance of {@link \Traversable}, an exception should be thrown. + */ + public const TRAVERSE = 4; + + /** + * Not instantiable. + */ + private function __construct() + { + } +} diff --git a/src/vendor/symfony/validator/ObjectInitializerInterface.php b/src/vendor/symfony/validator/ObjectInitializerInterface.php new file mode 100644 index 0000000..6f3ac5e --- /dev/null +++ b/src/vendor/symfony/validator/ObjectInitializerInterface.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator; + +/** + * Prepares an object for validation. + * + * Concrete implementations of this interface are used by {@link Validator\ContextualValidatorInterface} + * to initialize objects just before validating them. + * + * @author Fabien Potencier + * @author Bernhard Schussek + */ +interface ObjectInitializerInterface +{ + public function initialize(object $object); +} diff --git a/src/vendor/symfony/validator/README.md b/src/vendor/symfony/validator/README.md new file mode 100644 index 0000000..8f3fd0e --- /dev/null +++ b/src/vendor/symfony/validator/README.md @@ -0,0 +1,16 @@ +Validator Component +=================== + +The Validator component provides tools to validate values following the +[JSR-303 Bean Validation specification][1]. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/validator.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) + +[1]: https://jcp.org/en/jsr/detail?id=303 diff --git a/src/vendor/symfony/validator/Resources/translations/validators.af.xlf b/src/vendor/symfony/validator/Resources/translations/validators.af.xlf new file mode 100644 index 0000000..d1dcf3e --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.af.xlf @@ -0,0 +1,403 @@ + + + + + + This value should be false. + Hierdie waarde moet vals wees. + + + This value should be true. + Hierdie waarde moet waar wees. + + + This value should be of type {{ type }}. + Hierdie waarde moet van die soort {{type}} wees. + + + This value should be blank. + Hierdie waarde moet leeg wees. + + + The value you selected is not a valid choice. + Die waarde wat jy gekies het is nie 'n geldige keuse nie. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Jy moet ten minste {{ limit }} kies.|Jy moet ten minste {{ limit }} keuses kies. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Jy moet by die meeste {{ limit }} keuse kies.|Jy moet by die meeste {{ limit }} keuses kies. + + + One or more of the given values is invalid. + Een of meer van die gegewe waardes is ongeldig. + + + This field was not expected. + Die veld is nie verwag nie. + + + This field is missing. + Hierdie veld ontbreek. + + + This value is not a valid date. + Hierdie waarde is nie 'n geldige datum nie. + + + This value is not a valid datetime. + Hierdie waarde is nie 'n geldige datum en tyd nie. + + + This value is not a valid email address. + Hierdie waarde is nie 'n geldige e-pos adres nie. + + + The file could not be found. + Die lêer kon nie gevind word nie. + + + The file is not readable. + Die lêer kan nie gelees word nie. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + Die lêer is te groot ({{ size }} {{ suffix }}). Toegelaat maksimum grootte is {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + Die MIME-tipe van die lêer is ongeldig ({{ type }}). Toegelaat MIME-tipes is {{ types }}. + + + This value should be {{ limit }} or less. + Hierdie waarde moet {{ limit }} of minder wees. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Hierdie waarde is te lank. Dit moet {{ limit }} karakter of minder wees.|Hierdie waarde is te lank. Dit moet {{ limit }} karakters of minder wees. + + + This value should be {{ limit }} or more. + Hierdie waarde moet {{ limit }} of meer wees. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Hierdie waarde is te kort. Dit moet {{ limit }} karakter of meer wees.|Hierdie waarde is te kort. Dit moet {{ limit }} karakters of meer wees. + + + This value should not be blank. + Hierdie waarde moet nie leeg wees nie. + + + This value should not be null. + Hierdie waarde moet nie nul wees nie. + + + This value should be null. + Hierdie waarde moet nul wees. + + + This value is not valid. + Hierdie waarde is nie geldig nie. + + + This value is not a valid time. + Hierdie waarde is nie 'n geldige tyd nie. + + + This value is not a valid URL. + Hierdie waarde is nie 'n geldige URL nie. + + + The two values should be equal. + Die twee waardes moet gelyk wees. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + Die lêer is te groot. Toegelaat maksimum grootte is {{ limit }} {{ suffix }}. + + + The file is too large. + Die lêer is te groot. + + + The file could not be uploaded. + Die lêer kan nie opgelaai word nie. + + + This value should be a valid number. + Hierdie waarde moet 'n geldige nommer wees. + + + This file is not a valid image. + Hierdie lêer is nie 'n geldige beeld nie. + + + This is not a valid IP address. + Hierdie is nie 'n geldige IP-adres nie. + + + This value is not a valid language. + Hierdie waarde is nie 'n geldige taal nie. + + + This value is not a valid locale. + Hierdie waarde is nie 'n geldige land instelling nie. + + + This value is not a valid country. + Hierdie waarde is nie 'n geldige land nie. + + + This value is already used. + Hierdie waarde word reeds gebruik. + + + The size of the image could not be detected. + Die grootte van die beeld kon nie opgespoor word nie. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + Die beeld breedte is te groot ({{ width }}px). Toegelaat maksimum breedte is {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + Die beeld breedte is te klein ({{ width }}px). Minimum breedte verwag is {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + Die beeld hoogte is te groot ({{ height }}px). Toegelaat maksimum hoogte is {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + Die beeld hoogte is te klein ({{ height }}px). Minimum hoogte verwag is {{ min_height }}px. + + + This value should be the user's current password. + Hierdie waarde moet die huidige wagwoord van die gebruiker wees. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Hierdie waarde moet presies {{ limit }} karakter wees.|Hierdie waarde moet presies {{ limit }} karakters wees. + + + The file was only partially uploaded. + Die lêer is slegs gedeeltelik opgelaai. + + + No file was uploaded. + Geen lêer is opgelaai nie. + + + No temporary folder was configured in php.ini. + Geen tydelike lêer is ingestel in php.ini nie. + + + Cannot write temporary file to disk. + Kan nie tydelike lêer skryf op skyf nie. + + + A PHP extension caused the upload to fail. + 'n PHP-uitbreiding veroorsaak die oplaai van die lêer om te misluk. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Hierdie versameling moet {{ limit }} element of meer bevat.|Hierdie versameling moet {{ limit }} elemente of meer bevat. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Hierdie versameling moet {{ limit }} element of minder bevat.|Hierdie versameling moet {{ limit }} elemente of meer bevat. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Hierdie versameling moet presies {{ limit }} element bevat.|Hierdie versameling moet presies {{ limit }} elemente bevat. + + + Invalid card number. + Ongeldige kredietkaart nommer. + + + Unsupported card type or invalid card number. + Nie-ondersteunde tipe kaart of ongeldige kredietkaart nommer. + + + This is not a valid International Bank Account Number (IBAN). + Hierdie is nie 'n geldige Internationale Bank Rekening Nommer (IBAN) nie. + + + This value is not a valid ISBN-10. + Hierdie waarde is nie 'n geldige ISBN-10 nie. + + + This value is not a valid ISBN-13. + Hierdie waarde is nie 'n geldige ISBN-13 nie. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Hierdie waarde is nie 'n geldige ISBN-10 of ISBN-13 nie. + + + This value is not a valid ISSN. + Hierdie waarde is nie 'n geldige ISSN nie. + + + This value is not a valid currency. + Hierdie waarde is nie 'n geldige geldeenheid nie. + + + This value should be equal to {{ compared_value }}. + Hierdie waarde moet gelyk aan {{ compared_value }} wees. + + + This value should be greater than {{ compared_value }}. + Hierdie waarde moet meer as {{ compared_value }} wees. + + + This value should be greater than or equal to {{ compared_value }}. + Hierdie waarde moet meer of gelyk aan {{ compared_value }} wees. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Hierdie waarde moet identies aan {{ compared_value_type }} {{ compared_value }} wees. + + + This value should be less than {{ compared_value }}. + Hierdie waarde moet minder as {{ compared_value }} wees. + + + This value should be less than or equal to {{ compared_value }}. + Hierdie waarde moet minder of gelyk aan {{ compared_value }} wees. + + + This value should not be equal to {{ compared_value }}. + Hierdie waarde moet nie dieselfde as {{ compared_value }} wees nie. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Hierdie waarde moet nie identies as {{ compared_value_type }} {{ compared_value }} wees nie. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Die beeld aspek is te groot ({{ ratio }}). Die maksimum toegelate aspek is {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + Die beeld aspek is te klein ({{ ratio }}). Die minimum toegelate aspek is {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + Die beeld is vierkantig ({{ width }}x{{ height }}px). Vierkantige beelde word nie toegelaat nie. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + Die beeld is landskap georiënteerd ({{ width }}x{{ height }}px). Landskap georiënteerde beelde word nie toegelaat nie. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + Die beeld dis portret georiënteerd ({{ width }}x{{ height }}px). Portret georiënteerde beelde word nie toegelaat nie. + + + An empty file is not allowed. + 'n Leë lêer word nie toegelaat nie. + + + The host could not be resolved. + Die gasheer kon nie opgelos word nie. + + + This value does not match the expected {{ charset }} charset. + Die waarde stem nie ooreen met die verwagte {{ charset }} karakterstel nie. + + + This is not a valid Business Identifier Code (BIC). + Hierdie is nie 'n geldige Besigheids Identifikasie Kode (BIC) nie. + + + Error + Fout + + + This is not a valid UUID. + Hierdie is nie 'n geldige UUID nie. + + + This value should be a multiple of {{ compared_value }}. + Hierdie waarde moet 'n veelvoud van {{ compared_value }} wees. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Hierdie Besigheids Identifikasie Kode (BIK) is nie geassosieer met IBAN {{ iban }} nie. + + + This value should be valid JSON. + Hierdie waarde moet geldige JSON wees. + + + This collection should contain only unique elements. + Hierdie versameling moet net unieke elemente bevat. + + + This value should be positive. + Hierdie waarde moet positief wees. + + + This value should be either positive or zero. + Hierdie waarde moet positief of nul wees. + + + This value should be negative. + Hierdie waarde moet negatief wees. + + + This value should be either negative or zero. + Hierdie waarde moet negatief of nul wees. + + + This value is not a valid timezone. + Hierdie waarde is nie 'n geldige tydsone nie. + + + This value should be between {{ min }} and {{ max }}. + Hierdie waarde moet tussen {{ min }} en {{ max }} wees. + + + This value is not a valid hostname. + Hierdie waarde is nie 'n geldige gasheernaam nie. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + Die hoeveelheid elemente in hierdie versameling moet 'n meelvoud van {{ compared_value }} wees. + + + This value should satisfy at least one of the following constraints: + Hierdie waarde moet voldoen aan ten minste een van hierdie beperkings: + + + Each element of this collection should satisfy its own set of constraints. + Elke element van hierdie versameling moet voldoen aan hulle eie beperkings. + + + This value is not a valid International Securities Identification Number (ISIN). + Hierdie waarde is nie 'n geldige Internasionale veiligheidsidentifikasienommer (ISIN) nie. + + + This value should be a valid expression. + Hierdie waarde moet 'n geldige uitdrukking wees. + + + This value is not a valid CSS color. + Hierdie waarde is nie 'n geldige CSS-kleur nie. + + + This value is not a valid CIDR notation. + Hierdie waarde is nie 'n geldige CIDR-notasie nie. + + + The value of the netmask should be between {{ min }} and {{ max }}. + Die waarde van die netmasker moet tussen {{ min }} en {{ max }} wees. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.ar.xlf b/src/vendor/symfony/validator/Resources/translations/validators.ar.xlf new file mode 100644 index 0000000..6aa0d59 --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.ar.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + هذه القيمة يجب أن تكون خاطئة. + + + This value should be true. + هذه القيمة يجب أن تكون حقيقية. + + + This value should be of type {{ type }}. + هذه القيمة يجب ان تكون من نوع {{ type }}. + + + This value should be blank. + هذه القيمة يجب ان تكون فارغة. + + + The value you selected is not a valid choice. + القيمة المختارة ليست خيارا صحيحا. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + يجب ان تختار {{ limit }} اختيار على الاقل.|يجب ان تختار {{ limit }} اختيار على الاقل.|يجب ان تختار {{ limit }} اختيارات على الاقل.|يجب ان تختار {{ limit }} اختيار على الاقل.|يجب ان تختار {{ limit }} اختيار على الاقل.|يجب ان تختار {{ limit }} اختيار على الاقل. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + يجب ان تختار {{ limit }} اختيار على الاكثر.|يجب ان تختار {{ limit }} اختيار على الاكثر.|يجب ان تختار {{ limit }} اختيارات على الاكثر.|يجب ان تختار {{ limit }} اختيار على الاكثر.|يجب ان تختار {{ limit }} اختيار على الاكثر.|يجب ان تختار {{ limit }} اختيار على الاكثر. + + + One or more of the given values is invalid. + واحد أو أكثر من القيم المعطاه خاطئ. + + + This field was not expected. + لم يكن من المتوقع هذا المجال. + + + This field is missing. + هذا المجال مفقود. + + + This value is not a valid date. + هذه القيمة ليست تاريخا صالحا. + + + This value is not a valid datetime. + هذه القيمة ليست تاريخا و وقتا صالحا. + + + This value is not a valid email address. + هذه القيمة ليست عنوان بريد إلكتروني صحيح. + + + The file could not be found. + لا يمكن العثور على الملف. + + + The file is not readable. + الملف غير قابل للقراءة. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + الملف كبير جدا ({{ size }} {{ suffix }}).اقصى مساحه مسموح بها ({{ limit }} {{ suffix }}). + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + نوع الملف غير صحيح ({{ type }}). الانواع المسموح بها هى {{ types }}. + + + This value should be {{ limit }} or less. + هذه القيمة يجب ان تكون {{ limit }} او اقل. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + هذه القيمة طويلة جدا. يجب ان تكون {{ limit }} حرف او اقل.|هذه القيمة طويلة جدا. يجب ان تكون {{ limit }} حرف او اقل.|هذه القيمة طويلة جدا. يجب ان تكون {{ limit }} حروف او اقل.|هذه القيمة طويلة جدا. يجب ان تكون {{ limit }} حرف او اقل.|هذه القيمة طويلة جدا. يجب ان تكون {{ limit }} حرف او اقل.|هذه القيمة طويلة جدا. يجب ان تكون {{ limit }} حرف او اقل. + + + This value should be {{ limit }} or more. + هذه القيمة يجب ان تكون {{ limit }} او اكثر. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + هذه القيمة قصيرة جدا. يجب ان تكون {{ limit }} حرف او اكثر.|هذه القيمة قصيرة جدا. يجب ان تكون {{ limit }} حرف او اكثر.|هذه القيمة قصيرة جدا. يجب ان تكون {{ limit }} حروف او اكثر.|هذه القيمة قصيرة جدا. يجب ان تكون {{ limit }} حرف او اكثر.|هذه القيمة قصيرة جدا. يجب ان تكون {{ limit }} حرف او اكثر.|هذه القيمة قصيرة جدا. يجب ان تكون {{ limit }} حرف او اكثر. + + + This value should not be blank. + هذه القيمة يجب الا تكون فارغة. + + + This value should not be null. + هذه القيمة يجب الا تكون فارغة. + + + This value should be null. + هذه القيمة يجب ان تكون فارغة. + + + This value is not valid. + هذه القيمة غير صحيحة. + + + This value is not a valid time. + هذه القيمة ليست وقت صحيح. + + + This value is not a valid URL. + هذه القيمة ليست رابط الكترونى صحيح. + + + The two values should be equal. + القيمتان يجب ان تكونا متساويتان. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + الملف كبير جدا. اقصى مساحه مسموح بها {{ limit }} {{ suffix }}. + + + The file is too large. + الملف كبير جدا. + + + The file could not be uploaded. + لم استطع استقبال الملف. + + + This value should be a valid number. + هذه القيمة يجب ان تكون رقم. + + + This file is not a valid image. + هذا الملف ليس صورة صحيحة. + + + This is not a valid IP address. + هذه القيمة ليست عنوان رقمى صحيح. + + + This value is not a valid language. + هذه القيمة ليست لغة صحيحة. + + + This value is not a valid locale. + هذه القيمة ليست موقع صحيح. + + + This value is not a valid country. + هذه القيمة ليست بلدا صالحا. + + + This value is already used. + هذه القيمة مستخدمة بالفعل. + + + The size of the image could not be detected. + لم استطع معرفة حجم الصورة. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + عرض الصورة كبير جدا ({{ width }}px). اقصى عرض مسموح به هو{{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + عرض الصورة صغير جدا ({{ width }}px). اقل عرض مسموح به هو{{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + طول الصورة كبير جدا ({{ height }}px). اقصى طول مسموح به هو{{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + طول الصورة صغير جدا ({{ height }}px). اقل طول مسموح به هو{{ min_height }}px. + + + This value should be the user's current password. + هذه القيمة يجب ان تكون كلمة سر المستخدم الحالية. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + هذه القيمة يجب ان تحتوى على {{ limit }} حرف فقط.|هذه القيمة يجب ان تحتوى على {{ limit }} حرف فقط.|هذه القيمة يجب ان تحتوى على {{ limit }} حروف فقط.|هذه القيمة يجب ان تحتوى على {{ limit }} حرف فقط.|هذه القيمة يجب ان تحتوى على {{ limit }} حرف فقط.|هذه القيمة يجب ان تحتوى على {{ limit }} حرف فقط. + + + The file was only partially uploaded. + تم استقبال جزء من الملف فقط. + + + No file was uploaded. + لم يتم ارسال اى ملف. + + + No temporary folder was configured in php.ini. + لم يتم تهيئة حافظة مؤقتة فى ملف php.ini. + + + Cannot write temporary file to disk. + لم استطع كتابة الملف المؤقت. + + + A PHP extension caused the upload to fail. + احد اضافات PHP تسببت فى فشل استقبال الملف. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + هذه المجموعة يجب ان تحتوى على {{ limit }} عنصر او اكثر.|هذه المجموعة يجب ان تحتوى على {{ limit }} عنصر او اكثر.|هذه المجموعة يجب ان تحتوى على {{ limit }} عناصر او اكثر.|هذه المجموعة يجب ان تحتوى على {{ limit }} عنصر او اكثر.|هذه المجموعة يجب ان تحتوى على {{ limit }} عنصر او اكثر.|هذه المجموعة يجب ان تحتوى على {{ limit }} عنصر او اكثر. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + هذه المجموعة يجب ان تحتوى على {{ limit }} عنصر او اقل.|هذه المجموعة يجب ان تحتوى على {{ limit }} عنصر او اقل.|هذه المجموعة يجب ان تحتوى على {{ limit }} عناصر او اقل.|هذه المجموعة يجب ان تحتوى على {{ limit }} عنصر او اقل.|هذه المجموعة يجب ان تحتوى على {{ limit }} عنصر او اقل.|هذه المجموعة يجب ان تحتوى على {{ limit }} عنصر او اقل. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + هذه المجموعة يجب ان تحتوى على {{ limit }} عنصر فقط.|هذه المجموعة يجب ان تحتوى على {{ limit }} عنصر فقط.|هذه المجموعة يجب ان تحتوى على {{ limit }} عناصر فقط.|هذه المجموعة يجب ان تحتوى على {{ limit }} عنصر فقط.|هذه المجموعة يجب ان تحتوى على {{ limit }} عنصر فقط.|هذه المجموعة يجب ان تحتوى على {{ limit }} عنصر فقط. + + + Invalid card number. + رقم البطاقه غير صحيح. + + + Unsupported card type or invalid card number. + نوع البطاقه غير مدعوم او الرقم غير صحيح. + + + This is not a valid International Bank Account Number (IBAN). + الرقم IBAN (رقم الحساب المصرفي الدولي) الذي تم إدخاله غير صالح. + + + This value is not a valid ISBN-10. + هذه القيمة ليست ISBN-10 صالحة. + + + This value is not a valid ISBN-13. + هذه القيمة ليست ISBN-13 صالحة. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + هذه القيمة ليست ISBN-10 صالحة ولا ISBN-13 صالحة. + + + This value is not a valid ISSN. + هذه القيمة ليست ISSN صالحة. + + + This value is not a valid currency. + العُملة غير صحيحة. + + + This value should be equal to {{ compared_value }}. + القيمة يجب ان تساوي {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + القيمة يجب ان تكون اعلي من {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + القيمة يجب ان تكون مساوية او اعلي من {{ compared_value }}. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + القيمة يجب ان تطابق {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + القيمة يجب ان تكون اقل من {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + القيمة يجب ان تساوي او تقل عن {{ compared_value }}. + + + This value should not be equal to {{ compared_value }}. + القيمة يجب ان لا تساوي {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + القيمة يجب ان لا تطابق {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + نسبة العرض على الارتفاع للصورة كبيرة جدا ({{ ratio }}). الحد الأقصى للنسبة المسموح به هو {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + نسبة العرض على الارتفاع للصورة صغيرة جدا ({{ ratio }}). الحد الأدنى للنسبة المسموح به هو {{ max_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + الصورة مربعة ({{ width }}x{{ height }}px). الصور المربعة غير مسموح بها. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + الصورة في وضع أفقي ({{ width }}x{{ height }}px). الصور في وضع أفقي غير مسموح بها. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + الصورة في وضع عمودي ({{ width }}x{{ height }}px). الصور في وضع عمودي غير مسموح بها. + + + An empty file is not allowed. + ملف فارغ غير مسموح به. + + + The host could not be resolved. + يتعذر الإتصال بالنطاق. + + + This value does not match the expected {{ charset }} charset. + هذه القيمة غير متطابقة مع صيغة التحويل {{ charset }}. + + + This is not a valid Business Identifier Code (BIC). + هذه القيمة ليست رمز معرّف نشاط تجاري صالح (BIC). + + + Error + خطأ + + + This is not a valid UUID. + هذا ليس UUID صالح. + + + This value should be a multiple of {{ compared_value }}. + هذه القيمة يجب أن تكون مضاعف ل {{ compared_value }}. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + رمز المعرّف نشاط تجاري (BIC) هذا لا يرتبط مع IBAN {{ iban }}. + + + This value should be valid JSON. + هذه القيمة يجب أن تكون صالحة ل JSON. + + + This collection should contain only unique elements. + يجب أن تحتوي هذه المجموعة علي عناصر فريدة فقط. + + + This value should be positive. + يجب أن تكون هذه القيمة موجبة. + + + This value should be either positive or zero. + يجب أن تكون هذه القيمة إما موجبة او صفر. + + + This value should be negative. + يجب أن تكون هذه القيمة سالبة. + + + This value should be either negative or zero. + يجب أن تكون هذه القيمة إما سالبة او صفر. + + + This value is not a valid timezone. + هذه القيمة ليست منطقة زمنية صحيحة. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + تم تسريب كلمة المرور هذه في خرق للبيانات، ويجب عدم استخدامها. يرجي استخدام كلمة مرور أخري. + + + This value should be between {{ min }} and {{ max }}. + يجب أن تكون هذه القيمة بين {{ min }} و {{ max }}. + + + This value is not a valid hostname. + هذه القيمة ليست اسم مضيف صالح. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + يجب أن يكون عدد العناصر في هذه المجموعة مضاعف {{ compared_value }}. + + + This value should satisfy at least one of the following constraints: + يجب أن تستوفي هذه القيمة واحدة من القيود التالية: + + + Each element of this collection should satisfy its own set of constraints. + يجب أن يفي كل عنصر من عناصر هذه المجموعة بمجموعة القيود الخاصة به. + + + This value is not a valid International Securities Identification Number (ISIN). + صالح (ISIN) هذه القيمة ليست رقم تعريف الأوراق المالية الدولي. + + + This value should be a valid expression. + يجب أن تكون هذه القيمة تعبيرًا صالحًا. + + + This value is not a valid CSS color. + هذه القيمة ليست لون CSS صالحًا. + + + This value is not a valid CIDR notation. + هذه القيمة ليست تدوين CIDR صالحًا. + + + The value of the netmask should be between {{ min }} and {{ max }}. + يجب أن تكون قيمة netmask بين {{ min }} و {{ max }}. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.az.xlf b/src/vendor/symfony/validator/Resources/translations/validators.az.xlf new file mode 100644 index 0000000..b3e0999 --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.az.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + Bu dəyər false olmalıdır. + + + This value should be true. + Bu dəyər true olmalıdır. + + + This value should be of type {{ type }}. + Bu dəyərin tipi {{ type }} olmalıdır. + + + This value should be blank. + Bu dəyər boş olmalıdır. + + + The value you selected is not a valid choice. + Seçdiyiniz dəyər düzgün bir seçim değil. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Ən az {{ limit }} seçim qeyd edilməlidir. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Ən çox {{ limit }} seçim qeyd edilməlidir. + + + One or more of the given values is invalid. + Təqdim edilən dəyərlərdən bir və ya bir neçəsi yanlışdır. + + + This field was not expected. + Bu sahə gözlənilmirdi. + + + This field is missing. + Bu sahə əksikdir. + + + This value is not a valid date. + Bu dəyər düzgün bir tarix deyil. + + + This value is not a valid datetime. + Bu dəyər düzgün bir tarixsaat deyil. + + + This value is not a valid email address. + Bu dəyər düzgün bir e-poçt adresi deyil. + + + The file could not be found. + Fayl tapılmadı. + + + The file is not readable. + Fayl oxunabilən deyil. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + Fayl çox böyükdür ({{ size }} {{ suffix }}). İcazə verilən maksimum fayl ölçüsü {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + Faylın mime tipi yanlışdr ({{ type }}). İcazə verilən mime tipləri {{ types }}. + + + This value should be {{ limit }} or less. + Bu dəyər {{ limit }} və ya altında olmalıdır. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Bu dəyər çox uzundur. {{ limit }} və ya daha az simvol olmalıdır. + + + This value should be {{ limit }} or more. + Bu dəyər {{ limit }} veya daha fazla olmalıdır. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Bu dəyər çox qısadır. {{ limit }} və ya daha çox simvol olmalıdır. + + + This value should not be blank. + Bu dəyər boş olmamalıdır. + + + This value should not be null. + Bu dəyər boş olmamalıdır. + + + This value should be null. + Bu dəyər boş olmamalıdır. + + + This value is not valid. + Bu dəyər doğru deyil. + + + This value is not a valid time. + Bu dəyər doğru bir saat deyil. + + + This value is not a valid URL. + Bu dəyər doğru bir URL değil. + + + The two values should be equal. + İki dəyər eyni olmalıdır. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + Fayl çox böyükdür. İcazə verilən ən böyük fayl ölçüsü {{ limit }} {{ suffix }}. + + + The file is too large. + Fayl çox böyükdür. + + + The file could not be uploaded. + Fayl yüklənəbilmir. + + + This value should be a valid number. + Bu dəyər rəqəm olmalıdır. + + + This file is not a valid image. + Bu fayl düzgün bir şəkil deyil. + + + This is not a valid IP address. + Bu düzgün bir IP adresi deyil. + + + This value is not a valid language. + Bu dəyər düzgün bir dil deyil. + + + This value is not a valid locale. + Bu dəyər düzgün bir dil deyil. + + + This value is not a valid country. + Bu dəyər düzgün bir ölkə deyil. + + + This value is already used. + Bu dəyər hal-hazırda istifadədədir. + + + The size of the image could not be detected. + Şəklin ölçüsü hesablana bilmir. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + Şəklin genişliyi çox böyükdür ({{ width }}px). İcazə verilən ən böyük genişlik {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + Şəklin genişliyi çox kiçikdir ({{ width }}px). Ən az {{ min_width }}px olmalıdır. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + Şəklin yüksəkliyi çox böyükdür ({{ height }}px). İcazə verilən ən böyük yüksəklik {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + Şəklin yüksəkliyi çox kiçikdir ({{ height }}px). Ən az {{ min_height }}px olmalıdır. + + + This value should be the user's current password. + Bu dəyər istifadəçinin hazırkı parolu olmalıdır. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Bu dəyər tam olaraq {{ limit }} simvol olmaldır. + + + The file was only partially uploaded. + Fayl qismən yükləndi. + + + No file was uploaded. + Fayl yüklənmədi. + + + No temporary folder was configured in php.ini. + php.ini'də müvəqqəti qovluq quraşdırılmayıb. + + + Cannot write temporary file to disk. + Müvəqqəti fayl diskə yazıla bilmir. + + + A PHP extension caused the upload to fail. + Bir PHP əlavəsi faylın yüklənməsinə mane oldu. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Bu kolleksiyada {{ limit }} və ya daha çox element olmalıdır. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Bu kolleksiyada {{ limit }} və ya daha az element olmalıdır. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Bu kolleksiyada tam olaraq {{ limit }} element olmalıdır. + + + Invalid card number. + Yanlış kart nömrəsi. + + + Unsupported card type or invalid card number. + Dəstəklənməyən kart tipi və ya yanlış kart nömrəsi. + + + This is not a valid International Bank Account Number (IBAN). + Bu dəyər doğru bir Beynəlxalq Bank Hesap Nömrəsi (IBAN) deyil. + + + This value is not a valid ISBN-10. + Bu dəyər doğru bir ISBN-10 deyil. + + + This value is not a valid ISBN-13. + Bu dəyər doğru bir ISBN-13 deyil. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Bu dəyər doğru bir ISBN-10 və ya ISBN-13 deyil. + + + This value is not a valid ISSN. + Bu dəyər doğru bir ISSN deyil. + + + This value is not a valid currency. + Bu dəyər doğru bir valyuta deyil. + + + This value should be equal to {{ compared_value }}. + Bu dəyər {{ compared_value }} ilə bərabər olmalıdır. + + + This value should be greater than {{ compared_value }}. + Bu dəyər {{ compared_value }} dəyərindən büyük olmalıdır. + + + This value should be greater than or equal to {{ compared_value }}. + Bu dəyər {{ compared_value }} ilə bərabər və ya daha böyük olmaldır. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Bu dəyər {{ compared_value_type }} {{ compared_value }} ilə eyni olmalıdır. + + + This value should be less than {{ compared_value }}. + Bu dəyər {{ compared_value }} dəyərindən kiçik olmalıdır. + + + This value should be less than or equal to {{ compared_value }}. + Bu dəyər {{ compared_value }} dəyərindən kiçik və ya bərabər olmalıdır. + + + This value should not be equal to {{ compared_value }}. + Bu değer {{ compared_value }} ile eşit olmamalıdır. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Bu dəyər {{ compared_value_type }} {{ compared_value }} ilə eyni olmamalıdır. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Şəkil nisbəti çox büyükdür ({{ ratio }}). İcazə verilən maksimum nisbət: {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + Şəkil nisbəti çox balacadır ({{ ratio }}). İcazə verilən minimum nisbət: {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + Şəkil kvadratdır ({{ width }}x{{ height }}px). Kvadrat şəkillərə icazə verilmir. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + Şəkil albom rejimindədir ({{ width }}x{{ height }}px). Albom rejimli şəkillərə icazə verilmir. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + Şəkil portret rejimindədir ({{ width }}x{{ height }}px). Portret rejimli şəkillərə icazə verilmir. + + + An empty file is not allowed. + Boş fayla icazə verilmir. + + + The host could not be resolved. + Ünvan tapılmadı. + + + This value does not match the expected {{ charset }} charset. + Bu dəyər gözlənilən {{ charset }} simvol cədvəli ilə uyğun gəlmir. + + + This is not a valid Business Identifier Code (BIC). + Bu dəyər doğru bir Biznes Təyinedici Kodu (BIC) deyil. + + + Error + Xəta + + + This is not a valid UUID. + Bu dəyər doğru bir UUID deyil. + + + This value should be a multiple of {{ compared_value }}. + Bu dəyər {{ compare_value }} dəyərinin bölənlərindən biri olmalıdır. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Bu Biznes Təyinedici Kodu (BIC) {{ iban }} IBAN kodu ilə əlaqəli deyil. + + + This value should be valid JSON. + Bu dəyər doğru bir JSON olmalıdır. + + + This collection should contain only unique elements. + Bu kolleksiyada sadəcə unikal elementlər olmalıdır. + + + This value should be positive. + Bu dəyər müsbət olmalıdır. + + + This value should be either positive or zero. + Bu dəyər müsbət və ya sıfır olmalıdır. + + + This value should be negative. + Bu dəyər mənfi olmaldır. + + + This value should be either negative or zero. + Bu dəyər mənfi və ya sıfır olmaldır. + + + This value is not a valid timezone. + Bu dəyər doğru bir zaman zolağı deyil. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Bu parol data oğurluğunda tapıldığı üçün işlədilməməlidir. Zəhmət olmasa, başqa parol seçin. + + + This value should be between {{ min }} and {{ max }}. + Bu dəyər {{ min }} və {{ max }} arasında olmaldır. + + + This value is not a valid hostname. + Bu dəyər doğru bir host adı deyil. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + Bu kolleksiyadakı elementlerin sayı {{ compared_value }} tam bölünəni olmalıdır. + + + This value should satisfy at least one of the following constraints: + Bu dəyər aşağıdakı məcburiyyətlərdən birini qarşılamalıdır: + + + Each element of this collection should satisfy its own set of constraints. + Bu kolleksiyadakı hər element öz məcburiyyətlərini qarşılamalıdır. + + + This value is not a valid International Securities Identification Number (ISIN). + Bu dəyər doğru bir Qiymətli Kağızın Beynəlxalq İdentifikasiya Kodu (ISIN) deyil. + + + This value should be a valid expression. + Bu dəyər etibarlı ifadə olmalıdır. + + + This value is not a valid CSS color. + Bu dəyər etibarlı CSS rəngi deyil. + + + This value is not a valid CIDR notation. + Bu dəyər etibarlı CIDR notasiyası deyil. + + + The value of the netmask should be between {{ min }} and {{ max }}. + Şəbəkə maskasının dəyəri {{ min }} və {{ max }} arasında olmalıdır. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.be.xlf b/src/vendor/symfony/validator/Resources/translations/validators.be.xlf new file mode 100644 index 0000000..6489556 --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.be.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + Значэнне павінна быць Не. + + + This value should be true. + Значэнне павінна быць Так. + + + This value should be of type {{ type }}. + Тып значэння павінен быць {{ type }}. + + + This value should be blank. + Значэнне павінна быць пустым. + + + The value you selected is not a valid choice. + Абранае вамі значэнне не сапраўднае. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Вы павінны выбраць хаця б {{ limit }} варыянт.|Вы павінны выбраць хаця б {{ limit }} варыянтаў. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Вы павінны выбраць не больш за {{ limit }} варыянт.|Вы павінны выбраць не больш за {{ limit }} варыянтаў. + + + One or more of the given values is invalid. + Адзін або некалькі пазначаных значэнняў з'яўляецца несапраўдным. + + + This field was not expected. + Гэта поле не чакаецца. + + + This field is missing. + Гэта поле адсутнічае. + + + This value is not a valid date. + Гэта значэнне не з'яўляецца карэктнай датай. + + + This value is not a valid datetime. + Гэта значэнне не з'яўляецца карэктнай датай i часом. + + + This value is not a valid email address. + Гэта значэнне не з'яўляецца карэктным адрасам электроннай пошты. + + + The file could not be found. + Файл не знойдзен. + + + The file is not readable. + Файл не чытаецца. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + Файл занадта вялікі ({{ size }} {{ suffix }}). Максімальна дазволены памер {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + MIME-тып файлу некарэкты ({{ type }}). Дазволеныя MIME-тыпы файлу {{ types }}. + + + This value should be {{ limit }} or less. + Значэнне павінна быць {{ limit }} або менш. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Значэнне занадта доўгае. Яно павінна мець {{ limit }} сімвал або менш.|Значэнне занадта доўгае. Яно павінна мець {{ limit }} сімвалаў або менш. + + + This value should be {{ limit }} or more. + Значэнне павінна быць {{ limit }} або больш. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Значэнне занадта кароткае. Яно павінна мець прынамсі {{ limit }} сімвал.|Значэнне занадта кароткае. Яно павінна мець прынамсі {{ limit }} сімвалаў. + + + This value should not be blank. + Значэнне не павінна быць пустым. + + + This value should not be null. + Значэнне не павінна быць null. + + + This value should be null. + Значэнне павінна быць null. + + + This value is not valid. + Значэнне з'яўляецца не сапраўдным. + + + This value is not a valid time. + Значэнне не з'яўляецца сапраўдным часам. + + + This value is not a valid URL. + Значэнне не з'яўляецца сапраўдным URL-адрасам. + + + The two values should be equal. + Абодва значэнні павінны быць аднолькавымі. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + Файл занадта вялікі. Максімальна дазволены памер {{ limit }} {{ suffix }}. + + + The file is too large. + Файл занадта вялікі. + + + The file could not be uploaded. + Немагчыма запампаваць файл. + + + This value should be a valid number. + Значэнне павінна быць лікам. + + + This file is not a valid image. + Гэты файл не з'яўляецца сапраўднай выявай. + + + This is not a valid IP address. + Значэнне не з'яўляецца сапраўдным IP-адрасам. + + + This value is not a valid language. + Значэнне не з'яўляецца сапраўдным мовай. + + + This value is not a valid locale. + Значэнне не з'яўляецца сапраўднай лакаллю. + + + This value is not a valid country. + Значэнне не з'яўляецца сапраўднай краінай. + + + This value is already used. + Гэта значэнне ўжо выкарыстоўваецца. + + + The size of the image could not be detected. + Немагчыма вызначыць памер выявы. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + Гэта выява занадта вялікая ({{ width }}px). Дазваляецца максімальная шырыня {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + Гэта выява занадта маленькая ({{ width }}px). Дазваляецца мінімальная шырыня {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + Гэты выява занадта вялікая ({{ width }}px). Дазваляецца максімальная вышыня {{ max_width }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + Гэта выява занадта маленькая ({{ width }}px). Дазваляецца мінімальная вышыня {{ min_width }}px. + + + This value should be the user's current password. + Значэнне павінна быць цяперашнім паролем карыстальніка. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Значэнне павінна мець {{ limit }} сімвал.|Значэнне павінна мець {{ limit }} сімвалаў. + + + The file was only partially uploaded. + Файл быў запампаваны толькі часткова. + + + No file was uploaded. + Файл не быў запампаваны. + + + No temporary folder was configured in php.ini. + У php.ini не была налажана часовая папка, або часовая папка не існуе. + + + Cannot write temporary file to disk. + Немагчыма запісаць часовы файл на дыск. + + + A PHP extension caused the upload to fail. + Пашырэнне PHP выклікала памылку загрузкі. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Калекцыя павінна змяшчаць прынамсі {{ limit }} элемент.|Калекцыя павінна змяшчаць прынамсі {{ limit }} элементаў. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Калекцыя павінна змяшчаць {{ limit }} або менш элемент.|Калекцыя павінна змяшчаць {{ limit }} або менш элементаў. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Калекцыя павінна змяшчаць роўна {{ limit }} элемент.|Калекцыя павінна змяшчаць роўна {{ limit }} элементаў. + + + Invalid card number. + Несапраўдны нумар карты. + + + Unsupported card type or invalid card number. + Тып карты не падтрымліваецца або несапраўдны нумар карты. + + + This is not a valid International Bank Account Number (IBAN). + Несапраўдны міжнародны нумар банкаўскага рахунку (IBAN). + + + This value is not a valid ISBN-10. + Гэта значэнне не з'яўляецца сапраўдным ISBN-10. + + + This value is not a valid ISBN-13. + Гэта значэнне не з'яўляецца сапраўдным ISBN-13. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Гэта значэнне не з'яўляецца сапраўдным ISBN-10 або ISBN-13. + + + This value is not a valid ISSN. + Гэта значэнне не з'яўляецца сапраўдным ISSN. + + + This value is not a valid currency. + Гэта значэнне не з'яўляецца сапраўднай валютай. + + + This value should be equal to {{ compared_value }}. + Значэнне павінна раўняцца {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + Значэнне павінна быць больш чым {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + Значэнне павінна быць больш чым або раўняцца {{ compared_value }}. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Значэнне павінна быць ідэнтычным {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Значэнне павінна быць менш чым {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + Значэнне павінна быць менш чым або раўняцца {{ compared_value }}. + + + This value should not be equal to {{ compared_value }}. + Значэнне не павінна раўняцца {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Значэнне не павінна быць ідэнтычным {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Суадносіны бакоў выявы з'яўляецца занадта вялікім ({{ ratio }}). Дазваляецца максімальныя суадносіны {{max_ratio}} . + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + Суадносіны бакоў выявы з'яўляецца занадта маленькімі ({{ ratio }}). Дазваляецца мінімальныя суадносіны {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + Выява квадратная ({{width}}x{{height}}px). Квадратныя выявы не дазволены. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + Выява ў альбомнай арыентацыі ({{ width }}x{{ height }}px). Выявы ў альбомнай арыентацыі не дазволены. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + Выява ў партрэтнай арыентацыі ({{ width }}x{{ height }}px). Выявы ў партрэтнай арыентацыі не дазволены. + + + An empty file is not allowed. + Пусты файл не дазволены. + + + The host could not be resolved. + Не магчыма знайсці імя хоста. + + + This value does not match the expected {{ charset }} charset. + Гэта значэнне не супадае з чаканай {{ charset }} кадыроўкай. + + + This is not a valid Business Identifier Code (BIC). + Несапраўдны банкаўскі ідэнтыфікацыйны код (BIC). + + + Error + Памылка + + + This is not a valid UUID. + Гэта несапраўдны UUID. + + + This value should be a multiple of {{ compared_value }}. + Значэнне павінна быць кратным {{ compared_value }}. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Банкаўскі ідэнтыфікацыйны код (BIC) не звязан з IBAN {{ iban }}. + + + This value should be valid JSON. + Гэта значэнне павінна быць у фармаце JSON. + + + This collection should contain only unique elements. + Калекцыя павінна змяшчаць толькі ўнікальныя элементы. + + + This value should be positive. + Значэнне павінна быць дадатным. + + + This value should be either positive or zero. + Значэнне павінна быць дадатным ці нуль. + + + This value should be negative. + Значэнне павінна быць адмоўным. + + + This value should be either negative or zero. + Значэнне павінна быць адмоўным ці нуль. + + + This value is not a valid timezone. + Значэнне не з'яўляецца сапраўдным гадзінным поясам. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Гэты пароль быў выкрадзены ў выніку ўзлому дадзеных, таму яго нельга выкарыстоўваць. Калі ласка, выкарыстоўвайце іншы пароль. + + + This value should be between {{ min }} and {{ max }}. + Значэнне павінна быць паміж {{min}} і {{max}}. + + + This value is not a valid hostname. + Значэнне не з'яўляецца карэктным імем хаста. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + Колькасць элементаў у гэтай калекцыі павінна быць кратным {{compared_value}}. + + + This value should satisfy at least one of the following constraints: + Значэнне павінна задавальняць як мінімум аднаму з наступных абмежаванняў: + + + Each element of this collection should satisfy its own set of constraints. + Кожны элемент гэтай калекцыі павінен задавальняць свайму ўласнаму набору абмежаванняў. + + + This value is not a valid International Securities Identification Number (ISIN). + Значэнне не з'яўляецца карэктным міжнародным ідэнтыфікацыйным нумарам каштоўных папер (ISIN). + + + This value should be a valid expression. + Значэнне не з'яўляецца сапраўдным выразам. + + + This value is not a valid CSS color. + Значэнне не з'яўляецца дапушчальным колерам CSS. + + + This value is not a valid CIDR notation. + Значэнне не з'яўляецца сапраўднай натацыяй CIDR. + + + The value of the netmask should be between {{ min }} and {{ max }}. + Значэнне сеткавай маскі павінна быць ад {{min}} да {{max}}. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.bg.xlf b/src/vendor/symfony/validator/Resources/translations/validators.bg.xlf new file mode 100644 index 0000000..455ff81 --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.bg.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + Стойността трябва да бъде лъжа (false). + + + This value should be true. + Стойността трябва да бъде истина (true). + + + This value should be of type {{ type }}. + Стойността трябва да бъде от тип {{ type }}. + + + This value should be blank. + Стойността трябва да бъде празна. + + + The value you selected is not a valid choice. + Избраната стойност е невалидна. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Трябва да изберете поне {{ limit }} опция.|Трябва да изберете поне {{ limit }} опции. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Трябва да изберете най-много {{ limit }} опция.|Трябва да изберете най-много {{ limit }} опции. + + + One or more of the given values is invalid. + Една или повече от зададените стойности е невалидна. + + + This field was not expected. + Полето не се е очаквало. + + + This field is missing. + Полето липсва. + + + This value is not a valid date. + Стойността не е валидна дата. + + + This value is not a valid datetime. + Стойността не е валидна дата и час. + + + This value is not a valid email address. + Стойността не е валиден имейл адрес. + + + The file could not be found. + Файлът не беше открит. + + + The file is not readable. + Файлът не може да бъде прочетен. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + Файлът е твърде голям ({{ size }} {{ suffix }}). Максималният размер е {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + Mime типа на файла е невалиден ({{ type }}). Разрешени mime типове са {{ types }}. + + + This value should be {{ limit }} or less. + Стойността трябва да бъде {{ limit }} или по-малко. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Стойността е твърде дълга. Трябва да съдържа най-много {{ limit }} символ.|Стойността е твърде дълга. Трябва да съдържа най-много {{ limit }} символа. + + + This value should be {{ limit }} or more. + Стойността трябва да бъде {{ limit }} или повече. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Стойността е твърде кратка. Трябва да съдържа поне {{ limit }} символ.|Стойността е твърде кратка. Трябва да съдържа поне {{ limit }} символа. + + + This value should not be blank. + Стойността не трябва да бъде празна. + + + This value should not be null. + Стойността не трябва да бъде null. + + + This value should be null. + Стойността трябва да бъде null. + + + This value is not valid. + Стойността не е валидна. + + + This value is not a valid time. + Стойността не е валидно време. + + + This value is not a valid URL. + Стойността не е валиден URL. + + + The two values should be equal. + Двете стойности трябва да бъдат равни. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + Файлът е твърде голям. Разрешеният максимален размер е {{ limit }} {{ suffix }}. + + + The file is too large. + Файлът е твърде голям. + + + The file could not be uploaded. + Файлът не може да бъде качен. + + + This value should be a valid number. + Стойността трябва да бъде валиден номер. + + + This file is not a valid image. + Файлът не е валидно изображение. + + + This is not a valid IP address. + Това не е валиден IP адрес. + + + This value is not a valid language. + Стойността не е валиден език. + + + This value is not a valid locale. + Стойността не е валидна локализация. + + + This value is not a valid country. + Стойността не е валидна държава. + + + This value is already used. + Стойността вече е в употреба. + + + The size of the image could not be detected. + Размера на изображението не може да бъде определен. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + Изображението е твърде широко ({{ width }}px). Широчината трябва да бъде максимум {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + Изображението е с твърде малка широчина ({{ width }}px). Широчината трябва да бъде минимум {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + Изображението е с твърде голяма височина ({{ height }}px). Височината трябва да бъде максимум {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + Изображението е с твърде малка височина ({{ height }}px). Височина трябва да бъде минимум {{ min_height }}px. + + + This value should be the user's current password. + Стойността трябва да бъде текущата потребителска парола. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Стойността трябва да бъде точно {{ limit }} символ.|Стойността трябва да бъде точно {{ limit }} символа. + + + The file was only partially uploaded. + Файлът е качен частично. + + + No file was uploaded. + Файлът не беше качен. + + + No temporary folder was configured in php.ini. + Не е посочена директория за временни файлове в php.ini. + + + Cannot write temporary file to disk. + Не може да запише временен файл на диска. + + + A PHP extension caused the upload to fail. + PHP разширение предизвика прекъсване на качването. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Колекцията трябва да съдържа поне {{ limit }} елемент.|Колекцията трябва да съдържа поне {{ limit }} елемента. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Колекцията трябва да съдържа най-много {{ limit }} елемент.|Колекцията трябва да съдържа най-много {{ limit }} елемента. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Колекцията трябва да съдържа точно {{ limit }} елемент.|Колекцията трябва да съдържа точно {{ limit }} елемента. + + + Invalid card number. + Невалиден номер на карта. + + + Unsupported card type or invalid card number. + Неподдържан тип карта или невалиден номер на карта. + + + This is not a valid International Bank Account Number (IBAN). + Това не е валиден Международен номер на банкова сметка (IBAN). + + + This value is not a valid ISBN-10. + Стойността не е валиден ISBN-10. + + + This value is not a valid ISBN-13. + Стойността не е валиден ISBN-13. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Стойността не е нито валиден ISBN-10, нито валиден ISBN-13. + + + This value is not a valid ISSN. + Стойността не е валиден ISSN. + + + This value is not a valid currency. + Стойността не е валидна валута. + + + This value should be equal to {{ compared_value }}. + Стойността трябва да бъде равна на {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + Стойността трябва да бъде по-голяма от {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + Стойността трябва да бъде по-голяма или равна на {{ compared_value }}. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Стойността трябва да бъде идентична с {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Стойността трябва да бъде по-малка {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + Стойността трябва да бъде по-малка или равна на {{ compared_value }}. + + + This value should not be equal to {{ compared_value }}. + Стойността не трябва да бъде равна на {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Стойността не трябва да бъде идентична с {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Изображението е с твърде голяма пропорция ({{ ratio }}). Максималната пропорция трябва да е {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + Изображението е с твърде малка пропорция ({{ ratio }}). Минималната пропорция трябва да е {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + Изображението е квадрат ({{ width }}x{{ height }}px). Такива изображения не са разрешени. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + Изображението е с пейзажна ориентация ({{ width }}x{{ height }}px). Изображения с такава ориентация не са разрешени. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + Изображението е с портретна ориентация ({{ width }}x{{ height }}px). Изображения с такава ориентация не са разрешени. + + + An empty file is not allowed. + Празни файлове не са разрешени. + + + The host could not be resolved. + Хостът е недостъпен. + + + This value does not match the expected {{ charset }} charset. + Стойността не съвпада с очакваната {{ charset }} кодировка. + + + This is not a valid Business Identifier Code (BIC). + Това не е валиден Бизнес идентификационен код (BIC). + + + Error + Грешка + + + This is not a valid UUID. + Това не е валиден UUID. + + + This value should be a multiple of {{ compared_value }}. + Стойността трябва да бъде кратно число на {{ compared_value }}. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Бизнес идентификационния код (BIC) не е свързан с IBAN {{ iban }}. + + + This value should be valid JSON. + Стойността трябва да е валиден JSON. + + + This collection should contain only unique elements. + Колекцията трябва да съдържа само уникални елементи. + + + This value should be positive. + Стойността трябва да бъде положително число. + + + This value should be either positive or zero. + Стойността трябва бъде положително число или нула. + + + This value should be negative. + Стойността трябва да бъде отрицателно число. + + + This value should be either negative or zero. + Стойността трябва да бъде отрицателно число или нула. + + + This value is not a valid timezone. + Стойността не е валидна часова зона. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Тази парола е компрометирана, не трябва да бъде използвана. Моля използвайте друга парола. + + + This value should be between {{ min }} and {{ max }}. + Стойността трябва да бъде между {{ min }} и {{ max }}. + + + This value is not a valid hostname. + Стойността не е валиден hostname. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + Броят на елементите в тази колекция трябва да бъде кратен на {{ compared_value }}. + + + This value should satisfy at least one of the following constraints: + Стойността трябва да отговаря на поне едно от следните ограничения: + + + Each element of this collection should satisfy its own set of constraints. + Всеки елемент от тази колекция трябва да отговаря на собствения си набор от ограничения. + + + This value is not a valid International Securities Identification Number (ISIN). + Стойността не е валиден Международен идентификационен номер на ценни книжа (ISIN). + + + This value should be a valid expression. + Стойността трябва да бъде валиден израз. + + + This value is not a valid CSS color. + Стойността не е валиден CSS цвят. + + + This value is not a valid CIDR notation. + Стойността не е валидна CIDR нотация. + + + The value of the netmask should be between {{ min }} and {{ max }}. + Стойността на мрежовата маска трябва да бъде между {{ min }} и {{ max }}. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.bs.xlf b/src/vendor/symfony/validator/Resources/translations/validators.bs.xlf new file mode 100644 index 0000000..43102cc --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.bs.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + Ova vrijednost bi trebalo da bude "netačno" (false). + + + This value should be true. + Ova vrijednost bi trebalo da bude "tačno" (true). + + + This value should be of type {{ type }}. + Ova vrijednost bi trebalo da bude tipa {{ type }}. + + + This value should be blank. + Ova vrijednost bi trebalo da bude prazna. + + + The value you selected is not a valid choice. + Odabrana vrijednost nije validan izbor. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Morate odabrati barem {{ limit }} mogućnost.|Morate odabrati barem {{ limit }} mogućnosti.|Morate odabrati barem {{ limit }} mogućnosti. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Morate odabrati najviše {{ limit }} mogućnost.|Morate odabrati najviše {{ limit }} mogućnosti.|Morate odabrati najviše {{ limit }} mogućnosti. + + + One or more of the given values is invalid. + Jedna ili više datih vrijednosti nisu validne. + + + This field was not expected. + Ovo polje nije očekivano. + + + This field is missing. + Ovo polje nedostaje. + + + This value is not a valid date. + Ova vrijednost nije ispravan datum. + + + This value is not a valid datetime. + Ova vrijednost nije ispravnog datum-vrijeme (datetime) formata. + + + This value is not a valid email address. + Ova vrijednost nije ispravna e-mail adresa. + + + The file could not be found. + Ova datoteka ne može biti pronađena. + + + The file is not readable. + Ova datoteka nije čitljiva. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + Ova datoteka je prevelika ({{ size }} {{ suffix }}). Najveća dozvoljena veličina je {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + Mime tip datoteke nije ispravan ({{ type }}). Dozvoljeni mime tipovi su {{ types }}. + + + This value should be {{ limit }} or less. + Ova vrijednost bi trebalo da bude {{ limit }} ili manje. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Ova vrijednost je predugačka. Trebalo bi da ima {{ limit }} karakter ili manje.|Ova vrijednost je predugačka. Trebalo bi da ima {{ limit }} karaktera ili manje.|Ova vrijednost je predugačka. Trebalo bi da ima {{ limit }} karaktera ili manje. + + + This value should be {{ limit }} or more. + Ova vrijednost bi trebalo da bude {{ limit }} ili više. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Ova vrijednost je prekratka. Trebalo bi da ima {{ limit }} karakter ili više.|Ova vrijednost je prekratka. Trebalo bi da ima {{ limit }} karaktera ili više.|Ova vrijednost je prekratka. Trebalo bi da ima {{ limit }} karaktera ili više. + + + This value should not be blank. + Ova vrijednost ne bi trebalo da bude prazna. + + + This value should not be null. + Ova vrijednost ne bi trebalo da bude null. + + + This value should be null. + Ova vrijednost bi trebalo da bude null. + + + This value is not valid. + Ova vrijednost nije ispravna. + + + This value is not a valid time. + Ova vrijednost nije ispravno vrijeme. + + + This value is not a valid URL. + Ova vrijednost nije ispravan URL. + + + The two values should be equal. + Obje vrijednosti bi trebalo da budu jednake. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + Ova datoteka je prevelika. Najveća dozvoljena veličina je {{ limit }} {{ suffix }}. + + + The file is too large. + Ova datoteka je prevelika. + + + The file could not be uploaded. + Ova datoteka ne može biti prenijeta (uploaded). + + + This value should be a valid number. + Ova vrijednost bi trebalo da bude ispravan broj. + + + This file is not a valid image. + Ova datoteka nije validna slika. + + + This is not a valid IP address. + Ovo nije ispravna IP adresa. + + + This value is not a valid language. + Ova vrijednost nije validan jezik. + + + This value is not a valid locale. + Ova vrijednost nije validna regionalna oznaka. + + + This value is not a valid country. + Ova vrijednost nije validna država. + + + This value is already used. + Ova vrijednost je već upotrebljena. + + + The size of the image could not be detected. + Nije moguće otkriti veličinu ove slike. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + Širina slike je prevelika ({{ width }}px). Najveća dozvoljena širina je {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + Širina slike je premala ({{ width }}px). Najmanja dozvoljena širina je {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + Dužina slike je prevelika ({{ height }}px). Najveća dozvoljena dužina je {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + Dužina slike je premala ({{ height }}px). Najmanja dozvoljena dužina je {{ min_height }}px. + + + This value should be the user's current password. + Ova vrijednost bi trebalo da bude trenutna korisnička lozinka. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Ova vrijednost bi trebalo da ima tačno {{ limit }} karakter.|Ova vrijednost bi trebalo da ima tačno {{ limit }} karaktera. + + + The file was only partially uploaded. + Datoteka je samo djelimično prenijeta (uploaded). + + + No file was uploaded. + Nijedna datoteka nije prenijeta (uploaded). + + + No temporary folder was configured in php.ini. + Privremeni direktorijum nije konfigurisan u datoteci php.ini. + + + Cannot write temporary file to disk. + Privremenu datoteku nije moguće upisati na disk. + + + A PHP extension caused the upload to fail. + Prenos datoteke nije uspio zbog PHP ekstenzije. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Ova kolekcija bi trebalo da sadrži {{ limit }} ili više elemenata.|Ova kolekcija bi trebalo da sadrži {{ limit }} ili više elemenata.|Ova kolekcija bi trebalo da sadrži {{ limit }} ili više elemenata. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Ova kolekcija bi trebalo da sadrži {{ limit }} ili manje elemenata.|Ova kolekcija bi trebalo da sadrži {{ limit }} ili manje elemenata.|Ova kolekcija bi trebalo da sadrži {{ limit }} ili manje elemenata. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Ova kolekcija bi trebalo da sadrži tačno {{ limit }} element.|Ova kolekcija bi trebalo da sadrži tačno {{ limit }} elementa.|Ova kolekcija bi trebalo da sadrži tačno {{ limit }} elemenata. + + + Invalid card number. + Broj kartice je neispravan. + + + Unsupported card type or invalid card number. + Tip kartice nije podržan ili je broj kartice neispravan. + + + This is not a valid International Bank Account Number (IBAN). + Ova vrijednost nije ispravan međunarodni broj bankovnog računa (IBAN). + + + This value is not a valid ISBN-10. + Ova vrijednost nije ispravan ISBN-10. + + + This value is not a valid ISBN-13. + Ova vrijednost nije ispravan ISBN-13. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Ova vrijednost nije ispravan ISBN-10 niti ISBN-13. + + + This value is not a valid ISSN. + Ova vrijednost nije ispravan ISSN. + + + This value is not a valid currency. + Ova vrijednost nije ispravna valuta. + + + This value should be equal to {{ compared_value }}. + Ova vrijednost bi trebalo da bude jednaka {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + Ova vrijednost bi trebalo da bude veća od {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + Ova vrijednost bi trebalo da bude jednaka ili veća od {{ compared_value }}. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Ova vrijednost bi trebalo da bude identična {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Ova vrijednost bi trebalo da bude manja od {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + Ova vrijednost bi trebalo da bude jednaka ili manja od {{ compared_value }}. + + + This value should not be equal to {{ compared_value }}. + Ova vrijednost bi trebalo da bude različita od {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Ova vrijednost bi trebalo da bude identična sa {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Razmjera ove slike je prevelika ({{ ratio }}). Maksimalna dozvoljena razmjera je {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + Razmjera ove slike je premala ({{ ratio }}). Minimalna očekivana razmjera je {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + Ova slika je kvadratnog oblika ({{ width }}x{{ height }}px). Kvadratne slike nisu dozvoljene. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + Ova slika je orijentisana horizontalno (landscape) ({{ width }}x{{ height }}px). Horizontalno orijentisane slike nisu dozvoljene. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + Ova slika je orijentisana vertikalno (portrait) ({{ width }}x{{ height }}px). Vertikalno orijentisane slike nisu dozvoljene. + + + An empty file is not allowed. + Prazna datoteka nije dozvoljena. + + + The host could not be resolved. + Nije moguće odrediti poslužitelja (host). + + + This value does not match the expected {{ charset }} charset. + Ova vrijednost ne odgovara očekivanom {{ charset }} setu karaktera (charset). + + + This is not a valid Business Identifier Code (BIC). + Ovo nije validan poslovni identifikacioni kod (BIC). + + + Error + Greška + + + This is not a valid UUID. + Ovo nije validan UUID. + + + This value should be a multiple of {{ compared_value }}. + Ova vrijednost bi trebalo da bude djeljiva sa {{ compared_value }}. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Ovaj poslovni identifikacioni kod (BIC) nije povezan sa IBAN-om {{ iban }}. + + + This value should be valid JSON. + Ova vrijednost bi trebalo da bude validan JSON. + + + This collection should contain only unique elements. + Ova kolekcija bi trebala da sadrži samo jedinstvene elemente. + + + This value should be positive. + Ova vrijednost bi trebalo da bude pozitivna. + + + This value should be either positive or zero. + Ova vrijednost bi trebalo da bude pozitivna ili jednaka nuli. + + + This value should be negative. + Ova vrijednost bi trebalo da bude negativna. + + + This value should be either negative or zero. + Ova vrijednost bi trebalo da bude negativna ili jednaka nuli. + + + This value is not a valid timezone. + Ova vrijednost nije validna vremenska zona. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Ova lozinka je procurila u nekom od slučajeva kompromitovanja podataka, nemojte je koristiti. Koristite drugu lozinku. + + + This value should be between {{ min }} and {{ max }}. + Ova vrijednosti bi trebala biti između {{ min }} i {{ max }}. + + + This value is not a valid hostname. + Ova vrijednost nije ispravno ime poslužitelja (hostname). + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + Broj elemenata u ovoj kolekciji bi trebalo da bude djeljiv sa {{ compared_value }}. + + + This value should satisfy at least one of the following constraints: + Ova vrijednost bi trebalo da zadovoljava namjanje jedno od narednih ograničenja: + + + Each element of this collection should satisfy its own set of constraints. + Svaki element ove kolekcije bi trebalo da zadovolji sopstveni skup ograničenja. + + + This value is not a valid International Securities Identification Number (ISIN). + Ova vrijednost nije ispravna međunarodna identifikaciona oznaka hartija od vrijednosti (ISIN). + + + This value should be a valid expression. + Ova vrijednost bi trebala biti važeći izraz. + + + This value is not a valid CSS color. + Ova vrijednost nije važeća CSS boja. + + + This value is not a valid CIDR notation. + Ova vrijednost nije važeća CIDR notacija. + + + The value of the netmask should be between {{ min }} and {{ max }}. + Vrijednost NetMask bi trebala biti između {{min}} i {{max}}. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.ca.xlf b/src/vendor/symfony/validator/Resources/translations/validators.ca.xlf new file mode 100644 index 0000000..04f3e9a --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.ca.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + Aquest valor hauria de ser fals. + + + This value should be true. + Aquest valor hauria de ser cert. + + + This value should be of type {{ type }}. + Aquest valor hauria de ser del tipus {{ type }}. + + + This value should be blank. + Aquest valor hauria d'estar buit. + + + The value you selected is not a valid choice. + El valor seleccionat no és una opció vàlida. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Ha de seleccionar almenys {{ limit }} opció.|Ha de seleccionar almenys {{ limit }} opcions. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Ha de seleccionar com a màxim {{ limit }} opció.|Ha de seleccionar com a màxim {{ limit }} opcions. + + + One or more of the given values is invalid. + Un o més dels valors facilitats són incorrectes. + + + This field was not expected. + Aquest camp no s'esperava. + + + This field is missing. + Aquest camp està desaparegut. + + + This value is not a valid date. + Aquest valor no és una data vàlida. + + + This value is not a valid datetime. + Aquest valor no és una data i hora vàlida. + + + This value is not a valid email address. + Aquest valor no és una adreça d'email vàlida. + + + The file could not be found. + No s'ha pogut trobar l'arxiu. + + + The file is not readable. + No es pot llegir l'arxiu. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + L'arxiu és massa gran ({{ size }} {{ suffix }}). La grandària màxima permesa és {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + El tipus mime de l'arxiu no és vàlid ({{ type }}). Els tipus mime vàlids són {{ types }}. + + + This value should be {{ limit }} or less. + Aquest valor hauria de ser {{ limit }} o menys. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Aquest valor és massa llarg. Hauria de tenir {{ limit }} caràcter o menys.|Aquest valor és massa llarg. Hauria de tenir {{ limit }} caràcters o menys. + + + This value should be {{ limit }} or more. + Aquest valor hauria de ser {{ limit }} o més. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Aquest valor és massa curt. Hauria de tenir {{ limit }} caràcters o més. + + + This value should not be blank. + Aquest valor no hauria d'estar buit. + + + This value should not be null. + Aquest valor no hauria de ser null. + + + This value should be null. + Aquest valor hauria de ser null. + + + This value is not valid. + Aquest valor no és vàlid. + + + This value is not a valid time. + Aquest valor no és una hora vàlida. + + + This value is not a valid URL. + Aquest valor no és una URL vàlida. + + + The two values should be equal. + Els dos valors haurien de ser iguals. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + L'arxiu és massa gran. El tamany màxim permés és {{ limit }} {{ suffix }}. + + + The file is too large. + L'arxiu és massa gran. + + + The file could not be uploaded. + No es pot pujar l'arxiu. + + + This value should be a valid number. + Aquest valor hauria de ser un nombre vàlid. + + + This file is not a valid image. + L'arxiu no és una imatge vàlida. + + + This is not a valid IP address. + Això no és una adreça IP vàlida. + + + This value is not a valid language. + Aquest valor no és un idioma vàlid. + + + This value is not a valid locale. + Aquest valor no és una localització vàlida. + + + This value is not a valid country. + Aquest valor no és un país vàlid. + + + This value is already used. + Aquest valor ja s'ha utilitzat. + + + The size of the image could not be detected. + No s'ha pogut determinar la grandària de la imatge. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + L'amplària de la imatge és massa gran ({{ width }}px). L'amplària màxima permesa són {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + L'amplària de la imatge és massa petita ({{ width }}px). L'amplària mínima requerida són {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + L'altura de la imatge és massa gran ({{ height }}px). L'altura màxima permesa són {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + L'altura de la imatge és massa petita ({{ height }}px). L'altura mínima requerida són {{ min_height }}px. + + + This value should be the user's current password. + Aquest valor hauria de ser la contrasenya actual de l'usuari. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Aquest valor hauria de tenir exactament {{ limit }} caràcter.|Aquest valor hauria de tenir exactament {{ limit }} caràcters. + + + The file was only partially uploaded. + L'arxiu va ser només pujat parcialment. + + + No file was uploaded. + Cap arxiu va ser pujat. + + + No temporary folder was configured in php.ini. + Cap carpeta temporal va ser configurada en php.ini. + + + Cannot write temporary file to disk. + No es va poder escriure l'arxiu temporal en el disc. + + + A PHP extension caused the upload to fail. + Una extensió de PHP va fer que la pujada fallara. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Aquesta col·lecció ha de contenir {{ limit }} element o més.|Aquesta col·lecció ha de contenir {{ limit }} elements o més. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Aquesta col·lecció ha de contenir {{ limit }} element o menys.|Aquesta col·lecció ha de contenir {{ limit }} elements o menys. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Aquesta col·lecció ha de contenir exactament {{ limit }} element.|Aquesta col·lecció ha de contenir exactament {{ limit }} elements. + + + Invalid card number. + Número de targeta invàlid. + + + Unsupported card type or invalid card number. + Tipus de targeta no suportada o número de targeta invàlid. + + + This is not a valid International Bank Account Number (IBAN). + Això no és un nombre de compte bancari internacional (IBAN) vàlid. + + + This value is not a valid ISBN-10. + Aquest valor no és un ISBN-10 vàlid. + + + This value is not a valid ISBN-13. + Aquest valor no és un ISBN-13 vàlid. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Aquest valor no és ni un ISBN-10 vàlid ni un ISBN-13 vàlid. + + + This value is not a valid ISSN. + Aquest valor no és un ISSN vàlid. + + + This value is not a valid currency. + Aquest valor no és una divisa vàlida. + + + This value should be equal to {{ compared_value }}. + Aquest valor hauria de ser igual a {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + Aquest valor hauria de ser més gran a {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + Aquest valor hauria de ser major o igual a {{ compared_value }}. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Aquest valor hauria de ser idèntic a {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Aquest valor hauria de ser menor a {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + Aquest valor hauria de ser menor o igual a {{ compared_value }}. + + + This value should not be equal to {{ compared_value }}. + Aquest valor no hauria de ser igual a {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Aquest valor no hauria de idèntic a {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + La proporció de l'imatge és massa gran ({{ ratio }}). La màxima proporció permesa és {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + La proporció de l'imatge és massa petita ({{ ratio }}). La mínima proporció permesa és {{ max_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + L'imatge és quadrada({{ width }}x{{ height }}px). Les imatges quadrades no estan permeses. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + L'imatge està orientada horitzontalment ({{ width }}x{{ height }}px). Les imatges orientades horitzontalment no estan permeses. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + L'imatge està orientada verticalment ({{ width }}x{{ height }}px). Les imatges orientades verticalment no estan permeses. + + + An empty file is not allowed. + No està permès un fixter buit. + + + The host could not be resolved. + No s'ha pogut resoldre l'amfitrió. + + + This value does not match the expected {{ charset }} charset. + Aquest valor no coincideix amb l'esperat {{ charset }} joc de caràcters. + + + This is not a valid Business Identifier Code (BIC). + Aquest no és un codi d'identificació bancari (BIC) vàlid. + + + Error + Error + + + This is not a valid UUID. + Aquest valor no és un UUID vàlid. + + + This value should be a multiple of {{ compared_value }}. + Aquest valor ha de ser múltiple de {{ compared_value }}. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Aquest Codi d'identificació bancari (BIC) no està associat amb l'IBAN {{ iban }}. + + + This value should be valid JSON. + Aquest valor hauria de ser un JSON vàlid. + + + This collection should contain only unique elements. + Aquesta col·lecció només hauria de contenir elements únics. + + + This value should be positive. + Aquest valor hauria de ser positiu. + + + This value should be either positive or zero. + Aquest valor ha de ser positiu o zero. + + + This value should be negative. + Aquest valor ha de ser negatiu. + + + This value should be either negative or zero. + Aquest valor ha de ser negatiu o zero. + + + This value is not a valid timezone. + Aquest valor no és una zona horària vàlida. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Aquesta contrasenya s'ha filtrat en cas de violació de dades, no s'ha d'utilitzar. Utilitzeu una altra contrasenya. + + + This value should be between {{ min }} and {{ max }}. + Aquest valor ha d'estar entre {{ min }} i {{ max }}. + + + This value is not a valid hostname. + Aquest valor no és un nom d'amfitrió vàlid. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + El nombre d'elements d'aquesta col·lecció ha de ser múltiple de {{compared_value}}. + + + This value should satisfy at least one of the following constraints: + Aquest valor ha de satisfer almenys una de les restriccions següents: + + + Each element of this collection should satisfy its own set of constraints. + Cada element d'aquesta col·lecció hauria de satisfer el seu propi conjunt de restriccions. + + + This value is not a valid International Securities Identification Number (ISIN). + Aquest valor no és un número d'identificació de valors internacionals (ISIN) vàlid. + + + This value should be a valid expression. + Aquest valor hauria de ser una expressió vàlida. + + + This value is not a valid CSS color. + Aquest valor no és un color CSS vàlid. + + + This value is not a valid CIDR notation. + Aquest valor no és una notació CIDR vàlida. + + + The value of the netmask should be between {{ min }} and {{ max }}. + El valor de la màscara de xarxa hauria d'estar entre {{ min }} i {{ max }}. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.cs.xlf b/src/vendor/symfony/validator/Resources/translations/validators.cs.xlf new file mode 100644 index 0000000..7541019 --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.cs.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + Tato hodnota musí být nepravdivá (false). + + + This value should be true. + Tato hodnota musí být pravdivá (true). + + + This value should be of type {{ type }}. + Tato hodnota musí být typu {{ type }}. + + + This value should be blank. + Tato hodnota musí být prázdná. + + + The value you selected is not a valid choice. + Vybraná hodnota není platnou možností. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Musí být vybrána nejméně {{ limit }} možnost.|Musí být vybrány nejméně {{ limit }} možnosti.|Musí být vybráno nejméně {{ limit }} možností. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Musí být vybrána maximálně {{ limit }} možnost.|Musí být vybrány maximálně {{ limit }} možnosti.|Musí být vybráno maximálně {{ limit }} možností. + + + One or more of the given values is invalid. + Některé z uvedených hodnot jsou neplatné. + + + This field was not expected. + Toto pole nebylo očekáváno. + + + This field is missing. + Toto pole chybí. + + + This value is not a valid date. + Tato hodnota není platné datum. + + + This value is not a valid datetime. + Tato hodnota není platné datum s časovým údajem. + + + This value is not a valid email address. + Tato hodnota není platná e-mailová adresa. + + + The file could not be found. + Soubor nebyl nalezen. + + + The file is not readable. + Soubor je nečitelný. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + Soubor je příliš velký ({{ size }} {{ suffix }}). Maximální povolená velikost souboru je {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + Neplatný mime typ souboru ({{ type }}). Povolené mime typy souborů jsou {{ types }}. + + + This value should be {{ limit }} or less. + Tato hodnota musí být {{ limit }} nebo méně. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Tato hodnota je příliš dlouhá. Musí obsahovat maximálně {{ limit }} znak.|Tato hodnota je příliš dlouhá. Musí obsahovat maximálně {{ limit }} znaky.|Tato hodnota je příliš dlouhá. Musí obsahovat maximálně {{ limit }} znaků. + + + This value should be {{ limit }} or more. + Tato hodnota musí být {{ limit }} nebo více. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Tato hodnota je příliš krátká. Musí obsahovat minimálně {{ limit }} znak.|Tato hodnota je příliš krátká. Musí obsahovat minimálně {{ limit }} znaky.|Tato hodnota je příliš krátká. Musí obsahovat minimálně {{ limit }} znaků. + + + This value should not be blank. + Tato hodnota nesmí být prázdná. + + + This value should not be null. + Tato hodnota nesmí být null. + + + This value should be null. + Tato hodnota musí být null. + + + This value is not valid. + Tato hodnota není platná. + + + This value is not a valid time. + Tato hodnota není platný časový údaj. + + + This value is not a valid URL. + Tato hodnota není platná URL adresa. + + + The two values should be equal. + Tyto dvě hodnoty musí být stejné. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + Soubor je příliš velký. Maximální povolená velikost souboru je {{ limit }} {{ suffix }}. + + + The file is too large. + Soubor je příliš velký. + + + The file could not be uploaded. + Soubor se nepodařilo nahrát. + + + This value should be a valid number. + Tato hodnota musí být číslo. + + + This file is not a valid image. + Tento soubor není obrázek. + + + This is not a valid IP address. + Toto není platná IP adresa. + + + This value is not a valid language. + Tento jazyk neexistuje. + + + This value is not a valid locale. + Tato lokalizace neexistuje. + + + This value is not a valid country. + Tato země neexistuje. + + + This value is already used. + Tato hodnota je již používána. + + + The size of the image could not be detected. + Nepodařily se zjistit rozměry obrázku. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + Obrázek je příliš široký ({{ width }}px). Maximální povolená šířka obrázku je {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + Obrázek je příliš úzký ({{ width }}px). Minimální šířka musí být {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + Obrázek je příliš vysoký ({{ height }}px). Maximální povolená výška obrázku je {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + Obrázek je příliš nízký ({{ height }}px). Minimální výška obrázku musí být {{ min_height }}px. + + + This value should be the user's current password. + Tato hodnota musí být aktuální heslo uživatele. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Tato hodnota musí mít přesně {{ limit }} znak.|Tato hodnota musí mít přesně {{ limit }} znaky.|Tato hodnota musí mít přesně {{ limit }} znaků. + + + The file was only partially uploaded. + Byla nahrána jen část souboru. + + + No file was uploaded. + Žádný soubor nebyl nahrán. + + + No temporary folder was configured in php.ini. + V php.ini není nastavena cesta k adresáři pro dočasné soubory. + + + Cannot write temporary file to disk. + Dočasný soubor se nepodařilo zapsat na disk. + + + A PHP extension caused the upload to fail. + Rozšíření PHP zabránilo nahrání souboru. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Tato kolekce musí obsahovat minimálně {{ limit }} prvek.|Tato kolekce musí obsahovat minimálně {{ limit }} prvky.|Tato kolekce musí obsahovat minimálně {{ limit }} prvků. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Tato kolekce musí obsahovat maximálně {{ limit }} prvek.|Tato kolekce musí obsahovat maximálně {{ limit }} prvky.|Tato kolekce musí obsahovat maximálně {{ limit }} prvků. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Tato kolekce musí obsahovat přesně {{ limit }} prvek.|Tato kolekce musí obsahovat přesně {{ limit }} prvky.|Tato kolekce musí obsahovat přesně {{ limit }} prvků. + + + Invalid card number. + Neplatné číslo karty. + + + Unsupported card type or invalid card number. + Nepodporovaný typ karty nebo neplatné číslo karty. + + + This is not a valid International Bank Account Number (IBAN). + Toto je neplatný IBAN. + + + This value is not a valid ISBN-10. + Tato hodnota není platné ISBN-10. + + + This value is not a valid ISBN-13. + Tato hodnota není platné ISBN-13. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Tato hodnota není platné ISBN-10 ani ISBN-13. + + + This value is not a valid ISSN. + Tato hodnota není platné ISSN. + + + This value is not a valid currency. + Tato měna neexistuje. + + + This value should be equal to {{ compared_value }}. + Tato hodnota musí být rovna {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + Tato hodnota musí být větší než {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + Tato hodnota musí být větší nebo rovna {{ compared_value }}. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Tato hodnota musí být typu {{ compared_value_type }} a zároveň musí být rovna {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Tato hodnota musí být menší než {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + Tato hodnota musí být menší nebo rovna {{ compared_value }}. + + + This value should not be equal to {{ compared_value }}. + Tato hodnota nesmí být rovna {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Tato hodnota nesmí být typu {{ compared_value_type }} a zároveň nesmí být rovna {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Poměr stran obrázku je příliš velký ({{ ratio }}). Maximální povolený poměr stran obrázku je {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + Poměr stran obrázku je příliš malý ({{ ratio }}). Minimální povolený poměr stran obrázku je {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + Strany obrázku jsou čtvercové ({{ width }}x{{ height }}px). Čtvercové obrázky nejsou povolené. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + Obrázek je orientovaný na šířku ({{ width }}x{{ height }}px). Obrázky orientované na šířku nejsou povolené. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + Obrázek je orientovaný na výšku ({{ width }}x{{ height }}px). Obrázky orientované na výšku nejsou povolené. + + + An empty file is not allowed. + Soubor nesmí být prázdný. + + + The host could not be resolved. + Hostitele nebylo možné rozpoznat. + + + This value does not match the expected {{ charset }} charset. + Tato hodnota neodpovídá očekávané znakové sadě {{ charset }}. + + + This is not a valid Business Identifier Code (BIC). + Tato hodnota není platný identifikační kód podniku (BIC). + + + Error + Chyba + + + This is not a valid UUID. + Tato hodnota není platné UUID. + + + This value should be a multiple of {{ compared_value }}. + Tato hodnota musí být násobek hodnoty {{ compared_value }}. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Bankovní identifikační kód (BIC) neodpovídá mezinárodnímu číslu účtu (IBAN) {{ iban }}. + + + This value should be valid JSON. + Tato hodnota musí být validní JSON. + + + This collection should contain only unique elements. + Tato kolekce musí obsahovat pouze unikátní prvky. + + + This value should be positive. + Tato hodnota musí být kladná. + + + This value should be either positive or zero. + Tato hodnota musí být buď kladná nebo nula. + + + This value should be negative. + Tato hodnota musí být záporná. + + + This value should be either negative or zero. + Tato hodnota musí být buď záporná nebo nula. + + + This value is not a valid timezone. + Tato časová zóna neexistuje. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Zadané heslo bylo součástí úniku dat, takže ho není možné použít. Použijte prosím jiné heslo. + + + This value should be between {{ min }} and {{ max }}. + Hodnota musí být mezi {{ min }} a {{ max }}. + + + This value is not a valid hostname. + Tato hodnota není platný hostname. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + Počet prvků v této kolekci musí být násobek {{ compared_value }}. + + + This value should satisfy at least one of the following constraints: + Tato hodnota musí splňovat alespoň jedno z následujících omezení: + + + Each element of this collection should satisfy its own set of constraints. + Každý prvek v této kolekci musí splňovat svá vlastní omezení. + + + This value is not a valid International Securities Identification Number (ISIN). + Tato hodnota není platné mezinárodní identifikační číslo cenného papíru (ISIN). + + + This value should be a valid expression. + Tato hodnota musí být platný výraz. + + + This value is not a valid CSS color. + Tato hodnota není platná barva CSS. + + + This value is not a valid CIDR notation. + Tato hodnota není platná notace CIDR. + + + The value of the netmask should be between {{ min }} and {{ max }}. + Hodnota masky sítě musí být mezi {{ min }} a {{ max }}. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.cy.xlf b/src/vendor/symfony/validator/Resources/translations/validators.cy.xlf new file mode 100644 index 0000000..752b6c2 --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.cy.xlf @@ -0,0 +1,335 @@ + + + + + + This value should be false. + Dylid bod y gwerth hwn yn ffug. + + + This value should be true. + Dylid bod y gwerth hwn yn wir. + + + This value should be of type {{ type }}. + Dylid bod y gwerth hwn bod o fath {{ type }}. + + + This value should be blank. + Dylid bod y gwerth hwn yn wag. + + + The value you selected is not a valid choice. + Nid yw'r gwerth â ddewiswyd yn ddilys. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Rhaid dewis o leiaf {{ limit }} opsiwn. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Rhaid dewis dim mwy na {{ limit }} opsiwn. + + + One or more of the given values is invalid. + Mae un neu fwy o'r gwerthoedd a roddwyd yn annilys. + + + This field was not expected. + Nid oedd disgwyl y maes hwn. + + + This field is missing. + Mae'r maes hwn ar goll. + + + This value is not a valid date. + Nid yw'r gwerth yn ddyddiad dilys. + + + This value is not a valid datetime. + Nid yw'r gwerth yn datetime dilys. + + + This value is not a valid email address. + Nid yw'r gwerth yn gyfeiriad ebost dilys. + + + The file could not be found. + Ni ddarganfyddwyd y ffeil. + + + The file is not readable. + Ni ellir darllen y ffeil. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + Mae'r ffeil yn rhy fawr ({{ size }} {{ suffix }}). Yr uchafswm â ganiateir yw {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + Nid yw math mime y ffeil yn ddilys ({{ type }}). Dyma'r mathau â ganiateir {{ types }}. + + + This value should be {{ limit }} or less. + Dylai'r gwerth hwn fod yn {{ limit }} neu lai. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Mae'r gwerth hwn rhy hir. Dylai gynnwys {{ limit }} nodyn cyfrifiadurol neu lai. + + + This value should be {{ limit }} or more. + Dylai'r gwerth hwn fod yn {{ limit }} neu fwy. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Mae'r gwerth hwn yn rhy fyr. Dylai gynnwys {{ limit }} nodyn cyfrifiadurol neu fwy. + + + This value should not be blank. + Ni ddylai'r gwerth hwn fod yn wag. + + + This value should not be null. + Ni ddylai'r gwerth hwn fod yn null. + + + This value should be null. + Dylai'r gwerth fod yn null. + + + This value is not valid. + Nid yw'r gwerth hwn yn ddilys. + + + This value is not a valid time. + Nid yw'r gwerth hwn yn amser dilys. + + + This value is not a valid URL. + Nid yw'r gwerth hwn yn URL dilys. + + + The two values should be equal. + Rhaid i'r ddau werth fod yn gyfystyr a'u gilydd. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + Mae'r ffeil yn rhy fawr. Yr uchafswm â ganiateir yw {{ limit }} {{ suffix }}. + + + The file is too large. + Mae'r ffeil yn rhy fawr. + + + The file could not be uploaded. + Methwyd ag uwchlwytho'r ffeil. + + + This value should be a valid number. + Dylai'r gwerth hwn fod yn rif dilys. + + + This file is not a valid image. + Nid yw'r ffeil hon yn ddelwedd dilys. + + + This is not a valid IP address. + Nid yw hwn yn gyfeiriad IP dilys. + + + This value is not a valid language. + Nid yw'r gwerth hwn yn iaith ddilys. + + + This value is not a valid locale. + Nid yw'r gwerth hwn yn locale dilys. + + + This value is not a valid country. + Nid yw'r gwerth hwn yn wlad dilys. + + + This value is already used. + Mae'r gwerth hwn eisoes yn cael ei ddefnyddio. + + + The size of the image could not be detected. + Methwyd â darganfod maint y ddelwedd. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + Mae lled y ddelwedd yn rhy fawr ({{ width }}px). Y lled mwyaf â ganiateir yw {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + Mae lled y ddelwedd yn rhy fach ({{ width }}px). Y lled lleiaf â ganiateir yw {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + Mae uchder y ddelwedd yn rhy fawr ({{ width }}px). Yr uchder mwyaf â ganiateir yw {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + Mae uchder y ddelwedd yn rhy fach ({{ width }}px). Yr uchder lleiaf â ganiateir yw {{ min_height }}px. + + + This value should be the user's current password. + Dylaid bod y gwerth hwn yn gyfrinair presenol y defnyddiwr. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Dylai'r gwerth hwn fod yn union {{ limit }} nodyn cyfrifiadurol o hyd. + + + The file was only partially uploaded. + Dim ond rhan o'r ffeil ag uwchlwythwyd. + + + No file was uploaded. + Ni uwchlwythwyd unrhyw ffeil. + + + No temporary folder was configured in php.ini. + Nid oes ffolder dros-dro wedi'i gosod yn php.ini. + + + Cannot write temporary file to disk. + Methwyd ag ysgrifennu'r ffeil dros-dro ar ddisg. + + + A PHP extension caused the upload to fail. + Methwyd ag uwchlwytho oherwydd ategyn PHP. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Dylai'r casgliad hwn gynnwys {{ limit }} elfen neu fwy. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Dylai'r casgliad hwn gynnwys {{ limit }} elfen neu lai. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Dylai'r casgliad hwn gynnwys union {{ limit }} elfen. + + + Invalid card number. + Nid oedd rhif y cerdyn yn ddilys. + + + Unsupported card type or invalid card number. + Unai ni dderbynir y math yna o gerdyn, neu nid yw rhif y cerdyn yn ddilys. + + + This is not a valid International Bank Account Number (IBAN). + Nid yw hwn yn Rhif Cyfrif Banc Rhyngwladol (IBAN) dilys. + + + This value is not a valid ISBN-10. + Nid yw'r gwerth hwn yn ISBN-10 dilys. + + + This value is not a valid ISBN-13. + Nid yw'r gwerth hwn yn ISBN-13 dilys. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Nid yw'r gwerth hwn yn Rhif ISBN-10 dilys nac yn ISBN-13 dilys. + + + This value is not a valid ISSN. + Nid yw'r gwerth hwn yn ISSN dilys. + + + This value is not a valid currency. + Nid yw'r gwerth hwn yn arian dilys. + + + This value should be equal to {{ compared_value }}. + Dylai'r gwerth hwn fod yn gyfartal â {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + Dylai'r gwerth hwn fod yn fwy na {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + Dylai'r gwerth hwn fod yn fwy na neu'n hafal i {{ compared_value }}. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Dylai'r gwerth hwn fod yn union yr un fath â {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Dylai'r gwerth hwn fod yn llai na {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + Dylai'r gwerth hwn fod yn llai na neu'n hafal i {{ compared_value }}. + + + This value should not be equal to {{ compared_value }}. + Ni ddylai'r gwerth hwn fod yn hafal i {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Ni ddylai'r gwerth hwn fod yn union yr un fath â {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Mae'r gymhareb delwedd yn rhy fawr ({{ ratio }}). Y gymhareb uchaf a ganiateir yw {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + Mae'r gymhareb delwedd yn rhy fach ({{ ratio }}). Y gymhareb isaf a ddisgwylir yw {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + Mae'r ddelwedd yn sgwâr ({{ width }}x{{ height }}px). Ni chaniateir delweddau sgwâr. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + Mae'r ddelwedd mewn fformat tirlun ({{ width }}x{{ height }}px). Ni chaniateir delweddau mewn fformat tirlun. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + Mae'r ddelwedd mewn fformat portread ({{ width }}x{{ height }}px). Ni chaniateir delweddau mewn fformat portread. + + + An empty file is not allowed. + Ni chaniateir ffeil wag. + + + The host could not be resolved. + Ni fu modd datrys y gwesteiwr. + + + This value does not match the expected {{ charset }} charset. + Nid yw'r gwerth hwn yn cyfateb â'r {{ charset }} set nodau ddisgwyliedig. + + + This is not a valid Business Identifier Code (BIC). + Nid yw hwn yn God Adnabod Busnes (BIC) dilys. + + + Error + Gwall + + + This is not a valid UUID. + Nid yw hyn yn UUID dilys. + + + This value should be a multiple of {{ compared_value }}. + Dylai'r gwerth hwn fod yn luosrif o {{ compared_value }}. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Nid yw'r Cod Adnabod Busnes (BIC) hwn yn gysylltiedig ag IBAN {{ iban }}. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.da.xlf b/src/vendor/symfony/validator/Resources/translations/validators.da.xlf new file mode 100644 index 0000000..b76624e --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.da.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + Værdien skal være falsk. + + + This value should be true. + Værdien skal være sand. + + + This value should be of type {{ type }}. + Værdien skal være af typen {{ type }}. + + + This value should be blank. + Værdien skal være blank. + + + The value you selected is not a valid choice. + Den valgte værdi er ikke gyldig. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Du skal vælge mindst én mulighed.|Du skal vælge mindst {{ limit }} muligheder. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Du kan højst vælge én mulighed.|Du kan højst vælge {{ limit }} muligheder. + + + One or more of the given values is invalid. + En eller flere af de angivne værdier er ugyldige. + + + This field was not expected. + Feltet blev ikke forventet. + + + This field is missing. + Dette felt mangler. + + + This value is not a valid date. + Værdien er ikke en gyldig dato. + + + This value is not a valid datetime. + Værdien er ikke et gyldigt tidspunkt. + + + This value is not a valid email address. + Værdien er ikke en gyldig e-mailadresse. + + + The file could not be found. + Filen kunne ikke findes. + + + The file is not readable. + Filen kan ikke læses. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + Filen er for stor ({{ size }} {{ suffix }}). Maksimale tilladte størrelse er {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + Filens MIME-type er ugyldig ({{ type }}). Tilladte MIME-typer er {{ types }}. + + + This value should be {{ limit }} or less. + Værdien skal være {{ limit }} eller mindre. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Værdien er for lang. Den må højst indeholde {{ limit }} tegn. + + + This value should be {{ limit }} or more. + Værdien skal være {{ limit }} eller mere. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Værdien er for kort. Den skal indeholde mindst {{ limit }} tegn. + + + This value should not be blank. + Værdien må ikke være blank. + + + This value should not be null. + Værdien må ikke være tom (null). + + + This value should be null. + Værdien skal være tom (null). + + + This value is not valid. + Værdien er ikke gyldig. + + + This value is not a valid time. + Værdien er ikke et gyldigt klokkeslæt. + + + This value is not a valid URL. + Værdien er ikke en gyldig URL. + + + The two values should be equal. + De to værdier skal være ens. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + Filen er for stor. Den maksimale størrelse er {{ limit }} {{ suffix }}. + + + The file is too large. + Filen er for stor. + + + The file could not be uploaded. + Filen kunne ikke uploades. + + + This value should be a valid number. + Værdien skal være et gyldigt tal. + + + This file is not a valid image. + Filen er ikke gyldigt billede. + + + This is not a valid IP address. + Dette er ikke en gyldig IP-adresse. + + + This value is not a valid language. + Værdien er ikke et gyldigt sprog. + + + This value is not a valid locale. + Værdien er ikke en gyldig lokalitet. + + + This value is not a valid country. + Værdien er ikke et gyldigt land. + + + This value is already used. + Værdien er allerede i brug. + + + The size of the image could not be detected. + Størrelsen på billedet kunne ikke detekteres. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + Billedet er for bredt ({{ width }}px). Største tilladte bredde er {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + Billedet er for smalt ({{ width }}px). Mindste forventede bredde er {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + Billedet er for højt ({{ height }}px). Største tilladte højde er {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + Billedet er for lavt ({{ height }}px). Mindste forventede højde er {{ min_height }}px. + + + This value should be the user's current password. + Værdien skal være brugerens nuværende adgangskode. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Værdien skal være på præcis {{ limit }} tegn. + + + The file was only partially uploaded. + Filen blev kun delvist uploadet. + + + No file was uploaded. + Ingen fil blev uploadet. + + + No temporary folder was configured in php.ini. + Ingen midlertidig mappe er konfigureret i php.ini. + + + Cannot write temporary file to disk. + Kan ikke skrive midlertidig fil til disk. + + + A PHP extension caused the upload to fail. + En PHP-udvidelse forårsagede fejl i upload. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Denne samling skal indeholde mindst ét element.|Denne samling skal indeholde mindst {{ limit }} elementer. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Denne samling skal indeholde højst ét element.|Denne samling skal indeholde højst {{ limit }} elementer. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Denne samling skal indeholde præcis ét element.|Denne samling skal indeholde præcis {{ limit }} elementer. + + + Invalid card number. + Ugyldigt kortnummer. + + + Unsupported card type or invalid card number. + Ikke-understøttet korttype eller ugyldigt kortnummer. + + + This is not a valid International Bank Account Number (IBAN). + Det er ikke et gyldigt International Bank Account Number (IBAN). + + + This value is not a valid ISBN-10. + Værdien er ikke en gyldig ISBN-10. + + + This value is not a valid ISBN-13. + Værdien er ikke en gyldig ISBN-13. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Værdien er hverken en gyldig ISBN-10 eller en gyldig ISBN-13. + + + This value is not a valid ISSN. + Værdien er ikke en gyldig ISSN. + + + This value is not a valid currency. + Denne værdi er ikke en gyldig valuta. + + + This value should be equal to {{ compared_value }}. + Denne værdi skal være lig med {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + Denne værdi skal være større end {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + Denne værdi skal være større end eller lig med {{ compared_value }}. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Denne værdi skal være identisk med {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Denne værdi skal være mindre end {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + Denne værdi skal være mindre end eller lig med {{ compared_value }}. + + + This value should not be equal to {{ compared_value }}. + Denne værdi bør ikke være lig med {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Denne værdi bør ikke være identisk med {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Billedforholdet er for stort ({{ratio}}). Tilladt maksimumsforhold er {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + Billedforholdet er for lille ({{ ratio }}). Minimumsforventet forventet er {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + Billedet er firkantet ({{ width }} x {{ height }} px). Firkantede billeder er ikke tilladt. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + Billedet er landskabsorienteret ({{width}} x {{height}} px). Landskabsorienterede billeder er ikke tilladt + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + Billedet er portrætorienteret ({{ width }}x{{ height }}px). Portrætorienterede billeder er ikke tilladt. + + + An empty file is not allowed. + En tom fil er ikke tilladt. + + + The host could not be resolved. + Værten kunne ikke løses. + + + This value does not match the expected {{ charset }} charset. + Denne værdi stemmer ikke overens med den forventede {{ charset }} charset. + + + This is not a valid Business Identifier Code (BIC). + Dette er ikke en gyldig Business Identifier Code (BIC).a + + + Error + Fejl + + + This is not a valid UUID. + Dette er ikke en gyldig UUID. + + + This value should be a multiple of {{ compared_value }}. + Denne værdi skal være et multiplikation af {{ compared_value }}. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Denne Business Identifier Code (BIC) er ikke forbundet med IBAN {{ iban }}. + + + This value should be valid JSON. + Denne værdi skal være gyldig JSON. + + + This collection should contain only unique elements. + Denne samling bør kun indeholde unikke elementer. + + + This value should be positive. + Denne værdi skal være positiv. + + + This value should be either positive or zero. + Denne værdi skal være enten positiv eller nul. + + + This value should be negative. + Denne værdi skal være negativ. + + + This value should be either negative or zero. + Denne værdi skal være enten negativ eller nul. + + + This value is not a valid timezone. + Denne værdi er ikke en gyldig tidszone. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Denne adgangskode er blevet lækket i et databrud, det må ikke bruges. Brug venligst en anden adgangskode. + + + This value should be between {{ min }} and {{ max }}. + Værdien skal være mellem {{ min }} og {{ max }}. + + + This value is not a valid hostname. + Værdien er ikke et gyldigt værtsnavn. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + Antallet af elementer i denne samling skal være en multiplikation af {{ compared_value }}. + + + This value should satisfy at least one of the following constraints: + Værdien skal overholde mindst én af følgende krav: + + + Each element of this collection should satisfy its own set of constraints. + Hvert element i denne samling skal overholde dens egne krav. + + + This value is not a valid International Securities Identification Number (ISIN). + Værdien er ikke et gyldig International Securities Identification Number (ISIN). + + + This value should be a valid expression. + Værdien skal være et gyldigt udtryk. + + + This value is not a valid CSS color. + Værdien skal være en gyldig CSS farve. + + + This value is not a valid CIDR notation. + Værdien er ikke en gyldig CIDR notation. + + + The value of the netmask should be between {{ min }} and {{ max }}. + Værdien af netmasken skal være mellem {{ min }} og {{ max }}. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.de.xlf b/src/vendor/symfony/validator/Resources/translations/validators.de.xlf new file mode 100644 index 0000000..32bfbab --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.de.xlf @@ -0,0 +1,431 @@ + + + + + + This value should be false. + Dieser Wert sollte false sein. + + + This value should be true. + Dieser Wert sollte true sein. + + + This value should be of type {{ type }}. + Dieser Wert sollte vom Typ {{ type }} sein. + + + This value should be blank. + Dieser Wert sollte leer sein. + + + The value you selected is not a valid choice. + Sie haben einen ungültigen Wert ausgewählt. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Sie müssen mindestens {{ limit }} Möglichkeit wählen.|Sie müssen mindestens {{ limit }} Möglichkeiten wählen. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Sie dürfen höchstens {{ limit }} Möglichkeit wählen.|Sie dürfen höchstens {{ limit }} Möglichkeiten wählen. + + + One or more of the given values is invalid. + Einer oder mehrere der angegebenen Werte sind ungültig. + + + This field was not expected. + Dieses Feld wurde nicht erwartet. + + + This field is missing. + Dieses Feld fehlt. + + + This value is not a valid date. + Dieser Wert entspricht keiner gültigen Datumsangabe. + + + This value is not a valid datetime. + Dieser Wert entspricht keiner gültigen Datums- und Zeitangabe. + + + This value is not a valid email address. + Dieser Wert ist keine gültige E-Mail-Adresse. + + + The file could not be found. + Die Datei wurde nicht gefunden. + + + The file is not readable. + Die Datei ist nicht lesbar. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + Die Datei ist zu groß ({{ size }} {{ suffix }}). Die maximal zulässige Größe beträgt {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + Der Dateityp ist ungültig ({{ type }}). Erlaubte Dateitypen sind {{ types }}. + + + This value should be {{ limit }} or less. + Dieser Wert sollte kleiner oder gleich {{ limit }} sein. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Diese Zeichenkette ist zu lang. Sie sollte höchstens {{ limit }} Zeichen haben.|Diese Zeichenkette ist zu lang. Sie sollte höchstens {{ limit }} Zeichen haben. + + + This value should be {{ limit }} or more. + Dieser Wert sollte größer oder gleich {{ limit }} sein. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Diese Zeichenkette ist zu kurz. Sie sollte mindestens {{ limit }} Zeichen haben.|Diese Zeichenkette ist zu kurz. Sie sollte mindestens {{ limit }} Zeichen haben. + + + This value should not be blank. + Dieser Wert sollte nicht leer sein. + + + This value should not be null. + Dieser Wert sollte nicht null sein. + + + This value should be null. + Dieser Wert sollte null sein. + + + This value is not valid. + Dieser Wert ist nicht gültig. + + + This value is not a valid time. + Dieser Wert entspricht keiner gültigen Zeitangabe. + + + This value is not a valid URL. + Dieser Wert ist keine gültige URL. + + + The two values should be equal. + Die beiden Werte sollten identisch sein. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + Die Datei ist zu groß. Die maximal zulässige Größe beträgt {{ limit }} {{ suffix }}. + + + The file is too large. + Die Datei ist zu groß. + + + The file could not be uploaded. + Die Datei konnte nicht hochgeladen werden. + + + This value should be a valid number. + Dieser Wert sollte eine gültige Zahl sein. + + + This file is not a valid image. + Diese Datei ist kein gültiges Bild. + + + This is not a valid IP address. + Dies ist keine gültige IP-Adresse. + + + This value is not a valid language. + Dieser Wert entspricht keiner gültigen Sprache. + + + This value is not a valid locale. + Dieser Wert entspricht keinem gültigen Gebietsschema. + + + This value is not a valid country. + Dieser Wert entspricht keinem gültigen Land. + + + This value is already used. + Dieser Wert wird bereits verwendet. + + + The size of the image could not be detected. + Die Größe des Bildes konnte nicht ermittelt werden. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + Die Bildbreite ist zu groß ({{ width }}px). Die maximal zulässige Breite beträgt {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + Die Bildbreite ist zu gering ({{ width }}px). Die erwartete Mindestbreite beträgt {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + Die Bildhöhe ist zu groß ({{ height }}px). Die maximal zulässige Höhe beträgt {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + Die Bildhöhe ist zu gering ({{ height }}px). Die erwartete Mindesthöhe beträgt {{ min_height }}px. + + + This value should be the user's current password. + Dieser Wert sollte dem aktuellen Benutzerpasswort entsprechen. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Dieser Wert sollte genau {{ limit }} Zeichen lang sein.|Dieser Wert sollte genau {{ limit }} Zeichen lang sein. + + + The file was only partially uploaded. + Die Datei wurde nur teilweise hochgeladen. + + + No file was uploaded. + Es wurde keine Datei hochgeladen. + + + No temporary folder was configured in php.ini. + Es wurde kein temporärer Ordner in der php.ini konfiguriert oder der temporäre Ordner existiert nicht. + + + Cannot write temporary file to disk. + Kann die temporäre Datei nicht speichern. + + + A PHP extension caused the upload to fail. + Eine PHP-Erweiterung verhinderte den Upload. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Diese Sammlung sollte {{ limit }} oder mehr Elemente beinhalten.|Diese Sammlung sollte {{ limit }} oder mehr Elemente beinhalten. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Diese Sammlung sollte {{ limit }} oder weniger Elemente beinhalten.|Diese Sammlung sollte {{ limit }} oder weniger Elemente beinhalten. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Diese Sammlung sollte genau {{ limit }} Element beinhalten.|Diese Sammlung sollte genau {{ limit }} Elemente beinhalten. + + + Invalid card number. + Ungültige Kartennummer. + + + Unsupported card type or invalid card number. + Nicht unterstützter Kartentyp oder ungültige Kartennummer. + + + This is not a valid International Bank Account Number (IBAN). + Dieser Wert ist keine gültige internationale Bankkontonummer (IBAN). + + + This value is not a valid ISBN-10. + Dieser Wert entspricht keiner gültigen ISBN-10. + + + This value is not a valid ISBN-13. + Dieser Wert entspricht keiner gültigen ISBN-13. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Dieser Wert ist weder eine gültige ISBN-10 noch eine gültige ISBN-13. + + + This value is not a valid ISSN. + Dieser Wert ist keine gültige ISSN. + + + This value is not a valid currency. + Dieser Wert ist keine gültige Währung. + + + This value should be equal to {{ compared_value }}. + Dieser Wert sollte gleich {{ compared_value }} sein. + + + This value should be greater than {{ compared_value }}. + Dieser Wert sollte größer als {{ compared_value }} sein. + + + This value should be greater than or equal to {{ compared_value }}. + Dieser Wert sollte größer oder gleich {{ compared_value }} sein. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Dieser Wert sollte identisch sein mit {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Dieser Wert sollte kleiner als {{ compared_value }} sein. + + + This value should be less than or equal to {{ compared_value }}. + Dieser Wert sollte kleiner oder gleich {{ compared_value }} sein. + + + This value should not be equal to {{ compared_value }}. + Dieser Wert sollte nicht {{ compared_value }} sein. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Dieser Wert sollte nicht identisch sein mit {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Das Seitenverhältnis des Bildes ist zu groß ({{ ratio }}). Der erlaubte Maximalwert ist {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + Das Seitenverhältnis des Bildes ist zu klein ({{ ratio }}). Der erwartete Minimalwert ist {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + Das Bild ist quadratisch ({{ width }}x{{ height }}px). Quadratische Bilder sind nicht erlaubt. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + Das Bild ist im Querformat ({{ width }}x{{ height }}px). Bilder im Querformat sind nicht erlaubt. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + Das Bild ist im Hochformat ({{ width }}x{{ height }}px). Bilder im Hochformat sind nicht erlaubt. + + + An empty file is not allowed. + Eine leere Datei ist nicht erlaubt. + + + The host could not be resolved. + Der Hostname konnte nicht aufgelöst werden. + + + This value does not match the expected {{ charset }} charset. + Dieser Wert entspricht nicht dem erwarteten Zeichensatz {{ charset }}. + + + This is not a valid Business Identifier Code (BIC). + Dieser Wert ist keine gültige internationale Bankleitzahl (BIC). + + + Error + Fehler + + + This is not a valid UUID. + Dies ist keine gültige UUID. + + + This value should be a multiple of {{ compared_value }}. + Dieser Wert sollte ein Vielfaches von {{ compared_value }} sein. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Diese internationale Bankleitzahl (BIC) ist nicht mit der IBAN {{ iban }} assoziiert. + + + This value should be valid JSON. + Dieser Wert sollte gültiges JSON sein. + + + This collection should contain only unique elements. + Diese Sammlung darf keine doppelten Elemente enthalten. + + + This value should be positive. + Diese Zahl sollte positiv sein. + + + This value should be either positive or zero. + Diese Zahl sollte entweder positiv oder 0 sein. + + + This value should be negative. + Diese Zahl sollte negativ sein. + + + This value should be either negative or zero. + Diese Zahl sollte entweder negativ oder 0 sein. + + + This value is not a valid timezone. + Dieser Wert ist keine gültige Zeitzone. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Dieses Passwort ist Teil eines Datenlecks, es darf nicht verwendet werden. + + + This value should be between {{ min }} and {{ max }}. + Dieser Wert sollte zwischen {{ min }} und {{ max }} sein. + + + This value is not a valid hostname. + Dieser Wert ist kein gültiger Hostname. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + Die Anzahl an Elementen in dieser Sammlung sollte ein Vielfaches von {{ compared_value }} sein. + + + This value should satisfy at least one of the following constraints: + Dieser Wert sollte eine der folgenden Bedingungen erfüllen: + + + Each element of this collection should satisfy its own set of constraints. + Jedes Element dieser Sammlung sollte seine eigene Menge an Bedingungen erfüllen. + + + This value is not a valid International Securities Identification Number (ISIN). + Dieser Wert ist keine gültige Internationale Wertpapierkennnummer (ISIN). + + + This value should be a valid expression. + Dieser Wert sollte eine gültige Expression sein. + + + This value is not a valid CSS color. + Dieser Wert ist keine gültige CSS-Farbe. + + + This value is not a valid CIDR notation. + Dieser Wert entspricht nicht der CIDR-Notation. + + + The value of the netmask should be between {{ min }} and {{ max }}. + Der Wert der Subnetzmaske sollte zwischen {{ min }} und {{ max }} liegen. + + + The filename is too long. It should have {{ filename_max_length }} character or less.|The filename is too long. It should have {{ filename_max_length }} characters or less. + Der Dateiname ist zu lang. Er sollte nicht länger als {{ filename_max_length }} Zeichen sein.|Der Dateiname ist zu lang. Er sollte nicht länger als {{ filename_max_length }} Zeichen sein. + + + The password strength is too low. Please use a stronger password. + Das Passwort ist zu schwach. + + + This value contains characters that are not allowed by the current restriction-level. + Der Wert enthält Zeichen, die auf der aktuellen Einschränkungsstufe nicht erlaubt sind. + + + Using invisible characters is not allowed. + Unsichtbare Zeichen sind nicht erlaubt. + + + Mixing numbers from different scripts is not allowed. + Das Mischen von Zahlen aus verschiedenen Skripten ist nicht erlaubt. + + + Using hidden overlay characters is not allowed. + Verstecke Overlay-Zeichen sind nicht erlaubt. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.el.xlf b/src/vendor/symfony/validator/Resources/translations/validators.el.xlf new file mode 100644 index 0000000..768986d --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.el.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + Αυτή η τιμή πρέπει να είναι ψευδής. + + + This value should be true. + Αυτή η τιμή πρέπει να είναι αληθής. + + + This value should be of type {{ type }}. + Αυτή η τιμή πρέπει να είναι τύπου {{ type }}. + + + This value should be blank. + Αυτή η τιμή πρέπει να είναι κενή. + + + The value you selected is not a valid choice. + Η τιμή που επιλέχθηκε δεν αντιστοιχεί σε έγκυρη επιλογή. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Πρέπει να επιλέξτε τουλάχιστον {{ limit }} επιλογή.|Πρέπει να επιλέξτε τουλάχιστον {{ limit }} επιλογές. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Πρέπει να επιλέξτε το πολύ {{ limit }} επιλογή.|Πρέπει να επιλέξτε το πολύ {{ limit }} επιλογές. + + + One or more of the given values is invalid. + Μια ή περισσότερες τιμές δεν είναι έγκυρες. + + + This field was not expected. + Αυτό το πεδίο δεν ήταν αναμενόμενο. + + + This field is missing. + Λείπει αυτό το πεδίο. + + + This value is not a valid date. + Η τιμή δεν αντιστοιχεί σε έγκυρη ημερομηνία. + + + This value is not a valid datetime. + Η τιμή δεν αντιστοιχεί σε έγκυρη ημερομηνία και ώρα. + + + This value is not a valid email address. + Η τιμή δεν αντιστοιχεί σε έγκυρο email. + + + The file could not be found. + Το αρχείο δε μπορεί να βρεθεί. + + + The file is not readable. + Το αρχείο δεν είναι αναγνώσιμο. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + Το αρχείο είναι πολύ μεγάλο ({{ size }} {{ suffix }}). Το μέγιστο επιτρεπτό μέγεθος είναι {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + Ο τύπος mime του αρχείου δεν είναι έγκυρος ({{ type }}). Οι έγκυροι τύποι mime είναι {{ types }}. + + + This value should be {{ limit }} or less. + Αυτή η τιμή θα έπρεπε να είναι {{ limit }} ή λιγότερο. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Αυτή η τιμή είναι πολύ μεγάλη. Θα έπρεπε να έχει {{ limit }} χαρακτήρα ή λιγότερο.|Αυτή η τιμή είναι πολύ μεγάλη. Θα έπρεπε να έχει {{ limit }} χαρακτήρες ή λιγότερο. + + + This value should be {{ limit }} or more. + Αυτή η τιμή θα έπρεπε να είναι {{ limit }} ή περισσότερο. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Αυτή η τιμή είναι πολύ μικρή. Θα έπρεπε να έχει {{ limit }} χαρακτήρα ή περισσότερο.|Αυτή η τιμή είναι πολύ μικρή. Θα έπρεπε να έχει {{ limit }} χαρακτήρες ή περισσότερο. + + + This value should not be blank. + Αυτή η τιμή δεν πρέπει να είναι κενή. + + + This value should not be null. + Αυτή η τιμή δεν πρέπει να είναι μηδενική. + + + This value should be null. + Αυτή η τιμή πρέπει να είναι μηδενική. + + + This value is not valid. + Αυτή η τιμή δεν είναι έγκυρη. + + + This value is not a valid time. + Αυτή η τιμή δεν αντιστοιχεί σε έγκυρη ώρα. + + + This value is not a valid URL. + Αυτή η τιμή δεν αντιστοιχεί σε έγκυρο URL. + + + The two values should be equal. + Οι δύο τιμές θα πρέπει να είναι ίδιες. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + Το αρχείο είναι πολύ μεγάλο. Το μέγιστο επιτρεπτό μέγεθος είναι {{ limit }} {{ suffix }}. + + + The file is too large. + Το αρχείο είναι πολύ μεγάλο. + + + The file could not be uploaded. + Το αρχείο δε μπορεί να ανέβει. + + + This value should be a valid number. + Αυτή η τιμή θα πρέπει να είναι ένας έγκυρος αριθμός. + + + This file is not a valid image. + Το αρχείο δεν αποτελεί έγκυρη εικόνα. + + + This is not a valid IP address. + Αυτό δεν είναι μια έγκυρη διεύθυνση IP. + + + This value is not a valid language. + Αυτή η τιμή δεν αντιστοιχεί σε μια έγκυρη γλώσσα. + + + This value is not a valid locale. + Αυτή η τιμή δεν αντιστοιχεί σε έγκυρο κωδικό τοποθεσίας. + + + This value is not a valid country. + Αυτή η τιμή δεν αντιστοιχεί σε μια έγκυρη χώρα. + + + This value is already used. + Αυτή η τιμή χρησιμοποιείται ήδη. + + + The size of the image could not be detected. + Το μέγεθος της εικόνας δεν ήταν δυνατό να ανιχνευθεί. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + Το πλάτος της εικόνας είναι πολύ μεγάλο ({{ width }}px). Το μέγιστο επιτρεπτό πλάτος είναι {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + Το πλάτος της εικόνας είναι πολύ μικρό ({{ width }}px). Το ελάχιστο επιτρεπτό πλάτος είναι {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + Το ύψος της εικόνας είναι πολύ μεγάλο ({{ height }}px). Το μέγιστο επιτρεπτό ύψος είναι {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + Το ύψος της εικόνας είναι πολύ μικρό ({{ height }}px). Το ελάχιστο επιτρεπτό ύψος είναι {{ min_height }}px. + + + This value should be the user's current password. + Αυτή η τιμή θα έπρεπε να είναι ο τρέχων κωδικός. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Αυτή η τιμή θα έπρεπε να έχει ακριβώς {{ limit }} χαρακτήρα.|Αυτή η τιμή θα έπρεπε να έχει ακριβώς {{ limit }} χαρακτήρες. + + + The file was only partially uploaded. + Το αρχείο δεν ανέβηκε ολόκληρο. + + + No file was uploaded. + Δεν ανέβηκε κανένα αρχείο. + + + No temporary folder was configured in php.ini. + Κανένας προσωρινός φάκελος δεν έχει ρυθμιστεί στο php.ini. + + + Cannot write temporary file to disk. + Αδυναμία εγγραφής προσωρινού αρχείου στο δίσκο. + + + A PHP extension caused the upload to fail. + Μια επέκταση PHP προκάλεσε αδυναμία ανεβάσματος. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Αυτή η συλλογή θα πρέπει να περιέχει {{ limit }} στοιχείο ή περισσότερα.|Αυτή η συλλογή θα πρέπει να περιέχει {{ limit }} στοιχεία ή περισσότερα. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Αυτή η συλλογή θα πρέπει να περιέχει {{ limit }} στοιχείo ή λιγότερα.|Αυτή η συλλογή θα πρέπει να περιέχει {{ limit }} στοιχεία ή λιγότερα. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Αυτή η συλλογή θα πρέπει να περιέχει ακριβώς {{ limit }} στοιχείo.|Αυτή η συλλογή θα πρέπει να περιέχει ακριβώς {{ limit }} στοιχεία. + + + Invalid card number. + Μη έγκυρος αριθμός κάρτας. + + + Unsupported card type or invalid card number. + Μη υποστηριζόμενος τύπος κάρτας ή μη έγκυρος αριθμός κάρτας. + + + This is not a valid International Bank Account Number (IBAN). + Αυτό δεν αντιστοιχεί σε έγκυρο διεθνή αριθμό τραπεζικού λογαριασμού (IBAN). + + + This value is not a valid ISBN-10. + Αυτό δεν είναι έγκυρος κωδικός ISBN-10. + + + This value is not a valid ISBN-13. + Αυτό δεν είναι έγκυρος κωδικός ISBN-13. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Αυτό δεν είναι ούτε έγκυρος κωδικός ISBN-10 ούτε έγκυρος κωδικός ISBN-13. + + + This value is not a valid ISSN. + Αυτό δεν είναι έγκυρος κωδικός ISSN. + + + This value is not a valid currency. + Αυτό δεν αντιστοιχεί σε έγκυρο νόμισμα. + + + This value should be equal to {{ compared_value }}. + Αυτή η τιμή θα πρέπει να είναι ίση με {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + Αυτή η τιμή θα πρέπει να είναι μεγαλύτερη από {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + Αυτή η τιμή θα πρέπει να είναι μεγαλύτερη ή ίση με {{ compared_value }}. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Αυτή η τιμή θα πρέπει να είναι πανομοιότυπη με {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Αυτή η τιμή θα πρέπει να είναι μικρότερη από {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + Αυτή η τιμή θα πρέπει να είναι μικρότερη ή ίση με {{ compared_value }}. + + + This value should not be equal to {{ compared_value }}. + Αυτή η τιμή δεν θα πρέπει να είναι ίση με {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Αυτή η τιμή δεν πρέπει να είναι πανομοιότυπη με {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Η αναλογία πλάτους-ύψους της εικόνας είναι πολύ μεγάλη ({{ ratio }}). Μέγιστη επιτρεπτή αναλογία {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + Η αναλογία πλάτους-ύψους της εικόνας είναι πολύ μικρή ({{ ratio }}). Ελάχιστη επιτρεπτή αναλογία {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + Η εικόνα είναι τετράγωνη ({{ width }}x{{ height }}px). Δεν επιτρέπονται τετράγωνες εικόνες. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + Η εικόνα έχει οριζόντιο προσανατολισμό ({{ width }}x{{ height }}px). Δεν επιτρέπονται εικόνες με οριζόντιο προσανατολισμό. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + Η εικόνα έχει κάθετο προσανατολισμό ({{ width }}x{{ height }}px). Δεν επιτρέπονται εικόνες με κάθετο προσανατολισμό. + + + An empty file is not allowed. + Δεν επιτρέπεται κενό αρχείο. + + + The host could not be resolved. + Η διεύθυνση δεν μπόρεσε να επιλυθεί. + + + This value does not match the expected {{ charset }} charset. + Αυτή η τιμή δεν ταιριάζει στο αναμενόμενο {{ charset }} σύνολο χαρακτήρων. + + + This is not a valid Business Identifier Code (BIC). + Αυτός δεν είναι ένας έγκυρος κωδικός BIC. + + + Error + Σφάλμα + + + This is not a valid UUID. + Αυτό δεν είναι ένα έγκυρο UUID. + + + This value should be a multiple of {{ compared_value }}. + Αυτή η τιμή θα έπρεπε να είναι πολλαπλάσιο του {{ compared_value }}. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Αυτός ο κωδικός BIC δεν σχετίζεται με το IBAN {{ iban }}. + + + This value should be valid JSON. + Αυτή η τιμή θα πρέπει να είναι έγκυρο JSON. + + + This collection should contain only unique elements. + Αυτή η συλλογή θα πρέπει να περιέχει μόνο μοναδικά στοιχεία. + + + This value should be positive. + Αυτή η τιμή θα πρέπει να είναι θετική. + + + This value should be either positive or zero. + Αυτή η τιμή θα πρέπει να είναι θετική ή μηδενική. + + + This value should be negative. + Αυτή η τιμή θα πρέπει να είναι αρνητική. + + + This value should be either negative or zero. + Αυτή η τιμή θα πρέπει να είναι αρνητική ή μηδενική. + + + This value is not a valid timezone. + Αυτή η τιμή θα δεν είναι έγκυρη ζώνη ώρας. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Αυτός ο κωδικός πρόσβασης έχει διαρρεύσει σε παραβίαση δεδομένων. Παρακαλούμε να χρησιμοποιήσετε έναν άλλο κωδικό. + + + This value should be between {{ min }} and {{ max }}. + Αυτή η τιμή θα πρέπει να είναι μεταξύ {{ min }} και {{ max }}. + + + This value is not a valid hostname. + Αυτή η τιμή δεν είναι έγκυρο όνομα υποδοχής. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + Το νούμερο των στοιχείων σε αυτή τη συλλογή θα πρέπει να είναι πολλαπλάσιο του {{ compared_value }}. + + + This value should satisfy at least one of the following constraints: + Αυτή η τιμή θα πρέπει να ικανοποιεί τουλάχιστον έναν από τους παρακάτω περιορισμούς: + + + Each element of this collection should satisfy its own set of constraints. + Κάθε στοιχείο σε αυτή τη συλλογή θα πρέπει να ικανοποιεί το δικό του σύνολο περιορισμών. + + + This value is not a valid International Securities Identification Number (ISIN). + Αυτή η τιμή δεν είναι έγκυρο International Securities Identification Number (ISIN). + + + This value should be a valid expression. + Αυτή η τιμή θα πρέπει να είναι μία έγκυρη έκφραση. + + + This value is not a valid CSS color. + Αυτή η τιμή δεν είναι έγκυρο χρώμα CSS. + + + This value is not a valid CIDR notation. + Αυτή η τιμή δεν είναι έγκυρη CIDR σημειογραφία. + + + The value of the netmask should be between {{ min }} and {{ max }}. + Η τιμή του netmask πρέπει να είναι ανάμεσα σε {{ min }} και {{ max }}. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.en.xlf b/src/vendor/symfony/validator/Resources/translations/validators.en.xlf new file mode 100644 index 0000000..aaf6ada --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.en.xlf @@ -0,0 +1,431 @@ + + + + + + This value should be false. + This value should be false. + + + This value should be true. + This value should be true. + + + This value should be of type {{ type }}. + This value should be of type {{ type }}. + + + This value should be blank. + This value should be blank. + + + The value you selected is not a valid choice. + The value you selected is not a valid choice. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + + + One or more of the given values is invalid. + One or more of the given values is invalid. + + + This field was not expected. + This field was not expected. + + + This field is missing. + This field is missing. + + + This value is not a valid date. + This value is not a valid date. + + + This value is not a valid datetime. + This value is not a valid datetime. + + + This value is not a valid email address. + This value is not a valid email address. + + + The file could not be found. + The file could not be found. + + + The file is not readable. + The file is not readable. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + + + This value should be {{ limit }} or less. + This value should be {{ limit }} or less. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + + + This value should be {{ limit }} or more. + This value should be {{ limit }} or more. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + + + This value should not be blank. + This value should not be blank. + + + This value should not be null. + This value should not be null. + + + This value should be null. + This value should be null. + + + This value is not valid. + This value is not valid. + + + This value is not a valid time. + This value is not a valid time. + + + This value is not a valid URL. + This value is not a valid URL. + + + The two values should be equal. + The two values should be equal. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + + + The file is too large. + The file is too large. + + + The file could not be uploaded. + The file could not be uploaded. + + + This value should be a valid number. + This value should be a valid number. + + + This file is not a valid image. + This file is not a valid image. + + + This is not a valid IP address. + This is not a valid IP address. + + + This value is not a valid language. + This value is not a valid language. + + + This value is not a valid locale. + This value is not a valid locale. + + + This value is not a valid country. + This value is not a valid country. + + + This value is already used. + This value is already used. + + + The size of the image could not be detected. + The size of the image could not be detected. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + + + This value should be the user's current password. + This value should be the user's current password. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + + + The file was only partially uploaded. + The file was only partially uploaded. + + + No file was uploaded. + No file was uploaded. + + + No temporary folder was configured in php.ini. + No temporary folder was configured in php.ini, or the configured folder does not exist. + + + Cannot write temporary file to disk. + Cannot write temporary file to disk. + + + A PHP extension caused the upload to fail. + A PHP extension caused the upload to fail. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + + + Invalid card number. + Invalid card number. + + + Unsupported card type or invalid card number. + Unsupported card type or invalid card number. + + + This is not a valid International Bank Account Number (IBAN). + This is not a valid International Bank Account Number (IBAN). + + + This value is not a valid ISBN-10. + This value is not a valid ISBN-10. + + + This value is not a valid ISBN-13. + This value is not a valid ISBN-13. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + This value is neither a valid ISBN-10 nor a valid ISBN-13. + + + This value is not a valid ISSN. + This value is not a valid ISSN. + + + This value is not a valid currency. + This value is not a valid currency. + + + This value should be equal to {{ compared_value }}. + This value should be equal to {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + This value should be greater than {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + This value should be greater than or equal to {{ compared_value }}. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + This value should be less than {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + This value should be less than or equal to {{ compared_value }}. + + + This value should not be equal to {{ compared_value }}. + This value should not be equal to {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + + + An empty file is not allowed. + An empty file is not allowed. + + + The host could not be resolved. + The host could not be resolved. + + + This value does not match the expected {{ charset }} charset. + This value does not match the expected {{ charset }} charset. + + + This is not a valid Business Identifier Code (BIC). + This is not a valid Business Identifier Code (BIC). + + + Error + Error + + + This is not a valid UUID. + This is not a valid UUID. + + + This value should be a multiple of {{ compared_value }}. + This value should be a multiple of {{ compared_value }}. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + + + This value should be valid JSON. + This value should be valid JSON. + + + This collection should contain only unique elements. + This collection should contain only unique elements. + + + This value should be positive. + This value should be positive. + + + This value should be either positive or zero. + This value should be either positive or zero. + + + This value should be negative. + This value should be negative. + + + This value should be either negative or zero. + This value should be either negative or zero. + + + This value is not a valid timezone. + This value is not a valid timezone. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + This password has been leaked in a data breach, it must not be used. Please use another password. + + + This value should be between {{ min }} and {{ max }}. + This value should be between {{ min }} and {{ max }}. + + + This value is not a valid hostname. + This value is not a valid hostname. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + The number of elements in this collection should be a multiple of {{ compared_value }}. + + + This value should satisfy at least one of the following constraints: + This value should satisfy at least one of the following constraints: + + + Each element of this collection should satisfy its own set of constraints. + Each element of this collection should satisfy its own set of constraints. + + + This value is not a valid International Securities Identification Number (ISIN). + This value is not a valid International Securities Identification Number (ISIN). + + + This value should be a valid expression. + This value should be a valid expression. + + + This value is not a valid CSS color. + This value is not a valid CSS color. + + + This value is not a valid CIDR notation. + This value is not a valid CIDR notation. + + + The value of the netmask should be between {{ min }} and {{ max }}. + The value of the netmask should be between {{ min }} and {{ max }}. + + + The filename is too long. It should have {{ filename_max_length }} character or less.|The filename is too long. It should have {{ filename_max_length }} characters or less. + The filename is too long. It should have {{ filename_max_length }} character or less.|The filename is too long. It should have {{ filename_max_length }} characters or less. + + + The password strength is too low. Please use a stronger password. + The password strength is too low. Please use a stronger password. + + + This value contains characters that are not allowed by the current restriction-level. + This value contains characters that are not allowed by the current restriction-level. + + + Using invisible characters is not allowed. + Using invisible characters is not allowed. + + + Mixing numbers from different scripts is not allowed. + Mixing numbers from different scripts is not allowed. + + + Using hidden overlay characters is not allowed. + Using hidden overlay characters is not allowed. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.es.xlf b/src/vendor/symfony/validator/Resources/translations/validators.es.xlf new file mode 100644 index 0000000..897d0a4 --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.es.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + Este valor debería ser falso. + + + This value should be true. + Este valor debería ser verdadero. + + + This value should be of type {{ type }}. + Este valor debería ser de tipo {{ type }}. + + + This value should be blank. + Este valor debería estar vacío. + + + The value you selected is not a valid choice. + El valor seleccionado no es una opción válida. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Debe seleccionar al menos {{ limit }} opción.|Debe seleccionar al menos {{ limit }} opciones. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Debe seleccionar como máximo {{ limit }} opción.|Debe seleccionar como máximo {{ limit }} opciones. + + + One or more of the given values is invalid. + Uno o más de los valores indicados no son válidos. + + + This field was not expected. + Este campo no se esperaba. + + + This field is missing. + Este campo está desaparecido. + + + This value is not a valid date. + Este valor no es una fecha válida. + + + This value is not a valid datetime. + Este valor no es una fecha y hora válidas. + + + This value is not a valid email address. + Este valor no es una dirección de email válida. + + + The file could not be found. + No se pudo encontrar el archivo. + + + The file is not readable. + No se puede leer el archivo. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + El archivo es demasiado grande ({{ size }} {{ suffix }}). El tamaño máximo permitido es {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + El tipo mime del archivo no es válido ({{ type }}). Los tipos mime válidos son {{ types }}. + + + This value should be {{ limit }} or less. + Este valor debería ser {{ limit }} o menos. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Este valor es demasiado largo. Debería tener {{ limit }} carácter o menos.|Este valor es demasiado largo. Debería tener {{ limit }} caracteres o menos. + + + This value should be {{ limit }} or more. + Este valor debería ser {{ limit }} o más. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Este valor es demasiado corto. Debería tener {{ limit }} carácter o más.|Este valor es demasiado corto. Debería tener {{ limit }} caracteres o más. + + + This value should not be blank. + Este valor no debería estar vacío. + + + This value should not be null. + Este valor no debería ser nulo. + + + This value should be null. + Este valor debería ser nulo. + + + This value is not valid. + Este valor no es válido. + + + This value is not a valid time. + Este valor no es una hora válida. + + + This value is not a valid URL. + Este valor no es una URL válida. + + + The two values should be equal. + Los dos valores deberían ser iguales. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + El archivo es demasiado grande. El tamaño máximo permitido es {{ limit }} {{ suffix }}. + + + The file is too large. + El archivo es demasiado grande. + + + The file could not be uploaded. + No se pudo subir el archivo. + + + This value should be a valid number. + Este valor debería ser un número válido. + + + This file is not a valid image. + El archivo no es una imagen válida. + + + This is not a valid IP address. + Esto no es una dirección IP válida. + + + This value is not a valid language. + Este valor no es un idioma válido. + + + This value is not a valid locale. + Este valor no es una localización válida. + + + This value is not a valid country. + Este valor no es un país válido. + + + This value is already used. + Este valor ya se ha utilizado. + + + The size of the image could not be detected. + No se pudo determinar el tamaño de la imagen. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + El ancho de la imagen es demasiado grande ({{ width }}px). El ancho máximo permitido es de {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + El ancho de la imagen es demasiado pequeño ({{ width }}px). El ancho mínimo requerido es {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + La altura de la imagen es demasiado grande ({{ height }}px). La altura máxima permitida es de {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + La altura de la imagen es demasiado pequeña ({{ height }}px). La altura mínima requerida es de {{ min_height }}px. + + + This value should be the user's current password. + Este valor debería ser la contraseña actual del usuario. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Este valor debería tener exactamente {{ limit }} carácter.|Este valor debería tener exactamente {{ limit }} caracteres. + + + The file was only partially uploaded. + El archivo fue sólo subido parcialmente. + + + No file was uploaded. + Ningún archivo fue subido. + + + No temporary folder was configured in php.ini. + Ninguna carpeta temporal fue configurada en php.ini o la carpeta configurada no existe. + + + Cannot write temporary file to disk. + No se pudo escribir el archivo temporal en el disco. + + + A PHP extension caused the upload to fail. + Una extensión de PHP hizo que la subida fallara. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Esta colección debe contener {{ limit }} elemento o más.|Esta colección debe contener {{ limit }} elementos o más. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Esta colección debe contener {{ limit }} elemento o menos.|Esta colección debe contener {{ limit }} elementos o menos. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Esta colección debe contener exactamente {{ limit }} elemento.|Esta colección debe contener exactamente {{ limit }} elementos. + + + Invalid card number. + Número de tarjeta inválido. + + + Unsupported card type or invalid card number. + Tipo de tarjeta no soportado o número de tarjeta inválido. + + + This is not a valid International Bank Account Number (IBAN). + Esto no es un International Bank Account Number (IBAN) válido. + + + This value is not a valid ISBN-10. + Este valor no es un ISBN-10 válido. + + + This value is not a valid ISBN-13. + Este valor no es un ISBN-13 válido. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Este valor no es ni un ISBN-10 válido ni un ISBN-13 válido. + + + This value is not a valid ISSN. + Este valor no es un ISSN válido. + + + This value is not a valid currency. + Este valor no es una divisa válida. + + + This value should be equal to {{ compared_value }}. + Este valor debería ser igual que {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + Este valor debería ser mayor que {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + Este valor debería ser mayor o igual que {{ compared_value }}. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Este valor debería ser idéntico a {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Este valor debería ser menor que {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + Este valor debería ser menor o igual que {{ compared_value }}. + + + This value should not be equal to {{ compared_value }}. + Este valor debería ser distinto de {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Este valor no debería ser idéntico a {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + La proporción de la imagen es demasiado grande ({{ ratio }}). La máxima proporción permitida es {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + La proporción de la imagen es demasiado pequeña ({{ ratio }}). La mínima proporción permitida es {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + La imagen es cuadrada ({{ width }}x{{ height }}px). Las imágenes cuadradas no están permitidas. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + La imagen está orientada horizontalmente ({{ width }}x{{ height }}px). Las imágenes orientadas horizontalmente no están permitidas. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + La imagen está orientada verticalmente ({{ width }}x{{ height }}px). Las imágenes orientadas verticalmente no están permitidas. + + + An empty file is not allowed. + No está permitido un archivo vacío. + + + The host could not be resolved. + No se puede resolver el host. + + + This value does not match the expected {{ charset }} charset. + La codificación de caracteres para este valor debería ser {{ charset }}. + + + This is not a valid Business Identifier Code (BIC). + No es un Código de Identificación Bancaria (BIC) válido. + + + Error + Error + + + This is not a valid UUID. + Este valor no es un UUID válido. + + + This value should be a multiple of {{ compared_value }}. + Este valor debería ser múltiplo de {{ compared_value }}. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Este Código de Identificación Bancaria (BIC) no está asociado con el IBAN {{ iban }}. + + + This value should be valid JSON. + Este valor debería ser un JSON válido. + + + This collection should contain only unique elements. + Esta colección debería tener exclusivamente elementos únicos. + + + This value should be positive. + Este valor debería ser positivo. + + + This value should be either positive or zero. + Este valor debería ser positivo o igual a cero. + + + This value should be negative. + Este valor debería ser negativo. + + + This value should be either negative or zero. + Este valor debería ser negativo o igual a cero. + + + This value is not a valid timezone. + Este valor no es una zona horaria válida. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Esta contraseña no se puede utilizar porque está incluida en un listado de contraseñas públicas obtenido gracias a fallos de seguridad de otros sitios y aplicaciones. Por favor utilice otra contraseña. + + + This value should be between {{ min }} and {{ max }}. + Este valor debería estar entre {{ min }} y {{ max }}. + + + This value is not a valid hostname. + Este valor no es un nombre de host válido. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + El número de elementos en esta colección debería ser múltiplo de {{ compared_value }}. + + + This value should satisfy at least one of the following constraints: + Este valor debería satisfacer al menos una de las siguientes restricciones: + + + Each element of this collection should satisfy its own set of constraints. + Cada elemento de esta colección debería satisfacer su propio conjunto de restricciones. + + + This value is not a valid International Securities Identification Number (ISIN). + Este valor no es un número de identificación internacional de valores (ISIN) válido. + + + This value should be a valid expression. + Este valor debería ser una expresión válida. + + + This value is not a valid CSS color. + Este valor no es un color CSS válido. + + + This value is not a valid CIDR notation. + Este valor no es una notación CIDR válida. + + + The value of the netmask should be between {{ min }} and {{ max }}. + El valor de la máscara de red debería estar entre {{ min }} y {{ max }}. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.et.xlf b/src/vendor/symfony/validator/Resources/translations/validators.et.xlf new file mode 100644 index 0000000..b323dcd --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.et.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + Väärtus peaks olema väär. + + + This value should be true. + Väärtus peaks oleme tõene. + + + This value should be of type {{ type }}. + Väärtus peaks olema {{ type }}-tüüpi. + + + This value should be blank. + Väärtus peaks olema tühi. + + + The value you selected is not a valid choice. + Väärtus peaks olema üks etteantud valikutest. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Valima peaks vähemalt {{ limit }} valikut. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Valima peaks mitte rohkem kui {{ limit }} valikut. + + + One or more of the given values is invalid. + Üks või rohkem väärtustest on vigane. + + + This field was not expected. + See väli ei olnud oodatud. + + + This field is missing. + See väli on puudu. + + + This value is not a valid date. + Väärtus pole korrektne kuupäev. + + + This value is not a valid datetime. + Väärtus pole korrektne kuupäev ja kellaeg. + + + This value is not a valid email address. + Väärtus pole korrektne e-maili aadress. + + + The file could not be found. + Faili ei leita. + + + The file is not readable. + Fail ei ole loetav. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + Fail on liiga suur ({{ size }} {{ suffix }}). Suurim lubatud suurus on {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + Faili sisutüüp on vigane ({{ type }}). Lubatud sisutüübid on {{ types }}. + + + This value should be {{ limit }} or less. + Väärtus peaks olema {{ limit }} või vähem. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Väärtus on liiga pikk. Pikkus peaks olema {{ limit }} tähemärki või vähem. + + + This value should be {{ limit }} or more. + Väärtus peaks olema {{ limit }} või rohkem. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Väärtus on liiga lühike. Pikkus peaks olema {{ limit }} tähemärki või rohkem. + + + This value should not be blank. + Väärtus ei tohiks olla tühi. + + + This value should not be null. + Väärtus ei tohiks olla 'null'. + + + This value should be null. + Väärtus peaks olema 'null'. + + + This value is not valid. + Väärtus on vigane. + + + This value is not a valid time. + Väärtus pole korrektne aeg. + + + This value is not a valid URL. + Väärtus pole korrektne URL. + + + The two values should be equal. + Väärtused peaksid olema võrdsed. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + Fail on liiga suur. Maksimaalne lubatud suurus on {{ limit }} {{ suffix }}. + + + The file is too large. + Fail on liiga suur. + + + The file could not be uploaded. + Faili ei saa üles laadida. + + + This value should be a valid number. + Väärtus peaks olema korrektne number. + + + This file is not a valid image. + Fail ei ole korrektne pilt. + + + This is not a valid IP address. + IP aadress pole korrektne. + + + This value is not a valid language. + Väärtus pole korrektne keel. + + + This value is not a valid locale. + Väärtus pole korrektne asukohakeel. + + + This value is not a valid country. + Väärtus pole olemasolev riik. + + + This value is already used. + Väärtust on juba kasutatud. + + + The size of the image could not be detected. + Pildi suurust polnud võimalik tuvastada. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + Pilt on liiga lai ({{ width }}px). Suurim lubatud laius on {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + Pilt on liiga kitsas ({{ width }}px). Vähim lubatud laius on {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + Pilt on liiga pikk ({{ height }}px). Lubatud suurim pikkus on {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + Pilt pole piisavalt pikk ({{ height }}px). Lubatud vähim pikkus on {{ min_height }}px. + + + This value should be the user's current password. + Väärtus peaks olema kasutaja kehtiv salasõna. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Väärtus peaks olema täpselt {{ limit }} tähemärk pikk.|Väärtus peaks olema täpselt {{ limit }} tähemärki pikk. + + + The file was only partially uploaded. + Fail ei laetud täielikult üles. + + + No file was uploaded. + Ühtegi faili ei laetud üles. + + + No temporary folder was configured in php.ini. + Ühtegi ajutist kausta polnud php.ini-s seadistatud. + + + Cannot write temporary file to disk. + Ajutist faili ei saa kettale kirjutada. + + + A PHP extension caused the upload to fail. + PHP laiendi tõttu ebaõnnestus faili üleslaadimine. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Kogumikus peaks olema vähemalt {{ limit }} element.|Kogumikus peaks olema vähemalt {{ limit }} elementi. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Kogumikus peaks olema ülimalt {{ limit }} element.|Kogumikus peaks olema ülimalt {{ limit }} elementi. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Kogumikus peaks olema täpselt {{ limit }} element.|Kogumikus peaks olema täpselt {{ limit }}|elementi. + + + Invalid card number. + Vigane kaardi number. + + + Unsupported card type or invalid card number. + Kaardi tüüpi ei toetata või kaardi number on vigane. + + + This is not a valid International Bank Account Number (IBAN). + Väärtus pole korrektne IBAN-number. + + + This value is not a valid ISBN-10. + Väärtus pole korrektne ISBN-10. + + + This value is not a valid ISBN-13. + Väärtus pole korrektne ISBN-13. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Väärtus pole korrektne ISBN-10 ega ISBN-13. + + + This value is not a valid ISSN. + Väärtus pole korrektne ISSN. + + + This value is not a valid currency. + Väärtus pole korrektne valuuta. + + + This value should be equal to {{ compared_value }}. + Väärtus peaks olema võrdne {{ compared_value }}-ga. + + + This value should be greater than {{ compared_value }}. + Väärtus peaks olema suurem kui {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + Väärtus peaks olema suurem kui või võrduma {{ compared_value }}-ga. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Väärtus peaks olema identne väärtusega {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Väärtus peaks olema väiksem kui {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + Väärtus peaks olema väiksem kui või võrduma {{ compared_value }}-ga. + + + This value should not be equal to {{ compared_value }}. + Väärtus ei tohiks võrduda {{ compared_value }}-ga. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Väärtus ei tohiks olla identne väärtusega {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Kuvasuhe on liiga suur ({{ ratio }}). Lubatud maksimaalne suhe on {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + Kuvasuhe on liiga väike ({{ ratio }}). Oodatav minimaalne suhe on {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + Pilt on ruudukujuline ({{ width }}x{{ height }}px). Ruudukujulised pildid pole lubatud. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + Pilt on horisontaalselt orienteeritud ({{ width }}x{{ height }}px). Maastikulised pildid pole lubatud. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + Pilt on vertikaalselt orienteeritud ({{ width }}x{{ height }}px). Portreepildid pole lubatud. + + + An empty file is not allowed. + Tühi fail pole lubatud. + + + The host could not be resolved. + Sellist domeeni ei õnnestunud leida. + + + This value does not match the expected {{ charset }} charset. + See väärtus ei ühti eeldatava tähemärgiga {{ charset }}. + + + This is not a valid Business Identifier Code (BIC). + See ei ole kehtiv ettevõtte identifitseerimiskood (BIC). + + + Error + Viga + + + This is not a valid UUID. + See pole kehtiv UUID. + + + This value should be a multiple of {{ compared_value }}. + See väärtus peaks olema väärtuse {{ compared_value }} kordne. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + See ettevõtte identifitseerimiskood (BIC) ei ole seotud IBAN-iga {{ iban }}. + + + This value should be valid JSON. + See väärtus peaks olema kehtiv JSON. + + + This collection should contain only unique elements. + See kogu peaks sisaldama ainult unikaalseid elemente. + + + This value should be positive. + See väärtus peaks olema positiivne. + + + This value should be either positive or zero. + See väärtus peaks olema kas positiivne või null. + + + This value should be negative. + See väärtus peaks olema negatiivne. + + + This value should be either negative or zero. + See väärtus peaks olema kas negatiivne või null. + + + This value is not a valid timezone. + See väärtus pole kehtiv ajavöönd. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + See parool on lekkinud andmerikkumise korral, seda ei tohi kasutada. Palun kasutage muud parooli. + + + This value should be between {{ min }} and {{ max }}. + See väärtus peaks olema vahemikus {{ min }} kuni {{ max }}. + + + This value is not a valid hostname. + See väärtus pole korrektne domeeninimi. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + Selles kogus olevate elementide arv peab olema arvu {{ compared_value }} kordne. + + + This value should satisfy at least one of the following constraints: + See väärtus peab vastama vähemalt ühele järgmistest tingimustest: + + + Each element of this collection should satisfy its own set of constraints. + Kõik väärtused selles kogus peavad vastama oma tingimustele. + + + This value is not a valid International Securities Identification Number (ISIN). + See väärtus pole korrektne ISIN-kood. + + + This value should be a valid expression. + See väärtus pole korrektne avaldis. + + + This value is not a valid CSS color. + See väärtus pole korrektne CSS-i värv. + + + This value is not a valid CIDR notation. + See väärtus pole korrektne CIDR võrguaadress. + + + The value of the netmask should be between {{ min }} and {{ max }}. + Võrgumaski väärtus peaks olema vahemikus {{ min }} kuni {{ max }}. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.eu.xlf b/src/vendor/symfony/validator/Resources/translations/validators.eu.xlf new file mode 100644 index 0000000..ece2da0 --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.eu.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + Balio hau faltsua izan beharko litzateke. + + + This value should be true. + Balio hau egia izan beharko litzateke. + + + This value should be of type {{ type }}. + Balio hau {{ type }} motakoa izan beharko litzateke. + + + This value should be blank. + Balio hau hutsik egon beharko litzateke. + + + The value you selected is not a valid choice. + Hautatu duzun balioa ez da aukera egoki bat. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Gutxienez aukera {{ limit }} hautatu behar duzu.|Gutxienez {{ limit }} aukera hautatu behar dituzu. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Gehienez aukera {{ limit }} hautatu behar duzu.|Gehienez {{ limit }} aukera hautatu behar dituzu. + + + One or more of the given values is invalid. + Emandako balioetatik gutxienez bat ez da egokia. + + + This field was not expected. + Eremu hau ez zen espero. + + + This field is missing. + Eremu hau falta da. + + + This value is not a valid date. + Balio hau ez da data egoki bat. + + + This value is not a valid datetime. + Balio hau ez da data-ordu egoki bat. + + + This value is not a valid email address. + Balio hau ez da posta elektroniko egoki bat. + + + The file could not be found. + Ezin izan da fitxategia aurkitu. + + + The file is not readable. + Fitxategia ez da irakurgarria. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + Fitxategia handiegia da ({{ size }} {{ suffix }}). Baimendutako tamaina handiena {{ limit }} {{ suffix }} da. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + Fitxategiaren mime mota ez da egokia ({{ type }}). Hauek dira baimendutako mime motak: {{ types }}. + + + This value should be {{ limit }} or less. + Balio hau gehienez {{ limit }} izan beharko litzateke. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Balio hau luzeegia da. Gehienez karaktere {{ limit }} eduki beharko luke.|Balio hau luzeegia da. Gehienez {{ limit }} karaktere eduki beharko lituzke. + + + This value should be {{ limit }} or more. + Balio hau gutxienez {{ limit }} izan beharko litzateke. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Balio hau motzegia da. Karaktere {{ limit }} gutxienez eduki beharko luke.|Balio hau motzegia da. Gutxienez {{ limit }} karaktere eduki beharko lituzke. + + + This value should not be blank. + Balio hau ez litzateke hutsik egon behar. + + + This value should not be null. + Balio hau ez litzateke nulua izan behar. + + + This value should be null. + Balio hau nulua izan beharko litzateke. + + + This value is not valid. + Balio hau ez da egokia. + + + This value is not a valid time. + Balio hau ez da ordu egoki bat. + + + This value is not a valid URL. + Balio hau ez da baliabideen kokatzaile uniforme (URL) egoki bat. + + + The two values should be equal. + Bi balioak berdinak izan beharko lirateke. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + Fitxategia handiegia da. Baimendutako tamaina handiena {{ limit }} {{ suffix }} da. + + + The file is too large. + Fitxategia handiegia da. + + + The file could not be uploaded. + Ezin izan da fitxategia igo. + + + This value should be a valid number. + Balio hau zenbaki egoki bat izan beharko litzateke. + + + This file is not a valid image. + Fitxategi hau ez da irudi egoki bat. + + + This is not a valid IP address. + Honako hau ez da IP helbide egoki bat. + + + This value is not a valid language. + Balio hau ez da hizkuntza egoki bat. + + + This value is not a valid locale. + Balio hau ez da kokapen egoki bat. + + + This value is not a valid country. + Balio hau ez da herrialde egoki bat. + + + This value is already used. + Balio hau jadanik erabilia izan da. + + + The size of the image could not be detected. + Ezin izan da irudiaren tamaina detektatu. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + Irudiaren zabalera handiegia da ({{ width }}px). Onartutako gehienezko zabalera {{ max_width }}px dira. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + Irudiaren zabalera txikiegia da ({{ width }}px). Onartutako gutxieneko zabalera {{ min_width }}px dira. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + Irudiaren altuera handiegia da ({{ height }}px). Onartutako gehienezko altuera {{ max_height }}px dira. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + Irudiaren altuera txikiegia da ({{ height }}px). Onartutako gutxieneko altuera {{ min_height }}px dira. + + + This value should be the user's current password. + Balio hau uneko erabiltzailearen pasahitza izan beharko litzateke. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Balio honek zehazki karaktere {{ limit }} izan beharko luke.|Balio honek zehazki {{ limit }} karaktere izan beharko lituzke. + + + The file was only partially uploaded. + Fitxategiaren zati bat bakarrik igo da. + + + No file was uploaded. + Ez da fitxategirik igo. + + + No temporary folder was configured in php.ini. + Ez da aldi baterako karpetarik konfiguratu php.ini fitxategian. + + + Cannot write temporary file to disk. + Ezin izan da aldi baterako fitxategia diskoan idatzi. + + + A PHP extension caused the upload to fail. + PHP luzapen batek igoeraren hutsa eragin du. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Bilduma honek gutxienez elementu {{ limit }} eduki beharko luke.|Bilduma honek gutxienez {{ limit }} elementu eduki beharko lituzke. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Bilduma honek gehienez elementu {{ limit }} eduki beharko luke.|Bilduma honek gehienez {{ limit }} elementu eduki beharko lituzke. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Bilduma honek zehazki elementu {{ limit }} eduki beharko luke.|Bilduma honek zehazki {{ limit }} elementu eduki beharko lituzke. + + + Invalid card number. + Txartel zenbaki baliogabea. + + + Unsupported card type or invalid card number. + Txartel mota onartezina edo txartel zenbaki baliogabea. + + + This is not a valid International Bank Account Number (IBAN). + Hau ez da baliozko banku internazionaleko kontu zenbaki (IBAN) bat. + + + This value is not a valid ISBN-10. + Balio hau ez da onartutako ISBN-10 bat. + + + This value is not a valid ISBN-13. + Balio hau ez da onartutako ISBN-13 bat. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Balio hau ez da onartutako ISBN-10 edo ISBN-13 bat. + + + This value is not a valid ISSN. + Balio hau ez da onartutako ISSN bat. + + + This value is not a valid currency. + Balio hau ez da baliozko moneta bat. + + + This value should be equal to {{ compared_value }}. + Balio hau {{ compared_value }}-(r)en berbera izan beharko litzateke. + + + This value should be greater than {{ compared_value }}. + Balio hau {{ compared_value }} baino handiagoa izan beharko litzateke. + + + This value should be greater than or equal to {{ compared_value }}. + Balio hau {{ compared_value }}-(r)en berdina edota handiagoa izan beharko litzateke. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Balio hau {{ compared_value_type }} {{ compared_value }}-(r)en berbera izan beharko litzateke. + + + This value should be less than {{ compared_value }}. + Balio hau {{ compared_value }} baino txikiagoa izan beharko litzateke. + + + This value should be less than or equal to {{ compared_value }}. + Balio hau {{ compared_value }}-(r)en berdina edota txikiagoa izan beharko litzateke. + + + This value should not be equal to {{ compared_value }}. + Balio hau ez litzateke {{ compared_value }}-(r)en berdina izan behar. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Balio hau ez litzateke {{ compared_value_type }} {{ compared_value }}-(r)en berbera izan behar. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Irudiaren proportzioa oso handia da ({{ ratio }}). Onartutako proportzio handienda {{ max_ratio }} da. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + Irudiaren proportzioa oso txikia da ({{ ratio }}). Onartutako proportzio txikiena {{ min_ratio }} da. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + Irudia karratua da ({{ width }}x{{ height }}px). Karratuak diren irudiak ez dira onartzen. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + Irudia horizontalki bideratua dago ({{ width }}x{{ height }}px). Horizontalki bideratutako irudiak ez dira onartzen. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + Irudia bertikalki bideratua dago ({{ width }}x{{ height }}px). Bertikalki bideratutako irudiak ez dira onartzen. + + + An empty file is not allowed. + Hutsik dagoen fitxategia ez da onartzen. + + + The host could not be resolved. + Host-a ezin da ebatzi. + + + This value does not match the expected {{ charset }} charset. + Balio honen karaktere kodea ez da esperotakoa {{ charset }}. + + + This is not a valid Business Identifier Code (BIC). + Ez da balizko Banku Identifikazioko Kodea (BIC). + + + Error + Errore + + + This is not a valid UUID. + Balio hau ez da onartutako UUID bat. + + + This value should be a multiple of {{ compared_value }}. + Balio honek {{ compared_value }}-ren multiploa izan beharko luke. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Banku Identifikazioko Kode hau ez dago lotuta {{ IBAN }} IBAN-rekin. + + + This value should be valid JSON. + Balio honek baliozko JSON bat izan behar luke. + + + This collection should contain only unique elements. + Bilduma honek elementu bakarrak soilik izan beharko lituzke. + + + This value should be positive. + Balio honek positiboa izan beharko luke. + + + This value should be either positive or zero. + Balio honek positiboa edo zero izan behar luke. + + + This value should be negative. + Balio honek negatiboa izan behar luke. + + + This value should be either negative or zero. + Balio honek negatiboa edo zero izan behar luke. + + + This value is not a valid timezone. + Balio hori ez da baliozko ordu-eremua. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Pasahitz hori ezin da erabili, beste gune eta aplikazio batzuetako segurtasun-akatsei esker lortutako pasahitz publikoen zerrendan sartuta dagoelako. Mesedez, erabili beste pasahitz bat. + + + This value should be between {{ min }} and {{ max }}. + Balio honek {{ min }} eta {{ max }} artean egon behar luke. + + + This value is not a valid hostname. + Balio hori ez da ostalari-izen onargarria. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + Bilduma honetako elementu-kopuruak {{ compared_value }}-ren multiploa izan behar luke. + + + This value should satisfy at least one of the following constraints: + Balio honek, gutxienez, murrizketa hauetako bat bete behar du: + + + Each element of this collection should satisfy its own set of constraints. + Bilduma honetako elementu bakoitzak bere murriztapen-multzoa bete behar du. + + + This value is not a valid International Securities Identification Number (ISIN). + Balio hori ez da baliozko baloreen nazioarteko identifikazio-zenbaki bat (ISIN). + + + This value should be a valid expression. + Balio hori baliozko adierazpena izan beharko litzateke. + + + This value is not a valid CSS color. + Balio hori ez da baliozko CSS kolorea. + + + This value is not a valid CIDR notation. + Balio hori ez da baliozko CIDR notazioa. + + + The value of the netmask should be between {{ min }} and {{ max }}. + Maskararen balioa {{ min }} eta {{ max }} artekoa izan beharko litzateke. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.fa.xlf b/src/vendor/symfony/validator/Resources/translations/validators.fa.xlf new file mode 100644 index 0000000..b72bc6e --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.fa.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + این مقدار باید نادرست (False) باشد. + + + This value should be true. + این مقدار باید درست (True) باشد. + + + This value should be of type {{ type }}. + این مقدار باید از نوع {{ type }} باشد. + + + This value should be blank. + این مقدار باید خالی باشد. + + + The value you selected is not a valid choice. + مقدار انتخاب شده یک گزینه معتبر نمی‌باشد. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + شما باید حداقل {{ limit }} گزینه انتخاب نمایید.|شما باید حداقل {{ limit }} گزینه انتخاب نمایید. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + شما باید حداکثر {{ limit }} گزینه انتخاب نمایید.|شما باید حداکثر {{ limit }} گزینه انتخاب نمایید. + + + One or more of the given values is invalid. + یک یا چند مقدار داده شده نامعتبر است. + + + The fields {{ fields }} were not expected. + فیلدهای {{ fields }} مورد انتظار نبود. + + + The fields {{ fields }} are missing. + فیلدهای {{ fields }} مفقود شده اند. + + + This value is not a valid date. + این مقدار یک تاریخ معتبر نمی‌باشد. + + + This value is not a valid datetime. + این مقدار یک تاریخ و زمان معتبر نمی‌باشد. + + + This value is not a valid email address. + این یک آدرس رایانامه (ایمیل) معتبر نمی‌باشد. + + + The file could not be found. + فایل یافت نشد. + + + The file is not readable. + فایل قابل خواندن نیست. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + فایل بیش از اندازه بزرگ است({{ size }} {{ suffix }}). بیشینه (حداکثر) اندازه مجاز برابر با {{ limit }} {{ suffix }} می‌باشد. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + نوع mime این فایل نامعتبر است({{ type }}). انواع mime مجاز {{ types }} هستند. + + + This value should be {{ limit }} or less. + این مقدار باید کوچکتر و یا مساوی {{ limit }} باشد. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + این مقدار بسیار طولانی است. باید دارای {{limit}} کاراکتر یا کمتر باشد. | این مقدار بسیار طولانی است. باید دارای {{limit}} کاراکتر یا کمتر باشد. + + + This value should be {{ limit }} or more. + این مقدار باید بزرگتر و یا مساوی {{ limit }} باشد. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + این مقدار بیش از اندازه کوتاه است. باید {{ limit }} کاراکتر یا بیشتر داشته باشد.|این مقدار بیش از اندازه کوتاه است. باید {{ limit }} کاراکتر یا بیشتر داشته باشد. + + + This value should not be blank. + این مقدار نباید خالی باشد. + + + This value should not be null. + این مقدار نباید خالی باشد. + + + This value should be null. + این مقدار باید خالی باشد. + + + This value is not valid. + این مقدار معتبر نمی‌باشد. + + + This value is not a valid time. + این مقدار یک زمان معتبر نمی‌باشد. + + + This value is not a valid URL. + این مقدار شامل یک URL معتبر نمی‌باشد. + + + The two values should be equal. + دو مقدار باید با یکدیگر برابر باشند. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + فایل بیش از اندازه بزرگ است. بیشینه (حداکثر) اندازه مجاز {{ limit }} {{ suffix }} است. + + + The file is too large. + فایل بیش از اندازه بزرگ است. + + + The file could not be uploaded. + بارگذاری فایل با شکست مواجه گردید. + + + This value should be a valid number. + این مقدار باید یک عدد معتبر باشد. + + + This file is not a valid image. + این فایل یک تصویر معتبر نمی‌باشد. + + + This is not a valid IP address. + این آدرس IP معتبر نیست. + + + This value is not a valid language. + این مقدار یک زبان معتبر نمی‌باشد. + + + This value is not a valid locale. + این مقدار یک محل (locale) معتبر نمی‌باشد. + + + This value is not a valid country. + این مقدار یک کشور معتبر نمی‌باشد. + + + This value is already used. + این مقدار قبلاً استفاده شده است. + + + The size of the image could not be detected. + اندازه تصویر قابل شناسایی نمی‌باشد. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + عرض تصویر بسیار بزرگ است({{ width }}px). بیشینه (حداکثر) عرض مجاز {{ max_width }}px می‌باشد. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + عرض تصویر بسیار کوچک است({{ width }}px). کمینه (حداقل) عرض مورد انتظار {{ min_width }}px می‌باشد. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + ارتفاع تصویر بسیار بزرگ است({{ height }}px). بیشینه (حداکثر) ارتفاع مجاز {{ max_height }}px می‌باشد. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + ارتفاع تصویر بسیار کوچک است({{ height }}px). کمینه (حداقل) ارتفاع مورد انتظار {{ min_height }}px می‌باشد. + + + This value should be the user's current password. + این مقدار باید رمزعبور فعلی کاربر باشد. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + این مقدار باید دقیقا {{ limit }} کاراکتر داشته باشد.| این مقدار باید دقیقا {{ limit }} کاراکتر داشته باشد. + + + The file was only partially uploaded. + فایل به صورت جزئی بارگذاری گردیده است. + + + No file was uploaded. + هیچ فایلی بارگذاری نشد. + + + No temporary folder was configured in php.ini. + پوشه موقتی در php.ini پیکربندی نگردیده است. + + + Cannot write temporary file to disk. + فایل موقتی را نمی‌توان در دیسک نوشت. + + + A PHP extension caused the upload to fail. + یک افزونه PHP باعث شد بارگذاری ناموفق باشد. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + این مجموعه باید حاوی {{ limit }} عنصر یا بیشتر باشد.|این مجموعه باید حاوی {{ limit }} عنصر یا بیشتر باشد. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + این مجموعه باید حاوی {{ limit }} عنصر یا کمتر باشد.|این مجموعه باید حاوی {{ limit }} عنصر یا کمتر باشد. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + این مجموعه باید دقیقا حاوی {{ limit }} عنصر باشد.|این مجموعه باید دقیقا حاوی {{ limit }} عنصر باشد. + + + Invalid card number. + شماره کارت نامعتبر است. + + + Unsupported card type or invalid card number. + نوع کارت پشتیبانی نمی‌شود و یا شماره کارت نامعتبر می‌باشد. + + + This is not a valid International Bank Account Number (IBAN). + این یک شماره حساب بانک بین المللی معتبر نمی‌باشد(IBAN). + + + This value is not a valid ISBN-10. + این مقدار یک ISBN-10 معتبر نمی‌باشد. + + + This value is not a valid ISBN-13. + این مقدار یک ISBN-13 معتبر نمی‌باشد. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + این مقدار یک ISBN-10 معتبر و یا ISBN-13 معتبر نمی‌باشد. + + + This value is not a valid ISSN. + این مقدار یک ISSN معتبر نمی‌باشد. + + + This value is not a valid currency. + این مقدار یک واحد پول معتبر نمی‌باشد. + + + This value should be equal to {{ compared_value }}. + این مقدار باید برابر با {{ compared_value }} باشد. + + + This value should be greater than {{ compared_value }}. + این مقدار باید از {{ compared_value }} بیشتر باشد. + + + This value should be greater than or equal to {{ compared_value }}. + این مقدار باید بزرگتر و یا مساوی با {{ compared_value }} باشد. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + این مقدار باید برابر {{ compared_value_type }} {{ compared_value }} باشد. + + + This value should be less than {{ compared_value }}. + این مقدار باید کمتر از {{ compared_value }} باشد. + + + This value should be less than or equal to {{ compared_value }}. + این مقدار باید کمتر و یا مساوی با {{ compared_value }} باشد. + + + This value should not be equal to {{ compared_value }}. + این مقدار نباید با {{ compared_value }} برابر باشد. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + این مقدار نباید برابر {{ compared_value_type }} {{ compared_value }} باشد. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + ابعاد ({{ ratio }}) عکس بیش از حد بزرگ است. بیشینه (حداکثر) ابعاد مجاز {{ max_ratio }} می‌باشد. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + ابعاد ({{ ratio }}) عکس بیش از حد کوچک است. کمینه (حداقل) ابعاد مورد انتظار {{ min_ratio }} می‌باشد. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + این تصویر یک مربع ({{ width }}x{{ height }}px) می‌باشد. تصاویر مربع شکل مجاز نمی‌باشند. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + این تصویر افقی ({{ width }}x{{ height }}px) می‌باشد. تصاویر افقی مجاز نمی‌باشند. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + این تصویر عمودی ({{ width }}x{{ height }}px) می‌باشد. تصاویر عمودی مجاز نمی‌باشند. + + + An empty file is not allowed. + فایل خالی مجاز نمی‌باشد. + + + The host could not be resolved. + میزبان (Host) شناسایی نشد. + + + This value does not match the expected {{ charset }} charset. + این مقدار مطابق charset مورد انتظار {{ charset }} نمی باشد. + + + This is not a valid Business Identifier Code (BIC). + این مقدار یک کد شناسایی کسب‌و‌کار معتبر (BIC) نیست. + + + Error + خطا + + + This is not a valid UUID. + این مقدار یک UUID معتبر نمی‌باشد. + + + This value should be a multiple of {{ compared_value }}. + این مقدار باید چند برابر {{ compared_value }} باشد. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + این کد شناسایی کسب‌و‌کار (BIC) با شماره حساب بانکی بین‌المللی (IBAN) {{ iban }} مرتبط نیست. + + + This value should be valid JSON. + این مقدار باید یک JSON معتبر باشد. + + + This collection should contain only unique elements. + این مجموعه باید فقط حاوی عناصر یکتا باشد. + + + This value should be positive. + این مقدار باید مثبت باشد. + + + This value should be either positive or zero. + این مقدار باید مثبت یا صفر باشد. + + + This value should be negative. + این مقدار باید منفی باشد. + + + This value should be either negative or zero. + این مقدار باید منفی یا صفر باشد. + + + This value is not a valid timezone. + این مقدار یک منطقه‌زمانی (timezone) معتبر نیست. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + این رمزعبور در یک رخنه‌ی اطلاعاتی نشت کرده است. لطفاً از یک رمزعبور دیگر استفاده کنید. + + + This value should be between {{ min }} and {{ max }}. + این مقدار باید بین {{ min }} و {{ max }} باشد + + + This value is not a valid hostname. + این مقدار یک hostname معتبر نیست. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + تعداد عناصر این مجموعه باید ضریبی از {{ compared_value }} باشد. + + + This value should satisfy at least one of the following constraints: + این مقدار باید حداقل یکی از محدودیت‌های زیر را ارضا کند: + + + Each element of this collection should satisfy its own set of constraints. + هر یک از عناصر این مجموعه باید دسته محدودیت‌های خودش را ارضا کند. + + + This value is not a valid International Securities Identification Number (ISIN). + این مقدار یک شماره شناسایی بین‌المللی اوراق بهادار (ISIN) معتبر نیست. + + + This value should be a valid expression. + این مقدار باید یک عبارت معتبر باشد. + + + This value is not a valid CSS color. + این مقدار یک رنگ معتبر در CSS نیست. + + + This value is not a valid CIDR notation. + این مقدار یک نماد معتبر در CIDR نیست. + + + The value of the netmask should be between {{ min }} and {{ max }}. + مقدار ماسک شبکه (NetMask) باید بین {{ min }} و {{ max }} باشد. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.fi.xlf b/src/vendor/symfony/validator/Resources/translations/validators.fi.xlf new file mode 100644 index 0000000..9a6bfe4 --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.fi.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + Arvon tulee olla epätosi. + + + This value should be true. + Arvon tulee olla tosi. + + + This value should be of type {{ type }}. + Arvon tulee olla tyyppiä {{ type }}. + + + This value should be blank. + Arvon tulee olla tyhjä. + + + The value you selected is not a valid choice. + Arvon tulee olla yksi annetuista vaihtoehdoista. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Sinun tulee valita vähintään {{ limit }} vaihtoehtoa. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Sinun tulee valitan enintään {{ limit }} vaihtoehtoa. + + + One or more of the given values is invalid. + Yksi tai useampi annetuista arvoista on virheellinen. + + + This field was not expected. + Tässä kentässä ei odotettu. + + + This field is missing. + Tämä kenttä puuttuu. + + + This value is not a valid date. + Annettu arvo ei ole kelvollinen päivämäärä. + + + This value is not a valid datetime. + Annettu arvo ei ole kelvollinen päivämäärä ja kellonaika. + + + This value is not a valid email address. + Annettu arvo ei ole kelvollinen sähköpostiosoite. + + + The file could not be found. + Tiedostoa ei löydy. + + + The file is not readable. + Tiedostoa ei voida lukea. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + Tiedostonkoko ({{ size }} {{ suffix }}) on liian iso. Suurin sallittu tiedostonkoko on {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + Tiedostotyyppi ({{ type }}) on virheellinen. Sallittuja tiedostotyyppejä ovat {{ types }}. + + + This value should be {{ limit }} or less. + Arvon tulee olla {{ limit }} tai vähemmän. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Liian pitkä syöte. Syöte saa olla enintään {{ limit }} merkkiä. + + + This value should be {{ limit }} or more. + Arvon tulee olla {{ limit }} tai enemmän. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Liian lyhyt syöte. Syötteen tulee olla vähintään {{ limit }} merkkiä. + + + This value should not be blank. + Kenttä ei voi olla tyhjä. + + + This value should not be null. + Syöte ei voi olla null. + + + This value should be null. + Syötteen tulee olla null. + + + This value is not valid. + Virheellinen arvo. + + + This value is not a valid time. + Annettu arvo ei ole kelvollinen kellonaika. + + + This value is not a valid URL. + Annettu arvo ei ole kelvollinen URL-osoite. + + + The two values should be equal. + Kahden annetun arvon tulee olla samat. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + Annettu tiedosto on liian iso. Suurin sallittu tiedostokoko on {{ limit }} {{ suffix }}. + + + The file is too large. + Tiedosto on liian iso. + + + The file could not be uploaded. + Tiedoston siirto epäonnistui. + + + This value should be a valid number. + Tämän arvon tulee olla numero. + + + This file is not a valid image. + Tämä tiedosto ei ole kelvollinen kuva. + + + This is not a valid IP address. + Tämä ei ole kelvollinen IP-osoite. + + + This value is not a valid language. + Tämä arvo ei ole kelvollinen kieli. + + + This value is not a valid locale. + Tämä arvo ei ole kelvollinen kieli- ja alueasetus (locale). + + + This value is not a valid country. + Tämä arvo ei ole kelvollinen maa. + + + This value is already used. + Tämä arvo on jo käytetty. + + + The size of the image could not be detected. + Kuvan kokoa ei voitu tunnistaa. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + Kuva on liian leveä ({{ width }}px). Sallittu maksimileveys on {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + Kuva on liian kapea ({{ width }}px). Leveyden tulisi olla vähintään {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + Kuva on liian korkea ({{ width }}px). Sallittu maksimikorkeus on {{ max_width }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + Kuva on liian matala ({{ height }}px). Korkeuden tulisi olla vähintään {{ min_height }}px. + + + This value should be the user's current password. + Tämän arvon tulisi olla käyttäjän tämänhetkinen salasana. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Tämän arvon tulisi olla tasan yhden merkin pituinen.|Tämän arvon tulisi olla tasan {{ limit }} merkkiä pitkä. + + + The file was only partially uploaded. + Tiedosto ladattiin vain osittain. + + + No file was uploaded. + Tiedostoa ei ladattu. + + + No temporary folder was configured in php.ini. + Väliaikaishakemistoa ei ole asetettu php.ini -tiedostoon. + + + Cannot write temporary file to disk. + Väliaikaistiedostoa ei voitu kirjoittaa levylle. + + + A PHP extension caused the upload to fail. + PHP-laajennoksen vuoksi tiedoston lataus epäonnistui. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Tässä ryhmässä tulisi olla yksi tai useampi elementti.|Tässä ryhmässä tulisi olla vähintään {{ limit }} elementtiä. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Tässä ryhmässä tulisi olla enintään yksi elementti.|Tässä ryhmässä tulisi olla enintään {{ limit }} elementtiä. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Tässä ryhmässä tulisi olla tasan yksi elementti.|Tässä ryhmässä tulisi olla enintään {{ limit }} elementtiä. + + + Invalid card number. + Virheellinen korttinumero. + + + Unsupported card type or invalid card number. + Tätä korttityyppiä ei tueta tai korttinumero on virheellinen. + + + This is not a valid International Bank Account Number (IBAN). + Arvo ei ole kelvollinen kansainvälinen pankkitilinumero (IBAN). + + + This value is not a valid ISBN-10. + Arvo ei ole kelvollinen ISBN-10. + + + This value is not a valid ISBN-13. + Arvo ei ole kelvollinen ISBN-13. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Arvo ei ole kelvollinen ISBN-10 tai kelvollinen ISBN-13. + + + This value is not a valid ISSN. + Arvo ei ole kelvollinen ISSN. + + + This value is not a valid currency. + Arvo ei ole kelvollinen valuutta. + + + This value should be equal to {{ compared_value }}. + Arvo ei ole sama kuin {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + Arvon tulee olla suurempi kuin {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + Arvon tulee olla suurempi tai yhtä suuri kuin {{ compared_value }}. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Tämä arvo tulee olla sama kuin {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Arvon tulee olla pienempi kuin {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + Arvon tulee olla pienempi tai yhtä suuri {{ compared_value }}. + + + This value should not be equal to {{ compared_value }}. + Arvon ei tule olla sama kuin {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Tämä arvo ei tule olla sama kuin {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Kuvasuhde on liian suuri ({{ ratio }}). Suurin sallittu suhde on {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + Kuvasuhde on liian pieni ({{ ratio }}). Pienin sallittu arvo on {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + Kuva on neliä ({{ width }}x{{ height }}px). Neliöt kuvat eivät ole sallittuja. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + Kuva on vaakasuuntainen ({{ width }}x{{ height }}px). Vaakasuuntaiset kuvat eivät ole sallittuja. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + Kuva on pystysuuntainen ({{ width }}x{{ height }}px). Pystysuuntaiset kuvat eivät ole sallittuja. + + + An empty file is not allowed. + Tyhjä tiedosto ei ole sallittu. + + + The host could not be resolved. + Palvelimeen ei saatu yhteyttä. + + + This value does not match the expected {{ charset }} charset. + Arvo ei vastaa odotettua merkistöä {{ charset }}. + + + This is not a valid Business Identifier Code (BIC). + Arvo ei ole kelvollinen yritystunnus (BIC). + + + Error + Virhe + + + This is not a valid UUID. + Arvo ei ole kelvollinen UUID. + + + This value should be a multiple of {{ compared_value }}. + Tämän arvon tulisi olla kerrannainen {{ compared_value }}. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Tämä yritystunnus (BIC) ei ole liitetty IBAN {{ iban }}. + + + This value should be valid JSON. + Arvon tulee olla kelvollinen JSON. + + + This collection should contain only unique elements. + Tämän ryhmän tulisi sisältää vain yksilöllisiä arvoja. + + + This value should be positive. + Arvon tulisi olla positiivinen. + + + This value should be either positive or zero. + Arvon tulisi olla joko positiivinen tai nolla. + + + This value should be negative. + Arvon tulisi olla negatiivinen. + + + This value should be either negative or zero. + Arvon tulisi olla joko negatiivinen tai nolla. + + + This value is not a valid timezone. + Arvo ei ole kelvollinen aikavyöhyke. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Tämä salasana on vuotanut tietomurrossa, sitä ei saa käyttää. Käytä toista salasanaa. + + + This value should be between {{ min }} and {{ max }}. + Arvon tulisi olla välillä {{ min }} - {{ max }}. + + + This value is not a valid hostname. + Arvo ei ole kelvollinen laitenimi (hostname). + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + Ryhmässä olevien elementtien määrän pitää olla monikerta luvulle {{ compared_value }}. + + + This value should satisfy at least one of the following constraints: + Tämän arvon tulee läpäistä vähintään yksi seuraavista tarkistuksista: + + + Each element of this collection should satisfy its own set of constraints. + Ryhmän jokaisen elementin tulee läpäistä omat tarkistuksensa. + + + This value is not a valid International Securities Identification Number (ISIN). + Tämä arvo ei ole kelvollinen ISIN-koodi (International Securities Identification Number). + + + This value should be a valid expression. + Tämän arvon on oltava kelvollinen lauseke. + + + This value is not a valid CSS color. + Tämä arvo ei ole kelvollinen CSS-värimääritys. + + + This value is not a valid CIDR notation. + Tämä arvo ei ole kelvollinen CIDR-merkintä. + + + The value of the netmask should be between {{ min }} and {{ max }}. + Verkkomaskille annetun arvon tulisi olla {{ min }} ja {{ max }} välillä. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.fr.xlf b/src/vendor/symfony/validator/Resources/translations/validators.fr.xlf new file mode 100644 index 0000000..a118689 --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.fr.xlf @@ -0,0 +1,431 @@ + + + + + + This value should be false. + Cette valeur doit être fausse. + + + This value should be true. + Cette valeur doit être vraie. + + + This value should be of type {{ type }}. + Cette valeur doit être de type {{ type }}. + + + This value should be blank. + Cette valeur doit être vide. + + + The value you selected is not a valid choice. + Cette valeur doit être l'un des choix proposés. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Vous devez sélectionner au moins {{ limit }} choix.|Vous devez sélectionner au moins {{ limit }} choix. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Vous devez sélectionner au maximum {{ limit }} choix.|Vous devez sélectionner au maximum {{ limit }} choix. + + + One or more of the given values is invalid. + Une ou plusieurs des valeurs soumises sont invalides. + + + This field was not expected. + Ce champ n'a pas été prévu. + + + This field is missing. + Ce champ est manquant. + + + This value is not a valid date. + Cette valeur n'est pas une date valide. + + + This value is not a valid datetime. + Cette valeur n'est pas une date valide. + + + This value is not a valid email address. + Cette valeur n'est pas une adresse email valide. + + + The file could not be found. + Le fichier n'a pas été trouvé. + + + The file is not readable. + Le fichier n'est pas lisible. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + Le fichier est trop volumineux ({{ size }} {{ suffix }}). Sa taille ne doit pas dépasser {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + Le type du fichier est invalide ({{ type }}). Les types autorisés sont {{ types }}. + + + This value should be {{ limit }} or less. + Cette valeur doit être inférieure ou égale à {{ limit }}. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Cette chaîne est trop longue. Elle doit avoir au maximum {{ limit }} caractère.|Cette chaîne est trop longue. Elle doit avoir au maximum {{ limit }} caractères. + + + This value should be {{ limit }} or more. + Cette valeur doit être supérieure ou égale à {{ limit }}. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Cette chaîne est trop courte. Elle doit avoir au minimum {{ limit }} caractère.|Cette chaîne est trop courte. Elle doit avoir au minimum {{ limit }} caractères. + + + This value should not be blank. + Cette valeur ne doit pas être vide. + + + This value should not be null. + Cette valeur ne doit pas être nulle. + + + This value should be null. + Cette valeur doit être nulle. + + + This value is not valid. + Cette valeur n'est pas valide. + + + This value is not a valid time. + Cette valeur n'est pas une heure valide. + + + This value is not a valid URL. + Cette valeur n'est pas une URL valide. + + + The two values should be equal. + Les deux valeurs doivent être identiques. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + Le fichier est trop volumineux. Sa taille ne doit pas dépasser {{ limit }} {{ suffix }}. + + + The file is too large. + Le fichier est trop volumineux. + + + The file could not be uploaded. + Le téléchargement de ce fichier est impossible. + + + This value should be a valid number. + Cette valeur doit être un nombre. + + + This file is not a valid image. + Ce fichier n'est pas une image valide. + + + This is not a valid IP address. + Cette adresse IP n'est pas valide. + + + This value is not a valid language. + Cette langue n'est pas valide. + + + This value is not a valid locale. + Ce paramètre régional n'est pas valide. + + + This value is not a valid country. + Ce pays n'est pas valide. + + + This value is already used. + Cette valeur est déjà utilisée. + + + The size of the image could not be detected. + La taille de l'image n'a pas pu être détectée. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + La largeur de l'image est trop grande ({{ width }}px). La largeur maximale autorisée est de {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + La largeur de l'image est trop petite ({{ width }}px). La largeur minimale attendue est de {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + La hauteur de l'image est trop grande ({{ height }}px). La hauteur maximale autorisée est de {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + La hauteur de l'image est trop petite ({{ height }}px). La hauteur minimale attendue est de {{ min_height }}px. + + + This value should be the user's current password. + Cette valeur doit être le mot de passe actuel de l'utilisateur. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Cette chaîne doit avoir exactement {{ limit }} caractère.|Cette chaîne doit avoir exactement {{ limit }} caractères. + + + The file was only partially uploaded. + Le fichier a été partiellement transféré. + + + No file was uploaded. + Aucun fichier n'a été transféré. + + + No temporary folder was configured in php.ini. + Aucun répertoire temporaire n'a été configuré dans le php.ini, ou le répertoire configuré n'existe pas. + + + Cannot write temporary file to disk. + Impossible d'écrire le fichier temporaire sur le disque. + + + A PHP extension caused the upload to fail. + Une extension PHP a empêché le transfert du fichier. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Cette collection doit contenir {{ limit }} élément ou plus.|Cette collection doit contenir {{ limit }} éléments ou plus. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Cette collection doit contenir {{ limit }} élément ou moins.|Cette collection doit contenir {{ limit }} éléments ou moins. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Cette collection doit contenir exactement {{ limit }} élément.|Cette collection doit contenir exactement {{ limit }} éléments. + + + Invalid card number. + Numéro de carte invalide. + + + Unsupported card type or invalid card number. + Type de carte non supporté ou numéro invalide. + + + This is not a valid International Bank Account Number (IBAN). + Le numéro IBAN (International Bank Account Number) saisi n'est pas valide. + + + This value is not a valid ISBN-10. + Cette valeur n'est pas un code ISBN-10 valide. + + + This value is not a valid ISBN-13. + Cette valeur n'est pas un code ISBN-13 valide. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Cette valeur n'est ni un code ISBN-10, ni un code ISBN-13 valide. + + + This value is not a valid ISSN. + Cette valeur n'est pas un code ISSN valide. + + + This value is not a valid currency. + Cette valeur n'est pas une devise valide. + + + This value should be equal to {{ compared_value }}. + Cette valeur doit être égale à {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + Cette valeur doit être supérieure à {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + Cette valeur doit être supérieure ou égale à {{ compared_value }}. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Cette valeur doit être identique à {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Cette valeur doit être inférieure à {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + Cette valeur doit être inférieure ou égale à {{ compared_value }}. + + + This value should not be equal to {{ compared_value }}. + Cette valeur ne doit pas être égale à {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Cette valeur ne doit pas être identique à {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Le rapport largeur/hauteur de l'image est trop grand ({{ ratio }}). Le rapport maximal autorisé est {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + Le rapport largeur/hauteur de l'image est trop petit ({{ ratio }}). Le rapport minimal attendu est {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + L'image est carrée ({{ width }}x{{ height }}px). Les images carrées ne sont pas autorisées. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + L'image est au format paysage ({{ width }}x{{ height }}px). Les images au format paysage ne sont pas autorisées. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + L'image est au format portrait ({{ width }}x{{ height }}px). Les images au format portrait ne sont pas autorisées. + + + An empty file is not allowed. + Un fichier vide n'est pas autorisé. + + + The host could not be resolved. + Le nom de domaine n'a pas pu être résolu. + + + This value does not match the expected {{ charset }} charset. + Cette valeur ne correspond pas au jeu de caractères {{ charset }} attendu. + + + This is not a valid Business Identifier Code (BIC). + Ce n'est pas un code universel d'identification des banques (BIC) valide. + + + Error + Erreur + + + This is not a valid UUID. + Ceci n'est pas un UUID valide. + + + This value should be a multiple of {{ compared_value }}. + Cette valeur doit être un multiple de {{ compared_value }}. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Ce code d'identification d'entreprise (BIC) n'est pas associé à l'IBAN {{ iban }}. + + + This value should be valid JSON. + Cette valeur doit être un JSON valide. + + + This collection should contain only unique elements. + Cette collection ne doit pas comporter de doublons. + + + This value should be positive. + Cette valeur doit être strictement positive. + + + This value should be either positive or zero. + Cette valeur doit être supérieure ou égale à zéro. + + + This value should be negative. + Cette valeur doit être strictement négative. + + + This value should be either negative or zero. + Cette valeur doit être inférieure ou égale à zéro. + + + This value is not a valid timezone. + Cette valeur n'est pas un fuseau horaire valide. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Ce mot de passe a été divulgué lors d'une fuite de données, il ne doit plus être utilisé. Veuillez utiliser un autre mot de passe. + + + This value should be between {{ min }} and {{ max }}. + Cette valeur doit être comprise entre {{ min }} et {{ max }}. + + + This value is not a valid hostname. + Cette valeur n'est pas un nom d'hôte valide. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + Le nombre d'éléments de cette collection doit être un multiple de {{ compared_value }}. + + + This value should satisfy at least one of the following constraints: + Cette valeur doit satisfaire à au moins une des contraintes suivantes : + + + Each element of this collection should satisfy its own set of constraints. + Chaque élément de cette collection doit satisfaire à son propre jeu de contraintes. + + + This value is not a valid International Securities Identification Number (ISIN). + Cette valeur n'est pas un code international de sécurité valide (ISIN). + + + This value should be a valid expression. + Cette valeur doit être une expression valide. + + + This value is not a valid CSS color. + Cette valeur n'est pas une couleur CSS valide. + + + This value is not a valid CIDR notation. + Cette valeur n'est pas une notation CIDR valide. + + + The value of the netmask should be between {{ min }} and {{ max }}. + La valeur du masque de réseau doit être comprise entre {{ min }} et {{ max }}. + + + The filename is too long. It should have {{ filename_max_length }} character or less.|The filename is too long. It should have {{ filename_max_length }} characters or less. + Le nom du fichier est trop long. Il doit contenir au maximum {{ filename_max_length }} caractère.|Le nom de fichier est trop long. Il doit contenir au maximum {{ filename_max_length }} caractères. + + + The password strength is too low. Please use a stronger password. + La force du mot de passe est trop faible. Veuillez utiliser un mot de passe plus fort. + + + This value contains characters that are not allowed by the current restriction-level. + Cette valeur contient des caractères qui ne sont pas autorisés par le niveau de restriction actuel. + + + Using invisible characters is not allowed. + Utiliser des caractères invisibles n'est pas autorisé. + + + Mixing numbers from different scripts is not allowed. + Mélanger des chiffres provenant de différents scripts n'est pas autorisé. + + + Using hidden overlay characters is not allowed. + Utiliser des caractères de superposition cachés n'est pas autorisé. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.gl.xlf b/src/vendor/symfony/validator/Resources/translations/validators.gl.xlf new file mode 100644 index 0000000..f8c5c04 --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.gl.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + Este valor debería ser falso. + + + This value should be true. + Este valor debería ser verdadeiro. + + + This value should be of type {{ type }}. + Este valor debería ser de tipo {{ type }}. + + + This value should be blank. + Este valor debería estar baleiro. + + + The value you selected is not a valid choice. + O valor seleccionado non é unha opción válida. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Debe seleccionar polo menos {{ limit }} opción.|Debe seleccionar polo menos {{ limit }} opcions. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Debe seleccionar como máximo {{ limit }} opción.|Debe seleccionar como máximo {{ limit }} opcions. + + + One or more of the given values is invalid. + Un ou máis dos valores indicados non son válidos. + + + This field was not expected. + Este campo non era esperado. + + + This field is missing. + Este campo falta. + + + This value is not a valid date. + Este valor non é unha data válida. + + + This value is not a valid datetime. + Este valor non é unha data e hora válidas. + + + This value is not a valid email address. + Este valor non é unha dirección de correo electrónico válida. + + + The file could not be found. + Non se puido atopar o arquivo. + + + The file is not readable. + O arquivo non se pode ler. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + O arquivo é demasiado grande ({{ size }} {{ suffix }}). O tamaño máximo permitido é {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + O tipo mime do arquivo non é válido ({{ type }}). Os tipos mime válidos son {{ types }}. + + + This value should be {{ limit }} or less. + Este valor debería ser {{ limit }} ou menos. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Este valor é demasiado longo. Debería ter {{ limit }} carácter ou menos.|Este valor é demasiado longo. Debería ter {{ limit }} caracteres ou menos. + + + This value should be {{ limit }} or more. + Este valor debería ser {{ limit }} ou máis. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Este valor é demasiado curto. Debería ter {{ limit }} carácter ou máis.|Este valor é demasiado corto. Debería ter {{ limit }} caracteres ou máis. + + + This value should not be blank. + Este valor non debería estar baleiro. + + + This value should not be null. + Este valor non debería ser null. + + + This value should be null. + Este valor debería ser null. + + + This value is not valid. + Este valor non é válido. + + + This value is not a valid time. + Este valor non é unha hora válida. + + + This value is not a valid URL. + Este valor non é unha URL válida. + + + The two values should be equal. + Os dous valores deberían ser iguais. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + O arquivo é demasiado grande. O tamaño máximo permitido é {{ limit }} {{ suffix }}. + + + The file is too large. + O arquivo é demasiado grande. + + + The file could not be uploaded. + No se puido cargar o arquivo. + + + This value should be a valid number. + Este valor debería ser un número válido. + + + This file is not a valid image. + O arquivo non é unha imaxe válida. + + + This is not a valid IP address. + Isto non é unha dirección IP válida. + + + This value is not a valid language. + Este valor non é un idioma válido. + + + This value is not a valid locale. + Este valor non é unha localización válida. + + + This value is not a valid country. + Este valor non é un país válido. + + + This value is already used. + Este valor xa está a ser empregado. + + + The size of the image could not be detected. + Non se puido determinar o tamaño da imaxe. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + A largura da imaxe é demasiado grande ({{ width }}px). A largura máxima permitida son {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + A largura da imaxe é demasiado pequena ({{ width }}px). A largura mínima requerida son {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + A altura da imaxe é demasiado grande ({{ height }}px). A altura máxima permitida son {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + A altura da imaxe é demasiado pequena ({{ height }}px). A altura mínima requerida son {{ min_height }}px. + + + This value should be the user's current password. + Este valor debería ser a contrasinal actual do usuario. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Este valor debería ter exactamente {{ limit }} carácter.|Este valor debería ter exactamente {{ limit }} caracteres. + + + The file was only partially uploaded. + O arquivo foi só subido parcialmente. + + + No file was uploaded. + Non se subiu ningún arquivo. + + + No temporary folder was configured in php.ini. + Ningunha carpeta temporal foi configurada en php.ini, ou a carpeta non existe. + + + Cannot write temporary file to disk. + Non se puido escribir o arquivo temporal no disco. + + + A PHP extension caused the upload to fail. + Unha extensión de PHP provocou que a subida fallara. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Esta colección debe conter {{ limit }} elemento ou máis.|Esta colección debe conter {{ limit }} elementos ou máis. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Esta colección debe conter {{ limit }} elemento ou menos.|Esta colección debe conter {{ limit }} elementos ou menos. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Esta colección debe conter exactamente {{ limit }} elemento.|Esta colección debe conter exactamente {{ limit }} elementos. + + + Invalid card number. + Número de tarxeta non válido. + + + Unsupported card type or invalid card number. + Tipo de tarxeta non soportado ou número de tarxeta non válido. + + + This is not a valid International Bank Account Number (IBAN). + Este valor non é un International Bank Account Number (IBAN) válido. + + + This value is not a valid ISBN-10. + Este valor non é un ISBN-10 válido. + + + This value is not a valid ISBN-13. + Este valor non é un ISBN-13 válido. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Este valor non é nin un ISBN-10 válido nin un ISBN-13 válido. + + + This value is not a valid ISSN. + Este valor non é un ISSN válido. + + + This value is not a valid currency. + Este valor non é unha moeda válida. + + + This value should be equal to {{ compared_value }}. + Este valor debería ser igual a {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + Este valor debería ser maior que {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + Este valor debería ser maior ou igual que {{ compared_value }}. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Este valor debería ser identico a {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Este valor debería ser menor que {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + Este valor debería ser menor ou igual que {{ compared_value }}. + + + This value should not be equal to {{ compared_value }}. + Este valor non debería ser igual a {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Este valor non debería ser identico a {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + A proporción da imaxe é demasiado grande ({{ ratio }}). A proporción máxima permitida é {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + A proporción da é demasiado pequena ({{ ratio }}). A proporción mínima permitida é {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + A imaxe é cadrada ({{ width }}x{{ height }}px). As imáxenes cadradas non están permitidas. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + A imaxe está orientada horizontalmente ({{ width }}x{{ height }}px). As imáxenes orientadas horizontalmente non están permitidas. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + A imaxe está orientada verticalmente ({{ width }}x{{ height }}px). As imáxenes orientadas verticalmente non están permitidas. + + + An empty file is not allowed. + Non está permitido un arquivo baleiro. + + + The host could not be resolved. + Non se puido resolver o host. + + + This value does not match the expected {{ charset }} charset. + A codificación de caracteres para este valor debería ser {{ charset }}. + + + This is not a valid Business Identifier Code (BIC). + Non é un Código de Identificación Bancaria (BIC) válido. + + + Error + Erro + + + This is not a valid UUID. + Isto non é un UUID válido. + + + This value should be a multiple of {{ compared_value }}. + Este valor debería ser multiplo de {{ compared_value }}. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Este Código de identificación bancaria (BIC) non está asociado co IBAN {{ iban }}. + + + This value should be valid JSON. + Este valor debería ser un JSON válido. + + + This collection should contain only unique elements. + Esta colección só debería ter elementos únicos. + + + This value should be positive. + Este valor debería ser positivo. + + + This value should be either positive or zero. + Este valor debe ser positivo ou igual a cero. + + + This value should be negative. + Este valor debe ser negativo. + + + This value should be either negative or zero. + Este valor debe ser negativo ou igual a cero. + + + This value is not a valid timezone. + Este valor non é unha zona horaria válida. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Este contrasinal non se pode usar porque está incluído nunha lista de contrasinais públicos obtidos grazas a fallos de seguridade noutros sitios e aplicacións. Utiliza outro contrasinal. + + + This value should be between {{ min }} and {{ max }}. + Este valor debe estar comprendido entre {{ min }} e {{ max }}. + + + This value is not a valid hostname. + Este valor non é un nome de host válido. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + O número de elementos desta colección debería ser múltiplo de {{compare_value}}. + + + This value should satisfy at least one of the following constraints: + Este valor debe cumprir polo menos unha das seguintes restricións: + + + Each element of this collection should satisfy its own set of constraints. + Cada elemento desta colección debe satisfacer o seu propio conxunto de restricións. + + + This value is not a valid International Securities Identification Number (ISIN). + Este valor non é un número de identificación de valores internacionais (ISIN) válido. + + + This value should be a valid expression. + Este valor debe ser unha expresión válida. + + + This value is not a valid CSS color. + Este valor non é unha cor CSS válida. + + + This value is not a valid CIDR notation. + Este valor non ten unha notación CIDR válida. + + + The value of the netmask should be between {{ min }} and {{ max }}. + O valor da máscara de rede debería estar entre {{ min }} e {{ max }}. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.he.xlf b/src/vendor/symfony/validator/Resources/translations/validators.he.xlf new file mode 100644 index 0000000..af82426 --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.he.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + הערך צריך להיות שקר. + + + This value should be true. + הערך צריך להיות אמת. + + + This value should be of type {{ type }}. + הערך צריך להיות מסוג {{ type }}. + + + This value should be blank. + הערך צריך להיות ריק. + + + The value you selected is not a valid choice. + הערך שבחרת אינו חוקי. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + אתה צריך לבחור לפחות {{ limit }} אפשרויות.|אתה צריך לבחור לפחות {{ limit }} אפשרויות. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + אתה צריך לבחור לכל היותר {{ limit }} אפשרויות.|אתה צריך לבחור לכל היותר {{ limit }} אפשרויות. + + + One or more of the given values is invalid. + אחד או יותר מהערכים אינו חוקי. + + + This field was not expected. + שדה זה לא היה צפוי + + + This field is missing. + שדה זה חסר. + + + This value is not a valid date. + הערך אינו תאריך חוקי. + + + This value is not a valid datetime. + הערך אינו תאריך ושעה חוקיים. + + + This value is not a valid email address. + כתובת המייל אינה תקינה. + + + The file could not be found. + הקובץ לא נמצא. + + + The file is not readable. + לא ניתן לקרוא את הקובץ. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + הקובץ גדול מדי ({{ size }} {{ suffix }}). הגודל המרבי המותר הוא {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + סוג MIME של הקובץ אינו חוקי ({{ type }}). מותרים סוגי MIME {{ types }}. + + + This value should be {{ limit }} or less. + הערך צריך להכיל {{ limit }} תווים לכל היותר. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + הערך ארוך מידי. הוא צריך להכיל {{ limit }} תווים לכל היותר.|הערך ארוך מידי. הוא צריך להכיל {{ limit }} תווים לכל היותר. + + + This value should be {{ limit }} or more. + הערך צריך להכיל {{ limit }} תווים לפחות. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + הערך קצר מידי. הוא צריך להכיל {{ limit }} תווים לפחות.|הערך קצר מידי. הערך צריך להכיל {{ limit }} תווים לפחות. + + + This value should not be blank. + הערך לא אמור להיות ריק. + + + This value should not be null. + הערך לא אמור להיות ריק. + + + This value should be null. + הערך צריך להיות ריק. + + + This value is not valid. + הערך אינו חוקי. + + + This value is not a valid time. + הערך אינו זמן תקין. + + + This value is not a valid URL. + זאת אינה כתובת אתר תקינה. + + + The two values should be equal. + שני הערכים צריכים להיות שווים. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + הקובץ גדול מדי. הגודל המרבי המותר הוא {{ limit }} {{ suffix }}. + + + The file is too large. + הקובץ גדול מדי. + + + The file could not be uploaded. + לא ניתן לעלות את הקובץ. + + + This value should be a valid number. + הערך צריך להיות מספר חוקי. + + + This file is not a valid image. + הקובץ הזה אינו תמונה תקינה. + + + This is not a valid IP address. + זו אינה כתובת IP חוקית. + + + This value is not a valid language. + הערך אינו שפה חוקית. + + + This value is not a valid locale. + הערך אינו אזור תקף. + + + This value is not a valid country. + הערך אינו ארץ חוקית. + + + This value is already used. + הערך כבר בשימוש. + + + The size of the image could not be detected. + לא ניתן לקבוע את גודל התמונה. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + רוחב התמונה גדול מדי ({{ width }}px). הרוחב המקסימלי הוא {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + רוחב התמונה קטן מדי ({{ width }}px). הרוחב המינימלי הוא {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + גובה התמונה גדול מדי ({{ height }}px). הגובה המקסימלי הוא {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + גובה התמונה קטן מדי ({{ height }}px). הגובה המינימלי הוא {{ min_height }}px. + + + This value should be the user's current password. + הערך צריך להיות סיסמת המשתמש הנוכחי. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + הערך צריך להיות בדיוק {{ limit }} תווים.|הערך צריך להיות בדיוק {{ limit }} תווים. + + + The file was only partially uploaded. + הקובץ הועלה באופן חלקי. + + + No file was uploaded. + הקובץ לא הועלה. + + + No temporary folder was configured in php.ini. + לא הוגדרה תיקייה זמנית ב php.ini. + + + Cannot write temporary file to disk. + לא ניתן לכתוב קובץ זמני לדיסק. + + + A PHP extension caused the upload to fail. + סיומת PHP גרם להעלאה להיכשל. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + האוסף אמור להכיל {{ limit }} אלמנטים או יותר.|האוסף אמור להכיל {{ limit }} אלמנטים או יותר. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + האוסף אמור להכיל {{ limit }} אלמנטים או פחות.|האוסף אמור להכיל {{ limit }} אלמנטים או פחות. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + האוסף צריך להכיל בדיוק {{ limit }} אלמנטים.|האוסף צריך להכיל בדיוק {{ limit }} אלמנטים. + + + Invalid card number. + מספר הכרטיס אינו חוקי. + + + Unsupported card type or invalid card number. + סוג הכרטיס אינו נתמך או לא חוקי. + + + This is not a valid International Bank Account Number (IBAN). + מספר חשבון בנק בינלאומי אינו חוקי (IBAN). + + + This value is not a valid ISBN-10. + הערך אינו ערך ISBN-10 חוקי. + + + This value is not a valid ISBN-13. + הערך אינו ערך ISBN-13 חוקי. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + הערך אינו ערך ISBN-10 חוקי או ערך ISBN-13 חוקי. + + + This value is not a valid ISSN. + הערך אינו ערך ISSN חוקי. + + + This value is not a valid currency. + הערך אינו ערך מטבע חוקי. + + + This value should be equal to {{ compared_value }}. + הערך חייב להיות שווה ל {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + הערך חייב להיות גדול מ {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + הערך חייב להיות גדול או שווה ל {{ compared_value }}. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + הערך חייב להיות זהה ל {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + הערך חייב להיות קטן מ {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + הערך חייב להיות קטן או שווה ל {{ compared_value }}. + + + This value should not be equal to {{ compared_value }}. + הערך חייב להיות לא שווה ל {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + הערך חייב להיות לא זהה ל {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + היחס של התמונה הוא גדול מדי ({{ ratio }}). היחס המקסימלי האפשרי הוא {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + היחס של התמונה הוא קטן מדי ({{ ratio }}). היחס המינימלי האפשרי הוא {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + התמונה מרובעת ({{ width }}x{{ height }}px). אסורות תמונות מרובעות. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + התמונה היא לרוחב ({{ width }}x{{ height }}px). אסורות תמונות לרוחב. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + התמונה היא לאורך ({{ width }}x{{ height }}px). אסורות תמונות לאורך. + + + An empty file is not allowed. + אסור קובץ ריק. + + + The host could not be resolved. + לא הייתה אפשרות לזהות את המארח. + + + This value does not match the expected {{ charset }} charset. + הערך אינו תואם למערך התווים {{ charset }} הצפוי. + + + This is not a valid Business Identifier Code (BIC). + קוד זיהוי עסקי אינו חוקי (BIC). + + + Error + שגיאה + + + This is not a valid UUID. + הערך אינו ערך UUID חוקי. + + + This value should be a multiple of {{ compared_value }}. + הערך חייב להיות כפולה של {{ compared_value }}. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + הקוד זיהוי עסקי (BIC) אינו משוייך ל IBAN {{ iban }}. + + + This value should be valid JSON. + הערך אינו ערך JSON תקין. + + + This collection should contain only unique elements. + האוסף חייב להכיל רק אלמנטים ייחודיים. + + + This value should be positive. + הערך חייב להיות חיובי. + + + This value should be either positive or zero. + הערך חייב להיות חיובי או אפס. + + + This value should be negative. + הערך חייב להיות שלילי. + + + This value should be either negative or zero. + הערך חייב להיות שלילי או אפס. + + + This value is not a valid timezone. + הערך אינו אזור זמן תקין. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + סיסמא זו הודלפה בהדלפת מידע, אסור להשתמש בה. אנא השתמש בסיסמה אחרת. + + + This value should be between {{ min }} and {{ max }}. + הערך חייב להיות בין {{ min }} ו- {{ max }}. + + + This value is not a valid hostname. + ערך זה אינו שם מארח חוקי. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + מספר האלמנטים באוסף זה צריך להיות מכפיל של {{ compared_value }}. + + + This value should satisfy at least one of the following constraints: + ערך זה אמור לעמוד לפחות באחד התנאים הבאים: + + + Each element of this collection should satisfy its own set of constraints. + כל אלמנט באוסף זה אמור לעמוד בקבוצת התנאים שלו. + + + This value is not a valid International Securities Identification Number (ISIN). + ערך זה אינו מספר זיהוי ניירות ערך בינלאומי תקף (ISIN). + + + This value should be a valid expression. + ערך זה חייב להיות ביטוי חוקי. + + + This value is not a valid CSS color. + ערך זה אינו צבע CSS חוקי. + + + This value is not a valid CIDR notation. + ערך זה אינו סימון CIDR חוקי. + + + The value of the netmask should be between {{ min }} and {{ max }}. + הערך של מסכת הרשת חייב להיות בין {{ min }} ו {{ max }}. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.hr.xlf b/src/vendor/symfony/validator/Resources/translations/validators.hr.xlf new file mode 100644 index 0000000..34384b4 --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.hr.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + Ova vrijednost treba biti netočna (false). + + + This value should be true. + Ova vrijednost treba biti točna (true). + + + This value should be of type {{ type }}. + Ova vrijednost treba biti tipa {{ type }}. + + + This value should be blank. + Ova vrijednost treba biti prazna. + + + The value you selected is not a valid choice. + Ova vrijednost nije valjan izbor. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Izaberite barem {{ limit }} mogućnosti. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Izaberite najviše {{ limit }} mogućnosti. + + + One or more of the given values is invalid. + Jedna ili više danih vrijednosti nije ispravna. + + + This field was not expected. + Ovo polje nije očekivano. + + + This field is missing. + Ovo polje nedostaje. + + + This value is not a valid date. + Ova vrijednost nije ispravan datum. + + + This value is not a valid datetime. + Ova vrijednost nije ispravnog datum-vrijeme formata. + + + This value is not a valid email address. + Ova vrijednost nije ispravna e-mail adresa. + + + The file could not be found. + Datoteka ne može biti pronađena. + + + The file is not readable. + Datoteka nije čitljiva. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + Datoteka je prevelika ({{ size }} {{ suffix }}). Najveća dozvoljena veličina je {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + Mime tip datoteke nije ispravan ({{ type }}). Dozvoljeni mime tipovi su {{ types }}. + + + This value should be {{ limit }} or less. + Ova vrijednost treba biti {{ limit }} ili manje. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Ova vrijednost je predugačka. Treba imati {{ limit }} znakova ili manje. + + + This value should be {{ limit }} or more. + Ova vrijednost treba biti {{ limit }} ili više. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Ova vrijednost je prekratka. Treba imati {{ limit }} znakova ili više. + + + This value should not be blank. + Ova vrijednost ne bi trebala biti prazna. + + + This value should not be null. + Ova vrijednost ne bi trebala biti null. + + + This value should be null. + Ova vrijednost treba biti null. + + + This value is not valid. + Ova vrijednost nije ispravna. + + + This value is not a valid time. + Ova vrijednost nije ispravno vrijeme. + + + This value is not a valid URL. + Ova vrijednost nije ispravan URL. + + + The two values should be equal. + Obje vrijednosti trebaju biti jednake. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + Ova datoteka je prevelika. Najveća dozvoljena veličina je {{ limit }} {{ suffix }}. + + + The file is too large. + Ova datoteka je prevelika. + + + The file could not be uploaded. + Ova datoteka ne može biti prenesena. + + + This value should be a valid number. + Ova vrijednost treba biti ispravan broj. + + + This file is not a valid image. + Ova datoteka nije ispravna slika. + + + This is not a valid IP address. + Ovo nije ispravna IP adresa. + + + This value is not a valid language. + Ova vrijednost nije ispravan jezik. + + + This value is not a valid locale. + Ova vrijednost nije ispravana regionalna oznaka. + + + This value is not a valid country. + Ova vrijednost nije ispravna država. + + + This value is already used. + Ova vrijednost je već iskorištena. + + + The size of the image could not be detected. + Veličina slike se ne može odrediti. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + Širina slike je prevelika ({{ width }}px). Najveća dozvoljena širina je {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + Širina slike je premala ({{ width }}px). Najmanja dozvoljena širina je {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + Visina slike je prevelika ({{ height }}px). Najveća dozvoljena visina je {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + Visina slike je premala ({{ height }}px). Najmanja dozvoljena visina je {{ min_height }}px. + + + This value should be the user's current password. + Ova vrijednost treba biti trenutna korisnička lozinka. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Ova vrijednost treba imati točno {{ limit }} znakova. + + + The file was only partially uploaded. + Datoteka je samo djelomično prenesena. + + + No file was uploaded. + Niti jedna datoteka nije prenesena. + + + No temporary folder was configured in php.ini. + U php.ini datoteci nije konfiguriran privremeni direktorij. + + + Cannot write temporary file to disk. + Ne mogu zapisati privremenu datoteku na disk. + + + A PHP extension caused the upload to fail. + Prijenos datoteke nije uspio zbog PHP ekstenzije. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Ova kolekcija treba sadržavati {{ limit }} ili više elemenata.|Ova kolekcija treba sadržavati {{ limit }} ili više elemenata.|Ova kolekcija treba sadržavati {{ limit }} ili više elemenata. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Ova kolekcija treba sadržavati {{ limit }} ili manje elemenata.|Ova kolekcija treba sadržavati {{ limit }} ili manje elemenata.|Ova kolekcija treba sadržavati {{ limit }} ili manje elemenata. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Ova kolekcija treba sadržavati točno {{ limit }} element.|Ova kolekcija treba sadržavati točno {{ limit }} elementa.|Ova kolekcija treba sadržavati točno {{ limit }} elemenata. + + + Invalid card number. + Neispravan broj kartice. + + + Unsupported card type or invalid card number. + Tip kartice nije podržan ili je broj kartice neispravan. + + + This is not a valid International Bank Account Number (IBAN). + Ova vrijednost nije ispravan međunarodni broj bankovnog računa (IBAN). + + + This value is not a valid ISBN-10. + Ova vrijednost nije ispravan ISBN-10. + + + This value is not a valid ISBN-13. + Ova vrijednost nije ispravan ISBN-13. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Ova vrijednost nije ispravan ISBN-10 niti ISBN-13. + + + This value is not a valid ISSN. + Ova vrijednost nije ispravan ISSN. + + + This value is not a valid currency. + Ova vrijednost nije ispravna valuta. + + + This value should be equal to {{ compared_value }}. + Ova vrijednost treba biti jednaka {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + Ova vrijednost treba biti veća od {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + Ova vrijednost treba biti veća od ili jednaka {{ compared_value }}. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Ova vrijednost treba biti {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Ova vrijednost treba biti manja od {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + Ova vrijednost treba biti manja od ili jednaka {{ compared_value }}. + + + This value should not be equal to {{ compared_value }}. + Ova vrijednost treba biti različita od {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Ova vrijednost treba biti različita od {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Omjer slike je prevelik ({{ ratio }}). Dozvoljeni maksimalni omjer je {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + Omjer slike je premali ({{ ratio }}). Minimalni očekivani omjer je {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + Slika je kvadratnog oblika ({{ width }}x{{ height }}px). Kvadratne slike nisu dozvoljene. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + Slika je orijentirana horizontalno ({{ width }}x{{ height }}px). Horizontalno orijentirane slike nisu dozvoljene. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + Slika je orijentirana vertikalno ({{ width }}x{{ height }}px). Vertikalno orijentirane slike nisu dozvoljene. + + + An empty file is not allowed. + Prazna datoteka nije dozvoljena. + + + The host could not be resolved. + Poslužitelj ne može biti pronađen. + + + This value does not match the expected {{ charset }} charset. + Ova vrijednost ne odgovara očekivanom {{ charset }} znakovnom skupu. + + + This is not a valid Business Identifier Code (BIC). + Ovo nije validan poslovni identifikacijski broj (BIC). + + + Error + Greška + + + This is not a valid UUID. + Ovo nije validan UUID. + + + This value should be a multiple of {{ compared_value }}. + Ova vrijednost treba biti višekratnik od {{ compared_value }}. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Poslovni identifikacijski broj (BIC) nije povezan sa IBAN brojem {{ iban }}. + + + This value should be valid JSON. + Ova vrijednost treba biti validan JSON. + + + This collection should contain only unique elements. + Ova kolekcija treba sadržavati samo unikatne elemente. + + + This value should be positive. + Ova vrijednost treba biti pozitivna. + + + This value should be either positive or zero. + Ova vrijednost treba biti pozitivna ili jednaka nuli. + + + This value should be negative. + Ova vrijednost treba biti negativna. + + + This value should be either negative or zero. + Ova vrijednost treba biti negativna ili jednaka nuli. + + + This value is not a valid timezone. + Ova vrijednost nije validna vremenska zona. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Ova lozinka je procurila u nekom od sigurnosnih propusta, te je potrebno koristiti drugu lozinku. + + + This value should be between {{ min }} and {{ max }}. + Ova vrijednost treba biti između {{ min }} i {{ max }}. + + + This value is not a valid hostname. + Ova vrijednost nije ispravno ime poslužitelja (engl. hostname). + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + Broj elemenata u kolekciji treba biti djeljiv s {{ compared_value }}. + + + This value should satisfy at least one of the following constraints: + Ova vrijednost mora zadovoljiti jedan od sljedećih ograničenja: + + + Each element of this collection should satisfy its own set of constraints. + Svaki element ove kolekcije mora zadovoljiti vlastiti skup ograničenja. + + + This value is not a valid International Securities Identification Number (ISIN). + Ova vrijednost nije ispravan međunarodni identifikacijski broj vrijednosnih papira (ISIN). + + + This value should be a valid expression. + Ova vrijednost mora biti valjani izraz. + + + This value is not a valid CSS color. + Ova vrijednost nije važeća CSS boja. + + + This value is not a valid CIDR notation. + Ova vrijednost nije valjana CIDR notacija. + + + The value of the netmask should be between {{ min }} and {{ max }}. + Vrijednost mrežne maske trebala bi biti između {{ min }} i {{ max }}. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.hu.xlf b/src/vendor/symfony/validator/Resources/translations/validators.hu.xlf new file mode 100644 index 0000000..30b0dbe --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.hu.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + Ennek az értéknek hamisnak kell lennie. + + + This value should be true. + Ennek az értéknek igaznak kell lennie. + + + This value should be of type {{ type }}. + Ennek az értéknek {{ type }} típusúnak kell lennie. + + + This value should be blank. + Ennek az értéknek üresnek kell lennie. + + + The value you selected is not a valid choice. + A választott érték érvénytelen. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Legalább {{ limit }} értéket kell kiválasztani.|Legalább {{ limit }} értéket kell kiválasztani. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Legfeljebb {{ limit }} értéket lehet kiválasztani.|Legfeljebb {{ limit }} értéket lehet kiválasztani. + + + One or more of the given values is invalid. + A megadott értékek közül legalább egy érvénytelen. + + + This field was not expected. + Nem várt mező. + + + This field is missing. + Ez a mező hiányzik. + + + This value is not a valid date. + Ez az érték nem egy érvényes dátum. + + + This value is not a valid datetime. + Ez az érték nem egy érvényes időpont. + + + This value is not a valid email address. + Ez az érték nem egy érvényes e-mail cím. + + + The file could not be found. + A fájl nem található. + + + The file is not readable. + A fájl nem olvasható. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + A fájl túl nagy ({{ size }} {{ suffix }}). A legnagyobb megengedett méret {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + A fájl MIME típusa érvénytelen ({{ type }}). Az engedélyezett MIME típusok: {{ types }}. + + + This value should be {{ limit }} or less. + Ez az érték legfeljebb {{ limit }} lehet. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Ez az érték túl hosszú. Legfeljebb {{ limit }} karaktert tartalmazhat.|Ez az érték túl hosszú. Legfeljebb {{ limit }} karaktert tartalmazhat. + + + This value should be {{ limit }} or more. + Ez az érték legalább {{ limit }} kell, hogy legyen. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Ez az érték túl rövid. Legalább {{ limit }} karaktert kell tartalmaznia.|Ez az érték túl rövid. Legalább {{ limit }} karaktert kell tartalmaznia. + + + This value should not be blank. + Ez az érték nem lehet üres. + + + This value should not be null. + Ez az érték nem lehet null. + + + This value should be null. + Ennek az értéknek nullnak kell lennie. + + + This value is not valid. + Ez az érték nem érvényes. + + + This value is not a valid time. + Ez az érték nem egy érvényes időpont. + + + This value is not a valid URL. + Ez az érték nem egy érvényes URL. + + + The two values should be equal. + A két értéknek azonosnak kell lennie. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + A fájl túl nagy. A megengedett maximális méret: {{ limit }} {{ suffix }}. + + + The file is too large. + A fájl túl nagy. + + + The file could not be uploaded. + A fájl nem tölthető fel. + + + This value should be a valid number. + Ennek az értéknek érvényes számnak kell lennie. + + + This file is not a valid image. + Ez a fájl nem egy érvényes kép. + + + This is not a valid IP address. + Ez az érték nem egy érvényes IP cím. + + + This value is not a valid language. + Ez az érték nem egy érvényes nyelv. + + + This value is not a valid locale. + Ez az érték nem egy érvényes területi beállítás. + + + This value is not a valid country. + Ez az érték nem egy érvényes ország. + + + This value is already used. + Ez az érték már használatban van. + + + The size of the image could not be detected. + A kép méretét nem lehet megállapítani. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + A kép szélessége túl nagy ({{ width }}px). A megengedett legnagyobb szélesség {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + A kép szélessége túl kicsi ({{ width }}px). Az elvárt legkisebb szélesség {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + A kép magassága túl nagy ({{ height }}px). A megengedett legnagyobb magasság {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + A kép magassága túl kicsi ({{ height }}px). Az elvárt legkisebb magasság {{ min_height }}px. + + + This value should be the user's current password. + Ez az érték a felhasználó jelenlegi jelszavával kell megegyezzen. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Ennek az értéknek pontosan {{ limit }} karaktert kell tartalmaznia.|Ennek az értéknek pontosan {{ limit }} karaktert kell tartalmaznia. + + + The file was only partially uploaded. + A fájl csak részben lett feltöltve. + + + No file was uploaded. + Nem lett fájl feltöltve. + + + No temporary folder was configured in php.ini. + Nincs ideiglenes könyvtár beállítva a php.ini-ben. + + + Cannot write temporary file to disk. + Az ideiglenes fájl nem írható a lemezre. + + + A PHP extension caused the upload to fail. + Egy PHP bővítmény miatt a feltöltés nem sikerült. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Ennek a gyűjteménynek legalább {{ limit }} elemet kell tartalmaznia.|Ennek a gyűjteménynek legalább {{ limit }} elemet kell tartalmaznia. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Ez a gyűjtemény legfeljebb {{ limit }} elemet tartalmazhat.|Ez a gyűjtemény legfeljebb {{ limit }} elemet tartalmazhat. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Ennek a gyűjteménynek pontosan {{ limit }} elemet kell tartalmaznia.|Ennek a gyűjteménynek pontosan {{ limit }} elemet kell tartalmaznia. + + + Invalid card number. + Érvénytelen kártyaszám. + + + Unsupported card type or invalid card number. + Nem támogatott kártyatípus vagy érvénytelen kártyaszám. + + + This is not a valid International Bank Account Number (IBAN). + Érvénytelen nemzetközi bankszámlaszám (IBAN). + + + This value is not a valid ISBN-10. + Ez az érték nem egy érvényes ISBN-10. + + + This value is not a valid ISBN-13. + Ez az érték nem egy érvényes ISBN-13. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Ez az érték nem egy érvényes ISBN-10 vagy ISBN-13. + + + This value is not a valid ISSN. + Ez az érték nem egy érvényes ISSN. + + + This value is not a valid currency. + Ez az érték nem egy érvényes pénznem. + + + This value should be equal to {{ compared_value }}. + Ez az érték legyen {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + Ez az érték nagyobb legyen, mint {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + Ez az érték nagyobb vagy egyenlő legyen, mint {{ compared_value }}. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Ez az érték ugyanolyan legyen, mint {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Ez az érték kisebb legyen, mint {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + Ez az érték kisebb vagy egyenlő legyen, mint {{ compared_value }}. + + + This value should not be equal to {{ compared_value }}. + Ez az érték ne legyen {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Ez az érték ne legyen ugyanolyan, mint {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + A képarány túl nagy ({{ ratio }}). A megengedett legnagyobb képarány {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + A képarány túl kicsi ({{ ratio }}). A megengedett legkisebb képarány {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + A kép négyzet alakú ({{ width }}x{{ height }}px). A négyzet alakú képek nem engedélyezettek. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + A kép fekvő tájolású ({{ width }}x{{ height }}px). A fekvő tájolású képek nem engedélyezettek. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + A kép álló tájolású ({{ width }}x{{ height }}px). Az álló tájolású képek nem engedélyezettek. + + + An empty file is not allowed. + Üres fájl nem megengedett. + + + The host could not be resolved. + Az állomásnevet nem lehet feloldani. + + + This value does not match the expected {{ charset }} charset. + Ez az érték nem az elvárt {{ charset }} karakterkódolást használja. + + + This is not a valid Business Identifier Code (BIC). + Érvénytelen nemzetközi bankazonosító kód (BIC/SWIFT). + + + Error + Hiba + + + This is not a valid UUID. + Érvénytelen egyedi azonosító (UUID). + + + This value should be a multiple of {{ compared_value }}. + Ennek az értéknek oszthatónak kell lennie a következővel: {{ compared_value }} + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Ez a Bankazonosító kód (BIC) nem kapcsolódik az IBAN kódhoz ({{ iban }}). + + + This value should be valid JSON. + Ez az érték érvényes JSON kell, hogy legyen. + + + This value should be positive. + Ennek az értéknek pozitívnak kell lennie. + + + This value should be either positive or zero. + Ennek az értéknek pozitívnak vagy nullának kell lennie. + + + This value should be negative. + Ennek az értéknek negatívnak kell lennie. + + + This value should be either negative or zero. + Ennek az értéknek negatívnak vagy nullának kell lennie. + + + This collection should contain only unique elements. + Ez a gyűjtemény csak egyedi elemeket tartalmazhat. + + + This value is not a valid timezone. + Ez az érték nem egy érvényes időzóna. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Ez a jelszó korábban egy adatvédelmi incidens során illetéktelenek kezébe került, így nem használható. Kérjük, használjon másik jelszót. + + + This value should be between {{ min }} and {{ max }}. + Ennek az értéknek {{ min }} és {{ max }} között kell lennie. + + + This value is not a valid hostname. + Ez az érték nem egy érvényes állomásnév (hosztnév). + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + A gyűjteményben lévő elemek számának oszthatónak kell lennie a következővel: {{ compared_value }}. + + + This value should satisfy at least one of the following constraints: + Ennek az értéknek meg kell felelni legalább egynek a következő feltételek közül: + + + Each element of this collection should satisfy its own set of constraints. + A gyűjtemény minden elemének meg kell felelni a saját feltételeinek. + + + This value is not a valid International Securities Identification Number (ISIN). + Ez az érték nem egy érvényes nemzetközi értékpapír-azonosító szám (ISIN). + + + This value should be a valid expression. + Ennek az értéknek érvényes kifejezésnek kell lennie. + + + This value is not a valid CSS color. + Ez az érték nem egy érvényes CSS szín. + + + This value is not a valid CIDR notation. + Ez az érték nem egy érvényes CIDR jelölés. + + + The value of the netmask should be between {{ min }} and {{ max }}. + Ennek a netmask értéknek {{ min }} és {{ max }} között kell lennie. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.hy.xlf b/src/vendor/symfony/validator/Resources/translations/validators.hy.xlf new file mode 100644 index 0000000..f53df12 --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.hy.xlf @@ -0,0 +1,395 @@ + + + + + + This value should be false. + Արժեքը պետք է լինի կեղծ։ + + + This value should be true. + Արժեքը պետք է լինի իրական։ + + + This value should be of type {{ type }}. + Արժեքը պետք է լինի {{ type }} տեսակի։ + + + This value should be blank. + Արժեքը պետք է լինի դատարկ։ + + + The value you selected is not a valid choice. + Ձեր ընտրած արժեքը անվավեր ընտրություն է։ + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Դուք պետք է ընտրեք ամենաքիչը {{ limit }} տարբերակներ։ + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Դուք պետք է ընտրեք ոչ ավելի քան {{ limit }} տարբերակներ։ + + + One or more of the given values is invalid. + Մեկ կամ ավելի տրված արժեքները անվավեր են։ + + + This field was not expected. + Այս դաշտը չի սպասվում։ + + + This field is missing. + Այս դաշտը բացակայում է։ + + + This value is not a valid date. + Արժեքը սխալ ամսաթիվ է։ + + + This value is not a valid datetime. + Ամսաթվի և ժամանակի արժեքը անվավեր է։ + + + This value is not a valid email address. + Անվավեր էլ֊փոստի արժեք։ + + + The file could not be found. + Նիշքը չի գտնվել։ + + + The file is not readable. + Նիշքը անընթեռնելի է։ + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + Նիշքը չափազանց մեծ է ({{ size }} {{ suffix }}): Մաքսիմալ թույլատրելի չափսը՝ {{ limit }} {{ suffix }}։ + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + MIME-տեսակը անվավեր է է({{ type }}): Նիշքերի թույլատրելի MIME-տեսակներն են: {{ types }}։ + + + This value should be {{ limit }} or less. + Արժեքը պետք է լինի {{ limit }} կամ փոքր։ + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Արժեքը չափազանց երկար է: Պետք է լինի {{ limit }} կամ ավել սիմվոլներ։ + + + This value should be {{ limit }} or more. + Արժեքը պետ է լինի {{ limit }} կամ շատ։ + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Արժեքը չափազանց կարճ է: Պետք է լինի {{ limit }} կամ ավելի սիմվոլներ։ + + + This value should not be blank. + Արժեքը չպետք է դատարկ լինի։ + + + This value should not be null. + Արժեքը չպետք է լինի null։ + + + This value should be null. + Արժեքը պետք է լինի null։ + + + This value is not valid. + Անվավեր արժեք։ + + + This value is not a valid time. + Ժամանակի արժեքը անվավեր է։ + + + This value is not a valid URL. + Արժեքը URL չէ։ + + + The two values should be equal. + Երկու արժեքները պետք է նույնը լինեն։ + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + Նիշքը չափազանց մեծ է: Մաքսիմալ թույլատրելի չափսը {{ limit }} {{ suffix }} է։ + + + The file is too large. + Նիշքը չափազանց մեծ է։ + + + The file could not be uploaded. + Նիշքը չի կարող բեռնվել։ + + + This value should be a valid number. + Արժեքը պետք է լինի թիվ։ + + + This file is not a valid image. + Նիշքը նկարի վավեր ֆորմատ չէ։ + + + This is not a valid IP address. + Արժեքը վավեր IP հասցե չէ։ + + + This value is not a valid language. + Արժեքը վավեր լեզու չէ։ + + + This value is not a valid locale. + Արժեքը չի հանդիսանում վավեր տեղայնացում։ + + + This value is not a valid country. + Արժեքը պետք է լինի երկիր։ + + + This value is already used. + Այդ արժեքն արդեն օգտագործվում է։ + + + The size of the image could not be detected. + Նկարի չափսերը չստացվեց որոշել։ + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + Նկարի լայնությունը չափազանց մեծ է({{ width }}px). Մաքսիմալ չափն է {{ max_width }}px։ + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + Նկարի լայնությունը չափազանց փոքր է ({{ width }}px). Մինիմալ չափն է {{ min_ width }}px։ + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + Նկարի բարձրությունը չափազանց մեծ է ({{ height }}px). Մաքսիմալ չափն է {{ max_height }}px։ + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + Նկարի բարձրությունը չափազանց փոքր է ({{ height }}px). Մինիմալ չափն է {{ min_height }}px։ + + + This value should be the user's current password. + Այս արժեքը պետք է լինի օգտագործողի ներկա ծածկագիրը։ + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Այս արժեքը պետք է ունենա ճիշտ {{ limit }} սիմվոլներ։ + + + The file was only partially uploaded. + Նիշքի մասնակի բեռնման սխալ։ + + + No file was uploaded. + Նիշքը չի բեռնվել։ + + + No temporary folder was configured in php.ini. + php.ini նիշքում ժամանակավոր պանակ նշված չէ։ + + + Cannot write temporary file to disk. + Ժամանակավոր նիշքը հնարավոր չէ գրել սկավառակի վրա։ + + + A PHP extension caused the upload to fail. + PHP ֆորմատը դարձել է բեռնման չհաջողման պատճառ։ + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Այս համախումբը պետք է պաուրակի {{ limit }} կամ ավելի տարրեր։|Այս հավելվածը պետք է պարունակի limit }} տարր կամ ավելին։|Այս համախումբը պետք է պարունակի {{ limit }} տարրերին կամ ավելի։ + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Այս համախումբը պետք է պաուրակի {{ limit }} տարրեր կամ քիչ։|Այս համախումբը պետք է պաուրակի {{ limit }} տարր կամ քիչ։|Այս համախումբը պետք է պաուրակի {{ limit }} տարրեր կամ քիչ։ + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Այս համախումբը պետք է պաուրակի ուղիղ {{ limit }} տարր։|Այս համախումբը պետք է պաուրակի ուղիղ {{ limit }} տարրեր։|Այս համախումբը պետք է պաուրակի {{ limit }} տարրեր։ + + + Invalid card number. + Քարտի սխալ համար: + + + Unsupported card type or invalid card number. + Չսպասարկվող կամ սխալ քարտի համար: + + + This is not a valid International Bank Account Number (IBAN). + Արժեքը վավեր միջազային բանկային հաշվի համար չէ (IBAN)։ + + + This value is not a valid ISBN-10. + Արժեքը ունի անվավեր ISBN-10 ձևաչափ։ + + + This value is not a valid ISBN-13. + Արժեքը ունի անվավեր ISBN-13 ձևաչափ։ + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Արժեքը չի համապատասխանում ISBN-10 և ISBN-13 ձևաչափերին։ + + + This value is not a valid ISSN. + Արժեքը չի համապաստասխանում ISSN ձևաչափին։ + + + This value is not a valid currency. + Արժեքը վավեր տարադրամ չէ։ + + + This value should be equal to {{ compared_value }}. + Արժեքը պետք է լինի {{ compared_value }}։ + + + This value should be greater than {{ compared_value }}. + Արժեքը պետք է մեծ լինի, քան {{ compared_value }}։ + + + This value should be greater than or equal to {{ compared_value }}. + Արժեքը պետք է լինի հավասար կամ մեծ քան {{ compared_value }}։ + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Արժեքը պետք է լինի ինչպես {{ compared_value_type }} {{ compared_value }}։ + + + This value should be less than {{ compared_value }}. + Արժեքը պետք է լինի փոքր քան {{ compared_value }}։ + + + This value should be less than or equal to {{ compared_value }}. + Արժեքը պետք է լինի փոքր կամ հավասար {{ compared_value }}։ + + + This value should not be equal to {{ compared_value }}. + Արժեքը պետք է լինի հավասար {{ compared_value }}։ + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Արժեքը պետք է լինի նունը {{ compared_value_type }} {{ compared_value }}: + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Պատկերի կողմերի հարաբերակցությունը խիստ մեծ է ({{ ratio }}). Մաքսիմալ հարաբերակցությունը՝ {{ max_ratio }}։ + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + Պատկերի կողմերի հարաբերակցությունը խիստ փոքր է ({{ ratio }}). Մինիմալ հարաբերակցությունը՝ {{ min_ratio }}։ + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + Պատկերը քառակուսի է({{ width }}x{{ height }}px)։ Քառակուսի նկարներ չեն թույլատրվում։ + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + Պատկերը ալբոմային ուղղվածության է({{ width }}x{{ height }}px)․ դա չի թույլատրվում։ + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + Պատկերը պորտրետային ուղղվածության է ({{ width }}x{{ height }}px)․ դա չի թույլատրվում։ + + + An empty file is not allowed. + Դատարկ նիշք չի թույլատրվում։ + + + The host could not be resolved. + Հոսթի անունը հնարավոր չի պարզել։ + + + This value does not match the expected {{ charset }} charset. + Արժեքը չի համընկնում {{ charset }} կոդավորման հետ։ + + + This is not a valid Business Identifier Code (BIC). + Սա վավեր Business Identifier Code (BIC) չէ։ + + + Error + Սխալ + + + This is not a valid UUID. + Սա վավեր UUID չէ։ + + + This value should be a multiple of {{ compared_value }}. + Այս արժեքը պետք է լինի բազմակի {{ compared_value }}։ + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Բիզնեսի նույնականացման կոդը (BIC) կապված չէ IBAN- ի հետ {{ iban }}։ + + + This value should be valid JSON. + Այս արժեքը պետք է լինի վավեր JSON։ + + + This collection should contain only unique elements. + Այս համախումբը պետք է պարունակի միայն եզակի տարրեր։ + + + This value should be positive. + Այս արժեքը պետք է լինի դրական։ + + + This value should be either positive or zero. + Այս արժեքը պետք է լինի դրական կամ զրոյական։ + + + This value should be negative. + Այս արժեքը պետք է լինի բացասական։ + + + This value should be either negative or zero. + Այս արժեքը պետք է լինի բացասական կամ զրոյական։ + + + This value is not a valid timezone. + Այս արժեքը վավեր ժամային գոտի չէ։ + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Գաղտնաբառը գտնվել է տվյալների արտահոսքում. այն չպետք է օգտագործվի: Խնդրում ենք օգտագործել մեկ այլ գաղտնաբառ։ + + + This value should be between {{ min }} and {{ max }}. + Այս արժեքը պետք է լինի {{ min }}-ի և {{ max }}-ի միջև։ + + + This value is not a valid hostname. + Այս հոստի անունը վավեր չէ։ + + + The number of elements in this collection should be a multiple of {{ compared_value }}․ + Այս համախմբի տարրերի քանակը պետք է հավասար լինի {{ compared_value }}-ի բազմապատիկներին։ + + + This value should satisfy at least one of the following constraints: + Այս արժեքը պետք է բավարարի հետևյալ սահմանափակումներից առնվազն մեկը։ + + + Each element of this collection should satisfy its own set of constraints. + Այս համախմբի յուրաքանչյուր տարր պետք է բավարարի իր սեփական սահմանափակումները։ + + + This value is not a valid International Securities Identification Number (ISIN). + Այս արժեքը արժեթղթերի նույնականացման միջազգային համարը վավեր չէ(ISIN)։ + + + This value should be a valid expression. + Այս արժեքը պետք է լինի վավեր արտահայտություն: + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.id.xlf b/src/vendor/symfony/validator/Resources/translations/validators.id.xlf new file mode 100644 index 0000000..1687f33 --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.id.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + Nilai ini harus bernilai salah. + + + This value should be true. + Nilai ini harus bernilai benar. + + + This value should be of type {{ type }}. + Nilai ini harus bertipe {{ type }}. + + + This value should be blank. + Nilai ini harus kosong. + + + The value you selected is not a valid choice. + Nilai yang dipilih tidak tepat. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Anda harus memilih paling tidak {{ limit }} pilihan. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Anda harus memilih paling banyak {{ limit }} pilihan. + + + One or more of the given values is invalid. + Satu atau lebih nilai yang diberikan tidak sah. + + + This field was not expected. + Ruas ini tidak diharapkan. + + + This field is missing. + Ruas ini hilang. + + + This value is not a valid date. + Nilai ini bukan merupakan tanggal yang sah. + + + This value is not a valid datetime. + Nilai ini bukan merupakan tanggal dan waktu yang sah. + + + This value is not a valid email address. + Nilai ini bukan alamat surel yang sah. + + + The file could not be found. + Berkas tidak dapat ditemukan. + + + The file is not readable. + Berkas tidak dapat dibaca. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + Ukuran berkas terlalu besar ({{ size }} {{ suffix }}). Ukuran maksimum yang diizinkan adalah {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + Jenis berkas ({{ type }}) tidak sah. Jenis berkas yang diizinkan adalah {{ types }}. + + + This value should be {{ limit }} or less. + Nilai ini harus {{ limit }} atau kurang. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Nilai ini terlalu panjang. Seharusnya {{ limit }} karakter atau kurang. + + + This value should be {{ limit }} or more. + Nilai ini harus {{ limit }} atau lebih. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Nilai ini terlalu pendek. Seharusnya {{ limit }} karakter atau lebih. + + + This value should not be blank. + Nilai ini tidak boleh kosong. + + + This value should not be null. + Nilai ini tidak boleh 'null'. + + + This value should be null. + Nilai ini harus 'null'. + + + This value is not valid. + Nilai ini tidak sah. + + + This value is not a valid time. + Nilai ini bukan merupakan waktu yang sah. + + + This value is not a valid URL. + Nilai ini bukan URL yang sah. + + + The two values should be equal. + Isi keduanya harus sama. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + Ukuran berkas terlalu besar. Ukuran maksimum yang diizinkan adalah {{ limit }} {{ suffix }}. + + + The file is too large. + Ukuran berkas terlalu besar. + + + The file could not be uploaded. + Berkas tidak dapat diunggah. + + + This value should be a valid number. + Nilai ini harus angka yang sah. + + + This file is not a valid image. + Berkas ini tidak termasuk citra. + + + This is not a valid IP address. + Ini bukan alamat IP yang sah. + + + This value is not a valid language. + Nilai ini bukan bahasa yang sah. + + + This value is not a valid locale. + Nilai ini bukan lokal yang sah. + + + This value is not a valid country. + Nilai ini bukan negara yang sah. + + + This value is already used. + Nilai ini sudah digunakan. + + + The size of the image could not be detected. + Ukuran dari citra tidak bisa dideteksi. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + Lebar citra terlalu besar ({{ width }}px). Ukuran lebar maksimum adalah {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + Lebar citra terlalu kecil ({{ width }}px). Ukuran lebar minimum yang diharapkan adalah {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + Tinggi citra terlalu besar ({{ height }}px). Ukuran tinggi maksimum adalah {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + Tinggi citra terlalu kecil ({{ height }}px). Ukuran tinggi minimum yang diharapkan adalah {{ min_height }}px. + + + This value should be the user's current password. + Nilai ini harus kata sandi pengguna saat ini. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Nilai ini harus memiliki tepat {{ limit }} karakter. + + + The file was only partially uploaded. + Berkas hanya terunggah sebagian. + + + No file was uploaded. + Tidak ada berkas terunggah. + + + No temporary folder was configured in php.ini. + Direktori sementara tidak dikonfiguasi pada php.ini. + + + Cannot write temporary file to disk. + Tidak dapat menuliskan berkas sementara ke dalam media penyimpanan. + + + A PHP extension caused the upload to fail. + Sebuah ekstensi PHP menyebabkan kegagalan unggah. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Kumpulan ini harus memiliki {{ limit }} elemen atau lebih. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Kumpulan ini harus memiliki kurang dari {{ limit }} elemen. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Kumpulan ini harus memiliki tepat {{ limit }} elemen. + + + Invalid card number. + Nomor kartu tidak sah. + + + Unsupported card type or invalid card number. + Jenis kartu tidak didukung atau nomor kartu tidak sah. + + + This is not a valid International Bank Account Number (IBAN). + Ini bukan Nomor Rekening Bank Internasional (IBAN) yang sah. + + + This value is not a valid ISBN-10. + Nilai ini bukan ISBN-10 yang sah. + + + This value is not a valid ISBN-13. + Nilai ini bukan ISBN-13 yang sah. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Nilai ini bukan ISBN-10 maupun ISBN-13 yang sah. + + + This value is not a valid ISSN. + Nilai ini bukan ISSN yang sah. + + + This value is not a valid currency. + Nilai ini bukan mata uang yang sah. + + + This value should be equal to {{ compared_value }}. + Nilai ini seharusnya sama dengan {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + Nilai ini seharusnya lebih dari {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + Nilai ini seharusnya lebih dari atau sama dengan {{ compared_value }}. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Nilai ini seharusnya identik dengan {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Nilai ini seharusnya kurang dari {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + Nilai ini seharusnya kurang dari atau sama dengan {{ compared_value }}. + + + This value should not be equal to {{ compared_value }}. + Nilai ini seharusnya tidak sama dengan {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Nilai ini seharusnya tidak identik dengan {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Rasio citra terlalu besar ({{ ratio }}). Rasio maksimum yang diizinkan adalah {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + Rasio citra terlalu kecil ({{ ratio }}). Rasio minimum yang diharapkan adalah {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + Citra persegi ({{ width }}x{{ height }}px). Citra persegi tidak diizinkan. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + Citra berorientasi lanskap ({{ width }}x{{ height }}px). Citra berorientasi lanskap tidak diizinkan. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + Citra berorientasi potret ({{ width }}x{{ height }}px). Citra berorientasi potret tidak diizinkan. + + + An empty file is not allowed. + Berkas kosong tidak diizinkan. + + + The host could not be resolved. + Host tidak dapat diselesaikan. + + + This value does not match the expected {{ charset }} charset. + Nilai ini tidak memenuhi set karakter {{ charset }} yang diharapkan. + + + This is not a valid Business Identifier Code (BIC). + Ini bukan Business Identifier Code (BIC) yang sah. + + + Error + Galat + + + This is not a valid UUID. + Ini bukan UUID yang sah. + + + This value should be a multiple of {{ compared_value }}. + Nilai ini harus kelipatan dari {{ compared_value }}. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Business Identifier Code (BIC) ini tidak terkait dengan IBAN {{ iban }}. + + + This value should be valid JSON. + Nilai ini harus berisi JSON yang sah. + + + This collection should contain only unique elements. + Kumpulan ini harus mengandung elemen yang unik. + + + This value should be positive. + Nilai ini harus positif. + + + This value should be either positive or zero. + Nilai ini harus positif atau nol. + + + This value should be negative. + Nilai ini harus negatif. + + + This value should be either negative or zero. + Nilai ini harus negatif atau nol. + + + This value is not a valid timezone. + Nilai ini harus merupakan zona waktu yang sah. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Kata sandi ini telah bocor di data breach, harus tidak digunakan kembali. Silahkan gunakan kata sandi yang lain. + + + This value should be between {{ min }} and {{ max }}. + Nilai ini harus berada diantara {{ min }} dan {{ max }}. + + + This value is not a valid hostname. + Nilai ini bukan merupakan hostname yang sah. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + Angka dari setiap elemen di dalam kumpulan ini harus kelipatan dari {{ compared_value }}. + + + This value should satisfy at least one of the following constraints: + Nilai ini harus memenuhi setidaknya satu dari batasan berikut: + + + Each element of this collection should satisfy its own set of constraints. + Setiap elemen koleksi ini harus memenuhi batasannya sendiri. + + + This value is not a valid International Securities Identification Number (ISIN). + Nilai ini bukan merupakan International Securities Identification Number (ISIN) yang sah. + + + This value should be a valid expression. + Nilai ini harus berupa ekspresi yang sah. + + + This value is not a valid CSS color. + Nilai ini bukan merupakan warna CSS yang sah. + + + This value is not a valid CIDR notation. + Nilai ini bukan merupakan notasi CIDR yang sah. + + + The value of the netmask should be between {{ min }} and {{ max }}. + Nilai dari netmask harus berada diantara {{ min }} dan {{ max }}. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.it.xlf b/src/vendor/symfony/validator/Resources/translations/validators.it.xlf new file mode 100644 index 0000000..c7cd437 --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.it.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + Questo valore dovrebbe essere falso. + + + This value should be true. + Questo valore dovrebbe essere vero. + + + This value should be of type {{ type }}. + Questo valore dovrebbe essere di tipo {{ type }}. + + + This value should be blank. + Questo valore dovrebbe essere vuoto. + + + The value you selected is not a valid choice. + Questo valore dovrebbe essere una delle opzioni disponibili. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Si dovrebbe selezionare almeno {{ limit }} opzione.|Si dovrebbero selezionare almeno {{ limit }} opzioni. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Si dovrebbe selezionare al massimo {{ limit }} opzione.|Si dovrebbero selezionare al massimo {{ limit }} opzioni. + + + One or more of the given values is invalid. + Uno o più valori inseriti non sono validi. + + + This field was not expected. + Questo campo non è stato previsto. + + + This field is missing. + Questo campo è mancante. + + + This value is not a valid date. + Questo valore non è una data valida. + + + This value is not a valid datetime. + Questo valore non è una data e ora valida. + + + This value is not a valid email address. + Questo valore non è un indirizzo email valido. + + + The file could not be found. + Non è stato possibile trovare il file. + + + The file is not readable. + Il file non è leggibile. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + Il file è troppo grande ({{ size }} {{ suffix }}). La dimensione massima consentita è {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + Il mime type del file non è valido ({{ type }}). I tipi permessi sono {{ types }}. + + + This value should be {{ limit }} or less. + Questo valore dovrebbe essere {{ limit }} o inferiore. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Questo valore è troppo lungo. Dovrebbe essere al massimo di {{ limit }} carattere.|Questo valore è troppo lungo. Dovrebbe essere al massimo di {{ limit }} caratteri. + + + This value should be {{ limit }} or more. + Questo valore dovrebbe essere {{ limit }} o superiore. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Questo valore è troppo corto. Dovrebbe essere almeno di {{ limit }} carattere.|Questo valore è troppo corto. Dovrebbe essere almeno di {{ limit }} caratteri. + + + This value should not be blank. + Questo valore non dovrebbe essere vuoto. + + + This value should not be null. + Questo valore non dovrebbe essere nullo. + + + This value should be null. + Questo valore dovrebbe essere nullo. + + + This value is not valid. + Questo valore non è valido. + + + This value is not a valid time. + Questo valore non è un'ora valida. + + + This value is not a valid URL. + Questo valore non è un URL valido. + + + The two values should be equal. + I due valori dovrebbero essere uguali. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + Il file è troppo grande. La dimensione massima è {{ limit }} {{ suffix }}. + + + The file is too large. + Il file è troppo grande. + + + The file could not be uploaded. + Il file non può essere caricato. + + + This value should be a valid number. + Questo valore dovrebbe essere un numero. + + + This file is not a valid image. + Questo file non è una immagine valida. + + + This is not a valid IP address. + Questo valore non è un indirizzo IP valido. + + + This value is not a valid language. + Questo valore non è una lingua valida. + + + This value is not a valid locale. + Questo valore non è una impostazione regionale valida. + + + This value is not a valid country. + Questo valore non è una nazione valida. + + + This value is already used. + Questo valore è già stato utilizzato. + + + The size of the image could not be detected. + La dimensione dell'immagine non può essere determinata. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + La larghezza dell'immagine è troppo grande ({{ width }}px). La larghezza massima è di {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + La larghezza dell'immagine è troppo piccola ({{ width }}px). La larghezza minima è di {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + L'altezza dell'immagine è troppo grande ({{ height }}px). L'altezza massima è di {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + L'altezza dell'immagine è troppo piccola ({{ height }}px). L'altezza minima è di {{ min_height }}px. + + + This value should be the user's current password. + Questo valore dovrebbe essere la password attuale dell'utente. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Questo valore dovrebbe contenere esattamente {{ limit }} carattere.|Questo valore dovrebbe contenere esattamente {{ limit }} caratteri. + + + The file was only partially uploaded. + Il file è stato caricato solo parzialmente. + + + No file was uploaded. + Nessun file è stato caricato. + + + No temporary folder was configured in php.ini. + Nessuna cartella temporanea è stata configurata nel php.ini. + + + Cannot write temporary file to disk. + Impossibile scrivere il file temporaneo sul disco. + + + A PHP extension caused the upload to fail. + Un'estensione PHP ha causato il fallimento del caricamento. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Questa collezione dovrebbe contenere almeno {{ limit }} elemento.|Questa collezione dovrebbe contenere almeno {{ limit }} elementi. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Questa collezione dovrebbe contenere massimo {{ limit }} elemento.|Questa collezione dovrebbe contenere massimo {{ limit }} elementi. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Questa collezione dovrebbe contenere esattamente {{ limit }} elemento.|Questa collezione dovrebbe contenere esattamente {{ limit }} elementi. + + + Invalid card number. + Numero di carta non valido. + + + Unsupported card type or invalid card number. + Tipo di carta non supportato o numero non valido. + + + This is not a valid International Bank Account Number (IBAN). + Questo valore non è un IBAN (International Bank Account Number) valido. + + + This value is not a valid ISBN-10. + Questo valore non è un codice ISBN-10 valido. + + + This value is not a valid ISBN-13. + Questo valore non è un codice ISBN-13 valido. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Questo valore non è un codice ISBN-10 o ISBN-13 valido. + + + This value is not a valid ISSN. + Questo valore non è un codice ISSN valido. + + + This value is not a valid currency. + Questo valore non è una valuta valida. + + + This value should be equal to {{ compared_value }}. + Questo valore dovrebbe essere uguale a {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + Questo valore dovrebbe essere maggiore di {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + Questo valore dovrebbe essere maggiore o uguale a {{ compared_value }}. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Questo valore dovrebbe essere identico a {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Questo valore dovrebbe essere minore di {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + Questo valore dovrebbe essere minore o uguale a {{ compared_value }}. + + + This value should not be equal to {{ compared_value }}. + Questo valore dovrebbe essere diverso da {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Questo valore dovrebbe essere diverso da {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Il rapporto di aspetto dell'immagine è troppo grande ({{ ratio }}). Il rapporto massimo consentito è {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + Il rapporto di aspetto dell'immagine è troppo piccolo ({{ ratio }}). Il rapporto minimo consentito è {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + L'immagine è quadrata ({{ width }}x{{ height }}px). Le immagini quadrate non sono consentite. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + L'immagine è orizzontale ({{ width }}x{{ height }}px). Le immagini orizzontali non sono consentite. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + L'immagine è verticale ({{ width }}x{{ height }}px). Le immagini verticali non sono consentite. + + + An empty file is not allowed. + Un file vuoto non è consentito. + + + The host could not be resolved. + L'host non può essere risolto. + + + This value does not match the expected {{ charset }} charset. + Questo valore non corrisponde al charset {{ charset }}. + + + This is not a valid Business Identifier Code (BIC). + Questo valore non è un codice BIC valido. + + + Error + Errore + + + This is not a valid UUID. + Questo non è un UUID valido. + + + This value should be a multiple of {{ compared_value }}. + Questo valore dovrebbe essere un multiplo di {{ compared_value }}. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Questo codice identificativo bancario (BIC) non è associato all'IBAN {{ iban }}. + + + This value should be valid JSON. + Questo valore dovrebbe essere un JSON valido. + + + This collection should contain only unique elements. + Questa collezione dovrebbe contenere solo elementi unici. + + + This value should be positive. + Questo valore dovrebbe essere positivo. + + + This value should be either positive or zero. + Questo valore dovrebbe essere positivo oppure zero. + + + This value should be negative. + Questo valore dovrebbe essere negativo. + + + This value should be either negative or zero. + Questo valore dovrebbe essere negativo oppure zero. + + + This value is not a valid timezone. + Questo valore non è un fuso orario valido. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Questa password è trapelata durante una compromissione di dati, non deve essere usata. Si prega di usare una password diversa. + + + This value should be between {{ min }} and {{ max }}. + Questo valore dovrebbe essere compreso tra {{ min }} e {{ max }}. + + + This value is not a valid hostname. + Questo valore non è un nome di host valido. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + Il numero di elementi in questa collezione dovrebbe essere un multiplo di {{ compared_value }}. + + + This value should satisfy at least one of the following constraints: + Questo valore dovrebbe soddisfare almeno uno dei vincoli seguenti: + + + Each element of this collection should satisfy its own set of constraints. + Ciascun elemento di questa collezione dovrebbe soddisfare il suo insieme di vincoli. + + + This value is not a valid International Securities Identification Number (ISIN). + Questo valore non è un codice identificativo internazionale di valori mobiliari (ISIN) valido. + + + This value should be a valid expression. + Questo valore dovrebbe essere un'espressione valida. + + + This value is not a valid CSS color. + Questo valore non è un colore CSS valido. + + + This value is not a valid CIDR notation. + Questo valore non è una notazione CIDR valida. + + + The value of the netmask should be between {{ min }} and {{ max }}. + Il valore della netmask dovrebbe essere compreso tra {{ min }} e {{ max }}. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.ja.xlf b/src/vendor/symfony/validator/Resources/translations/validators.ja.xlf new file mode 100644 index 0000000..9feed48 --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.ja.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + falseでなければなりません。 + + + This value should be true. + trueでなければなりません。 + + + This value should be of type {{ type }}. + 型は{{ type }}でなければなりません。 + + + This value should be blank. + 空でなければなりません。 + + + The value you selected is not a valid choice. + 有効な選択肢ではありません。 + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + {{ limit }}個以上選択してください。 + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + {{ limit }}個以内で選択してください。 + + + One or more of the given values is invalid. + 無効な選択肢が含まれています。 + + + This field was not expected. + このフィールドは予期されていませんでした。 + + + This field is missing. + このフィールドは、欠落しています。 + + + This value is not a valid date. + 有効な日付ではありません。 + + + This value is not a valid datetime. + 有効な日時ではありません。 + + + This value is not a valid email address. + 有効なメールアドレスではありません。 + + + The file could not be found. + ファイルが見つかりません。 + + + The file is not readable. + ファイルを読み込めません。 + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + ファイルのサイズが大きすぎます({{ size }} {{ suffix }})。有効な最大サイズは{{ limit }} {{ suffix }}です。 + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + ファイルのMIMEタイプが無効です({{ type }})。有効なMIMEタイプは{{ types }}です。 + + + This value should be {{ limit }} or less. + {{ limit }}以下でなければなりません。 + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + 値が長すぎます。{{ limit }}文字以内でなければなりません。 + + + This value should be {{ limit }} or more. + {{ limit }}以上でなければなりません。 + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + 値が短すぎます。{{ limit }}文字以上でなければなりません。 + + + This value should not be blank. + 空であってはなりません。 + + + This value should not be null. + nullであってはなりません。 + + + This value should be null. + nullでなければなりません。 + + + This value is not valid. + 有効な値ではありません。 + + + This value is not a valid time. + 有効な時刻ではありません。 + + + This value is not a valid URL. + 有効なURLではありません。 + + + The two values should be equal. + 2つの値が同じでなければなりません。 + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + ファイルのサイズが大きすぎます。有効な最大サイズは{{ limit }} {{ suffix }}です。 + + + The file is too large. + ファイルのサイズが大きすぎます。 + + + The file could not be uploaded. + ファイルをアップロードできませんでした。 + + + This value should be a valid number. + 有効な数字ではありません。 + + + This file is not a valid image. + ファイルが画像ではありません。 + + + This is not a valid IP address. + 有効なIPアドレスではありません。 + + + This value is not a valid language. + 有効な言語名ではありません。 + + + This value is not a valid locale. + 有効なロケールではありません。 + + + This value is not a valid country. + 有効な国名ではありません。 + + + This value is already used. + 既に使用されています。 + + + The size of the image could not be detected. + 画像のサイズが検出できません。 + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + 画像の幅が大きすぎます({{ width }}ピクセル)。{{ max_width }}ピクセルまでにしてください。 + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + 画像の幅が小さすぎます({{ width }}ピクセル)。{{ min_width }}ピクセル以上にしてください。 + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + 画像の高さが大きすぎます({{ height }}ピクセル)。{{ max_height }}ピクセルまでにしてください。 + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + 画像の高さが小さすぎます({{ height }}ピクセル)。{{ min_height }}ピクセル以上にしてください。 + + + This value should be the user's current password. + ユーザーの現在のパスワードでなければなりません。 + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + ちょうど{{ limit }}文字でなければなりません。 + + + The file was only partially uploaded. + ファイルのアップロードは完全ではありません。 + + + No file was uploaded. + ファイルがアップロードされていません。 + + + No temporary folder was configured in php.ini. + php.iniで一時フォルダが設定されていません。 + + + Cannot write temporary file to disk. + 一時ファイルをディスクに書き込むことができません。 + + + A PHP extension caused the upload to fail. + PHP拡張によってアップロードに失敗しました。 + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + {{ limit }}個以上の要素を含んでなければいけません。 + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + 要素は{{ limit }}個までです。 + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + 要素はちょうど{{ limit }}個でなければなりません。 + + + Invalid card number. + 無効なカード番号です。 + + + Unsupported card type or invalid card number. + 未対応のカード種類又は無効なカード番号です。 + + + This is not a valid International Bank Account Number (IBAN). + 有効なIBANコードではありません。 + + + This value is not a valid ISBN-10. + 有効なISBN-10コードではありません。 + + + This value is not a valid ISBN-13. + 有効なISBN-13コードではありません。 + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + 有効なISBN-10コード又はISBN-13コードではありません。 + + + This value is not a valid ISSN. + 有効なISSNコードではありません。 + + + This value is not a valid currency. + 有効な貨幣ではありません。 + + + This value should be equal to {{ compared_value }}. + {{ compared_value }}と等しくなければなりません。 + + + This value should be greater than {{ compared_value }}. + {{ compared_value }}より大きくなければなりません。 + + + This value should be greater than or equal to {{ compared_value }}. + {{ compared_value }}以上でなければなりません。 + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + {{ compared_value_type }}としての{{ compared_value }}と等しくなければなりません。 + + + This value should be less than {{ compared_value }}. + {{ compared_value }}未満でなければなりません。 + + + This value should be less than or equal to {{ compared_value }}. + {{ compared_value }}以下でなければなりません。 + + + This value should not be equal to {{ compared_value }}. + {{ compared_value }}と等しくてはいけません。 + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + {{ compared_value_type }}としての{{ compared_value }}と等しくてはいけません。 + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + 画像のアスペクト比が大きすぎます({{ ratio }})。{{ max_ratio }}までにしてください。 + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + 画像のアスペクト比が小さすぎます({{ ratio }})。{{ min_ratio }}以上にしてください。 + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + 画像が正方形になっています({{ width }}x{{ height }}ピクセル)。正方形の画像は許可されていません。 + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + 画像が横向きになっています({{ width }}x{{ height }}ピクセル)。横向きの画像は許可されていません。 + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + 画像が縦向きになっています({{ width }}x{{ height }}ピクセル)。縦向きの画像は許可されていません。 + + + An empty file is not allowed. + 空のファイルは許可されていません。 + + + The host could not be resolved. + ホストを解決できませんでした。 + + + This value does not match the expected {{ charset }} charset. + この値は予期される文字コード({{ charset }})と異なります。 + + + This is not a valid Business Identifier Code (BIC). + 有効なSWIFTコードではありません。 + + + Error + エラー + + + This is not a valid UUID. + 有効なUUIDではありません。 + + + This value should be a multiple of {{ compared_value }}. + {{ compared_value }}の倍数でなければなりません。 + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + このSWIFTコードはIBANコード({{ iban }})に関連付けられていません。 + + + This value should be valid JSON. + JSONでなければなりません。 + + + This collection should contain only unique elements. + 要素は重複してはなりません。 + + + This value should be positive. + 正の数でなければなりません。 + + + This value should be either positive or zero. + 正の数、または0でなければなりません。 + + + This value should be negative. + 負の数でなければなりません。 + + + This value should be either negative or zero. + 負の数、または0でなければなりません。 + + + This value is not a valid timezone. + 有効なタイムゾーンではありません。 + + + This password has been leaked in a data breach, it must not be used. Please use another password. + このパスワードは漏洩している為使用できません。 + + + This value should be between {{ min }} and {{ max }}. + {{ min }}以上{{ max }}以下でなければなりません。 + + + This value is not a valid hostname. + 有効なホスト名ではありません。 + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + 要素の数は{{ compared_value }}の倍数でなければなりません。 + + + This value should satisfy at least one of the following constraints: + 以下の制約のうち少なくとも1つを満たす必要があります: + + + Each element of this collection should satisfy its own set of constraints. + コレクションの各要素は、それぞれの制約を満たす必要があります。 + + + This value is not a valid International Securities Identification Number (ISIN). + この値は有効な国際証券識別番号(ISIN)ではありません。 + + + This value should be a valid expression. + 式でなければなりません。 + + + This value is not a valid CSS color. + この値は有効なCSSカラーではありません。 + + + This value is not a valid CIDR notation. + この値は有効なCIDR表記ではありません。 + + + The value of the netmask should be between {{ min }} and {{ max }}. + ネットマスクの値は、{{ min }}から{{ max }}の間にある必要があります。 + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.lb.xlf b/src/vendor/symfony/validator/Resources/translations/validators.lb.xlf new file mode 100644 index 0000000..f27bbd4 --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.lb.xlf @@ -0,0 +1,391 @@ + + + + + + This value should be false. + Dëse Wäert sollt falsch sinn. + + + This value should be true. + Dëse Wäert sollt wouer sinn. + + + This value should be of type {{ type }}. + Dëse Wäert sollt vum Typ {{ type }} sinn. + + + This value should be blank. + Dëse Wäert sollt eidel sinn. + + + The value you selected is not a valid choice. + Dëse Wäert sollt enger vun de Wielméiglechkeeten entspriechen. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Et muss mindestens {{ limit }} Méiglechkeet ausgewielt ginn.|Et musse mindestens {{ limit }} Méiglechkeeten ausgewielt ginn. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Et dierf héchstens {{ limit }} Méiglechkeet ausgewielt ginn.|Et dierfen héchstens {{ limit }} Méiglechkeeten ausgewielt ginn. + + + One or more of the given values is invalid. + Een oder méi vun de Wäerter ass ongëlteg. + + + This field was not expected. + D'Feld gouf net erwaart. + + + This field is missing. + D'Feld feelt. + + + This value is not a valid date. + Dëse Wäert entsprécht kenger gëlteger Datumsangab. + + + This value is not a valid datetime. + Dëse Wäert entsprécht kenger gëlteger Datums- an Zäitangab. + + + This value is not a valid email address. + Dëse Wäert ass keng gëlteg Email-Adress. + + + The file could not be found. + De Fichier gouf net fonnt. + + + The file is not readable. + De Fichier ass net liesbar. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + De Fichier ass ze grouss ({{ size }} {{ suffix }}). Déi zougeloosse Maximalgréisst bedréit {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + Den Typ vum Fichier ass ongëlteg ({{ type }}). Erlaabten Type sinn {{ types }}. + + + This value should be {{ limit }} or less. + Dëse Wäert soll méi kleng oder gläich {{ limit }} sinn. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Dës Zeecheketten ass ze laang. Se sollt héchstens {{ limit }} Zeechen hunn. + + + This value should be {{ limit }} or more. + Dëse Wäert sollt méi grouss oder gläich {{ limit }} sinn. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Dës Zeecheketten ass ze kuerz. Se sollt mindestens {{ limit }} Zeechen hunn. + + + This value should not be blank. + Dëse Wäert sollt net eidel sinn. + + + This value should not be null. + Dëst sollt keen Null-Wäert sinn. + + + This value should be null. + Dëst sollt keen Null-Wäert sinn. + + + This value is not valid. + Dëse Wäert ass net gëlteg. + + + This value is not a valid time. + Dëse Wäert entsprécht kenger gëlteger Zäitangab. + + + This value is not a valid URL. + Dëse Wäert ass keng gëlteg URL. + + + The two values should be equal. + Béid Wäerter sollten identesch sinn. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + De fichier ass ze grouss. Déi maximal Gréisst dierf {{ limit }} {{ suffix }} net depasséieren. + + + The file is too large. + De Fichier ass ze grouss. + + + The file could not be uploaded. + De Fichier konnt net eropgeluede ginn. + + + This value should be a valid number. + Dëse Wäert sollt eng gëlteg Zuel sinn. + + + This file is not a valid image. + Dëse Fichier ass kee gëltegt Bild. + + + This is not a valid IP address. + Dëst ass keng gëlteg IP-Adress. + + + This value is not a valid language. + Dëse Wäert entsprécht kenger gëlteger Sprooch. + + + This value is not a valid locale. + Dëse Wäert entsprécht kengem gëltege Gebittsschema. + + + This value is not a valid country. + Dëse Wäert entsprécht kengem gëltege Land. + + + This value is already used. + Dëse Wäert gëtt scho benotzt. + + + The size of the image could not be detected. + D'Gréisst vum Bild konnt net detektéiert ginn. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + D'Breet vum Bild ass ze grouss ({{ width }}px). Déi erlaabte maximal Breet ass {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + D'Breet vum Bild ass ze kleng ({{ width }}px). Déi minimal Breet ass {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + D'Héicht vum Bild ass ze grouss ({{ height }}px). Déi erlaabte maximal Héicht ass {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + D'Héicht vum Bild ass ze kleng ({{ height }}px). Déi minimal Héicht ass {{ min_height }}px. + + + This value should be the user's current password. + Dëse Wäert sollt dem aktuelle Benotzerpasswuert entspriechen. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Dëse Wäert sollt exakt {{ limit }} Buschtaf hunn.|Dëse Wäert sollt exakt {{ limit }} Buschtawen hunn. + + + The file was only partially uploaded. + De Fichier gouf just deelweis eropgelueden. + + + No file was uploaded. + Et gouf kee Fichier eropgelueden. + + + No temporary folder was configured in php.ini. + Et gouf keen temporären Dossier an der php.ini konfiguréiert oder den temporären Dossier existéiert net. + + + Cannot write temporary file to disk. + Den temporäre Fichier kann net gespäichert ginn. + + + A PHP extension caused the upload to fail. + Eng PHP-Erweiderung huet den Upload verhënnert. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Dës Sammlung sollt {{ limit }} oder méi Elementer hunn. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Dës Sammlung sollt {{ limit }} oder manner Elementer hunn. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Dës Sammlung sollt exakt {{ limit }} Element hunn.|Dës Sammlung sollt exakt {{ limit }} Elementer hunn. + + + Invalid card number. + Ongëlteg Kaartennummer. + + + Unsupported card type or invalid card number. + Net ënnerstëtzte Kaartentyp oder ongëlteg Kaartennummer. + + + This is not a valid International Bank Account Number (IBAN). + Dëst ass keng gëlteg IBAN-Kontonummer. + + + This value is not a valid ISBN-10. + Dëse Wäert ass keng gëlteg ISBN-10. + + + This value is not a valid ISBN-13. + Dëse Wäert ass keng gëlteg ISBN-13. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Dëse Wäert ass weder eng gëlteg ISBN-10 nach eng gëlteg ISBN-13. + + + This value is not a valid ISSN. + Dëse Wäert ass keng gëlteg ISSN. + + + This value is not a valid currency. + Dëse Wäert ass keng gëlteg Währung. + + + This value should be equal to {{ compared_value }}. + Dëse Wäert sollt {{ compared_value }} sinn. + + + This value should be greater than {{ compared_value }}. + Dëse Wäert sollt méi grouss wéi {{ compared_value }} sinn. + + + This value should be greater than or equal to {{ compared_value }}. + Dëse Wäert sollt méi grouss wéi oder gläich {{ compared_value }} sinn. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Dëse Wäert sollt identesch si mat {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Dëse Wäert sollt méi kleng wéi {{ compared_value }} sinn. + + + This value should be less than or equal to {{ compared_value }}. + Dëse Wäert sollt méi kleng wéi oder gläich {{ compared_value }} sinn. + + + This value should not be equal to {{ compared_value }}. + Dëse Wäert sollt net {{ compared_value }} sinn. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Dëse Wäert sollt net identesch si mat {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + D'Säiteverhältnis vum Bild ass ze grouss ({{ ratio }}). Den erlaabte Maximalwäert ass {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + D'Säiteverhältnis vum Bild ass ze kleng ({{ ratio }}). Den erwaarte Minimalwäert ass {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + D'Bild ass quadratesch ({{ width }}x{{ height }}px). Quadratesch Biller sinn net erlaabt. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + D'Bild ass am Queeschformat ({{ width }}x{{ height }}px). Biller am Queeschformat sinn net erlaabt. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + D'Bild ass am Héichformat ({{ width }}x{{ height }}px). Biller am Héichformat sinn net erlaabt. + + + An empty file is not allowed. + En eidele Fichier ass net erlaabt. + + + The host could not be resolved. + Den Host-Numm konnt net opgeléist ginn. + + + This value does not match the expected {{ charset }} charset. + Dëse Wäert entsprécht net dem erwaarten Zeechesaz {{ charset }}. + + + This is not a valid Business Identifier Code (BIC). + Dëst ass kee gëltege "Business Identifier Code" (BIC). + + + Error + Feeler + + + This is not a valid UUID. + Dëst ass keng gëlteg UUID. + + + This value should be a multiple of {{ compared_value }}. + Dëse Wäert sollt e puer vun {{ compared_value }} sinn. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Dëse "Business Identifier Code" (BIC) ass net mat IBAN verbonnen {{ iban }}. + + + This value should be valid JSON. + Dëse Wäert sollt gëlteg JSON. + + + This collection should contain only unique elements. + Dës Sammlung sollt just eenzegaarteg Elementer enthalen. + + + This value should be positive. + Dëse Wäert sollt positiv sinn. + + + This value should be either positive or zero. + Dëse Wäert sollt entweeder positiv oder null sinn. + + + This value should be negative. + Dëse Wäert sollt negativ sinn. + + + This value should be either negative or zero. + Dëse Wäert sollt entweeder negativ oder null sinn. + + + This value is not a valid timezone. + Dëse Wäert ass keng gëlteg Zäitzon. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Dëst Passwuert war Deel vun engem Dateleck an dierf net benotzt ginn. Benotzt w.e.g. en anert Passwuert . + + + This value should be between {{ min }} and {{ max }}. + De Wäert sollt tëscht {{ min }} a(n) {{ max }} leien. + + + This value is not a valid hostname. + Dëse Wäert ass kee gëltegen Hostnumm. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + D'Unzuel un Elementer an dëser Sammlung sollt e multipel vu(n) {{ compared_value }} sinn. + + + This value should satisfy at least one of the following constraints: + Dëse Wäert sollt op d'mannst ee vun dësen Aschränkungen erfëllen: + + + Each element of this collection should satisfy its own set of constraints. + All Element aus dëser Sammlung sollt seng eegen Aschränkungen erfëllen. + + + This value is not a valid International Securities Identification Number (ISIN). + Dëse Wäert ass keng gëlteg International Wäertpabeiererkennnummer (ISIN). + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.lt.xlf b/src/vendor/symfony/validator/Resources/translations/validators.lt.xlf new file mode 100644 index 0000000..7a2c4c5 --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.lt.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + Reikšmė turi būti neigiama. + + + This value should be true. + Reikšmė turi būti teigiama. + + + This value should be of type {{ type }}. + Šios reikšmės tipas turi būti {{ type }}. + + + This value should be blank. + Ši reikšmė turi būti tuščia. + + + The value you selected is not a valid choice. + Neteisingas pasirinkimas. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Turite pasirinkti bent {{ limit }} variantą.|Turite pasirinkti bent {{ limit }} variantus.|Turite pasirinkti bent {{ limit }} variantų. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Turite pasirinkti ne daugiau kaip {{ limit }} variantą.|Turite pasirinkti ne daugiau kaip {{ limit }} variantus.|Turite pasirinkti ne daugiau kaip {{ limit }} variantų. + + + One or more of the given values is invalid. + Viena ar daugiau įvestų reikšmių yra netinkamos. + + + This field was not expected. + Nebuvo tikimasi Šis laukas. + + + This field is missing. + Šiame lauke yra dingęs. + + + This value is not a valid date. + Ši reikšmė nėra data. + + + This value is not a valid datetime. + Ši reikšmė nera data ir laikas. + + + This value is not a valid email address. + Ši reikšmė nėra tinkamas el. pašto adresas. + + + The file could not be found. + Byla nerasta. + + + The file is not readable. + Negalima nuskaityti bylos. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + Byla yra per didelė ({{ size }} {{ suffix }}). Maksimalus dydis {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + Netinkamas bylos tipas (mime type) ({{ type }}). Galimi bylų tipai {{ types }}. + + + This value should be {{ limit }} or less. + Reikšmė turi būti {{ limit }} arba mažiau. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Per didelis simbolių skaičius. Turi susidaryti iš {{ limit }} arba mažiau simbolių.|Per didelis simbolių skaičius. Turi susidaryti iš {{ limit }} arba mažiau simbolių.|Per didelis simbolių skaičius. Turi susidaryti iš {{ limit }} arba mažiau simbolių. + + + This value should be {{ limit }} or more. + Reikšmė turi būti {{ limit }} arba daugiau. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Per mažas simbolių skaičius. Turi susidaryti iš {{ limit }} arba daugiau simbolių.|Per mažas simbolių skaičius. Turi susidaryti iš {{ limit }} arba daugiau simbolių.|Per mažas simbolių skaičius. Turi susidaryti iš {{ limit }} arba daugiau simbolių. + + + This value should not be blank. + Ši reikšmė negali būti tuščia. + + + This value should not be null. + Ši reikšmė negali būti null. + + + This value should be null. + Ši reikšmė turi būti null. + + + This value is not valid. + Netinkama reikšmė. + + + This value is not a valid time. + Ši reikšmė nėra laikas. + + + This value is not a valid URL. + Ši reikšmė nėra tinkamas interneto adresas. + + + The two values should be equal. + Abi reikšmės turi būti identiškos. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + Byla yra per didelė. Maksimalus dydis yra {{ limit }} {{ suffix }}. + + + The file is too large. + Byla per didelė. + + + The file could not be uploaded. + Byla negali būti įkelta. + + + This value should be a valid number. + Ši reikšmė turi būti skaičius. + + + This file is not a valid image. + Byla nėra paveikslėlis. + + + This is not a valid IP address. + Ši reikšmė nėra tinkamas IP adresas. + + + This value is not a valid language. + Ši reikšmė nėra tinkama kalba. + + + This value is not a valid locale. + Ši reikšmė nėra tinkama lokalė. + + + This value is not a valid country. + Ši reikšmė nėra tinkama šalis. + + + This value is already used. + Ši reikšmė jau yra naudojama. + + + The size of the image could not be detected. + Nepavyko nustatyti nuotraukos dydžio. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + Nuotraukos plotis per didelis ({{ width }}px). Maksimalus leidžiamas plotis yra {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + Nuotraukos plotis per mažas ({{ width }}px). Minimalus leidžiamas plotis yra {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + Nuotraukos aukštis per didelis ({{ height }}px). Maksimalus leidžiamas aukštis yra {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + Nuotraukos aukštis per mažas ({{ height }}px). Minimalus leidžiamas aukštis yra {{ min_height }}px. + + + This value should be the user's current password. + Ši reikšmė turi sutapti su dabartiniu naudotojo slaptažodžiu. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Ši reikšmė turi turėti lygiai {{ limit }} simbolį.|Ši reikšmė turi turėti lygiai {{ limit }} simbolius.|Ši reikšmė turi turėti lygiai {{ limit }} simbolių. + + + The file was only partially uploaded. + Failas buvo tik dalinai įkeltas. + + + No file was uploaded. + Nebuvo įkelta jokių failų. + + + No temporary folder was configured in php.ini. + Nėra sukonfiguruoto jokio laikino katalogo php.ini faile. + + + Cannot write temporary file to disk. + Nepavyko išsaugoti laikino failo. + + + A PHP extension caused the upload to fail. + PHP plėtinys sutrukdė failo įkėlimą ir jis nepavyko. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Sąraše turi būti {{ limit }} arba daugiau įrašų.|Sąraše turi būti {{ limit }} arba daugiau įrašų.|Sąraše turi būti {{ limit }} arba daugiau įrašų. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Sąraše turi būti {{ limit }} arba mažiau įrašų.|Sąraše turi būti {{ limit }} arba mažiau įrašų.|Sąraše turi būti {{ limit }} arba mažiau įrašų. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Sąraše turi būti lygiai {{ limit }} įrašas.|Sąraše turi būti lygiai {{ limit }} įrašai.|Sąraše turi būti lygiai {{ limit }} įrašų. + + + Invalid card number. + Klaidingas kortelės numeris. + + + Unsupported card type or invalid card number. + Kortelės tipas nepalaikomas arba klaidingas kortelės numeris. + + + This is not a valid International Bank Account Number (IBAN). + Ši reišmė neatitinka tarptautinio banko sąskaitos numerio formato (IBAN). + + + This value is not a valid ISBN-10. + Ši reikšmė neatitinka ISBN-10 formato. + + + This value is not a valid ISBN-13. + Ši reikšmė neatitinka ISBN-13 formato. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Ši reikšmė neatitinka nei ISBN-10, nei ISBN-13 formato. + + + This value is not a valid ISSN. + Ši reišmė neatitinka ISSN formato. + + + This value is not a valid currency. + Netinkamas valiutos formatas. + + + This value should be equal to {{ compared_value }}. + Ši reikšmė turi būti lygi {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + Ši reikšmė turi būti didesnė už {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + Ši reikšmė turi būti didesnė už arba lygi {{ compared_value }}. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Ši reikšmė turi būti identiška {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Ši reikšmė turi būti mažesnė už {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + Ši reikšmė turi būti mažesnė už arba lygi {{ compared_value }}. + + + This value should not be equal to {{ compared_value }}. + Ši reikšmė neturi būti lygi {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Ši reikšmė neturi būti identiška {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Nuotraukos santykis yra per didelis ({{ ratio }}). Didžiausias leistinas santykis yra {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + Nuotraukos santykis yra per mažas ({{ ratio }}). Mažiausias leistinas santykis yra {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + Nuotrauka yra kvadratinė ({{ width }}x{{ height }}px). Kvadratinės nuotraukos nėra leistinos. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + Nuotrauka orientuota į plotį ({{ width }}x{{ height }}px). Nuotraukos orientuotos į plotį nėra leistinos. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + Nuotrauka orientuota į aukštį ({{ width }}x{{ height }}px). Nuotraukos orientuotos į aukštį nėra leistinos. + + + An empty file is not allowed. + Failas negali būti tuščias. + + + The host could not be resolved. + Serveris nepasiekiamas. + + + This value does not match the expected {{ charset }} charset. + Ši reikšmė neatitinka {{ charset }} koduotės. + + + This is not a valid Business Identifier Code (BIC). + Bendrovės Identifikavimo Kodas (BIC) nėra tinkamas. + + + Error + Klaida + + + This is not a valid UUID. + Ši reikšmė nėra tinkamas UUID. + + + This value should be a multiple of {{ compared_value }}. + Ši reikšmė turi būti skaičiaus {{ compared_value }} kartotinis. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Šis bendrovės identifikavimo kodas (BIC) nesusijęs su IBAN {{ iban }}. + + + This value should be valid JSON. + Ši reikšmė turi būti tinkamo JSON formato. + + + This collection should contain only unique elements. + Sąraše turi būti tik unikalios reikšmės. + + + This value should be positive. + Reikšmė turi būti teigiama. + + + This value should be either positive or zero. + Reikšmė turi būti teigiama arba lygi nuliui. + + + This value should be negative. + Reikšmė turi būti neigiama. + + + This value should be either negative or zero. + Reikšmė turi būti neigiama arba lygi nuliui. + + + This value is not a valid timezone. + Reikšmė nėra tinkama laiko juosta. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Slaptažodis yra nutekėjęs duomenų saugumo pažeidime, jo naudoti negalima. Prašome naudoti kitą slaptažodį. + + + This value should be between {{ min }} and {{ max }}. + Ši reikšmė turi būti tarp {{ min }} ir {{ max }}. + + + This value is not a valid hostname. + Ši reikšmė nėra tinkamas svetainės adresas. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + Šio sąrašo elementų skaičius turėtų būti skaičiaus {{ compared_value }} kartotinis. + + + This value should satisfy at least one of the following constraints: + Ši reikšmė turėtų atitikti bent vieną iš šių nurodymų: + + + Each element of this collection should satisfy its own set of constraints. + Kiekvienas šio sąrašo elementas turi atitikti savo nurodymų rinkinį. + + + This value is not a valid International Securities Identification Number (ISIN). + Ši reišmė neatitinka tarptautinio vertybinių popierių identifikavimo numerio formato (ISIN). + + + This value should be a valid expression. + Ši vertė turėtų būti teisinga išraiška. + + + This value is not a valid CSS color. + Ši reikšmė nėra tinkama CSS spalva. + + + This value is not a valid CIDR notation. + Ši vertė nėra tinkamas CIDR žymėjimas. + + + The value of the netmask should be between {{ min }} and {{ max }}. + Tinklo kaukės reikšmė turi būti nuo {{ min }} iki {{ max }}. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.lv.xlf b/src/vendor/symfony/validator/Resources/translations/validators.lv.xlf new file mode 100644 index 0000000..fc71d5f --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.lv.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + Šai vērtībai ir jābūt nepatiesai. + + + This value should be true. + Šai vērtībai ir jābūt patiesai. + + + This value should be of type {{ type }}. + Šīs vērtības tipam ir jābūt {{ type }}. + + + This value should be blank. + Šai vērtībai ir jābūt tukšai. + + + The value you selected is not a valid choice. + Vērtība, kuru jūs izvēlējāties nav derīga izvēle. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Jums nav jāveic izvēle.|Jums ir jāveic vismaz {{ limit }} izvēle.|Jums ir jāveic vismaz {{ limit }} izvēles. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Jums nav jāveic izvēle.|Jums ir jāveic ne vairāk kā {{ limit }} izvēle.|Jums ir jāveic ne vairāk kā {{ limit }} izvēles. + + + One or more of the given values is invalid. + Viena vai vairākas no dotajām vērtībām ir nederīgas. + + + This field was not expected. + Šis lauks netika gaidīts. + + + This field is missing. + Šis lauks ir pazudis. + + + This value is not a valid date. + Šī vērtība ir nederīgs datums. + + + This value is not a valid datetime. + Šī vērtība ir nederīgs datums un laiks + + + This value is not a valid email address. + Šī vērtība ir nederīga e-pasta adrese. + + + The file could not be found. + Fails nav atrasts. + + + The file is not readable. + Fails nav lasāms. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + Fails ir pārāk liels ({{ size }} {{ suffix }}). Atļautais maksimālais izmērs ir {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + Faila mime tips nav derīgs ({{ type }}). Atļautie mime tipi ir {{ types }}. + + + This value should be {{ limit }} or less. + Šai vērtībai ir jābūt ne vairāk kā {{ limit }}. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Šīs vērtības garums ir 0 rakstzīmju.|Šī vērtība ir pārāk gara. Tai būtu jābūt ne vairāk kā {{ limit }} rakstzīmei.|Šī vērtība ir pārāk gara. Tai būtu jābūt ne vairāk kā {{ limit }} rakstzīmēm. + + + This value should be {{ limit }} or more. + Šai vērtībai ir jābūt ne mazāk kā {{ limit }}. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Šīs vērtības garums ir 0 rakstzīmju.|Šī vērtība ir pārāk īsa. Tai būtu jābūt ne mazāk kā {{ limit }} rakstzīmei.|Šī vērtība ir pārāk īsa. Tai būtu jābūt ne mazāk kā {{ limit }} rakstzīmēm. + + + This value should not be blank. + Šai vērtībai nav jābūt tukšai. + + + This value should not be null. + Šai vērtībai nav jābūt null. + + + This value should be null. + Šai vērtībai ir jābūt null. + + + This value is not valid. + Šī vērtība ir nederīga. + + + This value is not a valid time. + Šī vērtība ir nederīgs laiks. + + + This value is not a valid URL. + Šī vērtība ir nederīgs URL. + + + The two values should be equal. + Abām vērtībām jābūt vienādam. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + Fails ir pārāk liels. Atļautais maksimālais izmērs ir {{ limit }} {{ suffix }}. + + + The file is too large. + Fails ir pārāk liels. + + + The file could not be uploaded. + Failu nevarēja augšupielādēt. + + + This value should be a valid number. + Šai vērtībai ir jābūt derīgam skaitlim. + + + This file is not a valid image. + Šis fails nav derīgs attēls. + + + This is not a valid IP address. + Šī nav derīga IP adrese. + + + This value is not a valid language. + Šī vērtība nav derīga valoda. + + + This value is not a valid locale. + Šī vērtība nav derīga lokalizācija. + + + This value is not a valid country. + Šī vērtība nav derīga valsts. + + + This value is already used. + Šī vērtība jau tiek izmantota. + + + The size of the image could not be detected. + Nevar noteikt attēla izmēru. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + Attēla platums ir pārāk liels ({{ width }}px). Atļautais maksimālais platums ir {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + Attēla platums ir pārāk mazs ({{ width }}px). Minimālais sagaidāmais platums ir {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + Attēla augstums ir pārāk liels ({{ height }}px). Atļautais maksimālais augstums ir {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + Attēla augstums ir pārāk mazs ({{ height }}px). Minimālais sagaidāmais augstums ir {{ min_height }}px. + + + This value should be the user's current password. + Šai vērtībai ir jābūt lietotāja pašreizējai parolei. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Šīs vērtības garums ir 0 rakstzīmju.|Šai vērtībai ir jābūt tieši {{ limit }} rakstzīmei.|Šai vērtībai ir jābūt tieši {{ limit }} rakstzīmēm. + + + The file was only partially uploaded. + Fails bija tikai daļēji augšupielādēts. + + + No file was uploaded. + Fails netika augšupielādēts. + + + No temporary folder was configured in php.ini. + Pagaidu mape php.ini failā nav nokonfigurēta. + + + Cannot write temporary file to disk. + Nevar ierakstīt pagaidu failu uz diska. + + + A PHP extension caused the upload to fail. + PHP paplašinājums izraisīja augšupielādes neizdošanos. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Šis krājums satur 0 elementu.|Šim krājumam jāsatur vismaz {{ limit }} elementu.|Šim krājumam jāsatur vismaz {{ limit }} elementus. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Šis krājums satur 0 elementu.|Šim krājumam jāsatur ne vairāk kā {{ limit }} elementu.|Šim krājumam jāsatur ne vairāk kā {{ limit }} elementus. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Šis krājums satur 0 elementu.|Šim krājumam jāsatur tieši {{ limit }} elementu.|Šim krājumam jāsatur tieši {{ limit }} elementus. + + + Invalid card number. + Nederīgs kartes numurs. + + + Unsupported card type or invalid card number. + Neatbalstīts kartes tips vai nederīgs kartes numurs. + + + This is not a valid International Bank Account Number (IBAN). + Šis nav derīgs starptautisks banku konta numurs (IBAN). + + + This value is not a valid ISBN-10. + Šī vērtība nav derīgs ISBN-10 numurs. + + + This value is not a valid ISBN-13. + Šī vērtība nav derīgs ISBN-13 numurs + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Šī vērtība neatbilst ne derīgam ISBN-10 numuram, ne derīgm ISBN-13 numuram. + + + This value is not a valid ISSN. + Šī vērtība nav derīgs ISSN numurs + + + This value is not a valid currency. + Šī vērtība nav derīga valūta + + + This value should be equal to {{ compared_value }}. + Šai vērtībai ir jābūt vienādai ar {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + Šai vērtībai ir jābūt lielākai par {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + Šai vērtībai ir jābūt lielākai vai vienādai ar {{ compared_value }}. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Šai vērtībai ir jābūt identiskai ar {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Šai vērtībai ir jābūt mazākai par {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + Šai vērtībai ir jābūt mazākai vai vienādai ar {{ compared_value }}. + + + This value should not be equal to {{ compared_value }}. + Šai vērtībai ir jābūt vienādai ar {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Šai vērtībai nav jābūt identiskai ar {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Attēla attiecība ir pārāk liela ({{ ratio }}). Atļautā maksimālā attiecība ir {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + Attēla attiecība ir pārāk maza ({{ ratio }}). Minimālā sagaidāmā attiecība ir {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + Šis attēls ir kvadrāts ({{ width }}x{{ height }}px). Kvadrātveida attēli nav atļauti. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + Attēls ir orientēts kā ainava ({{ width }}x{{ height }}px). Attēli, kas ir orientēti kā ainavas nav atļauti. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + Attēls ir orientēts kā portrets ({{ width }}x{{ height }}px). Attēli, kas ir orientēti kā portreti nav atļauti. + + + An empty file is not allowed. + Tukšs fails nav atļauts. + + + The host could not be resolved. + Resursdatora nosaukumu nevar atrisināt. + + + This value does not match the expected {{ charset }} charset. + Šī vērtība neatbilst sagaidāmajai rakstzīmju kopai {{ charset }}. + + + This is not a valid Business Identifier Code (BIC). + Šī vērtība nav derīgs Biznesa Identifikācijas Kods (BIC). + + + Error + Kļūda + + + This is not a valid UUID. + Šis nav derīgs UUID. + + + This value should be a multiple of {{ compared_value }}. + Šai vērtībai jābūt vairākas reizes atkārtotai {{ compared_value }}. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Šis Biznesa Identifikācijas Kods (BIC) neatbilst {{ iban }} konta numuram (IBAN). + + + This value should be valid JSON. + Šai vērtībai jābūt derīgam JSON. + + + This collection should contain only unique elements. + Šai kolekcijai jāsatur tikai unikāli elementi. + + + This value should be positive. + Šai vērtībai jābūt pozitīvai. + + + This value should be either positive or zero. + Šai vērtībai jābūt pozitīvai vai vienādai ar nulli. + + + This value should be negative. + Šai vērtībai jābūt negatīvai. + + + This value should be either negative or zero. + Šai vērtībai jābūt negatīvai vai vienādai ar nulli. + + + This value is not a valid timezone. + Šī vērtība nav derīga laika zona. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Šī parole tika publicēta datu noplūdē, viņu nedrīkst izmantot. Lūdzu, izvēlieties citu paroli. + + + This value should be between {{ min }} and {{ max }}. + Šai vērtībai jābūt starp {{ min }} un {{ max }}. + + + This value is not a valid hostname. + Šī vērtība nav derīgs tīmekļa servera nosaukums. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + Elementu skaitam šajā kolekcijā jābūt {{ compared_value }} reizinājumam. + + + This value should satisfy at least one of the following constraints: + Šai vērtībai jāiekļaujas vismaz vienā no sekojošiem ierobežojumiem: + + + Each element of this collection should satisfy its own set of constraints. + Šīs kolekcijas katram elementam jāiekļaujas savā ierobežojumu kopā. + + + This value is not a valid International Securities Identification Number (ISIN). + Šī vērtība nav derīgs starptautiskais vērtspapīru identifikācijas numurs (ISIN). + + + This value should be a valid expression. + Šai vērtībai jābūt korektai izteiksmei. + + + This value is not a valid CSS color. + Šī vērtība nav korekta CSS krāsa. + + + This value is not a valid CIDR notation. + Šī vērtība nav korekts CIDR apzīmējums. + + + The value of the netmask should be between {{ min }} and {{ max }}. + Tīkla maskas (netmask) vērtībai jābūt starp {{ min }} un {{ max }}. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.mn.xlf b/src/vendor/symfony/validator/Resources/translations/validators.mn.xlf new file mode 100644 index 0000000..b767dc8 --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.mn.xlf @@ -0,0 +1,391 @@ + + + + + + This value should be false. + Энэ утга буруу байх ёстой. + + + This value should be true. + Энэ утга үнэн байх ёстой. + + + This value should be of type {{ type }}. + Энэ утга {{ type }} -н төрөл байх ёстой. + + + This value should be blank. + Энэ утга хоосон байх ёстой. + + + The value you selected is not a valid choice. + Сонгосон утга буруу байна. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Хамгийн багадаа {{ limit }} утга сонгогдсон байх ёстой. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Хамгийн ихдээ {{ limit }} утга сонгогдох боломжтой. + + + One or more of the given values is invalid. + Өгөгдсөн нэг эсвэл нэгээс олон утга буруу байна. + + + This field was not expected. + Энэ талбар нь хүлээгдэж байсан юм. + + + This field is missing. + Энэ талбар нь алга болсон байна. + + + This value is not a valid date. + Энэ утга буруу date төрөл байна . + + + This value is not a valid datetime. + Энэ утга буруу цаг төрөл байна. + + + This value is not a valid email address. + И-майл хаяг буруу байна. + + + The file could not be found. + Файл олдсонгүй. + + + The file is not readable. + Файл уншигдахуйц биш байна. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + Файл хэтэрхий том байна ({{ size }} {{ suffix }}). Зөвшөөрөгдөх дээд хэмжээ {{ limit }} {{ suffix }} байна. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + Файлын MIME-төрөл нь буруу байна ({{ type }}). Зөвшөөрөгдөх MIME-төрлүүд {{ types }}. + + + This value should be {{ limit }} or less. + Энэ утга {{ limit }} юмуу эсвэл бага байна. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Энэ утга хэтэрхий урт байна. {{ limit }} тэмдэгтийн урттай юмуу эсвэл бага байна. + + + This value should be {{ limit }} or more. + Энэ утга {{ limit }} юмуу эсвэл их байна. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Энэ утга хэтэрхий богино байна. {{ limit }} тэмдэгт эсвэл их байна. + + + This value should not be blank. + Энэ утга хоосон байж болохгүй. + + + This value should not be null. + Энэ утга null байж болохгүй. + + + This value should be null. + Энэ утга null байна. + + + This value is not valid. + Энэ утга буруу байна. + + + This value is not a valid time. + Энэ утга буруу цаг төрөл байна. + + + This value is not a valid URL. + Энэ утга буруу URL байна . + + + The two values should be equal. + Хоёр утгууд ижил байх ёстой. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + Файл хэтэрхий том байна. Зөвшөөрөгдөх дээд хэмжээ нь {{ limit }} {{ suffix }} байна. + + + The file is too large. + Файл хэтэрхий том байна. + + + The file could not be uploaded. + Файл upload хийгдсэнгүй. + + + This value should be a valid number. + Энэ утга зөвхөн тоо байна. + + + This file is not a valid image. + Файл зураг биш байна. + + + This is not a valid IP address. + IP хаяг зөв биш байна. + + + This value is not a valid language. + Энэ утга үнэн зөв хэл биш байна. + + + This value is not a valid locale. + Энэ утга үнэн зөв байршил биш байна. + + + This value is not a valid country. + Энэ утга үнэн бодит улс биш байна. + + + This value is already used. + Энэ утга аль хэдийнээ хэрэглэгдсэн байна. + + + The size of the image could not be detected. + Зургийн хэмжээ тогтоогдож чадсангүй. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + Зургийн өргөн хэтэрхий том байна ({{ width }}px). Өргөн нь хамгийн ихдээ {{ max_width }}px байх боломжтой. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + Зургийн өргөн хэтэрхий жижиг байна ({{ width }}px). Өргөн нь хамгийн багадаа {{ min_width }}px байх боломжтой. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + Зургийн өндөр хэтэрхий том байна ({{ height }}px). Өндөр нь хамгийн ихдээ {{ max_height }}px байх боломжтой. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + Зургийн өндөр хэтэрхий жижиг байна ({{ height }}px). Өндөр нь хамгийн багадаа {{ min_height }}px байх боломжтой. + + + This value should be the user's current password. + Энэ утга хэрэглэгчийн одоогийн нууц үг байх ёстой. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Энэ утга яг {{ limit }} тэмдэгт байх ёстой.|Энэ утга яг {{ limit }} тэмдэгт байх ёстой. + + + The file was only partially uploaded. + Файлын зөвхөн хагас нь upload хийгдсэн. + + + No file was uploaded. + Ямар ч файл upload хийгдсэнгүй. + + + No temporary folder was configured in php.ini. + php.ini дээр түр зуурын хавтсыг тохируулаагүй байна, эсвэл тохируулсан хавтас байхгүй байна. + + + Cannot write temporary file to disk. + Түр зуурын файлыг диск руу бичиж болохгүй байна. + + + A PHP extension caused the upload to fail. + PHP extension нь upload -г амжилтгүй болгоод байна. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Энэ коллекц {{ limit }} ба түүнээс дээш тооны элемент агуулах ёстой.|Энэ коллекц {{ limit }} ба түүнээс дээш тооны элемент агуулах ёстой. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Энэ коллекц {{ limit }} ба түүнээс доош тооны элемент агуулах ёстой.|Энэ коллекц {{ limit }} ба түүнээс доош тооны элемент агуулах ёстой. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Энэ коллекц яг {{ limit }} элемент агуулах ёстой.|Энэ коллекц яг {{ limit }} элемент агуулах ёстой. + + + Invalid card number. + Картын дугаар буруу байна. + + + Unsupported card type or invalid card number. + Дэмжигдээгүй картын төрөл эсвэл картын дугаар буруу байна. + + + This is not a valid International Bank Account Number (IBAN). + Энэ утга үнэн зөв Олон Улсын Банкны Дансны Дугаар (IBAN) биш байна. + + + This value is not a valid ISBN-10. + Энэ утга үнэн зөв ISBN-10 биш байна. + + + This value is not a valid ISBN-13. + Энэ утга үнэн зөв ISBN-13 биш байна. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Энэ утга үнэн зөв ISBN-10 юмуу ISBN-13 биш байна. + + + This value is not a valid ISSN. + Энэ утга үнэн зөв ISSN биш байна. + + + This value is not a valid currency. + Энэ утга үнэн бодит валют биш байна. + + + This value should be equal to {{ compared_value }}. + Энэ утга {{ compared_value }} -тaй тэнцүү байх ёстой. + + + This value should be greater than {{ compared_value }}. + Энэ утга {{ compared_value }} -с их байх ёстой. + + + This value should be greater than or equal to {{ compared_value }}. + Энэ утга {{ compared_value }} -тай тэнцүү юмуу эсвэл их байх ёстой. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Энэ утга {{ compared_value_type }} {{ compared_value }} -тай яг ижил байх ёстой. + + + This value should be less than {{ compared_value }}. + Энэ утга {{ compared_value }} -с бага байх ёстой. + + + This value should be less than or equal to {{ compared_value }}. + Энэ утга {{ compared_value }} -тай ижил юмуу эсвэл бага байх ёстой. + + + This value should not be equal to {{ compared_value }}. + Энэ утга {{ compared_value }} -тай тэнцүү байх ёсгүй. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Энэ утга {{ compared_value_type }} {{ compared_value }} -тай яг ижил байх ёсгүй. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Зургийн харьцаа хэтэрхий том байна ({{ ratio }}). Харьцаа нь хамгийн ихдээ {{ max_ratio }} байна. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + Зургийн харьцаа хэтэрхий жижиг байна ({{ ratio }}). Харьцаа нь хамгийн багадаа {{ min_ratio }} байна. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + Зураг дөрвөлжин хэлбэртэй байна ({{ width }}x{{ height }}px). Дөрвөлжин зургууд оруулах боломжгүй. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + Зураг хэвтээ байрлалтай байна ({{ width }}x{{ height }}px). Хэвтээ байрлалтай зургууд оруулах боломжгүй. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + Зургууд босоо байрлалтай байна ({{ width }}x{{ height }}px). Босоо байрлалтай зургууд оруулах боломжгүй. + + + An empty file is not allowed. + Хоосон файл оруулах боломжгүй. + + + The host could not be resolved. + Хост зөв тохирогдоогүй байна. + + + This value does not match the expected {{ charset }} charset. + Энэ утга тооцоолсон {{ charset }} тэмдэгттэй таарахгүй байна. + + + This is not a valid Business Identifier Code (BIC). + Энэ утга үнэн зөв Business Identifier Code (BIC) биш байна. + + + Error + Алдаа + + + This is not a valid UUID. + Энэ утга үнэн зөв UUID биш байна. + + + This value should be a multiple of {{ compared_value }}. + Энэ утга {{ compared_value }} -н үржвэр байх ёстой. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Энэ Business Identifier Code (BIC) код нь IBAN {{ iban }} -тай холбоогүй байна. + + + This value should be valid JSON. + Энэ утга JSON байх ёстой. + + + This collection should contain only unique elements. + Энэ коллекц зөвхөн давтагдахгүй элементүүд агуулах ёстой. + + + This value should be positive. + Энэ утга эерэг байх ёстой. + + + This value should be either positive or zero. + Энэ утга тэг эсвэл эерэг байх ёстой. + + + This value should be negative. + Энэ утга сөрөг байх ёстой. + + + This value should be either negative or zero. + Энэ утга сөрөг эсвэл тэг байх ёстой. + + + This value is not a valid timezone. + Энэ утга үнэн зөв цагийн бүс биш байна. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Энэ нууц үгийн мэдээлэл алдагдсан байх магадлалтай учраас дахин ашиглагдах ёсгүй. Өөр нууц үг ашиглана уу. + + + This value should be between {{ min }} and {{ max }}. + Энэ утга {{ min }} -с {{ max }} хооронд байх ёстой. + + + This value is not a valid hostname. + Энэ утга буруу hostname байна. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + Энэхүү цуглуулган дахь элемэнтийн тоо, {{ compared_value }}-н үржвэр байх ёстой. + + + This value should satisfy at least one of the following constraints: + Энэ утга доорх болзолуудын ядаж нэгийг хангах ёстой: + + + Each element of this collection should satisfy its own set of constraints. + Энэхүү цуглуулган дахь элемэнтүүд өөр өөрсдийн болзолуудаа хангах ёстой. + + + This value is not a valid International Securities Identification Number (ISIN). + Энэ утга зөв International Securities Identification Number (ISIN) биш байна. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.my.xlf b/src/vendor/symfony/validator/Resources/translations/validators.my.xlf new file mode 100644 index 0000000..7f45aae --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.my.xlf @@ -0,0 +1,395 @@ + + + + + + This value should be false. + ဤတန်ဖိုးသည် false ဖြစ်ရမည်။ + + + This value should be true. + ဤတန်ဖိုးသည် true ဖြစ်ရမည်။ + + + This value should be of type {{ type }}. + ဤတန်ဖိုးသည် {{ type }} အမျိုးအစားဖြစ်ရမည်။ + + + This value should be blank. + ဤတန်ဖိုးသည် ကွပ်လပ်မဖြစ်သင့်ပါ။ + + + The value you selected is not a valid choice. + သင်ရွေးချယ်သောတန်ဖိုးသည် သင့်လျှော်သော် တန်ဖိုးမဟုတ်ပါ။ + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + သင်သည် အနည်းဆုံးရွေးချယ်မှု {{ limit }} ခုရွေးချယ်ရမည်။ + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + သင်သည်အများဆုံး {{ limit }} ခုသာရွေးချယ်ခွင့်ရှိသည်။ + + + One or more of the given values is invalid. + ပေးထားသောတန်ဖိုးတစ်ခု (သို့မဟုတ်) တစ်ခုထက်ပို၍မမှန်ကန်ပါ။ + + + This field was not expected. + ဤကွက်လပ်ကိုမမျှော်လင့်ထားပါ။ + + + This field is missing. + ဤကွက်လပ်ကိုမမျှော်လင့်ထားပါ။ + + + This value is not a valid date. + ဤတန်ဖိုးသည်မှန်ကန်သော်ရက်စွဲမဟုတ်ပါ။ + + + This value is not a valid datetime. + ဤတန်ဖိုးသည် မှန်ကန်သော် ရက်စွဲ/အချိန် မဟုတ်ပါ။ + + + This value is not a valid email address. + ဤတန်ဖိုးသည် မှန်ကန်သော် အီးမေးလိပ်စာ မဟုတ်ပါ။ + + + The file could not be found. + ဖိုင်ရှာမတွေ့ပါ။ + + + The file is not readable. + ဤဖိုင်ကို ဖတ်၍မရပါ။ + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + ဖိုင်အရွယ်အစား အလွန်ကြီးနေသည် ({{ size }} {{ suffix }}). ခွင့်ပြုထားသော အများဆုံး ဖိုင်ဆိုဒ်သည် {{ limit }} {{ suffix }} ဖြစ်သည်။ + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + ဖိုင်၏ mime အမျိုးအစားမမှန်ကန်ပါ ({{ type }})။ ခွင့်ပြုထားသော mime အမျိုးအစားများမှာ {{ types }}. + + + This value should be {{ limit }} or less. + ဤတန်ဖိုးသည် {{ limit }} (သို့မဟုတ်) {{ limit }} ထက်နည်းသင့်သည်။ + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + ဤတန်ဖိုးသည် အလွန်ရှည်လွန်းသည်။ ၎င်းတွင်အက္ခရာ {{ limit }} (သို့မဟုတ်) ၎င်းထက်နည်းသင့်သည်။ | ဤတန်ဖိုးသည် အလွန်ရှည်လွန်းသည်။ ၎င်းတွင်အက္ခရာ {{limit}} ခုနှင့်အထက်ရှိသင့်သည်။ + + + This value should be {{ limit }} or more. + ဤတန်ဖိုးသည် {{limit}} (သို့မဟုတ်) ထို့ထက်ပိုသင့်သည်။ + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + ဤတန်ဖိုးသည် အလွန်တိုလွန်းသည်။ ၎င်းတွင်အက္ခရာ {{limit}} (သို့မဟုတ်) ထို့ထက်ပိုရှိသင့်သည်။ | ဤတန်ဖိုးသည်တိုလွန်းသည်။ ၎င်းတွင်အက္ခရာ {{limit}} လုံးနှင့်အထက်ရှိသင့်သည်။ + + + This value should not be blank. + ဤတန်ဖိုးသည်ကွက်လပ်မဖြစ်သင့်ပါ။ + + + This value should not be null. + ဤတန်ဖိုးသည် null မဖြစ်သင့်ပါ။ + + + This value should be null. + ဤတန်ဖိုးသည် null ဖြစ်သင့်သည်။ + + + This value is not valid. + ဤတန်ဖိုးသည်မှန်ကန်သောတန်ဖိုးမဟုတ်ပါ။ + + + This value is not a valid time. + ဤတန်ဖိုးသည်မှန်ကန်သော အချိန်တန်ဖိုးမဟုတ်ပါ။ + + + This value is not a valid URL. + ဤတန်ဖိုးသည်မှန်ကန်သော URL တန်ဖိုးမဟုတ်ပါ။ + + + The two values should be equal. + တန်ဖိုးနှစ်ခုသည် တူညီသင့်သည်။ + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + ဤဖိုင်သည် အလွန်ကြီးသည်။ ခွင့်ပြုထားသည့်အများဆုံးဖိုင်အရွယ်အစားသည် {{ limit }} {{ suffix }} ဖြစ်သည်။ + + + The file is too large. + ဤဖိုင်သည် အလွန်ကြီးသည်။ + + + The file could not be uploaded. + ဤဖိုင်ကိုတင်၍မရပါ။ + + + This value should be a valid number. + ဤတန်ဖိုးသည်မှန်ကန်သောနံပါတ်ဖြစ်သင့်သည်။ + + + This file is not a valid image. + ဤဖိုင်သည်မှန်ကန်သော ဓါတ်ပုံမဟုတ်ပါ။ + + + This is not a valid IP address. + ၎င်းသည်တရားဝင် IP လိပ်စာမဟုတ်ပါ။ + + + This value is not a valid language. + ဤတန်ဖိုးသည် မှန်ကန်သောဘာသာစကားမဟုတ်ပါ။ + + + This value is not a valid locale. + ဤတန်ဖိုးသည်မှန်ကန်သောဘာသာပြန်မဟုတ်ပါ။ + + + This value is not a valid country. + ဤတန်ဖိုးသည်မှန်ကန်သောနိုင်ငံမဟုတ်ပါ။ + + + This value is already used. + ဤတန်ဖိုးသည် အသုံးပြုပြီးသားဖြစ်သည်။ + + + The size of the image could not be detected. + ဓါတ်ပုံအရွယ်အစားကိုရှာမတွေ့ပါ။ + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + ပုံ၏အလျားသည် ကြီးလွန်းသည် ({{ width }}px)။ ခွင့်ပြုထားသည့်အများဆုံးအလျားသည် {{max_width}}px ဖြစ်သည်။ + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + ပုံ၏အလျားသည် သေးလွန်းသည် ({{ width }}px)။ ခွင့်ပြုထားသည့်အနည်းဆုံးအလျားသည် {{max_width}}px ဖြစ်သည်။ + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + ပုံ၏အနံသည် ကြီးလွန်းသည် ({{ height }}px)။ ခွင့်ပြုထားသည့်အများဆုံးအနံသည် {{max_height}}px ဖြစ်သည်။ + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + ပုံ၏အနံသည် သေးလွန်းသည် ({{ height }}px)။ ခွင့်ပြုထားသည့်အနည်းဆုံးအနံသည် {{min_height}}px ဖြစ်သည်။ + + + This value should be the user's current password. + ဤတန်ဖိုးသည်အသုံးပြုသူ၏ လက်ရှိစကားဝှက်ဖြစ်သင့်သည်။ + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + ဤတန်ဖိုးသည်စာလုံး {{limit}} အတိအကျရှိသင့်သည်။ + + + The file was only partially uploaded. + ဤဖိုင်သည်တစ်စိတ်တစ်ပိုင်းသာ upload တင်ခဲ့သည်။ + + + No file was uploaded. + မည်သည့် ဖိုင်မျှ upload မလုပ်ခဲ့ပါ။ + + + No temporary folder was configured in php.ini. + php.ini တွင်ယာယီဖိုင်တွဲကိုပြင်ဆင်ထားခြင်းမရှိပါ၊ + + + Cannot write temporary file to disk. + ယာရီဖိုင်ကို disk မရေးနိုင်ပါ။ + + + A PHP extension caused the upload to fail. + PHP extension တစ်ခုကြောင့် upload တင်၍မရနိုင်ပါ။ + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + ဤ collection တွင် {{limit}} element (သို့မဟုတ်) ထို့ထက်မပိုသင့်ပါ။ + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + ဤ collection တွင် {{limit}} element (သို့မဟုတ်) ၎င်းထက်နည်းသင့်သည်။ + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + ဤစုစည်းမှုတွင် {{limit}} element အတိအကျပါသင့်သည်။ + + + Invalid card number. + ကဒ်နံပါတ်မမှန်ပါ။ + + + Unsupported card type or invalid card number. + ကဒ်အမျိုးအစားမမှန်ပါ (သို့မဟုတ်) ကဒ်နံပါတ်မမှန်ပါ။ + + + This is not a valid International Bank Account Number (IBAN). + ဤတန်ဖိုးသည် တရား၀င်နိုင်ငံတကာဘဏ်အကောင့်နံပါတ် (International Bank Account Number, IBAN) မဟုတ်ပါ။ + + + This value is not a valid ISBN-10. + ဤတန်ဖိုးသည် မှန်ကန်သော ISBN-10 တန်ဖိုးမဟုတ်ပါ၊ + + + This value is not a valid ISBN-13. + ဤတန်ဖိုးသည် မှန်ကန်သော ISBN-13 တန်ဖိုးမဟုတ်ပါ၊ + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + ဤတန်ဖိုးသည် သင့်လျှော်သော် ISBN-10 (သို့မဟုတ်) ISBN-13 တန်ဖိုးမဟုတ်ပါ၊ + + + This value is not a valid ISSN. + ဤတန်ဖိုးသည် သင့်လျှော်သော် ISSN တန်ဖိုးမဟုတ်ပါ။ + + + This value is not a valid currency. + ဤတန်ဖိုးသည် သင့်လျှော်သော် ငွေကြေးတန်ဖိုးမဟုတ်ပါ။ + + + This value should be equal to {{ compared_value }}. + ဤတန်ဖိုးသည် {{ compared_value }} နှင့်ညီသင့်သည်။ + + + This value should be greater than {{ compared_value }}. + ဤတန်ဖိုးသည် {{ compared_value }} ထက်ကြီးသင့်သည်။ + + + This value should be greater than or equal to {{ compared_value }}. + ဤတန်ဖိုးသည် {{ compared_value }} ထက်ကြီးသင့်သည် (သို့မဟုတ်) ဤတန်ဖိုးသည် {{ compared_value }} ညီသင့်သည်။ + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + ဤတန်ဖိုးသည် {{ compared_value_type }} {{ compared_value }} နှင့်ထပ်တူညီမျှသင့်သည်။ + + + This value should be less than {{ compared_value }}. + ဤတန်ဖိုးသည် {{ compared_value }} ထက်မနဲသောတဲ့ တန်ဖိုးဖြစ်သင့်သည်။ + + + This value should be less than or equal to {{ compared_value }}. + ဤတန်ဖိုးသည် {{ compared_value }} ထက် မနည်းသောတန်ဖိုး (သို့မဟုတ်) ညီမျှသောတန်ဖိုးဖြစ်သင့်သည်။ + + + This value should not be equal to {{ compared_value }}. + ဤတန်ဖိုးသည် {{ compared_value }} နှင့်မညီသင့်ပါ။ + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + ဤတန်ဖိုးသည် {{ compared_value_type }} {{ compared_value }} နှင့်ထပ်တူမညီမျှသင့်သည်။ + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + ဤဓာတ်ပုံအချိုးအစားသည်အလွန်ကြီးလွန်းသည်။ ({{ ratio }})။ ခွင့်ပြုထားသောဓာတ်ပုံအချိုးအသားသည် {{ max_ratio }} ဖြစ်သည်။ + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + ဤဓာတ်ပုံအချိုးအစားသည်အလွန်သေးလွန်းသည်။ ({{ ratio }})။ ခွင့်ပြုထားသောဓာတ်ပုံအချိုးအသားသည် {{ min_ratio }} ဖြစ်သည်။ + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + ဤဓာတ်ပုံသည် စတုရန်းဖြစ်နေသည် ({{ width }}x{{ height }}px)။ စတုရန်းဓာတ်ပုံများကို ခွင့်မပြုပါ။ + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + ဤဓာတ်ပုံသည် အလျှားလိုက်ဖြစ်နေသည် ({{ width }}x{{ height }}px). အလျှားလိုက်ဓာတ်ပုံများခွင့်မပြုပါ။ + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + ဤဓာတ်ပုံသည် ဒေါင်လိုက်ဖြစ်နေသည် ({{ width }}x{{ height }}px). ဒေါင်လိုက်ဓာတ်ပုံများခွင့်မပြုပါ။ + + + An empty file is not allowed. + ဖိုင်အလွတ်ကိုတင်ခွင့်မပြုပါ။ + + + The host could not be resolved. + host ဖြေရှင်း၍မနိုင်ပါ။ + + + This value does not match the expected {{ charset }} charset. + ဤတန်ဖိုးသည် မျှော်မှန်းထားသော {{ charset }} စားလုံးနှင့် ကိုက်ညီမှုမရှိပါ။ + + + This is not a valid Business Identifier Code (BIC). + ၎င်းသည်မှန်ကန်သော Business Identifier Code (BIC) မဟုတ်ပါ။ + + + Error + အမှား + + + This is not a valid UUID. + ဤတန်ဖိုးသည် သင့်လျှော်သော် UUID မဟုတ်ပါ။ + + + This value should be a multiple of {{ compared_value }}. + ဤတန်ဖိုးသည် {{compared_value}} ၏ စတူတန်ဖိုးဖြစ်သင့်သည်။ + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + ဤ Business Identifier Code (BIC) သည် IBAN {{ iban }} နှင့်ဆက်စပ်မှုမရှိပါ။ + + + This value should be valid JSON. + ဤတန်ဖိုးသည် သင့်လျှော်သော် JSON တန်ဖိုးဖြစ်သင့်သည်။ + + + This collection should contain only unique elements. + ဤ collection ကိုယ်ပိုင် elements များ ပါသင့်သည်။ + + + This value should be positive. + ဤတန်ဖိုးသည် အပေါင်းဖြစ်သင့်သည်။ + + + This value should be either positive or zero. + ဤတန်ဖိုးသည် အပေါင်း (သို့မဟုတ်) သုည ဖြစ်သင့်သည်။ + + + This value should be negative. + ဤတန်ဖိုးသည် အနုတ် ဖြစ်သင့်သည်။ + + + This value should be either negative or zero. + ဤတန်ဖိုးသည် အနုတ် (သို့မဟုတ်) သုည ဖြစ်သင့်သည်။ + + + This value is not a valid timezone. + ဤတန်ဖိုးသည် မှန်ကန်သော အချိန်ဇုန်မဟုတ်ပါ။ + + + This password has been leaked in a data breach, it must not be used. Please use another password. + ဤစကားဝှက် သည် ဒေတာပေါက်ကြားမှုတစ်ခုဖြစ်ခဲ့သည်။ ဤစကား၀ှက်ကိုအသုံးမပြုရပါ။ ကျေးဇူးပြု၍ အခြားစကားဝှက်ကိုသုံးပါ။ + + + This value should be between {{ min }} and {{ max }}. + ဤတန်ဖိုးသည် {{ min }} နှင့် {{ max }} ကြားရှိသင့်သည်။ + + + This value is not a valid hostname. + ဤတန်ဖိုးသည် သင့်လျှော်သော် hostname မဟုတ်ပါ။ + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + ဤ collection တွင်ပါပါ၀င်သော elements အရေအတွက်သည် {{ compared_value }} ၏ စတူဖြစ်သင့်သည်။ + + + This value should satisfy at least one of the following constraints: + ဤတန်ဖိုးသည် အောက်ပါကန့်သတ်ချက်များအနက်မှအနည်းဆုံးတစ်ခု ဖြည့်ဆည်းပေးသင့်သည်။ + + + Each element of this collection should satisfy its own set of constraints. + ဤ collection ၏ element တစ်ခုစီသည်၎င်း၏ကိုယ်ပိုင်ကန့်သတ်ချက်များကိုဖြည့်ဆည်းသင့်သည်။ + + + This value is not a valid International Securities Identification Number (ISIN). + ဤတန်ဖိုးသည် သင့်လျှော်သော် အပြည်ပြည်ဆိုင်ရာငွေချေးသက်သေခံနံပါတ် ,International Securities Identification Number (ISIN) မဟုတ်ပါ။ + + + This value should be a valid expression. + ဤတန်ဖိုးသည်မှန်ကန်သောစကားရပ်ဖြစ်သင့်သည်။ + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.nb.xlf b/src/vendor/symfony/validator/Resources/translations/validators.nb.xlf new file mode 100644 index 0000000..5e1ebc1 --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.nb.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + Verdien må være usann. + + + This value should be true. + Verdien må være sann. + + + This value should be of type {{ type }}. + Verdien skal ha typen {{ type }}. + + + This value should be blank. + Verdien skal være blank. + + + The value you selected is not a valid choice. + Den valgte verdien er ikke gyldig. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Du må velge minst {{ limit }} valg. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Du kan maks velge {{ limit }} valg. + + + One or more of the given values is invalid. + En eller flere av de oppgitte verdiene er ugyldige. + + + This field was not expected. + Dette feltet var ikke forventet. + + + This field is missing. + Dette feltet mangler. + + + This value is not a valid date. + Verdien er ikke en gyldig dato. + + + This value is not a valid datetime. + Verdien er ikke en gyldig dato/tid. + + + This value is not a valid email address. + Verdien er ikke en gyldig e-postadresse. + + + The file could not be found. + Filen kunne ikke finnes. + + + The file is not readable. + Filen er ikke lesbar. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + Filen er for stor ({{ size }} {{ suffix }}). Tilatte maksimale størrelse {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + Mimetypen av filen er ugyldig ({{ type }}). Tilatte mimetyper er {{ types }}. + + + This value should be {{ limit }} or less. + Verdien må være {{ limit }} tegn lang eller mindre. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Verdien er for lang. Den må ha {{ limit }} tegn eller mindre. + + + This value should be {{ limit }} or more. + Verdien må være {{ limit }} eller mer. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Verdien er for kort. Den må ha {{ limit }} tegn eller flere. + + + This value should not be blank. + Verdien kan ikke være blank. + + + This value should not be null. + Verdien kan ikke være tom (null). + + + This value should be null. + Verdien skal være tom (null). + + + This value is not valid. + Verdien er ugyldig. + + + This value is not a valid time. + Verdien er ikke en gyldig tid. + + + This value is not a valid URL. + Verdien er ikke en gyldig URL. + + + The two values should be equal. + Verdiene skal være identiske. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + Filen er for stor. Den maksimale størrelsen er {{ limit }} {{ suffix }}. + + + The file is too large. + Filen er for stor. + + + The file could not be uploaded. + Filen kunne ikke lastes opp. + + + This value should be a valid number. + Verdien skal være et gyldig tall. + + + This file is not a valid image. + Denne filen er ikke et gyldig bilde. + + + This is not a valid IP address. + Dette er ikke en gyldig IP adresse. + + + This value is not a valid language. + Verdien er ikke et gyldig språk. + + + This value is not a valid locale. + Verdien er ikke en gyldig lokalitet. + + + This value is not a valid country. + Verdien er ikke et gyldig navn på land. + + + This value is already used. + Verdien er allerede brukt. + + + The size of the image could not be detected. + Bildestørrelsen kunne ikke oppdages. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + Bildebredden er for stor ({{ width }} piksler). Tillatt maksimumsbredde er {{ max_width }} piksler. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + Bildebredden er for liten ({{ width }} piksler). Forventet minimumsbredde er {{ min_width }} piksler. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + Bildehøyden er for stor ({{ height }} piksler). Tillatt maksimumshøyde er {{ max_height }} piksler. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + Bildehøyden er for liten ({{ height }} piksler). Forventet minimumshøyde er {{ min_height }} piksler. + + + This value should be the user's current password. + Verdien skal være brukerens sitt nåværende passord. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Verdien skal være nøyaktig {{ limit }} tegn. + + + The file was only partially uploaded. + Filen var kun delvis opplastet. + + + No file was uploaded. + Ingen fil var lastet opp. + + + No temporary folder was configured in php.ini. + Den midlertidige mappen (tmp) er ikke konfigurert i php.ini. + + + Cannot write temporary file to disk. + Kan ikke skrive midlertidig fil til disk. + + + A PHP extension caused the upload to fail. + En PHP-utvidelse forårsaket en feil under opplasting. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Denne samlingen må inneholde {{ limit }} element eller flere.|Denne samlingen må inneholde {{ limit }} elementer eller flere. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Denne samlingen må inneholde {{ limit }} element eller færre.|Denne samlingen må inneholde {{ limit }} elementer eller færre. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Denne samlingen må inneholde nøyaktig {{ limit }} element.|Denne samlingen må inneholde nøyaktig {{ limit }} elementer. + + + Invalid card number. + Ugyldig kortnummer. + + + Unsupported card type or invalid card number. + Korttypen er ikke støttet eller kortnummeret er ugyldig. + + + This is not a valid International Bank Account Number (IBAN). + Dette er ikke et gyldig IBAN-nummer. + + + This value is not a valid ISBN-10. + Verdien er ikke en gyldig ISBN-10. + + + This value is not a valid ISBN-13. + Verdien er ikke en gyldig ISBN-13. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Verdien er hverken en gyldig ISBN-10 eller ISBN-13. + + + This value is not a valid ISSN. + Verdien er ikke en gyldig ISSN. + + + This value is not a valid currency. + Verdien er ikke gyldig valuta. + + + This value should be equal to {{ compared_value }}. + Verdien skal være lik {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + Verdien skal være større enn {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + Verdien skal være større enn eller lik {{ compared_value }}. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Verdien skal være identisk med {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Verdien skal være mindre enn {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + Verdien skal være mindre enn eller lik {{ compared_value }}. + + + This value should not be equal to {{ compared_value }}. + Verdien skal ikke være lik {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Verdien skal ikke være identisk med {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Bildeforholdet er for stort ({{ ratio }}). Tillatt bildeforhold er maks {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + Bildeforholdet er for lite ({{ ratio }}). Forventet bildeforhold er minst {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + Bildet er en kvadrat ({{ width }}x{{ height }}px). Kvadratiske bilder er ikke tillatt. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + Bildet er i liggende retning ({{ width }}x{{ height }}px). Bilder i liggende retning er ikke tillatt. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + Bildet er i stående retning ({{ width }}x{{ height }}px). Bilder i stående retning er ikke tillatt. + + + An empty file is not allowed. + Tomme filer er ikke tilatt. + + + The host could not be resolved. + Vertsnavn kunne ikke løses. + + + This value does not match the expected {{ charset }} charset. + Verdien samsvarer ikke med forventet tegnsett {{ charset }}. + + + This is not a valid Business Identifier Code (BIC). + Dette er ikke en gyldig BIC. + + + Error + Feil + + + This is not a valid UUID. + Dette er ikke en gyldig UUID. + + + This value should be a multiple of {{ compared_value }}. + Verdien skal være flertall av {{ compared_value }}. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Business Identifier Code (BIC) er ikke tilknyttet en IBAN {{ iban }}. + + + This value should be valid JSON. + Verdien er ikke gyldig JSON. + + + This collection should contain only unique elements. + Samlingen kan kun inneholde unike elementer. + + + This value should be positive. + Denne verdien må være positiv. + + + This value should be either positive or zero. + Denne verdien må være positiv eller null. + + + This value should be negative. + Denne verdien må være negativ. + + + This value should be either negative or zero. + Denne verdien må være negativ eller null. + + + This value is not a valid timezone. + Verdien er ikke en gyldig tidssone. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Dette passordet er lekket i et datainnbrudd, det må ikke tas i bruk. Vennligst bruk et annet passord. + + + This value should be between {{ min }} and {{ max }}. + Verdien må være mellom {{ min }} og {{ max }}. + + + This value is not a valid hostname. + Denne verdien er ikke et gyldig vertsnavn. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + Antall elementer i denne samlingen bør være et multiplum av {{ compared_value }}. + + + This value should satisfy at least one of the following constraints: + Denne verdien skal tilfredsstille minst en av følgende begrensninger: + + + Each element of this collection should satisfy its own set of constraints. + Hvert element i denne samlingen skal tilfredsstille sitt eget sett med begrensninger. + + + This value is not a valid International Securities Identification Number (ISIN). + Denne verdien er ikke et gyldig International Securities Identification Number (ISIN). + + + This value should be a valid expression. + Denne verdien skal være et gyldig uttrykk. + + + This value is not a valid CSS color. + Denne verdien er ikke en gyldig CSS-farge. + + + This value is not a valid CIDR notation. + Denne verdien er ikke en gyldig CIDR-notasjon. + + + The value of the netmask should be between {{ min }} and {{ max }}. + Verdien på nettmasken skal være mellom {{ min }} og {{ max }}. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.nl.xlf b/src/vendor/symfony/validator/Resources/translations/validators.nl.xlf new file mode 100644 index 0000000..97d1da0 --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.nl.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + Deze waarde moet onwaar zijn. + + + This value should be true. + Deze waarde moet waar zijn. + + + This value should be of type {{ type }}. + Deze waarde moet van het type {{ type }} zijn. + + + This value should be blank. + Deze waarde moet leeg zijn. + + + The value you selected is not a valid choice. + De geselecteerde waarde is geen geldige optie. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Selecteer ten minste {{ limit }} optie.|Selecteer ten minste {{ limit }} opties. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Selecteer maximaal {{ limit }} optie.|Selecteer maximaal {{ limit }} opties. + + + One or more of the given values is invalid. + Eén of meer van de ingegeven waarden zijn ongeldig. + + + This field was not expected. + Dit veld werd niet verwacht. + + + This field is missing. + Dit veld ontbreekt. + + + This value is not a valid date. + Deze waarde is geen geldige datum. + + + This value is not a valid datetime. + Deze waarde is geen geldige datum en tijd. + + + This value is not a valid email address. + Deze waarde is geen geldig e-mailadres. + + + The file could not be found. + Het bestand kon niet gevonden worden. + + + The file is not readable. + Het bestand is niet leesbaar. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + Het bestand is te groot ({{ size }} {{ suffix }}). Toegestane maximum grootte is {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + Het mime type van het bestand is ongeldig ({{ type }}). Toegestane mime types zijn {{ types }}. + + + This value should be {{ limit }} or less. + Deze waarde moet {{ limit }} of minder zijn. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Deze waarde is te lang. Hij mag maximaal {{ limit }} teken bevatten.|Deze waarde is te lang. Hij mag maximaal {{ limit }} tekens bevatten. + + + This value should be {{ limit }} or more. + Deze waarde moet {{ limit }} of meer zijn. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Deze waarde is te kort. Hij moet tenminste {{ limit }} teken bevatten.|Deze waarde is te kort. Hij moet tenminste {{ limit }} tekens bevatten. + + + This value should not be blank. + Deze waarde mag niet leeg zijn. + + + This value should not be null. + Deze waarde mag niet null zijn. + + + This value should be null. + Deze waarde moet null zijn. + + + This value is not valid. + Deze waarde is niet geldig. + + + This value is not a valid time. + Deze waarde is geen geldige tijd. + + + This value is not a valid URL. + Deze waarde is geen geldige URL. + + + The two values should be equal. + De twee waarden moeten gelijk zijn. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + Het bestand is te groot. Toegestane maximum grootte is {{ limit }} {{ suffix }}. + + + The file is too large. + Het bestand is te groot. + + + The file could not be uploaded. + Het bestand kon niet worden geüpload. + + + This value should be a valid number. + Deze waarde moet een geldig getal zijn. + + + This file is not a valid image. + Dit bestand is geen geldige afbeelding. + + + This is not a valid IP address. + Dit is geen geldig IP-adres. + + + This value is not a valid language. + Deze waarde is geen geldige taal. + + + This value is not a valid locale. + Deze waarde is geen geldige locale. + + + This value is not a valid country. + Deze waarde is geen geldig land. + + + This value is already used. + Deze waarde wordt al gebruikt. + + + The size of the image could not be detected. + De grootte van de afbeelding kon niet bepaald worden. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + De afbeelding is te breed ({{ width }}px). De maximaal toegestane breedte is {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + De afbeelding is niet breed genoeg ({{ width }}px). De minimaal verwachte breedte is {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + De afbeelding is te hoog ({{ height }}px). De maximaal toegestane hoogte is {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + De afbeelding is niet hoog genoeg ({{ height }}px). De minimaal verwachte hoogte is {{ min_height }}px. + + + This value should be the user's current password. + Deze waarde moet het huidige wachtwoord van de gebruiker zijn. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Deze waarde moet exact {{ limit }} teken lang zijn.|Deze waarde moet exact {{ limit }} tekens lang zijn. + + + The file was only partially uploaded. + Het bestand is slechts gedeeltelijk geüpload. + + + No file was uploaded. + Er is geen bestand geüpload. + + + No temporary folder was configured in php.ini. + Er is geen tijdelijke map geconfigureerd in php.ini, of de gespecificeerde map bestaat niet. + + + Cannot write temporary file to disk. + Kan het tijdelijke bestand niet wegschrijven op disk. + + + A PHP extension caused the upload to fail. + De upload is mislukt vanwege een PHP-extensie. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Deze collectie moet {{ limit }} element of meer bevatten.|Deze collectie moet {{ limit }} elementen of meer bevatten. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Deze collectie moet {{ limit }} element of minder bevatten.|Deze collectie moet {{ limit }} elementen of minder bevatten. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Deze collectie moet exact {{ limit }} element bevatten.|Deze collectie moet exact {{ limit }} elementen bevatten. + + + Invalid card number. + Ongeldig creditcardnummer. + + + Unsupported card type or invalid card number. + Niet-ondersteund type creditcard of ongeldig nummer. + + + This is not a valid International Bank Account Number (IBAN). + Dit is geen geldig internationaal bankrekeningnummer (IBAN). + + + This value is not a valid ISBN-10. + Deze waarde is geen geldige ISBN-10. + + + This value is not a valid ISBN-13. + Deze waarde is geen geldige ISBN-13. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Deze waarde is geen geldige ISBN-10 of ISBN-13 waarde. + + + This value is not a valid ISSN. + Deze waarde is geen geldige ISSN waarde. + + + This value is not a valid currency. + Deze waarde is geen geldige valuta. + + + This value should be equal to {{ compared_value }}. + Deze waarde moet gelijk zijn aan {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + Deze waarde moet groter zijn dan {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + Deze waarde moet groter dan of gelijk aan {{ compared_value }} zijn. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Deze waarde moet identiek zijn aan {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Deze waarde moet minder zijn dan {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + Deze waarde moet minder dan of gelijk aan {{ compared_value }} zijn. + + + This value should not be equal to {{ compared_value }}. + Deze waarde mag niet gelijk zijn aan {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Deze waarde mag niet identiek zijn aan {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + De afbeeldingsverhouding is te groot ({{ ratio }}). Maximale verhouding is {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + De afbeeldingsverhouding is te klein ({{ ratio }}). Minimale verhouding is {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + De afbeelding is vierkant ({{ width }}x{{ height }}px). Vierkante afbeeldingen zijn niet toegestaan. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + De afbeelding is liggend ({{ width }}x{{ height }}px). Liggende afbeeldingen zijn niet toegestaan. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + De afbeelding is staand ({{ width }}x{{ height }}px). Staande afbeeldingen zijn niet toegestaan. + + + An empty file is not allowed. + Lege bestanden zijn niet toegestaan. + + + The host could not be resolved. + De hostnaam kon niet worden bepaald. + + + This value does not match the expected {{ charset }} charset. + Deze waarde is niet in de verwachte tekencodering {{ charset }}. + + + This is not a valid Business Identifier Code (BIC). + Dit is geen geldige bedrijfsidentificatiecode (BIC/SWIFT). + + + Error + Fout + + + This is not a valid UUID. + Dit is geen geldige UUID. + + + This value should be a multiple of {{ compared_value }}. + Deze waarde zou een meervoud van {{ compared_value }} moeten zijn. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Deze bedrijfsidentificatiecode (BIC) is niet gekoppeld aan IBAN {{ iban }}. + + + This value should be valid JSON. + Deze waarde moet geldige JSON zijn. + + + This collection should contain only unique elements. + Deze collectie moet alleen unieke elementen bevatten. + + + This value should be positive. + Deze waarde moet positief zijn. + + + This value should be either positive or zero. + Deze waarde moet positief of gelijk aan nul zijn. + + + This value should be negative. + Deze waarde moet negatief zijn. + + + This value should be either negative or zero. + Deze waarde moet negatief of gelijk aan nul zijn. + + + This value is not a valid timezone. + Deze waarde is geen geldige tijdzone. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Dit wachtwoord is gelekt vanwege een data-inbreuk, het moet niet worden gebruikt. Kies een ander wachtwoord. + + + This value should be between {{ min }} and {{ max }}. + Deze waarde moet zich tussen {{ min }} en {{ max }} bevinden. + + + This value is not a valid hostname. + Deze waarde is geen geldige hostnaam. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + Het aantal elementen van deze collectie moet een veelvoud zijn van {{ compared_value }}. + + + This value should satisfy at least one of the following constraints: + Deze waarde moet voldoen aan tenminste een van de volgende voorwaarden: + + + Each element of this collection should satisfy its own set of constraints. + Elk element van deze collectie moet voldoen aan zijn eigen set voorwaarden. + + + This value is not a valid International Securities Identification Number (ISIN). + Deze waarde is geen geldig International Securities Identification Number (ISIN). + + + This value should be a valid expression. + Deze waarde moet een geldige expressie zijn. + + + This value is not a valid CSS color. + Deze waarde is geen geldige CSS kleur. + + + This value is not a valid CIDR notation. + Deze waarde is geen geldige CIDR notatie. + + + The value of the netmask should be between {{ min }} and {{ max }}. + De waarde van de netmask moet zich tussen {{ min }} en {{ max }} bevinden. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.nn.xlf b/src/vendor/symfony/validator/Resources/translations/validators.nn.xlf new file mode 100644 index 0000000..fa472b5 --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.nn.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + Verdien skulle ha vore tom/nei. + + + This value should be true. + Verdien skulla ha vore satt/ja. + + + This value should be of type {{ type }}. + Verdien må vere av typen {{ type }}. + + + This value should be blank. + Verdien skal vere blank. + + + The value you selected is not a valid choice. + Verdien du valde er ikkje gyldig. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Du må gjere minst {{ limit }} val. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Du kan maksimalt gjere {{ limit }} val. + + + One or more of the given values is invalid. + Ein eller fleire av dei opplyste verdiane er ugyldige. + + + This field was not expected. + Dette feltet var ikkje forventa. + + + This field is missing. + Dette feltet mangler. + + + This value is not a valid date. + Verdien er ikkje ein gyldig dato. + + + This value is not a valid datetime. + Verdien er ikkje ein gyldig dato og tid. + + + This value is not a valid email address. + Verdien er ikkje ei gyldig e-postadresse. + + + The file could not be found. + Fila er ikkje funnen. + + + The file is not readable. + Fila kan ikkje lesast. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + Fila er for stor ({{ size }} {{ suffix }}). Maksimal storleik er {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + Mime-typen av fila er ugyldig ({{ type }}). Tillatne mime-typar er {{ types }}. + + + This value should be {{ limit }} or less. + Verdien må vere {{ limit }} eller mindre. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Verdien er for lang. Den må vere {{ limit }} bokstavar eller mindre. + + + This value should be {{ limit }} or more. + Verdien må vere {{ limit }} eller meir. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Verdien er for kort. Den må ha {{ limit }} teikn eller fleire. + + + This value should not be blank. + Verdien kan ikkje vere blank. + + + This value should not be null. + Verdien kan ikkje vere tom (null). + + + This value should be null. + Verdien må vere tom (null). + + + This value is not valid. + Verdien er ikkje gyldig. + + + This value is not a valid time. + Verdien er ikkje ei gyldig tidseining. + + + This value is not a valid URL. + Verdien er ikkje ein gyldig URL. + + + The two values should be equal. + Dei to verdiane må vere like. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + Fila er for stor. Den maksimale storleiken er {{ limit }} {{ suffix }}. + + + The file is too large. + Fila er for stor. + + + The file could not be uploaded. + Fila kunne ikkje bli lasta opp. + + + This value should be a valid number. + Verdien må vere eit gyldig tal. + + + This file is not a valid image. + Fila er ikkje eit gyldig bilete. + + + This is not a valid IP address. + Dette er ikkje ei gyldig IP-adresse. + + + This value is not a valid language. + Verdien er ikkje eit gyldig språk. + + + This value is not a valid locale. + Verdien er ikkje ein gyldig lokalitet (språk/region). + + + This value is not a valid country. + Verdien er ikkje eit gyldig land. + + + This value is already used. + Verdien er allereie i bruk. + + + The size of the image could not be detected. + Storleiken på biletet kunne ikkje oppdagast. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + Biletbreidda er for stor, ({{ width }} pikslar). Tillaten maksimumsbreidde er {{ max_width }} pikslar. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + Biletbreidda er for liten, ({{ width }} pikslar). Forventa minimumsbreidde er {{ min_width }} pikslar. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + Bilethøgda er for stor, ({{ height }} pikslar). Tillaten maksimumshøgde er {{ max_height }} pikslar. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + Billethøgda er for låg, ({{ height }} pikslar). Forventa minimumshøgde er {{ min_height }} pikslar. + + + This value should be the user's current password. + Verdien må vere brukaren sitt noverande passord. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Verdien må vere nøyaktig {{ limit }} teikn. + + + The file was only partially uploaded. + Fila vart berre delvis lasta opp. + + + No file was uploaded. + Inga fil vart lasta opp. + + + No temporary folder was configured in php.ini. + Førebels mappe (tmp) er ikkje konfigurert i php.ini. + + + Cannot write temporary file to disk. + Kan ikkje skrive førebels fil til disk. + + + A PHP extension caused the upload to fail. + Ei PHP-udviding forårsaka feil under opplasting. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Denne samlinga må innehalde {{ limit }} element eller meir.|Denne samlinga må innehalde {{ limit }} element eller meir. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Denne samlinga må innehalde {{ limit }} element eller færre.|Denne samlinga må innehalde {{ limit }} element eller færre. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Denne samlinga må innehalde nøyaktig {{ limit }} element.|Denne samlinga må innehalde nøyaktig {{ limit }} element. + + + Invalid card number. + Ugyldig kortnummer. + + + Unsupported card type or invalid card number. + Korttypen er ikkje støtta, eller kortnummeret er ugyldig. + + + This is not a valid International Bank Account Number (IBAN). + Dette er ikkje eit gyldig internasjonalt bankkontonummer (IBAN). + + + This value is not a valid ISBN-10. + Verdien er ikkje eit gyldig ISBN-10. + + + This value is not a valid ISBN-13. + Verdien er ikkje eit gyldig ISBN-13. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Verdien er verken eit gyldig ISBN-10 eller eit gyldig ISBN-13. + + + This value is not a valid ISSN. + Verdien er ikkje eit gyldig ISSN. + + + This value is not a valid currency. + Verdien er ikkje ein gyldig valuta. + + + This value should be equal to {{ compared_value }}. + Verdien bør vera eins med {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + Verdien bør vera større enn {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + Verdien bør vera større enn eller eins med {{ compared_value }}. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Verdien bør vera eins med {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Verdien bør vera mindre enn {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + Verdi bør vera mindre enn eller eins med {{ compared_value }}. + + + This value should not be equal to {{ compared_value }}. + Verdi bør ikkje vera eins med {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Denne verdien bør ikkje vera eins med {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Sideforholdet til biletet er for stort ({{ ratio }}). Sideforholdet kan ikkje vere større enn {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + Sideforholdet til biletet er for lite ({{ ratio }}). Sideforholdet kan ikkje vere mindre enn {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + Biletet er kvadratisk ({{ width }}x{{ height }}px). Kvadratiske bilete er ikkje tillatne. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + Biletet er landskapsorientert ({{ width }}x{{ height }}px). Landskapsorienterte bilete er ikkje tillatne. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + Biletet er portrettorientert ({{ width }}x{{ height }}px). Portrettorienterte bilete er ikkje tillatne. + + + An empty file is not allowed. + Ei tom fil er ikkje tillate. + + + The host could not be resolved. + Verten kunne ikkje finnast. + + + This value does not match the expected {{ charset }} charset. + Verdien stemmer ikkje med forventa {{ charset }} charset. + + + This is not a valid Business Identifier Code (BIC). + Dette er ikkje ein gyldig Business Identifier Code (BIC). + + + Error + Feil + + + This is not a valid UUID. + Dette er ikkje ein gyldig UUID. + + + This value should be a multiple of {{ compared_value }}. + Verdien bør vera eit multipel av {{ compared_value }}. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Denne Business Identifier Code (BIC) er ikkje kopla til IBAN {{ iban }}. + + + This value should be valid JSON. + Verdien bør vera gyldig JSON. + + + This collection should contain only unique elements. + Denne samlinga bør berre innehalda unike element. + + + This value should be positive. + Verdien bør vera positiv. + + + This value should be either positive or zero. + Verdien bør vera anten positiv eller null. + + + This value should be negative. + Verdien bør vera negativ. + + + This value should be either negative or zero. + Verdien bør vera negativ eller null. + + + This value is not a valid timezone. + Verdien er ikkje ei gyldig tidssone. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Dette passordet har lekt ut ved eit datainnbrot, det får ikkje nyttast. Gje opp eit anna passord. + + + This value should be between {{ min }} and {{ max }}. + Denne verdien bør liggje mellom {{ min }} og {{ max }}. + + + This value is not a valid hostname. + Verdien er ikkje eit gyldig vertsnamn. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + Talet på element i denne samlinga bør vera eit multippel av {{ compared_value }}. + + + This value should satisfy at least one of the following constraints: + Verdien burde oppfylla minst ein av følgjande avgrensingar: + + + Each element of this collection should satisfy its own set of constraints. + Kvart element i denne samlinga bør oppfylla sine eigne avgrensingar. + + + This value is not a valid International Securities Identification Number (ISIN). + Verdien er ikkje eit gyldig International Securities Identification Number (ISIN). + + + This value should be a valid expression. + Denne verdien skal være et gyldig uttrykk. + + + This value is not a valid CSS color. + Denne verdien er ikke en gyldig CSS-farge. + + + This value is not a valid CIDR notation. + Denne verdien er ikke en gyldig CIDR-notasjon. + + + The value of the netmask should be between {{ min }} and {{ max }}. + Verdien av nettmasken skal være mellom {{ min }} og {{ max }}. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.no.xlf b/src/vendor/symfony/validator/Resources/translations/validators.no.xlf new file mode 100644 index 0000000..5e1ebc1 --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.no.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + Verdien må være usann. + + + This value should be true. + Verdien må være sann. + + + This value should be of type {{ type }}. + Verdien skal ha typen {{ type }}. + + + This value should be blank. + Verdien skal være blank. + + + The value you selected is not a valid choice. + Den valgte verdien er ikke gyldig. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Du må velge minst {{ limit }} valg. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Du kan maks velge {{ limit }} valg. + + + One or more of the given values is invalid. + En eller flere av de oppgitte verdiene er ugyldige. + + + This field was not expected. + Dette feltet var ikke forventet. + + + This field is missing. + Dette feltet mangler. + + + This value is not a valid date. + Verdien er ikke en gyldig dato. + + + This value is not a valid datetime. + Verdien er ikke en gyldig dato/tid. + + + This value is not a valid email address. + Verdien er ikke en gyldig e-postadresse. + + + The file could not be found. + Filen kunne ikke finnes. + + + The file is not readable. + Filen er ikke lesbar. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + Filen er for stor ({{ size }} {{ suffix }}). Tilatte maksimale størrelse {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + Mimetypen av filen er ugyldig ({{ type }}). Tilatte mimetyper er {{ types }}. + + + This value should be {{ limit }} or less. + Verdien må være {{ limit }} tegn lang eller mindre. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Verdien er for lang. Den må ha {{ limit }} tegn eller mindre. + + + This value should be {{ limit }} or more. + Verdien må være {{ limit }} eller mer. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Verdien er for kort. Den må ha {{ limit }} tegn eller flere. + + + This value should not be blank. + Verdien kan ikke være blank. + + + This value should not be null. + Verdien kan ikke være tom (null). + + + This value should be null. + Verdien skal være tom (null). + + + This value is not valid. + Verdien er ugyldig. + + + This value is not a valid time. + Verdien er ikke en gyldig tid. + + + This value is not a valid URL. + Verdien er ikke en gyldig URL. + + + The two values should be equal. + Verdiene skal være identiske. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + Filen er for stor. Den maksimale størrelsen er {{ limit }} {{ suffix }}. + + + The file is too large. + Filen er for stor. + + + The file could not be uploaded. + Filen kunne ikke lastes opp. + + + This value should be a valid number. + Verdien skal være et gyldig tall. + + + This file is not a valid image. + Denne filen er ikke et gyldig bilde. + + + This is not a valid IP address. + Dette er ikke en gyldig IP adresse. + + + This value is not a valid language. + Verdien er ikke et gyldig språk. + + + This value is not a valid locale. + Verdien er ikke en gyldig lokalitet. + + + This value is not a valid country. + Verdien er ikke et gyldig navn på land. + + + This value is already used. + Verdien er allerede brukt. + + + The size of the image could not be detected. + Bildestørrelsen kunne ikke oppdages. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + Bildebredden er for stor ({{ width }} piksler). Tillatt maksimumsbredde er {{ max_width }} piksler. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + Bildebredden er for liten ({{ width }} piksler). Forventet minimumsbredde er {{ min_width }} piksler. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + Bildehøyden er for stor ({{ height }} piksler). Tillatt maksimumshøyde er {{ max_height }} piksler. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + Bildehøyden er for liten ({{ height }} piksler). Forventet minimumshøyde er {{ min_height }} piksler. + + + This value should be the user's current password. + Verdien skal være brukerens sitt nåværende passord. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Verdien skal være nøyaktig {{ limit }} tegn. + + + The file was only partially uploaded. + Filen var kun delvis opplastet. + + + No file was uploaded. + Ingen fil var lastet opp. + + + No temporary folder was configured in php.ini. + Den midlertidige mappen (tmp) er ikke konfigurert i php.ini. + + + Cannot write temporary file to disk. + Kan ikke skrive midlertidig fil til disk. + + + A PHP extension caused the upload to fail. + En PHP-utvidelse forårsaket en feil under opplasting. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Denne samlingen må inneholde {{ limit }} element eller flere.|Denne samlingen må inneholde {{ limit }} elementer eller flere. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Denne samlingen må inneholde {{ limit }} element eller færre.|Denne samlingen må inneholde {{ limit }} elementer eller færre. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Denne samlingen må inneholde nøyaktig {{ limit }} element.|Denne samlingen må inneholde nøyaktig {{ limit }} elementer. + + + Invalid card number. + Ugyldig kortnummer. + + + Unsupported card type or invalid card number. + Korttypen er ikke støttet eller kortnummeret er ugyldig. + + + This is not a valid International Bank Account Number (IBAN). + Dette er ikke et gyldig IBAN-nummer. + + + This value is not a valid ISBN-10. + Verdien er ikke en gyldig ISBN-10. + + + This value is not a valid ISBN-13. + Verdien er ikke en gyldig ISBN-13. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Verdien er hverken en gyldig ISBN-10 eller ISBN-13. + + + This value is not a valid ISSN. + Verdien er ikke en gyldig ISSN. + + + This value is not a valid currency. + Verdien er ikke gyldig valuta. + + + This value should be equal to {{ compared_value }}. + Verdien skal være lik {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + Verdien skal være større enn {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + Verdien skal være større enn eller lik {{ compared_value }}. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Verdien skal være identisk med {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Verdien skal være mindre enn {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + Verdien skal være mindre enn eller lik {{ compared_value }}. + + + This value should not be equal to {{ compared_value }}. + Verdien skal ikke være lik {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Verdien skal ikke være identisk med {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Bildeforholdet er for stort ({{ ratio }}). Tillatt bildeforhold er maks {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + Bildeforholdet er for lite ({{ ratio }}). Forventet bildeforhold er minst {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + Bildet er en kvadrat ({{ width }}x{{ height }}px). Kvadratiske bilder er ikke tillatt. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + Bildet er i liggende retning ({{ width }}x{{ height }}px). Bilder i liggende retning er ikke tillatt. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + Bildet er i stående retning ({{ width }}x{{ height }}px). Bilder i stående retning er ikke tillatt. + + + An empty file is not allowed. + Tomme filer er ikke tilatt. + + + The host could not be resolved. + Vertsnavn kunne ikke løses. + + + This value does not match the expected {{ charset }} charset. + Verdien samsvarer ikke med forventet tegnsett {{ charset }}. + + + This is not a valid Business Identifier Code (BIC). + Dette er ikke en gyldig BIC. + + + Error + Feil + + + This is not a valid UUID. + Dette er ikke en gyldig UUID. + + + This value should be a multiple of {{ compared_value }}. + Verdien skal være flertall av {{ compared_value }}. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Business Identifier Code (BIC) er ikke tilknyttet en IBAN {{ iban }}. + + + This value should be valid JSON. + Verdien er ikke gyldig JSON. + + + This collection should contain only unique elements. + Samlingen kan kun inneholde unike elementer. + + + This value should be positive. + Denne verdien må være positiv. + + + This value should be either positive or zero. + Denne verdien må være positiv eller null. + + + This value should be negative. + Denne verdien må være negativ. + + + This value should be either negative or zero. + Denne verdien må være negativ eller null. + + + This value is not a valid timezone. + Verdien er ikke en gyldig tidssone. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Dette passordet er lekket i et datainnbrudd, det må ikke tas i bruk. Vennligst bruk et annet passord. + + + This value should be between {{ min }} and {{ max }}. + Verdien må være mellom {{ min }} og {{ max }}. + + + This value is not a valid hostname. + Denne verdien er ikke et gyldig vertsnavn. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + Antall elementer i denne samlingen bør være et multiplum av {{ compared_value }}. + + + This value should satisfy at least one of the following constraints: + Denne verdien skal tilfredsstille minst en av følgende begrensninger: + + + Each element of this collection should satisfy its own set of constraints. + Hvert element i denne samlingen skal tilfredsstille sitt eget sett med begrensninger. + + + This value is not a valid International Securities Identification Number (ISIN). + Denne verdien er ikke et gyldig International Securities Identification Number (ISIN). + + + This value should be a valid expression. + Denne verdien skal være et gyldig uttrykk. + + + This value is not a valid CSS color. + Denne verdien er ikke en gyldig CSS-farge. + + + This value is not a valid CIDR notation. + Denne verdien er ikke en gyldig CIDR-notasjon. + + + The value of the netmask should be between {{ min }} and {{ max }}. + Verdien på nettmasken skal være mellom {{ min }} og {{ max }}. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.pl.xlf b/src/vendor/symfony/validator/Resources/translations/validators.pl.xlf new file mode 100644 index 0000000..e20f490 --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.pl.xlf @@ -0,0 +1,431 @@ + + + + + + This value should be false. + Ta wartość powinna być fałszem. + + + This value should be true. + Ta wartość powinna być prawdą. + + + This value should be of type {{ type }}. + Ta wartość powinna być typu {{ type }}. + + + This value should be blank. + Ta wartość powinna być pusta. + + + The value you selected is not a valid choice. + Ta wartość powinna być jedną z podanych opcji. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Powinieneś wybrać co najmniej {{ limit }} opcję.|Powinieneś wybrać co najmniej {{ limit }} opcje.|Powinieneś wybrać co najmniej {{ limit }} opcji. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Powinieneś wybrać maksymalnie {{ limit }} opcję.|Powinieneś wybrać maksymalnie {{ limit }} opcje.|Powinieneś wybrać maksymalnie {{ limit }} opcji. + + + One or more of the given values is invalid. + Jedna lub więcej z podanych wartości jest nieprawidłowa. + + + This field was not expected. + Tego pola się nie spodziewano. + + + This field is missing. + Tego pola brakuje. + + + This value is not a valid date. + Ta wartość nie jest prawidłową datą. + + + This value is not a valid datetime. + Ta wartość nie jest prawidłową datą i czasem. + + + This value is not a valid email address. + Ta wartość nie jest prawidłowym adresem email. + + + The file could not be found. + Plik nie mógł zostać odnaleziony. + + + The file is not readable. + Nie można odczytać pliku. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + Plik jest za duży ({{ size }} {{ suffix }}). Maksymalny dozwolony rozmiar to {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + Nieprawidłowy typ mime pliku ({{ type }}). Dozwolone typy mime to {{ types }}. + + + This value should be {{ limit }} or less. + Ta wartość powinna wynosić {{ limit }} lub mniej. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Ta wartość jest zbyt długa. Powinna mieć {{ limit }} lub mniej znaków.|Ta wartość jest zbyt długa. Powinna mieć {{ limit }} lub mniej znaków.|Ta wartość jest zbyt długa. Powinna mieć {{ limit }} lub mniej znaków. + + + This value should be {{ limit }} or more. + Ta wartość powinna wynosić {{ limit }} lub więcej. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Ta wartość jest zbyt krótka. Powinna mieć {{ limit }} lub więcej znaków.|Ta wartość jest zbyt krótka. Powinna mieć {{ limit }} lub więcej znaków.|Ta wartość jest zbyt krótka. Powinna mieć {{ limit }} lub więcej znaków. + + + This value should not be blank. + Ta wartość nie powinna być pusta. + + + This value should not be null. + Ta wartość nie powinna być nullem. + + + This value should be null. + Ta wartość powinna być nullem. + + + This value is not valid. + Ta wartość jest nieprawidłowa. + + + This value is not a valid time. + Ta wartość nie jest prawidłowym czasem. + + + This value is not a valid URL. + Ta wartość nie jest prawidłowym adresem URL. + + + The two values should be equal. + Obie wartości powinny być równe. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + Plik jest za duży. Maksymalny dozwolony rozmiar to {{ limit }} {{ suffix }}. + + + The file is too large. + Plik jest za duży. + + + The file could not be uploaded. + Plik nie mógł być wgrany. + + + This value should be a valid number. + Ta wartość powinna być prawidłową liczbą. + + + This file is not a valid image. + Ten plik nie jest obrazem. + + + This is not a valid IP address. + To nie jest prawidłowy adres IP. + + + This value is not a valid language. + Ta wartość nie jest prawidłowym językiem. + + + This value is not a valid locale. + Ta wartość nie jest prawidłową lokalizacją. + + + This value is not a valid country. + Ta wartość nie jest prawidłową nazwą kraju. + + + This value is already used. + Ta wartość jest już wykorzystywana. + + + The size of the image could not be detected. + Nie można wykryć rozmiaru obrazka. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + Szerokość obrazka jest zbyt duża ({{ width }}px). Maksymalna dopuszczalna szerokość to {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + Szerokość obrazka jest zbyt mała ({{ width }}px). Oczekiwana minimalna szerokość to {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + Wysokość obrazka jest zbyt duża ({{ height }}px). Maksymalna dopuszczalna wysokość to {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + Wysokość obrazka jest zbyt mała ({{ height }}px). Oczekiwana minimalna wysokość to {{ min_height }}px. + + + This value should be the user's current password. + Ta wartość powinna być aktualnym hasłem użytkownika. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Ta wartość powinna mieć dokładnie {{ limit }} znak.|Ta wartość powinna mieć dokładnie {{ limit }} znaki.|Ta wartość powinna mieć dokładnie {{ limit }} znaków. + + + The file was only partially uploaded. + Plik został wgrany tylko częściowo. + + + No file was uploaded. + Żaden plik nie został wgrany. + + + No temporary folder was configured in php.ini. + Nie skonfigurowano folderu tymczasowego w php.ini lub skonfigurowany folder nie istnieje. + + + Cannot write temporary file to disk. + Nie można zapisać pliku tymczasowego na dysku. + + + A PHP extension caused the upload to fail. + Rozszerzenie PHP spowodowało błąd podczas wgrywania. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Ten zbiór powinien zawierać {{ limit }} lub więcej elementów. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Ten zbiór powinien zawierać {{ limit }} lub mniej elementów. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Ten zbiór powinien zawierać dokładnie {{ limit }} element.|Ten zbiór powinien zawierać dokładnie {{ limit }} elementy.|Ten zbiór powinien zawierać dokładnie {{ limit }} elementów. + + + Invalid card number. + Nieprawidłowy numer karty. + + + Unsupported card type or invalid card number. + Nieobsługiwany rodzaj karty lub nieprawidłowy numer karty. + + + This is not a valid International Bank Account Number (IBAN). + Nieprawidłowy międzynarodowy numer rachunku bankowego (IBAN). + + + This value is not a valid ISBN-10. + Ta wartość nie jest prawidłowym numerem ISBN-10. + + + This value is not a valid ISBN-13. + Ta wartość nie jest prawidłowym numerem ISBN-13. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Ta wartość nie jest prawidłowym numerem ISBN-10 ani ISBN-13. + + + This value is not a valid ISSN. + Ta wartość nie jest prawidłowym numerem ISSN. + + + This value is not a valid currency. + Ta wartość nie jest prawidłową walutą. + + + This value should be equal to {{ compared_value }}. + Ta wartość powinna być równa {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + Ta wartość powinna być większa niż {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + Ta wartość powinna być większa bądź równa {{ compared_value }}. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Ta wartość powinna być identycznego typu {{ compared_value_type }} oraz wartości {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Ta wartość powinna być mniejsza niż {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + Ta wartość powinna być mniejsza bądź równa {{ compared_value }}. + + + This value should not be equal to {{ compared_value }}. + Ta wartość nie powinna być równa {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Ta wartość nie powinna być identycznego typu {{ compared_value_type }} oraz wartości {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Proporcje obrazu są zbyt duże ({{ ratio }}). Maksymalne proporcje to {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + Proporcje obrazu są zbyt małe ({{ ratio }}). Minimalne proporcje to {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + Obraz jest kwadratem ({{ width }}x{{ height }}px). Kwadratowe obrazy nie są akceptowane. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + Obraz jest panoramiczny ({{ width }}x{{ height }}px). Panoramiczne zdjęcia nie są akceptowane. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + Obraz jest portretowy ({{ width }}x{{ height }}px). Portretowe zdjęcia nie są akceptowane. + + + An empty file is not allowed. + Plik nie może być pusty. + + + The host could not be resolved. + Nazwa hosta nie została rozpoznana. + + + This value does not match the expected {{ charset }} charset. + Ta wartość nie pasuje do oczekiwanego zestawu znaków {{ charset }}. + + + This is not a valid Business Identifier Code (BIC). + Ta wartość nie jest poprawnym kodem BIC (Business Identifier Code). + + + Error + Błąd + + + This is not a valid UUID. + To nie jest poprawne UUID. + + + This value should be a multiple of {{ compared_value }}. + Ta wartość powinna być wielokrotnością {{ compared_value }}. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Ten kod BIC (Business Identifier Code) nie jest powiązany z międzynarodowym numerem rachunku bankowego (IBAN) {{ iban }}. + + + This value should be valid JSON. + Ta wartość powinna być prawidłowym formatem JSON. + + + This collection should contain only unique elements. + Ten zbiór powinien zawierać tylko unikalne elementy. + + + This value should be positive. + Ta wartość powinna być dodatnia. + + + This value should be either positive or zero. + Ta wartość powinna być dodatnia lub równa zero. + + + This value should be negative. + Ta wartość powinna być ujemna. + + + This value should be either negative or zero. + Ta wartość powinna być ujemna lub równa zero. + + + This value is not a valid timezone. + Ta wartość nie jest prawidłową strefą czasową. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + To hasło wyciekło w wyniku naruszenia danych i nie może być użyte. Proszę użyć innego hasła. + + + This value should be between {{ min }} and {{ max }}. + Ta wartość powinna być pomiędzy {{ min }} a {{ max }}. + + + This value is not a valid hostname. + Ta wartość nie jest prawidłową nazwą hosta. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + Liczba elementów w tym zbiorze powinna być wielokrotnością {{ compared_value }}. + + + This value should satisfy at least one of the following constraints: + Ta wartość powinna spełniać co najmniej jedną z następujących reguł: + + + Each element of this collection should satisfy its own set of constraints. + Każdy element w tym zbiorze powinien spełniać własny zestaw reguł. + + + This value is not a valid International Securities Identification Number (ISIN). + Ta wartość nie jest prawidłowym Międzynarodowym Numerem Identyfikacyjnym Papierów Wartościowych (ISIN). + + + This value should be a valid expression. + Ta wartość powinna być prawidłowym wyrażeniem. + + + This value is not a valid CSS color. + Ta wartość nie jest prawidłowym kolorem CSS. + + + This value is not a valid CIDR notation. + Ta wartość nie jest prawidłową notacją CIDR. + + + The value of the netmask should be between {{ min }} and {{ max }}. + Wartość maski podsieci powinna być pomiędzy {{ min }} i {{ max }}. + + + The filename is too long. It should have {{ filename_max_length }} character or less.|The filename is too long. It should have {{ filename_max_length }} characters or less. + Nazwa pliku jest za długa. Powinna mieć {{ filename_max_length }} znak lub mniej.|Nazwa pliku jest za długa. Powinna mieć {{ filename_max_length }} znaków lub mniej. + + + The password strength is too low. Please use a stronger password. + Siła hasła jest zbyt niska. Użyj mocniejszego hasła. + + + This value contains characters that are not allowed by the current restriction-level. + Ta wartość zawiera znaki, które nie są dozwolone przez aktualny poziom ograniczeń. + + + Using invisible characters is not allowed. + Używanie niewidzialnych znaków jest niedozwolone. + + + Mixing numbers from different scripts is not allowed. + Mieszanie liczb z różnych skryptów jest niedozwolone. + + + Using hidden overlay characters is not allowed. + Używanie ukrytych znaków nakładki jest niedozwolone. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.pt.xlf b/src/vendor/symfony/validator/Resources/translations/validators.pt.xlf new file mode 100644 index 0000000..090add6 --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.pt.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + Este valor deveria ser falso. + + + This value should be true. + Este valor deveria ser verdadeiro. + + + This value should be of type {{ type }}. + Este valor deveria ser do tipo {{ type }}. + + + This value should be blank. + Este valor deveria ser vazio. + + + The value you selected is not a valid choice. + O valor selecionado não é uma opção válida. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Você deveria selecionar {{ limit }} opção no mínimo.|Você deveria selecionar {{ limit }} opções no mínimo. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Você deve selecionar, no máximo {{ limit }} opção.|Você deve selecionar, no máximo {{ limit }} opções. + + + One or more of the given values is invalid. + Um ou mais dos valores introduzidos não são válidos. + + + This field was not expected. + Este campo não era esperado. + + + This field is missing. + Este campo está faltando. + + + This value is not a valid date. + Este valor não é uma data válida. + + + This value is not a valid datetime. + Este valor não é uma data-hora válida. + + + This value is not a valid email address. + Este valor não é um endereço de e-mail válido. + + + The file could not be found. + O arquivo não pôde ser encontrado. + + + The file is not readable. + O arquivo não pôde ser lido. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + O arquivo é muito grande ({{ size }} {{ suffix }}). O tamanho máximo permitido é de {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + O tipo mime do arquivo é inválido ({{ type }}). Os tipos mime permitidos são {{ types }}. + + + This value should be {{ limit }} or less. + Este valor deveria ser {{ limit }} ou menor. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + O valor é muito longo. Deveria ter {{ limit }} caracteres ou menos. + + + This value should be {{ limit }} or more. + Este valor deveria ser {{ limit }} ou mais. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + O valor é muito curto. Deveria de ter {{ limit }} caractere ou mais.|O valor é muito curto. Deveria de ter {{ limit }} caracteres ou mais. + + + This value should not be blank. + Este valor não deveria ser branco/vazio. + + + This value should not be null. + Este valor não deveria ser nulo. + + + This value should be null. + Este valor deveria ser nulo. + + + This value is not valid. + Este valor não é válido. + + + This value is not a valid time. + Este valor não é uma hora válida. + + + This value is not a valid URL. + Este valor não é um URL válido. + + + The two values should be equal. + Os dois valores deveriam ser iguais. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + O arquivo é muito grande. O tamanho máximo permitido é de {{ limit }} {{ suffix }}. + + + The file is too large. + O ficheiro é muito grande. + + + The file could not be uploaded. + Não foi possível carregar o ficheiro. + + + This value should be a valid number. + Este valor deveria ser um número válido. + + + This file is not a valid image. + Este ficheiro não é uma imagem. + + + This is not a valid IP address. + Este endereço de IP não é válido. + + + This value is not a valid language. + Este valor não é uma linguagem válida. + + + This value is not a valid locale. + Este valor não é um 'locale' válido. + + + This value is not a valid country. + Este valor não é um País válido. + + + This value is already used. + Este valor já está a ser usado. + + + The size of the image could not be detected. + O tamanho da imagem não foi detetado. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + A largura da imagem ({{ width }}px) é muito grande. A largura máxima da imagem é: {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + A largura da imagem ({{ width }}px) é muito pequena. A largura miníma da imagem é de: {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + A altura da imagem ({{ height }}px) é muito grande. A altura máxima da imagem é de: {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + A altura da imagem ({{ height }}px) é muito pequena. A altura miníma da imagem é de: {{ min_height }}px. + + + This value should be the user's current password. + Este valor deveria ser a senha atual do usuário. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Este valor deve possuir exatamente {{ limit }} caracteres. + + + The file was only partially uploaded. + Só foi enviada uma parte do arquivo. + + + No file was uploaded. + Nenhum arquivo foi enviado. + + + No temporary folder was configured in php.ini. + Não existe uma pasta temporária configurada no arquivo php.ini. + + + Cannot write temporary file to disk. + Não foi possível escrever os arquivos temporários no disco. + + + A PHP extension caused the upload to fail. + Uma extensão PHP causou a falha no envio. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Esta coleção deve conter {{ limit }} elemento ou mais.|Esta coleção deve conter {{ limit }} elementos ou mais. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Esta coleção deve conter {{ limit }} elemento ou menos.|Esta coleção deve conter {{ limit }} elementos ou menos. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Esta coleção deve conter exatamente {{ limit }} elemento.|Esta coleção deve conter exatamente {{ limit }} elementos. + + + Invalid card number. + Número de cartão inválido. + + + Unsupported card type or invalid card number. + Tipo de cartão não suportado ou número de cartão inválido. + + + This is not a valid International Bank Account Number (IBAN). + Este não é um Número Internacional de Conta Bancária (IBAN) válido. + + + This value is not a valid ISBN-10. + Este valor não é um ISBN-10 válido. + + + This value is not a valid ISBN-13. + Este valor não é um ISBN-13 válido. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Este valor não é um ISBN-10 ou ISBN-13 válido. + + + This value is not a valid ISSN. + Este valor não é um ISSN válido. + + + This value is not a valid currency. + Este não é um valor monetário válido. + + + This value should be equal to {{ compared_value }}. + Este valor deve ser igual a {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + Este valor deve ser superior a {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + Este valor deve ser igual ou superior a {{ compared_value }}. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Este valor deve ser idêntico a {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Este valor deve ser inferior a {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + Este valor deve ser igual ou inferior a {{ compared_value }}. + + + This value should not be equal to {{ compared_value }}. + Este valor não deve ser igual a {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Este valor não deve ser idêntico a {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + O formato da imagem é muito grande ({{ ratio }}). O formato máximo é {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + O formato da imagem é muito pequeno ({{ ratio }}). O formato mínimo esperado é {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + A imagem é um quadrado ({{ width }}x{{ height }}px). Imagens quadradas não são permitidas. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + A imagem está em orientação de paisagem ({{ width }}x{{ height }}px). Imagens orientadas em paisagem não são permitidas. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + A imagem está em orientação de retrato ({{ width }}x{{ height }}px). Imagens orientadas em retrato não são permitidas. + + + An empty file is not allowed. + Um arquivo vazio não é permitido. + + + The host could not be resolved. + O host não pode ser resolvido. + + + This value does not match the expected {{ charset }} charset. + O valor não corresponde ao conjunto de caracteres {{ charset }} esperado. + + + This is not a valid Business Identifier Code (BIC). + O Código de Identificação de Empresa (BIC) não é válido. + + + Error + Erro + + + This is not a valid UUID. + Este valor não é um UUID válido. + + + This value should be a multiple of {{ compared_value }}. + Este valor deve ser um múltiplo de {{ compared_value }}. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + O Código de Identificação de Empresa (BIC) não está associado ao IBAN {{ iban }}. + + + This value should be valid JSON. + Este valor deve ser um JSON válido. + + + This collection should contain only unique elements. + Esta coleção deve conter só elementos únicos. + + + This value should be positive. + Este valor deve ser estritamente positivo. + + + This value should be either positive or zero. + Este valor deve ser superior ou igual a zero. + + + This value should be negative. + Este valor deve ser estritamente negativo. + + + This value should be either negative or zero. + Este valor deve ser inferior ou igual a zero. + + + This value is not a valid timezone. + Este valor não é um fuso horário válido. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Esta senha foi divulgada durante uma fuga de dados, não deve ser usada de novamente. Por favor usar uma senha outra. + + + This value should be between {{ min }} and {{ max }}. + Este valor deve situar-se entre {{ min }} e {{ max }}. + + + This value is not a valid hostname. + Este valor não é um nome de host válido. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + O número de elementos desta coleção deve ser um múltiplo de {{ compared_value }}. + + + This value should satisfy at least one of the following constraints: + Este valor deve satisfazer pelo menos uma das seguintes restrições : + + + Each element of this collection should satisfy its own set of constraints. + Cada elemento desta coleção deve satisfazer o seu próprio conjunto de restrições. + + + This value is not a valid International Securities Identification Number (ISIN). + Este valor não é um Número Internacional de Identificação de Segurança (ISIN) válido. + + + This value should be a valid expression. + Este valor deve ser uma expressão válida. + + + This value is not a valid CSS color. + Este valor não é uma cor de CSS válida. + + + This value is not a valid CIDR notation. + Este valor não é uma notação CIDR válida. + + + The value of the netmask should be between {{ min }} and {{ max }}. + O valor da máscara de rede deve estar entre {{ min }} e {{ max }}. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.pt_BR.xlf b/src/vendor/symfony/validator/Resources/translations/validators.pt_BR.xlf new file mode 100644 index 0000000..e88b81f --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.pt_BR.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + Este valor deve ser falso. + + + This value should be true. + Este valor deve ser verdadeiro. + + + This value should be of type {{ type }}. + Este valor deve ser do tipo {{ type }}. + + + This value should be blank. + Este valor deve ser vazio. + + + The value you selected is not a valid choice. + O valor selecionado não é uma opção válida. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Você deve selecionar, no mínimo, {{ limit }} opção.|Você deve selecionar, no mínimo, {{ limit }} opções. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Você deve selecionar, no máximo, {{ limit }} opção.|Você deve selecionar, no máximo, {{ limit }} opções. + + + One or more of the given values is invalid. + Um ou mais valores informados são inválidos. + + + This field was not expected. + Este campo não era esperado. + + + This field is missing. + Este campo está ausente. + + + This value is not a valid date. + Este valor não é uma data válida. + + + This value is not a valid datetime. + Este valor não é uma data e hora válida. + + + This value is not a valid email address. + Este valor não é um endereço de e-mail válido. + + + The file could not be found. + O arquivo não foi encontrado. + + + The file is not readable. + O arquivo não pode ser lido. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + O arquivo é muito grande ({{ size }} {{ suffix }}). O tamanho máximo permitido é {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + O tipo mime do arquivo é inválido ({{ type }}). Os tipos mime permitidos são {{ types }}. + + + This value should be {{ limit }} or less. + Este valor deve ser {{ limit }} ou menos. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Este valor é muito longo. Deve ter {{ limit }} caractere ou menos.|Este valor é muito longo. Deve ter {{ limit }} caracteres ou menos. + + + This value should be {{ limit }} or more. + Este valor deve ser {{ limit }} ou mais. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Este valor é muito curto. Deve ter {{ limit }} caractere ou mais.|Este valor é muito curto. Deve ter {{ limit }} caracteres ou mais. + + + This value should not be blank. + Este valor não deve ser vazio. + + + This value should not be null. + Este valor não deve ser nulo. + + + This value should be null. + Este valor deve ser nulo. + + + This value is not valid. + Este valor não é válido. + + + This value is not a valid time. + Este valor não é uma hora válida. + + + This value is not a valid URL. + Este valor não é uma URL válida. + + + The two values should be equal. + Os dois valores devem ser iguais. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + O arquivo é muito grande. O tamanho máximo permitido é de {{ limit }} {{ suffix }}. + + + The file is too large. + O arquivo é muito grande. + + + The file could not be uploaded. + O arquivo não pode ser enviado. + + + This value should be a valid number. + Este valor deve ser um número válido. + + + This file is not a valid image. + Este arquivo não é uma imagem válida. + + + This is not a valid IP address. + Este não é um endereço de IP válido. + + + This value is not a valid language. + Este valor não é um idioma válido. + + + This value is not a valid locale. + Este valor não é uma localidade válida. + + + This value is not a valid country. + Este valor não é um país válido. + + + This value is already used. + Este valor já está sendo usado. + + + The size of the image could not be detected. + O tamanho da imagem não pode ser detectado. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + A largura da imagem é muito grande ({{ width }}px). A largura máxima permitida é de {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + A largura da imagem é muito pequena ({{ width }}px). A largura mínima esperada é de {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + A altura da imagem é muito grande ({{ height }}px). A altura máxima permitida é de {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + A altura da imagem é muito pequena ({{ height }}px). A altura mínima esperada é de {{ min_height }}px. + + + This value should be the user's current password. + Este valor deve ser a senha atual do usuário. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Este valor deve ter exatamente {{ limit }} caractere.|Este valor deve ter exatamente {{ limit }} caracteres. + + + The file was only partially uploaded. + O arquivo foi enviado apenas parcialmente. + + + No file was uploaded. + Nenhum arquivo foi enviado. + + + No temporary folder was configured in php.ini. + Nenhum diretório temporário foi configurado no php.ini. + + + Cannot write temporary file to disk. + Não foi possível escrever o arquivo temporário no disco. + + + A PHP extension caused the upload to fail. + Uma extensão PHP fez com que o envio falhasse. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Esta coleção deve conter {{ limit }} elemento ou mais.|Esta coleção deve conter {{ limit }} elementos ou mais. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Esta coleção deve conter {{ limit }} elemento ou menos.|Esta coleção deve conter {{ limit }} elementos ou menos. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Esta coleção deve conter exatamente {{ limit }} elemento.|Esta coleção deve conter exatamente {{ limit }} elementos. + + + Invalid card number. + Número de cartão inválido. + + + Unsupported card type or invalid card number. + Tipo de cartão não suportado ou número de cartão inválido. + + + This is not a valid International Bank Account Number (IBAN). + Este não é um Número Internacional de Conta Bancária (IBAN) válido. + + + This value is not a valid ISBN-10. + Este valor não é um ISBN-10 válido. + + + This value is not a valid ISBN-13. + Este valor não é um ISBN-13 válido. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Este valor não é um ISBN-10 e nem um ISBN-13 válido. + + + This value is not a valid ISSN. + Este valor não é um ISSN válido. + + + This value is not a valid currency. + Este não é um valor monetário válido. + + + This value should be equal to {{ compared_value }}. + Este valor deve ser igual a {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + Este valor deve ser maior que {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + Este valor deve ser maior ou igual a {{ compared_value }}. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Este valor deve ser idêntico a {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Este valor deve ser menor que {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + Este valor deve ser menor ou igual a {{ compared_value }}. + + + This value should not be equal to {{ compared_value }}. + Este valor não deve ser igual a {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Este valor não deve ser idêntico a {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + A proporção da imagem é muito grande ({{ ratio }}). A proporção máxima permitida é {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + A proporção da imagem é muito pequena ({{ ratio }}). A proporção mínima esperada é {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + A imagem está num formato quadrado ({{ width }}x{{ height }}px). Imagens com formato quadrado não são permitidas. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + A imagem está orientada à paisagem ({{ width }}x{{ height }}px). Imagens orientadas à paisagem não são permitidas. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + A imagem está orientada ao retrato ({{ width }}x{{ height }}px). Imagens orientadas ao retrato não são permitidas. + + + An empty file is not allowed. + Arquivo vazio não é permitido. + + + The host could not be resolved. + O host não pôde ser resolvido. + + + This value does not match the expected {{ charset }} charset. + Este valor não corresponde ao charset {{ charset }} esperado. + + + This is not a valid Business Identifier Code (BIC). + Este não é um Código Identificador Bancário (BIC) válido. + + + Error + Erro + + + This is not a valid UUID. + Este não é um UUID válido. + + + This value should be a multiple of {{ compared_value }}. + Este valor deve ser múltiplo de {{ compared_value }}. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Este Código Identificador Bancário (BIC) não está associado ao IBAN {{ iban }}. + + + This value should be valid JSON. + Este valor deve ser um JSON válido. + + + This collection should contain only unique elements. + Esta coleção deve conter somente elementos únicos. + + + This value should be positive. + Este valor deve ser positivo. + + + This value should be either positive or zero. + Este valor deve ser positivo ou zero. + + + This value should be negative. + Este valor deve ser negativo. + + + This value should be either negative or zero. + Este valor deve ser negativo ou zero. + + + This value is not a valid timezone. + Este valor não representa um fuso horário válido. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Esta senha foi divulgada num vazamento de dados e não deve ser utilizada. Por favor, utilize outra senha. + + + This value should be between {{ min }} and {{ max }}. + Este valor deve estar entre {{ min }} e {{ max }}. + + + This value is not a valid hostname. + Este valor não é um nome de host válido. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + O número de elementos desta coleção deve ser um múltiplo de {{ compared_value }}. + + + This value should satisfy at least one of the following constraints: + Este valor deve satisfazer pelo menos uma das seguintes restrições: + + + Each element of this collection should satisfy its own set of constraints. + Cada elemento desta coleção deve satisfazer seu próprio grupo de restrições. + + + This value is not a valid International Securities Identification Number (ISIN). + Este valor não é um Número de Identificação de Títulos Internacionais (ISIN) válido. + + + This value should be a valid expression. + Este valor deve ser uma expressão válida. + + + This value is not a valid CSS color. + Este valor não é uma cor CSS válida. + + + This value is not a valid CIDR notation. + Este valor não é uma notação CIDR válida. + + + The value of the netmask should be between {{ min }} and {{ max }}. + O valor da máscara de rede deve estar entre {{ min }} e {{ max }}. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.ro.xlf b/src/vendor/symfony/validator/Resources/translations/validators.ro.xlf new file mode 100644 index 0000000..7fba2cd --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.ro.xlf @@ -0,0 +1,403 @@ + + + + + + This value should be false. + Această valoare ar trebui să fie falsă (false). + + + This value should be true. + Această valoare ar trebui să fie adevărată (true). + + + This value should be of type {{ type }}. + Această valoare ar trebui să fie de tipul {{ type }}. + + + This value should be blank. + Această valoare ar trebui sa fie goală. + + + The value you selected is not a valid choice. + Valoarea selectată nu este o opțiune validă. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Trebuie să selectați cel puțin {{ limit }} opțiune.|Trebuie să selectați cel puțin {{ limit }} opțiuni.|Trebuie să selectați cel puțin {{ limit }} de opțiuni + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Trebuie să selectați cel mult {{ limit }} opțiune.|Trebuie să selectați cel mult {{ limit }} opțiuni.|Trebuie să selectați cel mult {{ limit }} de opțiuni. + + + One or more of the given values is invalid. + Una sau mai multe dintre valorile furnizate sunt invalide. + + + This field was not expected. + Acest câmp nu era de aşteptat. + + + This field is missing. + Acest câmp este lipsă. + + + This value is not a valid date. + Această valoare nu reprezintă o dată validă. + + + This value is not a valid datetime. + Această valoare nu reprezintă o dată și oră validă. + + + This value is not a valid email address. + Această valoare nu reprezintă o adresă de e-mail validă. + + + The file could not be found. + Fișierul nu a putut fi găsit. + + + The file is not readable. + Fișierul nu poate fi citit. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + Fișierul este prea mare ({{ size }} {{ suffix }}). Dimensiunea maximă permisă este {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + Tipul fișierului este invalid ({{ type }}). Tipurile permise de fișiere sunt ({{ types }}). + + + This value should be {{ limit }} or less. + Această valoare ar trebui să fie cel mult {{ limit }}. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Această valoare este prea lungă. Ar trebui să aibă maxim {{ limit }} caracter.|Această valoare este prea lungă. Ar trebui să aibă maxim {{ limit }} caractere.|Această valoare este prea lungă. Ar trebui să aibă maxim {{ limit }} de caractere. + + + This value should be {{ limit }} or more. + Această valoare ar trebui să fie cel puțin {{ limit }}. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Această valoare este prea scurtă. Ar trebui să aibă minim {{ limit }} caracter.|Această valoare este prea scurtă. Ar trebui să aibă minim {{ limit }} caractere.|Această valoare este prea scurtă. Ar trebui să aibă minim {{ limit }} de caractere. + + + This value should not be blank. + Această valoare nu ar trebui să fie goală. + + + This value should not be null. + Această valoare nu ar trebui să fie nulă (null). + + + This value should be null. + Această valoare ar trebui să fie nulă (null). + + + This value is not valid. + Această valoare nu este validă. + + + This value is not a valid time. + Această valoare nu reprezintă o oră validă. + + + This value is not a valid URL. + Această valoare nu reprezintă un URL (link) valid. + + + The two values should be equal. + Cele două valori ar trebui să fie egale. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + Fișierul este prea mare. Mărimea maximă permisă este {{ limit }} {{ suffix }}. + + + The file is too large. + Fișierul este prea mare. + + + The file could not be uploaded. + Fișierul nu a putut fi încărcat. + + + This value should be a valid number. + Această valoare nu reprezintă un număr valid. + + + This file is not a valid image. + Acest fișier nu este o imagine validă. + + + This is not a valid IP address. + Această valoare nu este o adresă IP validă. + + + This value is not a valid language. + Această valoare nu reprezintă o limbă corectă. + + + This value is not a valid locale. + Această valoare nu reprezintă un dialect (o limbă) corect. + + + This value is not a valid country. + Această valoare nu este o țară validă. + + + This value is already used. + Această valoare este folosită deja. + + + The size of the image could not be detected. + Mărimea imaginii nu a putut fi detectată. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + Lățimea imaginii este prea mare ({{ width }}px). Lățimea maximă permisă este de {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + Lățimea imaginii este prea mică ({{ width }}px). Lățimea minimă permisă este de {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + Înălțimea imaginii este prea mare ({{ height }}px). Înălțimea maximă permisă este de {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + Înălțimea imaginii este prea mică ({{ height }}px). Înălțimea minimă permisă este de {{ min_height }}px. + + + This value should be the user's current password. + Această valoare trebuie să fie parola curentă a utilizatorului. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Această valoare trebuie să conțină exact {{ limit }} caracter.|Această valoare trebuie să conțină exact {{ limit }} caractere.|Această valoare trebuie să conțină exact {{ limit }} de caractere. + + + The file was only partially uploaded. + Fișierul a fost încărcat parțial. + + + No file was uploaded. + Nu a fost încărcat nici un fișier. + + + No temporary folder was configured in php.ini. + Nu este configurat nici un director temporar in php.ini. + + + Cannot write temporary file to disk. + Nu a fost posibilă scrierea fișierului temporar pe disk. + + + A PHP extension caused the upload to fail. + O extensie PHP a prevenit încărcarea cu succes a fișierului. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Această colecție trebuie să conțină cel puțin {{ limit }} element.|Această colecție trebuie să conțină cel puțin {{ limit }} elemente.|Această colecție trebuie să conțină cel puțin {{ limit }} de elemente. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Această colecție trebuie să conțină cel mult {{ limit }} element.|Această colecție trebuie să conțină cel mult {{ limit }} elemente.|Această colecție trebuie să conțină cel mult {{ limit }} de elemente. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Această colecție trebuie să conțină {{ limit }} element.|Această colecție trebuie să conțină {{ limit }} elemente.|Această colecție trebuie să conțină {{ limit }} de elemente. + + + Invalid card number. + Numărul card invalid. + + + Unsupported card type or invalid card number. + Tipul sau numărul cardului nu sunt valide. + + + This is not a valid International Bank Account Number (IBAN). + Acesta nu este un cod IBAN (International Bank Account Number) valid. + + + This value is not a valid ISBN-10. + Această valoare nu este un cod ISBN-10 valid. + + + This value is not a valid ISBN-13. + Această valoare nu este un cod ISBN-13 valid. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Această valoare nu este un cod ISBN-10 sau ISBN-13 valid. + + + This value is not a valid ISSN. + Această valoare nu este un cod ISSN valid. + + + This value is not a valid currency. + Această valoare nu este o monedă validă. + + + This value should be equal to {{ compared_value }}. + Această valoare trebuie să fie egală cu {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + Această valoare trebuie să fie mai mare de {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + Această valoare trebuie să fie mai mare sau egală cu {{ compared_value }}. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Această valoare trebuie identică cu {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Această valoare trebuie să fie mai mică de {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + Această valoare trebuie să fie mai mică sau egală cu {{ compared_value }}. + + + This value should not be equal to {{ compared_value }}. + Această valoare nu trebuie să fie egală cu {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Această valoare nu trebuie să fie identică cu {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Raportul imaginii este prea mare ({{ ratio }}). Raportul maxim permis este {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + Raportul imaginii este prea mic ({{ ratio }}). Raportul minim permis este {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + Imaginea este un pătrat ({{ width }}x{{ height }}px). Imaginile pătrat nu sunt permise. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + Imaginea are orientarea peisaj ({{ width }}x{{ height }}px). Imaginile cu orientare peisaj nu sunt permise. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + Imaginea are orientarea portret ({{ width }}x{{ height }}px). Imaginile cu orientare portret nu sunt permise. + + + An empty file is not allowed. + Nu se permite un fișier gol. + + + The host could not be resolved. + Numele host nu a putut fi rezolvat către o adresă IP. + + + This value does not match the expected {{ charset }} charset. + Această valoare nu corespunde setului de caractere {{ charset }} așteptat. + + + This is not a valid Business Identifier Code (BIC). + Codul BIC (Business Identifier Code) nu este valid. + + + Error + Eroare + + + This is not a valid UUID. + Identificatorul universal unic (UUID) nu este valid. + + + This value should be a multiple of {{ compared_value }}. + Această valoare trebuie să fie un multiplu de {{ compared_value }}. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Codul BIC (Business Identifier Code) nu este asociat cu codul IBAN {{ iban }}. + + + This value should be valid JSON. + Această valoare trebuie să fie un JSON valid. + + + This collection should contain only unique elements. + Acest set ar trebui să conțină numai elemente unice. + + + This value should be positive. + Această valoare ar trebui să fie pozitivă. + + + This value should be either positive or zero. + Această valoare trebuie să fie pozitivă sau zero. + + + This value should be negative. + Această valoare ar trebui să fie negativă. + + + This value should be either negative or zero. + Această valoare trebuie să fie negativă sau zero. + + + This value is not a valid timezone. + Această valoare nu este un fus orar valid. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Această parolă a fost compromisă și nu poate fi utilizată. Vă rugăm să utilizați o altă parolă. + + + This value should be between {{ min }} and {{ max }}. + Această valoare trebuie să fie între {{ min }} și {{ max }}. + + + This value is not a valid hostname. + Această valoare nu este un numele gazdei valid. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + Numărul de elemente din această colecție ar trebui să fie un multiplu al {{ compared_value }}. + + + This value should satisfy at least one of the following constraints: + Această valoare trebuie să îndeplinească cel puțin una dintre următoarele reguli: + + + Each element of this collection should satisfy its own set of constraints. + Fiecare element din acest set ar trebui să îndeplinească propriul set de reguli. + + + This value is not a valid International Securities Identification Number (ISIN). + Această valoare nu este un număr internațional de identificare (ISIN) valabil. + + + This value should be a valid expression. + Această valoare ar trebui să fie o expresie validă. + + + This value is not a valid CIDR notation. + Această valoare nu este o notație CIDR validă. + + + The value of the netmask should be between {{ min }} and {{ max }}. + Valoarea netmask-ului trebuie sa fie intre {{ min }} si {{ max }}. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.ru.xlf b/src/vendor/symfony/validator/Resources/translations/validators.ru.xlf new file mode 100644 index 0000000..8705cbb --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.ru.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + Значение должно быть ложным. + + + This value should be true. + Значение должно быть истинным. + + + This value should be of type {{ type }}. + Тип значения должен быть {{ type }}. + + + This value should be blank. + Значение должно быть пустым. + + + The value you selected is not a valid choice. + Выбранное Вами значение недопустимо. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Вы должны выбрать хотя бы {{ limit }} вариант.|Вы должны выбрать хотя бы {{ limit }} варианта.|Вы должны выбрать хотя бы {{ limit }} вариантов. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Вы должны выбрать не более чем {{ limit }} вариант.|Вы должны выбрать не более чем {{ limit }} варианта.|Вы должны выбрать не более чем {{ limit }} вариантов. + + + One or more of the given values is invalid. + Одно или несколько заданных значений недопустимо. + + + This field was not expected. + Это поле не ожидалось. + + + This field is missing. + Это поле отсутствует. + + + This value is not a valid date. + Значение не является правильной датой. + + + This value is not a valid datetime. + Значение даты и времени недопустимо. + + + This value is not a valid email address. + Значение адреса электронной почты недопустимо. + + + The file could not be found. + Файл не может быть найден. + + + The file is not readable. + Файл не может быть прочитан. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + Файл слишком большой ({{ size }} {{ suffix }}). Максимально допустимый размер {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + MIME-тип файла недопустим ({{ type }}). Допустимы MIME-типы файлов {{ types }}. + + + This value should be {{ limit }} or less. + Значение должно быть {{ limit }} или меньше. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Значение слишком длинное. Должно быть равно {{ limit }} символу или меньше.|Значение слишком длинное. Должно быть равно {{ limit }} символам или меньше.|Значение слишком длинное. Должно быть равно {{ limit }} символам или меньше. + + + This value should be {{ limit }} or more. + Значение должно быть {{ limit }} или больше. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Значение слишком короткое. Должно быть равно {{ limit }} символу или больше.|Значение слишком короткое. Должно быть равно {{ limit }} символам или больше.|Значение слишком короткое. Должно быть равно {{ limit }} символам или больше. + + + This value should not be blank. + Значение не должно быть пустым. + + + This value should not be null. + Значение не должно быть null. + + + This value should be null. + Значение должно быть null. + + + This value is not valid. + Значение недопустимо. + + + This value is not a valid time. + Значение времени недопустимо. + + + This value is not a valid URL. + Значение не является допустимым URL. + + + The two values should be equal. + Оба значения должны быть одинаковыми. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + Файл слишком большой. Максимально допустимый размер {{ limit }} {{ suffix }}. + + + The file is too large. + Файл слишком большой. + + + The file could not be uploaded. + Файл не может быть загружен. + + + This value should be a valid number. + Значение должно быть числом. + + + This file is not a valid image. + Файл не является допустимым форматом изображения. + + + This is not a valid IP address. + Значение не является допустимым IP адресом. + + + This value is not a valid language. + Значение не является допустимым языком. + + + This value is not a valid locale. + Значение не является допустимой локалью. + + + This value is not a valid country. + Значение не является допустимой страной. + + + This value is already used. + Это значение уже используется. + + + The size of the image could not be detected. + Не удалось определить размер изображения. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + Ширина изображения слишком велика ({{ width }}px). Максимально допустимая ширина {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + Ширина изображения слишком мала ({{ width }}px). Минимально допустимая ширина {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + Высота изображения слишком велика ({{ height }}px). Максимально допустимая высота {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + Высота изображения слишком мала ({{ height }}px). Минимально допустимая высота {{ min_height }}px. + + + This value should be the user's current password. + Значение должно быть текущим паролем пользователя. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Значение должно быть равно {{ limit }} символу.|Значение должно быть равно {{ limit }} символам.|Значение должно быть равно {{ limit }} символам. + + + The file was only partially uploaded. + Файл был загружен только частично. + + + No file was uploaded. + Файл не был загружен. + + + No temporary folder was configured in php.ini. + Не настроена временная директория в php.ini. + + + Cannot write temporary file to disk. + Невозможно записать временный файл на диск. + + + A PHP extension caused the upload to fail. + Расширение PHP вызвало ошибку при загрузке. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Эта коллекция должна содержать {{ limit }} элемент или больше.|Эта коллекция должна содержать {{ limit }} элемента или больше.|Эта коллекция должна содержать {{ limit }} элементов или больше. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Эта коллекция должна содержать {{ limit }} элемент или меньше.|Эта коллекция должна содержать {{ limit }} элемента или меньше.|Эта коллекция должна содержать {{ limit }} элементов или меньше. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Эта коллекция должна содержать ровно {{ limit }} элемент.|Эта коллекция должна содержать ровно {{ limit }} элемента.|Эта коллекция должна содержать ровно {{ limit }} элементов. + + + Invalid card number. + Неверный номер карты. + + + Unsupported card type or invalid card number. + Неподдерживаемый тип или неверный номер карты. + + + This is not a valid International Bank Account Number (IBAN). + Значение не является допустимым международным номером банковского счета (IBAN). + + + This value is not a valid ISBN-10. + Значение имеет неверный формат ISBN-10. + + + This value is not a valid ISBN-13. + Значение имеет неверный формат ISBN-13. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Значение не соответствует форматам ISBN-10 и ISBN-13. + + + This value is not a valid ISSN. + Значение не соответствует формату ISSN. + + + This value is not a valid currency. + Некорректный формат валюты. + + + This value should be equal to {{ compared_value }}. + Значение должно быть равно {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + Значение должно быть больше чем {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + Значение должно быть больше или равно {{ compared_value }}. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Значение должно быть идентичным {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Значение должно быть меньше чем {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + Значение должно быть меньше или равно {{ compared_value }}. + + + This value should not be equal to {{ compared_value }}. + Значение не должно быть равно {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Значение не должно быть идентичным {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Соотношение сторон изображения слишком велико ({{ ratio }}). Максимальное соотношение сторон {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + Соотношение сторон изображения слишком мало ({{ ratio }}). Минимальное соотношение сторон {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + Изображение квадратное ({{ width }}x{{ height }}px). Квадратные изображения не разрешены. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + Изображение в альбомной ориентации ({{ width }}x{{ height }}px). Изображения в альбомной ориентации не разрешены. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + Изображение в портретной ориентации ({{ width }}x{{ height }}px). Изображения в портретной ориентации не разрешены. + + + An empty file is not allowed. + Пустые файлы не разрешены. + + + The host could not be resolved. + Имя хоста не может быть разрешено. + + + This value does not match the expected {{ charset }} charset. + Значение не совпадает с ожидаемой {{ charset }} кодировкой. + + + This is not a valid Business Identifier Code (BIC). + Значение не соответствует формату BIC. + + + Error + Ошибка + + + This is not a valid UUID. + Значение не соответствует формату UUID. + + + This value should be a multiple of {{ compared_value }}. + Значение должно быть кратно {{ compared_value }}. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Данный BIC не связан с IBAN {{ iban }}. + + + This value should be valid JSON. + Значение должно быть корректным JSON. + + + This collection should contain only unique elements. + Эта коллекция должна содержать только уникальные элементы. + + + This value should be positive. + Значение должно быть положительным. + + + This value should be either positive or zero. + Значение должно быть положительным или равным нулю. + + + This value should be negative. + Значение должно быть отрицательным. + + + This value should be either negative or zero. + Значение должно быть отрицательным или равным нулю. + + + This value is not a valid timezone. + Значение не является корректным часовым поясом. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Данный пароль был скомпрометирован в результате утечки данных и не должен быть использован. Пожалуйста, используйте другой пароль. + + + This value should be between {{ min }} and {{ max }}. + Значение должно быть между {{ min }} и {{ max }}. + + + This value is not a valid hostname. + Значение не является корректным именем хоста. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + Количество элементов в этой коллекции должно быть кратным {{ compared_value }}. + + + This value should satisfy at least one of the following constraints: + Значение должно удовлетворять как минимум одному из следующих ограничений: + + + Each element of this collection should satisfy its own set of constraints. + Каждый элемент этой коллекции должен удовлетворять своему собственному набору ограничений. + + + This value is not a valid International Securities Identification Number (ISIN). + Значение не является корректным международным идентификационным номером ценных бумаг (ISIN). + + + This value should be a valid expression. + Это значение должно быть корректным выражением. + + + This value is not a valid CSS color. + Значение не является корректным CSS цветом. + + + This value is not a valid CIDR notation. + Значение не соответствует нотации CIDR. + + + The value of the netmask should be between {{ min }} and {{ max }}. + Значение маски подсети должно быть от {{ min }} до {{ max }}. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.sk.xlf b/src/vendor/symfony/validator/Resources/translations/validators.sk.xlf new file mode 100644 index 0000000..55a8111 --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.sk.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + Táto hodnota by mala byť nastavená na false. + + + This value should be true. + Táto hodnota by mala byť nastavená na true. + + + This value should be of type {{ type }}. + Táto hodnota by mala byť typu {{ type }}. + + + This value should be blank. + Táto hodnota by mala byť prázdna. + + + The value you selected is not a valid choice. + Táto hodnota by mala byť jednou z poskytnutých možností. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Mali by ste vybrať minimálne {{ limit }} možnosť.|Mali by ste vybrať minimálne {{ limit }} možnosti.|Mali by ste vybrať minimálne {{ limit }} možností. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Mali by ste vybrať najviac {{ limit }} možnosť.|Mali by ste vybrať najviac {{ limit }} možnosti.|Mali by ste vybrať najviac {{ limit }} možností. + + + One or more of the given values is invalid. + Niektoré z uvedených hodnôt sú neplatné. + + + This field was not expected. + Toto pole sa neočakáva. + + + This field is missing. + Toto pole chýba. + + + This value is not a valid date. + Tato hodnota nemá platný formát dátumu. + + + This value is not a valid datetime. + Táto hodnota nemá platný formát dátumu a času. + + + This value is not a valid email address. + Táto hodnota nie je platná emailová adresa. + + + The file could not be found. + Súbor sa nenašiel. + + + The file is not readable. + Súbor nie je čitateľný. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + Súbor je príliš veľký ({{ size }} {{ suffix }}). Maximálna povolená veľkosť je {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + Súbor typu ({{ type }}) nie je podporovaný. Podporované typy sú {{ types }}. + + + This value should be {{ limit }} or less. + Táto hodnota by mala byť {{ limit }} alebo menej. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Táto hodnota obsahuje viac znakov ako je povolené. Mala by obsahovať najviac {{ limit }} znak.|Táto hodnota obsahuje viac znakov ako je povolené. Mala by obsahovať najviac {{ limit }} znaky.|Táto hodnota obsahuje viac znakov ako je povolené. Mala by obsahovať najviac {{ limit }} znakov. + + + This value should be {{ limit }} or more. + Táto hodnota by mala byť viac ako {{ limit }}. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Táto hodnota je príliš krátka. Musí obsahovať minimálne {{ limit }} znak.|Táto hodnota je príliš krátka. Musí obsahovať minimálne {{ limit }} znaky.|Táto hodnota je príliš krátka. Minimálny počet znakov je {{ limit }}. + + + This value should not be blank. + Táto hodnota by mala byť vyplnená. + + + This value should not be null. + Táto hodnota by nemala byť null. + + + This value should be null. + Táto hodnota by mala byť null. + + + This value is not valid. + Táto hodnota nie je platná. + + + This value is not a valid time. + Tato hodnota nemá správny formát času. + + + This value is not a valid URL. + Táto hodnota nie je platnou URL adresou. + + + The two values should be equal. + Tieto dve hodnoty by mali byť rovnaké. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + Súbor je príliš veľký. Maximálna povolená veľkosť je {{ limit }} {{ suffix }}. + + + The file is too large. + Súbor je príliš veľký. + + + The file could not be uploaded. + Súbor sa nepodarilo nahrať. + + + This value should be a valid number. + Táto hodnota by mala byť číslo. + + + This file is not a valid image. + Tento súbor nie je obrázok. + + + This is not a valid IP address. + Toto nie je platná IP adresa. + + + This value is not a valid language. + Tento jazyk neexistuje. + + + This value is not a valid locale. + Táto lokalizácia neexistuje. + + + This value is not a valid country. + Táto krajina neexistuje. + + + This value is already used. + Táto hodnota sa už používa. + + + The size of the image could not be detected. + Nepodarilo sa zistiť rozmery obrázku. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + Obrázok je príliš široký ({{ width }}px). Maximálna povolená šírka obrázku je {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + Obrázok je príliš úzky ({{ width }}px). Minimálna šírka obrázku by mala byť {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + >Obrázok je príliš vysoký ({{ height }}px). Maximálna povolená výška obrázku je {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + Obrázok je príliš nízky ({{ height }}px). Minimálna výška obrázku by mala byť {{ min_height }}px. + + + This value should be the user's current password. + Táto hodnota by mala byť aktuálne heslo používateľa. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Táto hodnota by mala mať presne {{ limit }} znak.|Táto hodnota by mala mať presne {{ limit }} znaky.|Táto hodnota by mala mať presne {{ limit }} znakov. + + + The file was only partially uploaded. + Bola nahraná len časť súboru. + + + No file was uploaded. + Žiadny súbor nebol nahraný. + + + No temporary folder was configured in php.ini. + V php.ini nie je nastavená cesta k addressáru pre dočasné súbory. + + + Cannot write temporary file to disk. + Dočasný súbor sa nepodarilo zapísať na disk. + + + A PHP extension caused the upload to fail. + Rozšírenie PHP zabránilo nahraniu súboru. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Táto kolekcia by mala obsahovať aspoň {{ limit }} prvok alebo viac.|Táto kolekcia by mala obsahovať aspoň {{ limit }} prvky alebo viac.|Táto kolekcia by mala obsahovať aspoň {{ limit }} prvkov alebo viac. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Táto kolekcia by mala maximálne {{ limit }} prvok.|Táto kolekcia by mala obsahovať maximálne {{ limit }} prvky.|Táto kolekcia by mala obsahovať maximálne {{ limit }} prvkov. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Táto kolekcia by mala obsahovať presne {{ limit }} prvok.|Táto kolekcia by mala obsahovať presne {{ limit }} prvky.|Táto kolekcia by mala obsahovať presne {{ limit }} prvkov. + + + Invalid card number. + Neplatné číslo karty. + + + Unsupported card type or invalid card number. + Nepodporovaný typ karty alebo neplatné číslo karty. + + + This is not a valid International Bank Account Number (IBAN). + Toto je neplatný IBAN. + + + This value is not a valid ISBN-10. + Táto hodnota je neplatné ISBN-10. + + + This value is not a valid ISBN-13. + Táto hodnota je neplatné ISBN-13. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Táto hodnota nie je platné ISBN-10 ani ISBN-13. + + + This value is not a valid ISSN. + Táto hodnota nie je platné ISSN. + + + This value is not a valid currency. + Táto hodnota nie je platná mena. + + + This value should be equal to {{ compared_value }}. + Táto hodnota by mala byť rovná {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + Táto hodnota by mala byť väčšia ako {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + Táto hodnota by mala byť väčšia alebo rovná {{ compared_value }}. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Táto hodnota by mala byť typu {{ compared_value_type }} a zároveň by mala byť rovná {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Táto hodnota by mala byť menšia ako {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + Táto hodnota by mala byť menšia alebo rovná {{ compared_value }}. + + + This value should not be equal to {{ compared_value }}. + Táto hodnota by nemala byť rovná {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Táto hodnota by nemala byť typu {{ compared_value_type }} a zároveň by nemala byť rovná {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Pomer strán obrázku je príliš veľký ({{ ratio }}). Maximálny povolený pomer strán obrázku je {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + Pomer strán obrázku je príliš malý ({{ ratio }}). Minimálny povolený pomer strán obrázku je {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + Strany obrázku sú štvorcové ({{ width }}x{{ height }}px). Štvorcové obrázky nie sú povolené. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + Obrázok je orientovaný na šírku ({{ width }}x{{ height }}px). Obrázky orientované na šírku nie sú povolené. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + Obrázok je orientovaný na výšku ({{ width }}x{{ height }}px). Obrázky orientované na výšku nie sú povolené. + + + An empty file is not allowed. + Súbor nesmie byť prázdny. + + + The host could not be resolved. + Hostiteľa nebolo možné rozpoznať. + + + This value does not match the expected {{ charset }} charset. + Táto hodnota nezodpovedá očakávanej znakovej sade {{ charset }}. + + + This is not a valid Business Identifier Code (BIC). + Táto hodnota nie je platný identifikačný kód podniku (BIC). + + + Error + Chyba + + + This is not a valid UUID. + Táto hodnota nie je platný UUID. + + + This value should be a multiple of {{ compared_value }}. + Táto hodnota by mala byť násobkom {{ compared_value }}. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Tento identifikačný kód podniku (BIC) nie je spojený s IBAN {{ iban }}. + + + This value should be valid JSON. + Táto hodnota by mala byť platný JSON. + + + This collection should contain only unique elements. + Táto kolekcia by mala obsahovať len unikátne prkvy. + + + This value should be positive. + Táto hodnota by mala byť kladná. + + + This value should be either positive or zero. + Táto hodnota by mala byť kladná alebo nulová. + + + This value should be negative. + Táto hodnota by mala byť záporná. + + + This value should be either negative or zero. + Táto hodnota by mala byť záporná alebo nulová. + + + This value is not a valid timezone. + Táto hodnota nie je platné časové pásmo. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Toto heslo uniklo pri narušení ochrany dát, nie je možné ho použiť. Prosím, použite iné heslo. + + + This value should be between {{ min }} and {{ max }}. + Táto hodnota by mala byť medzi {{ min }} a {{ max }}. + + + This value is not a valid hostname. + Táto hodnota nie je platný hostname. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + Počet prvkov v tejto kolekcii musí byť násobok {{ compared_value }}. + + + This value should satisfy at least one of the following constraints: + Táto hodnota musí spĺňať aspoň jedno z nasledujúcich obmedzení: + + + Each element of this collection should satisfy its own set of constraints. + Každý prvok v tejto kolekcii musí spĺňať svoje vlastné obmedzenia. + + + This value is not a valid International Securities Identification Number (ISIN). + Táto hodnota nie je platné medzinárodné označenie cenného papiera (ISIN). + + + This value should be a valid expression. + Táto hodnota by mala byť platným výrazom. + + + This value is not a valid CSS color. + Táto hodnota nie je platná CSS farba. + + + This value is not a valid CIDR notation. + Táto hodnota nie je platnou notáciou CIDR. + + + The value of the netmask should be between {{ min }} and {{ max }}. + Hodnota masky siete by mala byť medzi {{ min }} a {{ max }}. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.sl.xlf b/src/vendor/symfony/validator/Resources/translations/validators.sl.xlf new file mode 100644 index 0000000..b956911 --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.sl.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + Vrednost bi morala biti nepravilna (false). + + + This value should be true. + Vrednost bi morala biti pravilna (true). + + + This value should be of type {{ type }}. + Vrednost mora biti naslednjega tipa {{ type }}. + + + This value should be blank. + Vrednost mora biti prazna. + + + The value you selected is not a valid choice. + Vrednost, ki ste jo izbrali, ni veljavna možnost. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Izbrati morate vsaj {{ limit }} možnost.|Izbrati morate vsaj {{ limit }} možnosti.|Izbrati morate vsaj {{ limit }} možnosti.|Izbrati morate vsaj {{ limit }} možnosti. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Izberete lahko največ {{ limit }} možnost.|Izberete lahko največ {{ limit }} možnosti.|Izberete lahko največ {{ limit }} možnosti.|Izberete lahko največ {{ limit }} možnosti. + + + One or more of the given values is invalid. + Ena ali več podanih vrednosti ni veljavnih. + + + This field was not expected. + To polje ni bilo pričakovati. + + + This field is missing. + To polje manjka. + + + This value is not a valid date. + Ta vrednost ni veljaven datum. + + + This value is not a valid datetime. + Ta vrednost ni veljaven datum in čas. + + + This value is not a valid email address. + Ta vrednost ni veljaven e-poštni naslov. + + + The file could not be found. + Datoteke ni mogoče najti. + + + The file is not readable. + Datoteke ni mogoče prebrati. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + Datoteka je prevelika ({{ size }} {{ suffix }}). Največja dovoljena velikost je {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + Mime tip datoteke je neveljaven ({{ type }}). Dovoljeni mime tipi so {{ types }}. + + + This value should be {{ limit }} or less. + Ta vrednost bi morala biti {{ limit }} ali manj. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Ta vrednost je predolga. Morala bi imeti {{ limit }} znak ali manj.|Ta vrednost je predolga. Morala bi imeti {{ limit }} znaka ali manj.|Ta vrednost je predolga. Morala bi imeti {{ limit }} znake ali manj.|Ta vrednost je predolga. Morala bi imeti {{ limit }} znakov ali manj. + + + This value should be {{ limit }} or more. + Ta vrednost bi morala biti {{ limit }} ali več. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Ta vrednost je prekratka. Morala bi imeti {{ limit }} znak ali več.|Ta vrednost je prekratka. Morala bi imeti {{ limit }} znaka ali več.|Ta vrednost je prekratka. Morala bi imeti {{ limit }} znake ali več.|Ta vrednost je prekratka. Morala bi imeti {{ limit }} znakov ali več. + + + This value should not be blank. + Ta vrednost ne bi smela biti prazna. + + + This value should not be null. + Ta vrednost ne bi smela biti nedefinirana (null). + + + This value should be null. + Ta vrednost bi morala biti nedefinirana (null). + + + This value is not valid. + Ta vrednost ni veljavna. + + + This value is not a valid time. + Ta vrednost ni veljaven čas. + + + This value is not a valid URL. + Ta vrednost ni veljaven URL. + + + The two values should be equal. + Ti dve vrednosti bi morali biti enaki. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + Datoteka je prevelika. Največja dovoljena velikost je {{ limit }} {{ suffix }}. + + + The file is too large. + Datoteka je prevelika. + + + The file could not be uploaded. + Datoteke ni bilo mogoče naložiti. + + + This value should be a valid number. + Ta vrednost bi morala biti veljavna številka. + + + This file is not a valid image. + Ta datoteka ni veljavna slika. + + + This is not a valid IP address. + To ni veljaven IP naslov. + + + This value is not a valid language. + Ta vrednost ni veljaven jezik. + + + This value is not a valid locale. + Ta vrednost ni veljavna lokalnost. + + + This value is not a valid country. + Ta vrednost ni veljavna država. + + + This value is already used. + Ta vrednost je že uporabljena. + + + The size of the image could not be detected. + Velikosti slike ni bilo mogoče zaznati. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + Širina slike je preširoka ({{ width }}px). Največja dovoljena širina je {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + Širina slike je premajhna ({{ width }}px). Najmanjša predvidena širina je {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + Višina slike je prevelika ({{ height }}px). Največja dovoljena višina je {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + Višina slike je premajhna ({{ height }}px). Najmanjša predvidena višina je {{ min_height }}px. + + + This value should be the user's current password. + Ta vrednost bi morala biti trenutno uporabnikovo geslo. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Ta vrednost bi morala imeti točno {{ limit }} znak.|Ta vrednost bi morala imeti točno {{ limit }} znaka.|Ta vrednost bi morala imeti točno {{ limit }} znake.|Ta vrednost bi morala imeti točno {{ limit }} znakov. + + + The file was only partially uploaded. + Datoteka je bila le delno naložena. + + + No file was uploaded. + Nobena datoteka ni bila naložena. + + + No temporary folder was configured in php.ini. + Začasna mapa ni nastavljena v php.ini. + + + Cannot write temporary file to disk. + Začasne datoteke ni bilo mogoče zapisati na disk. + + + A PHP extension caused the upload to fail. + PHP razširitev je vzrok, da nalaganje ni uspelo. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Ta zbirka bi morala vsebovati {{ limit }} element ali več.|Ta zbirka bi morala vsebovati {{ limit }} elementa ali več.|Ta zbirka bi morala vsebovati {{ limit }} elemente ali več.|Ta zbirka bi morala vsebovati {{ limit }} elementov ali več. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Ta zbirka bi morala vsebovati {{ limit }} element ali manj.|Ta zbirka bi morala vsebovati {{ limit }} elementa ali manj.|Ta zbirka bi morala vsebovati {{ limit }} elemente ali manj.|Ta zbirka bi morala vsebovati {{ limit }} elementov ali manj. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Ta zbirka bi morala vsebovati točno {{ limit }} element.|Ta zbirka bi morala vsebovati točno {{ limit }} elementa.|Ta zbirka bi morala vsebovati točno {{ limit }} elemente.|Ta zbirka bi morala vsebovati točno {{ limit }} elementov. + + + Invalid card number. + Neveljavna številka kartice. + + + Unsupported card type or invalid card number. + Nepodprti tip kartice ali neveljavna številka kartice. + + + This is not a valid International Bank Account Number (IBAN). + To ni veljavna mednarodna številka bančnega računa (IBAN). + + + This value is not a valid ISBN-10. + Neveljavna vrednost po ISBN-10. + + + This value is not a valid ISBN-13. + Neveljavna vrednost po ISBN-13. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Neveljavna vrednost po ISBN-10 ali po ISBN-13. + + + This value is not a valid ISSN. + Neveljavna vrednost ISSN. + + + This value is not a valid currency. + Ta vrednost ni veljavna valuta. + + + This value should be equal to {{ compared_value }}. + Ta vrednost bi morala biti enaka {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + Ta vrednost bi morala biti večja od {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + Ta vrednost bi morala biti večja ali enaka {{ compared_value }}. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Ta vrednost bi morala biti identična {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Ta vrednost bi morala biti manjša od {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + Ta vrednost bi morala biti manjša ali enaka {{ compared_value }}. + + + This value should not be equal to {{ compared_value }}. + Ta vrednost ne bi smela biti enaka {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Ta vrednost ne bi smela biti identična {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Razmerje slike je preveliko ({{ ratio }}). Največje dovoljeno razmerje je {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + Razmerje slike je premajhno ({{ ratio }}). Najmanjše pričakovano razmerje je {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + Slika je kvadrat ({{ width }}x{{ height }}px). Kvadratne slike niso dovoljene. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + Slika je ležeče usmerjena ({{ width }}x{{ height }}px). Ležeče usmerjene slike niso dovoljene. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + Slika je pokončno usmerjena ({{ width }}x{{ height }}px). Pokončno usmerjene slike niso dovoljene. + + + An empty file is not allowed. + Prazna datoteka ni dovoljena. + + + The host could not be resolved. + Gostitelja ni bilo mogoče prepoznati. + + + This value does not match the expected {{ charset }} charset. + Ta vrednost se ne ujema s pričakovanim naborom znakov {{ charset }}. + + + This is not a valid Business Identifier Code (BIC). + To ni veljavna identifikacijska koda podjetja (BIC). + + + Error + Napaka + + + This is not a valid UUID. + To ni veljaven UUID. + + + This value should be a multiple of {{ compared_value }}. + Ta vrednost bi morala biti večkratnik od {{ compared_value }}. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Ta poslovna identifikacijska koda (BIC) ni povezana z IBAN {{ iban }}. + + + This value should be valid JSON. + Ta vrednost bi morala biti veljaven JSON. + + + This collection should contain only unique elements. + Ta zbirka bi morala vsebovati samo edinstvene elemente. + + + This value should be positive. + Ta vrednost bi morala biti pozitivna. + + + This value should be either positive or zero. + Ta vrednost bi morala biti pozitivna ali enaka nič. + + + This value should be negative. + Ta vrednost bi morala biti negativna. + + + This value should be either negative or zero. + Ta vrednost bi morala biti negativna ali enaka nič. + + + This value is not a valid timezone. + Ta vrednost ni veljaven časovni pas. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + To geslo je ušlo pri kršitvi varnosti podatkov in ga ne smete uporabljati. Prosimo, uporabite drugo geslo. + + + This value should be between {{ min }} and {{ max }}. + Ta vrednost bi morala biti med {{ min }} in {{ max }}. + + + This value is not a valid hostname. + Ta vrednost ni veljavno ime gostitelja. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + Število elementov v tej zbirki bi moralo biti mnogokratnik {{ compared_value }}. + + + This value should satisfy at least one of the following constraints: + Ta vrednost bi morala zadostiti vsaj eni izmed sledečih omejitev: + + + Each element of this collection should satisfy its own set of constraints. + Vsak element te zbirke bi moral zadostiti svojemu lastnemu naboru omejitev. + + + This value is not a valid International Securities Identification Number (ISIN). + Ta vrednost ni veljavna mednarodna identifikacijska koda vrednostnih papirjev (ISIN). + + + This value should be a valid expression. + Ta vrednost bi morala biti veljaven izraz. + + + This value is not a valid CSS color. + Ta vrednost ni veljavna barva CSS. + + + This value is not a valid CIDR notation. + Ta vrednost ni veljaven zapis CIDR. + + + The value of the netmask should be between {{ min }} and {{ max }}. + Vrednost omrežne maske mora biti med {{ min }} in {{ max }}. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.sq.xlf b/src/vendor/symfony/validator/Resources/translations/validators.sq.xlf new file mode 100644 index 0000000..6c0acb9 --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.sq.xlf @@ -0,0 +1,391 @@ + + + + + + This value should be false. + Kjo vlerë duhet të jetë e pavërtetë (false). + + + This value should be true. + Kjo vlerë duhet të jetë e vërtetë (true). + + + This value should be of type {{ type }}. + Kjo vlerë duhet të jetë e llojit {{ type }}. + + + This value should be blank. + Kjo vlerë duhet të jetë e zbrazët. + + + The value you selected is not a valid choice. + Vlera që keni zgjedhur nuk është alternativë e vlefshme. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Duhet të zgjedhni së paku {{ limit }} alternativë.|Duhet të zgjedhni së paku {{ limit }} alternativa. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Duhet të zgjedhni më së shumti {{ limit }} alternativë.|Duhet të zgjedhni më së shumti {{ limit }} alternativa. + + + One or more of the given values is invalid. + Një apo më shumë nga vlerat e dhëna janë të pavlefshme. + + + This field was not expected. + Kjo fushë nuk pritej. + + + This field is missing. + Kjo fushë mungon. + + + This value is not a valid date. + Kjo vlerë nuk është datë e vlefshme. + + + This value is not a valid datetime. + Kjo vlerë nuk është datë-kohë e vlefshme. + + + This value is not a valid email address. + Kjo vlerë nuk është adresë email-i e vlefshme. + + + The file could not be found. + File nuk mund të gjindej. + + + The file is not readable. + File nuk është i lexueshëm. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + File është shumë i madh ({{ size }} {{ suffix }}). Madhësia maksimale e lejuar është {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + Lloji mime i file-it është i pavlefshëm ({{ type }}). Llojet mime të lejuara janë {{ types }}. + + + This value should be {{ limit }} or less. + Kjo vlerë duhet të jetë {{ limit }} ose më pak. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Kjo vlerë është shumë e gjatë. Duhet të përmbaj {{ limit }} karakter ose më pak.|Kjo vlerë është shumë e gjatë. Duhet të përmbaj {{ limit }} karaktere ose më pak. + + + This value should be {{ limit }} or more. + Kjo vlerë duhet të jetë {{ limit }} ose më shumë. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Kjo vlerë është shumë e shkurtër. Duhet të përmbaj {{ limit }} karakter ose më shumë.|Kjo vlerë është shumë e shkurtër. Duhet të përmbaj {{ limit }} karaktere ose më shumë. + + + This value should not be blank. + Kjo vlerë nuk duhet të jetë e zbrazët. + + + This value should not be null. + Kjo vlerë nuk duhet të jetë null. + + + This value should be null. + Kjo vlerë duhet të jetë null. + + + This value is not valid. + Kjo vlerë nuk është e vlefshme. + + + This value is not a valid time. + Kjo vlerë nuk është kohë e vlefshme. + + + This value is not a valid URL. + Kjo vlerë nuk është URL e vlefshme. + + + The two values should be equal. + Këto dy vlera duhet të jenë të barabarta. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + Ky file është shumë i madh. Madhësia maksimale e lejuar është {{ limit }} {{ suffix }}. + + + The file is too large. + Ky file është shumë i madh. + + + The file could not be uploaded. + Ky file nuk mund të ngarkohet. + + + This value should be a valid number. + Kjo vlerë duhet të jetë numër i vlefshëm. + + + This file is not a valid image. + Ky file nuk është imazh i vlefshëm. + + + This is not a valid IP address. + Kjo adresë IP nuk është e vlefshme. + + + This value is not a valid language. + Kjo vlerë nuk është gjuhë e vlefshme. + + + This value is not a valid locale. + Kjo vlerë nuk është nje locale i vlefshëm. + + + This value is not a valid country. + Kjo vlerë nuk është shtet i vlefshëm. + + + This value is already used. + Kjo vlerë është tashmë në përdorim. + + + The size of the image could not be detected. + Madhësia e imazhit nuk mund të zbulohet. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + Gjerësia e imazhit është shumë e madhe ({{ width }}px). Gjerësia maksimale e lejuar është {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + Gjerësia e imazhit është shumë e vogël ({{ width }}px). Gjerësia minimale e pritur është {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + Gjatësia e imazhit është shumë e madhe ({{ height }}px). Gjatësia maksimale e lejuar është {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + Gjatësia e imazhit është shumë e vogël ({{ height }}px). Gjatësia minimale e pritur është {{ min_height }}px. + + + This value should be the user's current password. + Kjo vlerë duhet të jetë fjalëkalimi aktual i përdoruesit. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Kjo vlerë duhet të ketë saktësisht {{ limit }} karakter.|Kjo vlerë duhet të ketë saktësisht {{ limit }} karaktere. + + + The file was only partially uploaded. + Ky file është ngarkuar pjesërisht. + + + No file was uploaded. + Nuk është ngarkuar ndonjë file. + + + No temporary folder was configured in php.ini. + Asnjë folder i përkohshëm nuk është konfiguruar në php.ini. + + + Cannot write temporary file to disk. + Nuk mund të shkruhet file i përkohshëm në disk. + + + A PHP extension caused the upload to fail. + Një ekstension i PHP-së shkaktoi dështimin e ngarkimit. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Ky koleksion duhet të përmbajë {{ limit }} element ose më shumë.|Ky koleksion duhet të përmbajë {{ limit }} elemente ose më shumë. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Ky koleksion duhet të përmbajë {{ limit }} element ose më pak.|Ky koleksion duhet të përmbajë {{ limit }} elemente ose më pak. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Ky koleksion duhet të përmbajë saktësisht {{ limit }} element.|Ky koleksion duhet të përmbajë saktësisht {{ limit }} elemente. + + + Invalid card number. + Numër karte i pavlefshëm. + + + Unsupported card type or invalid card number. + Lloj karte i papranuar ose numër karte i pavlefshëm. + + + This is not a valid International Bank Account Number (IBAN). + Ky nuk është një numër i vlefshëm ndërkombëtar i llogarisë bankare (IBAN). + + + This value is not a valid ISBN-10. + Kjo vlerë nuk është një ISBN-10 e vlefshme. + + + This value is not a valid ISBN-13. + Kjo vlerë nuk është një ISBN-13 e vlefshme. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Kjo vlerë nuk është as ISBN-10 e vlefshme as ISBN-13 e vlefshme. + + + This value is not a valid ISSN. + Kjo vlerë nuk është një ISSN e vlefshme. + + + This value is not a valid currency. + Kjo vlerë nuk është një monedhë e vlefshme. + + + This value should be equal to {{ compared_value }}. + Kjo vlerë duhet të jetë e barabartë me {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + Kjo vlerë duhet të jetë më e madhe se {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + Kjo vlerë duhet të jetë më e madhe ose e barabartë me {{ compared_value }}. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Kjo vlerë duhet të jetë identike me {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Kjo vlerë duhet të jetë më vogël se {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + Kjo vlerë duhet të jetë më e vogël ose e barabartë me {{ compared_value }}. + + + This value should not be equal to {{ compared_value }}. + Kjo vlerë nuk duhet të jetë e barabartë me {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Kjo vlerë nuk duhet të jetë identike me {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Raporti i imazhit është shumë i madh ({{ ratio }}). Raporti maksimal i lejuar është {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + Raporti i imazhit është shumë i vogël ({{ ratio }}). Raporti minimal pritet të jetë {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + Imazhi është katror ({{ width }}x{{ height }}px). Imazhet katrore nuk janë të lejuara. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + Imazhi është i orientuar horizontalisht ({{ width }}x{{ height }}px). Imazhet e orientuara horizontalisht nuk lejohen. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + Imazhi është i orientuar vertikalisht ({{ width }}x{{ height }}px). Imazhet orientuara vertikalisht nuk lejohen. + + + An empty file is not allowed. + Një file i zbrazët nuk lejohet. + + + The host could not be resolved. + Host-i nuk mund te zbulohej. + + + This value does not match the expected {{ charset }} charset. + Kjo vlerë nuk përputhet me kodifikimin e karaktereve {{ charset }} që pritej. + + + This is not a valid Business Identifier Code (BIC). + Ky nuk është një Kod Identifikues i Biznesit (BIC) i vleflshem. + + + Error + Gabim + + + This is not a valid UUID. + Ky nuk është një UUID i vlefshëm. + + + This value should be a multiple of {{ compared_value }}. + Kjo vlerë duhet të jetë një shumëfish i {{ compared_value }}. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Ky Kod Identifikues i Biznesit (BIC) nuk është i lidhur me IBAN {{ iban }}. + + + This value should be valid JSON. + Kjo vlerë duhet të jetë JSON i vlefshëm. + + + This collection should contain only unique elements. + Ky koleksion duhet të përmbajë vetëm elementë unikë. + + + This value should be positive. + Kjo vlerë duhet të jetë pozitive. + + + This value should be either positive or zero. + Kjo vlerë duhet të jetë pozitive ose zero. + + + This value should be negative. + Kjo vlerë duhet të jetë negative. + + + This value should be either negative or zero. + Kjo vlerë duhet të jetë negative ose zero. + + + This value is not a valid timezone. + Kjo vlerë nuk është një zonë e vlefshme kohore. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Ky fjalëkalim është zbuluar në një shkelje të të dhënave, nuk duhet të përdoret. Ju lutemi përdorni një fjalëkalim tjetër. + + + This value should be between {{ min }} and {{ max }}. + Kjo vlerë duhet të jetë ndërmjet {{ min }} dhe {{ max }}. + + + This value is not a valid hostname. + Kjo vlerë nuk është një emër i vlefshëm hosti. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + Numri i elementeve në këtë koleksion duhet të jetë një shumëfish i {{ compared_value }}. + + + This value should satisfy at least one of the following constraints: + Kjo vlerë duhet të plotësojë të paktën njërën nga kufizimet e mëposhtme: + + + Each element of this collection should satisfy its own set of constraints. + Secili element i këtij koleksioni duhet të përmbushë kufizimet e veta. + + + This value is not a valid International Securities Identification Number (ISIN). + Kjo vlerë nuk është një numër i vlefshëm identifikues ndërkombëtar i sigurisë (ISIN). + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.sr_Cyrl.xlf b/src/vendor/symfony/validator/Resources/translations/validators.sr_Cyrl.xlf new file mode 100644 index 0000000..03ef713 --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.sr_Cyrl.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + Вредност треба да буде нетачна. + + + This value should be true. + Вредност треба да буде тачна. + + + This value should be of type {{ type }}. + Вредност треба да буде типа {{ type }}. + + + This value should be blank. + Вредност треба да буде празна. + + + The value you selected is not a valid choice. + Вредност треба да буде једна од понуђених. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Изаберите бар {{ limit }} могућност.|Изаберите бар {{ limit }} могућности.|Изаберите бар {{ limit }} могућности. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Изаберите највише {{ limit }} могућност.|Изаберите највише {{ limit }} могућности.|Изаберите највише {{ limit }} могућности. + + + One or more of the given values is invalid. + Једна или више вредности је невалидна. + + + This field was not expected. + Ово поље није било очекивано. + + + This field is missing. + Ово поље недостаје. + + + This value is not a valid date. + Вредност није валидан датум. + + + This value is not a valid datetime. + Вредност није валидан датум-време. + + + This value is not a valid email address. + Вредност није валидна адреса електронске поште. + + + The file could not be found. + Датотека не може бити пронађена. + + + The file is not readable. + Датотека није читљива. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + Датотека је превелика ({{ size }} {{ suffix }}). Највећа дозвољена величина је {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + Миме тип датотеке није валидан ({{ type }}). Дозвољени миме типови су {{ types }}. + + + This value should be {{ limit }} or less. + Вредност треба да буде {{ limit }} или мање. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Вредност је предугачка. Треба да има {{ limit }} карактер или мање.|Вредност је предугачка. Треба да има {{ limit }} карактера или мање.|Вредност је предугачка. Треба да има {{ limit }} карактера или мање. + + + This value should be {{ limit }} or more. + Вредност треба да буде {{ limit }} или више. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Вредност је прекратка. Треба да има {{ limit }} карактер или више.|Вредност је прекратка. Треба да има {{ limit }} карактера или више.|Вредност је прекратка. Треба да има {{ limit }} карактера или више. + + + This value should not be blank. + Вредност не треба да буде празна. + + + This value should not be null. + Вредност не треба да буде null. + + + This value should be null. + Вредност треба да буде null. + + + This value is not valid. + Вредност није валидна. + + + This value is not a valid time. + Вредност није валидно време. + + + This value is not a valid URL. + Вредност није валидан URL. + + + The two values should be equal. + Обе вредности треба да буду једнаке. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + Датотека је превелика. Највећа дозвољена величина је {{ limit }} {{ suffix }}. + + + The file is too large. + Датотека је превелика. + + + The file could not be uploaded. + Датотека не може бити отпремљена. + + + This value should be a valid number. + Вредност треба да буде валидан број. + + + This file is not a valid image. + Ова датотека није валидна слика. + + + This is not a valid IP address. + Ово није валидна ИП адреса. + + + This value is not a valid language. + Вредност није валидан језик. + + + This value is not a valid locale. + Вредност није валидан локал. + + + This value is not a valid country. + Вредност није валидна земља. + + + This value is already used. + Вредност је већ искоришћена. + + + The size of the image could not be detected. + Величина слике не може бити одређена. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + Ширина слике је превелика ({{ width }}px). Најећа дозвољена ширина је {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + Ширина слике је премала ({{ width }}px). Најмања дозвољена ширина је {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + Висина слике је превелика ({{ height }}px). Најећа дозвољена висина је {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + Висина слике је премала ({{ height }}px). Најмања дозвољена висина је {{ min_height }}px. + + + This value should be the user's current password. + Вредност треба да буде тренутна корисничка лозинка. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Вредност треба да има тачно {{ limit }} карактер.|Вредност треба да има тачно {{ limit }} карактера.|Вредност треба да има тачно {{ limit }} карактера. + + + The file was only partially uploaded. + Датотека је само парцијално отпремљена. + + + No file was uploaded. + Датотека није отпремљена. + + + No temporary folder was configured in php.ini. + Привремени директоријум није конфигурисан у php.ini. + + + Cannot write temporary file to disk. + Немогуће писање привремене датотеке на диск. + + + A PHP extension caused the upload to fail. + PHP екстензија је проузроковала неуспех отпремања датотеке. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Ова колекција треба да садржи {{ limit }} или више елемената.|Ова колекција треба да садржи {{ limit }} или више елемената.|Ова колекција треба да садржи {{ limit }} или више елемената. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Ова колекција треба да садржи {{ limit }} или мање елемената.|Ова колекција треба да садржи {{ limit }} или мање елемената.|Ова колекција треба да садржи {{ limit }} или мање елемената. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Ова колекција треба да садржи тачно {{ limit }} елемент.|Ова колекција треба да садржи тачно {{ limit }} елемента.|Ова колекција треба да садржи тачно {{ limit }} елемената. + + + Invalid card number. + Невалидан број картице. + + + Unsupported card type or invalid card number. + Невалидан број картице или тип картице није подржан. + + + This is not a valid International Bank Account Number (IBAN). + Ово није валидан међународни број банковног рачуна (IBAN). + + + This value is not a valid ISBN-10. + Ово није валидан ISBN-10. + + + This value is not a valid ISBN-13. + Ово није валидан ISBN-13. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Ово није валидан ISBN-10 или ISBN-13. + + + This value is not a valid ISSN. + Ово није валидан ISSN. + + + This value is not a valid currency. + Ово није валидна валута. + + + This value should be equal to {{ compared_value }}. + Ова вредност треба да буде {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + Ова вредност треба да буде већа од {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + Ова вредност треба да буде већа или једнака {{ compared_value }}. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Ова вредност треба да буде идентична са {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Ова вредност треба да буде мања од {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + Ова вредност треба да буде мања или једнака {{ compared_value }}. + + + This value should not be equal to {{ compared_value }}. + Ова вредност не треба да буде једнака {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Ова вредност не треба да буде идентична са {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Размера ове слике је превелика ({{ ratio }}). Максимална дозвољена размера је {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + Размера ове слике је премала ({{ ratio }}). Минимална очекивана размера је {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + Слика је квадратна ({{ width }}x{{ height }}px). Квадратне слике нису дозвољене. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + Слика је оријентације пејзажа ({{ width }}x{{ height }}px). Пејзажна оријентација слика није дозвољена. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + Слика је оријантације портрета ({{ width }}x{{ height }}px). Портретна оријентација слика није дозвољена. + + + An empty file is not allowed. + Празна датотека није дозвољена. + + + The host could not be resolved. + Име хоста не може бити разрешено. + + + This value does not match the expected {{ charset }} charset. + Вредност се не поклапа са очекиваним {{ charset }} сетом карактера. + + + This is not a valid Business Identifier Code (BIC). + Ово није валидан међународни идентификацијски код банке (BIC). + + + Error + Грешка + + + This is not a valid UUID. + Ово није валидан универзални уникатни идентификатор (UUID). + + + This value should be a multiple of {{ compared_value }}. + Ова вредност би требало да буде дељива са {{ compared_value }}. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + BIC код није повезан са IBAN {{ iban }}. + + + This value should be valid JSON. + Ова вредност би требало да буде валидан JSON. + + + This collection should contain only unique elements. + Ова колекција би требала да садржи само јединствене елементе. + + + This value should be positive. + Ова вредност би требала бити позитивна. + + + This value should be either positive or zero. + Ова вредност би требала бити позитивна или нула. + + + This value should be negative. + Ова вредност би требала бити негативна. + + + This value should be either negative or zero. + Ова вредност би требала бити позитивна или нула. + + + This value is not a valid timezone. + Ова вредност није валидна временска зона. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Ова лозинка је компромитована приликом претходних напада, немојте је користити. Користите другу лозинку. + + + This value should be between {{ min }} and {{ max }}. + Ова вредност треба да буде између {{ min }} и {{ max }}. + + + This value is not a valid hostname. + Ова вредност није исправно име хоста. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + Број елемената у овој колекцији би требало да буде дељив са {{ compared_value }}. + + + This value should satisfy at least one of the following constraints: + Ова вредност би требало да задовољава најмање једно од наредних ограничења: + + + Each element of this collection should satisfy its own set of constraints. + Сваки елемент ове колекције би требало да задовољи сопствени скуп ограничења. + + + This value is not a valid International Securities Identification Number (ISIN). + Ова вредност није исправна међународна идентификациона ознака хартија од вредности (ISIN). + + + This value should be a valid expression. + Ова вредност треба да буде валидан израз. + + + This value is not a valid CSS color. + Ова вредност није исправна CSS боја. + + + This value is not a valid CIDR notation. + Ова вредност није исправна CIDR нотација. + + + The value of the netmask should be between {{ min }} and {{ max }}. + Вредност мрежне маске треба бити између {{ min }} и {{ max }}. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.sr_Latn.xlf b/src/vendor/symfony/validator/Resources/translations/validators.sr_Latn.xlf new file mode 100644 index 0000000..86453ad --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.sr_Latn.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + Vrednost bi trebalo da bude netačna. + + + This value should be true. + Vrednost bi trebalo da bude tačna. + + + This value should be of type {{ type }}. + Vrednost bi trebalo da bude tipa {{ type }}. + + + This value should be blank. + Vrednost bi trebalo da bude prazna. + + + The value you selected is not a valid choice. + Odabrana vrednost nije validan izbor. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Morate odabrati bar {{ limit }} mogućnost.|Morate odabrati bar {{ limit }} mogućnosti.|Morate odabrati bar {{ limit }} mogućnosti. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Morate odabrati najviše {{ limit }} mogućnost.|Morate odabrati najviše {{ limit }} mogućnosti.|Morate odabrati najviše {{ limit }} mogućnosti. + + + One or more of the given values is invalid. + Jedna ili više vrednosti nisu validne. + + + This field was not expected. + Ovo polje nije bilo očekivano. + + + This field is missing. + Ovo polje nedostaje. + + + This value is not a valid date. + Vrednost nije validan datum. + + + This value is not a valid datetime. + Vrednost nije validno vreme. + + + This value is not a valid email address. + Vrednost nije validna adresa elektronske pošte. + + + The file could not be found. + Datoteka ne može biti pronađena. + + + The file is not readable. + Datoteka nije čitljiva. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + Datoteka je prevelika ({{ size }} {{ suffix }}). Najveća dozvoljena veličina je {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + MIME tip datoteke nije validan ({{ type }}). Dozvoljeni MIME tipovi su {{ types }}. + + + This value should be {{ limit }} or less. + Vrednost bi trebalo da bude {{ limit }} ili manje. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Vrednost je predugačka. Trebalo bi da ima {{ limit }} karakter ili manje.|Vrednost je predugačka. Trebalo bi da ima {{ limit }} karaktera ili manje.|Vrednost je predugačka. Trebalo bi da ima {{ limit }} karaktera ili manje. + + + This value should be {{ limit }} or more. + Vrednost bi trebalo da bude {{ limit }} ili više. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Vrednost je prekratka. Trebalo bi da ima {{ limit }} karakter ili više.|Vrednost je prekratka. Trebalo bi da ima {{ limit }} karaktera ili više.|Vrednost je prekratka. Trebalo bi da ima {{ limit }} karaktera ili više. + + + This value should not be blank. + Vrednost ne bi trebalo da bude prazna. + + + This value should not be null. + Vrednost ne bi trebalo da bude prazna. + + + This value should be null. + Vrednost bi trebalo da bude prazna. + + + This value is not valid. + Vrednost nije validna. + + + This value is not a valid time. + Vrednost nije validno vreme. + + + This value is not a valid URL. + Vrednost nije validan URL. + + + The two values should be equal. + Obe vrednosti bi trebalo da budu jednake. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + Datoteka je prevelika. Najveća dozvoljena veličina je {{ limit }} {{ suffix }}. + + + The file is too large. + Datoteka je prevelika. + + + The file could not be uploaded. + Datoteka ne može biti otpremljena. + + + This value should be a valid number. + Vrednost bi trebalo da bude validan broj. + + + This file is not a valid image. + Ova datoteka nije validna slika. + + + This is not a valid IP address. + Ovo nije validna IP adresa. + + + This value is not a valid language. + Vrednost nije validan jezik. + + + This value is not a valid locale. + Vrednost nije validna međunarodna oznaka jezika. + + + This value is not a valid country. + Vrednost nije validna država. + + + This value is already used. + Vrednost je već iskorišćena. + + + The size of the image could not be detected. + Veličina slike ne može biti određena. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + Širina slike je prevelika ({{ width }} piksela). Najveća dozvoljena širina je {{ max_width }} piksela. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + Širina slike je premala ({{ width }} piksela). Najmanja dozvoljena širina je {{ min_width }} piksela. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + Visina slike je prevelika ({{ height }} piksela). Najveća dozvoljena visina je {{ max_height }} piksela. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + Visina slike je premala ({{ height }} piksela). Najmanja dozvoljena visina je {{ min_height }} piksela. + + + This value should be the user's current password. + Vrednost bi trebalo da bude trenutna korisnička lozinka. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Vrednost bi trebalo da ima tačno {{ limit }} karakter.|Vrednost bi trebalo da ima tačno {{ limit }} karaktera.|Vrednost bi trebalo da ima tačno {{ limit }} karaktera. + + + The file was only partially uploaded. + Datoteka je samo parcijalno otpremljena. + + + No file was uploaded. + Datoteka nije otpremljena. + + + No temporary folder was configured in php.ini. + Privremeni direktorijum nije konfigurisan u php.ini. + + + Cannot write temporary file to disk. + Nemoguće pisanje privremene datoteke na disk. + + + A PHP extension caused the upload to fail. + PHP ekstenzija je prouzrokovala neuspeh otpremanja datoteke. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Ova kolekcija bi trebalo da sadrži {{ limit }} ili više elemenata.|Ova kolekcija bi trebalo da sadrži {{ limit }} ili više elemenata.|Ova kolekcija bi trebalo da sadrži {{ limit }} ili više elemenata. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Ova kolekcija bi trebalo da sadrži {{ limit }} ili manje elemenata.|Ova kolekcija bi trebalo da sadrži {{ limit }} ili manje elemenata.|Ova kolekcija bi trebalo da sadrži {{ limit }} ili manje elemenata. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Ova kolekcija bi trebalo da sadrži tačno {{ limit }} element.|Ova kolekcija bi trebalo da sadrži tačno {{ limit }} elementa.|Ova kolekcija bi trebalo da sadrži tačno {{ limit }} elemenata. + + + Invalid card number. + Broj kartice nije validan. + + + Unsupported card type or invalid card number. + Tip kartice nije podržan ili broj kartice nije validan. + + + This is not a valid International Bank Account Number (IBAN). + Ovo nije validan međunarodni broj bankovnog računa (IBAN). + + + This value is not a valid ISBN-10. + Ovo nije validan ISBN-10. + + + This value is not a valid ISBN-13. + Ovo nije validan ISBN-13. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Ovo nije validan ISBN-10 ili ISBN-13. + + + This value is not a valid ISSN. + Ovo nije validan ISSN. + + + This value is not a valid currency. + Ovo nije validna valuta. + + + This value should be equal to {{ compared_value }}. + Ova vrednost bi trebalo da bude jednaka {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + Ova vrednost bi trebalo da bude veća od {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + Ova vrednost bi trebalo da bude veća ili jednaka {{ compared_value }}. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Ova vrednost bi trebalo da bude identična sa {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Ova vrednost bi trebalo da bude manja od {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + Ova vrednost bi trebalo da bude manja ili jednaka {{ compared_value }}. + + + This value should not be equal to {{ compared_value }}. + Ova vrednost ne bi trebalo da bude jednaka {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Ova vrednost ne bi trebalo da bude identična sa {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Razmera ove slike je prevelika ({{ ratio }}). Maksimalna dozvoljena razmera je {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + Razmera ove slike je premala ({{ ratio }}). Minimalna očekivana razmera je {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + Slika je kvadratna ({{ width }}x{{ height }} piksela). Kvadratne slike nisu dozvoljene. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + Slika je pejzažno orijentisana ({{ width }}x{{ height }} piksela). Pejzažna orijentisane slike nisu dozvoljene. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + Slika je portretno orijentisana ({{ width }}x{{ height }} piksela). Portretno orijentisane slike nisu dozvoljene. + + + An empty file is not allowed. + Prazna datoteka nije dozvoljena. + + + The host could not be resolved. + Ime hosta ne može biti razrešeno. + + + This value does not match the expected {{ charset }} charset. + Vrednost se ne poklapa sa očekivanim {{ charset }} setom karaktera. + + + This is not a valid Business Identifier Code (BIC). + Ovo nije validan BIC. + + + Error + Greška + + + This is not a valid UUID. + Ovo nije validan univerzalni unikatni identifikator (UUID). + + + This value should be a multiple of {{ compared_value }}. + Ova vrednost bi trebalo da bude deljiva sa {{ compared_value }}. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + BIC kod nije povezan sa IBAN {{ iban }}. + + + This value should be valid JSON. + Ova vrednost bi trebalo da bude validan JSON. + + + This collection should contain only unique elements. + Ova kolekcija bi trebala da sadrži samo jedinstvene elemente. + + + This value should be positive. + Ova vrednost bi trebala biti pozitivna. + + + This value should be either positive or zero. + Ova vrednost bi trebala biti pozitivna ili nula. + + + This value should be negative. + Ova vrednost bi trebala biti negativna. + + + This value should be either negative or zero. + Ova vrednost bi trebala biti negativna ili nula. + + + This value is not a valid timezone. + Ova vrednost nije validna vremenska zona. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Ova lozinka je kompromitovana prilikom prethodnih napada, nemojte je koristiti. Koristite drugu lozinku. + + + This value should be between {{ min }} and {{ max }}. + Ova vrednost treba da bude između {{ min }} i {{ max }}. + + + This value is not a valid hostname. + Ova vrednost nije ispravno ime poslužitelja (hostname). + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + Broj elemenata u ovoj kolekciji bi trebalo da bude deljiv sa {{ compared_value }}. + + + This value should satisfy at least one of the following constraints: + Ova vrednost bi trebalo da zadovoljava namjanje jedno od narednih ograničenja: + + + Each element of this collection should satisfy its own set of constraints. + Svaki element ove kolekcije bi trebalo da zadovolji sopstveni skup ograničenja. + + + This value is not a valid International Securities Identification Number (ISIN). + Ova vrednost nije ispravna međunarodna identifikaciona oznaka hartija od vrednosti (ISIN). + + + This value should be a valid expression. + Ova vrednost treba da bude validan izraz. + + + This value is not a valid CSS color. + Ova vrednost nije ispravna CSS boja. + + + This value is not a valid CIDR notation. + Ova vrednost nije ispravna CIDR notacija. + + + The value of the netmask should be between {{ min }} and {{ max }}. + Vrednost mrežne maske treba biti između {{ min }} i {{ max }}. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.sv.xlf b/src/vendor/symfony/validator/Resources/translations/validators.sv.xlf new file mode 100644 index 0000000..fca7bdc --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.sv.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + Värdet ska vara falskt. + + + This value should be true. + Värdet ska vara sant. + + + This value should be of type {{ type }}. + Värdet ska vara av typen {{ type }}. + + + This value should be blank. + Värdet ska vara tomt. + + + The value you selected is not a valid choice. + Värdet ska vara ett av de givna valen. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Du måste välja minst {{ limit }} val.|Du måste välja minst {{ limit }} val. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Du kan som mest välja {{ limit }} val.|Du kan som mest välja {{ limit }} val. + + + One or more of the given values is invalid. + Ett eller fler av de angivna värdena är ogiltigt. + + + This field was not expected. + Det här fältet förväntades inte. + + + This field is missing. + Det här fältet saknas. + + + This value is not a valid date. + Värdet är inte ett giltigt datum. + + + This value is not a valid datetime. + Värdet är inte ett giltigt datum med tid. + + + This value is not a valid email address. + Värdet är inte en giltig e-postadress. + + + The file could not be found. + Filen kunde inte hittas. + + + The file is not readable. + Filen är inte läsbar. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + Filen är för stor ({{ size }} {{ suffix }}). Största tillåtna storlek är {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + Filens MIME-typ ({{ type }}) är ogiltig. De tillåtna typerna är {{ types }}. + + + This value should be {{ limit }} or less. + Värdet ska vara {{ limit }} eller mindre. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Värdet är för långt. Det ska ha {{ limit }} tecken eller färre.|Värdet är för långt. Det ska ha {{ limit }} tecken eller färre. + + + This value should be {{ limit }} or more. + Värdet ska vara {{ limit }} eller mer. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Värdet är för kort. Det ska ha {{ limit }} tecken eller mer.|Värdet är för kort. Det ska ha {{ limit }} tecken eller mer. + + + This value should not be blank. + Värdet kan inte vara tomt. + + + This value should not be null. + Värdet kan inte vara null. + + + This value should be null. + Värdet ska vara null. + + + This value is not valid. + Värdet är inte giltigt. + + + This value is not a valid time. + Värdet är inte en giltig tid. + + + This value is not a valid URL. + Värdet är inte en giltig URL. + + + The two values should be equal. + De två värdena måste vara lika. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + Filen är för stor. Tillåten maximal storlek är {{ limit }} {{ suffix }}. + + + The file is too large. + Filen är för stor. + + + The file could not be uploaded. + Filen kunde inte laddas upp. + + + This value should be a valid number. + Värdet ska vara ett giltigt nummer. + + + This file is not a valid image. + Filen är ingen giltig bild. + + + This is not a valid IP address. + Det här är inte en giltig IP-adress. + + + This value is not a valid language. + Värdet är inte ett giltigt språk. + + + This value is not a valid locale. + Värdet är inte en giltig plats. + + + This value is not a valid country. + Värdet är inte ett giltigt land. + + + This value is already used. + Värdet används redan. + + + The size of the image could not be detected. + Det gick inte att fastställa storleken på bilden. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + Bildens bredd är för stor ({{ width }}px). Tillåten maximal bredd är {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + Bildens bredd är för liten ({{ width }}px). Minsta förväntade bredd är {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + Bildens höjd är för stor ({{ height }}px). Tillåten maximal bredd är {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + Bildens höjd är för liten ({{ height }}px). Minsta förväntade höjd är {{ min_height }}px. + + + This value should be the user's current password. + Värdet ska vara användarens nuvarande lösenord. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Värdet ska ha exakt {{ limit }} tecken.|Värdet ska ha exakt {{ limit }} tecken. + + + The file was only partially uploaded. + Filen laddades bara upp delvis. + + + No file was uploaded. + Ingen fil laddades upp. + + + No temporary folder was configured in php.ini. + Det finns ingen temporär mapp konfigurerad i php.ini. + + + Cannot write temporary file to disk. + Kan inte skriva temporär fil till disken. + + + A PHP extension caused the upload to fail. + En PHP extension gjorde att uppladdningen misslyckades. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Den här samlingen ska innehålla {{ limit }} element eller mer.|Den här samlingen ska innehålla {{ limit }} element eller mer. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Den här samlingen ska innehålla {{ limit }} element eller mindre.|Den här samlingen ska innehålla {{ limit }} element eller mindre. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Den här samlingen ska innehålla exakt {{ limit }} element.|Den här samlingen ska innehålla exakt {{ limit }} element. + + + Invalid card number. + Ogiltigt kortnummer. + + + Unsupported card type or invalid card number. + Okänd korttyp eller ogiltigt kortnummer. + + + This is not a valid International Bank Account Number (IBAN). + Det här är inte en giltig International Bank Account Number (IBANK). + + + This value is not a valid ISBN-10. + Värdet är inte en giltig ISBN-10. + + + This value is not a valid ISBN-13. + Värdet är inte en giltig ISBN-13. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Värdet är varken en giltig ISBN-10 eller en giltig ISBN-13. + + + This value is not a valid ISSN. + Värdet är inte en giltig ISSN. + + + This value is not a valid currency. + Värdet är inte en giltig valuta. + + + This value should be equal to {{ compared_value }}. + Värdet ska vara detsamma som {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + Värdet ska vara större än {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + Värdet ska bara större än eller detsamma som {{ compared_value }}. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Värdet ska vara identiskt till {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Värdet ska vara mindre än {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + Värdet ska vara mindre än eller detsamma som {{ compared_value }}. + + + This value should not be equal to {{ compared_value }}. + Värdet ska inte vara detsamma som {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Värdet ska inte vara identiskt med {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Förhållandet mellan bildens bredd och höjd är för stort ({{ ratio }}). Högsta tillåtna förhållande är {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + Förhållandet mellan bildens bredd och höjd är för litet ({{ ratio }}). Minsta tillåtna förhållande är {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + Bilden är kvadratisk ({{ width }}x{{ height }}px). Kvadratiska bilder tillåts inte. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + Bilden är landskapsorienterad ({{ width }}x{{ height }}px). Landskapsorienterade bilder tillåts inte. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + Bilden är porträttsorienterad ({{ width }}x{{ height }}px). Porträttsorienterade bilder tillåts inte. + + + An empty file is not allowed. + En tom fil är inte tillåten. + + + The host could not be resolved. + Värddatorn kunde inte hittas. + + + This value does not match the expected {{ charset }} charset. + Detta värde har inte den förväntade teckenkodningen {{ charset }}. + + + This is not a valid Business Identifier Code (BIC). + Detta är inte en giltig BIC-kod. + + + Error + Fel + + + This is not a valid UUID. + Detta är inte ett giltigt UUID. + + + This value should be a multiple of {{ compared_value }}. + Detta värde ska vara en multipel av {{ compared_value }}. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Denna BIC-koden är inte associerad med IBAN {{ iban }}. + + + This value should be valid JSON. + Detta värde ska vara giltig JSON. + + + This collection should contain only unique elements. + Denna samling bör endast innehålla unika element. + + + This value should be positive. + Detta värde bör vara positivt. + + + This value should be either positive or zero. + Detta värde bör vara antingen positivt eller noll. + + + This value should be negative. + Detta värde bör vara negativt. + + + This value should be either negative or zero. + Detta värde bör vara antingen negativt eller noll. + + + This value is not a valid timezone. + Detta värde är inte en giltig tidszon. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Det här lösenordet har läckt ut vid ett dataintrång, det får inte användas. Använd ett annat lösenord. + + + This value should be between {{ min }} and {{ max }}. + Detta värde bör ligga mellan {{ min }} och {{ max }}. + + + This value is not a valid hostname. + Värdet är inte ett giltigt servernamn. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + Antalet element i samlingen ska vara en multipel av {{ compared_value }}. + + + This value should satisfy at least one of the following constraints: + Det här värdet skall uppfylla minst ett av följande krav: + + + Each element of this collection should satisfy its own set of constraints. + Varje element i samlingen skall uppfylla sin egen uppsättning av krav. + + + This value is not a valid International Securities Identification Number (ISIN). + Det här värdet är inte ett giltigt "International Securities Identification Number" (ISIN). + + + This value should be a valid expression. + Det här värdet bör vara ett giltigt uttryck. + + + This value is not a valid CSS color. + Det här värdet är inte en giltig CSS-färg. + + + This value is not a valid CIDR notation. + Det här värdet är inte en giltig CIDR-notation. + + + The value of the netmask should be between {{ min }} and {{ max }}. + Värdet på nätmasken bör vara mellan {{ min }} och {{ max }}. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.th.xlf b/src/vendor/symfony/validator/Resources/translations/validators.th.xlf new file mode 100644 index 0000000..26affc5 --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.th.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + ค่านี้ควรเป็น false + + + This value should be true. + ค่านี้ควรเป็น true + + + This value should be of type {{ type }}. + ค่านี้ควรเป็น {{ type }} + + + This value should be blank. + ควรเป็นค่าว่าง + + + The value you selected is not a valid choice. + คุณเลือกค่าที่ไม่ตรงกับตัวเลือก + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + คุณต้องเลือกอย่างน้อย {{ limit }} ตัวเลือก + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + คุณเลือกได้มากที่สุด {{ limit }} ตัวเลือก + + + One or more of the given values is invalid. + มีบางค่าที่ส่งมาไม่ถูกต้อง + + + This field was not expected. + ไม่ควรมีฟิลด์นี้ + + + This field is missing. + ฟิลด์นี้หายไป + + + This value is not a valid date. + ค่าของวันที่ไม่ถูกต้อง + + + This value is not a valid datetime. + ค่าของวันที่และเวลาไม่ถูกต้อง + + + This value is not a valid email address. + ค่าของอีเมล์ไม่ถูกต้อง + + + The file could not be found. + ไม่พบไฟล์ + + + The file is not readable. + ไฟล์ไม่อยู่ในสถานะที่สามารถอ่านได้ + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + ไฟล์ใหญ่เกิน ({{ size }} {{ suffix }}) อนุญาตให้ใหญ่ที่สุดได้ไม่เกิน {{ limit }} {{ suffix }} + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + Mime type ของไฟล์ไม่ถูกต้อง ({{ type }}) Mime types ที่อนุญาตคือ {{ types }} + + + This value should be {{ limit }} or less. + ค่านี้ควรจะเป็น {{ limit }} หรือน้อยกว่านั้น + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + ค่านี้ยาวเกินไป ควรจะมีแค่ {{ limit }} ตัวอักษรหรือน้อยกว่านั้น + + + This value should be {{ limit }} or more. + ค่านี้ควรจะมี {{ limit }} หรือมากกว่านั้น + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + ค่านี้สั้นเกินไป ควรจะมี {{ limit }} ตัวอักษรหรือมากกว่านั้น + + + This value should not be blank. + ค่านี้ไม่ควรเป็นค่าว่าง + + + This value should not be null. + ค่านี้ไม่ควรเป็นค่า null + + + This value should be null. + ค่านี้ควรเป็นค่า null + + + This value is not valid. + ค่านี้ไม่ถูกต้อง + + + This value is not a valid time. + ค่าของเวลาไม่ถูกต้อง + + + This value is not a valid URL. + ค่าของ URL ไม่ถูกต้อง + + + The two values should be equal. + ค่าทั้งสองค่าควรจะเหมือนกัน + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + ขนาดไฟล์ใหญ่เกินไป อนุญาตให้ไฟล์ขนาดใหญ่ได้ไม่เกิน {{ limit }} {{ suffix }} + + + The file is too large. + ขนาดไฟล์ใหญ่เกินไป + + + The file could not be uploaded. + ไม่สามารถอัปโหลดไฟล์ได้ + + + This value should be a valid number. + ค่าของตัวเลขไม่ถูกต้อง + + + This file is not a valid image. + ไฟล์นี้ไม่ใช่ไฟล์รูปภาพ + + + This is not a valid IP address. + ค่าของ IP ไม่ถูกต้อง + + + This value is not a valid language. + ค่าของภาษาไม่ถูกต้อง + + + This value is not a valid locale. + ค่าของภูมิภาค (Locale) ไม่ถูกต้อง + + + This value is not a valid country. + ค่าของประเทศไม่ถูกต้อง + + + This value is already used. + ค่านี้ถูกใช้งานไปแล้ว + + + The size of the image could not be detected. + ไม่สามารถตรวจสอบขนาดไฟล์ของภาพได้ + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + ความกว้างของภาพเกินขนาด ({{ width }}px) อนุญาตให้กว้างได้มากที่สุด {{ max_width }}px + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + ความกว้างของภาพต่ำเกินไป ({{ width }}px) อนุญาตให้ความกว้างไม่ต่ำกว่า {{ min_width }}px + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + ความสูงของภาพเกินขนาด ({{ height }}px) อนุญาตให้สูงได้มากที่สุด {{ max_height }}px + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + ความสูงของภาพเล็กเกินไป ({{ height }}px) อนุญาตให้ความสูงไม่ควรต่ำกว่า {{ min_height }}px + + + This value should be the user's current password. + ค่านี้ควรจะเป็นรหัสผ่านปัจจุบันของผู้ใช้ + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + ค่านี้ควรจะมีความยาว {{ limit }} ตัวอักษร + + + The file was only partially uploaded. + อัปโหลดไฟล์ได้เพียงบางส่วนเท่านั้น + + + No file was uploaded. + ไม่มีไฟล์ใดถูกอัปโหลด + + + No temporary folder was configured in php.ini. + ไม่พบการตั้งค่าโฟลเดอร์ชั่วคราว (temporary folder) ใน php.ini + + + Cannot write temporary file to disk. + ไม่สามารถเขียนไฟล์ชั่วคราว (temporary file) ลงดิสก์ได้ + + + A PHP extension caused the upload to fail. + PHP extension ทำให้การอัปโหลดมีปัญหา + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + คอเล็กชั่นนี้ควรจะประกอบไปด้วยอย่างน้อย {{ limit }} สมาชิก + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + คอเล็กชั่นนี้ไม่ควรมีสมาชิกเกิน {{ limit }} + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + คอเล็กชั่นนี้ควรจะมี {{ limit }} สมาชิกเท่านั้น + + + Invalid card number. + หมายเลขบัตรไม่ถูกต้อง + + + Unsupported card type or invalid card number. + ไม่รู้จักประเภทของบัตร หรือหมายเลขบัตรไม่ถูกต้อง + + + This is not a valid International Bank Account Number (IBAN). + ค่านี้ไม่ใช่ International Bank Account Number (IBAN) ที่ถูกต้อง + + + This value is not a valid ISBN-10. + ค่านี้ไม่ใช่ ISBN-10 ที่ถูกต้อง + + + This value is not a valid ISBN-13. + ค่านี้ไม่ใช่ ISBN-13 ที่ถูกต้อง + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + ค่านี้ไม่ใช่ ISBN-10 หรือ ISBN-13 ที่ถูกต้อง + + + This value is not a valid ISSN. + ค่านี้ไม่ใช่ ISSN ที่ถูกต้อง + + + This value is not a valid currency. + ค่านี้ไม่ใช่สกุลเงินที่ถูกต้อง + + + This value should be equal to {{ compared_value }}. + ค่านี้ควรตรงกับ {{ compared_value }} + + + This value should be greater than {{ compared_value }}. + ค่านี้ควรจะมากกว่า {{ compared_value }} + + + This value should be greater than or equal to {{ compared_value }}. + ค่านี้ควรจะมากกว่าหรือตรงกับ {{ compared_value }} + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + ค่านี้ควรจะเหมือนกันกับ {{ compared_value_type }} {{ compared_value }} + + + This value should be less than {{ compared_value }}. + ค่านี้ควรจะน้อยกว่า {{ compared_value }} + + + This value should be less than or equal to {{ compared_value }}. + ค่านี้ควรจะน้อยกว่าหรือเท่ากับ {{ compared_value }} + + + This value should not be equal to {{ compared_value }}. + ค่านี้ไม่ควรเท่ากันกับ {{ compared_value }} + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + ค่านี้ไม่ควรเหมือนกันกับ {{ compared_value_type }} {{ compared_value }} + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + สัดส่วนของภาพใหญ่เกิน ({{ ratio }}) สัดส่วนใหญ่ที่สุดที่ใช้ได้คือ {{ max_ratio }} + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + สัดส่วนของภาพเล็กเกิน ({{ ratio }}) สัดส่วนเล็กที่สุดที่ใช้ได้คือ {{ min_ratio }} + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + รูปภาพเป็นจุตรัส ({{ width }}x{{ height }}px) ไม่อนุญาตภาพที่เป็นสี่เหลี่ยมจตุรัส + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + ภาพนี้เป็นแนวนอน ({{ width }}x{{ height }}px) ไม่อนุญาตภาพที่เป็นแนวนอน + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + ภาพนี้เป็นแนวตั้ง ({{ width }}x{{ height }}px) ไม่อนุญาตภาพที่เป็นแนวตั้ง + + + An empty file is not allowed. + ไม่อนุญาตให้ใช้ไฟล์ว่าง + + + The host could not be resolved. + ไม่สามารถแก้ไขชื่อโฮสต์ + + + This value does not match the expected {{ charset }} charset. + ค่านี้ไม่ตรงกับการเข้ารหัส {{ charset }} + + + This is not a valid Business Identifier Code (BIC). + นี่ไม่ถูกต้องตามรหัสสำหรับระบุธุรกิจนี้ (BIC) + + + Error + เกิดข้อผิดพลาด + + + This is not a valid UUID. + นี่ไม่ใช่ UUID ที่ถูกต้อง + + + This value should be a multiple of {{ compared_value }}. + ค่านี้ควรเป็น {{ compared_value }} หลายตัว + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + รหัสสำหรับระบุธุรกิจนี้ (BIC) ไม่เกี่ยวข้องกับ IBAN {{ iban }} + + + This value should be valid JSON. + ค่านี้ควรอยู่ในรูปแบบ JSON ที่ถูกต้อง + + + This collection should contain only unique elements. + คอเล็กชั่นนี้ควรมีเฉพาะสมาชิกที่ไม่ซ้ำกันเท่านั้น + + + This value should be positive. + ค่านี้ควรเป็นค่าบวก + + + This value should be either positive or zero. + ค่านี้ควรเป็นค่าบวกหรือค่าศูนย์ + + + This value should be negative. + ค่านี้ควรเป็นค่าลบ + + + This value should be either negative or zero. + ค่านี้ควรเป็นค่าลบหรือค่าศูนย์ + + + This value is not a valid timezone. + ค่าเขตเวลาไม่ถูกต้อง + + + This password has been leaked in a data breach, it must not be used. Please use another password. + รหัสผ่านนี้ได้เคยรั่วไหลออกไปโดยถูกการละเมิดข้อมูล ซึ่งไม่ควรนำกลับมาใช้ กรุณาใช้รหัสผ่านอื่น + + + This value should be between {{ min }} and {{ max }}. + ค่านี้ควรอยู่ระหว่าง {{ min }} ถึง {{ max }} + + + This value is not a valid hostname. + ค่าโฮสต์เนมไม่ถูกต้อง + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + จำนวนของสมาชิกในคอเล็กชั่นควรเป็นพหุคูณของ {{ compared_value }} + + + This value should satisfy at least one of the following constraints: + ค่านี้ควรเป็นไปตามข้อจำกัดอย่างน้อยหนึ่งข้อจากข้อจำกัดเหล่านี้: + + + Each element of this collection should satisfy its own set of constraints. + สมาชิกแต่ละตัวในคอเล็กชั่นควรเป็นไปตามข้อจำกัดของคอเล็กชั่นนั้นๆ + + + This value is not a valid International Securities Identification Number (ISIN). + ค่ารหัสหลักทรัพย์สากล (ISIN) ไม่ถูกต้อง + + + This value should be a valid expression. + ค่านี้ควรเป็นนิพจน์ที่ถูกต้อง + + + This value is not a valid CSS color. + ค่านี้ไม่ใช่สี CSS ที่ถูกต้อง + + + This value is not a valid CIDR notation. + ค่านี้ไม่ใช่รูปแบบ CIDR ที่ถูกต้อง + + + The value of the netmask should be between {{ min }} and {{ max }}. + ค่าของ netmask ควรมีค่าระหว่าง {{ min }} ถึง {{ max }} + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.tl.xlf b/src/vendor/symfony/validator/Resources/translations/validators.tl.xlf new file mode 100644 index 0000000..74d5ed5 --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.tl.xlf @@ -0,0 +1,399 @@ + + + + + + This value should be false. + Ang halaga nito ay dapat na huwad. + + + This value should be true. + Ang halaga nito ay dapat totoo. + + + This value should be of type {{ type }}. + Ang halaga nito ay dapat sa uri {{ type }}. + + + This value should be blank. + Ang halaga nito ay dapat walang laman. + + + The value you selected is not a valid choice. + Ang halaga ng iyong pinili ay hindi balidong pagpili. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Kailangan mong pumili ng pinakamababang {{ limit }} ng pagpilian.|Kailangan mong pumili ng pinakamababang {{ limit }} ng mga pagpipilian. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Kailangan mong pumili ng pinakamataas {{ limit }} ng pagpipilian.|Kailangan mong pumili ng pinakamataas {{ limit }} ng mga pagpipilian. + + + One or more of the given values is invalid. + Isa o higit pang mga halaga na binigay ay hindi balido. + + + This field was not expected. + Ang larangang ito ay hindi inaasahan. + + + This field is missing. + Ang patlang na ito ay nawawala. + + + This value is not a valid date. + Ang halagang ito ay hindi balidong petsa. + + + This value is not a valid datetime. + Ang halagang ito ay hindi wastong petsa/oras. + + + This value is not a valid email address. + Ang halagang ito ay hindi balidong address ng email. + + + The file could not be found. + Ang file na ito ay hindi makita. + + + The file is not readable. + Ang file na ito ay hindi mabasa. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + Ang file na ito ay masyadong malaki ({{ size }} {{ suffix }}). Ang pinakamalaking sukat {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + Ang uri ng file ng mime ay hindi balido ({{ type }}). Ang mga pinapayagang uri ng mime ay ang {{ types }}. + + + This value should be {{ limit }} or less. + Ang halaga nito ay dapat na {{ limit }} or maliit pa. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Ang halaga nito ay masyadong mahaba. Ito ay dapat na {{ limit }} karakter o maliit pa.|Ang halaga nito ay masyadong mahaba. Ito ay dapat na {{ limit }} mga karakter o maliit pa. + + + This value should be {{ limit }} or more. + Ang halaga nito ay dapat na {{ limit }} o mas marami pa. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Ang halaga nito ay masyadong maliit. Ito ay dapat na {{ limit }} karakter o marami pa.|Ang halaga nito ay masyadong maliit. Ito ay dapat na {{ limit }} mga karakter o marami pa. + + + This value should not be blank. + Ang halaga na ito ay dapat na may laman. + + + This value should not be null. + Meron dapt itong halaga. + + + This value should be null. + Wala dapat itong halaga. + + + This value is not valid. + Hindi balido ang halagang ito. + + + This value is not a valid time. + Ang halagang ito ay hindi wastong oras. + + + This value is not a valid URL. + Hindi ito isang balidong URL. + + + The two values should be equal. + Ang dalwang halaga ay dapat magkapareha. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + Ang file ay masyadong malaki. Ang pinapayagan halaga lamang ay {{ limit}} {{ suffix }}. + + + The file is too large. + Ang file na ito ay masyadong malaki. + + + The file could not be uploaded. + Ang file na ito ay hindi ma-upload. + + + This value should be a valid number. + Ang halaga nito ay dapat na wastong numero. + + + This file is not a valid image. + Ang file na ito ay hindi wastong imahe. + + + This is not a valid IP address. + Ito ay hindi wastong IP address. + + + This value is not a valid language. + Ang halaga na ito ay hindi balidong wika. + + + This value is not a valid locale. + Ito ay isang hindi wastong locale na halaga. + + + This value is not a valid country. + ng halaga na ito ay hindi wastong bansa. + + + This value is already used. + Ang halaga na ito ay ginamit na. + + + The size of the image could not be detected. + Ang sukat ng imahe ay hindi madetect. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + Ang lapad ng imahe ay masyadong malaki ({{ width }}px). Ang pinapayagang lapay ay {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + Ang lapad ng imahe ay masyadong maliit ({{ width }}px). Ang pinakamaliit na pinapayagang lapad ay {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + Ang haba ng imahe ay masyadong mataas ({{ height }}px). Ang pinakmataas na haba ay {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + Ang haba ng imahe ay masyadong maliit ({{ height }}px). Ang inaasahang haba ay {{ min_height }}px. + + + This value should be the user's current password. + Ang halagang ito ay dapat na password ng kasalukuyang gumagamit. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Ang halagang ito ay dapat na eksakto sa {{ limit}} karakter.|Ang halagang ito ay dapat na eksakto sa {{ limit }} mga karakter. + + + The file was only partially uploaded. + Ang file na ito ay kahalating na upload lamang. + + + No file was uploaded. + Walang na upload na file. + + + No temporary folder was configured in php.ini. + Walang temporaryong folder ang naayos sa php.ini. + + + Cannot write temporary file to disk. + Temporaryong hindi makasulat ng file sa disk. + + + A PHP extension caused the upload to fail. + Ang dahilan ng pagkabigo ng pagupload ng files ay isang extension ng PHP. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Ang koleksyon na ito ay dapat magkaroon ng {{ limit }} elemento o marami pa.|Ang koleksyon na ito ay dapat magkaroon ng {{ limit }} mga elemento o marami pa. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Ang koleksyon na ito ay dapat magkaroon ng {{ limit }} elemento o maliit pa.|Ang koleksyon na ito ay dapat magkaroon ng {{ limit }} mga elemento o maliit pa. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Ang koleksyong ito ay magkaroon ng eksaktong {{ limit }} elemento.|Ang koleksyong ito ay magkaroon ng eksaktong {{ limit }} mga elemento. + + + Invalid card number. + Hindi wastong numero ng kard. + + + Unsupported card type or invalid card number. + Hindi supportadong uri ng kard o hindi wastong numero ng kard. + + + This is not a valid International Bank Account Number (IBAN). + Ito ay hindi isang balidong International Bank Account Number (IBAN). + + + This value is not a valid ISBN-10. + Ang halagang ito ay hindi balidong SBN-10. + + + This value is not a valid ISBN-13. + Ang halagang ito ay hindi balidong ISBN-13. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Ang halagang ito ay pwdeng isang balidong ISBN-10 o isang balidong ISBN-13. + + + This value is not a valid ISSN. + Ang halangang ito ay hindi isang balidong ISSN. + + + This value is not a valid currency. + Ang halagang ito ay hindi balidong pera. + + + This value should be equal to {{ compared_value }}. + Ito ay hindi dapat magkapareho sa {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + Ang halagang ito ay dapat tataas sa {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + Ang halagang ito ay dapat mas mataas o magkapareha sa {{ compared_value }}. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Ang halagang ito ay dapat kapareha ng {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Ang halagang ito ay dapat mas maliit sa {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + Ang halagang ito ay dapat mas maliit o magkapareha sa {{ compared_value }}. + + + This value should not be equal to {{ compared_value }}. + Ang halagang ito ay hindi dapat magkapareha sa {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Ang halagang ito ay hindi dapat magkapareha sa {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Ang ratio ng imahe ay masyadong malaki ({{ ratio }}). Ang pinakamalaking ratio ay {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + Ang ratio ng imahe ay masyadong maliit ({{ ratio }}). Ang pinakamaliit na ratio ay {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + Ang imahe ay kwadrado ({{ width }}x{{ height }}px). Ang mga kwadradong imahe ay hindi pwede. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + Ang orientasyon ng imahe ay nakalandscape ({{ width }}x{{ height }}px). Ang mga imaheng nakalandscape ang orientasyon ay hindi pwede. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + Ang orientasyon ng imahe ay nakaportrait ({{ width }}x{{ height }}px). Ang mga imaheng nakaportrait ang orientasyon ay hindi pwede. + + + An empty file is not allowed. + Ang file na walang laman ay hindi pwede. + + + The host could not be resolved. + Hindi maresolba ang host. + + + This value does not match the expected {{ charset }} charset. + Ang halaga ay hindi kapareha sa inaasahang {{ charset }} set ng karater. + + + This is not a valid Business Identifier Code (BIC). + Ito ay hindi isang balidong Business Identifier Code (BIC). + + + Error + Error + + + This is not a valid UUID. + Ito ay hindi wastong UUID. + + + This value should be a multiple of {{ compared_value }}. + Ang halagang ito ay dapat multiple ng {{ compared_value }}. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Ang Business Identifier Code (BIC) na ito ay walang kaugnayan sa IBAN {{ iban }}. + + + This value should be valid JSON. + Ang halagang ito ay dapat naka wastong JSON. + + + This collection should contain only unique elements. + Ang mga elemento ng koleksyong ito ay dapat magkakaiba. + + + This value should be positive. + Ang halagang ito ay dapat positibo. + + + This value should be either positive or zero. + Ang halagang ito ay dapat positibo o zero. + + + This value should be negative. + Ang halagang ito ay dapat negatibo. + + + This value should be either negative or zero. + Ang halagang ito ay dapat negatibo o zero. + + + This value is not a valid timezone. + Ang halagang ito ay hindi wastong timezone. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Naikalat ang password na ito sa isang data breach at hindi na dapat gamitin. Mangyaring gumamit ng ibang pang password. + + + This value should be between {{ min }} and {{ max }}. + Ang halagang ito ay dapat nasa pagitan ng {{ min }} at {{ max }}. + + + This value is not a valid hostname. + Ang halagang ito ay hindi wastong hostname. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + Ang bilang ng mga elemento sa koleksyon na ito ay dapat multiple ng {{ compared_value }}. + + + This value should satisfy at least one of the following constraints: + Ang halagang ito ay dapat masunod ang kahit na isang sumusunod na batayan. + + + Each element of this collection should satisfy its own set of constraints. + Ang bawat elemento sa koleksyon na ito ay dapat masunod ang nararapat na batayan. + + + This value is not a valid International Securities Identification Number (ISIN). + Ang halagang ito ay hindi wastong International Securities Identification Number (ISIN). + + + This value should be a valid expression. + Ang halagang ito ay dapat wastong ekspresyon. + + + This value is not a valid CSS color. + Ang halagang ito ay hindi wastong kulay ng CSS. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.tr.xlf b/src/vendor/symfony/validator/Resources/translations/validators.tr.xlf new file mode 100644 index 0000000..715137d --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.tr.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + Bu değer olumsuz olmalıdır. + + + This value should be true. + Bu değer olumlu olmalıdır. + + + This value should be of type {{ type }}. + Bu değerin tipi {{ type }} olmalıdır. + + + This value should be blank. + Bu değer boş olmalıdır. + + + The value you selected is not a valid choice. + Seçtiğiniz değer geçerli bir seçenek değil. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + En az {{ limit }} seçenek belirtmelisiniz. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + En çok {{ limit }} seçenek belirtmelisiniz. + + + One or more of the given values is invalid. + Verilen değerlerden bir veya daha fazlası geçersiz. + + + This field was not expected. + Bu alan beklenen olmadı. + + + This field is missing. + Bu alan, eksik + + + This value is not a valid date. + Bu değer doğru bir tarih biçimi değildir. + + + This value is not a valid datetime. + Bu değer doğru bir tarihsaat biçimi değildir. + + + This value is not a valid email address. + Bu değer doğru bir e-mail adresi değildir. + + + The file could not be found. + Dosya bulunamadı. + + + The file is not readable. + Dosya okunabilir değil. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + Dosya çok büyük ({{ size }} {{ suffix }}). İzin verilen en büyük dosya boyutu {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + Dosyanın mime tipi geçersiz ({{ type }}). İzin verilen mime tipleri {{ types }}. + + + This value should be {{ limit }} or less. + Bu değer {{ limit }} ve altında olmalıdır. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Bu değer çok uzun. {{ limit }} karakter veya daha az olmalıdır. + + + This value should be {{ limit }} or more. + Bu değer {{ limit }} veya daha fazla olmalıdır. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Bu değer çok kısa. {{ limit }} karakter veya daha fazla olmalıdır. + + + This value should not be blank. + Bu değer boş bırakılmamalıdır. + + + This value should not be null. + Bu değer boş bırakılmamalıdır. + + + This value should be null. + Bu değer boş bırakılmalıdır. + + + This value is not valid. + Bu değer geçerli değil. + + + This value is not a valid time. + Bu değer doğru bir saat değil. + + + This value is not a valid URL. + Bu değer doğru bir URL değil. + + + The two values should be equal. + İki değer eşit olmalıdır. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + Dosya çok büyük. İzin verilen en büyük dosya boyutu {{ limit }} {{ suffix }}. + + + The file is too large. + Dosya çok büyük. + + + The file could not be uploaded. + Dosya yüklenemiyor. + + + This value should be a valid number. + Bu değer geçerli bir rakam olmalıdır. + + + This file is not a valid image. + Bu dosya geçerli bir resim değildir. + + + This is not a valid IP address. + Bu geçerli bir IP adresi değildir. + + + This value is not a valid language. + Bu değer geçerli bir lisan değil. + + + This value is not a valid locale. + Bu değer geçerli bir yer değildir. + + + This value is not a valid country. + Bu değer geçerli bir ülke değildir. + + + This value is already used. + Bu değer şu anda kullanımda. + + + The size of the image could not be detected. + Resmin boyutu saptanamıyor. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + Resmin genişliği çok büyük ({{ width }}px). İzin verilen en büyük genişlik {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + Resmin genişliği çok küçük ({{ width }}px). En az {{ min_width }}px olmalıdır. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + Resmin yüksekliği çok büyük ({{ height }}px). İzin verilen en büyük yükseklik {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + Resmin yüksekliği çok küçük ({{ height }}px). En az {{ min_height }}px olmalıdır. + + + This value should be the user's current password. + Bu değer kullanıcının şu anki şifresi olmalıdır. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Bu değer tam olarak {{ limit }} karakter olmaldır. + + + The file was only partially uploaded. + Dosya sadece kısmen yüklendi. + + + No file was uploaded. + Hiçbir dosya yüklenmedi. + + + No temporary folder was configured in php.ini. + php.ini içerisinde geçici dizin tanımlanmadı. + + + Cannot write temporary file to disk. + Geçici dosya diske yazılamıyor. + + + A PHP extension caused the upload to fail. + Bir PHP eklentisi dosyanın yüklemesini başarısız kıldı. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Bu derlem {{ limit }} veya daha çok eleman içermelidir. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Bu derlem {{ limit }} veya daha az eleman içermelidir. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Bu derlem {{ limit }} eleman içermelidir. + + + Invalid card number. + Geçersiz kart numarası. + + + Unsupported card type or invalid card number. + Desteklenmeyen kart tipi veya geçersiz kart numarası. + + + This is not a valid International Bank Account Number (IBAN). + Bu geçerli bir Uluslararası Banka Hesap Numarası (IBAN) değildir. + + + This value is not a valid ISBN-10. + Bu değer geçerli bir ISBN-10 değildir. + + + This value is not a valid ISBN-13. + Bu değer geçerli bir ISBN-13 değildir. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Bu değer geçerli bir ISBN-10 veya ISBN-13 değildir. + + + This value is not a valid ISSN. + Bu değer geçerli bir ISSN değildir. + + + This value is not a valid currency. + Bu değer geçerli bir para birimi değil. + + + This value should be equal to {{ compared_value }}. + Bu değer {{ compared_value }} ile eşit olmalıdır. + + + This value should be greater than {{ compared_value }}. + Bu değer {{ compared_value }} değerinden büyük olmalıdır. + + + This value should be greater than or equal to {{ compared_value }}. + Bu değer {{ compared_value }} ile eşit veya büyük olmalıdır. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Bu değer {{ compared_value_type }} {{ compared_value }} ile aynı olmalıdır. + + + This value should be less than {{ compared_value }}. + Bu değer {{ compared_value }} değerinden düşük olmalıdır. + + + This value should be less than or equal to {{ compared_value }}. + .Bu değer {{ compared_value }} ile eşit veya düşük olmalıdır. + + + This value should not be equal to {{ compared_value }}. + Bu değer {{ compared_value }} ile eşit olmamalıdır. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Bu değer {{ compared_value_type }} {{ compared_value }} ile aynı olmamalıdır. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Resim oranı çok büyük ({{ ratio }}). İzin verilen maksimum oran: {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + Resim oranı çok ufak ({{ ratio }}). Beklenen minimum oran {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + Resim karesi ({{ width }}x{{ height }}px). Kare resimlerine izin verilmiyor. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + Resim manzara odaklı ({{ width }}x{{ height }}px). Manzara odaklı resimlere izin verilmiyor. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + Resim portre odaklı ({{ width }}x{{ height }}px). Portre odaklı resimlere izin verilmiyor. + + + An empty file is not allowed. + Boş bir dosyaya izin verilmiyor. + + + The host could not be resolved. + Sunucu çözülemedi. + + + This value does not match the expected {{ charset }} charset. + Bu değer beklenen {{ charset }} karakter kümesiyle eşleşmiyor. + + + This is not a valid Business Identifier Code (BIC). + Bu geçerli bir İşletme Tanımlayıcı Kodu (BIC) değildir. + + + Error + Hata + + + This is not a valid UUID. + Bu geçerli bir UUID değildir. + + + This value should be a multiple of {{ compared_value }}. + Bu değer {{ compare_value }} değerinin katlarından biri olmalıdır. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Bu İşletme Tanımlayıcı Kodu (BIC), IBAN {{ iban }} ile ilişkili değildir. + + + This value should be valid JSON. + Bu değer için geçerli olmalıdır JSON. + + + This collection should contain only unique elements. + Bu grup yalnızca benzersiz öğeler içermelidir. + + + This value should be positive. + Bu değer pozitif olmalı. + + + This value should be either positive or zero. + Bu değer pozitif veya sıfır olmalıdır. + + + This value should be negative. + Bu değer negatif olmalıdır. + + + This value should be either negative or zero. + Bu değer, negatif veya sıfır olmalıdır. + + + This value is not a valid timezone. + Bu değer, geçerli bir saat dilimi değil. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Bu parola, bir veri ihlali nedeniyle sızdırılmıştır ve kullanılmamalıdır. Lütfen başka bir şifre kullanın. + + + This value should be between {{ min }} and {{ max }}. + Bu değer arasında olmalıdır {{ min }} ve {{ max }}. + + + This value is not a valid hostname. + Bu değer, geçerli bir ana bilgisayar adı değil. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + Bu gruptaki öğe sayısı birden fazla olmalıdır {{ compared_value }}. + + + This value should satisfy at least one of the following constraints: + Bu değer aşağıdaki kısıtlamalardan birini karşılamalıdır: + + + Each element of this collection should satisfy its own set of constraints. + Bu gruptaki her öğe kendi kısıtlamalarını karşılamalıdır. + + + This value is not a valid International Securities Identification Number (ISIN). + Bu değer geçerli bir Uluslararası Menkul Kıymetler Kimlik Numarası değil (ISIN). + + + This value should be a valid expression. + Bu değer geçerli bir ifade olmalıdır. + + + This value is not a valid CSS color. + Bu değer geçerli bir CSS rengi değil. + + + This value is not a valid CIDR notation. + Bu değer geçerli bir CIDR yazımı değil. + + + The value of the netmask should be between {{ min }} and {{ max }}. + Netmask'in değeri {{ min }} ve {{ max }} arasında olmaldır. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.uk.xlf b/src/vendor/symfony/validator/Resources/translations/validators.uk.xlf new file mode 100644 index 0000000..c11f851 --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.uk.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + Значення повинно бути Ні. + + + This value should be true. + Значення повинно бути Так. + + + This value should be of type {{ type }}. + Тип значення повинен бути {{ type }}. + + + This value should be blank. + Значення повинно бути пустим. + + + The value you selected is not a valid choice. + Обране вами значення недопустиме. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Ви повинні обрати хоча б {{ limit }} варіант.|Ви повинні обрати хоча б {{ limit }} варіанти.|Ви повинні обрати хоча б {{ limit }} варіантів. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Ви повинні обрати не більше ніж {{ limit }} варіантів. + + + One or more of the given values is invalid. + Одне або кілька заданих значень є недопустимі. + + + This field was not expected. + Це поле не очікується. + + + This field is missing. + Це поле не вистачає. + + + This value is not a valid date. + Дане значення не є вірною датою. + + + This value is not a valid datetime. + Дане значення дати та часу недопустиме. + + + This value is not a valid email address. + Значення адреси электронної пошти недопустиме. + + + The file could not be found. + Файл не знайдено. + + + The file is not readable. + Файл не читається. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + Файл занадто великий ({{ size }} {{ suffix }}). Дозволений максимальний розмір {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + MIME-тип файлу недопустимий ({{ type }}). Допустимі MIME-типи файлів {{ types }}. + + + This value should be {{ limit }} or less. + Значення повинно бути {{ limit }} або менше. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Значення занадто довге. Повинно бути рівне {{ limit }} символу або менше.|Значення занадто довге. Повинно бути рівне {{ limit }} символам або менше.|Значення занадто довге. Повинно бути рівне {{ limit }} символам або менше. + + + This value should be {{ limit }} or more. + Значення повинно бути {{ limit }} або більше. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Значення занадто коротке. Повинно бути рівне {{ limit }} символу або більше.|Значення занадто коротке. Повинно бути рівне {{ limit }} символам або більше.|Значення занадто коротке. Повинно бути рівне {{ limit }} символам або більше. + + + This value should not be blank. + Значення не повинно бути пустим. + + + This value should not be null. + Значення не повинно бути null. + + + This value should be null. + Значення повинно бути null. + + + This value is not valid. + Значення недопустиме. + + + This value is not a valid time. + Значення часу недопустиме. + + + This value is not a valid URL. + Значення URL недопустиме. + + + The two values should be equal. + Обидва занчення повинні бути одинаковими. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + Файл занадто великий. Максимальний допустимий розмір {{ limit }} {{ suffix }}. + + + The file is too large. + Файл занадто великий. + + + The file could not be uploaded. + Файл не можливо завантажити. + + + This value should be a valid number. + Значення має бути допустимим числом. + + + This file is not a valid image. + Цей файл не є допустимим форматом зображення. + + + This is not a valid IP address. + Це некоректна IP адреса. + + + This value is not a valid language. + Це некоректна мова. + + + This value is not a valid locale. + Це некоректна локалізація. + + + This value is not a valid country. + Це некоректна країна. + + + This value is already used. + Це значення вже використовується. + + + The size of the image could not be detected. + Не вдалося визначити розмір зображення. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + Ширина зображення занадто велика ({{ width }}px). Максимально допустима ширина {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + Ширина зображення занадто мала ({{ width }}px). Мінімально допустима ширина {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + Висота зображення занадто велика ({{ height }}px). Максимально допустима висота {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + Висота зображення занадто мала ({{ height }}px). Мінімально допустима висота {{ min_height }}px. + + + This value should be the user's current password. + Значення має бути поточним паролем користувача. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Значення повиино бути рівним {{ limit }} символу.|Значення повиино бути рівним {{ limit }} символам.|Значення повиино бути рівним {{ limit }} символам. + + + The file was only partially uploaded. + Файл був завантажений лише частково. + + + No file was uploaded. + Файл не був завантажений. + + + No temporary folder was configured in php.ini. + Не налаштована тимчасова директорія в php.ini. + + + Cannot write temporary file to disk. + Неможливо записати тимчасовий файл на диск. + + + A PHP extension caused the upload to fail. + Розширення PHP викликало помилку при завантаженні. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Ця колекція повинна містити {{ limit }} елемент чи більше.|Ця колекція повинна містити {{ limit }} елемента чи більше.|Ця колекція повинна містити {{ limit }} елементів чи більше. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Ця колекція повинна містити {{ limit }} елемент чи менше.|Ця колекція повинна містити {{ limit }} елемента чи менше.|Ця колекція повинна містити {{ limit }} елементов чи менше. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Ця колекція повинна містити рівно {{ limit }} елемент.|Ця колекція повинна містити рівно {{ limit }} елемента.|Ця колекція повинна містити рівно {{ limit }} елементів. + + + Invalid card number. + Невірний номер карти. + + + Unsupported card type or invalid card number. + Непідтримуваний тип карти або невірний номер карти. + + + This is not a valid International Bank Account Number (IBAN). + Це не дійсний міжнародний номер банківського рахунку (IBAN). + + + This value is not a valid ISBN-10. + Значення не у форматі ISBN-10. + + + This value is not a valid ISBN-13. + Значення не у форматі ISBN-13. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Значення не відповідає форматам ISBN-10 та ISBN-13. + + + This value is not a valid ISSN. + Значення має невірний формат ISSN. + + + This value is not a valid currency. + Значення має невірний формат валюти. + + + This value should be equal to {{ compared_value }}. + Значення повинно дорівнювати {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + Значення має бути більше ніж {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + Значення має бути більше або дорівнювати {{ compared_value }}. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Значення має бути ідентичним {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Значення повинно бути менше ніж {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + Значення повинно бути менше або дорівнювати {{ compared_value }}. + + + This value should not be equal to {{ compared_value }}. + Значення не повинно дорівнювати {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Значення не повинно бути ідентичним {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Співвідношення сторін зображення занадто велике ({{ ratio }}). Максимальне співвідношення сторін {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + Співвідношення сторін зображення занадто мало ({{ ratio }}). Мінімальне співвідношення сторін {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + Зображення квадратне ({{ width }}x{{ height }}px). Квадратні зображення не дозволені. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + Зображення альбомної орієнтації ({{ width }}x{{ height }}px). Зображення альбомної орієнтації не дозволені. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + Зображення в портретній орієнтації ({{ width }}x{{ height }}px). Зображення в портретної орієнтації не дозволені. + + + An empty file is not allowed. + Порожні файли не дозволені. + + + The host could not be resolved. + Ім'я хоста не знайдено. + + + This value does not match the expected {{ charset }} charset. + Значення не збігається з очікуваним {{ charset }} кодуванням. + + + This is not a valid Business Identifier Code (BIC). + Це не дійсний банківський код (BIC). + + + Error + Помилка + + + This is not a valid UUID. + Це не валідне значення UUID. + + + This value should be a multiple of {{ compared_value }}. + Це значення повинне бути кратним {{ compared_value }}. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Банківський код (BIC) не пов’язаний із міжнародним номером банківського рахунку (IBAN) {{ iban }}. + + + This value should be valid JSON. + Значення має бути корректним JSON. + + + This collection should contain only unique elements. + Ця колекція повинна мати тільки унікальни значення. + + + This value should be positive. + Значення має бути позитивним. + + + This value should be either positive or zero. + Значення має бути позитивним або дорівнювати нулю. + + + This value should be negative. + Значення має бути негативним. + + + This value should be either negative or zero. + Значення має бути негативним або дорівнювати нулю. + + + This value is not a valid timezone. + Значення не є дійсним часовим поясом. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Цей пароль був скомпрометований в результаті витоку даних та не повинен використовуватися. Будь ласка, використовуйте інший пароль. + + + This value should be between {{ min }} and {{ max }}. + Значення має бути між {{ min }} та {{ max }}. + + + This value is not a valid hostname. + Значення не є дійсним іменем хоста. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + Кількість елементів у цій колекції повинна бути кратною {{ compared_value }}. + + + This value should satisfy at least one of the following constraints: + Значення повинно задовольняти хоча б одному з наступних обмежень: + + + Each element of this collection should satisfy its own set of constraints. + Кожен елемент цієї колекції повинен задовольняти власному набору обмежень. + + + This value is not a valid International Securities Identification Number (ISIN). + Це значення не є дійсним міжнародним ідентифікаційним номером цінних паперів (ISIN). + + + This value should be a valid expression. + Це значення має бути дійсним виразом. + + + This value is not a valid CSS color. + Це значення не є дійсним CSS кольором. + + + This value is not a valid CIDR notation. + Це значення не є дійсною CIDR нотаціею. + + + The value of the netmask should be between {{ min }} and {{ max }}. + Значення в мережевій масці має бути між {{ min }} та {{ max }}. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.ur.xlf b/src/vendor/symfony/validator/Resources/translations/validators.ur.xlf new file mode 100644 index 0000000..c2b1149 --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.ur.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + یہ ويليو غلط ہونی چاہیے + + + This value should be true. + یہ ويليو درست ہونی چاہیے + + + This value should be of type {{ type }}. + قسم کی ہونی چاہیے {{type}} يھ ويليو + + + This value should be blank. + یہ ويليو خالی ہونی چاہیے + + + The value you selected is not a valid choice. + آپ نے جو ويليو منتخب کی ہے وہ درست انتخاب نہیں ہے + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + کا انتخاب کرنا چاہیے {{limit}} کا انتخاب کرنا چاہیے ۔آّپ کو کم اذ کم {{limit}} آپ کو کم از کم + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + کا انتخاب کرنا چاہیے {{limit}} کا انتخاب کرنا چاہیے ۔آّپ کو ذيادھ سے ذيادھ {{limit}} آپ کو ذيادھ سے ذيادھ + + + One or more of the given values is invalid. + دی گئی ويليوذ میں سے ایک یا زیادھ ويليوذ غلط ہیں + + + This field was not expected. + اس فیلڈ کی توقع نہیں تھی + + + This field is missing. + یہ فیلڈ غائب ہے + + + This value is not a valid date. + یہ ويليو درست تاریخ نہیں ہے + + + This value is not a valid datetime. + یہ ويليو درست تاریخ وقت نہیں ہے + + + This value is not a valid email address. + یہ ويليو درست ای میل ایڈریس نہیں ہے + + + The file could not be found. + فائل نہیں مل سکی + + + The file is not readable. + فائل پڑھنے کے قابل نہیں ہے + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + {{ suffix }} {{ limit }} زیادہ سے زیادہ سائز کی اجازت ہے {{ suffix }}) ({{ size }} فائل بہت بڑی ہے + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + ہیں {{ types }} مائیم کی قسمیں ({{ type }}) فائل کی ماۂيم قسم غلط ہے + + + This value should be {{ limit }} or less. + یا اس سے کم ہونی چاہیے {{ limit }} یہ ويليو + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + حروف یا اس سے کم ہونے چاہئیں {{ limit }} حرف یا اس سے کم ہونا چاہیے۔|یہ ويليو بہت لمبی ہے۔ اس میں{{ limit }} یہ ويليو بہت لمبی ہے۔ اس میں + + + This value should be {{ limit }} or more. + یا اس سے زیادہ ہونی چاہیے {{ limit }} یہ ويليو + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + حروف یا اس سے زیادہ ہونے چاہئیں {{ limit }} حرف یا اس سے زیادہ ہونا چاہیے۔|یہ ويليو بہت چھوٹی ہے۔ اس میں{{ limit }} یہ ويليو بہت مختصر ہے۔ اس میں + + + This value should not be blank. + یہ ويليو خالی نہیں ہونی چاہیے + + + This value should not be null. + یہ ويليو خالی نہیں ہونی چاہیے + + + This value should be null. + یہ ويليو خالی ہونی چاہیے + + + This value is not valid. + یہ ويليو درست نہیں ہے + + + This value is not a valid time. + یہ ويليو درست وقت نہیں ہے + + + This value is not a valid URL. + نہیں ہے URL یہ ويليو درست + + + The two values should be equal. + دونوں ويليوذ برابر ہونی چاہئیں + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + {{ suffix }} {{ limit }} فائل بہت بڑی ہے۔ زیادہ سے زیادہ سائز کی اجازت ہے + + + The file is too large. + فائل بہت بڑی ہے + + + The file could not be uploaded. + فائل اپ لوڈ نہیں ہو سکی + + + This value should be a valid number. + یہ ويليو ایک درست نمبر ہونی چاہیے + + + This file is not a valid image. + یہ فائل درست تصویر نہیں ہے + + + This is not a valid IP address. + ایڈریس نہیں ہے IP یہ ایک درست + + + This value is not a valid language. + یہ ويليو درست زبان نہیں ہے + + + This value is not a valid locale. + یہ ويليو درست مقام نہیں ہے + + + This value is not a valid country. + یہ ويليو ایک درست ملک نہیں ہے + + + This value is already used. + یہ ويليو پہلے ہی استعمال ہو چکی ہے + + + The size of the image could not be detected. + تصویر کے سائز کا پتہ نہیں چل سکا + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + ہے {{ max_width }}px اجازت دی گئی زیادہ سے زیاد چوڑائی ({{ width }}px) تصویر کی چوڑائی بہت بڑی ہے + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + ہے {{ min_width }}px کم از کم چوڑائی متوقع({{ width }}px) تصویر کی چوڑائی بہت چھوٹی ہے + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + ہے {{ max_height }}px اجازت دی گئی زیادہ سے زیاد اونچائی ({{ height }}px) تصویر کی اونچائی بہت بڑی ہے + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + ہے {{ min_height }}px متوقع کم از کم اونچائی ({{ height }}px) تصویر کی اونچائی بہت چھوٹی ہے + + + This value should be the user's current password. + یہ ويليو صارف کا موجودہ پاس ورڈ ہونا چاہیے + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + حروف ہونے چاہئیں {{ limit }} حرف ہونا چاہیے۔|اس ويليو میں بالکل {{ limit }} اس ويليو میں بالکل + + + The file was only partially uploaded. + فائل صرف جزوی طور پر اپ لوڈ کی گئی تھی + + + No file was uploaded. + کوئی فائل اپ لوڈ نہیں کی گئی + + + No temporary folder was configured in php.ini. + میں کوئی عارضی فولڈر کنفیگر نہیں کیا گیا، یا کنفیگرڈ فولڈر موجود نہیں ہے php.ini + + + Cannot write temporary file to disk. + عارضی فائل کو ڈسک پر نہیں لکھا جا سکتا + + + A PHP extension caused the upload to fail. + پی ایچ پی کی توسیع کی وجہ سے اپ لوڈ ناکام ہو گیا + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + عناصر یا اس سے زیادہ ہونا چاہیے {{ limit } عنصر یا اس سے زیادہ ہونا چاہیے۔|اس مجموعہ میں {{ limit }} اس مجموعہ میں + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + عناصر یا اس سے کم ہونا چاہیے {{ limit } عنصر یا اس سے کم ہونا چاہیے۔|اس مجموعہ میں {{ limit }} اس مجموعہ میں + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + عنصر ہونا چاہیے {{ limit }} عنصر ہونا چاہیے۔|اس مجموعے میں بالکل {{ limit }} اس مجموعہ میں بالکل + + + Invalid card number. + غلط کارڈ نمبر + + + Unsupported card type or invalid card number. + غیر تعاون یافتہ کارڈ کی قسم یا غلط کارڈ نمبر + + + This is not a valid International Bank Account Number (IBAN). + (IBAN)یہ ایک درست بین الاقوامی بینک اکاؤنٹ نمبر نہیں ہے + + + This value is not a valid ISBN-10. + نہیں ہے ISBN-10 یھ ويليو درست۔ + + + This value is not a valid ISBN-13. + نہیں ہے ISBN-13 یھ ويليو درست۔ + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + ISBN-13 ے اور نہ ہی درست ISBN-10 یہ ويليو نہ تو درست + + + This value is not a valid ISSN. + نہیں ہے ISSNیھ ويليو درست۔ + + + This value is not a valid currency. + یہ ويليو درست کرنسی نہیں ہے + + + This value should be equal to {{ compared_value }}. + کے برابر ہونا چاہیے {{ compared_value }} یھ ويليو + + + This value should be greater than {{ compared_value }}. + سے بڈي ہوني چاہیے {{ compared_value }} یھ ويليو + + + This value should be greater than or equal to {{ compared_value }}. + سے بڈي یا برابر ہوني چاہیے {{ compared_value }} یھ ويليو + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + {{ compared_value }} {{ compared_value_type }} یہ ويليو ایک جیسی ہونی چاہیے۔ + + + This value should be less than {{ compared_value }}. + سے کم ہوني چاہیے {{ compared_value }} یھ ويليو + + + This value should be less than or equal to {{ compared_value }}. + سے کم یا برابر ہوني چاہیے {{ compared_value }} یھ ويليو + + + This value should not be equal to {{ compared_value }}. + کے برابر نھيں ہوني چاہیے {{ compared_value }} یھ ويليو + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + ایک جیسی نیيں ہونی چاہیے {{ compared_value }} {{ compared_value_type }} یہ ويليو + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + ہے {{ max_ratio }} اجازت شدہ زیادہ سے زیادہ تناسب ({{ ratio }}) تصویر کا تناسب بہت بڑا ہے + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + ہے{{ min_ratio }} ratio متوقع کم از کم ({{ ratio }}) بہت چھوٹا ہے ratio تصویر کا + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + مربع تصاویر کی اجازت نہیں ہے (px{{ height }}x{{ width }}) تصویر مربع ہے + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + زمین کی تزئین پر مبنی تصاویر کی اجازت نہیں ہے ({{ width }}x{{ height }}px) تصویر زمین کی تزئین پر مبنی ہے + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + پورٹریٹ پر مبنی تصاویر کی اجازت نہیں ہے ({{ width }}x{{ height }}px) تصویر پورٹریٹ پر مبنی ہے + + + An empty file is not allowed. + خالی فائل کی اجازت نہیں ہے + + + The host could not be resolved. + میزبان حل نہیں ہو سکا + + + This value does not match the expected {{ charset }} charset. + کے جيسي نہیں ہے charset {{ charset }} یہ ويليو متوقع + + + This is not a valid Business Identifier Code (BIC). + (BIC)یہ ایک درست کاروباری شناخت کنندہ کوڈ نہیں ہے + + + Error + خرابی + + + This is not a valid UUID. + نہیں ہے UUID یہ درست + + + This value should be a multiple of {{ compared_value }}. + کا ضرب ہوني چاہیے {{ compared_value }} یہ ويليو + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + سے وابستہ نہیں ہے IBAN {{ iban }} (BIC) یہ کاروباری شناختی کوڈ + + + This value should be valid JSON. + ہونی چاہیے JSON یہ ويليو درست + + + This collection should contain only unique elements. + یہ مجموعہ صرف منفرد عناصر پر مشتمل ہونا چاہیے + + + This value should be positive. + یہ ويليو مثبت ہونی چاہیے + + + This value should be either positive or zero. + یہ ويليو یا تو مثبت یا صفر ہونی چاہیے + + + This value should be negative. + یہ ويليو منفی ہونی چاہیے + + + This value should be either negative or zero. + یہ ويليو یا تو منفی یا صفر ہونی چاہیے + + + This value is not a valid timezone. + یہ ويليو درست ٹائم زون نہیں ہے + + + This password has been leaked in a data breach, it must not be used. Please use another password. + یہ پاس ورڈ ڈیٹا کی خلاف ورزی میں لیک ہو گیا ہے، اسے استعمال نہیں کرنا چاہیے۔ براۓ کرم دوسرا پاس ورڈ استعمال کریں + + + This value should be between {{ min }} and {{ max }}. + کے درمیان ہونی چاہیے {{ max }} اور {{ min }} یہ ويليو + + + This value is not a valid hostname. + نہیں ہے hostname یہ ويليو درست + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + کی ضرب ہونی چاہیے {{ compared_value }} اس مجموعہ میں عناصر کی تعداد + + + This value should satisfy at least one of the following constraints: + اس ويليو کو درج ذیل رکاوٹوں میں سے کم از کم ایک کو پورا کرنا چاہیے + + + Each element of this collection should satisfy its own set of constraints. + اس مجموعے کے ہر عنصر کو اپنی پابندیوں کے سیٹ کو پورا کرنا چاہیے + + + This value is not a valid International Securities Identification Number (ISIN). + نہیں ہے (ISIN) یہ ويليو درست بین الاقوامی سیکیورٹیز شناختی نمبر + + + This value should be a valid expression. + یہ ويليو ایک درست اظہار ہوني چاہیے + + + This value is not a valid CSS color. + رنگ نہیں ہے CSS یہ ويليو درست + + + This value is not a valid CIDR notation. + نوٹیشن نہیں ہے CIDR یہ ويليو ایک درست + + + The value of the netmask should be between {{ min }} and {{ max }}. + کے درمیان ہونی چاہیے {{ max }} اور {{ min }} نیٹ ماسک کی ويليو + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.uz.xlf b/src/vendor/symfony/validator/Resources/translations/validators.uz.xlf new file mode 100644 index 0000000..d1ecaf1 --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.uz.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + Qiymat noto'g'ri bo'lishi kerak. + + + This value should be true. + Qiymat to'g'ri bo'lishi kerak. + + + This value should be of type {{ type }}. + Qiymat turi {{ type }} bo'lishi kerak. + + + This value should be blank. + Qiymat bo'sh bo'lishi kerak. + + + The value you selected is not a valid choice. + Tanlangan qiymat to'g'ri emas. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Siz hech bo'lmaganda {{ limit }} ta qiymat tanlashingiz kerak.|Siz kamida {{ limit }} ta qiymat tanlashingiz kerak. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Siz {{ limit }} ta qiymatni tanlashingiz kerak.|Siz {{ limit }} dan ortiq qiymat tanlashingiz kerak. + + + One or more of the given values is invalid. + Belgilangan qiymatlarning bir yoki bir nechtasi noto'g'ri. + + + This field was not expected. + Ushbu maydon kutilmagan edi. + + + This field is missing. + Bu maydon majvud emas. + + + This value is not a valid date. + Ushbu sana noto'g'ri. + + + This value is not a valid datetime. + Sana va vaqt qiymati noto'g'ri. + + + This value is not a valid email address. + Elektron pochta manzili noto'g'ri. + + + The file could not be found. + Fayl topilmadi. + + + The file is not readable. + Faylni o'qib bo'lmadi. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + Fayl hajmi katta ({{ size }} {{ suffix }}). Maksimal ruxsat etilgan hajim {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + Faylning MIME turi noto'g'ri ({{ type }}). Ruxsat etilgan MIME turlar {{ types }}. + + + This value should be {{ limit }} or less. + Qiymat {{ limit }} ga teng yoki kam bo'lishi kerak. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Qiymat juda uzun. {{ limit }} ga teng yoki kam bo'lishi kerak.|Qiymat juda uzun. {{ limit }} yoki undan kam belgidan iborat bo'lishi kerak. + + + This value should be {{ limit }} or more. + Qiymat {{ limit }} yoki undan ortiq bo'lishi kerak. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Qiymat juda qisqa. {{ limit }} ta yoki undan ortiq belgidan iborat bo'lishi kerak.|Qiymat juda qisqa. {{ limit }} yoki undan ko'p belgidan iborat bo'lishi kerak + + + This value should not be blank. + Qiymatni bo'sh kirtish mumkin emas. + + + This value should not be null. + Qiymat null bo'lmasligi kerak. + + + This value should be null. + Qiymat null bo'lishi kerak. + + + This value is not valid. + Qiymat noto'g'ri. + + + This value is not a valid time. + Vaqt noto'g'ri. + + + This value is not a valid URL. + URL noto'g'ri + + + The two values should be equal. + Ikkala qiymat ham bir xil bo'lishi kerak. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + Fayl hajmi katta. Maksimal ruxsat berilgan hajim {{ limit }} {{ suffix }}. + + + The file is too large. + Fayl hajmi katta. + + + The file could not be uploaded. + Faylni yuklab bo'lmadi. + + + This value should be a valid number. + Qiymat raqam bo'lishi kerak. + + + This file is not a valid image. + Fayl yaroqli rasm formati emas. + + + This is not a valid IP address. + Ip manzil noto'g'ri. + + + This value is not a valid language. + Noto'g'ri til. + + + This value is not a valid locale. + Ushbu qiymat mahalliy qiymat emas. + + + This value is not a valid country. + Mamlakat qiymati noto'g'ri. + + + This value is already used. + Ushbu qiymat allaqachon ishlatilgan. + + + The size of the image could not be detected. + Rasm o'lchamini aniqlab bo'lmadi. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + Rasm kengligi juda katta ({{ width }}px). Maksimal ruxsat etilgan kenglik {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + Rasm kengligi juda kichkina ({{ width }}px). Minimal ruxsat etilgan kenglik {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + Rasm bo'yi juda katta ({{ height }}px). Maksimal ruxsat etilgan balandlik {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + Rasm bo'yi juda kichkina ({{ height }}px). Minimal ruxsat etilgan balandlik {{ min_height }}px. + + + This value should be the user's current password. + Qiymat joriy foydalanuvchi paroli bo'lishi kerak. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Qiymat {{ limit }} ta belgidan iborat bo'lishi kerak.|Qiymat {{ limit }} belgidan iborat bo'lishi kerak. + + + The file was only partially uploaded. + Fayl faqat qisman yuklangan. + + + No file was uploaded. + Fayl yuklanmagan. + + + No temporary folder was configured in php.ini. + php.ini da vaqtinchalik katalog sozlanmagan. + + + Cannot write temporary file to disk. + Diskka vaqtinchalik faylni yozib bo'lmadi. + + + A PHP extension caused the upload to fail. + PHP kengaytmasi yuklash paytida xatolik yuz berdi. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Ushbu to'plam {{ limit }} ta yoki undan ko'p narsalarni o'z ichiga olishi kerak.|Ushbu to'plam {{ limit }} yoki undan ortiq narsalarni o'z ichiga olishi kerak. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Ushbu to'plam {{ limit }} ta yoki undan kam narsalarni o'z ichiga olishi kerak.|Ushbu to'plamda {{ limit }} yoki undan kam element bo'lishi kerak. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Ushbu to'plam to'liq {{ limit }} narsani o'z ichiga olishi kerak.|Ushbu to'plamda to'liq {{ limit }} ta ma'lumotlar bo'lishi kerak. + + + Invalid card number. + Kata raqami noto'g'ri. + + + Unsupported card type or invalid card number. + Qo'llab-quvvatlanmaydigan karta turi yoki yaroqsiz karta raqami. + + + This is not a valid International Bank Account Number (IBAN). + Qiymat haqiqiy xalqaro hisob raqamining raqami (IBAN) emas. + + + This value is not a valid ISBN-10. + Qiymat to'g'ri ISBN-10 formatida emas. + + + This value is not a valid ISBN-13. + Qiymat to'g'ri ISBN-13 formatida emas. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Qiymat ISBN-10 va ISBN-13 formatlariga mos kelmaydi. + + + This value is not a valid ISSN. + Qiymat ISSN formatiga mos kelmaydi. + + + This value is not a valid currency. + Noto'g'ri valyuta formati. + + + This value should be equal to {{ compared_value }}. + Qiymat {{ compared_value }} ga teng bo'lishi shart. + + + This value should be greater than {{ compared_value }}. + Qiymat {{ compared_value }} dan katta bo'lishi shart. + + + This value should be greater than or equal to {{ compared_value }}. + Qiymat {{ compared_value }} dan katta yoki teng bo'lishi shart. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Значение должно быть идентичным {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Qiymat bir xil bo'lishi kerak {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + Qiymat {{ compared_value }} dan kichik yoki teng bo'lishi shart. + + + This value should not be equal to {{ compared_value }}. + Qiymat {{ compared_value }} ga teng bo'lmasligi kerak. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Qiymat bir xil bo'lishi kerak emas {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Rasmning tomonlari nisbati juda katta ({{ ratio }}). Maksimal tomonlar nisbati {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + Rasmning format nisbati juda kichik ({{ ratio }}). Minimal tomonlar nisbati {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + Rasm kvadrat shaklida ({{ width }}x{{ height }}px). Kvadrat shaklida tasvirlarga ruxsat berilmaydi. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + Landshaft tasvir ({{ width }}x{{ height }}px). Landshaft rasmlarga ruxsat berilmaydi. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + Portret rasm ({{ width }}x{{ height }}px). Portretlarga ruxsat berilmaydi. + + + An empty file is not allowed. + Bo'sh fayllarga ruxsat berilmagan. + + + The host could not be resolved. + Xost nomini nomiga ruxsat berilmagan. + + + This value does not match the expected {{ charset }} charset. + Qiymat kutilgan {{ charset }} kodlashiga mos kelmaydi. + + + This is not a valid Business Identifier Code (BIC). + Qiymat BIC formatida emas. + + + Error + Xatolik + + + This is not a valid UUID. + Qiymat UUID formatida emas. + + + This value should be a multiple of {{ compared_value }}. + Qiymat {{ compared_value }} ning ko'paytmasi bo'lishi kerak. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Ushbu BIC IBAN {{ iban }} bilan bog'liq emas.. + + + This value should be valid JSON. + Qiymat to'g'ri JSON bo'lishi kerak. + + + This collection should contain only unique elements. + Ushbu kolleksiyada takroriy elementlar bo'lmasligi kerak. + + + This value should be positive. + Qiymat musbat bo'lishi kerak. + + + This value should be either positive or zero. + Qiymat musbat yoki 0 ga teng bo'lishi kerak. + + + This value should be negative. + Qiymat manfiy bo'lishi kerak. + + + This value should be either negative or zero. + Qiymat manfiy yoki 0 ga teng bo'lishi kerak. + + + This value is not a valid timezone. + Qiymat to'g'ri vaqt zonasi emas. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Ushbu parol ma'lumotlarning tarqalishi tufayli buzilgan va uni ishlatmaslik kerak. Boshqa paroldan foydalaning. + + + This value should be between {{ min }} and {{ max }}. + Qiymat {{ min }} va {{ max }} oralig'ida bo'lishi shart. + + + This value is not a valid hostname. + Qiymat to'g'ri xost nomi emas. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + Ushbu to'plamdagi narsalar soni {{ compared_value }} dan ko'p bo'lishi kerak. + + + This value should satisfy at least one of the following constraints: + Qiymat quyidagi cheklovlardan kamida bittasiga javob berishi kerak: + + + Each element of this collection should satisfy its own set of constraints. + Ushbu to'plamdagi har bir narsa o'ziga xos cheklovlarni qondirishi kerak. + + + This value is not a valid International Securities Identification Number (ISIN). + Qiymat Qimmatli qog'ozlarning xalqaro identifikatsiya raqami (ISIN) ga mos emas. + + + This value should be a valid expression. + Ushbu qiymat to'g'ri ifoda bo'lishi kerak. + + + This value is not a valid CSS color. + Bu qiymat haqiqiy CSS rangi emas. + + + This value is not a valid CIDR notation. + Qiymat CIDR belgisiga mos kelmaydi. + + + The value of the netmask should be between {{ min }} and {{ max }}. + Tarmoq niqobining qiymati {{ min }} va {{ max }} oralig'ida bo'lishi kerak. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.vi.xlf b/src/vendor/symfony/validator/Resources/translations/validators.vi.xlf new file mode 100644 index 0000000..0020179 --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.vi.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + Giá trị này phải là sai. + + + This value should be true. + Giá trị này phải là đúng. + + + This value should be of type {{ type }}. + Giá trị này phải là kiểu {{ type }}. + + + This value should be blank. + Giá trị này phải rỗng. + + + The value you selected is not a valid choice. + Giá trị bạn vừa chọn không hợp lệ. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Bạn phải chọn ít nhất {{ limit }} lựa chọn.|Bạn phải chọn ít nhất {{ limit }} lựa chọn. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Bạn phải chọn nhiều nhất {{ limit }} lựa chọn.|Bạn phải chọn nhiều nhất {{ limit }} lựa chọn. + + + One or more of the given values is invalid. + Một hoặc nhiều giá trị được chọn không hợp lệ. + + + This field was not expected. + Lĩnh vực này không được dự kiến. + + + This field is missing. + Lĩnh vực này bị thiếu. + + + This value is not a valid date. + Giá trị không phải là ngày hợp lệ. + + + This value is not a valid datetime. + Giá trị không phải là ngày tháng hợp lệ. + + + This value is not a valid email address. + Giá trị này không phải là email hợp lệ. + + + The file could not be found. + Tập tin không tìm thấy. + + + The file is not readable. + Tập tin không thể đọc được. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + Tập tin quá lớn ({{ size }} {{ suffix }}). Kích thước tối đa cho phép {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + Kiểu mime của tập tin không hợp lệ ({{ type }}). Kiểu hợp lệ là {{ types }}. + + + This value should be {{ limit }} or less. + Giá trị phải bằng hoặc nhỏ hơn {{ limit }}. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Giá trị quá dài. Phải bằng hoặc ít hơn {{ limit }} kí tự.|Giá trị quá dài. Phải bằng hoặc ít hơn {{ limit }} kí tự. + + + This value should be {{ limit }} or more. + Giá trị phải lớn hơn hoặc bằng {{ limit }}. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Giá trị quá ngắn. Phải hơn hoặc bằng {{ limit }} kí tự.|Giá trị quá ngắn. Phải hơn hoặc bằng {{ limit }} kí tự. + + + This value should not be blank. + Giá trị không được phép để trống. + + + This value should not be null. + Giá trị không được phép rỗng. + + + This value should be null. + Giá trị phải rỗng. + + + This value is not valid. + Giá trị không hợp lệ. + + + This value is not a valid time. + Giá trị không phải là thời gian hợp lệ. + + + This value is not a valid URL. + Giá trị không phải là địa chỉ URL hợp lệ. + + + The two values should be equal. + Hai giá trị phải bằng nhau. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + Tập tin quá lớn. Kích thước tối đa cho phép là {{ limit }} {{ suffix }}. + + + The file is too large. + Tập tin quá lớn. + + + The file could not be uploaded. + Tập tin không thể tải lên. + + + This value should be a valid number. + Giá trị phải là con số. + + + This file is not a valid image. + Tập tin không phải là hình ảnh hợp lệ. + + + This is not a valid IP address. + Địa chỉ IP không hợp lệ. + + + This value is not a valid language. + Giá trị không phải là ngôn ngữ hợp lệ. + + + This value is not a valid locale. + Giá trị không phải là một bản địa địa phương hợp lệ. + + + This value is not a valid country. + Giá trị không phải là quốc gia hợp lệ. + + + This value is already used. + Giá trị đã được sử dụng. + + + The size of the image could not be detected. + Kích thước của hình ảnh không thể xác định. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + Chiều rộng của hình quá lớn ({{ width }}px). Chiều rộng tối đa phải là {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + Chiều rộng của hình quá thấp ({{ width }}px). Chiều rộng tối thiểu phải là {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + Chiều cao của hình quá cao ({{ height }}px). Chiều cao tối đa phải là {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + Chiều cao của hình quá thấp ({{ height }}px). Chiều cao tối thiểu phải là {{ min_height }}px. + + + This value should be the user's current password. + Giá trị này phải là mật khẩu hiện tại của người dùng. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Giá trị này phải có chính xác {{ limit }} kí tự.|Giá trị này phải có chính xác {{ limit }} kí tự. + + + The file was only partially uploaded. + Tập tin chỉ được tải lên một phần. + + + No file was uploaded. + Tập tin không được tải lên. + + + No temporary folder was configured in php.ini. + Thư mục tạm không được định nghĩa trong php.ini. + + + Cannot write temporary file to disk. + Không thể ghi tập tin tạm ra đĩa. + + + A PHP extension caused the upload to fail. + Một PHP extension đã phá hỏng quá trình tải lên của tập tin. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Danh sách phải chứa {{ limit }} thành phần hoặc nhiều hơn.|Danh sách phải chứa {{ limit }} thành phần hoặc nhiều hơn. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Danh sách phải chứa {{ limit }} thành phần hoặc ít hơn.|Danh sách phải chứa {{ limit }} thành phần hoặc ít hơn. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Danh sách phải chứa chính xác {{ limit }} thành phần.|Danh sách phải chứa chính xác {{ limit }} thành phần. + + + Invalid card number. + Số thẻ không hợp lệ. + + + Unsupported card type or invalid card number. + Thẻ không được hỗ trợ hoặc số thẻ không hợp lệ. + + + This is not a valid International Bank Account Number (IBAN). + Giá trị không phải là International Bank Account Number (IBAN) hợp lệ. + + + This value is not a valid ISBN-10. + Giá trị không phải là ISBN-10 hợp lệ. + + + This value is not a valid ISBN-13. + Giá trị không phải là ISBN-13 hợp lệ. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Giá trị không phải là ISBN-10 hoặc ISBN-13 hợp lệ. + + + This value is not a valid ISSN. + Giá trị không phải là ISSN hợp lệ. + + + This value is not a valid currency. + Giá trị không phải là đơn vị tiền tệ hợp lệ. + + + This value should be equal to {{ compared_value }}. + Giá trị phải bằng {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + Giá trị phải lớn hơn {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + Giá trị phải lớn hơn hoặc bằng {{ compared_value }}. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Giá trị phải giống {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Giá trị phải bé hơn {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + Giá trị phải nhỏ hơn hoặc bằng {{ compared_value }}. + + + This value should not be equal to {{ compared_value }}. + Giá trị không được phép bằng {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Giá trị không được phép giống như {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Tỷ lệ bức ảnh quá lớn ({{ ratio }}). Tỷ lệ tối đa cho phép là {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + Tỷ lệ bức ảnh quá nhỏ ({{ ratio }}). Tỷ lệ tối thiểu mong muốn là {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + Bức ảnh là hình vuông ({{ width }}x{{ height }}px). Ảnh hình vuông không được phép. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + Bức ảnh theo chiều ngang ({{ width }}x{{ height }}px). Ảnh chiều ngang không được phép. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + Bức ảnh theo chiều dọc ({{ width }}x{{ height }}px). Ảnh chiều dọc không được phép. + + + An empty file is not allowed. + Một file rỗng không được phép. + + + The host could not be resolved. + Máy chủ không thể được tìm thấy. + + + This value does not match the expected {{ charset }} charset. + Giá trị này không đúng định dạng bộ ký tự mong muốn {{ charset }}. + + + This is not a valid Business Identifier Code (BIC). + Giá trị này không đúng định dạng mã định danh doanh nghiệp (BIC). + + + Error + Lỗi + + + This is not a valid UUID. + Giá trị này không đúng định dạng UUID. + + + This value should be a multiple of {{ compared_value }}. + Giá trị này nên là bội số của {{ compared_value }}. + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Mã định danh doanh nghiệp (BIC) này không liên kết với IBAN {{ iban }}. + + + This value should be valid JSON. + Giá trị này nên đúng định dạng JSON. + + + This collection should contain only unique elements. + Danh sách này chỉ nên chứa các phần tử khác nhau. + + + This value should be positive. + Giá trị này có thể thực hiện được. + + + This value should be either positive or zero. + Giá trị này có thể thực hiện được hoặc bằng không. + + + This value should be negative. + Giá trị này nên bị từ chối. + + + This value should be either negative or zero. + Giá trị này nên bị từ chối hoặc bằng không. + + + This value is not a valid timezone. + Giá trị này không phải là múi giờ hợp lệ. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Mật khẩu này đã bị rò rỉ dữ liệu, không được sử dụng nữa. Xin vui lòng sử dụng mật khẩu khác. + + + This value should be between {{ min }} and {{ max }}. + Giá trị này nên thuộc giữa {{ min }} và {{ max }}. + + + This value is not a valid hostname. + Giá trị này không phải là tên máy chủ hợp lệ. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + Số lượng các phần tử trong bộ sưu tập này nên là bội số của {{ compared_value }}. + + + This value should satisfy at least one of the following constraints: + Giá trị này nên thỏa mãn ít nhất một trong những ràng buộc sau: + + + Each element of this collection should satisfy its own set of constraints. + Mỗi phần tử trong bộ sưu tập này nên thỏa mãn những ràng buộc của nó. + + + This value is not a valid International Securities Identification Number (ISIN). + Giá trị này không phải là mã số chứng khoán quốc tế (ISIN) hợp lệ. + + + This value should be a valid expression. + Giá trị này phải là một biểu thức hợp lệ. + + + This value is not a valid CSS color. + Giá trị này không phải là màu CSS hợp lệ. + + + This value is not a valid CIDR notation. + Giá trị này không phải là ký hiệu CIDR hợp lệ. + + + The value of the netmask should be between {{ min }} and {{ max }}. + Giá trị của mặt nạ mạng phải nằm trong khoảng từ {{ min }} đến {{ max }}. + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.zh_CN.xlf b/src/vendor/symfony/validator/Resources/translations/validators.zh_CN.xlf new file mode 100644 index 0000000..a7d49ba --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.zh_CN.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + 该变量的值应为 false 。 + + + This value should be true. + 该变量的值应为 true 。 + + + This value should be of type {{ type }}. + 该变量的类型应为 {{ type }} 。 + + + This value should be blank. + 该变量值应为空。 + + + The value you selected is not a valid choice. + 选定变量的值不是有效的选项。 + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + 您至少要选择 {{ limit }} 个选项。 + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + 您最多能选择 {{ limit }} 个选项。 + + + One or more of the given values is invalid. + 一个或者多个给定的值无效。 + + + This field was not expected. + 此字段是多余的。 + + + This field is missing. + 此字段缺失。 + + + This value is not a valid date. + 该值不是一个有效的日期(date)。 + + + This value is not a valid datetime. + 该值不是一个有效的日期时间(datetime)。 + + + This value is not a valid email address. + 该值不是一个有效的邮件地址。 + + + The file could not be found. + 文件未找到。 + + + The file is not readable. + 文件不可读。 + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + 文件太大 ({{ size }} {{ suffix }})。文件大小不可以超过 {{ limit }} {{ suffix }} 。 + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + 无效的文件类型 ({{ type }}) 。允许的文件类型有 {{ types }} 。 + + + This value should be {{ limit }} or less. + 这个变量的值应该小于或等于 {{ limit }}。 + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + 字符串太长,长度不可超过 {{ limit }} 个字符。 + + + This value should be {{ limit }} or more. + 该变量的值应该大于或等于 {{ limit }}。 + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + 字符串太短,长度不可少于 {{ limit }} 个字符。 + + + This value should not be blank. + 该变量不应为空。 + + + This value should not be null. + 该变量不应为 null 。 + + + This value should be null. + 该变量应为空 null 。 + + + This value is not valid. + 该变量值无效 。 + + + This value is not a valid time. + 该值不是一个有效的时间。 + + + This value is not a valid URL. + 该值不是一个有效的 URL 。 + + + The two values should be equal. + 这两个变量的值应该相等。 + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + 文件太大,文件大小不可以超过 {{ limit }} {{ suffix }}。 + + + The file is too large. + 文件太大。 + + + The file could not be uploaded. + 无法上传此文件。 + + + This value should be a valid number. + 该值应该为有效的数字。 + + + This file is not a valid image. + 该文件不是有效的图片。 + + + This is not a valid IP address. + 该值不是有效的IP地址。 + + + This value is not a valid language. + 该值不是有效的语言名。 + + + This value is not a valid locale. + 该值不是有效的区域值(locale)。 + + + This value is not a valid country. + 该值不是有效的国家名。 + + + This value is already used. + 该值已经被使用。 + + + The size of the image could not be detected. + 不能解析图片大小。 + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + 图片太宽 ({{ width }}px),最大宽度为 {{ max_width }}px 。 + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + 图片宽度不够 ({{ width }}px),最小宽度为 {{ min_width }}px 。 + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + 图片太高 ({{ height }}px),最大高度为 {{ max_height }}px 。 + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + 图片高度不够 ({{ height }}px),最小高度为 {{ min_height }}px 。 + + + This value should be the user's current password. + 该变量的值应为用户当前的密码。 + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + 该变量应为 {{ limit }} 个字符。 + + + The file was only partially uploaded. + 该文件的上传不完整。 + + + No file was uploaded. + 没有上传任何文件。 + + + No temporary folder was configured in php.ini. + php.ini 里没有配置临时文件目录。 + + + Cannot write temporary file to disk. + 临时文件写入磁盘失败。 + + + A PHP extension caused the upload to fail. + 某个 PHP 扩展造成上传失败。 + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + 该集合最少应包含 {{ limit }} 个元素。 + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + 该集合最多包含 {{ limit }} 个元素。 + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + 该集合应包含 {{ limit }} 个元素 element 。 + + + Invalid card number. + 无效的信用卡号。 + + + Unsupported card type or invalid card number. + 不支持的信用卡类型或无效的信用卡号。 + + + This is not a valid International Bank Account Number (IBAN). + 该值不是有效的国际银行帐号(IBAN)。 + + + This value is not a valid ISBN-10. + 该值不是有效的10位国际标准书号(ISBN-10)。 + + + This value is not a valid ISBN-13. + 该值不是有效的13位国际标准书号(ISBN-13)。 + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + 该值不是有效的国际标准书号(ISBN-10 或 ISBN-13)。 + + + This value is not a valid ISSN. + 该值不是有效的国际标准期刊号(ISSN)。 + + + This value is not a valid currency. + 该值不是有效的货币名(currency)。 + + + This value should be equal to {{ compared_value }}. + 该值应等于 {{ compared_value }} 。 + + + This value should be greater than {{ compared_value }}. + 该值应大于 {{ compared_value }} 。 + + + This value should be greater than or equal to {{ compared_value }}. + 该值应大于或等于 {{ compared_value }} 。 + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + 该值应与 {{ compared_value_type }} {{ compared_value }} 相同。 + + + This value should be less than {{ compared_value }}. + 该值应小于 {{ compared_value }} 。 + + + This value should be less than or equal to {{ compared_value }}. + 该值应小于或等于 {{ compared_value }} 。 + + + This value should not be equal to {{ compared_value }}. + 该值不应先等于 {{ compared_value }} 。 + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + 该值不应与 {{ compared_value_type }} {{ compared_value }} 相同。 + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + 图片宽高比太大 ({{ ratio }})。允许的最大宽高比为 {{ max_ratio }}。 + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + 图片宽高比太小 ({{ ratio }})。允许的最大宽高比为 {{ min_ratio }}。 + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + 图片是方形的 ({{ width }}x{{ height }}px)。不允许使用方形的图片。 + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + 图片是横向的 ({{ width }}x{{ height }}px)。不允许使用横向的图片。 + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + 图片是纵向的 ({{ width }}x{{ height }}px)。不允许使用纵向的图片。 + + + An empty file is not allowed. + 不允许使用空文件。 + + + The host could not be resolved. + 主机名无法解析。 + + + This value does not match the expected {{ charset }} charset. + 该值不符合 {{ charset }} 编码。 + + + This is not a valid Business Identifier Code (BIC). + 这不是有效的业务标识符代码(BIC)。 + + + Error + 错误 + + + This is not a valid UUID. + 这不是有效的UUID。 + + + This value should be a multiple of {{ compared_value }}. + 此值应为 {{ compared_value }} 的倍数。 + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + 此业务标识符代码(BIC)与IBAN {{ iban }} 无关。 + + + This value should be valid JSON. + 该值应该是有效的JSON。 + + + This collection should contain only unique elements. + 该集合应仅包含独一无二的元素。 + + + This value should be positive. + 数值应为正数。 + + + This value should be either positive or zero. + 数值应是正数,或为零。 + + + This value should be negative. + 数值应为负数。 + + + This value should be either negative or zero. + 数值应是负数,或为零。 + + + This value is not a valid timezone. + 无效时区。 + + + This password has been leaked in a data breach, it must not be used. Please use another password. + 此密码已被泄露,切勿使用。请更换密码。 + + + This value should be between {{ min }} and {{ max }}. + 该数值应在 {{ min }} 和 {{ max }} 之间。 + + + This value is not a valid hostname. + 该值不是有效的主机名称。 + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + 该集合内的元素数量得是 {{ compared_value }} 的倍数。 + + + This value should satisfy at least one of the following constraints: + 该值需符合以下其中一个约束: + + + Each element of this collection should satisfy its own set of constraints. + 该集合内的每个元素需符合元素本身规定的约束。 + + + This value is not a valid International Securities Identification Number (ISIN). + 该值不是有效的国际证券识别码 (ISIN)。 + + + This value should be a valid expression. + 该值需为一个有效的表达式。 + + + This value is not a valid CSS color. + 该值不是有效的CSS颜色。 + + + This value is not a valid CIDR notation. + 该值不是一个有效的CIDR表示。 + + + The value of the netmask should be between {{ min }} and {{ max }}. + 网络掩码的值应当在 {{ min }} 和 {{ max }} 之间。 + + + + diff --git a/src/vendor/symfony/validator/Resources/translations/validators.zh_TW.xlf b/src/vendor/symfony/validator/Resources/translations/validators.zh_TW.xlf new file mode 100644 index 0000000..b1f7fb4 --- /dev/null +++ b/src/vendor/symfony/validator/Resources/translations/validators.zh_TW.xlf @@ -0,0 +1,407 @@ + + + + + + This value should be false. + 該變數的值應為 false 。 + + + This value should be true. + 該變數的值應為 true 。 + + + This value should be of type {{ type }}. + 該變數的類型應為 {{ type }} 。 + + + This value should be blank. + 該變數應為空。 + + + The value you selected is not a valid choice. + 選定變數的值不是有效的選項。 + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + 您至少要選擇 {{ limit }} 個選項。 + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + 您最多能選擇 {{ limit }} 個選項。 + + + One or more of the given values is invalid. + 一個或者多個給定的值無效。 + + + This field was not expected. + 此字段是沒有預料到。 + + + This field is missing. + 此字段缺失。 + + + This value is not a valid date. + 該值不是一個有效的日期(date)。 + + + This value is not a valid datetime. + 該值不是一個有效的日期時間(datetime)。 + + + This value is not a valid email address. + 該值不是一個有效的郵件地址。 + + + The file could not be found. + 找不到檔案。 + + + The file is not readable. + 無法讀取檔案。 + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + 檔案太大 ({{ size }} {{ suffix }})。檔案大小不可以超過 {{ limit }} {{ suffix }} 。 + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + 無效的檔案類型 ({{ type }}) 。允許的檔案類型有 {{ types }} 。 + + + This value should be {{ limit }} or less. + 這個變數的值應該小於或等於 {{ limit }}。 + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + 字串太長,長度不可超過 {{ limit }} 個字元。 + + + This value should be {{ limit }} or more. + 該變數的值應該大於或等於 {{ limit }}。 + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + 字串太短,長度不可少於 {{ limit }} 個字元。 + + + This value should not be blank. + 該變數不應為空白。 + + + This value should not be null. + 該值不應為 null 。 + + + This value should be null. + 該值應為 null 。 + + + This value is not valid. + 無效的數值 。 + + + This value is not a valid time. + 該值不是一個有效的時間。 + + + This value is not a valid URL. + 該值不是一個有效的 URL 。 + + + The two values should be equal. + 這兩個變數的值應該相等。 + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + 檔案太大,檔案大小不可以超過 {{ limit }} {{ suffix }}。 + + + The file is too large. + 檔案太大。 + + + The file could not be uploaded. + 無法上傳此檔案。 + + + This value should be a valid number. + 該值應該為有效的數字。 + + + This file is not a valid image. + 該檔案不是有效的圖片。 + + + This is not a valid IP address. + 該值不是有效的IP地址。 + + + This value is not a valid language. + 該值不是有效的語言名。 + + + This value is not a valid locale. + 該值不是有效的區域值(locale)。 + + + This value is not a valid country. + 該值不是有效的國家名。 + + + This value is already used. + 該值已經被使用。 + + + The size of the image could not be detected. + 不能解析圖片大小。 + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + 圖片太寬 ({{ width }}px),最大寬度為 {{ max_width }}px 。 + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + 圖片寬度不夠 ({{ width }}px),最小寬度為 {{ min_width }}px 。 + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + 圖片太高 ({{ height }}px),最大高度為 {{ max_height }}px 。 + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + 圖片高度不夠 ({{ height }}px),最小高度為 {{ min_height }}px 。 + + + This value should be the user's current password. + 該變數的值應為用戶目前的密碼。 + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + 該變數應為 {{ limit }} 個字元。 + + + The file was only partially uploaded. + 該檔案的上傳不完整。 + + + No file was uploaded. + 沒有上傳任何檔案。 + + + No temporary folder was configured in php.ini. + php.ini 裡沒有配置臨時目錄。 + + + Cannot write temporary file to disk. + 暫存檔寫入磁碟失敗。 + + + A PHP extension caused the upload to fail. + 某個 PHP 擴展造成上傳失敗。 + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + 該集合最少應包含 {{ limit }} 個元素。 + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + 該集合最多包含 {{ limit }} 個元素。 + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + 該集合應包含 {{ limit }} 個元素 element 。 + + + Invalid card number. + 無效的信用卡號。 + + + Unsupported card type or invalid card number. + 不支援的信用卡類型或無效的信用卡號。 + + + This is not a valid International Bank Account Number (IBAN). + 該值不是有效的國際銀行帳號(IBAN)。 + + + This value is not a valid ISBN-10. + 該值不是有效的10位國際標準書號(ISBN-10)。 + + + This value is not a valid ISBN-13. + 該值不是有效的13位國際標準書號(ISBN-13)。 + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + 該值不是有效的國際標準書號(ISBN-10 或 ISBN-13)。 + + + This value is not a valid ISSN. + 該值不是有效的國際標準期刊號(ISSN)。 + + + This value is not a valid currency. + 該值不是有效的貨幣名(currency)。 + + + This value should be equal to {{ compared_value }}. + 該值應等於 {{ compared_value }} 。 + + + This value should be greater than {{ compared_value }}. + 該值應大於 {{ compared_value }} 。 + + + This value should be greater than or equal to {{ compared_value }}. + 該值應大於或等於 {{ compared_value }} 。 + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + 該值應與 {{ compared_value_type }} {{ compared_value }} 相同。 + + + This value should be less than {{ compared_value }}. + 該值應小於 {{ compared_value }} 。 + + + This value should be less than or equal to {{ compared_value }}. + 該值應小於或等於 {{ compared_value }} 。 + + + This value should not be equal to {{ compared_value }}. + 該值應不等於 {{ compared_value }} 。 + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + 該值不應與 {{ compared_value_type }} {{ compared_value }} 相同。 + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + 圖像格式過大 ({{ ratio }})。 最大允許尺寸 {{ max_ratio }}。 + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + 圖像格式過小 ({{ ratio }})。最小尺寸 {{ min_ratio }}。 + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + 方形圖像 ({{ width }}x{{ height }}px)。不接受方形圖像。 + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + 紀念冊布局圖像 ({{ width }}x{{ height }}px)。 不接受紀念冊布局圖像。 + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + 書籍布局圖像 ({{ width }}x{{ height }}px)。不接受圖像書籍布局。 + + + An empty file is not allowed. + 不接受空白文件。 + + + The host could not be resolved. + 未找到服務器。 + + + This value does not match the expected {{ charset }} charset. + 該數值不符合預期 {{ charset }} 符號編碼。 + + + This is not a valid Business Identifier Code (BIC). + 無效企業識別碼 (BIC)。 + + + Error. + 錯誤。 + + + This is not a valid UUID. + 無效的通用唯壹標識符 (UUID)。 + + + This value should be a multiple of {{ compared_value }}. + 該值必須是倍數 {{ compared_value }}。 + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + 該企業識別碼 (BIC) 與銀行賬戶國際編號不壹致 (IBAN) {{ iban }}。 + + + This value should be valid JSON. + 該數值必須序列化為JSON格式。 + + + This collection should contain only unique elements. + 該集合應僅包含唯壹元素。 + + + This value should be positive. + 數值應為正數。 + + + This value should be either positive or zero. + 數值應是正數,或為零。 + + + This value should be negative. + 數值應為負數。 + + + This value should be either negative or zero. + 數值應是負數,或為零。 + + + This value is not a valid timezone. + 無效時區。 + + + This password has been leaked in a data breach, it must not be used. Please use another password. + 此密碼已被泄露,切勿使用。請更換密碼。 + + + This value should be between {{ min }} and {{ max }}. + 該數值應在 {{ min }} 和 {{ max }} 之間。 + + + This value is not a valid hostname. + 該數值不是有效的主機名稱。 + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + 該集合內的元素數量得是 {{ compared_value }} 的倍數。 + + + This value should satisfy at least one of the following constraints: + 該數值需符合以下其中一個約束: + + + Each element of this collection should satisfy its own set of constraints. + 該集合內的每個元素需符合元素本身規定的約束。 + + + This value is not a valid International Securities Identification Number (ISIN). + 該數值不是有效的國際證券識別碼 (ISIN)。 + + + This value should be a valid expression. + 該值需為一個有效的表達式。 + + + This value is not a valid CSS color. + 該值不是有效的CSS顏色。 + + + This value is not a valid CIDR notation. + 該值不是一個有效的CIDR表示。 + + + The value of the netmask should be between {{ min }} and {{ max }}. + 網絡掩碼的值應當在 {{ min }} 和 {{ max }} 之間。 + + + + diff --git a/src/vendor/symfony/validator/Test/ConstraintValidatorTestCase.php b/src/vendor/symfony/validator/Test/ConstraintValidatorTestCase.php new file mode 100644 index 0000000..9cc6f0a --- /dev/null +++ b/src/vendor/symfony/validator/Test/ConstraintValidatorTestCase.php @@ -0,0 +1,586 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Test; + +use PHPUnit\Framework\Assert; +use PHPUnit\Framework\Constraint\IsIdentical; +use PHPUnit\Framework\Constraint\IsInstanceOf; +use PHPUnit\Framework\Constraint\IsNull; +use PHPUnit\Framework\Constraint\LogicalOr; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\TestCase; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Constraints\NotNull; +use Symfony\Component\Validator\Constraints\Valid; +use Symfony\Component\Validator\ConstraintValidatorInterface; +use Symfony\Component\Validator\ConstraintViolation; +use Symfony\Component\Validator\ConstraintViolationInterface; +use Symfony\Component\Validator\ConstraintViolationList; +use Symfony\Component\Validator\ConstraintViolationListInterface; +use Symfony\Component\Validator\Context\ExecutionContext; +use Symfony\Component\Validator\Context\ExecutionContextInterface; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\PropertyMetadata; +use Symfony\Component\Validator\Validator\ContextualValidatorInterface; +use Symfony\Component\Validator\Validator\ValidatorInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * A test case to ease testing Constraint Validators. + * + * @author Bernhard Schussek + */ +abstract class ConstraintValidatorTestCase extends TestCase +{ + /** + * @var ExecutionContextInterface + */ + protected $context; + + /** + * @var ConstraintValidatorInterface + */ + protected $validator; + + protected $group; + protected $metadata; + protected $object; + protected $value; + protected $root; + protected $propertyPath; + protected $constraint; + protected $defaultTimezone; + private $defaultLocale; + private $expectedViolations; + private $call; + + protected function setUp(): void + { + $this->group = 'MyGroup'; + $this->metadata = null; + $this->object = null; + $this->value = 'InvalidValue'; + $this->root = 'root'; + $this->propertyPath = 'property.path'; + + // Initialize the context with some constraint so that we can + // successfully build a violation. + $this->constraint = new NotNull(); + + $this->context = $this->createContext(); + $this->validator = $this->createValidator(); + $this->validator->initialize($this->context); + + $this->defaultLocale = \Locale::getDefault(); + \Locale::setDefault('en'); + + $this->expectedViolations = []; + $this->call = 0; + + $this->setDefaultTimezone('UTC'); + } + + protected function tearDown(): void + { + $this->restoreDefaultTimezone(); + + \Locale::setDefault($this->defaultLocale); + } + + protected function setDefaultTimezone(?string $defaultTimezone) + { + // Make sure this method cannot be called twice before calling + // also restoreDefaultTimezone() + if (null === $this->defaultTimezone) { + $this->defaultTimezone = date_default_timezone_get(); + date_default_timezone_set($defaultTimezone); + } + } + + protected function restoreDefaultTimezone() + { + if (null !== $this->defaultTimezone) { + date_default_timezone_set($this->defaultTimezone); + $this->defaultTimezone = null; + } + } + + protected function createContext() + { + $translator = $this->createMock(TranslatorInterface::class); + $translator->expects($this->any())->method('trans')->willReturnArgument(0); + $validator = $this->createMock(ValidatorInterface::class); + $validator->expects($this->any()) + ->method('validate') + ->willReturnCallback(function () { + return $this->expectedViolations[$this->call++] ?? new ConstraintViolationList(); + }); + + $context = new ExecutionContext($validator, $this->root, $translator); + $context->setGroup($this->group); + $context->setNode($this->value, $this->object, $this->metadata, $this->propertyPath); + $context->setConstraint($this->constraint); + + $contextualValidatorMockBuilder = $this->getMockBuilder(AssertingContextualValidator::class) + ->setConstructorArgs([$context]); + $contextualValidatorMethods = [ + 'atPath', + 'validate', + 'validateProperty', + 'validatePropertyValue', + 'getViolations', + ]; + + $contextualValidatorMockBuilder->onlyMethods($contextualValidatorMethods); + $contextualValidator = $contextualValidatorMockBuilder->getMock(); + $contextualValidator->expects($this->any()) + ->method('atPath') + ->willReturnCallback(function ($path) use ($contextualValidator) { + return $contextualValidator->doAtPath($path); + }); + $contextualValidator->expects($this->any()) + ->method('validate') + ->willReturnCallback(function ($value, $constraints = null, $groups = null) use ($contextualValidator) { + return $contextualValidator->doValidate($value, $constraints, $groups); + }); + $contextualValidator->expects($this->any()) + ->method('validateProperty') + ->willReturnCallback(function ($object, $propertyName, $groups = null) use ($contextualValidator) { + return $contextualValidator->validateProperty($object, $propertyName, $groups); + }); + $contextualValidator->expects($this->any()) + ->method('validatePropertyValue') + ->willReturnCallback(function ($objectOrClass, $propertyName, $value, $groups = null) use ($contextualValidator) { + return $contextualValidator->doValidatePropertyValue($objectOrClass, $propertyName, $value, $groups); + }); + $contextualValidator->expects($this->any()) + ->method('getViolations') + ->willReturnCallback(function () use ($contextualValidator) { + return $contextualValidator->doGetViolations(); + }); + $validator->expects($this->any()) + ->method('inContext') + ->with($context) + ->willReturn($contextualValidator); + + return $context; + } + + protected function setGroup(?string $group) + { + $this->group = $group; + $this->context->setGroup($group); + } + + protected function setObject($object) + { + $this->object = $object; + $this->metadata = \is_object($object) + ? new ClassMetadata(\get_class($object)) + : null; + + $this->context->setNode($this->value, $this->object, $this->metadata, $this->propertyPath); + } + + protected function setProperty($object, $property) + { + $this->object = $object; + $this->metadata = \is_object($object) + ? new PropertyMetadata(\get_class($object), $property) + : null; + + $this->context->setNode($this->value, $this->object, $this->metadata, $this->propertyPath); + } + + protected function setValue($value) + { + $this->value = $value; + $this->context->setNode($this->value, $this->object, $this->metadata, $this->propertyPath); + } + + protected function setRoot($root) + { + $this->root = $root; + $this->context = $this->createContext(); + $this->validator->initialize($this->context); + } + + protected function setPropertyPath(string $propertyPath) + { + $this->propertyPath = $propertyPath; + $this->context->setNode($this->value, $this->object, $this->metadata, $this->propertyPath); + } + + protected function expectNoValidate() + { + $validator = $this->context->getValidator()->inContext($this->context); + $validator->expectNoValidate(); + } + + protected function expectValidateAt(int $i, string $propertyPath, $value, $group) + { + $validator = $this->context->getValidator()->inContext($this->context); + $validator->expectValidation($i, $propertyPath, $value, $group, function ($passedConstraints) { + $expectedConstraints = new LogicalOr(); + $expectedConstraints->setConstraints([new IsNull(), new IsIdentical([]), new IsInstanceOf(Valid::class)]); + + Assert::assertThat($passedConstraints, $expectedConstraints); + }); + } + + protected function expectValidateValue(int $i, $value, array $constraints = [], $group = null) + { + $contextualValidator = $this->context->getValidator()->inContext($this->context); + $contextualValidator->expectValidation($i, null, $value, $group, function ($passedConstraints) use ($constraints) { + if (\is_array($constraints) && !\is_array($passedConstraints)) { + $passedConstraints = [$passedConstraints]; + } + + Assert::assertEquals($constraints, $passedConstraints); + }); + } + + protected function expectFailingValueValidation(int $i, $value, array $constraints, $group, ConstraintViolationInterface $violation) + { + $contextualValidator = $this->context->getValidator()->inContext($this->context); + $contextualValidator->expectValidation($i, null, $value, $group, function ($passedConstraints) use ($constraints) { + if (\is_array($constraints) && !\is_array($passedConstraints)) { + $passedConstraints = [$passedConstraints]; + } + + Assert::assertEquals($constraints, $passedConstraints); + }, $violation); + } + + protected function expectValidateValueAt(int $i, string $propertyPath, $value, $constraints, $group = null) + { + $contextualValidator = $this->context->getValidator()->inContext($this->context); + $contextualValidator->expectValidation($i, $propertyPath, $value, $group, function ($passedConstraints) use ($constraints) { + Assert::assertEquals($constraints, $passedConstraints); + }); + } + + protected function expectViolationsAt($i, $value, Constraint $constraint) + { + $context = $this->createContext(); + + $validatorClassname = $constraint->validatedBy(); + + $validator = new $validatorClassname(); + $validator->initialize($context); + $validator->validate($value, $constraint); + + $this->expectedViolations[] = $context->getViolations(); + + return $context->getViolations(); + } + + protected function assertNoViolation() + { + $this->assertSame(0, $violationsCount = \count($this->context->getViolations()), sprintf('0 violation expected. Got %u.', $violationsCount)); + } + + /** + * @return ConstraintViolationAssertion + */ + protected function buildViolation($message) + { + return new ConstraintViolationAssertion($this->context, $message, $this->constraint); + } + + abstract protected function createValidator(); +} + +final class ConstraintViolationAssertion +{ + /** + * @var ExecutionContextInterface + */ + private $context; + + /** + * @var ConstraintViolationAssertion[] + */ + private $assertions; + + private $message; + private $parameters = []; + private $invalidValue = 'InvalidValue'; + private $propertyPath = 'property.path'; + private $plural; + private $code; + private $constraint; + private $cause; + + /** + * @internal + */ + public function __construct(ExecutionContextInterface $context, string $message, Constraint $constraint = null, array $assertions = []) + { + $this->context = $context; + $this->message = $message; + $this->constraint = $constraint; + $this->assertions = $assertions; + } + + /** + * @return $this + */ + public function atPath(string $path) + { + $this->propertyPath = $path; + + return $this; + } + + /** + * @return $this + */ + public function setParameter(string $key, string $value) + { + $this->parameters[$key] = $value; + + return $this; + } + + /** + * @return $this + */ + public function setParameters(array $parameters) + { + $this->parameters = $parameters; + + return $this; + } + + /** + * @return $this + */ + public function setTranslationDomain($translationDomain) + { + // no-op for BC + + return $this; + } + + /** + * @return $this + */ + public function setInvalidValue($invalidValue) + { + $this->invalidValue = $invalidValue; + + return $this; + } + + /** + * @return $this + */ + public function setPlural(int $number) + { + $this->plural = $number; + + return $this; + } + + /** + * @return $this + */ + public function setCode(string $code) + { + $this->code = $code; + + return $this; + } + + /** + * @return $this + */ + public function setCause($cause) + { + $this->cause = $cause; + + return $this; + } + + public function buildNextViolation(string $message): self + { + $assertions = $this->assertions; + $assertions[] = $this; + + return new self($this->context, $message, $this->constraint, $assertions); + } + + public function assertRaised() + { + $expected = []; + foreach ($this->assertions as $assertion) { + $expected[] = $assertion->getViolation(); + } + $expected[] = $this->getViolation(); + + $violations = iterator_to_array($this->context->getViolations()); + + Assert::assertSame($expectedCount = \count($expected), $violationsCount = \count($violations), sprintf('%u violation(s) expected. Got %u.', $expectedCount, $violationsCount)); + + reset($violations); + + foreach ($expected as $violation) { + Assert::assertEquals($violation, current($violations)); + next($violations); + } + } + + private function getViolation(): ConstraintViolation + { + return new ConstraintViolation( + $this->message, + $this->message, + $this->parameters, + $this->context->getRoot(), + $this->propertyPath, + $this->invalidValue, + $this->plural, + $this->code, + $this->constraint, + $this->cause + ); + } +} + +/** + * @internal + */ +class AssertingContextualValidator implements ContextualValidatorInterface +{ + private $context; + private $expectNoValidate = false; + private $atPathCalls = -1; + private $expectedAtPath = []; + private $validateCalls = -1; + private $expectedValidate = []; + + public function __construct(ExecutionContextInterface $context) + { + $this->context = $context; + } + + public function __destruct() + { + if ($this->expectedAtPath) { + throw new ExpectationFailedException('Some expected validation calls for paths were not done.'); + } + + if ($this->expectedValidate) { + throw new ExpectationFailedException('Some expected validation calls for values were not done.'); + } + } + + public function atPath(string $path) + { + } + + /** + * @return $this + */ + public function doAtPath(string $path) + { + Assert::assertFalse($this->expectNoValidate, 'No validation calls have been expected.'); + + if (!isset($this->expectedAtPath[++$this->atPathCalls])) { + throw new ExpectationFailedException(sprintf('Validation for property path "%s" was not expected.', $path)); + } + + $expectedPath = $this->expectedAtPath[$this->atPathCalls]; + unset($this->expectedAtPath[$this->atPathCalls]); + + Assert::assertSame($expectedPath, $path); + + return $this; + } + + public function validate($value, $constraints = null, $groups = null) + { + } + + /** + * @return $this + */ + public function doValidate($value, $constraints = null, $groups = null) + { + Assert::assertFalse($this->expectNoValidate, 'No validation calls have been expected.'); + + if (!isset($this->expectedValidate[++$this->validateCalls])) { + return $this; + } + + [$expectedValue, $expectedGroup, $expectedConstraints, $violation] = $this->expectedValidate[$this->validateCalls]; + unset($this->expectedValidate[$this->validateCalls]); + + Assert::assertSame($expectedValue, $value); + $expectedConstraints($constraints); + Assert::assertSame($expectedGroup, $groups); + + if (null !== $violation) { + $this->context->addViolation($violation->getMessage(), $violation->getParameters()); + } + + return $this; + } + + public function validateProperty(object $object, string $propertyName, $groups = null) + { + } + + /** + * @return $this + */ + public function doValidateProperty(object $object, string $propertyName, $groups = null) + { + return $this; + } + + public function validatePropertyValue($objectOrClass, string $propertyName, $value, $groups = null) + { + } + + /** + * @return $this + */ + public function doValidatePropertyValue($objectOrClass, string $propertyName, $value, $groups = null) + { + return $this; + } + + public function getViolations(): ConstraintViolationListInterface + { + } + + public function doGetViolations() + { + return $this->context->getViolations(); + } + + public function expectNoValidate() + { + $this->expectNoValidate = true; + } + + public function expectValidation(string $call, ?string $propertyPath, $value, $group, callable $constraints, ConstraintViolationInterface $violation = null) + { + if (null !== $propertyPath) { + $this->expectedAtPath[$call] = $propertyPath; + } + + $this->expectedValidate[$call] = [$value, $group, $constraints, $violation]; + } +} diff --git a/src/vendor/symfony/validator/Util/PropertyPath.php b/src/vendor/symfony/validator/Util/PropertyPath.php new file mode 100644 index 0000000..973b6f2 --- /dev/null +++ b/src/vendor/symfony/validator/Util/PropertyPath.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Util; + +/** + * Contains utility methods for dealing with property paths. + * + * For more extensive functionality, use Symfony's PropertyAccess component. + * + * @author Bernhard Schussek + */ +class PropertyPath +{ + /** + * Appends a path to a given property path. + * + * If the base path is empty, the appended path will be returned unchanged. + * If the base path is not empty, and the appended path starts with a + * squared opening bracket ("["), the concatenation of the two paths is + * returned. Otherwise, the concatenation of the two paths is returned, + * separated by a dot ("."). + * + * @return string + */ + public static function append(string $basePath, string $subPath) + { + if ('' !== $subPath) { + if ('[' === $subPath[0]) { + return $basePath.$subPath; + } + + return '' !== $basePath ? $basePath.'.'.$subPath : $subPath; + } + + return $basePath; + } + + /** + * Not instantiable. + */ + private function __construct() + { + } +} diff --git a/src/vendor/symfony/validator/Validation.php b/src/vendor/symfony/validator/Validation.php new file mode 100644 index 0000000..4d08072 --- /dev/null +++ b/src/vendor/symfony/validator/Validation.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator; + +use Symfony\Component\Validator\Exception\ValidationFailedException; +use Symfony\Component\Validator\Validator\ValidatorInterface; + +/** + * Entry point for the Validator component. + * + * @author Bernhard Schussek + */ +final class Validation +{ + /** + * Creates a callable chain of constraints. + * + * @param Constraint|ValidatorInterface|null $constraintOrValidator + * + * @return callable($value) + */ + public static function createCallable($constraintOrValidator = null, Constraint ...$constraints): callable + { + $validator = self::createIsValidCallable($constraintOrValidator, ...$constraints); + + return static function ($value) use ($validator) { + if (!$validator($value, $violations)) { + throw new ValidationFailedException($value, $violations); + } + + return $value; + }; + } + + /** + * Creates a callable that returns true/false instead of throwing validation exceptions. + * + * @param Constraint|ValidatorInterface|null $constraintOrValidator + * + * @return callable($value, &$violations = null): bool + */ + public static function createIsValidCallable($constraintOrValidator = null, Constraint ...$constraints): callable + { + $validator = $constraintOrValidator; + + if ($constraintOrValidator instanceof Constraint) { + $constraints = \func_get_args(); + $validator = null; + } elseif (null !== $constraintOrValidator && !$constraintOrValidator instanceof ValidatorInterface) { + throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be a "%s" or a "%s" object, "%s" given.', __METHOD__, Constraint::class, ValidatorInterface::class, get_debug_type($constraintOrValidator))); + } + + $validator = $validator ?? self::createValidator(); + + return static function ($value, &$violations = null) use ($constraints, $validator) { + $violations = $validator->validate($value, $constraints); + + return 0 === $violations->count(); + }; + } + + /** + * Creates a new validator. + * + * If you want to configure the validator, use + * {@link createValidatorBuilder()} instead. + */ + public static function createValidator(): ValidatorInterface + { + return self::createValidatorBuilder()->getValidator(); + } + + /** + * Creates a configurable builder for validator objects. + */ + public static function createValidatorBuilder(): ValidatorBuilder + { + return new ValidatorBuilder(); + } + + /** + * This class cannot be instantiated. + */ + private function __construct() + { + } +} diff --git a/src/vendor/symfony/validator/Validator/ContextualValidatorInterface.php b/src/vendor/symfony/validator/Validator/ContextualValidatorInterface.php new file mode 100644 index 0000000..1063a53 --- /dev/null +++ b/src/vendor/symfony/validator/Validator/ContextualValidatorInterface.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Validator; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Constraints\GroupSequence; +use Symfony\Component\Validator\ConstraintViolationListInterface; + +/** + * A validator in a specific execution context. + * + * @author Bernhard Schussek + */ +interface ContextualValidatorInterface +{ + /** + * Appends the given path to the property path of the context. + * + * If called multiple times, the path will always be reset to the context's + * original path with the given path appended to it. + * + * @return $this + */ + public function atPath(string $path); + + /** + * Validates a value against a constraint or a list of constraints. + * + * If no constraint is passed, the constraint + * {@link \Symfony\Component\Validator\Constraints\Valid} is assumed. + * + * @param mixed $value The value to validate + * @param Constraint|Constraint[]|null $constraints The constraint(s) to validate against + * @param string|GroupSequence|array|null $groups The validation groups to validate. If none is given, "Default" is assumed + * + * @return $this + */ + public function validate($value, $constraints = null, $groups = null); + + /** + * Validates a property of an object against the constraints specified + * for this property. + * + * @param string $propertyName The name of the validated property + * @param string|GroupSequence|array|null $groups The validation groups to validate. If none is given, "Default" is assumed + * + * @return $this + */ + public function validateProperty(object $object, string $propertyName, $groups = null); + + /** + * Validates a value against the constraints specified for an object's + * property. + * + * @param object|string $objectOrClass The object or its class name + * @param string $propertyName The name of the property + * @param mixed $value The value to validate against the property's constraints + * @param string|GroupSequence|array|null $groups The validation groups to validate. If none is given, "Default" is assumed + * + * @return $this + */ + public function validatePropertyValue($objectOrClass, string $propertyName, $value, $groups = null); + + /** + * Returns the violations that have been generated so far in the context + * of the validator. + * + * @return ConstraintViolationListInterface + */ + public function getViolations(); +} diff --git a/src/vendor/symfony/validator/Validator/LazyProperty.php b/src/vendor/symfony/validator/Validator/LazyProperty.php new file mode 100644 index 0000000..a70eeed --- /dev/null +++ b/src/vendor/symfony/validator/Validator/LazyProperty.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Validator; + +/** + * A wrapper for a callable initializing a property from a getter. + * + * @internal + */ +class LazyProperty +{ + private $propertyValueCallback; + + public function __construct(\Closure $propertyValueCallback) + { + $this->propertyValueCallback = $propertyValueCallback; + } + + public function getPropertyValue() + { + return ($this->propertyValueCallback)(); + } +} diff --git a/src/vendor/symfony/validator/Validator/RecursiveContextualValidator.php b/src/vendor/symfony/validator/Validator/RecursiveContextualValidator.php new file mode 100644 index 0000000..04c76e8 --- /dev/null +++ b/src/vendor/symfony/validator/Validator/RecursiveContextualValidator.php @@ -0,0 +1,782 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Validator; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Constraints\Composite; +use Symfony\Component\Validator\Constraints\Existence; +use Symfony\Component\Validator\Constraints\GroupSequence; +use Symfony\Component\Validator\Constraints\Valid; +use Symfony\Component\Validator\ConstraintValidatorFactoryInterface; +use Symfony\Component\Validator\Context\ExecutionContext; +use Symfony\Component\Validator\Context\ExecutionContextInterface; +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; +use Symfony\Component\Validator\Exception\NoSuchMetadataException; +use Symfony\Component\Validator\Exception\RuntimeException; +use Symfony\Component\Validator\Exception\UnexpectedValueException; +use Symfony\Component\Validator\Exception\UnsupportedMetadataException; +use Symfony\Component\Validator\Exception\ValidatorException; +use Symfony\Component\Validator\Mapping\CascadingStrategy; +use Symfony\Component\Validator\Mapping\ClassMetadataInterface; +use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface; +use Symfony\Component\Validator\Mapping\GenericMetadata; +use Symfony\Component\Validator\Mapping\GetterMetadata; +use Symfony\Component\Validator\Mapping\MetadataInterface; +use Symfony\Component\Validator\Mapping\PropertyMetadataInterface; +use Symfony\Component\Validator\Mapping\TraversalStrategy; +use Symfony\Component\Validator\ObjectInitializerInterface; +use Symfony\Component\Validator\Util\PropertyPath; + +/** + * Recursive implementation of {@link ContextualValidatorInterface}. + * + * @author Bernhard Schussek + */ +class RecursiveContextualValidator implements ContextualValidatorInterface +{ + private $context; + private $defaultPropertyPath; + private $defaultGroups; + private $metadataFactory; + private $validatorFactory; + private $objectInitializers; + + /** + * Creates a validator for the given context. + * + * @param ObjectInitializerInterface[] $objectInitializers The object initializers + */ + public function __construct(ExecutionContextInterface $context, MetadataFactoryInterface $metadataFactory, ConstraintValidatorFactoryInterface $validatorFactory, array $objectInitializers = []) + { + $this->context = $context; + $this->defaultPropertyPath = $context->getPropertyPath(); + $this->defaultGroups = [$context->getGroup() ?: Constraint::DEFAULT_GROUP]; + $this->metadataFactory = $metadataFactory; + $this->validatorFactory = $validatorFactory; + $this->objectInitializers = $objectInitializers; + } + + /** + * {@inheritdoc} + */ + public function atPath(string $path) + { + $this->defaultPropertyPath = $this->context->getPropertyPath($path); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function validate($value, $constraints = null, $groups = null) + { + $groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups; + + $previousValue = $this->context->getValue(); + $previousObject = $this->context->getObject(); + $previousMetadata = $this->context->getMetadata(); + $previousPath = $this->context->getPropertyPath(); + $previousGroup = $this->context->getGroup(); + $previousConstraint = null; + + if ($this->context instanceof ExecutionContext || method_exists($this->context, 'getConstraint')) { + $previousConstraint = $this->context->getConstraint(); + } + + // If explicit constraints are passed, validate the value against + // those constraints + if (null !== $constraints) { + // You can pass a single constraint or an array of constraints + // Make sure to deal with an array in the rest of the code + if (!\is_array($constraints)) { + $constraints = [$constraints]; + } + + $metadata = new GenericMetadata(); + $metadata->addConstraints($constraints); + + $this->validateGenericNode( + $value, + $previousObject, + \is_object($value) ? $this->generateCacheKey($value) : null, + $metadata, + $this->defaultPropertyPath, + $groups, + null, + TraversalStrategy::IMPLICIT, + $this->context + ); + + $this->context->setNode($previousValue, $previousObject, $previousMetadata, $previousPath); + $this->context->setGroup($previousGroup); + + if (null !== $previousConstraint) { + $this->context->setConstraint($previousConstraint); + } + + return $this; + } + + // If an object is passed without explicit constraints, validate that + // object against the constraints defined for the object's class + if (\is_object($value)) { + $this->validateObject( + $value, + $this->defaultPropertyPath, + $groups, + TraversalStrategy::IMPLICIT, + $this->context + ); + + $this->context->setNode($previousValue, $previousObject, $previousMetadata, $previousPath); + $this->context->setGroup($previousGroup); + + return $this; + } + + // If an array is passed without explicit constraints, validate each + // object in the array + if (\is_array($value)) { + $this->validateEachObjectIn( + $value, + $this->defaultPropertyPath, + $groups, + $this->context + ); + + $this->context->setNode($previousValue, $previousObject, $previousMetadata, $previousPath); + $this->context->setGroup($previousGroup); + + return $this; + } + + throw new RuntimeException(sprintf('Cannot validate values of type "%s" automatically. Please provide a constraint.', get_debug_type($value))); + } + + /** + * {@inheritdoc} + */ + public function validateProperty(object $object, string $propertyName, $groups = null) + { + $classMetadata = $this->metadataFactory->getMetadataFor($object); + + if (!$classMetadata instanceof ClassMetadataInterface) { + throw new ValidatorException(sprintf('The metadata factory should return instances of "\Symfony\Component\Validator\Mapping\ClassMetadataInterface", got: "%s".', get_debug_type($classMetadata))); + } + + $propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName); + $groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups; + $cacheKey = $this->generateCacheKey($object); + $propertyPath = PropertyPath::append($this->defaultPropertyPath, $propertyName); + + $previousValue = $this->context->getValue(); + $previousObject = $this->context->getObject(); + $previousMetadata = $this->context->getMetadata(); + $previousPath = $this->context->getPropertyPath(); + $previousGroup = $this->context->getGroup(); + + foreach ($propertyMetadatas as $propertyMetadata) { + $propertyValue = $propertyMetadata->getPropertyValue($object); + + $this->validateGenericNode( + $propertyValue, + $object, + $cacheKey.':'.\get_class($object).':'.$propertyName, + $propertyMetadata, + $propertyPath, + $groups, + null, + TraversalStrategy::IMPLICIT, + $this->context + ); + } + + $this->context->setNode($previousValue, $previousObject, $previousMetadata, $previousPath); + $this->context->setGroup($previousGroup); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function validatePropertyValue($objectOrClass, string $propertyName, $value, $groups = null) + { + $classMetadata = $this->metadataFactory->getMetadataFor($objectOrClass); + + if (!$classMetadata instanceof ClassMetadataInterface) { + throw new ValidatorException(sprintf('The metadata factory should return instances of "\Symfony\Component\Validator\Mapping\ClassMetadataInterface", got: "%s".', get_debug_type($classMetadata))); + } + + $propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName); + $groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups; + + if (\is_object($objectOrClass)) { + $object = $objectOrClass; + $class = \get_class($object); + $cacheKey = $this->generateCacheKey($objectOrClass); + $propertyPath = PropertyPath::append($this->defaultPropertyPath, $propertyName); + } else { + // $objectOrClass contains a class name + $object = null; + $class = $objectOrClass; + $cacheKey = null; + $propertyPath = $this->defaultPropertyPath; + } + + $previousValue = $this->context->getValue(); + $previousObject = $this->context->getObject(); + $previousMetadata = $this->context->getMetadata(); + $previousPath = $this->context->getPropertyPath(); + $previousGroup = $this->context->getGroup(); + + foreach ($propertyMetadatas as $propertyMetadata) { + $this->validateGenericNode( + $value, + $object, + $cacheKey.':'.$class.':'.$propertyName, + $propertyMetadata, + $propertyPath, + $groups, + null, + TraversalStrategy::IMPLICIT, + $this->context + ); + } + + $this->context->setNode($previousValue, $previousObject, $previousMetadata, $previousPath); + $this->context->setGroup($previousGroup); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getViolations() + { + return $this->context->getViolations(); + } + + /** + * Normalizes the given group or list of groups to an array. + * + * @param string|GroupSequence|array $groups The groups to normalize + * + * @return array + */ + protected function normalizeGroups($groups) + { + if (\is_array($groups)) { + return $groups; + } + + return [$groups]; + } + + /** + * Validates an object against the constraints defined for its class. + * + * If no metadata is available for the class, but the class is an instance + * of {@link \Traversable} and the selected traversal strategy allows + * traversal, the object will be iterated and each nested object will be + * validated instead. + * + * @throws NoSuchMetadataException If the object has no associated metadata + * and does not implement {@link \Traversable} + * or if traversal is disabled via the + * $traversalStrategy argument + * @throws UnsupportedMetadataException If the metadata returned by the + * metadata factory does not implement + * {@link ClassMetadataInterface} + */ + private function validateObject(object $object, string $propertyPath, array $groups, int $traversalStrategy, ExecutionContextInterface $context) + { + try { + $classMetadata = $this->metadataFactory->getMetadataFor($object); + + if (!$classMetadata instanceof ClassMetadataInterface) { + throw new UnsupportedMetadataException(sprintf('The metadata factory should return instances of "Symfony\Component\Validator\Mapping\ClassMetadataInterface", got: "%s".', get_debug_type($classMetadata))); + } + + $this->validateClassNode( + $object, + $this->generateCacheKey($object), + $classMetadata, + $propertyPath, + $groups, + null, + $traversalStrategy, + $context + ); + } catch (NoSuchMetadataException $e) { + // Rethrow if not Traversable + if (!$object instanceof \Traversable) { + throw $e; + } + + // Rethrow unless IMPLICIT or TRAVERSE + if (!($traversalStrategy & (TraversalStrategy::IMPLICIT | TraversalStrategy::TRAVERSE))) { + throw $e; + } + + $this->validateEachObjectIn( + $object, + $propertyPath, + $groups, + $context + ); + } + } + + /** + * Validates each object in a collection against the constraints defined + * for their classes. + * + * Nested arrays are also iterated. + */ + private function validateEachObjectIn(iterable $collection, string $propertyPath, array $groups, ExecutionContextInterface $context) + { + foreach ($collection as $key => $value) { + if (\is_array($value)) { + // Also traverse nested arrays + $this->validateEachObjectIn( + $value, + $propertyPath.'['.$key.']', + $groups, + $context + ); + + continue; + } + + // Scalar and null values in the collection are ignored + if (\is_object($value)) { + $this->validateObject( + $value, + $propertyPath.'['.$key.']', + $groups, + TraversalStrategy::IMPLICIT, + $context + ); + } + } + } + + /** + * Validates a class node. + * + * A class node is a combination of an object with a {@link ClassMetadataInterface} + * instance. Each class node (conceptually) has zero or more succeeding + * property nodes: + * + * (Article:class node) + * \ + * ($title:property node) + * + * This method validates the passed objects against all constraints defined + * at class level. It furthermore triggers the validation of each of the + * class' properties against the constraints for that property. + * + * If the selected traversal strategy allows traversal, the object is + * iterated and each nested object is validated against its own constraints. + * The object is not traversed if traversal is disabled in the class + * metadata. + * + * If the passed groups contain the group "Default", the validator will + * check whether the "Default" group has been replaced by a group sequence + * in the class metadata. If this is the case, the group sequence is + * validated instead. + * + * @throws UnsupportedMetadataException If a property metadata does not + * implement {@link PropertyMetadataInterface} + * @throws ConstraintDefinitionException If traversal was enabled but the + * object does not implement + * {@link \Traversable} + * + * @see TraversalStrategy + */ + private function validateClassNode(object $object, ?string $cacheKey, ClassMetadataInterface $metadata, string $propertyPath, array $groups, ?array $cascadedGroups, int $traversalStrategy, ExecutionContextInterface $context) + { + $context->setNode($object, $object, $metadata, $propertyPath); + + if (!$context->isObjectInitialized($cacheKey)) { + foreach ($this->objectInitializers as $initializer) { + $initializer->initialize($object); + } + + $context->markObjectAsInitialized($cacheKey); + } + + foreach ($groups as $key => $group) { + // If the "Default" group is replaced by a group sequence, remember + // to cascade the "Default" group when traversing the group + // sequence + $defaultOverridden = false; + + // Use the object hash for group sequences + $groupHash = \is_object($group) ? $this->generateCacheKey($group, true) : $group; + + if ($context->isGroupValidated($cacheKey, $groupHash)) { + // Skip this group when validating the properties and when + // traversing the object + unset($groups[$key]); + + continue; + } + + $context->markGroupAsValidated($cacheKey, $groupHash); + + // Replace the "Default" group by the group sequence defined + // for the class, if applicable. + // This is done after checking the cache, so that + // spl_object_hash() isn't called for this sequence and + // "Default" is used instead in the cache. This is useful + // if the getters below return different group sequences in + // every call. + if (Constraint::DEFAULT_GROUP === $group) { + if ($metadata->hasGroupSequence()) { + // The group sequence is statically defined for the class + $group = $metadata->getGroupSequence(); + $defaultOverridden = true; + } elseif ($metadata->isGroupSequenceProvider()) { + // The group sequence is dynamically obtained from the validated + // object + /* @var \Symfony\Component\Validator\GroupSequenceProviderInterface $object */ + $group = $object->getGroupSequence(); + $defaultOverridden = true; + + if (!$group instanceof GroupSequence) { + $group = new GroupSequence($group); + } + } + } + + // If the groups (=[,G3,G4]) contain a group sequence + // (=), then call validateClassNode() with each entry of the + // group sequence and abort if necessary (G1, G2) + if ($group instanceof GroupSequence) { + $this->stepThroughGroupSequence( + $object, + $object, + $cacheKey, + $metadata, + $propertyPath, + $traversalStrategy, + $group, + $defaultOverridden ? Constraint::DEFAULT_GROUP : null, + $context + ); + + // Skip the group sequence when validating properties, because + // stepThroughGroupSequence() already validates the properties + unset($groups[$key]); + + continue; + } + + $this->validateInGroup($object, $cacheKey, $metadata, $group, $context); + } + + // If no more groups should be validated for the property nodes, + // we can safely quit + if (0 === \count($groups)) { + return; + } + + // Validate all properties against their constraints + foreach ($metadata->getConstrainedProperties() as $propertyName) { + // If constraints are defined both on the getter of a property as + // well as on the property itself, then getPropertyMetadata() + // returns two metadata objects, not just one + foreach ($metadata->getPropertyMetadata($propertyName) as $propertyMetadata) { + if (!$propertyMetadata instanceof PropertyMetadataInterface) { + throw new UnsupportedMetadataException(sprintf('The property metadata instances should implement "Symfony\Component\Validator\Mapping\PropertyMetadataInterface", got: "%s".', get_debug_type($propertyMetadata))); + } + + if ($propertyMetadata instanceof GetterMetadata) { + $propertyValue = new LazyProperty(static function () use ($propertyMetadata, $object) { + return $propertyMetadata->getPropertyValue($object); + }); + } else { + $propertyValue = $propertyMetadata->getPropertyValue($object); + } + + $this->validateGenericNode( + $propertyValue, + $object, + $cacheKey.':'.\get_class($object).':'.$propertyName, + $propertyMetadata, + PropertyPath::append($propertyPath, $propertyName), + $groups, + $cascadedGroups, + TraversalStrategy::IMPLICIT, + $context + ); + } + } + + // If no specific traversal strategy was requested when this method + // was called, use the traversal strategy of the class' metadata + if ($traversalStrategy & TraversalStrategy::IMPLICIT) { + $traversalStrategy = $metadata->getTraversalStrategy(); + } + + // Traverse only if IMPLICIT or TRAVERSE + if (!($traversalStrategy & (TraversalStrategy::IMPLICIT | TraversalStrategy::TRAVERSE))) { + return; + } + + // If IMPLICIT, stop unless we deal with a Traversable + if ($traversalStrategy & TraversalStrategy::IMPLICIT && !$object instanceof \Traversable) { + return; + } + + // If TRAVERSE, fail if we have no Traversable + if (!$object instanceof \Traversable) { + throw new ConstraintDefinitionException(sprintf('Traversal was enabled for "%s", but this class does not implement "\Traversable".', get_debug_type($object))); + } + + $this->validateEachObjectIn( + $object, + $propertyPath, + $groups, + $context + ); + } + + /** + * Validates a node that is not a class node. + * + * Currently, two such node types exist: + * + * - property nodes, which consist of the value of an object's + * property together with a {@link PropertyMetadataInterface} instance + * - generic nodes, which consist of a value and some arbitrary + * constraints defined in a {@link MetadataInterface} container + * + * In both cases, the value is validated against all constraints defined + * in the passed metadata object. Then, if the value is an instance of + * {@link \Traversable} and the selected traversal strategy permits it, + * the value is traversed and each nested object validated against its own + * constraints. If the value is an array, it is traversed regardless of + * the given strategy. + * + * @see TraversalStrategy + */ + private function validateGenericNode($value, ?object $object, ?string $cacheKey, ?MetadataInterface $metadata, string $propertyPath, array $groups, ?array $cascadedGroups, int $traversalStrategy, ExecutionContextInterface $context) + { + $context->setNode($value, $object, $metadata, $propertyPath); + + foreach ($groups as $key => $group) { + if ($group instanceof GroupSequence) { + $this->stepThroughGroupSequence( + $value, + $object, + $cacheKey, + $metadata, + $propertyPath, + $traversalStrategy, + $group, + null, + $context + ); + + // Skip the group sequence when cascading, as the cascading + // logic is already done in stepThroughGroupSequence() + unset($groups[$key]); + + continue; + } + + $this->validateInGroup($value, $cacheKey, $metadata, $group, $context); + } + + if (0 === \count($groups)) { + return; + } + + if (null === $value) { + return; + } + + $cascadingStrategy = $metadata->getCascadingStrategy(); + + // Quit unless we cascade + if (!($cascadingStrategy & CascadingStrategy::CASCADE)) { + return; + } + + // If no specific traversal strategy was requested when this method + // was called, use the traversal strategy of the node's metadata + if ($traversalStrategy & TraversalStrategy::IMPLICIT) { + $traversalStrategy = $metadata->getTraversalStrategy(); + } + + // The $cascadedGroups property is set, if the "Default" group is + // overridden by a group sequence + // See validateClassNode() + $cascadedGroups = null !== $cascadedGroups && \count($cascadedGroups) > 0 ? $cascadedGroups : $groups; + + if ($value instanceof LazyProperty) { + $value = $value->getPropertyValue(); + + if (null === $value) { + return; + } + } + + if (\is_array($value)) { + // Arrays are always traversed, independent of the specified + // traversal strategy + $this->validateEachObjectIn( + $value, + $propertyPath, + $cascadedGroups, + $context + ); + + return; + } + + if (!\is_object($value)) { + throw new NoSuchMetadataException(sprintf('Cannot create metadata for non-objects. Got: "%s".', \gettype($value))); + } + + $this->validateObject( + $value, + $propertyPath, + $cascadedGroups, + $traversalStrategy, + $context + ); + + // Currently, the traversal strategy can only be TRAVERSE for a + // generic node if the cascading strategy is CASCADE. Thus, traversable + // objects will always be handled within validateObject() and there's + // nothing more to do here. + + // see GenericMetadata::addConstraint() + } + + /** + * Sequentially validates a node's value in each group of a group sequence. + * + * If any of the constraints generates a violation, subsequent groups in the + * group sequence are skipped. + */ + private function stepThroughGroupSequence($value, ?object $object, ?string $cacheKey, ?MetadataInterface $metadata, string $propertyPath, int $traversalStrategy, GroupSequence $groupSequence, ?string $cascadedGroup, ExecutionContextInterface $context) + { + $violationCount = \count($context->getViolations()); + $cascadedGroups = $cascadedGroup ? [$cascadedGroup] : null; + + foreach ($groupSequence->groups as $groupInSequence) { + $groups = (array) $groupInSequence; + + if ($metadata instanceof ClassMetadataInterface) { + $this->validateClassNode( + $value, + $cacheKey, + $metadata, + $propertyPath, + $groups, + $cascadedGroups, + $traversalStrategy, + $context + ); + } else { + $this->validateGenericNode( + $value, + $object, + $cacheKey, + $metadata, + $propertyPath, + $groups, + $cascadedGroups, + $traversalStrategy, + $context + ); + } + + // Abort sequence validation if a violation was generated + if (\count($context->getViolations()) > $violationCount) { + break; + } + } + } + + /** + * Validates a node's value against all constraints in the given group. + * + * @param mixed $value The validated value + */ + private function validateInGroup($value, ?string $cacheKey, MetadataInterface $metadata, string $group, ExecutionContextInterface $context) + { + $context->setGroup($group); + + foreach ($metadata->findConstraints($group) as $constraint) { + if ($constraint instanceof Existence) { + continue; + } + + // Prevent duplicate validation of constraints, in the case + // that constraints belong to multiple validated groups + if (null !== $cacheKey) { + $constraintHash = $this->generateCacheKey($constraint, true); + // instanceof Valid: In case of using a Valid constraint with many groups + // it makes a reference object get validated by each group + if ($constraint instanceof Composite || $constraint instanceof Valid) { + $constraintHash .= $group; + } + + if ($context->isConstraintValidated($cacheKey, $constraintHash)) { + continue; + } + + $context->markConstraintAsValidated($cacheKey, $constraintHash); + } + + $context->setConstraint($constraint); + + $validator = $this->validatorFactory->getInstance($constraint); + $validator->initialize($context); + + if ($value instanceof LazyProperty) { + $value = $value->getPropertyValue(); + } + + try { + $validator->validate($value, $constraint); + } catch (UnexpectedValueException $e) { + $context->buildViolation('This value should be of type {{ type }}.') + ->setParameter('{{ type }}', $e->getExpectedType()) + ->addViolation(); + } + } + } + + private function generateCacheKey(object $object, bool $dependsOnPropertyPath = false): string + { + if ($this->context instanceof ExecutionContext) { + $cacheKey = $this->context->generateCacheKey($object); + } else { + $cacheKey = spl_object_hash($object); + } + + if ($dependsOnPropertyPath) { + $cacheKey .= $this->context->getPropertyPath(); + } + + return $cacheKey; + } +} diff --git a/src/vendor/symfony/validator/Validator/RecursiveValidator.php b/src/vendor/symfony/validator/Validator/RecursiveValidator.php new file mode 100644 index 0000000..d57c908 --- /dev/null +++ b/src/vendor/symfony/validator/Validator/RecursiveValidator.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Validator; + +use Symfony\Component\Validator\ConstraintValidatorFactoryInterface; +use Symfony\Component\Validator\Context\ExecutionContextFactoryInterface; +use Symfony\Component\Validator\Context\ExecutionContextInterface; +use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface; +use Symfony\Component\Validator\ObjectInitializerInterface; + +/** + * Recursive implementation of {@link ValidatorInterface}. + * + * @author Bernhard Schussek + */ +class RecursiveValidator implements ValidatorInterface +{ + protected $contextFactory; + protected $metadataFactory; + protected $validatorFactory; + protected $objectInitializers; + + /** + * Creates a new validator. + * + * @param ObjectInitializerInterface[] $objectInitializers The object initializers + */ + public function __construct(ExecutionContextFactoryInterface $contextFactory, MetadataFactoryInterface $metadataFactory, ConstraintValidatorFactoryInterface $validatorFactory, array $objectInitializers = []) + { + $this->contextFactory = $contextFactory; + $this->metadataFactory = $metadataFactory; + $this->validatorFactory = $validatorFactory; + $this->objectInitializers = $objectInitializers; + } + + /** + * {@inheritdoc} + */ + public function startContext($root = null) + { + return new RecursiveContextualValidator( + $this->contextFactory->createContext($this, $root), + $this->metadataFactory, + $this->validatorFactory, + $this->objectInitializers + ); + } + + /** + * {@inheritdoc} + */ + public function inContext(ExecutionContextInterface $context) + { + return new RecursiveContextualValidator( + $context, + $this->metadataFactory, + $this->validatorFactory, + $this->objectInitializers + ); + } + + /** + * {@inheritdoc} + */ + public function getMetadataFor($object) + { + return $this->metadataFactory->getMetadataFor($object); + } + + /** + * {@inheritdoc} + */ + public function hasMetadataFor($object) + { + return $this->metadataFactory->hasMetadataFor($object); + } + + /** + * {@inheritdoc} + */ + public function validate($value, $constraints = null, $groups = null) + { + return $this->startContext($value) + ->validate($value, $constraints, $groups) + ->getViolations(); + } + + /** + * {@inheritdoc} + */ + public function validateProperty(object $object, string $propertyName, $groups = null) + { + return $this->startContext($object) + ->validateProperty($object, $propertyName, $groups) + ->getViolations(); + } + + /** + * {@inheritdoc} + */ + public function validatePropertyValue($objectOrClass, string $propertyName, $value, $groups = null) + { + // If a class name is passed, take $value as root + return $this->startContext(\is_object($objectOrClass) ? $objectOrClass : $value) + ->validatePropertyValue($objectOrClass, $propertyName, $value, $groups) + ->getViolations(); + } +} diff --git a/src/vendor/symfony/validator/Validator/TraceableValidator.php b/src/vendor/symfony/validator/Validator/TraceableValidator.php new file mode 100644 index 0000000..8c1d966 --- /dev/null +++ b/src/vendor/symfony/validator/Validator/TraceableValidator.php @@ -0,0 +1,136 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Validator; + +use Symfony\Component\Validator\Context\ExecutionContextInterface; +use Symfony\Contracts\Service\ResetInterface; + +/** + * Collects some data about validator calls. + * + * @author Maxime Steinhausser + */ +class TraceableValidator implements ValidatorInterface, ResetInterface +{ + private $validator; + private $collectedData = []; + + public function __construct(ValidatorInterface $validator) + { + $this->validator = $validator; + } + + /** + * @return array + */ + public function getCollectedData() + { + return $this->collectedData; + } + + public function reset() + { + $this->collectedData = []; + } + + /** + * {@inheritdoc} + */ + public function getMetadataFor($value) + { + return $this->validator->getMetadataFor($value); + } + + /** + * {@inheritdoc} + */ + public function hasMetadataFor($value) + { + return $this->validator->hasMetadataFor($value); + } + + /** + * {@inheritdoc} + */ + public function validate($value, $constraints = null, $groups = null) + { + $violations = $this->validator->validate($value, $constraints, $groups); + + $trace = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 7); + + $file = $trace[0]['file']; + $line = $trace[0]['line']; + + for ($i = 1; $i < 7; ++$i) { + if (isset($trace[$i]['class'], $trace[$i]['function']) + && 'validate' === $trace[$i]['function'] + && is_a($trace[$i]['class'], ValidatorInterface::class, true) + ) { + $file = $trace[$i]['file']; + $line = $trace[$i]['line']; + + while (++$i < 7) { + if (isset($trace[$i]['function'], $trace[$i]['file']) && empty($trace[$i]['class']) && !str_starts_with($trace[$i]['function'], 'call_user_func')) { + $file = $trace[$i]['file']; + $line = $trace[$i]['line']; + + break; + } + } + break; + } + } + + $name = str_replace('\\', '/', $file); + $name = substr($name, strrpos($name, '/') + 1); + + $this->collectedData[] = [ + 'caller' => compact('name', 'file', 'line'), + 'context' => compact('value', 'constraints', 'groups'), + 'violations' => iterator_to_array($violations), + ]; + + return $violations; + } + + /** + * {@inheritdoc} + */ + public function validateProperty(object $object, string $propertyName, $groups = null) + { + return $this->validator->validateProperty($object, $propertyName, $groups); + } + + /** + * {@inheritdoc} + */ + public function validatePropertyValue($objectOrClass, string $propertyName, $value, $groups = null) + { + return $this->validator->validatePropertyValue($objectOrClass, $propertyName, $value, $groups); + } + + /** + * {@inheritdoc} + */ + public function startContext() + { + return $this->validator->startContext(); + } + + /** + * {@inheritdoc} + */ + public function inContext(ExecutionContextInterface $context) + { + return $this->validator->inContext($context); + } +} diff --git a/src/vendor/symfony/validator/Validator/ValidatorInterface.php b/src/vendor/symfony/validator/Validator/ValidatorInterface.php new file mode 100644 index 0000000..e6aa7d7 --- /dev/null +++ b/src/vendor/symfony/validator/Validator/ValidatorInterface.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Validator; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Constraints\GroupSequence; +use Symfony\Component\Validator\ConstraintViolationListInterface; +use Symfony\Component\Validator\Context\ExecutionContextInterface; +use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface; + +/** + * Validates PHP values against constraints. + * + * @author Bernhard Schussek + */ +interface ValidatorInterface extends MetadataFactoryInterface +{ + /** + * Validates a value against a constraint or a list of constraints. + * + * If no constraint is passed, the constraint + * {@link \Symfony\Component\Validator\Constraints\Valid} is assumed. + * + * @param mixed $value The value to validate + * @param Constraint|Constraint[] $constraints The constraint(s) to validate against + * @param string|GroupSequence|array|null $groups The validation groups to validate. If none is given, "Default" is assumed + * + * @return ConstraintViolationListInterface A list of constraint violations + * If the list is empty, validation + * succeeded + */ + public function validate($value, $constraints = null, $groups = null); + + /** + * Validates a property of an object against the constraints specified + * for this property. + * + * @param string $propertyName The name of the validated property + * @param string|GroupSequence|array|null $groups The validation groups to validate. If none is given, "Default" is assumed + * + * @return ConstraintViolationListInterface A list of constraint violations + * If the list is empty, validation + * succeeded + */ + public function validateProperty(object $object, string $propertyName, $groups = null); + + /** + * Validates a value against the constraints specified for an object's + * property. + * + * @param object|string $objectOrClass The object or its class name + * @param string $propertyName The name of the property + * @param mixed $value The value to validate against the property's constraints + * @param string|GroupSequence|array|null $groups The validation groups to validate. If none is given, "Default" is assumed + * + * @return ConstraintViolationListInterface A list of constraint violations + * If the list is empty, validation + * succeeded + */ + public function validatePropertyValue($objectOrClass, string $propertyName, $value, $groups = null); + + /** + * Starts a new validation context and returns a validator for that context. + * + * The returned validator collects all violations generated within its + * context. You can access these violations with the + * {@link ContextualValidatorInterface::getViolations()} method. + * + * @return ContextualValidatorInterface + */ + public function startContext(); + + /** + * Returns a validator in the given execution context. + * + * The returned validator adds all generated violations to the given + * context. + * + * @return ContextualValidatorInterface + */ + public function inContext(ExecutionContextInterface $context); +} diff --git a/src/vendor/symfony/validator/ValidatorBuilder.php b/src/vendor/symfony/validator/ValidatorBuilder.php new file mode 100644 index 0000000..8df048b --- /dev/null +++ b/src/vendor/symfony/validator/ValidatorBuilder.php @@ -0,0 +1,444 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator; + +use Doctrine\Common\Annotations\AnnotationReader; +use Doctrine\Common\Annotations\CachedReader; +use Doctrine\Common\Annotations\PsrCachedReader; +use Doctrine\Common\Annotations\Reader; +use Doctrine\Common\Cache\ArrayCache; +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\Adapter\ArrayAdapter; +use Symfony\Component\Validator\Context\ExecutionContextFactory; +use Symfony\Component\Validator\Exception\LogicException; +use Symfony\Component\Validator\Exception\ValidatorException; +use Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory; +use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface; +use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; +use Symfony\Component\Validator\Mapping\Loader\LoaderChain; +use Symfony\Component\Validator\Mapping\Loader\LoaderInterface; +use Symfony\Component\Validator\Mapping\Loader\StaticMethodLoader; +use Symfony\Component\Validator\Mapping\Loader\XmlFileLoader; +use Symfony\Component\Validator\Mapping\Loader\YamlFileLoader; +use Symfony\Component\Validator\Validator\RecursiveValidator; +use Symfony\Component\Validator\Validator\ValidatorInterface; +use Symfony\Contracts\Translation\LocaleAwareInterface; +use Symfony\Contracts\Translation\TranslatorInterface; +use Symfony\Contracts\Translation\TranslatorTrait; + +// Help opcache.preload discover always-needed symbols +class_exists(TranslatorInterface::class); +class_exists(LocaleAwareInterface::class); +class_exists(TranslatorTrait::class); + +/** + * @author Bernhard Schussek + */ +class ValidatorBuilder +{ + private $initializers = []; + private $loaders = []; + private $xmlMappings = []; + private $yamlMappings = []; + private $methodMappings = []; + + /** + * @var Reader|null + */ + private $annotationReader; + private $enableAnnotationMapping = false; + + /** + * @var MetadataFactoryInterface|null + */ + private $metadataFactory; + + /** + * @var ConstraintValidatorFactoryInterface|null + */ + private $validatorFactory; + + /** + * @var CacheItemPoolInterface|null + */ + private $mappingCache; + + /** + * @var TranslatorInterface|null + */ + private $translator; + + /** + * @var string|null + */ + private $translationDomain; + + /** + * Adds an object initializer to the validator. + * + * @return $this + */ + public function addObjectInitializer(ObjectInitializerInterface $initializer) + { + $this->initializers[] = $initializer; + + return $this; + } + + /** + * Adds a list of object initializers to the validator. + * + * @param ObjectInitializerInterface[] $initializers + * + * @return $this + */ + public function addObjectInitializers(array $initializers) + { + $this->initializers = array_merge($this->initializers, $initializers); + + return $this; + } + + /** + * Adds an XML constraint mapping file to the validator. + * + * @return $this + */ + public function addXmlMapping(string $path) + { + if (null !== $this->metadataFactory) { + throw new ValidatorException('You cannot add custom mappings after setting a custom metadata factory. Configure your metadata factory instead.'); + } + + $this->xmlMappings[] = $path; + + return $this; + } + + /** + * Adds a list of XML constraint mapping files to the validator. + * + * @param string[] $paths The paths to the mapping files + * + * @return $this + */ + public function addXmlMappings(array $paths) + { + if (null !== $this->metadataFactory) { + throw new ValidatorException('You cannot add custom mappings after setting a custom metadata factory. Configure your metadata factory instead.'); + } + + $this->xmlMappings = array_merge($this->xmlMappings, $paths); + + return $this; + } + + /** + * Adds a YAML constraint mapping file to the validator. + * + * @param string $path The path to the mapping file + * + * @return $this + */ + public function addYamlMapping(string $path) + { + if (null !== $this->metadataFactory) { + throw new ValidatorException('You cannot add custom mappings after setting a custom metadata factory. Configure your metadata factory instead.'); + } + + $this->yamlMappings[] = $path; + + return $this; + } + + /** + * Adds a list of YAML constraint mappings file to the validator. + * + * @param string[] $paths The paths to the mapping files + * + * @return $this + */ + public function addYamlMappings(array $paths) + { + if (null !== $this->metadataFactory) { + throw new ValidatorException('You cannot add custom mappings after setting a custom metadata factory. Configure your metadata factory instead.'); + } + + $this->yamlMappings = array_merge($this->yamlMappings, $paths); + + return $this; + } + + /** + * Enables constraint mapping using the given static method. + * + * @return $this + */ + public function addMethodMapping(string $methodName) + { + if (null !== $this->metadataFactory) { + throw new ValidatorException('You cannot add custom mappings after setting a custom metadata factory. Configure your metadata factory instead.'); + } + + $this->methodMappings[] = $methodName; + + return $this; + } + + /** + * Enables constraint mapping using the given static methods. + * + * @param string[] $methodNames The names of the methods + * + * @return $this + */ + public function addMethodMappings(array $methodNames) + { + if (null !== $this->metadataFactory) { + throw new ValidatorException('You cannot add custom mappings after setting a custom metadata factory. Configure your metadata factory instead.'); + } + + $this->methodMappings = array_merge($this->methodMappings, $methodNames); + + return $this; + } + + /** + * Enables annotation based constraint mapping. + * + * @param bool $skipDoctrineAnnotations + * + * @return $this + */ + public function enableAnnotationMapping(/* bool $skipDoctrineAnnotations = true */) + { + if (null !== $this->metadataFactory) { + throw new ValidatorException('You cannot enable annotation mapping after setting a custom metadata factory. Configure your metadata factory instead.'); + } + + $skipDoctrineAnnotations = 1 > \func_num_args() ? false : func_get_arg(0); + if (false === $skipDoctrineAnnotations || null === $skipDoctrineAnnotations) { + trigger_deprecation('symfony/validator', '5.2', 'Not passing true as first argument to "%s" is deprecated. Pass true and call "addDefaultDoctrineAnnotationReader()" if you want to enable annotation mapping with Doctrine Annotations.', __METHOD__); + $this->addDefaultDoctrineAnnotationReader(); + } elseif ($skipDoctrineAnnotations instanceof Reader) { + trigger_deprecation('symfony/validator', '5.2', 'Passing an instance of "%s" as first argument to "%s" is deprecated. Pass true instead and call setDoctrineAnnotationReader() if you want to enable annotation mapping with Doctrine Annotations.', get_debug_type($skipDoctrineAnnotations), __METHOD__); + $this->setDoctrineAnnotationReader($skipDoctrineAnnotations); + } elseif (true !== $skipDoctrineAnnotations) { + throw new \TypeError(sprintf('"%s": Argument 1 is expected to be a boolean, "%s" given.', __METHOD__, get_debug_type($skipDoctrineAnnotations))); + } + + $this->enableAnnotationMapping = true; + + return $this; + } + + /** + * Disables annotation based constraint mapping. + * + * @return $this + */ + public function disableAnnotationMapping() + { + $this->enableAnnotationMapping = false; + $this->annotationReader = null; + + return $this; + } + + /** + * @return $this + */ + public function setDoctrineAnnotationReader(?Reader $reader): self + { + $this->annotationReader = $reader; + + return $this; + } + + /** + * @return $this + */ + public function addDefaultDoctrineAnnotationReader(): self + { + $this->annotationReader = $this->createAnnotationReader(); + + return $this; + } + + /** + * Sets the class metadata factory used by the validator. + * + * @return $this + */ + public function setMetadataFactory(MetadataFactoryInterface $metadataFactory) + { + if (\count($this->xmlMappings) > 0 || \count($this->yamlMappings) > 0 || \count($this->methodMappings) > 0 || $this->enableAnnotationMapping) { + throw new ValidatorException('You cannot set a custom metadata factory after adding custom mappings. You should do either of both.'); + } + + $this->metadataFactory = $metadataFactory; + + return $this; + } + + /** + * Sets the cache for caching class metadata. + * + * @return $this + */ + public function setMappingCache(CacheItemPoolInterface $cache) + { + if (null !== $this->metadataFactory) { + throw new ValidatorException('You cannot set a custom mapping cache after setting a custom metadata factory. Configure your metadata factory instead.'); + } + + $this->mappingCache = $cache; + + return $this; + } + + /** + * Sets the constraint validator factory used by the validator. + * + * @return $this + */ + public function setConstraintValidatorFactory(ConstraintValidatorFactoryInterface $validatorFactory) + { + $this->validatorFactory = $validatorFactory; + + return $this; + } + + /** + * Sets the translator used for translating violation messages. + * + * @return $this + */ + public function setTranslator(TranslatorInterface $translator) + { + $this->translator = $translator; + + return $this; + } + + /** + * Sets the default translation domain of violation messages. + * + * The same message can have different translations in different domains. + * Pass the domain that is used for violation messages by default to this + * method. + * + * @return $this + */ + public function setTranslationDomain(?string $translationDomain) + { + $this->translationDomain = $translationDomain; + + return $this; + } + + /** + * @return $this + */ + public function addLoader(LoaderInterface $loader) + { + $this->loaders[] = $loader; + + return $this; + } + + /** + * @return LoaderInterface[] + */ + public function getLoaders() + { + $loaders = []; + + foreach ($this->xmlMappings as $xmlMapping) { + $loaders[] = new XmlFileLoader($xmlMapping); + } + + foreach ($this->yamlMappings as $yamlMappings) { + $loaders[] = new YamlFileLoader($yamlMappings); + } + + foreach ($this->methodMappings as $methodName) { + $loaders[] = new StaticMethodLoader($methodName); + } + + if ($this->enableAnnotationMapping) { + $loaders[] = new AnnotationLoader($this->annotationReader); + } + + return array_merge($loaders, $this->loaders); + } + + /** + * Builds and returns a new validator object. + * + * @return ValidatorInterface + */ + public function getValidator() + { + $metadataFactory = $this->metadataFactory; + + if (!$metadataFactory) { + $loaders = $this->getLoaders(); + $loader = null; + + if (\count($loaders) > 1) { + $loader = new LoaderChain($loaders); + } elseif (1 === \count($loaders)) { + $loader = $loaders[0]; + } + + $metadataFactory = new LazyLoadingMetadataFactory($loader, $this->mappingCache); + } + + $validatorFactory = $this->validatorFactory ?? new ConstraintValidatorFactory(); + $translator = $this->translator; + + if (null === $translator) { + $translator = new class() implements TranslatorInterface, LocaleAwareInterface { + use TranslatorTrait; + }; + // Force the locale to be 'en' when no translator is provided rather than relying on the Intl default locale + // This avoids depending on Intl or the stub implementation being available. It also ensures that Symfony + // validation messages are pluralized properly even when the default locale gets changed because they are in + // English. + $translator->setLocale('en'); + } + + $contextFactory = new ExecutionContextFactory($translator, $this->translationDomain); + + return new RecursiveValidator($contextFactory, $metadataFactory, $validatorFactory, $this->initializers); + } + + private function createAnnotationReader(): Reader + { + if (!class_exists(AnnotationReader::class)) { + throw new LogicException('Enabling annotation based constraint mapping requires the packages doctrine/annotations and symfony/cache to be installed.'); + } + + if (class_exists(ArrayAdapter::class)) { + return new PsrCachedReader(new AnnotationReader(), new ArrayAdapter()); + } + + if (class_exists(CachedReader::class) && class_exists(ArrayCache::class)) { + trigger_deprecation('symfony/validator', '5.4', 'Enabling annotation based constraint mapping without having symfony/cache installed is deprecated.'); + + return new CachedReader(new AnnotationReader(), new ArrayCache()); + } + + throw new LogicException('Enabling annotation based constraint mapping requires the packages doctrine/annotations and symfony/cache to be installed.'); + } +} diff --git a/src/vendor/symfony/validator/Violation/ConstraintViolationBuilder.php b/src/vendor/symfony/validator/Violation/ConstraintViolationBuilder.php new file mode 100644 index 0000000..9e198c6 --- /dev/null +++ b/src/vendor/symfony/validator/Violation/ConstraintViolationBuilder.php @@ -0,0 +1,174 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Violation; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintViolation; +use Symfony\Component\Validator\ConstraintViolationList; +use Symfony\Component\Validator\Util\PropertyPath; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * Default implementation of {@link ConstraintViolationBuilderInterface}. + * + * @author Bernhard Schussek + * + * @internal since version 2.5. Code against ConstraintViolationBuilderInterface instead. + */ +class ConstraintViolationBuilder implements ConstraintViolationBuilderInterface +{ + private $violations; + private $message; + private $parameters; + private $root; + private $invalidValue; + private $propertyPath; + private $translator; + private $translationDomain; + private $plural; + private $constraint; + private $code; + + /** + * @var mixed + */ + private $cause; + + /** + * @param string $message The error message as a string or a stringable object + */ + public function __construct(ConstraintViolationList $violations, ?Constraint $constraint, $message, array $parameters, $root, $propertyPath, $invalidValue, TranslatorInterface $translator, $translationDomain = null) + { + $this->violations = $violations; + $this->message = $message; + $this->parameters = $parameters; + $this->root = $root; + $this->propertyPath = $propertyPath; + $this->invalidValue = $invalidValue; + $this->translator = $translator; + $this->translationDomain = $translationDomain; + $this->constraint = $constraint; + } + + /** + * {@inheritdoc} + */ + public function atPath(string $path) + { + $this->propertyPath = PropertyPath::append($this->propertyPath, $path); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setParameter(string $key, string $value) + { + $this->parameters[$key] = $value; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setParameters(array $parameters) + { + $this->parameters = $parameters; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setTranslationDomain(string $translationDomain) + { + $this->translationDomain = $translationDomain; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setInvalidValue($invalidValue) + { + $this->invalidValue = $invalidValue; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setPlural(int $number) + { + $this->plural = $number; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setCode(?string $code) + { + $this->code = $code; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setCause($cause) + { + $this->cause = $cause; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function addViolation() + { + if (null === $this->plural) { + $translatedMessage = $this->translator->trans( + $this->message, + $this->parameters, + $this->translationDomain + ); + } else { + $translatedMessage = $this->translator->trans( + $this->message, + ['%count%' => $this->plural] + $this->parameters, + $this->translationDomain + ); + } + + $this->violations->add(new ConstraintViolation( + $translatedMessage, + $this->message, + $this->parameters, + $this->root, + $this->propertyPath, + $this->invalidValue, + $this->plural, + $this->code, + $this->constraint, + $this->cause + )); + } +} diff --git a/src/vendor/symfony/validator/Violation/ConstraintViolationBuilderInterface.php b/src/vendor/symfony/validator/Violation/ConstraintViolationBuilderInterface.php new file mode 100644 index 0000000..9ac1b8b --- /dev/null +++ b/src/vendor/symfony/validator/Violation/ConstraintViolationBuilderInterface.php @@ -0,0 +1,114 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Violation; + +/** + * Builds {@link \Symfony\Component\Validator\ConstraintViolationInterface} + * objects. + * + * Use the various methods on this interface to configure the built violation. + * Finally, call {@link addViolation()} to add the violation to the current + * execution context. + * + * @author Bernhard Schussek + */ +interface ConstraintViolationBuilderInterface +{ + /** + * Stores the property path at which the violation should be generated. + * + * The passed path will be appended to the current property path of the + * execution context. + * + * @param string $path The property path + * + * @return $this + */ + public function atPath(string $path); + + /** + * Sets a parameter to be inserted into the violation message. + * + * @param string $key The name of the parameter + * @param string $value The value to be inserted in the parameter's place + * + * @return $this + */ + public function setParameter(string $key, string $value); + + /** + * Sets all parameters to be inserted into the violation message. + * + * @param array $parameters An array with the parameter names as keys and + * the values to be inserted in their place as + * values + * + * @return $this + */ + public function setParameters(array $parameters); + + /** + * Sets the translation domain which should be used for translating the + * violation message. + * + * @param string $translationDomain The translation domain + * + * @return $this + * + * @see \Symfony\Contracts\Translation\TranslatorInterface + */ + public function setTranslationDomain(string $translationDomain); + + /** + * Sets the invalid value that caused this violation. + * + * @param mixed $invalidValue The invalid value + * + * @return $this + */ + public function setInvalidValue($invalidValue); + + /** + * Sets the number which determines how the plural form of the violation + * message is chosen when it is translated. + * + * @param int $number The number for determining the plural form + * + * @return $this + * + * @see \Symfony\Contracts\Translation\TranslatorInterface::trans() + */ + public function setPlural(int $number); + + /** + * Sets the violation code. + * + * @param string|null $code The violation code + * + * @return $this + */ + public function setCode(?string $code); + + /** + * Sets the cause of the violation. + * + * @param mixed $cause The cause of the violation + * + * @return $this + */ + public function setCause($cause); + + /** + * Adds the violation to the current execution context. + */ + public function addViolation(); +} diff --git a/src/vendor/symfony/validator/composer.json b/src/vendor/symfony/validator/composer.json new file mode 100644 index 0000000..3e860da --- /dev/null +++ b/src/vendor/symfony/validator/composer.json @@ -0,0 +1,79 @@ +{ + "name": "symfony/validator", + "type": "library", + "description": "Provides tools to validate values", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "~1.0", + "symfony/polyfill-php80": "^1.16", + "symfony/polyfill-php81": "^1.22", + "symfony/translation-contracts": "^1.1|^2|^3" + }, + "require-dev": { + "symfony/console": "^4.4|^5.0|^6.0", + "symfony/finder": "^4.4|^5.0|^6.0", + "symfony/http-client": "^4.4|^5.0|^6.0", + "symfony/http-foundation": "^4.4|^5.0|^6.0", + "symfony/http-kernel": "^4.4|^5.0|^6.0", + "symfony/intl": "^4.4|^5.0|^6.0", + "symfony/yaml": "^4.4|^5.0|^6.0", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/expression-language": "^5.1|^6.0", + "symfony/cache": "^4.4|^5.0|^6.0", + "symfony/mime": "^4.4|^5.0|^6.0", + "symfony/property-access": "^4.4|^5.0|^6.0", + "symfony/property-info": "^5.3|^6.0", + "symfony/translation": "^4.4|^5.0|^6.0", + "doctrine/annotations": "^1.13|^2", + "doctrine/cache": "^1.11|^2.0", + "egulias/email-validator": "^2.1.10|^3|^4" + }, + "conflict": { + "doctrine/annotations": "<1.13", + "doctrine/cache": "<1.11", + "doctrine/lexer": "<1.1", + "symfony/dependency-injection": "<4.4", + "symfony/expression-language": "<5.1", + "symfony/http-kernel": "<4.4", + "symfony/intl": "<4.4", + "symfony/property-info": "<5.3", + "symfony/translation": "<4.4", + "symfony/yaml": "<4.4" + }, + "suggest": { + "psr/cache-implementation": "For using the mapping cache.", + "symfony/http-foundation": "", + "symfony/intl": "", + "symfony/translation": "For translating validation errors.", + "symfony/yaml": "", + "symfony/config": "", + "egulias/email-validator": "Strict (RFC compliant) email validation", + "symfony/property-access": "For accessing properties within comparison constraints", + "symfony/property-info": "To automatically add NotNull and Type constraints", + "symfony/expression-language": "For using the Expression validator and the ExpressionLanguageSyntax constraints" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Validator\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +}