diff --git a/src/core/Directus/Application/CoreServicesProvider.php b/src/core/Directus/Application/CoreServicesProvider.php index 2297463d62..412481bae0 100644 --- a/src/core/Directus/Application/CoreServicesProvider.php +++ b/src/core/Directus/Application/CoreServicesProvider.php @@ -640,18 +640,7 @@ protected function getEmitter() return $payload; }; - $preventNonAdminFromUpdateRoles = function (array $payload) use ($container) { - /** @var Acl $acl */ - $acl = $container->get('acl'); - - if (!$acl->isAdmin()) { - throw new ForbiddenException('You are not allowed to create, update or delete roles'); - } - }; - - $emitter->addAction('item.create.directus_user_roles:before', $preventNonAdminFromUpdateRoles); - $emitter->addAction('item.update.directus_user_roles:before', $preventNonAdminFromUpdateRoles); - $emitter->addAction('item.delete.directus_user_roles:before', $preventNonAdminFromUpdateRoles); + $generateExternalId = function (Payload $payload) { // generate an external id if none is passed if (!$payload->get('external_id')) { diff --git a/src/core/Directus/Database/Schema/Object/Field.php b/src/core/Directus/Database/Schema/Object/Field.php index 05cdce9b99..cae620b9ae 100644 --- a/src/core/Directus/Database/Schema/Object/Field.php +++ b/src/core/Directus/Database/Schema/Object/Field.php @@ -32,6 +32,16 @@ public function getName() return $this->attributes->get('field'); } + /** + * Gets the field name with proper formatisation + * + * @return string + */ + public function getFormatisedName() + { + return ucwords(str_replace("_", " ", $this->attributes->get('field'))); + } + /** * Gets the field type * diff --git a/src/core/Directus/Database/TableGateway/RelationalTableGateway.php b/src/core/Directus/Database/TableGateway/RelationalTableGateway.php index 4b8de56c84..b9043a29db 100644 --- a/src/core/Directus/Database/TableGateway/RelationalTableGateway.php +++ b/src/core/Directus/Database/TableGateway/RelationalTableGateway.php @@ -524,8 +524,8 @@ public function updateRecord($id, array $recordData, array $params = []) $statusField = $tableSchema->getStatusField(); $logEntryAction = ArrayUtils::get($params, 'revert') === true - ? DirectusActivityTableGateway::ACTION_REVERT - : DirectusActivityTableGateway::ACTION_UPDATE; + ? DirectusActivityTableGateway::ACTION_REVERT + : DirectusActivityTableGateway::ACTION_UPDATE; if ($statusField && $logEntryAction === DirectusActivityTableGateway::ACTION_UPDATE) { try { @@ -592,6 +592,13 @@ public function addOrUpdateManyToOneRelationships($schema, $parentRow, &$childLo $primaryKey = $foreignTableSchema->getPrimaryKeyName(); $ForeignTable = new RelationalTableGateway($foreignTableName, $this->adapter, $this->acl); + // If a system table is joined, stop relational update here. + if (strpos($foreignTableName, 'directus_') === 0) { + // Once they're managed, remove the foreign collections from the record array + unset($parentRow[$fieldName]); + continue; + } + if ($primaryKey && ArrayUtils::get($foreignRow, $this->deleteFlag) === true) { $Where = new Where(); $Where->equalTo($primaryKey, $foreignRow[$primaryKey]); @@ -941,7 +948,7 @@ public function createGlobalMetadata($single, array $list = []) */ public function createEntriesMetadata(array $entries, array $list = []) { - $allKeys = ['result_count', 'total_count', 'status']; + $allKeys = ['result_count', 'total_count', 'filter_count', 'status', 'page']; $tableSchema = $this->getTableSchema($this->table); $metadata = []; @@ -969,6 +976,78 @@ public function createEntriesMetadata(array $entries, array $list = []) $metadata['status_count'] = $statusCount; } + if (in_array('filter_count', $list) || in_array('page', $list)) { + $metadata = $this->createMetadataPagination($metadata, $_GET); + } + + return $metadata; + } + + /** + * Updates Metadata Object with Pagination + * + * @param $metadata - Existing metadata object + * @param $params - GET Parameters + * + * @return array + */ + public function createMetadataPagination(array $metadata = [], array $params = []) + { + if (empty($params)) $params = $_GET; + + $filtered = ArrayUtils::get($params, 'filter') || ArrayUtils::get($params, 'q'); + + $limit = intval( ArrayUtils::get($params, 'limit', 0) ); + $page = intval( ArrayUtils::get($params, 'page', 1) ); + $offset = intval( ArrayUtils::get($params, 'offset', -1) ); + + $total = intval(ArrayUtils::get($metadata, 'Published') ?: ArrayUtils::get($metadata, 'total_count')); + $rows = intval(ArrayUtils::get($metadata, 'result_count')); + $pathname = explode('?', ArrayUtils::get($_SERVER, 'REQUEST_URI')); + $url = trim(\Directus\get_url(), '/') . reset($pathname); + + if (!$rows || !$total) return $metadata; + + if ($filtered) { + $filteredparams = array_merge($params, [ + "depth" => 0, + "fields" => $this->primaryKeyFieldName, + "limit" => -1 + ]); + + $entries = $this->fetchItems($filteredparams); + $total = count($entries); + $metadata['filter_count'] = $total; + } + + $limit = $limit < 1 ? $rows : $limit; + $pages = $total ? ceil($total / $limit) : 1; + $page = $page > $pages ? $pages : ( $page && $offset >= 0 ? ( floor($offset / $limit) + 1 ) : $page ); + $offset = $offset >= 0 ? $offset : ($page ? (($page - 1) * $limit) : 0); + $next = $previous = $last = $first = -1; + + if ($pages > 1) { + $next = ($pages > $page) ? ($offset + $limit) : null; + $previous = ($offset >= $limit) ? ($offset - $limit) : ($limit * ( $pages - 1 )); + $first = ($pages < 2 || $limit < 1) ? null : 0; + $last = ($pages < 2) ? null : ( ($pages - 1) * $limit ); + } + + $metadata = array_merge($metadata, [ + "limit" => $limit, + "offset" => $offset, + "page" => $page, + "page_count" => $pages, + "links" => [ + "self" => $url, + "current" => "{$url}?" . urldecode( http_build_query(array_merge($params, ["page" => $page]))), + "next" => $next > 0 && $page < $pages ? ( "{$url}?" . urldecode( http_build_query(array_merge($params, ["offset" => $next, "page" => $page + 1])) ) ) : null, + "previous" => $previous >= 0 && $page > 1 ? ( "{$url}?" . urldecode( http_build_query(array_merge($params, ["offset" => $previous, "page" => $page - 1])) ) ) : null, + "first" => $first >= 0 ? ( "{$url}?" . urldecode( http_build_query(array_merge($params, ["offset" => $first, "page" => 1])) ) ) : null, + "last" => $last > 0 ? ( "{$url}?" . urldecode( http_build_query(array_merge($params, ["offset" => $last, "page" => $pages])) ) ) : null + ] + ]); + return $metadata; } @@ -1020,7 +1099,11 @@ public function fetchItems(array $params = [], \Closure $queryCallback = null) $builder->orderBy($this->primaryKeyFieldName); try { - $this->enforceReadPermission($builder); + $this->enforceReadPermission($builder); + + //If collection is directus_fields, also check permission of actual collection of which fields are retrieving + if($this->getTable() == SchemaManager::COLLECTION_FIELDS && ArrayUtils::has($params['filter'], 'collection')) + $this->acl->enforceReadOnce(ArrayUtils::get($params['filter'], 'collection')); } catch (PermissionException $e) { $isForbiddenRead = $e instanceof ForbiddenCollectionReadException; $isUnableFindItems = $e instanceof UnableFindOwnerItemsException; @@ -1220,7 +1303,7 @@ protected function parseDotFilters(Builder $mainQuery, array $filters) $relational = SchemaService::hasRelationship($nextTable, $nextColumn); $columnsTable[] = $nextTable; } - + // if one of the column in the list has not relationship // it will break the loop before going over all the columns // which we will call this as column not found @@ -1231,7 +1314,7 @@ protected function parseDotFilters(Builder $mainQuery, array $filters) // Remove the original filter column with dot-notation unset($filters[$column]); - + //Prepare relational data for all the fields $columnRelationalData = []; foreach($filterColumns as $filterColumn){ @@ -1247,7 +1330,7 @@ protected function parseDotFilters(Builder $mainQuery, array $filters) ]; } } - + // Reverse all the columns from comments.author.id to id.author.comments // To filter from the most deep relationship to their parents $columns = explode('.', \Directus\column_identifier_reverse($column)); @@ -1264,19 +1347,19 @@ protected function parseDotFilters(Builder $mainQuery, array $filters) $query = new Builder($this->getAdapter()); $mainTableObject = $this->getTableSchema($table); $selectColumn = $mainTableObject->getPrimaryField()->getName(); - + //check if column type is alias and relationship is O2M $previousRelation = isset($filterColumns[array_search($column, $filterColumns)-1])?$filterColumns[array_search($column, $filterColumns)-1]:''; - if ($previousRelation && $columnRelationalData[$previousRelation]['type'] == \Directus\Database\Schema\Object\FieldRelationship::ONE_TO_MANY) { + if ($previousRelation && $columnRelationalData[$previousRelation]['type'] == \Directus\Database\Schema\Object\FieldRelationship::ONE_TO_MANY) { $selectColumn = $columnRelationalData[$previousRelation]['field_many']; } - + //get last relationship if ($mainColumn && !empty($mainColumn) && $columnRelationalData[$mainColumn]['type'] == \Directus\Database\Schema\Object\FieldRelationship::ONE_TO_MANY) { $mainColumn = $mainTableObject->getPrimaryField()->getName(); } $query->columns([$selectColumn]); - + $query->from($table); $this->doFilter($query, $column, $condition, $table); @@ -1446,8 +1529,8 @@ protected function doFilter(Builder $query, $column, $condition, $table) $relatedTable = $relationship->getCollectionMany(); $relatedRightColumn = $relationship->getFieldMany(); $tableSchema = SchemaService::getCollection($relatedTable); - $relatedTableColumns = $tableSchema->getFields(); - + $relatedTableColumns = $tableSchema->getFields(); + $query->orWhereRelational($this->primaryKeyFieldName, $relatedTable, null, $relatedRightColumn, function(Builder $query) use ($column, $relatedTable, $relatedTableColumns, $value) { foreach ($relatedTableColumns as $column) { $isNumeric = $this->getSchemaManager()->isNumericType($column->getType()); @@ -1512,12 +1595,12 @@ protected function processFilter(Builder $query, array $filters = []) if (isset($fieldReadBlackListDetails['isReadBlackList']) && $fieldReadBlackListDetails['isReadBlackList']) { throw new Exception\ForbiddenFieldAccessException($column); }else if(isset($fieldReadBlackListDetails['statuses']) && !empty ($fieldReadBlackListDetails['statuses'])){ - $blackListStatuses = array_merge($blackListStatuses,array_values($fieldReadBlackListDetails['statuses'])); + $blackListStatuses = array_merge($blackListStatuses,array_values($fieldReadBlackListDetails['statuses'])); } } - $filters = $this->parseDotFilters($query, $filters); - - foreach ($filters as $column => $conditions) { + $filters = $this->parseDotFilters($query, $filters); + + foreach ($filters as $column => $conditions) { if ($conditions instanceof Filter) { $column = $conditions->getIdentifier(); $conditions = $conditions->getValue(); diff --git a/src/core/Directus/Permissions/Acl.php b/src/core/Directus/Permissions/Acl.php index 7e7b54d051..a71ed7bbba 100644 --- a/src/core/Directus/Permissions/Acl.php +++ b/src/core/Directus/Permissions/Acl.php @@ -1328,6 +1328,39 @@ public function getStatusesOnReadFieldBlacklist($collection, $field) return $blackListStatuses; } + /** + * Gets the statuses on which field has been write blacklisted + * + * @param string $collection + * @param mixed $status + * + * @return array + */ + public function getStatusesOnWriteFieldBlacklist($collection, $field) + { + $blackListStatuses = []; + $collectionPermission = $this->getCollectionPermissions($collection); + $statuses = $this->getCollectionStatuses($collection); + if($statuses){ + foreach($statuses as $status){ + $writeFieldBlackList = isset($collectionPermission[$status]['write_field_blacklist']) ? $collectionPermission[$status]['write_field_blacklist'] : []; + if($writeFieldBlackList && in_array($field, $writeFieldBlackList)){ + $blackListStatuses['statuses'][] = $status; + } + } + //Set flag for field which is blacklist for all statuses + if(isset($blackListStatuses['statuses']) && count($blackListStatuses['statuses']) == count($statuses)){ + $blackListStatuses['isWriteBlackList'] = true; + } + }else{ + $writeFieldBlackList = isset($collectionPermission['write_field_blacklist']) ? $collectionPermission['write_field_blacklist'] : []; + if($writeFieldBlackList && in_array($field, $writeFieldBlackList)){ + $blackListStatuses['isWriteBlackList'] = true; + } + } + return $blackListStatuses; + } + /** * Returns a list of status the given collection has permission to read * diff --git a/src/core/Directus/Services/AbstractService.php b/src/core/Directus/Services/AbstractService.php index 315a04a21b..c1c2a8e2e0 100644 --- a/src/core/Directus/Services/AbstractService.php +++ b/src/core/Directus/Services/AbstractService.php @@ -446,6 +446,8 @@ protected function validatePayloadWithFieldsValidation($collectionName, array $p continue; } + $this->validateFieldLength($field, $value); + if ($validation = $field->getValidation()) { $isCustomValidation = \Directus\is_custom_validation($validation); @@ -470,6 +472,39 @@ protected function validatePayloadWithFieldsValidation($collectionName, array $p $this->throwErrorIfAny($violations); } + /** + * To validate the length of input with the DB. + * + * @param array $field + * @param string $value + * + * @throws UnprocessableEntityException + */ + protected function validateFieldLength($field, $value) + { + if($field->getType() == "decimal"){ + $precision = $field->getPrecision(); + $scale = $field->getScale(); + $number = $precision - $scale; + $input = explode(".",$value); + $inputLengthScale = isset($input[1]) ? strlen($input[1]) : 0; + $inputLengthNumber = isset($input[0]) ? strlen($input[0]) : 0; + $inputLengthPrecision = $inputLengthScale+$inputLengthNumber; + + if($inputLengthNumber > $number || $inputLengthScale > $scale){ + throw new UnprocessableEntityException( + sprintf("The value submitted (%s) for '%s' is longer than the field's supported length (%s). Please submit a shorter value or ask an Admin to increase the length.",$value,$field->getFormatisedName(),$field['length']) + ); + } + }else{ + if(!is_null($field['length']) && $field['length'] < strlen($value) ){ + throw new UnprocessableEntityException( + sprintf("The value submitted (%s) for '%s' is longer than the field's supported length (%s). Please submit a shorter value or ask an Admin to increase the length.",$value,$field->getFormatisedName(),$field['length']) + ); + } + } + } + /** * @param string $collection * @param array $payload diff --git a/src/core/Directus/Services/ItemsService.php b/src/core/Directus/Services/ItemsService.php index 506ca35515..fcae596862 100644 --- a/src/core/Directus/Services/ItemsService.php +++ b/src/core/Directus/Services/ItemsService.php @@ -85,22 +85,27 @@ public function find($collection, $id, array $params = []) * @param string $collection * @param mixed $ids * @param array $params + * @params bool $acl default true * * @return array * * @throws ItemNotFoundException * @throws ForbiddenCollectionReadException */ - public function findByIds($collection, $ids, array $params = []) + public function findByIds($collection, $ids, array $params = [], $acl = true) { $params = ArrayUtils::omit($params, static::SINGLE_ITEM_PARAMS_BLACKLIST); $statusValue = $this->getStatusValue($collection, $ids); - $tableGateway = $this->createTableGateway($collection); + $tableGateway = $this->createTableGateway($collection, $acl); $ids = StringUtils::safeCvs($ids, false, false); try { - $this->getAcl()->enforceRead($collection, $statusValue); + // if acl check is disabled (e.g. fetching the logo from the settings endpoint/service) do not + // enforce permissions here! + if (false !== $acl) { + $this->getAcl()->enforceRead($collection, $statusValue); + } } catch (ForbiddenCollectionReadException $e) { if (is_array($ids) && count($ids) > 1) { throw $e; diff --git a/src/core/Directus/Services/SettingsService.php b/src/core/Directus/Services/SettingsService.php index ecda701979..82e420e8e9 100644 --- a/src/core/Directus/Services/SettingsService.php +++ b/src/core/Directus/Services/SettingsService.php @@ -57,7 +57,8 @@ public function findAll(array $params = []) public function findFile($id,array $params = []) { - return $this->itemsService->findByIds(SchemaManager::COLLECTION_FILES, $id,$params); + $noAcl = false; + return $this->itemsService->findByIds(SchemaManager::COLLECTION_FILES, $id,$params, $noAcl); } public function findAllFields(array $params = []) diff --git a/src/core/Directus/Services/TablesService.php b/src/core/Directus/Services/TablesService.php index 7aa340af5e..5aeeb5f74d 100644 --- a/src/core/Directus/Services/TablesService.php +++ b/src/core/Directus/Services/TablesService.php @@ -3,6 +3,7 @@ namespace Directus\Services; use Directus\Application\Container; +use Directus\Database\Exception; use Directus\Database\Exception\CollectionNotManagedException; use Directus\Database\Exception\FieldAlreadyExistsException; use Directus\Database\Exception\FieldLengthNotSupportedException; @@ -76,12 +77,6 @@ public function findAll(array $params = []) public function findAllFieldsByCollection($collectionName, array $params = []) { - if (!$this->getAcl()->isAdmin()) { - throw new UnauthorizedException('Permission denied'); - } - - // $this->tagResponseCache('tableColumnsSchema_'.$tableName); - $this->validate(['collection' => $collectionName], ['collection' => 'required|string']); /** @var SchemaManager $schemaManager */ @@ -98,17 +93,27 @@ public function findAllFieldsByCollection($collectionName, array $params = []) ] ])); - $result['data'] = $this->mergeMissingSchemaFields($collection, $result['data']); + $result = $this->mergeMissingSchemaFields($collection, $result); - return $result; + $data = []; + foreach($result as $resultDetails){ + if($this->getAcl()->isAdmin()){ + $data[] = $resultDetails; + }else{ + $fieldReadBlackListDetails = $this->getAcl()->getStatusesOnReadFieldBlacklist($collectionName,$resultDetails['field']); + if (!(isset($fieldReadBlackListDetails['isReadBlackList']) && $fieldReadBlackListDetails['isReadBlackList'])) { + $data[] = $resultDetails; + } + } + } + + return ['data' => $data]; } public function findAllFields(array $params = []) { - if (!$this->getAcl()->isAdmin()) { - throw new UnauthorizedException('Permission denied'); - } - + $this->getAcl()->enforceReadOnce('directus_fields'); + /** @var SchemaManager $schemaManager */ $schemaManager = $this->container->get('schema_manager'); @@ -119,8 +124,20 @@ public function findAllFields(array $params = []) foreach ($fields as $field) { $data[] = $this->mergeSchemaField($field); } - - return ['data' => $data]; + + $response = []; + foreach($data as $fieldDetails){ + if($this->getAcl()->isAdmin()){ + $response[] = $fieldDetails; + }else{ + $fieldReadBlackListDetails = $this->getAcl()->getStatusesOnReadFieldBlacklist($fieldDetails['collection'],$fieldDetails['field']); + if ($this->getAcl()->canReadOnce($fieldDetails['collection']) && !(isset($fieldReadBlackListDetails['isReadBlackList']) && $fieldReadBlackListDetails['isReadBlackList'])) { + $response[] = $fieldDetails; + } + } + } + + return ['data' => $response]; } public function find($name, array $params = []) @@ -166,8 +183,11 @@ public function findByIds($name, array $params = []) public function findField($collection, $field, array $params = []) { - if (!$this->getAcl()->isAdmin()) { - throw new UnauthorizedException('Permission denied'); + $this->getAcl()->enforceReadOnce('directus_fields'); + $this->getAcl()->enforceReadOnce($collection); + $fieldReadBlackListDetails = $this->getAcl()->getStatusesOnReadFieldBlacklist($collection,$field); + if (isset($fieldReadBlackListDetails['isReadBlackList']) && $fieldReadBlackListDetails['isReadBlackList']) { + throw new Exception\ForbiddenFieldAccessException($field); } $this->validate([ @@ -221,11 +241,14 @@ public function findField($collection, $field, array $params = []) public function findFields($collectionName, array $fieldsName, array $params = []) { - if (!$this->getAcl()->isAdmin()) { - throw new UnauthorizedException('Permission denied'); + $this->getAcl()->enforceReadOnce('directus_fields'); + $this->getAcl()->enforceReadOnce($collectionName); + foreach($fieldsName as $field){ + $fieldReadBlackListDetails = $this->getAcl()->getStatusesOnReadFieldBlacklist($collectionName,$field); + if (isset($fieldReadBlackListDetails['isReadBlackList']) && $fieldReadBlackListDetails['isReadBlackList']) { + throw new Exception\ForbiddenFieldAccessException($field); + } } - - // $this->tagResponseCache('tableColumnsSchema_'.$tableName); $this->validate(['fields' => $fieldsName], ['fields' => 'required|array']); /** @var SchemaManager $schemaManager */ @@ -250,12 +273,12 @@ public function findFields($collectionName, array $fieldsName, array $params = [ public function deleteField($collection, $field, array $params = []) { - if (!$this->getAcl()->isAdmin()) { - throw new UnauthorizedException('Permission denied'); + $this->getAcl()->enforceDelete('directus_fields'); + $this->getAcl()->enforceDelete($collection); + $fieldWriteBlackListDetails = $this->getAcl()->getStatusesOnWriteFieldBlacklist($collection,$field); + if (isset($fieldWriteBlackListDetails['isWriteBlackList']) && $fieldWriteBlackListDetails['isWriteBlackList']) { + throw new Exception\ForbiddenFieldAccessException($field); } - - $this->enforcePermissions('directus_fields', [], $params); - $this->validate([ 'collection' => $collection, 'field' => $field @@ -503,12 +526,9 @@ public function delete($name, array $params = []) */ public function addColumn($collectionName, $columnName, array $data, array $params = []) { - if (!$this->getAcl()->isAdmin()) { - throw new UnauthorizedException('Permission denied'); - } - - $this->enforcePermissions('directus_fields', $data, $params); - + $this->getAcl()->enforceCreate('directus_fields'); + $this->getAcl()->enforceCreate($collectionName); + $data['field'] = $columnName; $data['collection'] = $collectionName; $this->validateFieldPayload($data, null, $params); @@ -567,12 +587,13 @@ public function addColumn($collectionName, $columnName, array $data, array $para */ public function changeColumn($collectionName, $fieldName, array $data, array $params = []) { - if (!$this->getAcl()->isAdmin()) { - throw new UnauthorizedException('Permission denied'); + $this->getAcl()->enforceUpdate('directus_fields'); + $this->getAcl()->enforceUpdate($collectionName); + $fieldWriteBlackListDetails = $this->getAcl()->getStatusesOnWriteFieldBlacklist($collectionName,$fieldName); + if (isset($fieldWriteBlackListDetails['isWriteBlackList']) && $fieldWriteBlackListDetails['isWriteBlackList']) { + throw new Exception\ForbiddenFieldAccessException($fieldName); } - - $this->enforcePermissions('directus_fields', $data, $params); - + $data['field'] = $fieldName; $data['collection'] = $collectionName; $this->validateFieldPayload($data, array_keys($data), $params); diff --git a/src/core/Directus/Services/UsersService.php b/src/core/Directus/Services/UsersService.php index 0a52c1915f..92a6a5605c 100644 --- a/src/core/Directus/Services/UsersService.php +++ b/src/core/Directus/Services/UsersService.php @@ -13,6 +13,7 @@ use Directus\Database\TableGateway\RelationalTableGateway; use Directus\Exception\ForbiddenException; use Directus\Exception\ForbiddenLastAdminException; +use Directus\Permissions\Acl; use Directus\Util\ArrayUtils; use Directus\Util\DateTimeUtils; use Directus\Util\JWTUtils; @@ -217,7 +218,7 @@ protected function sendInvitationTo($email) $datetime = DateTimeUtils::nowInUTC(); $invitationToken = $auth->generateInvitationToken([ 'date' => $datetime->toString(), - 'exp' => $datetime->inDays(30)->toString(), + 'exp' => $datetime->inDays(30)->getTimestamp(), 'email' => $email, 'sender' => $this->getAcl()->getUserId() ]); @@ -260,6 +261,18 @@ public function enableUserWithInvitation($token) throw new InvalidTokenException(); } + // auth middleware doesn't parse this kind of token + // but we know that only admins can send invitations + $this->getAcl()->setUserId($payload->sender); + $this->getAcl()->setPermissions([ + 'directus_users' => [ + [ + Acl::ACTION_READ => Acl::LEVEL_FULL, + Acl::ACTION_UPDATE => Acl::LEVEL_FULL, + ], + ], + ]); + $auth = $this->getAuth(); $auth->validatePayloadOrigin($payload); @@ -267,7 +280,6 @@ public function enableUserWithInvitation($token) try { $result = $this->findOne([ 'filter' => [ - 'id' => $payload->id, 'email' => $payload->email, 'status' => DirectusUsersTableGateway::STATUS_INVITED ] diff --git a/src/endpoints/Users.php b/src/endpoints/Users.php index b8d30c1c5c..a95fa62027 100644 --- a/src/endpoints/Users.php +++ b/src/endpoints/Users.php @@ -25,7 +25,7 @@ public function __invoke(Application $app) $app->post('', [$this, 'create']); $app->get('/{id}', [$this, 'read']); $app->post('/invite', [$this, 'invite']); - $app->post('/invite/{token}', [$this, 'acceptInvitation']); + $app->map(['GET', 'POST'], '/invite/{token}', [$this, 'acceptInvitation']); $app->patch('/{id}', [$this, 'update']); $app->delete('/{id}', [$this, 'delete']); @@ -207,11 +207,9 @@ public function trackPage(Request $request, Response $response) */ public function acceptInvitation(Request $request, Response $response) { - $this->validateRequestPayload($request); - $service = new UsersService($this->container); $responseData = $service->enableUserWithInvitation( - $request->getParsedBodyParam('token') + $request->getAttribute('token') ); return $this->responseWithData($request, $response, $responseData); diff --git a/src/mail/user-invitation.twig b/src/mail/user-invitation.twig index d825d80dbe..bd8d915be4 100644 --- a/src/mail/user-invitation.twig +++ b/src/mail/user-invitation.twig @@ -3,7 +3,7 @@

You have been invited to {{settings.project_name }}. Please click the link below to join:

-{% set invitation_url = settings.project_url|trim('/') ~ '/' ~ api.project ~ '/auth/invitation/' ~ token %} +{% set invitation_url = settings.project_url|trim('/') ~ '/' ~ api.project ~ '/users/invite/' ~ token %}

{{ invitation_url }}

Love,
Directus

diff --git a/vendor/autoload.php b/vendor/autoload.php index 28b3f7f1b9..9cd9ce7512 100644 --- a/vendor/autoload.php +++ b/vendor/autoload.php @@ -4,4 +4,4 @@ require_once __DIR__ . '/composer/autoload_real.php'; -return ComposerAutoloaderInitfb1c448da18426b05d59dd149a19a87b::getLoader(); +return ComposerAutoloaderInitdacd47c943180ea3bff91a23543fc0c1::getLoader(); diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php index 2b4a2af707..ddfd897542 100644 --- a/vendor/composer/autoload_classmap.php +++ b/vendor/composer/autoload_classmap.php @@ -635,6 +635,7 @@ 'Intervention\\Image\\Point' => $vendorDir . '/intervention/image/src/Intervention/Image/Point.php', 'Intervention\\Image\\Response' => $vendorDir . '/intervention/image/src/Intervention/Image/Response.php', 'Intervention\\Image\\Size' => $vendorDir . '/intervention/image/src/Intervention/Image/Size.php', + 'JsonException' => $vendorDir . '/symfony/polyfill-php73/Resources/stubs/JsonException.php', 'League\\Flysystem\\AdapterInterface' => $vendorDir . '/league/flysystem/src/AdapterInterface.php', 'League\\Flysystem\\Adapter\\AbstractAdapter' => $vendorDir . '/league/flysystem/src/Adapter/AbstractAdapter.php', 'League\\Flysystem\\Adapter\\AbstractFtpAdapter' => $vendorDir . '/league/flysystem/src/Adapter/AbstractFtpAdapter.php', @@ -1144,6 +1145,7 @@ 'Symfony\\Component\\Console\\Formatter\\WrappableOutputFormatterInterface' => $vendorDir . '/symfony/console/Formatter/WrappableOutputFormatterInterface.php', 'Symfony\\Component\\Console\\Helper\\DebugFormatterHelper' => $vendorDir . '/symfony/console/Helper/DebugFormatterHelper.php', 'Symfony\\Component\\Console\\Helper\\DescriptorHelper' => $vendorDir . '/symfony/console/Helper/DescriptorHelper.php', + 'Symfony\\Component\\Console\\Helper\\Dumper' => $vendorDir . '/symfony/console/Helper/Dumper.php', 'Symfony\\Component\\Console\\Helper\\FormatterHelper' => $vendorDir . '/symfony/console/Helper/FormatterHelper.php', 'Symfony\\Component\\Console\\Helper\\Helper' => $vendorDir . '/symfony/console/Helper/Helper.php', 'Symfony\\Component\\Console\\Helper\\HelperInterface' => $vendorDir . '/symfony/console/Helper/HelperInterface.php', @@ -1204,6 +1206,7 @@ 'Symfony\\Component\\Translation\\DependencyInjection\\TranslationDumperPass' => $vendorDir . '/symfony/translation/DependencyInjection/TranslationDumperPass.php', 'Symfony\\Component\\Translation\\DependencyInjection\\TranslationExtractorPass' => $vendorDir . '/symfony/translation/DependencyInjection/TranslationExtractorPass.php', 'Symfony\\Component\\Translation\\DependencyInjection\\TranslatorPass' => $vendorDir . '/symfony/translation/DependencyInjection/TranslatorPass.php', + 'Symfony\\Component\\Translation\\DependencyInjection\\TranslatorPathsPass' => $vendorDir . '/symfony/translation/DependencyInjection/TranslatorPathsPass.php', 'Symfony\\Component\\Translation\\Dumper\\CsvFileDumper' => $vendorDir . '/symfony/translation/Dumper/CsvFileDumper.php', 'Symfony\\Component\\Translation\\Dumper\\DumperInterface' => $vendorDir . '/symfony/translation/Dumper/DumperInterface.php', 'Symfony\\Component\\Translation\\Dumper\\FileDumper' => $vendorDir . '/symfony/translation/Dumper/FileDumper.php', @@ -1446,43 +1449,19 @@ 'Symfony\\Component\\Yaml\\Tag\\TaggedValue' => $vendorDir . '/symfony/yaml/Tag/TaggedValue.php', 'Symfony\\Component\\Yaml\\Unescaper' => $vendorDir . '/symfony/yaml/Unescaper.php', 'Symfony\\Component\\Yaml\\Yaml' => $vendorDir . '/symfony/yaml/Yaml.php', - 'Symfony\\Contracts\\Cache\\CacheInterface' => $vendorDir . '/symfony/contracts/Cache/CacheInterface.php', - 'Symfony\\Contracts\\Cache\\CacheTrait' => $vendorDir . '/symfony/contracts/Cache/CacheTrait.php', - 'Symfony\\Contracts\\Cache\\CallbackInterface' => $vendorDir . '/symfony/contracts/Cache/CallbackInterface.php', - 'Symfony\\Contracts\\Cache\\ItemInterface' => $vendorDir . '/symfony/contracts/Cache/ItemInterface.php', - 'Symfony\\Contracts\\Cache\\TagAwareCacheInterface' => $vendorDir . '/symfony/contracts/Cache/TagAwareCacheInterface.php', - 'Symfony\\Contracts\\EventDispatcher\\Event' => $vendorDir . '/symfony/contracts/EventDispatcher/Event.php', - 'Symfony\\Contracts\\EventDispatcher\\EventDispatcherInterface' => $vendorDir . '/symfony/contracts/EventDispatcher/EventDispatcherInterface.php', - 'Symfony\\Contracts\\HttpClient\\ChunkInterface' => $vendorDir . '/symfony/contracts/HttpClient/ChunkInterface.php', - 'Symfony\\Contracts\\HttpClient\\Exception\\ClientExceptionInterface' => $vendorDir . '/symfony/contracts/HttpClient/Exception/ClientExceptionInterface.php', - 'Symfony\\Contracts\\HttpClient\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/contracts/HttpClient/Exception/ExceptionInterface.php', - 'Symfony\\Contracts\\HttpClient\\Exception\\HttpExceptionInterface' => $vendorDir . '/symfony/contracts/HttpClient/Exception/HttpExceptionInterface.php', - 'Symfony\\Contracts\\HttpClient\\Exception\\RedirectionExceptionInterface' => $vendorDir . '/symfony/contracts/HttpClient/Exception/RedirectionExceptionInterface.php', - 'Symfony\\Contracts\\HttpClient\\Exception\\ServerExceptionInterface' => $vendorDir . '/symfony/contracts/HttpClient/Exception/ServerExceptionInterface.php', - 'Symfony\\Contracts\\HttpClient\\Exception\\TransportExceptionInterface' => $vendorDir . '/symfony/contracts/HttpClient/Exception/TransportExceptionInterface.php', - 'Symfony\\Contracts\\HttpClient\\HttpClientInterface' => $vendorDir . '/symfony/contracts/HttpClient/HttpClientInterface.php', - 'Symfony\\Contracts\\HttpClient\\ResponseInterface' => $vendorDir . '/symfony/contracts/HttpClient/ResponseInterface.php', - 'Symfony\\Contracts\\HttpClient\\ResponseStreamInterface' => $vendorDir . '/symfony/contracts/HttpClient/ResponseStreamInterface.php', - 'Symfony\\Contracts\\HttpClient\\Test\\HttpClientTestCase' => $vendorDir . '/symfony/contracts/HttpClient/Test/HttpClientTestCase.php', - 'Symfony\\Contracts\\HttpClient\\Test\\TestHttpServer' => $vendorDir . '/symfony/contracts/HttpClient/Test/TestHttpServer.php', - 'Symfony\\Contracts\\Service\\ResetInterface' => $vendorDir . '/symfony/contracts/Service/ResetInterface.php', - 'Symfony\\Contracts\\Service\\ServiceLocatorTrait' => $vendorDir . '/symfony/contracts/Service/ServiceLocatorTrait.php', - 'Symfony\\Contracts\\Service\\ServiceProviderInterface' => $vendorDir . '/symfony/contracts/Service/ServiceProviderInterface.php', - 'Symfony\\Contracts\\Service\\ServiceSubscriberInterface' => $vendorDir . '/symfony/contracts/Service/ServiceSubscriberInterface.php', - 'Symfony\\Contracts\\Service\\ServiceSubscriberTrait' => $vendorDir . '/symfony/contracts/Service/ServiceSubscriberTrait.php', - 'Symfony\\Contracts\\Tests\\Cache\\CacheTraitTest' => $vendorDir . '/symfony/contracts/Tests/Cache/CacheTraitTest.php', - 'Symfony\\Contracts\\Tests\\Cache\\TestPool' => $vendorDir . '/symfony/contracts/Tests/Cache/CacheTraitTest.php', - 'Symfony\\Contracts\\Tests\\Service\\ChildTestService' => $vendorDir . '/symfony/contracts/Tests/Service/ServiceSubscriberTraitTest.php', - 'Symfony\\Contracts\\Tests\\Service\\ParentTestService' => $vendorDir . '/symfony/contracts/Tests/Service/ServiceSubscriberTraitTest.php', - 'Symfony\\Contracts\\Tests\\Service\\ServiceLocatorTest' => $vendorDir . '/symfony/contracts/Tests/Service/ServiceLocatorTest.php', - 'Symfony\\Contracts\\Tests\\Service\\ServiceSubscriberTraitTest' => $vendorDir . '/symfony/contracts/Tests/Service/ServiceSubscriberTraitTest.php', - 'Symfony\\Contracts\\Tests\\Service\\TestService' => $vendorDir . '/symfony/contracts/Tests/Service/ServiceSubscriberTraitTest.php', - 'Symfony\\Contracts\\Tests\\Translation\\TranslatorTest' => $vendorDir . '/symfony/contracts/Tests/Translation/TranslatorTest.php', - 'Symfony\\Contracts\\Translation\\LocaleAwareInterface' => $vendorDir . '/symfony/contracts/Translation/LocaleAwareInterface.php', - 'Symfony\\Contracts\\Translation\\TranslatorInterface' => $vendorDir . '/symfony/contracts/Translation/TranslatorInterface.php', - 'Symfony\\Contracts\\Translation\\TranslatorTrait' => $vendorDir . '/symfony/contracts/Translation/TranslatorTrait.php', + 'Symfony\\Contracts\\Service\\ResetInterface' => $vendorDir . '/symfony/service-contracts/ResetInterface.php', + 'Symfony\\Contracts\\Service\\ServiceLocatorTrait' => $vendorDir . '/symfony/service-contracts/ServiceLocatorTrait.php', + 'Symfony\\Contracts\\Service\\ServiceProviderInterface' => $vendorDir . '/symfony/service-contracts/ServiceProviderInterface.php', + 'Symfony\\Contracts\\Service\\ServiceSubscriberInterface' => $vendorDir . '/symfony/service-contracts/ServiceSubscriberInterface.php', + 'Symfony\\Contracts\\Service\\ServiceSubscriberTrait' => $vendorDir . '/symfony/service-contracts/ServiceSubscriberTrait.php', + 'Symfony\\Contracts\\Service\\Test\\ServiceLocatorTest' => $vendorDir . '/symfony/service-contracts/Test/ServiceLocatorTest.php', + 'Symfony\\Contracts\\Translation\\LocaleAwareInterface' => $vendorDir . '/symfony/translation-contracts/LocaleAwareInterface.php', + 'Symfony\\Contracts\\Translation\\Test\\TranslatorTest' => $vendorDir . '/symfony/translation-contracts/Test/TranslatorTest.php', + 'Symfony\\Contracts\\Translation\\TranslatorInterface' => $vendorDir . '/symfony/translation-contracts/TranslatorInterface.php', + 'Symfony\\Contracts\\Translation\\TranslatorTrait' => $vendorDir . '/symfony/translation-contracts/TranslatorTrait.php', 'Symfony\\Polyfill\\Ctype\\Ctype' => $vendorDir . '/symfony/polyfill-ctype/Ctype.php', 'Symfony\\Polyfill\\Mbstring\\Mbstring' => $vendorDir . '/symfony/polyfill-mbstring/Mbstring.php', + 'Symfony\\Polyfill\\Php73\\Php73' => $vendorDir . '/symfony/polyfill-php73/Php73.php', 'Twig\\Cache\\CacheInterface' => $vendorDir . '/twig/twig/src/Cache/CacheInterface.php', 'Twig\\Cache\\FilesystemCache' => $vendorDir . '/twig/twig/src/Cache/FilesystemCache.php', 'Twig\\Cache\\NullCache' => $vendorDir . '/twig/twig/src/Cache/NullCache.php', @@ -1519,6 +1498,7 @@ 'Twig\\NodeTraverser' => $vendorDir . '/twig/twig/src/NodeTraverser.php', 'Twig\\NodeVisitor\\AbstractNodeVisitor' => $vendorDir . '/twig/twig/src/NodeVisitor/AbstractNodeVisitor.php', 'Twig\\NodeVisitor\\EscaperNodeVisitor' => $vendorDir . '/twig/twig/src/NodeVisitor/EscaperNodeVisitor.php', + 'Twig\\NodeVisitor\\MacroAutoImportNodeVisitor' => $vendorDir . '/twig/twig/src/NodeVisitor/MacroAutoImportNodeVisitor.php', 'Twig\\NodeVisitor\\NodeVisitorInterface' => $vendorDir . '/twig/twig/src/NodeVisitor/NodeVisitorInterface.php', 'Twig\\NodeVisitor\\OptimizerNodeVisitor' => $vendorDir . '/twig/twig/src/NodeVisitor/OptimizerNodeVisitor.php', 'Twig\\NodeVisitor\\SafeAnalysisNodeVisitor' => $vendorDir . '/twig/twig/src/NodeVisitor/SafeAnalysisNodeVisitor.php', diff --git a/vendor/composer/autoload_files.php b/vendor/composer/autoload_files.php index c0dfffed39..18c9a600fd 100644 --- a/vendor/composer/autoload_files.php +++ b/vendor/composer/autoload_files.php @@ -8,6 +8,7 @@ return array( '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', + '0d59ee240a4cd96ddbb4ff164fccea4d' => $vendorDir . '/symfony/polyfill-php73/bootstrap.php', '7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php', 'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php', 'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php', diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php index 84a1df6b6d..e91566435c 100644 --- a/vendor/composer/autoload_psr4.php +++ b/vendor/composer/autoload_psr4.php @@ -10,9 +10,11 @@ 'Zend\\Db\\' => array($vendorDir . '/zendframework/zend-db/src'), 'WellingGuzman\\OAuth2\\Client\\' => array($vendorDir . '/directus/oauth2-okta/src'), 'Twig\\' => array($vendorDir . '/twig/twig/src'), + '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\\' => array($vendorDir . '/symfony/contracts'), + 'Symfony\\Contracts\\Translation\\' => array($vendorDir . '/symfony/translation-contracts'), + 'Symfony\\Contracts\\Service\\' => array($vendorDir . '/symfony/service-contracts'), 'Symfony\\Component\\Yaml\\' => array($vendorDir . '/symfony/yaml'), 'Symfony\\Component\\Validator\\' => array($vendorDir . '/symfony/validator'), 'Symfony\\Component\\Translation\\' => array($vendorDir . '/symfony/translation'), diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php index c5281d4dea..7197c71d78 100644 --- a/vendor/composer/autoload_real.php +++ b/vendor/composer/autoload_real.php @@ -2,7 +2,7 @@ // autoload_real.php @generated by Composer -class ComposerAutoloaderInitfb1c448da18426b05d59dd149a19a87b +class ComposerAutoloaderInitdacd47c943180ea3bff91a23543fc0c1 { private static $loader; @@ -19,15 +19,15 @@ public static function getLoader() return self::$loader; } - spl_autoload_register(array('ComposerAutoloaderInitfb1c448da18426b05d59dd149a19a87b', 'loadClassLoader'), true, true); + spl_autoload_register(array('ComposerAutoloaderInitdacd47c943180ea3bff91a23543fc0c1', 'loadClassLoader'), true, true); self::$loader = $loader = new \Composer\Autoload\ClassLoader(); - spl_autoload_unregister(array('ComposerAutoloaderInitfb1c448da18426b05d59dd149a19a87b', 'loadClassLoader')); + spl_autoload_unregister(array('ComposerAutoloaderInitdacd47c943180ea3bff91a23543fc0c1', 'loadClassLoader')); $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); if ($useStaticLoader) { require_once __DIR__ . '/autoload_static.php'; - call_user_func(\Composer\Autoload\ComposerStaticInitfb1c448da18426b05d59dd149a19a87b::getInitializer($loader)); + call_user_func(\Composer\Autoload\ComposerStaticInitdacd47c943180ea3bff91a23543fc0c1::getInitializer($loader)); } else { $classMap = require __DIR__ . '/autoload_classmap.php'; if ($classMap) { @@ -39,19 +39,19 @@ public static function getLoader() $loader->register(true); if ($useStaticLoader) { - $includeFiles = Composer\Autoload\ComposerStaticInitfb1c448da18426b05d59dd149a19a87b::$files; + $includeFiles = Composer\Autoload\ComposerStaticInitdacd47c943180ea3bff91a23543fc0c1::$files; } else { $includeFiles = require __DIR__ . '/autoload_files.php'; } foreach ($includeFiles as $fileIdentifier => $file) { - composerRequirefb1c448da18426b05d59dd149a19a87b($fileIdentifier, $file); + composerRequiredacd47c943180ea3bff91a23543fc0c1($fileIdentifier, $file); } return $loader; } } -function composerRequirefb1c448da18426b05d59dd149a19a87b($fileIdentifier, $file) +function composerRequiredacd47c943180ea3bff91a23543fc0c1($fileIdentifier, $file) { if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { require $file; diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index 570e7b1720..1c2c64b745 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -4,11 +4,12 @@ namespace Composer\Autoload; -class ComposerStaticInitfb1c448da18426b05d59dd149a19a87b +class ComposerStaticInitdacd47c943180ea3bff91a23543fc0c1 { public static $files = array ( '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', + '0d59ee240a4cd96ddbb4ff164fccea4d' => __DIR__ . '/..' . '/symfony/polyfill-php73/bootstrap.php', '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php', 'a0edc8309cc5e1d60e3047b5df6b7052' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/functions_include.php', 'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php', @@ -34,9 +35,11 @@ class ComposerStaticInitfb1c448da18426b05d59dd149a19a87b ), 'S' => array ( + 'Symfony\\Polyfill\\Php73\\' => 23, 'Symfony\\Polyfill\\Mbstring\\' => 26, 'Symfony\\Polyfill\\Ctype\\' => 23, - 'Symfony\\Contracts\\' => 18, + 'Symfony\\Contracts\\Translation\\' => 30, + 'Symfony\\Contracts\\Service\\' => 26, 'Symfony\\Component\\Yaml\\' => 23, 'Symfony\\Component\\Validator\\' => 28, 'Symfony\\Component\\Translation\\' => 30, @@ -122,6 +125,10 @@ class ComposerStaticInitfb1c448da18426b05d59dd149a19a87b array ( 0 => __DIR__ . '/..' . '/twig/twig/src', ), + 'Symfony\\Polyfill\\Php73\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php73', + ), 'Symfony\\Polyfill\\Mbstring\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', @@ -130,9 +137,13 @@ class ComposerStaticInitfb1c448da18426b05d59dd149a19a87b array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-ctype', ), - 'Symfony\\Contracts\\' => + 'Symfony\\Contracts\\Translation\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/translation-contracts', + ), + 'Symfony\\Contracts\\Service\\' => array ( - 0 => __DIR__ . '/..' . '/symfony/contracts', + 0 => __DIR__ . '/..' . '/symfony/service-contracts', ), 'Symfony\\Component\\Yaml\\' => array ( @@ -940,6 +951,7 @@ class ComposerStaticInitfb1c448da18426b05d59dd149a19a87b 'Intervention\\Image\\Point' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Point.php', 'Intervention\\Image\\Response' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Response.php', 'Intervention\\Image\\Size' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Size.php', + 'JsonException' => __DIR__ . '/..' . '/symfony/polyfill-php73/Resources/stubs/JsonException.php', 'League\\Flysystem\\AdapterInterface' => __DIR__ . '/..' . '/league/flysystem/src/AdapterInterface.php', 'League\\Flysystem\\Adapter\\AbstractAdapter' => __DIR__ . '/..' . '/league/flysystem/src/Adapter/AbstractAdapter.php', 'League\\Flysystem\\Adapter\\AbstractFtpAdapter' => __DIR__ . '/..' . '/league/flysystem/src/Adapter/AbstractFtpAdapter.php', @@ -1449,6 +1461,7 @@ class ComposerStaticInitfb1c448da18426b05d59dd149a19a87b 'Symfony\\Component\\Console\\Formatter\\WrappableOutputFormatterInterface' => __DIR__ . '/..' . '/symfony/console/Formatter/WrappableOutputFormatterInterface.php', 'Symfony\\Component\\Console\\Helper\\DebugFormatterHelper' => __DIR__ . '/..' . '/symfony/console/Helper/DebugFormatterHelper.php', 'Symfony\\Component\\Console\\Helper\\DescriptorHelper' => __DIR__ . '/..' . '/symfony/console/Helper/DescriptorHelper.php', + 'Symfony\\Component\\Console\\Helper\\Dumper' => __DIR__ . '/..' . '/symfony/console/Helper/Dumper.php', 'Symfony\\Component\\Console\\Helper\\FormatterHelper' => __DIR__ . '/..' . '/symfony/console/Helper/FormatterHelper.php', 'Symfony\\Component\\Console\\Helper\\Helper' => __DIR__ . '/..' . '/symfony/console/Helper/Helper.php', 'Symfony\\Component\\Console\\Helper\\HelperInterface' => __DIR__ . '/..' . '/symfony/console/Helper/HelperInterface.php', @@ -1509,6 +1522,7 @@ class ComposerStaticInitfb1c448da18426b05d59dd149a19a87b 'Symfony\\Component\\Translation\\DependencyInjection\\TranslationDumperPass' => __DIR__ . '/..' . '/symfony/translation/DependencyInjection/TranslationDumperPass.php', 'Symfony\\Component\\Translation\\DependencyInjection\\TranslationExtractorPass' => __DIR__ . '/..' . '/symfony/translation/DependencyInjection/TranslationExtractorPass.php', 'Symfony\\Component\\Translation\\DependencyInjection\\TranslatorPass' => __DIR__ . '/..' . '/symfony/translation/DependencyInjection/TranslatorPass.php', + 'Symfony\\Component\\Translation\\DependencyInjection\\TranslatorPathsPass' => __DIR__ . '/..' . '/symfony/translation/DependencyInjection/TranslatorPathsPass.php', 'Symfony\\Component\\Translation\\Dumper\\CsvFileDumper' => __DIR__ . '/..' . '/symfony/translation/Dumper/CsvFileDumper.php', 'Symfony\\Component\\Translation\\Dumper\\DumperInterface' => __DIR__ . '/..' . '/symfony/translation/Dumper/DumperInterface.php', 'Symfony\\Component\\Translation\\Dumper\\FileDumper' => __DIR__ . '/..' . '/symfony/translation/Dumper/FileDumper.php', @@ -1751,43 +1765,19 @@ class ComposerStaticInitfb1c448da18426b05d59dd149a19a87b 'Symfony\\Component\\Yaml\\Tag\\TaggedValue' => __DIR__ . '/..' . '/symfony/yaml/Tag/TaggedValue.php', 'Symfony\\Component\\Yaml\\Unescaper' => __DIR__ . '/..' . '/symfony/yaml/Unescaper.php', 'Symfony\\Component\\Yaml\\Yaml' => __DIR__ . '/..' . '/symfony/yaml/Yaml.php', - 'Symfony\\Contracts\\Cache\\CacheInterface' => __DIR__ . '/..' . '/symfony/contracts/Cache/CacheInterface.php', - 'Symfony\\Contracts\\Cache\\CacheTrait' => __DIR__ . '/..' . '/symfony/contracts/Cache/CacheTrait.php', - 'Symfony\\Contracts\\Cache\\CallbackInterface' => __DIR__ . '/..' . '/symfony/contracts/Cache/CallbackInterface.php', - 'Symfony\\Contracts\\Cache\\ItemInterface' => __DIR__ . '/..' . '/symfony/contracts/Cache/ItemInterface.php', - 'Symfony\\Contracts\\Cache\\TagAwareCacheInterface' => __DIR__ . '/..' . '/symfony/contracts/Cache/TagAwareCacheInterface.php', - 'Symfony\\Contracts\\EventDispatcher\\Event' => __DIR__ . '/..' . '/symfony/contracts/EventDispatcher/Event.php', - 'Symfony\\Contracts\\EventDispatcher\\EventDispatcherInterface' => __DIR__ . '/..' . '/symfony/contracts/EventDispatcher/EventDispatcherInterface.php', - 'Symfony\\Contracts\\HttpClient\\ChunkInterface' => __DIR__ . '/..' . '/symfony/contracts/HttpClient/ChunkInterface.php', - 'Symfony\\Contracts\\HttpClient\\Exception\\ClientExceptionInterface' => __DIR__ . '/..' . '/symfony/contracts/HttpClient/Exception/ClientExceptionInterface.php', - 'Symfony\\Contracts\\HttpClient\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/contracts/HttpClient/Exception/ExceptionInterface.php', - 'Symfony\\Contracts\\HttpClient\\Exception\\HttpExceptionInterface' => __DIR__ . '/..' . '/symfony/contracts/HttpClient/Exception/HttpExceptionInterface.php', - 'Symfony\\Contracts\\HttpClient\\Exception\\RedirectionExceptionInterface' => __DIR__ . '/..' . '/symfony/contracts/HttpClient/Exception/RedirectionExceptionInterface.php', - 'Symfony\\Contracts\\HttpClient\\Exception\\ServerExceptionInterface' => __DIR__ . '/..' . '/symfony/contracts/HttpClient/Exception/ServerExceptionInterface.php', - 'Symfony\\Contracts\\HttpClient\\Exception\\TransportExceptionInterface' => __DIR__ . '/..' . '/symfony/contracts/HttpClient/Exception/TransportExceptionInterface.php', - 'Symfony\\Contracts\\HttpClient\\HttpClientInterface' => __DIR__ . '/..' . '/symfony/contracts/HttpClient/HttpClientInterface.php', - 'Symfony\\Contracts\\HttpClient\\ResponseInterface' => __DIR__ . '/..' . '/symfony/contracts/HttpClient/ResponseInterface.php', - 'Symfony\\Contracts\\HttpClient\\ResponseStreamInterface' => __DIR__ . '/..' . '/symfony/contracts/HttpClient/ResponseStreamInterface.php', - 'Symfony\\Contracts\\HttpClient\\Test\\HttpClientTestCase' => __DIR__ . '/..' . '/symfony/contracts/HttpClient/Test/HttpClientTestCase.php', - 'Symfony\\Contracts\\HttpClient\\Test\\TestHttpServer' => __DIR__ . '/..' . '/symfony/contracts/HttpClient/Test/TestHttpServer.php', - 'Symfony\\Contracts\\Service\\ResetInterface' => __DIR__ . '/..' . '/symfony/contracts/Service/ResetInterface.php', - 'Symfony\\Contracts\\Service\\ServiceLocatorTrait' => __DIR__ . '/..' . '/symfony/contracts/Service/ServiceLocatorTrait.php', - 'Symfony\\Contracts\\Service\\ServiceProviderInterface' => __DIR__ . '/..' . '/symfony/contracts/Service/ServiceProviderInterface.php', - 'Symfony\\Contracts\\Service\\ServiceSubscriberInterface' => __DIR__ . '/..' . '/symfony/contracts/Service/ServiceSubscriberInterface.php', - 'Symfony\\Contracts\\Service\\ServiceSubscriberTrait' => __DIR__ . '/..' . '/symfony/contracts/Service/ServiceSubscriberTrait.php', - 'Symfony\\Contracts\\Tests\\Cache\\CacheTraitTest' => __DIR__ . '/..' . '/symfony/contracts/Tests/Cache/CacheTraitTest.php', - 'Symfony\\Contracts\\Tests\\Cache\\TestPool' => __DIR__ . '/..' . '/symfony/contracts/Tests/Cache/CacheTraitTest.php', - 'Symfony\\Contracts\\Tests\\Service\\ChildTestService' => __DIR__ . '/..' . '/symfony/contracts/Tests/Service/ServiceSubscriberTraitTest.php', - 'Symfony\\Contracts\\Tests\\Service\\ParentTestService' => __DIR__ . '/..' . '/symfony/contracts/Tests/Service/ServiceSubscriberTraitTest.php', - 'Symfony\\Contracts\\Tests\\Service\\ServiceLocatorTest' => __DIR__ . '/..' . '/symfony/contracts/Tests/Service/ServiceLocatorTest.php', - 'Symfony\\Contracts\\Tests\\Service\\ServiceSubscriberTraitTest' => __DIR__ . '/..' . '/symfony/contracts/Tests/Service/ServiceSubscriberTraitTest.php', - 'Symfony\\Contracts\\Tests\\Service\\TestService' => __DIR__ . '/..' . '/symfony/contracts/Tests/Service/ServiceSubscriberTraitTest.php', - 'Symfony\\Contracts\\Tests\\Translation\\TranslatorTest' => __DIR__ . '/..' . '/symfony/contracts/Tests/Translation/TranslatorTest.php', - 'Symfony\\Contracts\\Translation\\LocaleAwareInterface' => __DIR__ . '/..' . '/symfony/contracts/Translation/LocaleAwareInterface.php', - 'Symfony\\Contracts\\Translation\\TranslatorInterface' => __DIR__ . '/..' . '/symfony/contracts/Translation/TranslatorInterface.php', - 'Symfony\\Contracts\\Translation\\TranslatorTrait' => __DIR__ . '/..' . '/symfony/contracts/Translation/TranslatorTrait.php', + 'Symfony\\Contracts\\Service\\ResetInterface' => __DIR__ . '/..' . '/symfony/service-contracts/ResetInterface.php', + 'Symfony\\Contracts\\Service\\ServiceLocatorTrait' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceLocatorTrait.php', + 'Symfony\\Contracts\\Service\\ServiceProviderInterface' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceProviderInterface.php', + 'Symfony\\Contracts\\Service\\ServiceSubscriberInterface' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceSubscriberInterface.php', + 'Symfony\\Contracts\\Service\\ServiceSubscriberTrait' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceSubscriberTrait.php', + 'Symfony\\Contracts\\Service\\Test\\ServiceLocatorTest' => __DIR__ . '/..' . '/symfony/service-contracts/Test/ServiceLocatorTest.php', + 'Symfony\\Contracts\\Translation\\LocaleAwareInterface' => __DIR__ . '/..' . '/symfony/translation-contracts/LocaleAwareInterface.php', + 'Symfony\\Contracts\\Translation\\Test\\TranslatorTest' => __DIR__ . '/..' . '/symfony/translation-contracts/Test/TranslatorTest.php', + 'Symfony\\Contracts\\Translation\\TranslatorInterface' => __DIR__ . '/..' . '/symfony/translation-contracts/TranslatorInterface.php', + 'Symfony\\Contracts\\Translation\\TranslatorTrait' => __DIR__ . '/..' . '/symfony/translation-contracts/TranslatorTrait.php', 'Symfony\\Polyfill\\Ctype\\Ctype' => __DIR__ . '/..' . '/symfony/polyfill-ctype/Ctype.php', 'Symfony\\Polyfill\\Mbstring\\Mbstring' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/Mbstring.php', + 'Symfony\\Polyfill\\Php73\\Php73' => __DIR__ . '/..' . '/symfony/polyfill-php73/Php73.php', 'Twig\\Cache\\CacheInterface' => __DIR__ . '/..' . '/twig/twig/src/Cache/CacheInterface.php', 'Twig\\Cache\\FilesystemCache' => __DIR__ . '/..' . '/twig/twig/src/Cache/FilesystemCache.php', 'Twig\\Cache\\NullCache' => __DIR__ . '/..' . '/twig/twig/src/Cache/NullCache.php', @@ -1824,6 +1814,7 @@ class ComposerStaticInitfb1c448da18426b05d59dd149a19a87b 'Twig\\NodeTraverser' => __DIR__ . '/..' . '/twig/twig/src/NodeTraverser.php', 'Twig\\NodeVisitor\\AbstractNodeVisitor' => __DIR__ . '/..' . '/twig/twig/src/NodeVisitor/AbstractNodeVisitor.php', 'Twig\\NodeVisitor\\EscaperNodeVisitor' => __DIR__ . '/..' . '/twig/twig/src/NodeVisitor/EscaperNodeVisitor.php', + 'Twig\\NodeVisitor\\MacroAutoImportNodeVisitor' => __DIR__ . '/..' . '/twig/twig/src/NodeVisitor/MacroAutoImportNodeVisitor.php', 'Twig\\NodeVisitor\\NodeVisitorInterface' => __DIR__ . '/..' . '/twig/twig/src/NodeVisitor/NodeVisitorInterface.php', 'Twig\\NodeVisitor\\OptimizerNodeVisitor' => __DIR__ . '/..' . '/twig/twig/src/NodeVisitor/OptimizerNodeVisitor.php', 'Twig\\NodeVisitor\\SafeAnalysisNodeVisitor' => __DIR__ . '/..' . '/twig/twig/src/NodeVisitor/SafeAnalysisNodeVisitor.php', @@ -2398,10 +2389,10 @@ class ComposerStaticInitfb1c448da18426b05d59dd149a19a87b public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { - $loader->prefixLengthsPsr4 = ComposerStaticInitfb1c448da18426b05d59dd149a19a87b::$prefixLengthsPsr4; - $loader->prefixDirsPsr4 = ComposerStaticInitfb1c448da18426b05d59dd149a19a87b::$prefixDirsPsr4; - $loader->prefixesPsr0 = ComposerStaticInitfb1c448da18426b05d59dd149a19a87b::$prefixesPsr0; - $loader->classMap = ComposerStaticInitfb1c448da18426b05d59dd149a19a87b::$classMap; + $loader->prefixLengthsPsr4 = ComposerStaticInitdacd47c943180ea3bff91a23543fc0c1::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInitdacd47c943180ea3bff91a23543fc0c1::$prefixDirsPsr4; + $loader->prefixesPsr0 = ComposerStaticInitdacd47c943180ea3bff91a23543fc0c1::$prefixesPsr0; + $loader->classMap = ComposerStaticInitdacd47c943180ea3bff91a23543fc0c1::$classMap; }, null, ClassLoader::class); } diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index e669242d04..2708af5426 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -2160,17 +2160,17 @@ }, { "name": "symfony/config", - "version": "v4.2.8", - "version_normalized": "4.2.8.0", + "version": "v4.3.1", + "version_normalized": "4.3.1.0", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "0e745ead307d5dcd4e163e94a47ec04b1428943f" + "reference": "6379ee07398643e09e6ed1e87d9c62dfcad7f4eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/0e745ead307d5dcd4e163e94a47ec04b1428943f", - "reference": "0e745ead307d5dcd4e163e94a47ec04b1428943f", + "url": "https://api.github.com/repos/symfony/config/zipball/6379ee07398643e09e6ed1e87d9c62dfcad7f4eb", + "reference": "6379ee07398643e09e6ed1e87d9c62dfcad7f4eb", "shasum": "" }, "require": { @@ -2185,16 +2185,17 @@ "symfony/dependency-injection": "~3.4|~4.0", "symfony/event-dispatcher": "~3.4|~4.0", "symfony/finder": "~3.4|~4.0", + "symfony/messenger": "~4.1", "symfony/yaml": "~3.4|~4.0" }, "suggest": { "symfony/yaml": "To use the yaml reference dumper" }, - "time": "2019-04-01T14:03:25+00:00", + "time": "2019-05-30T16:10:05+00:00", "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "installation-source": "dist", @@ -2225,26 +2226,28 @@ }, { "name": "symfony/console", - "version": "v4.2.8", - "version_normalized": "4.2.8.0", + "version": "v4.3.1", + "version_normalized": "4.3.1.0", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "e2840bb38bddad7a0feaf85931e38fdcffdb2f81" + "reference": "d50bbeeb0e17e6dd4124ea391eff235e932cbf64" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/e2840bb38bddad7a0feaf85931e38fdcffdb2f81", - "reference": "e2840bb38bddad7a0feaf85931e38fdcffdb2f81", + "url": "https://api.github.com/repos/symfony/console/zipball/d50bbeeb0e17e6dd4124ea391eff235e932cbf64", + "reference": "d50bbeeb0e17e6dd4124ea391eff235e932cbf64", "shasum": "" }, "require": { "php": "^7.1.3", - "symfony/contracts": "^1.0", - "symfony/polyfill-mbstring": "~1.0" + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.8", + "symfony/service-contracts": "^1.1" }, "conflict": { "symfony/dependency-injection": "<3.4", + "symfony/event-dispatcher": "<4.3", "symfony/process": "<3.3" }, "provide": { @@ -2254,9 +2257,10 @@ "psr/log": "~1.0", "symfony/config": "~3.4|~4.0", "symfony/dependency-injection": "~3.4|~4.0", - "symfony/event-dispatcher": "~3.4|~4.0", + "symfony/event-dispatcher": "^4.3", "symfony/lock": "~3.4|~4.0", - "symfony/process": "~3.4|~4.0" + "symfony/process": "~3.4|~4.0", + "symfony/var-dumper": "^4.3" }, "suggest": { "psr/log": "For using the console logger", @@ -2264,11 +2268,11 @@ "symfony/lock": "", "symfony/process": "" }, - "time": "2019-04-08T14:23:48+00:00", + "time": "2019-06-05T13:25:51+00:00", "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "installation-source": "dist", @@ -2297,103 +2301,30 @@ "description": "Symfony Console Component", "homepage": "https://symfony.com" }, - { - "name": "symfony/contracts", - "version": "v1.1.0", - "version_normalized": "1.1.0.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/contracts.git", - "reference": "d3636025e8253c6144358ec0a62773cae588395b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/contracts/zipball/d3636025e8253c6144358ec0a62773cae588395b", - "reference": "d3636025e8253c6144358ec0a62773cae588395b", - "shasum": "" - }, - "require": { - "php": "^7.1.3" - }, - "require-dev": { - "psr/cache": "^1.0", - "psr/container": "^1.0", - "symfony/polyfill-intl-idn": "^1.10" - }, - "suggest": { - "psr/cache": "When using the Cache contracts", - "psr/container": "When using the Service contracts", - "symfony/cache-contracts-implementation": "", - "symfony/event-dispatcher-implementation": "", - "symfony/http-client-contracts-implementation": "", - "symfony/service-contracts-implementation": "", - "symfony/translation-contracts-implementation": "" - }, - "time": "2019-04-27T14:29:50+00:00", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1-dev" - } - }, - "installation-source": "dist", - "autoload": { - "psr-4": { - "Symfony\\Contracts\\": "" - }, - "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": "A set of abstractions extracted out of the Symfony components", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ] - }, { "name": "symfony/filesystem", - "version": "v4.2.8", - "version_normalized": "4.2.8.0", + "version": "v4.3.1", + "version_normalized": "4.3.1.0", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "e16b9e471703b2c60b95f14d31c1239f68f11601" + "reference": "bf2af40d738dec5e433faea7b00daa4431d0a4cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/e16b9e471703b2c60b95f14d31c1239f68f11601", - "reference": "e16b9e471703b2c60b95f14d31c1239f68f11601", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/bf2af40d738dec5e433faea7b00daa4431d0a4cf", + "reference": "bf2af40d738dec5e433faea7b00daa4431d0a4cf", "shasum": "" }, "require": { "php": "^7.1.3", "symfony/polyfill-ctype": "~1.8" }, - "time": "2019-02-07T11:40:08+00:00", + "time": "2019-06-03T20:27:40+00:00", "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "installation-source": "dist", @@ -2543,25 +2474,145 @@ "shim" ] }, + { + "name": "symfony/polyfill-php73", + "version": "v1.11.0", + "version_normalized": "1.11.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "d1fb4abcc0c47be136208ad9d68bf59f1ee17abd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/d1fb4abcc0c47be136208ad9d68bf59f1ee17abd", + "reference": "d1fb4abcc0c47be136208ad9d68bf59f1ee17abd", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "time": "2019-02-06T07:57:58+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "files": [ + "bootstrap.php" + ], + "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" + ] + }, + { + "name": "symfony/service-contracts", + "version": "v1.1.2", + "version_normalized": "1.1.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "191afdcb5804db960d26d8566b7e9a2843cab3a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/191afdcb5804db960d26d8566b7e9a2843cab3a0", + "reference": "191afdcb5804db960d26d8566b7e9a2843cab3a0", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "suggest": { + "psr/container": "", + "symfony/service-implementation": "" + }, + "time": "2019-05-28T07:50:59+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ] + }, { "name": "symfony/translation", - "version": "v4.2.8", - "version_normalized": "4.2.8.0", + "version": "v4.3.1", + "version_normalized": "4.3.1.0", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "181a426dd129cb496f12d7e7555f6d0b37a7615b" + "reference": "5dda505e5f65d759741dfaf4e54b36010a4b57aa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/181a426dd129cb496f12d7e7555f6d0b37a7615b", - "reference": "181a426dd129cb496f12d7e7555f6d0b37a7615b", + "url": "https://api.github.com/repos/symfony/translation/zipball/5dda505e5f65d759741dfaf4e54b36010a4b57aa", + "reference": "5dda505e5f65d759741dfaf4e54b36010a4b57aa", "shasum": "" }, "require": { "php": "^7.1.3", - "symfony/contracts": "^1.0.2", - "symfony/polyfill-mbstring": "~1.0" + "symfony/polyfill-mbstring": "~1.0", + "symfony/translation-contracts": "^1.1.2" }, "conflict": { "symfony/config": "<3.4", @@ -2569,7 +2620,7 @@ "symfony/yaml": "<3.4" }, "provide": { - "symfony/translation-contracts-implementation": "1.0" + "symfony/translation-implementation": "1.0" }, "require-dev": { "psr/log": "~1.0", @@ -2579,6 +2630,7 @@ "symfony/finder": "~2.8|~3.0|~4.0", "symfony/http-kernel": "~3.4|~4.0", "symfony/intl": "~3.4|~4.0", + "symfony/service-contracts": "^1.1.2", "symfony/var-dumper": "~3.4|~4.0", "symfony/yaml": "~3.4|~4.0" }, @@ -2587,11 +2639,11 @@ "symfony/config": "", "symfony/yaml": "" }, - "time": "2019-05-01T12:55:36+00:00", + "time": "2019-06-03T20:27:40+00:00", "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "installation-source": "dist", @@ -2620,19 +2672,78 @@ "description": "Symfony Translation Component", "homepage": "https://symfony.com" }, + { + "name": "symfony/translation-contracts", + "version": "v1.1.2", + "version_normalized": "1.1.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "93597ce975d91c52ebfaca1253343cd9ccb7916d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/93597ce975d91c52ebfaca1253343cd9ccb7916d", + "reference": "93597ce975d91c52ebfaca1253343cd9ccb7916d", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "suggest": { + "symfony/translation-implementation": "" + }, + "time": "2019-05-27T08:16:38+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ] + }, { "name": "symfony/validator", - "version": "v3.4.27", - "version_normalized": "3.4.27.0", + "version": "v3.4.28", + "version_normalized": "3.4.28.0", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "cc3f577d8887737df4d77a4c0cc6e3c22164cea4" + "reference": "23cf394faaffb6257f5764fbfc2db12ec30956f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/cc3f577d8887737df4d77a4c0cc6e3c22164cea4", - "reference": "cc3f577d8887737df4d77a4c0cc6e3c22164cea4", + "url": "https://api.github.com/repos/symfony/validator/zipball/23cf394faaffb6257f5764fbfc2db12ec30956f1", + "reference": "23cf394faaffb6257f5764fbfc2db12ec30956f1", "shasum": "" }, "require": { @@ -2674,7 +2785,7 @@ "symfony/property-access": "For accessing properties within comparison constraints", "symfony/yaml": "" }, - "time": "2019-04-29T08:34:27+00:00", + "time": "2019-05-05T16:11:06+00:00", "type": "library", "extra": { "branch-alias": { @@ -2709,17 +2820,17 @@ }, { "name": "symfony/yaml", - "version": "v4.2.8", - "version_normalized": "4.2.8.0", + "version": "v4.3.1", + "version_normalized": "4.3.1.0", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "6712daf03ee25b53abb14e7e8e0ede1a770efdb1" + "reference": "c60ecf5ba842324433b46f58dc7afc4487dbab99" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/6712daf03ee25b53abb14e7e8e0ede1a770efdb1", - "reference": "6712daf03ee25b53abb14e7e8e0ede1a770efdb1", + "url": "https://api.github.com/repos/symfony/yaml/zipball/c60ecf5ba842324433b46f58dc7afc4487dbab99", + "reference": "c60ecf5ba842324433b46f58dc7afc4487dbab99", "shasum": "" }, "require": { @@ -2735,11 +2846,11 @@ "suggest": { "symfony/console": "For validating YAML files using the lint command" }, - "time": "2019-03-30T15:58:42+00:00", + "time": "2019-04-06T14:04:46+00:00", "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "installation-source": "dist", @@ -2770,17 +2881,17 @@ }, { "name": "twig/twig", - "version": "v2.10.0", - "version_normalized": "2.10.0.0", + "version": "v2.11.2", + "version_normalized": "2.11.2.0", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "5240e21982885b76629552d83b4ebb6d41ccde6b" + "reference": "84a463403da1c81afbcedda8f0e788c78bd25a79" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/5240e21982885b76629552d83b4ebb6d41ccde6b", - "reference": "5240e21982885b76629552d83b4ebb6d41ccde6b", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/84a463403da1c81afbcedda8f0e788c78bd25a79", + "reference": "84a463403da1c81afbcedda8f0e788c78bd25a79", "shasum": "" }, "require": { @@ -2791,13 +2902,13 @@ "require-dev": { "psr/container": "^1.0", "symfony/debug": "^2.7", - "symfony/phpunit-bridge": "^3.4.19|^4.1.8" + "symfony/phpunit-bridge": "^3.4.19|^4.1.8|^5.0" }, - "time": "2019-05-14T12:03:52+00:00", + "time": "2019-06-05T11:17:07+00:00", "type": "library", "extra": { "branch-alias": { - "dev-master": "2.10-dev" + "dev-master": "2.11-dev" } }, "installation-source": "dist", diff --git a/vendor/symfony/config/CHANGELOG.md b/vendor/symfony/config/CHANGELOG.md index 6946d7f086..4d15e9aeda 100644 --- a/vendor/symfony/config/CHANGELOG.md +++ b/vendor/symfony/config/CHANGELOG.md @@ -1,6 +1,13 @@ CHANGELOG ========= +4.3.0 +----- + + * deprecated using environment variables with `cannotBeEmpty()` if the value is validated with `validate()` + * made `Resource\*` classes final and not implement `Serializable` anymore + * deprecated the `root()` method in `TreeBuilder`, pass the root node information to the constructor instead + 4.2.0 ----- diff --git a/vendor/symfony/config/Definition/Builder/TreeBuilder.php b/vendor/symfony/config/Definition/Builder/TreeBuilder.php index 71e7976da7..9a3b0351d2 100644 --- a/vendor/symfony/config/Definition/Builder/TreeBuilder.php +++ b/vendor/symfony/config/Definition/Builder/TreeBuilder.php @@ -29,7 +29,8 @@ public function __construct(string $name = null, string $type = 'array', NodeBui if (null === $name) { @trigger_error('A tree builder without a root node is deprecated since Symfony 4.2 and will not be supported anymore in 5.0.', E_USER_DEPRECATED); } else { - $this->root($name, $type, $builder); + $builder = $builder ?: new NodeBuilder(); + $this->root = $builder->node($name, $type)->setParent($this); } } @@ -43,9 +44,13 @@ public function __construct(string $name = null, string $type = 'array', NodeBui * @return ArrayNodeDefinition|NodeDefinition The root node (as an ArrayNodeDefinition when the type is 'array') * * @throws \RuntimeException When the node type is not supported + * + * @deprecated since Symfony 4.3, pass the root name to the constructor instead */ public function root($name, $type = 'array', NodeBuilder $builder = null) { + @trigger_error(sprintf('The "%s()" method called for the "%s" configuration is deprecated since Symfony 4.3, pass the root name to the constructor instead.', __METHOD__, $name), E_USER_DEPRECATED); + $builder = $builder ?: new NodeBuilder(); return $this->root = $builder->node($name, $type)->setParent($this); diff --git a/vendor/symfony/config/Definition/ScalarNode.php b/vendor/symfony/config/Definition/ScalarNode.php index da3dd90c94..5ad28ec4c5 100644 --- a/vendor/symfony/config/Definition/ScalarNode.php +++ b/vendor/symfony/config/Definition/ScalarNode.php @@ -48,6 +48,8 @@ protected function validateType($value) */ protected function isValueEmpty($value) { + // assume environment variables are never empty (which in practice is likely to be true during runtime) + // not doing so breaks many configs that are valid today if ($this->isHandlingPlaceholder()) { return false; } diff --git a/vendor/symfony/config/Definition/VariableNode.php b/vendor/symfony/config/Definition/VariableNode.php index 1a3442d961..0d722c6bd2 100644 --- a/vendor/symfony/config/Definition/VariableNode.php +++ b/vendor/symfony/config/Definition/VariableNode.php @@ -81,6 +81,19 @@ protected function validateType($value) */ protected function finalizeValue($value) { + // deny environment variables only when using custom validators + // this avoids ever passing an empty value to final validation closures + if (!$this->allowEmptyValue && $this->isHandlingPlaceholder() && $this->finalValidationClosures) { + @trigger_error(sprintf('Setting path "%s" to an environment variable is deprecated since Symfony 4.3. Remove "cannotBeEmpty()", "validate()" or include a prefix/suffix value instead.', $this->getPath()), E_USER_DEPRECATED); +// $e = new InvalidConfigurationException(sprintf('The path "%s" cannot contain an environment variable when empty values are not allowed by definition and are validated.', $this->getPath(), json_encode($value))); +// if ($hint = $this->getInfo()) { +// $e->addHint($hint); +// } +// $e->setPath($this->getPath()); +// +// throw $e; + } + if (!$this->allowEmptyValue && $this->isValueEmpty($value)) { $ex = new InvalidConfigurationException(sprintf('The path "%s" cannot contain an empty value, but got %s.', $this->getPath(), json_encode($value))); if ($hint = $this->getInfo()) { @@ -120,6 +133,8 @@ protected function mergeValues($leftSide, $rightSide) * @param mixed $value * * @return bool + * + * @see finalizeValue() */ protected function isValueEmpty($value) { diff --git a/vendor/symfony/config/Exception/FileLoaderLoadException.php b/vendor/symfony/config/Exception/FileLoaderLoadException.php index 45e73e3ce8..16beec5930 100644 --- a/vendor/symfony/config/Exception/FileLoaderLoadException.php +++ b/vendor/symfony/config/Exception/FileLoaderLoadException.php @@ -24,10 +24,10 @@ class FileLoaderLoadException extends \Exception * @param string $resource The resource that could not be imported * @param string $sourceResource The original resource importing the new resource * @param int $code The error code - * @param \Exception $previous A previous exception + * @param \Throwable $previous A previous exception * @param string $type The type of resource */ - public function __construct(string $resource, string $sourceResource = null, int $code = null, \Exception $previous = null, string $type = null) + public function __construct(string $resource, string $sourceResource = null, int $code = null, \Throwable $previous = null, string $type = null) { $message = ''; if ($previous) { diff --git a/vendor/symfony/config/Resource/ClassExistenceResource.php b/vendor/symfony/config/Resource/ClassExistenceResource.php index 035b3ce980..51154cfd6a 100644 --- a/vendor/symfony/config/Resource/ClassExistenceResource.php +++ b/vendor/symfony/config/Resource/ClassExistenceResource.php @@ -18,8 +18,10 @@ * The resource must be a fully-qualified class name. * * @author Fabien Potencier + * + * @final since Symfony 4.3 */ -class ClassExistenceResource implements SelfCheckingResourceInterface, \Serializable +class ClassExistenceResource implements SelfCheckingResourceInterface { private $resource; private $exists; @@ -97,21 +99,13 @@ public function isFresh($timestamp) /** * @internal */ - public function serialize() + public function __sleep(): array { if (null === $this->exists) { $this->isFresh(0); } - return serialize([$this->resource, $this->exists]); - } - - /** - * @internal - */ - public function unserialize($serialized) - { - list($this->resource, $this->exists) = unserialize($serialized); + return ['resource', 'exists']; } /** diff --git a/vendor/symfony/config/Resource/ComposerResource.php b/vendor/symfony/config/Resource/ComposerResource.php index c826d1bb75..db6b04c812 100644 --- a/vendor/symfony/config/Resource/ComposerResource.php +++ b/vendor/symfony/config/Resource/ComposerResource.php @@ -15,8 +15,10 @@ * ComposerResource tracks the PHP version and Composer dependencies. * * @author Nicolas Grekas + * + * @final since Symfony 4.3 */ -class ComposerResource implements SelfCheckingResourceInterface, \Serializable +class ComposerResource implements SelfCheckingResourceInterface { private $vendors; @@ -51,22 +53,6 @@ public function isFresh($timestamp) return self::$runtimeVendors === $this->vendors; } - /** - * @internal - */ - public function serialize() - { - return serialize($this->vendors); - } - - /** - * @internal - */ - public function unserialize($serialized) - { - $this->vendors = unserialize($serialized); - } - private static function refresh() { self::$runtimeVendors = []; diff --git a/vendor/symfony/config/Resource/DirectoryResource.php b/vendor/symfony/config/Resource/DirectoryResource.php index d657abcbae..3d703db7f6 100644 --- a/vendor/symfony/config/Resource/DirectoryResource.php +++ b/vendor/symfony/config/Resource/DirectoryResource.php @@ -15,8 +15,10 @@ * DirectoryResource represents a resources stored in a subdirectory tree. * * @author Fabien Potencier + * + * @final since Symfony 4.3 */ -class DirectoryResource implements SelfCheckingResourceInterface, \Serializable +class DirectoryResource implements SelfCheckingResourceInterface { private $resource; private $pattern; @@ -103,20 +105,4 @@ public function isFresh($timestamp) return true; } - - /** - * @internal - */ - public function serialize() - { - return serialize([$this->resource, $this->pattern]); - } - - /** - * @internal - */ - public function unserialize($serialized) - { - list($this->resource, $this->pattern) = unserialize($serialized); - } } diff --git a/vendor/symfony/config/Resource/FileExistenceResource.php b/vendor/symfony/config/Resource/FileExistenceResource.php index 8c65729c45..5723416158 100644 --- a/vendor/symfony/config/Resource/FileExistenceResource.php +++ b/vendor/symfony/config/Resource/FileExistenceResource.php @@ -18,8 +18,10 @@ * The resource can be a file or a directory. * * @author Charles-Henri Bruyand + * + * @final since Symfony 4.3 */ -class FileExistenceResource implements SelfCheckingResourceInterface, \Serializable +class FileExistenceResource implements SelfCheckingResourceInterface { private $resource; @@ -57,20 +59,4 @@ public function isFresh($timestamp) { return file_exists($this->resource) === $this->exists; } - - /** - * @internal - */ - public function serialize() - { - return serialize([$this->resource, $this->exists]); - } - - /** - * @internal - */ - public function unserialize($serialized) - { - list($this->resource, $this->exists) = unserialize($serialized); - } } diff --git a/vendor/symfony/config/Resource/FileResource.php b/vendor/symfony/config/Resource/FileResource.php index 94ec2c729a..95fe8a0bf8 100644 --- a/vendor/symfony/config/Resource/FileResource.php +++ b/vendor/symfony/config/Resource/FileResource.php @@ -17,8 +17,10 @@ * The resource can be a file or a directory. * * @author Fabien Potencier + * + * @final since Symfony 4.3 */ -class FileResource implements SelfCheckingResourceInterface, \Serializable +class FileResource implements SelfCheckingResourceInterface { /** * @var string|false @@ -62,20 +64,4 @@ public function isFresh($timestamp) { return false !== ($filemtime = @filemtime($this->resource)) && $filemtime <= $timestamp; } - - /** - * @internal - */ - public function serialize() - { - return serialize($this->resource); - } - - /** - * @internal - */ - public function unserialize($serialized) - { - $this->resource = unserialize($serialized); - } } diff --git a/vendor/symfony/config/Resource/GlobResource.php b/vendor/symfony/config/Resource/GlobResource.php index 5581e67eca..fce8f6e206 100644 --- a/vendor/symfony/config/Resource/GlobResource.php +++ b/vendor/symfony/config/Resource/GlobResource.php @@ -20,8 +20,10 @@ * Only existence/removal is tracked (not mtimes.) * * @author Nicolas Grekas + * + * @final since Symfony 4.3 */ -class GlobResource implements \IteratorAggregate, SelfCheckingResourceInterface, \Serializable +class GlobResource implements \IteratorAggregate, SelfCheckingResourceInterface { private $prefix; private $pattern; @@ -80,21 +82,13 @@ public function isFresh($timestamp) /** * @internal */ - public function serialize() + public function __sleep(): array { if (null === $this->hash) { $this->hash = $this->computeHash(); } - return serialize([$this->prefix, $this->pattern, $this->recursive, $this->hash, $this->forExclusion, $this->excludedPrefixes]); - } - - /** - * @internal - */ - public function unserialize($serialized) - { - list($this->prefix, $this->pattern, $this->recursive, $this->hash, $this->forExclusion, $this->excludedPrefixes) = unserialize($serialized) + [4 => false, []]; + return ['prefix', 'pattern', 'recursive', 'hash', 'forExclusion', 'excludedPrefixes']; } public function getIterator() diff --git a/vendor/symfony/config/Resource/ReflectionClassResource.php b/vendor/symfony/config/Resource/ReflectionClassResource.php index a7e73becf5..940b24d79b 100644 --- a/vendor/symfony/config/Resource/ReflectionClassResource.php +++ b/vendor/symfony/config/Resource/ReflectionClassResource.php @@ -13,12 +13,15 @@ use Symfony\Component\DependencyInjection\ServiceSubscriberInterface as LegacyServiceSubscriberInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Messenger\Handler\MessageSubscriberInterface; use Symfony\Contracts\Service\ServiceSubscriberInterface; /** * @author Nicolas Grekas + * + * @final since Symfony 4.3 */ -class ReflectionClassResource implements SelfCheckingResourceInterface, \Serializable +class ReflectionClassResource implements SelfCheckingResourceInterface { private $files = []; private $className; @@ -61,22 +64,14 @@ public function __toString() /** * @internal */ - public function serialize() + public function __sleep(): array { if (null === $this->hash) { $this->hash = $this->computeHash(); $this->loadFiles($this->classReflector); } - return serialize([$this->files, $this->className, $this->hash]); - } - - /** - * @internal - */ - public function unserialize($serialized) - { - list($this->files, $this->className, $this->hash) = unserialize($serialized); + return ['files', 'className', 'hash']; } private function loadFiles(\ReflectionClass $class) @@ -164,6 +159,13 @@ private function generateSignature(\ReflectionClass $class) yield print_r($class->name::getSubscribedEvents(), true); } + if (interface_exists(MessageSubscriberInterface::class, false) && $class->isSubclassOf(MessageSubscriberInterface::class)) { + yield MessageSubscriberInterface::class; + foreach ($class->name::getHandledMessages() as $key => $value) { + yield $key.print_r($value, true); + } + } + if (interface_exists(LegacyServiceSubscriberInterface::class, false) && $class->isSubclassOf(LegacyServiceSubscriberInterface::class)) { yield LegacyServiceSubscriberInterface::class; yield print_r([$class->name, 'getSubscribedServices'](), true); diff --git a/vendor/symfony/config/ResourceCheckerConfigCache.php b/vendor/symfony/config/ResourceCheckerConfigCache.php index ef18072844..34dc35d5f5 100644 --- a/vendor/symfony/config/ResourceCheckerConfigCache.php +++ b/vendor/symfony/config/ResourceCheckerConfigCache.php @@ -158,7 +158,7 @@ private function safelyUnserialize($file) $meta = false; $content = file_get_contents($file); $signalingException = new \UnexpectedValueException(); - $prevUnserializeHandler = ini_set('unserialize_callback_func', ''); + $prevUnserializeHandler = ini_set('unserialize_callback_func', self::class.'::handleUnserializeCallback'); $prevErrorHandler = set_error_handler(function ($type, $msg, $file, $line, $context = []) use (&$prevErrorHandler, $signalingException) { if (__FILE__ === $file) { throw $signalingException; @@ -180,4 +180,12 @@ private function safelyUnserialize($file) return $meta; } + + /** + * @internal + */ + public static function handleUnserializeCallback($class) + { + trigger_error('Class not found: '.$class); + } } diff --git a/vendor/symfony/config/Tests/Definition/Builder/TreeBuilderTest.php b/vendor/symfony/config/Tests/Definition/Builder/TreeBuilderTest.php index 0dae83be41..d0e3d2d52d 100644 --- a/vendor/symfony/config/Tests/Definition/Builder/TreeBuilderTest.php +++ b/vendor/symfony/config/Tests/Definition/Builder/TreeBuilderTest.php @@ -197,4 +197,14 @@ public function testInitializingTreeBuildersWithoutRootNode() { new TreeBuilder(); } + + /** + * @group legacy + * @expectedDeprecation The "Symfony\Component\Config\Definition\Builder\TreeBuilder::root()" method called for the "foo" configuration is deprecated since Symfony 4.3, pass the root name to the constructor instead. + */ + public function testRoot() + { + $builder = new TreeBuilder('foo'); + $builder->root('foo'); + } } diff --git a/vendor/symfony/config/Tests/Loader/DelegatingLoaderTest.php b/vendor/symfony/config/Tests/Loader/DelegatingLoaderTest.php index 3517cacfd5..daff5288ef 100644 --- a/vendor/symfony/config/Tests/Loader/DelegatingLoaderTest.php +++ b/vendor/symfony/config/Tests/Loader/DelegatingLoaderTest.php @@ -35,12 +35,12 @@ public function testGetSetResolver() public function testSupports() { $loader1 = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderInterface')->getMock(); - $loader1->expects($this->once())->method('supports')->will($this->returnValue(true)); + $loader1->expects($this->once())->method('supports')->willReturn(true); $loader = new DelegatingLoader(new LoaderResolver([$loader1])); $this->assertTrue($loader->supports('foo.xml'), '->supports() returns true if the resource is loadable'); $loader1 = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderInterface')->getMock(); - $loader1->expects($this->once())->method('supports')->will($this->returnValue(false)); + $loader1->expects($this->once())->method('supports')->willReturn(false); $loader = new DelegatingLoader(new LoaderResolver([$loader1])); $this->assertFalse($loader->supports('foo.foo'), '->supports() returns false if the resource is not loadable'); } @@ -48,7 +48,7 @@ public function testSupports() public function testLoad() { $loader = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderInterface')->getMock(); - $loader->expects($this->once())->method('supports')->will($this->returnValue(true)); + $loader->expects($this->once())->method('supports')->willReturn(true); $loader->expects($this->once())->method('load'); $resolver = new LoaderResolver([$loader]); $loader = new DelegatingLoader($resolver); @@ -62,7 +62,7 @@ public function testLoad() public function testLoadThrowsAnExceptionIfTheResourceCannotBeLoaded() { $loader = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderInterface')->getMock(); - $loader->expects($this->once())->method('supports')->will($this->returnValue(false)); + $loader->expects($this->once())->method('supports')->willReturn(false); $resolver = new LoaderResolver([$loader]); $loader = new DelegatingLoader($resolver); diff --git a/vendor/symfony/config/Tests/Loader/LoaderResolverTest.php b/vendor/symfony/config/Tests/Loader/LoaderResolverTest.php index 487dc43adc..aabc2a600d 100644 --- a/vendor/symfony/config/Tests/Loader/LoaderResolverTest.php +++ b/vendor/symfony/config/Tests/Loader/LoaderResolverTest.php @@ -32,7 +32,7 @@ public function testResolve() $this->assertFalse($resolver->resolve('foo.foo'), '->resolve() returns false if no loader is able to load the resource'); $loader = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderInterface')->getMock(); - $loader->expects($this->once())->method('supports')->will($this->returnValue(true)); + $loader->expects($this->once())->method('supports')->willReturn(true); $resolver = new LoaderResolver([$loader]); $this->assertEquals($loader, $resolver->resolve(function () {}), '->resolve() returns the loader for the given resource'); } diff --git a/vendor/symfony/config/Tests/Loader/LoaderTest.php b/vendor/symfony/config/Tests/Loader/LoaderTest.php index 926cec8333..cd14d58fe5 100644 --- a/vendor/symfony/config/Tests/Loader/LoaderTest.php +++ b/vendor/symfony/config/Tests/Loader/LoaderTest.php @@ -34,7 +34,7 @@ public function testResolve() $resolver->expects($this->once()) ->method('resolve') ->with('foo.xml') - ->will($this->returnValue($resolvedLoader)); + ->willReturn($resolvedLoader); $loader = new ProjectLoader1(); $loader->setResolver($resolver); @@ -52,7 +52,7 @@ public function testResolveWhenResolverCannotFindLoader() $resolver->expects($this->once()) ->method('resolve') ->with('FOOBAR') - ->will($this->returnValue(false)); + ->willReturn(false); $loader = new ProjectLoader1(); $loader->setResolver($resolver); @@ -66,13 +66,13 @@ public function testImport() $resolvedLoader->expects($this->once()) ->method('load') ->with('foo') - ->will($this->returnValue('yes')); + ->willReturn('yes'); $resolver = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderResolverInterface')->getMock(); $resolver->expects($this->once()) ->method('resolve') ->with('foo') - ->will($this->returnValue($resolvedLoader)); + ->willReturn($resolvedLoader); $loader = new ProjectLoader1(); $loader->setResolver($resolver); @@ -86,13 +86,13 @@ public function testImportWithType() $resolvedLoader->expects($this->once()) ->method('load') ->with('foo', 'bar') - ->will($this->returnValue('yes')); + ->willReturn('yes'); $resolver = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderResolverInterface')->getMock(); $resolver->expects($this->once()) ->method('resolve') ->with('foo', 'bar') - ->will($this->returnValue($resolvedLoader)); + ->willReturn($resolvedLoader); $loader = new ProjectLoader1(); $loader->setResolver($resolver); diff --git a/vendor/symfony/config/Tests/Resource/ReflectionClassResourceTest.php b/vendor/symfony/config/Tests/Resource/ReflectionClassResourceTest.php index abc461cd7c..e22933245d 100644 --- a/vendor/symfony/config/Tests/Resource/ReflectionClassResourceTest.php +++ b/vendor/symfony/config/Tests/Resource/ReflectionClassResourceTest.php @@ -15,6 +15,7 @@ use Symfony\Component\Config\Resource\ReflectionClassResource; use Symfony\Component\DependencyInjection\ServiceSubscriberInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Messenger\Handler\MessageSubscriberInterface; class ReflectionClassResourceTest extends TestCase { @@ -147,6 +148,24 @@ public function testEventSubscriber() $this->assertTrue($res->isFresh(0)); } + public function testMessageSubscriber() + { + $res = new ReflectionClassResource(new \ReflectionClass(TestMessageSubscriber::class)); + $this->assertTrue($res->isFresh(0)); + + TestMessageSubscriberConfigHolder::$handledMessages = ['SomeMessageClass' => []]; + $this->assertFalse($res->isFresh(0)); + + $res = new ReflectionClassResource(new \ReflectionClass(TestMessageSubscriber::class)); + $this->assertTrue($res->isFresh(0)); + + TestMessageSubscriberConfigHolder::$handledMessages = ['OtherMessageClass' => []]; + $this->assertFalse($res->isFresh(0)); + + $res = new ReflectionClassResource(new \ReflectionClass(TestMessageSubscriber::class)); + $this->assertTrue($res->isFresh(0)); + } + public function testServiceSubscriber() { $res = new ReflectionClassResource(new \ReflectionClass(TestServiceSubscriber::class)); @@ -174,6 +193,20 @@ public static function getSubscribedEvents() } } +class TestMessageSubscriber implements MessageSubscriberInterface +{ + public static function getHandledMessages(): iterable + { + foreach (TestMessageSubscriberConfigHolder::$handledMessages as $key => $subscribedMessage) { + yield $key => $subscribedMessage; + } + } +} +class TestMessageSubscriberConfigHolder +{ + public static $handledMessages = []; +} + class TestServiceSubscriber implements ServiceSubscriberInterface { public static $subscribedServices = []; diff --git a/vendor/symfony/config/composer.json b/vendor/symfony/config/composer.json index 35fbffdb69..c1f2338e5a 100644 --- a/vendor/symfony/config/composer.json +++ b/vendor/symfony/config/composer.json @@ -24,6 +24,7 @@ "symfony/dependency-injection": "~3.4|~4.0", "symfony/event-dispatcher": "~3.4|~4.0", "symfony/finder": "~3.4|~4.0", + "symfony/messenger": "~4.1", "symfony/yaml": "~3.4|~4.0" }, "conflict": { @@ -41,7 +42,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } } } diff --git a/vendor/symfony/console/Application.php b/vendor/symfony/console/Application.php index 17df91f994..5a9b185519 100644 --- a/vendor/symfony/console/Application.php +++ b/vendor/symfony/console/Application.php @@ -44,6 +44,7 @@ use Symfony\Component\Debug\ErrorHandler; use Symfony\Component\Debug\Exception\FatalThrowableError; use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy; /** * An Application is the container for a collection of commands. @@ -90,9 +91,12 @@ public function __construct(string $name = 'UNKNOWN', string $version = 'UNKNOWN $this->defaultCommand = 'list'; } + /** + * @final since Symfony 4.3, the type-hint will be updated to the interface from symfony/contracts in 5.0 + */ public function setDispatcher(EventDispatcherInterface $dispatcher) { - $this->dispatcher = $dispatcher; + $this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher); } public function setCommandLoader(CommandLoaderInterface $commandLoader) @@ -235,7 +239,7 @@ public function doRun(InputInterface $input, OutputInterface $output) if (!($e instanceof CommandNotFoundException && !$e instanceof NamespaceNotFoundException) || 1 !== \count($alternatives = $e->getAlternatives()) || !$input->isInteractive()) { if (null !== $this->dispatcher) { $event = new ConsoleErrorEvent($input, $output, $e); - $this->dispatcher->dispatch(ConsoleEvents::ERROR, $event); + $this->dispatcher->dispatch($event, ConsoleEvents::ERROR); if (0 === $event->getExitCode()) { return 0; @@ -254,7 +258,7 @@ public function doRun(InputInterface $input, OutputInterface $output) if (!$style->confirm(sprintf('Do you want to run "%s" instead? ', $alternative), false)) { if (null !== $this->dispatcher) { $event = new ConsoleErrorEvent($input, $output, $e); - $this->dispatcher->dispatch(ConsoleEvents::ERROR, $event); + $this->dispatcher->dispatch($event, ConsoleEvents::ERROR); return $event->getExitCode(); } @@ -610,6 +614,15 @@ public function find($name) $this->init(); $aliases = []; + + foreach ($this->commands as $command) { + foreach ($command->getAliases() as $alias) { + if (!$this->has($alias)) { + $this->commands[$alias] = $command; + } + } + } + $allCommands = $this->commandLoader ? array_merge($this->commandLoader->getNames(), array_keys($this->commands)) : array_keys($this->commands); $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $name); $commands = preg_grep('{^'.$expr.'}', $allCommands); @@ -920,7 +933,7 @@ protected function doRunCommand(Command $command, InputInterface $input, OutputI $e = null; try { - $this->dispatcher->dispatch(ConsoleEvents::COMMAND, $event); + $this->dispatcher->dispatch($event, ConsoleEvents::COMMAND); if ($event->commandShouldRun()) { $exitCode = $command->run($input, $output); @@ -929,7 +942,7 @@ protected function doRunCommand(Command $command, InputInterface $input, OutputI } } catch (\Throwable $e) { $event = new ConsoleErrorEvent($input, $output, $e, $command); - $this->dispatcher->dispatch(ConsoleEvents::ERROR, $event); + $this->dispatcher->dispatch($event, ConsoleEvents::ERROR); $e = $event->getError(); if (0 === $exitCode = $event->getExitCode()) { @@ -938,7 +951,7 @@ protected function doRunCommand(Command $command, InputInterface $input, OutputI } $event = new ConsoleTerminateEvent($command, $input, $output, $exitCode); - $this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event); + $this->dispatcher->dispatch($event, ConsoleEvents::TERMINATE); if (null !== $e) { throw $e; diff --git a/vendor/symfony/console/CHANGELOG.md b/vendor/symfony/console/CHANGELOG.md index 399bbc2213..67decd30be 100644 --- a/vendor/symfony/console/CHANGELOG.md +++ b/vendor/symfony/console/CHANGELOG.md @@ -1,6 +1,14 @@ CHANGELOG ========= +4.3.0 +----- + + * added support for hyperlinks + * added `ProgressBar::iterate()` method that simplify updating the progress bar when iterating + * added `Question::setAutocompleterCallback()` to provide a callback function + that dynamically generates suggestions as the user types + 4.2.0 ----- diff --git a/vendor/symfony/console/Descriptor/JsonDescriptor.php b/vendor/symfony/console/Descriptor/JsonDescriptor.php index 197b843d4b..f5a143800b 100644 --- a/vendor/symfony/console/Descriptor/JsonDescriptor.php +++ b/vendor/symfony/console/Descriptor/JsonDescriptor.php @@ -97,7 +97,9 @@ protected function describeApplication(Application $application, array $options */ private function writeData(array $data, array $options) { - $this->write(json_encode($data, isset($options['json_encoding']) ? $options['json_encoding'] : 0)); + $flags = isset($options['json_encoding']) ? $options['json_encoding'] : 0; + + $this->write(json_encode($data, $flags)); } /** diff --git a/vendor/symfony/console/Exception/CommandNotFoundException.php b/vendor/symfony/console/Exception/CommandNotFoundException.php index 15ac522c67..69d5cb996a 100644 --- a/vendor/symfony/console/Exception/CommandNotFoundException.php +++ b/vendor/symfony/console/Exception/CommandNotFoundException.php @@ -24,9 +24,9 @@ class CommandNotFoundException extends \InvalidArgumentException implements Exce * @param string $message Exception message to throw * @param array $alternatives List of similar defined names * @param int $code Exception code - * @param \Exception $previous Previous exception used for the exception chaining + * @param \Throwable $previous Previous exception used for the exception chaining */ - public function __construct(string $message, array $alternatives = [], int $code = 0, \Exception $previous = null) + public function __construct(string $message, array $alternatives = [], int $code = 0, \Throwable $previous = null) { parent::__construct($message, $code, $previous); diff --git a/vendor/symfony/console/Formatter/OutputFormatter.php b/vendor/symfony/console/Formatter/OutputFormatter.php index 2b6db373d8..83f16d7731 100644 --- a/vendor/symfony/console/Formatter/OutputFormatter.php +++ b/vendor/symfony/console/Formatter/OutputFormatter.php @@ -141,7 +141,7 @@ public function formatAndWrap(string $message, int $width) { $offset = 0; $output = ''; - $tagRegex = '[a-z][a-z0-9,_=;-]*+'; + $tagRegex = '[a-z][^<>]*+'; $currentLineLength = 0; preg_match_all("#<(($tagRegex) | /($tagRegex)?)>#ix", $message, $matches, PREG_OFFSET_CAPTURE); foreach ($matches[0] as $i => $match) { @@ -216,6 +216,8 @@ private function createStyleFromString(string $string) $style->setForeground(strtolower($match[1])); } elseif ('bg' == $match[0]) { $style->setBackground(strtolower($match[1])); + } elseif ('href' === $match[0]) { + $style->setHref($match[1]); } elseif ('options' === $match[0]) { preg_match_all('([^,;]+)', strtolower($match[1]), $options); $options = array_shift($options); diff --git a/vendor/symfony/console/Formatter/OutputFormatterStyle.php b/vendor/symfony/console/Formatter/OutputFormatterStyle.php index dcc1beb46a..655bdd083e 100644 --- a/vendor/symfony/console/Formatter/OutputFormatterStyle.php +++ b/vendor/symfony/console/Formatter/OutputFormatterStyle.php @@ -52,7 +52,9 @@ class OutputFormatterStyle implements OutputFormatterStyleInterface private $foreground; private $background; + private $href; private $options = []; + private $handlesHrefGracefully; /** * Initializes output formatter style. @@ -118,6 +120,11 @@ public function setBackground($color = null) $this->background = static::$availableBackgroundColors[$color]; } + public function setHref(string $url): void + { + $this->href = $url; + } + /** * Sets some specific style option. * @@ -179,6 +186,10 @@ public function apply($text) $setCodes = []; $unsetCodes = []; + if (null === $this->handlesHrefGracefully) { + $this->handlesHrefGracefully = 'JetBrains-JediTerm' !== getenv('TERMINAL_EMULATOR') && !getenv('KONSOLE_VERSION'); + } + if (null !== $this->foreground) { $setCodes[] = $this->foreground['set']; $unsetCodes[] = $this->foreground['unset']; @@ -187,11 +198,14 @@ public function apply($text) $setCodes[] = $this->background['set']; $unsetCodes[] = $this->background['unset']; } - if (\count($this->options)) { - foreach ($this->options as $option) { - $setCodes[] = $option['set']; - $unsetCodes[] = $option['unset']; - } + + foreach ($this->options as $option) { + $setCodes[] = $option['set']; + $unsetCodes[] = $option['unset']; + } + + if (null !== $this->href && $this->handlesHrefGracefully) { + $text = "\033]8;;$this->href\033\\$text\033]8;;\033\\"; } if (0 === \count($setCodes)) { diff --git a/vendor/symfony/console/Helper/Dumper.php b/vendor/symfony/console/Helper/Dumper.php new file mode 100644 index 0000000000..b013b6c527 --- /dev/null +++ b/vendor/symfony/console/Helper/Dumper.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\Console\Helper; + +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\VarDumper\Cloner\ClonerInterface; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\CliDumper; + +/** + * @author Roland Franssen + */ +final class Dumper +{ + private $output; + private $dumper; + private $cloner; + private $handler; + + public function __construct(OutputInterface $output, CliDumper $dumper = null, ClonerInterface $cloner = null) + { + $this->output = $output; + $this->dumper = $dumper; + $this->cloner = $cloner; + + if (class_exists(CliDumper::class)) { + $this->handler = function ($var): string { + $dumper = $this->dumper ?? $this->dumper = new CliDumper(null, null, CliDumper::DUMP_LIGHT_ARRAY | CliDumper::DUMP_COMMA_SEPARATOR); + $dumper->setColors($this->output->isDecorated()); + + return rtrim($dumper->dump(($this->cloner ?? $this->cloner = new VarCloner())->cloneVar($var)->withRefHandles(false), true)); + }; + } else { + $this->handler = function ($var): string { + switch (true) { + case null === $var: + return 'null'; + case true === $var: + return 'true'; + case false === $var: + return 'false'; + case \is_string($var): + return '"'.$var.'"'; + default: + return rtrim(print_r($var, true)); + } + }; + } + } + + public function __invoke($var): string + { + return ($this->handler)($var); + } +} diff --git a/vendor/symfony/console/Helper/ProgressBar.php b/vendor/symfony/console/Helper/ProgressBar.php index 12a6cf2245..34e6aa5d2b 100644 --- a/vendor/symfony/console/Helper/ProgressBar.php +++ b/vendor/symfony/console/Helper/ProgressBar.php @@ -243,6 +243,24 @@ public function setRedrawFrequency(int $freq) $this->redrawFreq = max($freq, 1); } + /** + * Returns an iterator that will automatically update the progress bar when iterated. + * + * @param int|null $max Number of steps to complete the bar (0 if indeterminate), if null it will be inferred from $iterable + */ + public function iterate(iterable $iterable, ?int $max = null): iterable + { + $this->start($max ?? (\is_countable($iterable) ? \count($iterable) : 0)); + + foreach ($iterable as $key => $value) { + yield $key => $value; + + $this->advance(); + } + + $this->finish(); + } + /** * Starts the progress output. * diff --git a/vendor/symfony/console/Helper/QuestionHelper.php b/vendor/symfony/console/Helper/QuestionHelper.php index b8b76833a6..e6a700aa45 100644 --- a/vendor/symfony/console/Helper/QuestionHelper.php +++ b/vendor/symfony/console/Helper/QuestionHelper.php @@ -115,7 +115,7 @@ private function doAsk(OutputInterface $output, Question $question) $this->writePrompt($output, $question); $inputStream = $this->inputStream ?: STDIN; - $autocomplete = $question->getAutocompleterValues(); + $autocomplete = $question->getAutocompleterCallback(); if (null === $autocomplete || !$this->hasSttyAvailable()) { $ret = false; @@ -137,7 +137,7 @@ private function doAsk(OutputInterface $output, Question $question) $ret = trim($ret); } } else { - $ret = trim($this->autocomplete($output, $question, $inputStream, \is_array($autocomplete) ? $autocomplete : iterator_to_array($autocomplete, false))); + $ret = trim($this->autocomplete($output, $question, $inputStream, $autocomplete)); } if ($output instanceof ConsoleSectionOutput) { @@ -194,17 +194,16 @@ protected function writeError(OutputInterface $output, \Exception $error) /** * Autocompletes a question. * - * @param OutputInterface $output - * @param Question $question - * @param resource $inputStream + * @param resource $inputStream */ - private function autocomplete(OutputInterface $output, Question $question, $inputStream, array $autocomplete): string + private function autocomplete(OutputInterface $output, Question $question, $inputStream, callable $autocomplete): string { + $fullChoice = ''; $ret = ''; $i = 0; $ofs = -1; - $matches = $autocomplete; + $matches = $autocomplete($ret); $numMatches = \count($matches); $sttyMode = shell_exec('stty -g'); @@ -226,13 +225,14 @@ private function autocomplete(OutputInterface $output, Question $question, $inpu } elseif ("\177" === $c) { // Backspace Character if (0 === $numMatches && 0 !== $i) { --$i; + $fullChoice = substr($fullChoice, 0, -1); // Move cursor backwards $output->write("\033[1D"); } if (0 === $i) { $ofs = -1; - $matches = $autocomplete; + $matches = $autocomplete($ret); $numMatches = \count($matches); } else { $numMatches = 0; @@ -260,18 +260,27 @@ private function autocomplete(OutputInterface $output, Question $question, $inpu } elseif (\ord($c) < 32) { if ("\t" === $c || "\n" === $c) { if ($numMatches > 0 && -1 !== $ofs) { - $ret = $matches[$ofs]; + $ret = (string) $matches[$ofs]; // Echo out remaining chars for current match - $output->write(substr($ret, $i)); - $i = \strlen($ret); + $remainingCharacters = substr($ret, \strlen(trim($this->mostRecentlyEnteredValue($fullChoice)))); + $output->write($remainingCharacters); + $fullChoice .= $remainingCharacters; + $i = \strlen($fullChoice); + + $matches = array_filter( + $autocomplete($ret), + function ($match) use ($ret) { + return '' === $ret || 0 === strpos($match, $ret); + } + ); + $numMatches = \count($matches); + $ofs = -1; } if ("\n" === $c) { $output->write($c); break; } - - $numMatches = 0; } continue; @@ -282,14 +291,21 @@ private function autocomplete(OutputInterface $output, Question $question, $inpu $output->write($c); $ret .= $c; + $fullChoice .= $c; ++$i; + $tempRet = $ret; + + if ($question instanceof ChoiceQuestion && $question->isMultiselect()) { + $tempRet = $this->mostRecentlyEnteredValue($fullChoice); + } + $numMatches = 0; $ofs = 0; - foreach ($autocomplete as $value) { + foreach ($autocomplete($ret) as $value) { // If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle) - if (0 === strpos($value, $ret)) { + if (0 === strpos($value, $tempRet)) { $matches[$numMatches++] = $value; } } @@ -301,8 +317,9 @@ private function autocomplete(OutputInterface $output, Question $question, $inpu if ($numMatches > 0 && -1 !== $ofs) { // Save cursor position $output->write("\0337"); - // Write highlighted text - $output->write(''.OutputFormatter::escapeTrailingBackslash(substr($matches[$ofs], $i)).''); + // Write highlighted text, complete the partially entered response + $charactersEntered = \strlen(trim($this->mostRecentlyEnteredValue($fullChoice))); + $output->write(''.OutputFormatter::escapeTrailingBackslash(substr($matches[$ofs], $charactersEntered)).''); // Restore cursor position $output->write("\0338"); } @@ -311,7 +328,22 @@ private function autocomplete(OutputInterface $output, Question $question, $inpu // Reset stty so it behaves normally again shell_exec(sprintf('stty %s', $sttyMode)); - return $ret; + return $fullChoice; + } + + private function mostRecentlyEnteredValue($entered) + { + // Determine the most recent value that the user entered + if (false === strpos($entered, ',')) { + return $entered; + } + + $choices = explode(',', $entered); + if (\strlen($lastChoice = trim($choices[\count($choices) - 1])) > 0) { + return $lastChoice; + } + + return $entered; } /** diff --git a/vendor/symfony/console/Question/Question.php b/vendor/symfony/console/Question/Question.php index eac82cfad3..9201af2fd5 100644 --- a/vendor/symfony/console/Question/Question.php +++ b/vendor/symfony/console/Question/Question.php @@ -25,7 +25,7 @@ class Question private $attempts; private $hidden = false; private $hiddenFallback = true; - private $autocompleterValues; + private $autocompleterCallback; private $validator; private $default; private $normalizer; @@ -81,7 +81,7 @@ public function isHidden() */ public function setHidden($hidden) { - if ($this->autocompleterValues) { + if ($this->autocompleterCallback) { throw new LogicException('A hidden question cannot use the autocompleter.'); } @@ -121,7 +121,9 @@ public function setHiddenFallback($fallback) */ public function getAutocompleterValues() { - return $this->autocompleterValues; + $callback = $this->getAutocompleterCallback(); + + return $callback ? $callback('') : null; } /** @@ -138,17 +140,46 @@ public function setAutocompleterValues($values) { if (\is_array($values)) { $values = $this->isAssoc($values) ? array_merge(array_keys($values), array_values($values)) : array_values($values); - } - if (null !== $values && !\is_array($values) && !$values instanceof \Traversable) { + $callback = static function () use ($values) { + return $values; + }; + } elseif ($values instanceof \Traversable) { + $valueCache = null; + $callback = static function () use ($values, &$valueCache) { + return $valueCache ?? $valueCache = iterator_to_array($values, false); + }; + } elseif (null === $values) { + $callback = null; + } else { throw new InvalidArgumentException('Autocompleter values can be either an array, "null" or a "Traversable" object.'); } - if ($this->hidden) { + return $this->setAutocompleterCallback($callback); + } + + /** + * Gets the callback function used for the autocompleter. + */ + public function getAutocompleterCallback(): ?callable + { + return $this->autocompleterCallback; + } + + /** + * Sets the callback function used for the autocompleter. + * + * The callback is passed the user input as argument and should return an iterable of corresponding suggestions. + * + * @return $this + */ + public function setAutocompleterCallback(callable $callback = null): self + { + if ($this->hidden && null !== $callback) { throw new LogicException('A hidden question cannot use the autocompleter.'); } - $this->autocompleterValues = $values; + $this->autocompleterCallback = $callback; return $this; } diff --git a/vendor/symfony/console/Style/SymfonyStyle.php b/vendor/symfony/console/Style/SymfonyStyle.php index b46162de68..962ba923f3 100644 --- a/vendor/symfony/console/Style/SymfonyStyle.php +++ b/vendor/symfony/console/Style/SymfonyStyle.php @@ -154,7 +154,7 @@ public function error($message) */ public function warning($message) { - $this->block($message, 'WARNING', 'fg=white;bg=red', ' ', true); + $this->block($message, 'WARNING', 'fg=black;bg=yellow', ' ', true); } /** diff --git a/vendor/symfony/console/Tests/ApplicationTest.php b/vendor/symfony/console/Tests/ApplicationTest.php index efbe2a81fd..e153e5609a 100644 --- a/vendor/symfony/console/Tests/ApplicationTest.php +++ b/vendor/symfony/console/Tests/ApplicationTest.php @@ -73,8 +73,8 @@ public static function setUpBeforeClass() require_once self::$fixturesPath.'/FooSubnamespaced1Command.php'; require_once self::$fixturesPath.'/FooSubnamespaced2Command.php'; require_once self::$fixturesPath.'/FooWithoutAliasCommand.php'; - require_once self::$fixturesPath.'/TestTiti.php'; - require_once self::$fixturesPath.'/TestToto.php'; + require_once self::$fixturesPath.'/TestAmbiguousCommandRegistering.php'; + require_once self::$fixturesPath.'/TestAmbiguousCommandRegistering2.php'; } protected function normalizeLineBreaks($text) @@ -165,6 +165,28 @@ public function testRegister() $this->assertEquals('foo', $command->getName(), '->register() registers a new command'); } + public function testRegisterAmbiguous() + { + $code = function (InputInterface $input, OutputInterface $output) { + $output->writeln('It works!'); + }; + + $application = new Application(); + $application->setAutoExit(false); + $application + ->register('test-foo') + ->setAliases(['test']) + ->setCode($code); + + $application + ->register('test-bar') + ->setCode($code); + + $tester = new ApplicationTester($application); + $tester->run(['test']); + $this->assertContains('It works!', $tester->getDisplay(true)); + } + public function testAdd() { $application = new Application(); @@ -304,9 +326,9 @@ public function testFindAmbiguousNamespace() public function testFindNonAmbiguous() { $application = new Application(); - $application->add(new \TestTiti()); - $application->add(new \TestToto()); - $this->assertEquals('test-toto', $application->find('test')->getName()); + $application->add(new \TestAmbiguousCommandRegistering()); + $application->add(new \TestAmbiguousCommandRegistering2()); + $this->assertEquals('test-ambiguous', $application->find('test')->getName()); } /** @@ -687,7 +709,7 @@ public function testFindNamespaceDoesNotFailOnDeepSimilarNamespaces() $application = $this->getMockBuilder('Symfony\Component\Console\Application')->setMethods(['getNamespaces'])->getMock(); $application->expects($this->once()) ->method('getNamespaces') - ->will($this->returnValue(['foo:sublong', 'bar:sub'])); + ->willReturn(['foo:sublong', 'bar:sub']); $this->assertEquals('foo:sublong', $application->findNamespace('f:sub')); } @@ -831,7 +853,7 @@ public function testRenderExceptionLineBreaks() $application->setAutoExit(false); $application->expects($this->any()) ->method('getTerminalWidth') - ->will($this->returnValue(120)); + ->willReturn(120); $application->register('foo')->setCode(function () { throw new \InvalidArgumentException("\n\nline 1 with extra spaces \nline 2\n\nline 4\n"); }); diff --git a/vendor/symfony/console/Tests/Command/CommandTest.php b/vendor/symfony/console/Tests/Command/CommandTest.php index 512feca706..be73e5c941 100644 --- a/vendor/symfony/console/Tests/Command/CommandTest.php +++ b/vendor/symfony/console/Tests/Command/CommandTest.php @@ -188,7 +188,7 @@ public function testGetSetAliases() public function testSetAliasesNull() { $command = new \TestCommand(); - $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('InvalidArgumentException'); + $this->expectException('InvalidArgumentException'); $command->setAliases(null); } @@ -321,7 +321,7 @@ public function testRunReturnsIntegerExitCode() $command = $this->getMockBuilder('TestCommand')->setMethods(['execute'])->getMock(); $command->expects($this->once()) ->method('execute') - ->will($this->returnValue('2.3')); + ->willReturn('2.3'); $exitCode = $command->run(new StringInput(''), new NullOutput()); $this->assertSame(2, $exitCode, '->run() returns integer exit code (casts numeric to int)'); } diff --git a/vendor/symfony/console/Tests/Fixtures/TestAmbiguousCommandRegistering.php b/vendor/symfony/console/Tests/Fixtures/TestAmbiguousCommandRegistering.php new file mode 100644 index 0000000000..bece09fcdd --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/TestAmbiguousCommandRegistering.php @@ -0,0 +1,22 @@ +setName('test-ambiguous') + ->setDescription('The test-ambiguous command') + ->setAliases(['test']) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $output->write('test-ambiguous'); + } +} diff --git a/vendor/symfony/console/Tests/Fixtures/TestAmbiguousCommandRegistering2.php b/vendor/symfony/console/Tests/Fixtures/TestAmbiguousCommandRegistering2.php new file mode 100644 index 0000000000..9dde486245 --- /dev/null +++ b/vendor/symfony/console/Tests/Fixtures/TestAmbiguousCommandRegistering2.php @@ -0,0 +1,21 @@ +setName('test-ambiguous2') + ->setDescription('The test-ambiguous2 command') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $output->write('test-ambiguous2'); + } +} diff --git a/vendor/symfony/console/Tests/Formatter/OutputFormatterStyleTest.php b/vendor/symfony/console/Tests/Formatter/OutputFormatterStyleTest.php index d47760fe4e..d4559e8def 100644 --- a/vendor/symfony/console/Tests/Formatter/OutputFormatterStyleTest.php +++ b/vendor/symfony/console/Tests/Formatter/OutputFormatterStyleTest.php @@ -41,7 +41,7 @@ public function testForeground() $style->setForeground('default'); $this->assertEquals("\033[39mfoo\033[39m", $style->apply('foo')); - $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('InvalidArgumentException'); + $this->expectException('InvalidArgumentException'); $style->setForeground('undefined-color'); } @@ -58,7 +58,7 @@ public function testBackground() $style->setBackground('default'); $this->assertEquals("\033[49mfoo\033[49m", $style->apply('foo')); - $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('InvalidArgumentException'); + $this->expectException('InvalidArgumentException'); $style->setBackground('undefined-color'); } @@ -97,4 +97,19 @@ public function testOptions() $this->assertContains('Invalid option specified: "foo"', $e->getMessage(), '->unsetOption() throws an \InvalidArgumentException when the option does not exist in the available options'); } } + + public function testHref() + { + $prevTerminalEmulator = getenv('TERMINAL_EMULATOR'); + putenv('TERMINAL_EMULATOR'); + + $style = new OutputFormatterStyle(); + + try { + $style->setHref('idea://open/?file=/path/SomeFile.php&line=12'); + $this->assertSame("\e]8;;idea://open/?file=/path/SomeFile.php&line=12\e\\some URL\e]8;;\e\\", $style->apply('some URL')); + } finally { + putenv('TERMINAL_EMULATOR'.($prevTerminalEmulator ? "=$prevTerminalEmulator" : '')); + } + } } diff --git a/vendor/symfony/console/Tests/Formatter/OutputFormatterTest.php b/vendor/symfony/console/Tests/Formatter/OutputFormatterTest.php index ce81c353f0..86473b3a8f 100644 --- a/vendor/symfony/console/Tests/Formatter/OutputFormatterTest.php +++ b/vendor/symfony/console/Tests/Formatter/OutputFormatterTest.php @@ -228,7 +228,7 @@ public function testFormatToStringObject() ); } - public function testNotDecoratedFormatter() + public function testFormatterHasStyles() { $formatter = new OutputFormatter(false); @@ -236,40 +236,35 @@ public function testNotDecoratedFormatter() $this->assertTrue($formatter->hasStyle('info')); $this->assertTrue($formatter->hasStyle('comment')); $this->assertTrue($formatter->hasStyle('question')); + } - $this->assertEquals( - 'some error', $formatter->format('some error') - ); - $this->assertEquals( - 'some info', $formatter->format('some info') - ); - $this->assertEquals( - 'some comment', $formatter->format('some comment') - ); - $this->assertEquals( - 'some question', $formatter->format('some question') - ); - $this->assertEquals( - 'some text with inline style', $formatter->format('some text with inline style') - ); - - $formatter->setDecorated(true); + /** + * @dataProvider provideDecoratedAndNonDecoratedOutput + */ + public function testNotDecoratedFormatter(string $input, string $expectedNonDecoratedOutput, string $expectedDecoratedOutput, string $terminalEmulator = 'foo') + { + $prevTerminalEmulator = getenv('TERMINAL_EMULATOR'); + putenv('TERMINAL_EMULATOR='.$terminalEmulator); + + try { + $this->assertEquals($expectedDecoratedOutput, (new OutputFormatter(true))->format($input)); + $this->assertEquals($expectedNonDecoratedOutput, (new OutputFormatter(false))->format($input)); + } finally { + putenv('TERMINAL_EMULATOR'.($prevTerminalEmulator ? "=$prevTerminalEmulator" : '')); + } + } - $this->assertEquals( - "\033[37;41msome error\033[39;49m", $formatter->format('some error') - ); - $this->assertEquals( - "\033[32msome info\033[39m", $formatter->format('some info') - ); - $this->assertEquals( - "\033[33msome comment\033[39m", $formatter->format('some comment') - ); - $this->assertEquals( - "\033[30;46msome question\033[39;49m", $formatter->format('some question') - ); - $this->assertEquals( - "\033[31msome text with inline style\033[39m", $formatter->format('some text with inline style') - ); + public function provideDecoratedAndNonDecoratedOutput() + { + return [ + ['some error', 'some error', "\033[37;41msome error\033[39;49m"], + ['some info', 'some info', "\033[32msome info\033[39m"], + ['some comment', 'some comment', "\033[33msome comment\033[39m"], + ['some question', 'some question', "\033[30;46msome question\033[39;49m"], + ['some text with inline style', 'some text with inline style', "\033[31msome text with inline style\033[39m"], + ['some URL', 'some URL', "\033]8;;idea://open/?file=/path/SomeFile.php&line=12\033\\some URL\033]8;;\033\\"], + ['some URL', 'some URL', 'some URL', 'JetBrains-JediTerm'], + ]; } public function testContentWithLineBreaks() diff --git a/vendor/symfony/console/Tests/Helper/AbstractQuestionHelperTest.php b/vendor/symfony/console/Tests/Helper/AbstractQuestionHelperTest.php index 56dd65f6b4..f12566dedd 100644 --- a/vendor/symfony/console/Tests/Helper/AbstractQuestionHelperTest.php +++ b/vendor/symfony/console/Tests/Helper/AbstractQuestionHelperTest.php @@ -21,7 +21,7 @@ protected function createStreamableInputInterfaceMock($stream = null, $interacti $mock = $this->getMockBuilder(StreamableInputInterface::class)->getMock(); $mock->expects($this->any()) ->method('isInteractive') - ->will($this->returnValue($interactive)); + ->willReturn($interactive); if ($stream) { $mock->expects($this->any()) diff --git a/vendor/symfony/console/Tests/Helper/DumperNativeFallbackTest.php b/vendor/symfony/console/Tests/Helper/DumperNativeFallbackTest.php new file mode 100644 index 0000000000..b9fa2dc294 --- /dev/null +++ b/vendor/symfony/console/Tests/Helper/DumperNativeFallbackTest.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Helper; + +use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ClassExistsMock; +use Symfony\Component\Console\Helper\Dumper; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\VarDumper\Dumper\CliDumper; + +class DumperNativeFallbackTest extends TestCase +{ + public static function setUpBeforeClass() + { + ClassExistsMock::register(Dumper::class); + ClassExistsMock::withMockedClasses([ + CliDumper::class => false, + ]); + } + + public static function tearDownAfterClass() + { + ClassExistsMock::withMockedClasses([]); + } + + /** + * @dataProvider provideVariables + */ + public function testInvoke($variable, $primitiveString) + { + $dumper = new Dumper($this->getMockBuilder(OutputInterface::class)->getMock()); + + $this->assertSame($primitiveString, $dumper($variable)); + } + + public function provideVariables() + { + return [ + [null, 'null'], + [true, 'true'], + [false, 'false'], + [1, '1'], + [-1.5, '-1.5'], + ['string', '"string"'], + [[1, '2'], "Array\n(\n [0] => 1\n [1] => 2\n)"], + [new \stdClass(), "stdClass Object\n(\n)"], + ]; + } +} diff --git a/vendor/symfony/console/Tests/Helper/DumperTest.php b/vendor/symfony/console/Tests/Helper/DumperTest.php new file mode 100644 index 0000000000..00c480a6a9 --- /dev/null +++ b/vendor/symfony/console/Tests/Helper/DumperTest.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Helper; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Helper\Dumper; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\VarDumper\Test\VarDumperTestTrait; + +class DumperTest extends TestCase +{ + use VarDumperTestTrait; + + public static function setUpBeforeClass() + { + putenv('DUMP_LIGHT_ARRAY=1'); + putenv('DUMP_COMMA_SEPARATOR=1'); + } + + public static function tearDownAfterClass() + { + putenv('DUMP_LIGHT_ARRAY'); + putenv('DUMP_COMMA_SEPARATOR'); + } + + /** + * @dataProvider provideVariables + */ + public function testInvoke($variable) + { + $dumper = new Dumper($this->getMockBuilder(OutputInterface::class)->getMock()); + + $this->assertDumpMatchesFormat($dumper($variable), $variable); + } + + public function provideVariables() + { + return [ + [null], + [true], + [false], + [1], + [-1.5], + ['string'], + [[1, '2']], + [new \stdClass()], + ]; + } +} diff --git a/vendor/symfony/console/Tests/Helper/HelperSetTest.php b/vendor/symfony/console/Tests/Helper/HelperSetTest.php index 826bc51dcd..ffb12b3421 100644 --- a/vendor/symfony/console/Tests/Helper/HelperSetTest.php +++ b/vendor/symfony/console/Tests/Helper/HelperSetTest.php @@ -114,7 +114,7 @@ private function getGenericMockHelper($name, HelperSet $helperset = null) $mock_helper = $this->getMockBuilder('\Symfony\Component\Console\Helper\HelperInterface')->getMock(); $mock_helper->expects($this->any()) ->method('getName') - ->will($this->returnValue($name)); + ->willReturn($name); if ($helperset) { $mock_helper->expects($this->any()) diff --git a/vendor/symfony/console/Tests/Helper/ProgressBarTest.php b/vendor/symfony/console/Tests/Helper/ProgressBarTest.php index b0c834cc41..8d37d3af04 100644 --- a/vendor/symfony/console/Tests/Helper/ProgressBarTest.php +++ b/vendor/symfony/console/Tests/Helper/ProgressBarTest.php @@ -880,6 +880,41 @@ public function provideFormat() ]; } + public function testIterate(): void + { + $bar = new ProgressBar($output = $this->getOutputStream()); + + $this->assertEquals([1, 2], \iterator_to_array($bar->iterate([1, 2]))); + + rewind($output->getStream()); + $this->assertEquals( + ' 0/2 [>---------------------------] 0%'. + $this->generateOutput(' 1/2 [==============>-------------] 50%'). + $this->generateOutput(' 2/2 [============================] 100%'). + $this->generateOutput(' 2/2 [============================] 100%'), + stream_get_contents($output->getStream()) + ); + } + + public function testIterateUncountable(): void + { + $bar = new ProgressBar($output = $this->getOutputStream()); + + $this->assertEquals([1, 2], \iterator_to_array($bar->iterate((function () { + yield 1; + yield 2; + })()))); + + rewind($output->getStream()); + $this->assertEquals( + ' 0 [>---------------------------]'. + $this->generateOutput(' 1 [->--------------------------]'). + $this->generateOutput(' 2 [-->-------------------------]'). + $this->generateOutput(' 2 [============================]'), + stream_get_contents($output->getStream()) + ); + } + protected function getOutputStream($decorated = true, $verbosity = StreamOutput::VERBOSITY_NORMAL) { return new StreamOutput(fopen('php://memory', 'r+', false), $verbosity, $decorated); diff --git a/vendor/symfony/console/Tests/Helper/QuestionHelperTest.php b/vendor/symfony/console/Tests/Helper/QuestionHelperTest.php index 69d5470b8c..eca929fd30 100644 --- a/vendor/symfony/console/Tests/Helper/QuestionHelperTest.php +++ b/vendor/symfony/console/Tests/Helper/QuestionHelperTest.php @@ -198,6 +198,49 @@ public function testAskWithAutocomplete() $this->assertEquals('FooBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); } + public function testAskWithAutocompleteCallback() + { + if (!$this->hasSttyAvailable()) { + $this->markTestSkipped('`stty` is required to test autocomplete functionality'); + } + + // PoCrP + $inputStream = $this->getInputStream("Pa\177\177o\tCr\tP\033[A\033[A\n"); + + $dialog = new QuestionHelper(); + $helperSet = new HelperSet([new FormatterHelper()]); + $dialog->setHelperSet($helperSet); + + $question = new Question('What\'s for dinner?'); + + // A simple test callback - return an array containing the words the + // user has already completed, suffixed with all known words. + // + // Eg: If the user inputs "Potato C", the return will be: + // + // ["Potato Carrot ", "Potato Creme ", "Potato Curry ", ...] + // + // No effort is made to avoid irrelevant suggestions, as this is handled + // by the autocomplete function. + $callback = function ($input) { + $knownWords = ['Carrot', 'Creme', 'Curry', 'Parsnip', 'Pie', 'Potato', 'Tart']; + $inputWords = explode(' ', $input); + array_pop($inputWords); + $suggestionBase = $inputWords ? implode(' ', $inputWords).' ' : ''; + + return array_map( + function ($word) use ($suggestionBase) { + return $suggestionBase.$word.' '; + }, + $knownWords + ); + }; + + $question->setAutocompleterCallback($callback); + + $this->assertSame('Potato Creme Pie', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); + } + public function testAskWithAutocompleteWithNonSequentialKeys() { if (!$this->hasSttyAvailable()) { @@ -667,6 +710,37 @@ public function testTraversableAutocomplete() $this->assertEquals('FooBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); } + public function testTraversableMultiselectAutocomplete() + { + // + // F + // A<3x UP ARROW>,F + // F00o,A,SecurityBundle + // Acme,As<29x BACKSPACE>S + // Ac,As<3x BACKSPACE>d + $inputStream = $this->getInputStream("\nF\t\nA\033[A\033[A\033[A\t,F\t\nF00\177\177o\t,A\033[B\t, SecurityBundle\nSecurityBundle\nAcme\t, As\t\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177S\t\nAc\t,As\t\177\177\177d\t\n"); + + $dialog = new QuestionHelper(); + $helperSet = new HelperSet([new FormatterHelper()]); + $dialog->setHelperSet($helperSet); + + $question = new ChoiceQuestion( + 'Please select a bundle (defaults to AcmeDemoBundle and AsseticBundle)', + ['AcmeDemoBundle', 'AsseticBundle', 'SecurityBundle', 'FooBundle'], + '0,1' + ); + + // This tests that autocomplete works for all multiselect choices entered by the user + $question->setMultiselect(true); + + $this->assertEquals(['AcmeDemoBundle', 'AsseticBundle'], $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); + $this->assertEquals(['FooBundle'], $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); + $this->assertEquals(['AsseticBundle', 'FooBundle'], $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); + $this->assertEquals(['FooBundle', 'AsseticBundle', 'SecurityBundle'], $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); + $this->assertEquals(['SecurityBundle'], $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); + $this->assertEquals(['AcmeDemoBundle', 'AsseticBundle'], $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); + } + protected function getInputStream($input) { $stream = fopen('php://memory', 'r+', false); @@ -686,7 +760,7 @@ protected function createInputInterfaceMock($interactive = true) $mock = $this->getMockBuilder('Symfony\Component\Console\Input\InputInterface')->getMock(); $mock->expects($this->any()) ->method('isInteractive') - ->will($this->returnValue($interactive)); + ->willReturn($interactive); return $mock; } diff --git a/vendor/symfony/console/Tests/Helper/SymfonyQuestionHelperTest.php b/vendor/symfony/console/Tests/Helper/SymfonyQuestionHelperTest.php index cf7a78c34e..6f621db954 100644 --- a/vendor/symfony/console/Tests/Helper/SymfonyQuestionHelperTest.php +++ b/vendor/symfony/console/Tests/Helper/SymfonyQuestionHelperTest.php @@ -154,7 +154,7 @@ protected function createInputInterfaceMock($interactive = true) $mock = $this->getMockBuilder('Symfony\Component\Console\Input\InputInterface')->getMock(); $mock->expects($this->any()) ->method('isInteractive') - ->will($this->returnValue($interactive)); + ->willReturn($interactive); return $mock; } diff --git a/vendor/symfony/console/Tests/Logger/ConsoleLoggerTest.php b/vendor/symfony/console/Tests/Logger/ConsoleLoggerTest.php index efeec4234e..c99eb839b7 100644 --- a/vendor/symfony/console/Tests/Logger/ConsoleLoggerTest.php +++ b/vendor/symfony/console/Tests/Logger/ConsoleLoggerTest.php @@ -166,7 +166,7 @@ public function testObjectCastToString() } else { $dummy = $this->getMock('Symfony\Component\Console\Tests\Logger\DummyTest', ['__toString']); } - $dummy->method('__toString')->will($this->returnValue('DUMMY')); + $dummy->method('__toString')->willReturn('DUMMY'); $this->getLogger()->warning($dummy); diff --git a/vendor/symfony/console/Tests/Question/QuestionTest.php b/vendor/symfony/console/Tests/Question/QuestionTest.php new file mode 100644 index 0000000000..13c8e362e1 --- /dev/null +++ b/vendor/symfony/console/Tests/Question/QuestionTest.php @@ -0,0 +1,304 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Question; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Question\Question; + +class QuestionTest extends TestCase +{ + private $question; + + protected function setUp() + { + parent::setUp(); + $this->question = new Question('Test question'); + } + + public function providerTrueFalse() + { + return [[true], [false]]; + } + + public function testGetQuestion() + { + self::assertSame('Test question', $this->question->getQuestion()); + } + + public function testGetDefault() + { + $question = new Question('Test question', 'Default value'); + self::assertSame('Default value', $question->getDefault()); + } + + public function testGetDefaultDefault() + { + self::assertNull($this->question->getDefault()); + } + + /** + * @dataProvider providerTrueFalse + */ + public function testIsSetHidden(bool $hidden) + { + $this->question->setHidden($hidden); + self::assertSame($hidden, $this->question->isHidden()); + } + + public function testIsHiddenDefault() + { + self::assertFalse($this->question->isHidden()); + } + + public function testSetHiddenWithAutocompleterCallback() + { + $this->question->setAutocompleterCallback( + function (string $input): array { return []; } + ); + + $this->expectException(\LogicException::class); + $this->expectExceptionMessage( + 'A hidden question cannot use the autocompleter.' + ); + + $this->question->setHidden(true); + } + + public function testSetHiddenWithNoAutocompleterCallback() + { + $this->question->setAutocompleterCallback( + function (string $input): array { return []; } + ); + $this->question->setAutocompleterCallback(null); + + $exception = null; + try { + $this->question->setHidden(true); + } catch (\Exception $exception) { + // Do nothing + } + + $this->assertNull($exception); + } + + /** + * @dataProvider providerTrueFalse + */ + public function testIsSetHiddenFallback(bool $hidden) + { + $this->question->setHiddenFallback($hidden); + self::assertSame($hidden, $this->question->isHiddenFallback()); + } + + public function testIsHiddenFallbackDefault() + { + self::assertTrue($this->question->isHiddenFallback()); + } + + public function providerGetSetAutocompleterValues() + { + return [ + 'array' => [ + ['a', 'b', 'c', 'd'], + ['a', 'b', 'c', 'd'], + ], + 'associative array' => [ + ['a' => 'c', 'b' => 'd'], + ['a', 'b', 'c', 'd'], + ], + 'iterator' => [ + new \ArrayIterator(['a', 'b', 'c', 'd']), + ['a', 'b', 'c', 'd'], + ], + 'null' => [null, null], + ]; + } + + /** + * @dataProvider providerGetSetAutocompleterValues + */ + public function testGetSetAutocompleterValues($values, $expectValues) + { + $this->question->setAutocompleterValues($values); + self::assertSame( + $expectValues, + $this->question->getAutocompleterValues() + ); + } + + public function providerSetAutocompleterValuesInvalid() + { + return [ + ['Potato'], + [new \stdclass()], + [false], + ]; + } + + /** + * @dataProvider providerSetAutocompleterValuesInvalid + */ + public function testSetAutocompleterValuesInvalid($values) + { + self::expectException(InvalidArgumentException::class); + self::expectExceptionMessage( + 'Autocompleter values can be either an array, "null" or a "Traversable" object.' + ); + + $this->question->setAutocompleterValues($values); + } + + public function testSetAutocompleterValuesWithTraversable() + { + $question1 = new Question('Test question 1'); + $iterator1 = $this->getMockForAbstractClass(\IteratorAggregate::class); + $iterator1 + ->expects($this->once()) + ->method('getIterator') + ->willReturn(new \ArrayIterator(['Potato'])); + $question1->setAutocompleterValues($iterator1); + + $question2 = new Question('Test question 2'); + $iterator2 = $this->getMockForAbstractClass(\IteratorAggregate::class); + $iterator2 + ->expects($this->once()) + ->method('getIterator') + ->willReturn(new \ArrayIterator(['Carrot'])); + $question2->setAutocompleterValues($iterator2); + + // Call multiple times to verify that Traversable result is cached, and + // that there is no crosstalk between cached copies. + self::assertSame(['Potato'], $question1->getAutocompleterValues()); + self::assertSame(['Carrot'], $question2->getAutocompleterValues()); + self::assertSame(['Potato'], $question1->getAutocompleterValues()); + self::assertSame(['Carrot'], $question2->getAutocompleterValues()); + } + + public function testGetAutocompleterValuesDefault() + { + self::assertNull($this->question->getAutocompleterValues()); + } + + public function testGetSetAutocompleterCallback() + { + $callback = function (string $input): array { return []; }; + + $this->question->setAutocompleterCallback($callback); + self::assertSame($callback, $this->question->getAutocompleterCallback()); + } + + public function testGetAutocompleterCallbackDefault() + { + self::assertNull($this->question->getAutocompleterCallback()); + } + + public function testSetAutocompleterCallbackWhenHidden() + { + $this->question->setHidden(true); + + $this->expectException(\LogicException::class); + $this->expectExceptionMessage( + 'A hidden question cannot use the autocompleter.' + ); + + $this->question->setAutocompleterCallback( + function (string $input): array { return []; } + ); + } + + public function testSetAutocompleterCallbackWhenNotHidden() + { + $this->question->setHidden(true); + $this->question->setHidden(false); + + $exception = null; + try { + $this->question->setAutocompleterCallback( + function (string $input): array { return []; } + ); + } catch (\Exception $exception) { + // Do nothing + } + + $this->assertNull($exception); + } + + public function providerGetSetValidator() + { + return [ + [function ($input) { return $input; }], + [null], + ]; + } + + /** + * @dataProvider providerGetSetValidator + */ + public function testGetSetValidator($callback) + { + $this->question->setValidator($callback); + self::assertSame($callback, $this->question->getValidator()); + } + + public function testGetValidatorDefault() + { + self::assertNull($this->question->getValidator()); + } + + public function providerGetSetMaxAttempts() + { + return [[1], [5], [null]]; + } + + /** + * @dataProvider providerGetSetMaxAttempts + */ + public function testGetSetMaxAttempts($attempts) + { + $this->question->setMaxAttempts($attempts); + self::assertSame($attempts, $this->question->getMaxAttempts()); + } + + public function providerSetMaxAttemptsInvalid() + { + return [['Potato'], [0], [-1]]; + } + + /** + * @dataProvider providerSetMaxAttemptsInvalid + */ + public function testSetMaxAttemptsInvalid($attempts) + { + self::expectException(\InvalidArgumentException::class); + self::expectExceptionMessage('Maximum number of attempts must be a positive value.'); + + $this->question->setMaxAttempts($attempts); + } + + public function testGetMaxAttemptsDefault() + { + self::assertNull($this->question->getMaxAttempts()); + } + + public function testGetSetNormalizer() + { + $normalizer = function ($input) { return $input; }; + $this->question->setNormalizer($normalizer); + self::assertSame($normalizer, $this->question->getNormalizer()); + } + + public function testGetNormalizerDefault() + { + self::assertNull($this->question->getNormalizer()); + } +} diff --git a/vendor/symfony/console/composer.json b/vendor/symfony/console/composer.json index 33922220b9..5613467fd2 100644 --- a/vendor/symfony/console/composer.json +++ b/vendor/symfony/console/composer.json @@ -17,15 +17,17 @@ ], "require": { "php": "^7.1.3", - "symfony/contracts": "^1.0", - "symfony/polyfill-mbstring": "~1.0" + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.8", + "symfony/service-contracts": "^1.1" }, "require-dev": { "symfony/config": "~3.4|~4.0", - "symfony/event-dispatcher": "~3.4|~4.0", + "symfony/event-dispatcher": "^4.3", "symfony/dependency-injection": "~3.4|~4.0", "symfony/lock": "~3.4|~4.0", "symfony/process": "~3.4|~4.0", + "symfony/var-dumper": "^4.3", "psr/log": "~1.0" }, "provide": { @@ -39,6 +41,7 @@ }, "conflict": { "symfony/dependency-injection": "<3.4", + "symfony/event-dispatcher": "<4.3", "symfony/process": "<3.3" }, "autoload": { @@ -50,7 +53,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } } } diff --git a/vendor/symfony/filesystem/CHANGELOG.md b/vendor/symfony/filesystem/CHANGELOG.md index 9f1f817e75..f6453c16e3 100644 --- a/vendor/symfony/filesystem/CHANGELOG.md +++ b/vendor/symfony/filesystem/CHANGELOG.md @@ -1,6 +1,12 @@ CHANGELOG ========= +4.3.0 +----- + + * support for passing arrays to `Filesystem::dumpFile()` is deprecated and will be removed in 5.0 + * support for passing arrays to `Filesystem::appendToFile()` is deprecated and will be removed in 5.0 + 4.0.0 ----- diff --git a/vendor/symfony/filesystem/Filesystem.php b/vendor/symfony/filesystem/Filesystem.php index 082084b4d9..fa88fd0d05 100644 --- a/vendor/symfony/filesystem/Filesystem.php +++ b/vendor/symfony/filesystem/Filesystem.php @@ -541,6 +541,10 @@ public function mirror($originDir, $targetDir, \Traversable $iterator = null, $o $originDir = rtrim($originDir, '/\\'); $originDirLen = \strlen($originDir); + if (!$this->exists($originDir)) { + throw new IOException(sprintf('The origin directory specified "%s" was not found.', $originDir), 0, null, $originDir); + } + // Iterate in destination folder to remove obsolete entries if ($this->exists($targetDir) && isset($options['delete']) && $options['delete']) { $deleteIterator = $iterator; @@ -564,35 +568,24 @@ public function mirror($originDir, $targetDir, \Traversable $iterator = null, $o $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($originDir, $flags), \RecursiveIteratorIterator::SELF_FIRST); } - if ($this->exists($originDir)) { - $this->mkdir($targetDir); - } + $this->mkdir($targetDir); + $targetDirInfo = new \SplFileInfo($targetDir); foreach ($iterator as $file) { - if (false === strpos($file->getPath(), $originDir)) { - throw new IOException(sprintf('Unable to mirror "%s" directory. If the origin directory is relative, try using "realpath" before calling the mirror method.', $originDir), 0, null, $originDir); + if ($file->getPathname() === $targetDir || $file->getRealPath() === $targetDir || 0 === strpos($file->getRealPath(), $targetDirInfo->getRealPath())) { + continue; } $target = $targetDir.substr($file->getPathname(), $originDirLen); - if ($copyOnWindows) { - if (is_file($file)) { - $this->copy($file, $target, isset($options['override']) ? $options['override'] : false); - } elseif (is_dir($file)) { - $this->mkdir($target); - } else { - throw new IOException(sprintf('Unable to guess "%s" file type.', $file), 0, null, $file); - } + if (!$copyOnWindows && is_link($file)) { + $this->symlink($file->getLinkTarget(), $target); + } elseif (is_dir($file)) { + $this->mkdir($target); + } elseif (is_file($file)) { + $this->copy($file, $target, isset($options['override']) ? $options['override'] : false); } else { - if (is_link($file)) { - $this->symlink($file->getLinkTarget(), $target); - } elseif (is_dir($file)) { - $this->mkdir($target); - } elseif (is_file($file)) { - $this->copy($file, $target, isset($options['override']) ? $options['override'] : false); - } else { - throw new IOException(sprintf('Unable to guess "%s" file type.', $file), 0, null, $file); - } + throw new IOException(sprintf('Unable to guess "%s" file type.', $file), 0, null, $file); } } } @@ -670,13 +663,17 @@ public function tempnam($dir, $prefix) /** * Atomically dumps content into a file. * - * @param string $filename The file to be written to - * @param string $content The data to write into the file + * @param string $filename The file to be written to + * @param string|resource $content The data to write into the file * * @throws IOException if the file cannot be written to */ public function dumpFile($filename, $content) { + if (\is_array($content)) { + @trigger_error(sprintf('Calling "%s()" with an array in the $content argument is deprecated since Symfony 4.3.', __METHOD__), E_USER_DEPRECATED); + } + $dir = \dirname($filename); if (!is_dir($dir)) { @@ -703,13 +700,17 @@ public function dumpFile($filename, $content) /** * Appends content to an existing file. * - * @param string $filename The file to which to append content - * @param string $content The content to append + * @param string $filename The file to which to append content + * @param string|resource $content The content to append * * @throws IOException If the file is not writable */ public function appendToFile($filename, $content) { + if (\is_array($content)) { + @trigger_error(sprintf('Calling "%s()" with an array in the $content argument is deprecated since Symfony 4.3.', __METHOD__), E_USER_DEPRECATED); + } + $dir = \dirname($filename); if (!is_dir($dir)) { @@ -750,7 +751,6 @@ private static function box($func) return $result; } catch (\Throwable $e) { - } catch (\Exception $e) { } \restore_error_handler(); diff --git a/vendor/symfony/filesystem/Tests/FilesystemTest.php b/vendor/symfony/filesystem/Tests/FilesystemTest.php index 8ad94a1e28..a1a3ce0e6e 100644 --- a/vendor/symfony/filesystem/Tests/FilesystemTest.php +++ b/vendor/symfony/filesystem/Tests/FilesystemTest.php @@ -1332,44 +1332,34 @@ public function testMirrorContentsWithSameNameAsSourceOrTargetWithDeleteOption() $this->assertFileNotExists($targetPath.'target'); } - public function testMirrorWithCustomIterator() + public function testMirrorAvoidCopyingTargetDirectoryIfInSourceDirectory() { $sourcePath = $this->workspace.\DIRECTORY_SEPARATOR.'source'.\DIRECTORY_SEPARATOR; - mkdir($sourcePath); - - $file = $sourcePath.\DIRECTORY_SEPARATOR.'file'; - file_put_contents($file, 'FILE'); - - $targetPath = $this->workspace.\DIRECTORY_SEPARATOR.'target'.\DIRECTORY_SEPARATOR; - - $splFile = new \SplFileInfo($file); - $iterator = new \ArrayObject([$splFile]); + $directory = $sourcePath.'directory'.\DIRECTORY_SEPARATOR; + $file1 = $directory.'file1'; + $file2 = $sourcePath.'file2'; - $this->filesystem->mirror($sourcePath, $targetPath, $iterator); + mkdir($sourcePath); + mkdir($directory); + file_put_contents($file1, 'FILE1'); + file_put_contents($file2, 'FILE2'); - $this->assertTrue(is_dir($targetPath)); - $this->assertFileEquals($file, $targetPath.\DIRECTORY_SEPARATOR.'file'); - } + $targetPath = $sourcePath.'target'.\DIRECTORY_SEPARATOR; - /** - * @expectedException \Symfony\Component\Filesystem\Exception\IOException - * @expectedExceptionMessageRegExp /Unable to mirror "(.*)" directory/ - */ - public function testMirrorWithCustomIteratorWithRelativePath() - { - $sourcePath = $this->workspace.\DIRECTORY_SEPARATOR.'source'.\DIRECTORY_SEPARATOR.'..'.\DIRECTORY_SEPARATOR.'source'.\DIRECTORY_SEPARATOR; - $realSourcePath = $this->workspace.\DIRECTORY_SEPARATOR.'source'.\DIRECTORY_SEPARATOR; - mkdir($realSourcePath); + if ('\\' !== \DIRECTORY_SEPARATOR) { + $this->filesystem->symlink($targetPath, $sourcePath.'target_simlink'); + } - $file = $realSourcePath.'file'; - file_put_contents($file, 'FILE'); + $this->filesystem->mirror($sourcePath, $targetPath, null, ['delete' => true]); - $targetPath = $this->workspace.\DIRECTORY_SEPARATOR.'target'.\DIRECTORY_SEPARATOR.'..'.\DIRECTORY_SEPARATOR.'target'.\DIRECTORY_SEPARATOR; + $this->assertTrue($this->filesystem->exists($targetPath)); + $this->assertTrue($this->filesystem->exists($targetPath.'directory')); - $splFile = new \SplFileInfo($file); - $iterator = new \ArrayObject([$splFile]); + $this->assertFileEquals($file1, $targetPath.'directory'.\DIRECTORY_SEPARATOR.'file1'); + $this->assertFileEquals($file2, $targetPath.'file2'); - $this->filesystem->mirror($sourcePath, $targetPath, $iterator); + $this->assertFalse($this->filesystem->exists($targetPath.'target_simlink')); + $this->assertFalse($this->filesystem->exists($targetPath.'target')); } /** @@ -1518,6 +1508,10 @@ public function testDumpFile() } } + /** + * @group legacy + * @expectedDeprecation Calling "Symfony\Component\Filesystem\Filesystem::dumpFile()" with an array in the $content argument is deprecated since Symfony 4.3. + */ public function testDumpFileWithArray() { $filename = $this->workspace.\DIRECTORY_SEPARATOR.'foo'.\DIRECTORY_SEPARATOR.'baz.txt'; @@ -1600,6 +1594,60 @@ public function testAppendToFile() } } + /** + * @group legacy + * @expectedDeprecation Calling "Symfony\Component\Filesystem\Filesystem::appendToFile()" with an array in the $content argument is deprecated since Symfony 4.3. + */ + public function testAppendToFileWithArray() + { + $filename = $this->workspace.\DIRECTORY_SEPARATOR.'foo'.\DIRECTORY_SEPARATOR.'bar.txt'; + + // skip mode check on Windows + if ('\\' !== \DIRECTORY_SEPARATOR) { + $oldMask = umask(0002); + } + + $this->filesystem->dumpFile($filename, 'foo'); + + $this->filesystem->appendToFile($filename, ['bar']); + + $this->assertFileExists($filename); + $this->assertStringEqualsFile($filename, 'foobar'); + + // skip mode check on Windows + if ('\\' !== \DIRECTORY_SEPARATOR) { + $this->assertFilePermissions(664, $filename); + umask($oldMask); + } + } + + public function testAppendToFileWithResource() + { + $filename = $this->workspace.\DIRECTORY_SEPARATOR.'foo'.\DIRECTORY_SEPARATOR.'bar.txt'; + + // skip mode check on Windows + if ('\\' !== \DIRECTORY_SEPARATOR) { + $oldMask = umask(0002); + } + + $this->filesystem->dumpFile($filename, 'foo'); + + $resource = fopen('php://memory', 'rw'); + fwrite($resource, 'bar'); + fseek($resource, 0); + + $this->filesystem->appendToFile($filename, $resource); + + $this->assertFileExists($filename); + $this->assertStringEqualsFile($filename, 'foobar'); + + // skip mode check on Windows + if ('\\' !== \DIRECTORY_SEPARATOR) { + $this->assertFilePermissions(664, $filename); + umask($oldMask); + } + } + public function testAppendToFileWithScheme() { $scheme = 'file://'; diff --git a/vendor/symfony/filesystem/composer.json b/vendor/symfony/filesystem/composer.json index ee8a319a7d..d13397b424 100644 --- a/vendor/symfony/filesystem/composer.json +++ b/vendor/symfony/filesystem/composer.json @@ -28,7 +28,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } } } diff --git a/vendor/symfony/polyfill-php73/LICENSE b/vendor/symfony/polyfill-php73/LICENSE new file mode 100644 index 0000000000..3f853aaf35 --- /dev/null +++ b/vendor/symfony/polyfill-php73/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2019 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/vendor/symfony/polyfill-php73/Php73.php b/vendor/symfony/polyfill-php73/Php73.php new file mode 100644 index 0000000000..aa3708f64f --- /dev/null +++ b/vendor/symfony/polyfill-php73/Php73.php @@ -0,0 +1,34 @@ + + * @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 array($s, (int) $ns); + } +} diff --git a/vendor/symfony/polyfill-php73/README.md b/vendor/symfony/polyfill-php73/README.md new file mode 100644 index 0000000000..b3ebbce511 --- /dev/null +++ b/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/master/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-php73/Resources/stubs/JsonException.php b/vendor/symfony/polyfill-php73/Resources/stubs/JsonException.php new file mode 100644 index 0000000000..673d100224 --- /dev/null +++ b/vendor/symfony/polyfill-php73/Resources/stubs/JsonException.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +class JsonException extends Exception +{ +} diff --git a/vendor/symfony/polyfill-php73/bootstrap.php b/vendor/symfony/polyfill-php73/bootstrap.php new file mode 100644 index 0000000000..049b8bb8aa --- /dev/null +++ b/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) { + if (!function_exists('is_countable')) { + function is_countable($var) { return is_array($var) || $var instanceof Countable || $var instanceof ResourceBundle || $var instanceof SimpleXmlElement; } + } + + if (!function_exists('hrtime')) { + p\Php73::$startAt = (int) microtime(true); + function hrtime($asNum = false) { return p\Php73::hrtime($asNum); } + } + + 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) { end($array); return key($array); } + } +} diff --git a/vendor/symfony/polyfill-php73/composer.json b/vendor/symfony/polyfill-php73/composer.json new file mode 100644 index 0000000000..e98167ed51 --- /dev/null +++ b/vendor/symfony/polyfill-php73/composer.json @@ -0,0 +1,32 @@ +{ + "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": ">=5.3.3" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php73\\": "" }, + "files": [ "bootstrap.php" ], + "classmap": [ "Resources/stubs" ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + } +} diff --git a/vendor/symfony/service-contracts/LICENSE b/vendor/symfony/service-contracts/LICENSE new file mode 100644 index 0000000000..3f853aaf35 --- /dev/null +++ b/vendor/symfony/service-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2019 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/vendor/symfony/service-contracts/README.md b/vendor/symfony/service-contracts/README.md new file mode 100644 index 0000000000..d033a439b9 --- /dev/null +++ b/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/master/README.md for more information. diff --git a/vendor/symfony/service-contracts/ResetInterface.php b/vendor/symfony/service-contracts/ResetInterface.php new file mode 100644 index 0000000000..1af1075eee --- /dev/null +++ b/vendor/symfony/service-contracts/ResetInterface.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\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 +{ + public function reset(); +} diff --git a/vendor/symfony/service-contracts/ServiceLocatorTrait.php b/vendor/symfony/service-contracts/ServiceLocatorTrait.php new file mode 100644 index 0000000000..71b1b7460d --- /dev/null +++ b/vendor/symfony/service-contracts/ServiceLocatorTrait.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\Contracts\Service; + +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\NotFoundExceptionInterface; + +/** + * A trait to help implement ServiceProviderInterface. + * + * @author Robin Chalas + * @author Nicolas Grekas + */ +trait ServiceLocatorTrait +{ + private $factories; + private $loading = []; + private $providedTypes; + + /** + * @param callable[] $factories + */ + public function __construct(array $factories) + { + $this->factories = $factories; + } + + /** + * {@inheritdoc} + */ + public function has($id) + { + return isset($this->factories[$id]); + } + + /** + * {@inheritdoc} + */ + public function get($id) + { + if (!isset($this->factories[$id])) { + throw $this->createNotFoundException($id); + } + + if (isset($this->loading[$id])) { + $ids = array_values($this->loading); + $ids = \array_slice($this->loading, array_search($id, $ids)); + $ids[] = $id; + + throw $this->createCircularReferenceException($id, $ids); + } + + $this->loading[$id] = $id; + try { + return $this->factories[$id]($this); + } finally { + unset($this->loading[$id]); + } + } + + /** + * {@inheritdoc} + */ + public function getProvidedServices(): array + { + if (null === $this->providedTypes) { + $this->providedTypes = []; + + foreach ($this->factories as $name => $factory) { + if (!\is_callable($factory)) { + $this->providedTypes[$name] = '?'; + } else { + $type = (new \ReflectionFunction($factory))->getReturnType(); + + $this->providedTypes[$name] = $type ? ($type->allowsNull() ? '?' : '').$type->getName() : '?'; + } + } + } + + 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/vendor/symfony/service-contracts/ServiceProviderInterface.php b/vendor/symfony/service-contracts/ServiceProviderInterface.php new file mode 100644 index 0000000000..c60ad0bd4b --- /dev/null +++ b/vendor/symfony/service-contracts/ServiceProviderInterface.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\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 + */ +interface ServiceProviderInterface extends ContainerInterface +{ + /** + * 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/vendor/symfony/service-contracts/ServiceSubscriberInterface.php b/vendor/symfony/service-contracts/ServiceSubscriberInterface.php new file mode 100644 index 0000000000..8bb320f5b3 --- /dev/null +++ b/vendor/symfony/service-contracts/ServiceSubscriberInterface.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\Contracts\Service; + +/** + * 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 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'] + * + * @return array The required service types, optionally keyed by service names + */ + public static function getSubscribedServices(); +} diff --git a/vendor/symfony/service-contracts/ServiceSubscriberTrait.php b/vendor/symfony/service-contracts/ServiceSubscriberTrait.php new file mode 100644 index 0000000000..ceaef6fa14 --- /dev/null +++ b/vendor/symfony/service-contracts/ServiceSubscriberTrait.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\Contracts\Service; + +use Psr\Container\ContainerInterface; + +/** + * Implementation of ServiceSubscriberInterface that determines subscribed services from + * private method return types. Service ids are available as "ClassName::methodName". + * + * @author Kevin Bond + */ +trait ServiceSubscriberTrait +{ + /** @var ContainerInterface */ + private $container; + + public static function getSubscribedServices(): array + { + static $services; + + if (null !== $services) { + return $services; + } + + $services = \is_callable(['parent', __FUNCTION__]) ? parent::getSubscribedServices() : []; + + foreach ((new \ReflectionClass(self::class))->getMethods() as $method) { + if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) { + continue; + } + + if (self::class === $method->getDeclaringClass()->name && ($returnType = $method->getReturnType()) && !$returnType->isBuiltin()) { + $services[self::class.'::'.$method->name] = '?'.$returnType->getName(); + } + } + + return $services; + } + + /** + * @required + */ + public function setContainer(ContainerInterface $container) + { + $this->container = $container; + + if (\is_callable(['parent', __FUNCTION__])) { + return parent::setContainer($container); + } + } +} diff --git a/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php b/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php new file mode 100644 index 0000000000..69594583f5 --- /dev/null +++ b/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php @@ -0,0 +1,94 @@ + + * + * 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; + +class ServiceLocatorTest extends TestCase +{ + public function getServiceLocator(array $factories) + { + 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); + } + + /** + * @expectedException \Psr\Container\NotFoundExceptionInterface + * @expectedExceptionMessage The service "foo" has a dependency on a non-existent service "bar". This locator only knows about the "foo" service. + */ + public function testThrowsOnUndefinedInternalService() + { + $locator = $this->getServiceLocator([ + 'foo' => function () use (&$locator) { return $locator->get('bar'); }, + ]); + + $locator->get('foo'); + } + + /** + * @expectedException \Psr\Container\ContainerExceptionInterface + * @expectedExceptionMessage Circular reference detected for service "bar", path: "bar -> baz -> bar". + */ + public function testThrowsOnCircularReference() + { + $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/vendor/symfony/service-contracts/composer.json b/vendor/symfony/service-contracts/composer.json new file mode 100644 index 0000000000..54341174ce --- /dev/null +++ b/vendor/symfony/service-contracts/composer.json @@ -0,0 +1,34 @@ +{ + "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": "^7.1.3" + }, + "suggest": { + "psr/container": "", + "symfony/service-implementation": "" + }, + "autoload": { + "psr-4": { "Symfony\\Contracts\\Service\\": "" } + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + } +} diff --git a/vendor/symfony/translation-contracts/LICENSE b/vendor/symfony/translation-contracts/LICENSE new file mode 100644 index 0000000000..3f853aaf35 --- /dev/null +++ b/vendor/symfony/translation-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2019 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/vendor/symfony/translation-contracts/LocaleAwareInterface.php b/vendor/symfony/translation-contracts/LocaleAwareInterface.php new file mode 100644 index 0000000000..dbd8894fe7 --- /dev/null +++ b/vendor/symfony/translation-contracts/LocaleAwareInterface.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\Contracts\Translation; + +interface LocaleAwareInterface +{ + /** + * Sets the current locale. + * + * @param string $locale The locale + * + * @throws \InvalidArgumentException If the locale contains invalid characters + */ + public function setLocale($locale); + + /** + * Returns the current locale. + * + * @return string The locale + */ + public function getLocale(); +} diff --git a/vendor/symfony/translation-contracts/README.md b/vendor/symfony/translation-contracts/README.md new file mode 100644 index 0000000000..6c693ce0b3 --- /dev/null +++ b/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/master/README.md for more information. diff --git a/vendor/symfony/translation-contracts/Test/TranslatorTest.php b/vendor/symfony/translation-contracts/Test/TranslatorTest.php new file mode 100644 index 0000000000..48466300b5 --- /dev/null +++ b/vendor/symfony/translation-contracts/Test/TranslatorTest.php @@ -0,0 +1,353 @@ + + * + * 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 +{ + public function getTranslator() + { + 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(); + $translator->setLocale('en'); + + $this->assertEquals($expected, $translator->trans($id, ['%count%' => $number])); + } + + /** + * @dataProvider getTransChoiceTests + */ + public function testTransChoiceWithDefaultLocale($expected, $id, $number) + { + \Locale::setDefault('en'); + + $translator = $this->getTranslator(); + + $this->assertEquals($expected, $translator->trans($id, ['%count%' => $number])); + } + + public function testGetSetLocale() + { + $translator = $this->getTranslator(); + $translator->setLocale('en'); + + $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 function getTransTests() + { + return [ + ['Symfony is great!', 'Symfony is great!', []], + ['Symfony is awesome!', 'Symfony is %what%!', ['%what%' => 'awesome']], + ]; + } + + public 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 getInternal + */ + public function testInterval($expected, $number, $interval) + { + $translator = $this->getTranslator(); + + $this->assertEquals($expected, $translator->trans($interval.' foo|[1,Inf[ bar', ['%count%' => $number])); + } + + public function getInternal() + { + 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) + { + $translator = $this->getTranslator(); + + $this->assertEquals($expected, $translator->trans($id, ['%count%' => $number])); + } + + public function testReturnMessageIfExactlyOneStandardRuleIsGiven() + { + $translator = $this->getTranslator(); + + $this->assertEquals('There are two apples', $translator->trans('There are two apples', ['%count%' => 2])); + } + + /** + * @dataProvider getNonMatchingMessages + * @expectedException \InvalidArgumentException + */ + public function testThrowExceptionIfMatchingMessageCannotBeFound($id, $number) + { + $translator = $this->getTranslator(); + + $translator->trans($id, ['%count%' => $number]); + } + + public 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 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 accros 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 accros 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 accros 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], + // esacape 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], + ]; + } + + /** + * @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. + * + * @return array + */ + public function successLangcodes() + { + return [ + ['1', ['ay', 'bo', 'cgg', 'dz', 'id', 'ja', 'jbo', 'ka', 'kk', 'km', 'ko', 'ky']], + ['2', ['nl', 'fr', 'en', 'de', 'de_GE', 'hy', 'hy_AM']], + ['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 function failingLangcodes() + { + 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 + * @param bool $expectSuccess + */ + protected function validateMatrix($nplural, $matrix, $expectSuccess = true) + { + foreach ($matrix as $langCode => $data) { + $indexes = array_flip($data); + if ($expectSuccess) { + $this->assertEquals($nplural, \count($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/vendor/symfony/translation-contracts/TranslatorInterface.php b/vendor/symfony/translation-contracts/TranslatorInterface.php new file mode 100644 index 0000000000..d867637370 --- /dev/null +++ b/vendor/symfony/translation-contracts/TranslatorInterface.php @@ -0,0 +1,65 @@ + + * + * 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 + * + * @return string The translated string + * + * @throws \InvalidArgumentException If the locale contains invalid characters + */ + public function trans($id, array $parameters = [], $domain = null, $locale = null); +} diff --git a/vendor/symfony/translation-contracts/TranslatorTrait.php b/vendor/symfony/translation-contracts/TranslatorTrait.php new file mode 100644 index 0000000000..c1021923c8 --- /dev/null +++ b/vendor/symfony/translation-contracts/TranslatorTrait.php @@ -0,0 +1,255 @@ + + * + * 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 $locale; + + /** + * {@inheritdoc} + */ + public function setLocale($locale) + { + $this->locale = (string) $locale; + } + + /** + * {@inheritdoc} + */ + public function getLocale() + { + return $this->locale ?: \Locale::getDefault(); + } + + /** + * {@inheritdoc} + */ + public function trans($id, array $parameters = [], $domain = null, $locale = null) + { + $id = (string) $id; + + if (!isset($parameters['%count%']) || !is_numeric($parameters['%count%'])) { + return strtr($id, $parameters); + } + + $number = (float) $parameters['%count%']; + $locale = (string) $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(int $number, string $locale): int + { + switch ('pt_BR' !== $locale && \strlen($locale) > 3 ? substr($locale, 0, strrpos($locale, '_')) : $locale) { + case 'af': + case 'bn': + case 'bg': + case 'ca': + case 'da': + case 'de': + case 'el': + case 'en': + case 'eo': + case 'es': + case 'et': + case 'eu': + case 'fa': + case 'fi': + case 'fo': + case 'fur': + case 'fy': + case 'gl': + case 'gu': + case 'ha': + case 'he': + case 'hu': + case 'is': + case 'it': + case 'ku': + case 'lb': + case 'ml': + case 'mn': + case 'mr': + case 'nah': + case 'nb': + case 'ne': + case 'nl': + case 'nn': + case 'no': + case 'oc': + case 'om': + case 'or': + case 'pa': + case 'pap': + case 'ps': + case 'pt': + case 'so': + case 'sq': + case 'sv': + case 'sw': + case 'ta': + case 'te': + case 'tk': + case 'ur': + case 'zu': + return (1 == $number) ? 0 : 1; + + case 'am': + case 'bh': + case 'fil': + case 'fr': + case 'gun': + case 'hi': + case 'hy': + case 'ln': + case 'mg': + case 'nso': + case 'pt_BR': + case 'ti': + case 'wa': + return ((0 == $number) || (1 == $number)) ? 0 : 1; + + case 'be': + case 'bs': + case 'hr': + case 'ru': + case 'sh': + case 'sr': + case 'uk': + return ((1 == $number % 10) && (11 != $number % 100)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2); + + case 'cs': + case 'sk': + return (1 == $number) ? 0 : ((($number >= 2) && ($number <= 4)) ? 1 : 2); + + case 'ga': + return (1 == $number) ? 0 : ((2 == $number) ? 1 : 2); + + case 'lt': + return ((1 == $number % 10) && (11 != $number % 100)) ? 0 : ((($number % 10 >= 2) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2); + + case 'sl': + return (1 == $number % 100) ? 0 : ((2 == $number % 100) ? 1 : (((3 == $number % 100) || (4 == $number % 100)) ? 2 : 3)); + + case 'mk': + return (1 == $number % 10) ? 0 : 1; + + case 'mt': + return (1 == $number) ? 0 : (((0 == $number) || (($number % 100 > 1) && ($number % 100 < 11))) ? 1 : ((($number % 100 > 10) && ($number % 100 < 20)) ? 2 : 3)); + + case 'lv': + return (0 == $number) ? 0 : (((1 == $number % 10) && (11 != $number % 100)) ? 1 : 2); + + case 'pl': + return (1 == $number) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 12) || ($number % 100 > 14))) ? 1 : 2); + + case 'cy': + return (1 == $number) ? 0 : ((2 == $number) ? 1 : (((8 == $number) || (11 == $number)) ? 2 : 3)); + + case 'ro': + return (1 == $number) ? 0 : (((0 == $number) || (($number % 100 > 0) && ($number % 100 < 20))) ? 1 : 2); + + case 'ar': + return (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: + return 0; + } + } +} diff --git a/vendor/symfony/translation-contracts/composer.json b/vendor/symfony/translation-contracts/composer.json new file mode 100644 index 0000000000..09749d35f5 --- /dev/null +++ b/vendor/symfony/translation-contracts/composer.json @@ -0,0 +1,33 @@ +{ + "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": "^7.1.3" + }, + "suggest": { + "symfony/translation-implementation": "" + }, + "autoload": { + "psr-4": { "Symfony\\Contracts\\Translation\\": "" } + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + } +} diff --git a/vendor/symfony/translation/CHANGELOG.md b/vendor/symfony/translation/CHANGELOG.md index 87eb2fa4a3..c80716838b 100644 --- a/vendor/symfony/translation/CHANGELOG.md +++ b/vendor/symfony/translation/CHANGELOG.md @@ -1,6 +1,12 @@ CHANGELOG ========= +4.3.0 +----- + + * Improved Xliff 1.2 loader to load the original file's metadata + * Added `TranslatorPathsPass` + 4.2.0 ----- diff --git a/vendor/symfony/translation/Command/XliffLintCommand.php b/vendor/symfony/translation/Command/XliffLintCommand.php index 9bea4d9499..3c2cc9efde 100644 --- a/vendor/symfony/translation/Command/XliffLintCommand.php +++ b/vendor/symfony/translation/Command/XliffLintCommand.php @@ -124,7 +124,9 @@ private function validate($content, $file = null) $normalizedLocale = preg_quote(str_replace('-', '_', $targetLanguage), '/'); // strict file names require translation files to be named '____.locale.xlf' // otherwise, both '____.locale.xlf' and 'locale.____.xlf' are allowed - $expectedFilenamePattern = $this->requireStrictFileNames ? sprintf('/^.*\.%s\.xlf/', $normalizedLocale) : sprintf('/^(.*\.%s\.xlf|%s\..*\.xlf)/', $normalizedLocale, $normalizedLocale); + // also, the regexp matching must be case-insensitive, as defined for 'target-language' values + // http://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html#target-language + $expectedFilenamePattern = $this->requireStrictFileNames ? sprintf('/^.*\.(?i:%s)\.xlf/', $normalizedLocale) : sprintf('/^(.*\.(?i:%s)\.xlf|(?i:%s)\..*\.xlf)/', $normalizedLocale, $normalizedLocale); if (0 === preg_match($expectedFilenamePattern, basename($file))) { $errors[] = [ diff --git a/vendor/symfony/translation/DataCollector/TranslationDataCollector.php b/vendor/symfony/translation/DataCollector/TranslationDataCollector.php index d99b493ac6..35dfc0e344 100644 --- a/vendor/symfony/translation/DataCollector/TranslationDataCollector.php +++ b/vendor/symfony/translation/DataCollector/TranslationDataCollector.php @@ -36,12 +36,9 @@ public function lateCollect() { $messages = $this->sanitizeCollectedMessages($this->translator->getCollectedMessages()); - $this->data = $this->computeCount($messages); + $this->data += $this->computeCount($messages); $this->data['messages'] = $messages; - $this->data['locale'] = $this->translator->getLocale(); - $this->data['fallback_locales'] = $this->translator->getFallbackLocales(); - $this->data = $this->cloneVar($this->data); } @@ -50,6 +47,8 @@ public function lateCollect() */ public function collect(Request $request, Response $response, \Exception $exception = null) { + $this->data['locale'] = $this->translator->getLocale(); + $this->data['fallback_locales'] = $this->translator->getFallbackLocales(); } /** diff --git a/vendor/symfony/translation/DataCollectorTranslator.php b/vendor/symfony/translation/DataCollectorTranslator.php index 68200a7f45..0284b77e9b 100644 --- a/vendor/symfony/translation/DataCollectorTranslator.php +++ b/vendor/symfony/translation/DataCollectorTranslator.php @@ -68,10 +68,10 @@ public function transChoice($id, $number, array $parameters = [], $domain = null { if ($this->translator instanceof TranslatorInterface) { $trans = $this->translator->trans($id, ['%count%' => $number] + $parameters, $domain, $locale); + } else { + $trans = $this->translator->transChoice($id, $number, $parameters, $domain, $locale); } - $trans = $this->translator->transChoice($id, $number, $parameters, $domain, $locale); - $this->collectMessage($locale, $domain, $id, $trans, ['%count%' => $number] + $parameters); return $trans; diff --git a/vendor/symfony/translation/DependencyInjection/TranslatorPass.php b/vendor/symfony/translation/DependencyInjection/TranslatorPass.php index fc1c08fc32..ed4a840d86 100644 --- a/vendor/symfony/translation/DependencyInjection/TranslatorPass.php +++ b/vendor/symfony/translation/DependencyInjection/TranslatorPass.php @@ -68,12 +68,22 @@ public function process(ContainerBuilder $container) return; } + $paths = array_keys($container->getDefinition('twig.template_iterator')->getArgument(2)); if ($container->hasDefinition($this->debugCommandServiceId)) { - $container->getDefinition($this->debugCommandServiceId)->replaceArgument(4, $container->getParameter('twig.default_path')); - } + $definition = $container->getDefinition($this->debugCommandServiceId); + $definition->replaceArgument(4, $container->getParameter('twig.default_path')); + if (\count($definition->getArguments()) > 6) { + $definition->replaceArgument(6, $paths); + } + } if ($container->hasDefinition($this->updateCommandServiceId)) { - $container->getDefinition($this->updateCommandServiceId)->replaceArgument(5, $container->getParameter('twig.default_path')); + $definition = $container->getDefinition($this->updateCommandServiceId); + $definition->replaceArgument(5, $container->getParameter('twig.default_path')); + + if (\count($definition->getArguments()) > 7) { + $definition->replaceArgument(7, $paths); + } } } } diff --git a/vendor/symfony/translation/DependencyInjection/TranslatorPathsPass.php b/vendor/symfony/translation/DependencyInjection/TranslatorPathsPass.php new file mode 100644 index 0000000000..d9fc71911f --- /dev/null +++ b/vendor/symfony/translation/DependencyInjection/TranslatorPathsPass.php @@ -0,0 +1,144 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\AbstractRecursivePass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\ServiceLocator; + +/** + * @author Yonel Ceruto + */ +class TranslatorPathsPass extends AbstractRecursivePass +{ + private $translatorServiceId; + private $debugCommandServiceId; + private $updateCommandServiceId; + private $resolverServiceId; + private $level = 0; + private $paths = []; + private $definitions = []; + private $controllers = []; + + public function __construct(string $translatorServiceId = 'translator', string $debugCommandServiceId = 'console.command.translation_debug', string $updateCommandServiceId = 'console.command.translation_update', string $resolverServiceId = 'argument_resolver.service') + { + $this->translatorServiceId = $translatorServiceId; + $this->debugCommandServiceId = $debugCommandServiceId; + $this->updateCommandServiceId = $updateCommandServiceId; + $this->resolverServiceId = $resolverServiceId; + } + + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition($this->translatorServiceId)) { + return; + } + + foreach ($this->findControllerArguments($container) as $controller => $argument) { + $id = \substr($controller, 0, \strpos($controller, ':') ?: \strlen($controller)); + if ($container->hasDefinition($id)) { + list($locatorRef) = $argument->getValues(); + $this->controllers[(string) $locatorRef][$container->getDefinition($id)->getClass()] = true; + } + } + + try { + parent::process($container); + + $paths = []; + foreach ($this->paths as $class => $_) { + if (($r = $container->getReflectionClass($class)) && !$r->isInterface()) { + $paths[] = $r->getFileName(); + } + } + if ($paths) { + if ($container->hasDefinition($this->debugCommandServiceId)) { + $definition = $container->getDefinition($this->debugCommandServiceId); + $definition->replaceArgument(6, array_merge($definition->getArgument(6), $paths)); + } + if ($container->hasDefinition($this->updateCommandServiceId)) { + $definition = $container->getDefinition($this->updateCommandServiceId); + $definition->replaceArgument(7, array_merge($definition->getArgument(7), $paths)); + } + } + } finally { + $this->level = 0; + $this->paths = []; + $this->definitions = []; + } + } + + protected function processValue($value, $isRoot = false) + { + if ($value instanceof Reference) { + if ((string) $value === $this->translatorServiceId) { + for ($i = $this->level - 1; $i >= 0; --$i) { + $class = $this->definitions[$i]->getClass(); + + if (ServiceLocator::class === $class) { + if (!isset($this->controllers[$this->currentId])) { + continue; + } + foreach ($this->controllers[$this->currentId] as $class => $_) { + $this->paths[$class] = true; + } + } else { + $this->paths[$class] = true; + } + + break; + } + } + + return $value; + } + + if ($value instanceof Definition) { + $this->definitions[$this->level++] = $value; + $value = parent::processValue($value, $isRoot); + unset($this->definitions[--$this->level]); + + return $value; + } + + return parent::processValue($value, $isRoot); + } + + private function findControllerArguments(ContainerBuilder $container): array + { + if ($container->hasDefinition($this->resolverServiceId)) { + $argument = $container->getDefinition($this->resolverServiceId)->getArgument(0); + if ($argument instanceof Reference) { + $argument = $container->getDefinition($argument); + } + + return $argument->getArgument(0); + } + + if ($container->hasDefinition('debug.'.$this->resolverServiceId)) { + $argument = $container->getDefinition('debug.'.$this->resolverServiceId)->getArgument(0); + if ($argument instanceof Reference) { + $argument = $container->getDefinition($argument); + } + $argument = $argument->getArgument(0); + if ($argument instanceof Reference) { + $argument = $container->getDefinition($argument); + } + + return $argument->getArgument(0); + } + + return []; + } +} diff --git a/vendor/symfony/translation/Dumper/PoFileDumper.php b/vendor/symfony/translation/Dumper/PoFileDumper.php index 0f7e6fa834..5f60086285 100644 --- a/vendor/symfony/translation/Dumper/PoFileDumper.php +++ b/vendor/symfony/translation/Dumper/PoFileDumper.php @@ -39,6 +39,18 @@ public function formatCatalogue(MessageCatalogue $messages, $domain, array $opti } else { $newLine = true; } + $metadata = $messages->getMetadata($source, $domain); + + if (isset($metadata['comments'])) { + $output .= $this->formatComments($metadata['comments']); + } + if (isset($metadata['flags'])) { + $output .= $this->formatComments(implode(',', (array) $metadata['flags']), ','); + } + if (isset($metadata['sources'])) { + $output .= $this->formatComments(implode(' ', (array) $metadata['sources']), ':'); + } + $output .= sprintf('msgid "%s"'."\n", $this->escape($source)); $output .= sprintf('msgstr "%s"'."\n", $this->escape($target)); } @@ -58,4 +70,15 @@ private function escape($str) { return addcslashes($str, "\0..\37\42\134"); } + + private function formatComments($comments, string $prefix = ''): ?string + { + $output = null; + + foreach ((array) $comments as $comment) { + $output .= sprintf('#%s %s'."\n", $prefix, $comment); + } + + return $output; + } } diff --git a/vendor/symfony/translation/Dumper/QtFileDumper.php b/vendor/symfony/translation/Dumper/QtFileDumper.php index ec93f92e4a..79a64b2430 100644 --- a/vendor/symfony/translation/Dumper/QtFileDumper.php +++ b/vendor/symfony/translation/Dumper/QtFileDumper.php @@ -33,6 +33,17 @@ public function formatCatalogue(MessageCatalogue $messages, $domain, array $opti foreach ($messages->all($domain) as $source => $target) { $message = $context->appendChild($dom->createElement('message')); + $metadata = $messages->getMetadata($source, $domain); + if (isset($metadata['sources'])) { + foreach ((array) $metadata['sources'] as $location) { + $loc = explode(':', $location, 2); + $location = $message->appendChild($dom->createElement('location')); + $location->setAttribute('filename', $loc[0]); + if (isset($loc[1])) { + $location->setAttribute('line', $loc[1]); + } + } + } $message->appendChild($dom->createElement('source', $source)); $message->appendChild($dom->createElement('translation', $target)); } diff --git a/vendor/symfony/translation/Extractor/PhpExtractor.php b/vendor/symfony/translation/Extractor/PhpExtractor.php index 55ebfa1623..84fd7400f8 100644 --- a/vendor/symfony/translation/Extractor/PhpExtractor.php +++ b/vendor/symfony/translation/Extractor/PhpExtractor.php @@ -81,9 +81,8 @@ public function extract($resource, MessageCatalogue $catalog) { $files = $this->extractFiles($resource); foreach ($files as $file) { - $this->parseTokens(token_get_all(file_get_contents($file)), $catalog); + $this->parseTokens(token_get_all(file_get_contents($file)), $catalog, $file); - // PHP 7 memory manager will not release after token_get_all(), see https://bugs.php.net/70098 gc_mem_caches(); } } @@ -198,9 +197,15 @@ private function getValue(\Iterator $tokenIterator) * * @param array $tokens * @param MessageCatalogue $catalog + * @param string $filename */ - protected function parseTokens($tokens, MessageCatalogue $catalog) + protected function parseTokens($tokens, MessageCatalogue $catalog/*, string $filename*/) { + if (\func_num_args() < 3 && __CLASS__ !== \get_class($this) && __CLASS__ !== (new \ReflectionMethod($this, __FUNCTION__))->getDeclaringClass()->getName() && !$this instanceof \PHPUnit\Framework\MockObject\MockObject && !$this instanceof \Prophecy\Prophecy\ProphecySubjectInterface) { + @trigger_error(sprintf('The "%s()" method will have a new "string $filename" argument in version 5.0, not defining it is deprecated since Symfony 4.3.', __METHOD__), E_USER_DEPRECATED); + } + $filename = 2 < \func_num_args() ? \func_get_arg(2) : ''; + $tokenIterator = new \ArrayIterator($tokens); for ($key = 0; $key < $tokenIterator->count(); ++$key) { @@ -237,6 +242,10 @@ protected function parseTokens($tokens, MessageCatalogue $catalog) if ($message) { $catalog->set($message, $this->prefix.$message, $domain); + $metadata = $catalog->getMetadata($message, $domain) ?? []; + $normalizedFilename = preg_replace('{[\\\\/]+}', '/', $filename); + $metadata['sources'][] = $normalizedFilename.':'.$tokens[$key][2]; + $catalog->setMetadata($message, $metadata, $domain); break; } } diff --git a/vendor/symfony/translation/Loader/XliffFileLoader.php b/vendor/symfony/translation/Loader/XliffFileLoader.php index ed97cae290..6e01a7119b 100644 --- a/vendor/symfony/translation/Loader/XliffFileLoader.php +++ b/vendor/symfony/translation/Loader/XliffFileLoader.php @@ -82,38 +82,51 @@ private function extractXliff1(\DOMDocument $dom, MessageCatalogue $catalogue, s $xml = simplexml_import_dom($dom); $encoding = strtoupper($dom->encoding); - $xml->registerXPathNamespace('xliff', 'urn:oasis:names:tc:xliff:document:1.2'); - foreach ($xml->xpath('//xliff:trans-unit') as $translation) { - $attributes = $translation->attributes(); + $namespace = 'urn:oasis:names:tc:xliff:document:1.2'; + $xml->registerXPathNamespace('xliff', $namespace); - if (!(isset($attributes['resname']) || isset($translation->source))) { - continue; - } + foreach ($xml->xpath('//xliff:file') as $file) { + $fileAttributes = $file->attributes(); - $source = isset($attributes['resname']) && $attributes['resname'] ? $attributes['resname'] : $translation->source; - // If the xlf file has another encoding specified, try to convert it because - // simple_xml will always return utf-8 encoded values - $target = $this->utf8ToCharset((string) (isset($translation->target) ? $translation->target : $translation->source), $encoding); + $file->registerXPathNamespace('xliff', $namespace); - $catalogue->set((string) $source, $target, $domain); + foreach ($file->xpath('.//xliff:trans-unit') as $translation) { + $attributes = $translation->attributes(); - $metadata = []; - if ($notes = $this->parseNotesMetadata($translation->note, $encoding)) { - $metadata['notes'] = $notes; - } + if (!(isset($attributes['resname']) || isset($translation->source))) { + continue; + } - if (isset($translation->target) && $translation->target->attributes()) { - $metadata['target-attributes'] = []; - foreach ($translation->target->attributes() as $key => $value) { - $metadata['target-attributes'][$key] = (string) $value; + $source = isset($attributes['resname']) && $attributes['resname'] ? $attributes['resname'] : $translation->source; + // If the xlf file has another encoding specified, try to convert it because + // simple_xml will always return utf-8 encoded values + $target = $this->utf8ToCharset((string) ($translation->target ?? $translation->source), $encoding); + + $catalogue->set((string) $source, $target, $domain); + + $metadata = [ + 'source' => (string) $translation->source, + 'file' => [ + 'original' => (string) $fileAttributes['original'], + ], + ]; + if ($notes = $this->parseNotesMetadata($translation->note, $encoding)) { + $metadata['notes'] = $notes; } - } - if (isset($attributes['id'])) { - $metadata['id'] = (string) $attributes['id']; - } + if (isset($translation->target) && $translation->target->attributes()) { + $metadata['target-attributes'] = []; + foreach ($translation->target->attributes() as $key => $value) { + $metadata['target-attributes'][$key] = (string) $value; + } + } - $catalogue->setMetadata((string) $source, $metadata, $domain); + if (isset($attributes['id'])) { + $metadata['id'] = (string) $attributes['id']; + } + + $catalogue->setMetadata((string) $source, $metadata, $domain); + } } } diff --git a/vendor/symfony/translation/LoggingTranslator.php b/vendor/symfony/translation/LoggingTranslator.php index d6b711d27e..3a84bf1170 100644 --- a/vendor/symfony/translation/LoggingTranslator.php +++ b/vendor/symfony/translation/LoggingTranslator.php @@ -82,7 +82,9 @@ public function transChoice($id, $number, array $parameters = [], $domain = null */ public function setLocale($locale) { + $prev = $this->translator->getLocale(); $this->translator->setLocale($locale); + $this->logger->debug(sprintf('The locale of the translator has changed from "%s" to "%s".', $prev, $locale)); } /** diff --git a/vendor/symfony/translation/Tests/Catalogue/AbstractOperationTest.php b/vendor/symfony/translation/Tests/Catalogue/AbstractOperationTest.php index e39ef39ec5..f82b18fdd7 100644 --- a/vendor/symfony/translation/Tests/Catalogue/AbstractOperationTest.php +++ b/vendor/symfony/translation/Tests/Catalogue/AbstractOperationTest.php @@ -41,7 +41,7 @@ public function testGetMergedDomains() public function testGetMessagesFromUnknownDomain() { - $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('InvalidArgumentException'); + $this->expectException('InvalidArgumentException'); $this->createOperation( new MessageCatalogue('en'), new MessageCatalogue('en') diff --git a/vendor/symfony/translation/Tests/Command/XliffLintCommandTest.php b/vendor/symfony/translation/Tests/Command/XliffLintCommandTest.php index 516d98af53..df2e2f0951 100644 --- a/vendor/symfony/translation/Tests/Command/XliffLintCommandTest.php +++ b/vendor/symfony/translation/Tests/Command/XliffLintCommandTest.php @@ -94,6 +94,17 @@ public function testLintIncorrectTargetLanguage() $this->assertContains('There is a mismatch between the language included in the file name ("messages.en.xlf") and the "es" value used in the "target-language" attribute of the file.', trim($tester->getDisplay())); } + public function testLintTargetLanguageIsCaseInsensitive() + { + $tester = $this->createCommandTester(); + $filename = $this->createFile('note', 'zh-cn', 'messages.zh_CN.xlf'); + + $tester->execute(['filename' => $filename], ['decorated' => false]); + + $this->assertEquals(0, $tester->getStatusCode()); + $this->assertContains('[OK] All 1 XLIFF files contain valid syntax.', trim($tester->getDisplay())); + } + /** * @expectedException \RuntimeException */ diff --git a/vendor/symfony/translation/Tests/DataCollector/TranslationDataCollectorTest.php b/vendor/symfony/translation/Tests/DataCollector/TranslationDataCollectorTest.php index b4d350ef86..bd97a2445c 100644 --- a/vendor/symfony/translation/Tests/DataCollector/TranslationDataCollectorTest.php +++ b/vendor/symfony/translation/Tests/DataCollector/TranslationDataCollectorTest.php @@ -27,7 +27,7 @@ protected function setUp() public function testCollectEmptyMessages() { $translator = $this->getTranslator(); - $translator->expects($this->any())->method('getCollectedMessages')->will($this->returnValue([])); + $translator->expects($this->any())->method('getCollectedMessages')->willReturn([]); $dataCollector = new TranslationDataCollector($translator); $dataCollector->lateCollect(); @@ -125,7 +125,7 @@ public function testCollect() ]; $translator = $this->getTranslator(); - $translator->expects($this->any())->method('getCollectedMessages')->will($this->returnValue($collectedMessages)); + $translator->expects($this->any())->method('getCollectedMessages')->willReturn($collectedMessages); $dataCollector = new TranslationDataCollector($translator); $dataCollector->lateCollect(); diff --git a/vendor/symfony/translation/Tests/DependencyInjection/TranslationPassTest.php b/vendor/symfony/translation/Tests/DependencyInjection/TranslationPassTest.php index 8b9c03d991..f62fc85ebc 100644 --- a/vendor/symfony/translation/Tests/DependencyInjection/TranslationPassTest.php +++ b/vendor/symfony/translation/Tests/DependencyInjection/TranslationPassTest.php @@ -54,4 +54,69 @@ public function testValidCollector() $expected = ['translation.xliff_loader' => new ServiceClosureArgument(new Reference('translation.xliff_loader'))]; $this->assertEquals($expected, $container->getDefinition((string) $translator->getArgument(0))->getArgument(0)); } + + public function testValidCommandsViewPathsArgument() + { + $container = new ContainerBuilder(); + $container->register('translator.default') + ->setArguments([null, null, null, null]) + ; + $debugCommand = $container->register('console.command.translation_debug') + ->setArguments([null, null, null, null, null, [], []]) + ; + $updateCommand = $container->register('console.command.translation_update') + ->setArguments([null, null, null, null, null, null, [], []]) + ; + $container->register('twig.template_iterator') + ->setArguments([null, null, ['other/templates' => null, 'tpl' => 'App']]) + ; + $container->setParameter('twig.default_path', 'templates'); + + $pass = new TranslatorPass('translator.default'); + $pass->process($container); + + $expectedViewPaths = ['other/templates', 'tpl']; + + $this->assertSame('templates', $debugCommand->getArgument(4)); + $this->assertSame('templates', $updateCommand->getArgument(5)); + $this->assertSame($expectedViewPaths, $debugCommand->getArgument(6)); + $this->assertSame($expectedViewPaths, $updateCommand->getArgument(7)); + } + + public function testCommandsViewPathsArgumentsAreIgnoredWithOldServiceDefinitions() + { + $container = new ContainerBuilder(); + $container->register('translator.default') + ->setArguments([null, null, null, null]) + ; + $debugCommand = $container->register('console.command.translation_debug') + ->setArguments([ + new Reference('translator'), + new Reference('translation.reader'), + new Reference('translation.extractor'), + '%translator.default_path%', + null, + ]) + ; + $updateCommand = $container->register('console.command.translation_update') + ->setArguments([ + new Reference('translation.writer'), + new Reference('translation.reader'), + new Reference('translation.extractor'), + '%kernel.default_locale%', + '%translator.default_path%', + null, + ]) + ; + $container->register('twig.template_iterator') + ->setArguments([null, null, ['other/templates' => null, 'tpl' => 'App']]) + ; + $container->setParameter('twig.default_path', 'templates'); + + $pass = new TranslatorPass('translator.default'); + $pass->process($container); + + $this->assertSame('templates', $debugCommand->getArgument(4)); + $this->assertSame('templates', $updateCommand->getArgument(5)); + } } diff --git a/vendor/symfony/translation/Tests/DependencyInjection/TranslationPathsPassTest.php b/vendor/symfony/translation/Tests/DependencyInjection/TranslationPathsPassTest.php new file mode 100644 index 0000000000..42ab398dff --- /dev/null +++ b/vendor/symfony/translation/Tests/DependencyInjection/TranslationPathsPassTest.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\Translation\Tests\DependencyInjection; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\ServiceLocator; +use Symfony\Component\Translation\DependencyInjection\TranslatorPathsPass; +use Symfony\Component\Translation\Tests\DependencyInjection\fixtures\ControllerArguments; +use Symfony\Component\Translation\Tests\DependencyInjection\fixtures\ServiceArguments; +use Symfony\Component\Translation\Tests\DependencyInjection\fixtures\ServiceMethodCalls; +use Symfony\Component\Translation\Tests\DependencyInjection\fixtures\ServiceProperties; +use Symfony\Component\Translation\Tests\DependencyInjection\fixtures\ServiceSubscriber; + +class TranslationPathsPassTest extends TestCase +{ + public function testProcess() + { + $container = new ContainerBuilder(); + $container->register('translator'); + $debugCommand = $container->register('console.command.translation_debug') + ->setArguments([null, null, null, null, null, [], []]) + ; + $updateCommand = $container->register('console.command.translation_update') + ->setArguments([null, null, null, null, null, null, [], []]) + ; + $container->register(ControllerArguments::class, ControllerArguments::class) + ->setTags(['controller.service_arguments']) + ; + $container->register(ServiceArguments::class, ServiceArguments::class) + ->setArguments([new Reference('translator')]) + ; + $container->register(ServiceProperties::class, ServiceProperties::class) + ->setProperties([new Reference('translator')]) + ; + $container->register(ServiceMethodCalls::class, ServiceMethodCalls::class) + ->setMethodCalls([['setTranslator', [new Reference('translator')]]]) + ; + $container->register('service_rc') + ->setArguments([new Definition(), new Reference(ServiceMethodCalls::class)]) + ; + $serviceLocator1 = $container->register('.service_locator.foo', ServiceLocator::class) + ->setArguments([new ServiceClosureArgument(new Reference('translator'))]) + ; + $serviceLocator2 = (new Definition(ServiceLocator::class)) + ->setArguments([ServiceSubscriber::class, new Reference('service_container')]) + ->setFactory([$serviceLocator1, 'withContext']) + ; + $container->register('service_subscriber', ServiceSubscriber::class) + ->setArguments([$serviceLocator2]) + ; + $container->register('.service_locator.bar', ServiceLocator::class) + ->setArguments([[ + ControllerArguments::class.'::index' => new ServiceClosureArgument(new Reference('.service_locator.foo')), + ControllerArguments::class.'::__invoke' => new ServiceClosureArgument(new Reference('.service_locator.foo')), + ControllerArguments::class => new ServiceClosureArgument(new Reference('.service_locator.foo')), + ]]) + ; + $container->register('argument_resolver.service') + ->setArguments([new Reference('.service_locator.bar')]) + ; + + $pass = new TranslatorPathsPass('translator', 'console.command.translation_debug', 'console.command.translation_update', 'argument_resolver.service'); + $pass->process($container); + + $expectedPaths = [ + $container->getReflectionClass(ServiceArguments::class)->getFileName(), + $container->getReflectionClass(ServiceProperties::class)->getFileName(), + $container->getReflectionClass(ServiceMethodCalls::class)->getFileName(), + $container->getReflectionClass(ControllerArguments::class)->getFileName(), + $container->getReflectionClass(ServiceSubscriber::class)->getFileName(), + ]; + + $this->assertSame($expectedPaths, $debugCommand->getArgument(6)); + $this->assertSame($expectedPaths, $updateCommand->getArgument(7)); + } +} diff --git a/vendor/symfony/translation/Tests/DependencyInjection/fixtures/ControllerArguments.php b/vendor/symfony/translation/Tests/DependencyInjection/fixtures/ControllerArguments.php new file mode 100644 index 0000000000..97a53fa76b --- /dev/null +++ b/vendor/symfony/translation/Tests/DependencyInjection/fixtures/ControllerArguments.php @@ -0,0 +1,16 @@ + TranslatorInterface::class]; + } +} diff --git a/vendor/symfony/translation/Tests/Dumper/PoFileDumperTest.php b/vendor/symfony/translation/Tests/Dumper/PoFileDumperTest.php index 960ec2df65..46df869f89 100644 --- a/vendor/symfony/translation/Tests/Dumper/PoFileDumperTest.php +++ b/vendor/symfony/translation/Tests/Dumper/PoFileDumperTest.php @@ -20,7 +20,26 @@ class PoFileDumperTest extends TestCase public function testFormatCatalogue() { $catalogue = new MessageCatalogue('en'); - $catalogue->add(['foo' => 'bar', 'bar' => 'foo']); + $catalogue->add(['foo' => 'bar', 'bar' => 'foo', 'foo_bar' => 'foobar', 'bar_foo' => 'barfoo']); + $catalogue->setMetadata('foo_bar', [ + 'comments' => [ + 'Comment 1', + 'Comment 2', + ], + 'flags' => [ + 'fuzzy', + 'another', + ], + 'sources' => [ + 'src/file_1', + 'src/file_2:50', + ], + ]); + $catalogue->setMetadata('bar_foo', [ + 'comments' => 'Comment', + 'flags' => 'fuzzy', + 'sources' => 'src/file_1', + ]); $dumper = new PoFileDumper(); diff --git a/vendor/symfony/translation/Tests/Dumper/QtFileDumperTest.php b/vendor/symfony/translation/Tests/Dumper/QtFileDumperTest.php index edfad6005c..6c4b559278 100644 --- a/vendor/symfony/translation/Tests/Dumper/QtFileDumperTest.php +++ b/vendor/symfony/translation/Tests/Dumper/QtFileDumperTest.php @@ -20,7 +20,26 @@ class QtFileDumperTest extends TestCase public function testFormatCatalogue() { $catalogue = new MessageCatalogue('en'); - $catalogue->add(['foo' => 'bar'], 'resources'); + $catalogue->add(['foo' => 'bar', 'foo_bar' => 'foobar', 'bar_foo' => 'barfoo'], 'resources'); + $catalogue->setMetadata('foo_bar', [ + 'comments' => [ + 'Comment 1', + 'Comment 2', + ], + 'flags' => [ + 'fuzzy', + 'another', + ], + 'sources' => [ + 'src/file_1', + 'src/file_2:50', + ], + ], 'resources'); + $catalogue->setMetadata('bar_foo', [ + 'comments' => 'Comment', + 'flags' => 'fuzzy', + 'sources' => 'src/file_1', + ], 'resources'); $dumper = new QtFileDumper(); diff --git a/vendor/symfony/translation/Tests/Extractor/PhpExtractorTest.php b/vendor/symfony/translation/Tests/Extractor/PhpExtractorTest.php index 7cde108080..a6d7c5001c 100644 --- a/vendor/symfony/translation/Tests/Extractor/PhpExtractorTest.php +++ b/vendor/symfony/translation/Tests/Extractor/PhpExtractorTest.php @@ -69,6 +69,10 @@ public function testExtraction($resource) $actualCatalogue = $catalogue->all(); $this->assertEquals($expectedCatalogue, $actualCatalogue); + + $filename = str_replace(\DIRECTORY_SEPARATOR, '/', __DIR__).'/../fixtures/extractor/translation.html.php'; + $this->assertEquals(['sources' => [$filename.':2']], $catalogue->getMetadata('single-quoted key')); + $this->assertEquals(['sources' => [$filename.':43']], $catalogue->getMetadata('other-domain-test-no-params-short-array', 'not_messages')); } public function resourcesProvider() diff --git a/vendor/symfony/translation/Tests/IdentityTranslatorTest.php b/vendor/symfony/translation/Tests/IdentityTranslatorTest.php index be0a548aa1..cf618d95a2 100644 --- a/vendor/symfony/translation/Tests/IdentityTranslatorTest.php +++ b/vendor/symfony/translation/Tests/IdentityTranslatorTest.php @@ -12,7 +12,7 @@ namespace Symfony\Component\Translation\Tests; use Symfony\Component\Translation\IdentityTranslator; -use Symfony\Contracts\Tests\Translation\TranslatorTest; +use Symfony\Contracts\Translation\Test\TranslatorTest; class IdentityTranslatorTest extends TranslatorTest { diff --git a/vendor/symfony/translation/Tests/Loader/QtFileLoaderTest.php b/vendor/symfony/translation/Tests/Loader/QtFileLoaderTest.php index 08f55e9022..f149b8c715 100644 --- a/vendor/symfony/translation/Tests/Loader/QtFileLoaderTest.php +++ b/vendor/symfony/translation/Tests/Loader/QtFileLoaderTest.php @@ -23,7 +23,11 @@ public function testLoad() $resource = __DIR__.'/../fixtures/resources.ts'; $catalogue = $loader->load($resource, 'en', 'resources'); - $this->assertEquals(['foo' => 'bar'], $catalogue->all('resources')); + $this->assertEquals([ + 'foo' => 'bar', + 'foo_bar' => 'foobar', + 'bar_foo' => 'barfoo', + ], $catalogue->all('resources')); $this->assertEquals('en', $catalogue->getLocale()); $this->assertEquals([new FileResource($resource)], $catalogue->getResources()); } diff --git a/vendor/symfony/translation/Tests/Loader/XliffFileLoaderTest.php b/vendor/symfony/translation/Tests/Loader/XliffFileLoaderTest.php index 7cb9f54fde..1ca8336d52 100644 --- a/vendor/symfony/translation/Tests/Loader/XliffFileLoaderTest.php +++ b/vendor/symfony/translation/Tests/Loader/XliffFileLoaderTest.php @@ -84,7 +84,17 @@ public function testEncoding() $this->assertEquals(utf8_decode('föö'), $catalogue->get('bar', 'domain1')); $this->assertEquals(utf8_decode('bär'), $catalogue->get('foo', 'domain1')); - $this->assertEquals(['notes' => [['content' => utf8_decode('bäz')]], 'id' => '1'], $catalogue->getMetadata('foo', 'domain1')); + $this->assertEquals( + [ + 'source' => 'foo', + 'notes' => [['content' => utf8_decode('bäz')]], + 'id' => '1', + 'file' => [ + 'original' => 'file.ext', + ], + ], + $catalogue->getMetadata('foo', 'domain1') + ); } public function testTargetAttributesAreStoredCorrectly() @@ -164,11 +174,44 @@ public function testLoadNotes() $loader = new XliffFileLoader(); $catalogue = $loader->load(__DIR__.'/../fixtures/withnote.xlf', 'en', 'domain1'); - $this->assertEquals(['notes' => [['priority' => 1, 'content' => 'foo']], 'id' => '1'], $catalogue->getMetadata('foo', 'domain1')); + $this->assertEquals( + [ + 'source' => 'foo', + 'notes' => [['priority' => 1, 'content' => 'foo']], + 'id' => '1', + 'file' => [ + 'original' => 'file.ext', + ], + ], + $catalogue->getMetadata('foo', 'domain1') + ); // message without target - $this->assertEquals(['notes' => [['content' => 'bar', 'from' => 'foo']], 'id' => '2'], $catalogue->getMetadata('extra', 'domain1')); + $this->assertEquals( + [ + 'source' => 'extrasource', + 'notes' => [['content' => 'bar', 'from' => 'foo']], + 'id' => '2', + 'file' => [ + 'original' => 'file.ext', + ], + ], + $catalogue->getMetadata('extra', 'domain1') + ); // message with empty target - $this->assertEquals(['notes' => [['content' => 'baz'], ['priority' => 2, 'from' => 'bar', 'content' => 'qux']], 'id' => '123'], $catalogue->getMetadata('key', 'domain1')); + $this->assertEquals( + [ + 'source' => 'key', + 'notes' => [ + ['content' => 'baz'], + ['priority' => 2, 'from' => 'bar', 'content' => 'qux'], + ], + 'id' => '123', + 'file' => [ + 'original' => 'file.ext', + ], + ], + $catalogue->getMetadata('key', 'domain1') + ); } public function testLoadVersion2() @@ -257,4 +300,32 @@ public function testLoadVersion2WithMultiSegmentUnit() $this->assertSame('processed', $metadata['notes'][0]['category']); $this->assertSame('true', $metadata['notes'][0]['content']); } + + public function testLoadWithMultipleFileNodes() + { + $loader = new XliffFileLoader(); + $catalogue = $loader->load(__DIR__.'/../fixtures/resources-multi-files.xlf', 'en', 'domain1'); + + $this->assertEquals( + [ + 'source' => 'foo', + 'id' => '1', + 'file' => [ + 'original' => 'file.ext', + ], + ], + $catalogue->getMetadata('foo', 'domain1') + ); + $this->assertEquals( + [ + 'source' => 'test', + 'notes' => [['content' => 'note']], + 'id' => '4', + 'file' => [ + 'original' => 'otherfile.ext', + ], + ], + $catalogue->getMetadata('test', 'domain1') + ); + } } diff --git a/vendor/symfony/translation/Tests/MessageCatalogueTest.php b/vendor/symfony/translation/Tests/MessageCatalogueTest.php index 6fe9368f5c..cf0dd1a24c 100644 --- a/vendor/symfony/translation/Tests/MessageCatalogueTest.php +++ b/vendor/symfony/translation/Tests/MessageCatalogueTest.php @@ -103,10 +103,10 @@ public function testReplace() public function testAddCatalogue() { $r = $this->getMockBuilder('Symfony\Component\Config\Resource\ResourceInterface')->getMock(); - $r->expects($this->any())->method('__toString')->will($this->returnValue('r')); + $r->expects($this->any())->method('__toString')->willReturn('r'); $r1 = $this->getMockBuilder('Symfony\Component\Config\Resource\ResourceInterface')->getMock(); - $r1->expects($this->any())->method('__toString')->will($this->returnValue('r1')); + $r1->expects($this->any())->method('__toString')->willReturn('r1'); $catalogue = new MessageCatalogue('en', ['domain1' => ['foo' => 'foo']]); $catalogue->addResource($r); @@ -127,13 +127,13 @@ public function testAddCatalogue() public function testAddFallbackCatalogue() { $r = $this->getMockBuilder('Symfony\Component\Config\Resource\ResourceInterface')->getMock(); - $r->expects($this->any())->method('__toString')->will($this->returnValue('r')); + $r->expects($this->any())->method('__toString')->willReturn('r'); $r1 = $this->getMockBuilder('Symfony\Component\Config\Resource\ResourceInterface')->getMock(); - $r1->expects($this->any())->method('__toString')->will($this->returnValue('r1')); + $r1->expects($this->any())->method('__toString')->willReturn('r1'); $r2 = $this->getMockBuilder('Symfony\Component\Config\Resource\ResourceInterface')->getMock(); - $r2->expects($this->any())->method('__toString')->will($this->returnValue('r2')); + $r2->expects($this->any())->method('__toString')->willReturn('r2'); $catalogue = new MessageCatalogue('fr_FR', ['domain1' => ['foo' => 'foo'], 'domain2' => ['bar' => 'bar']]); $catalogue->addResource($r); @@ -192,11 +192,11 @@ public function testGetAddResource() { $catalogue = new MessageCatalogue('en'); $r = $this->getMockBuilder('Symfony\Component\Config\Resource\ResourceInterface')->getMock(); - $r->expects($this->any())->method('__toString')->will($this->returnValue('r')); + $r->expects($this->any())->method('__toString')->willReturn('r'); $catalogue->addResource($r); $catalogue->addResource($r); $r1 = $this->getMockBuilder('Symfony\Component\Config\Resource\ResourceInterface')->getMock(); - $r1->expects($this->any())->method('__toString')->will($this->returnValue('r1')); + $r1->expects($this->any())->method('__toString')->willReturn('r1'); $catalogue->addResource($r1); $this->assertEquals([$r, $r1], $catalogue->getResources()); diff --git a/vendor/symfony/translation/Tests/TranslatorCacheTest.php b/vendor/symfony/translation/Tests/TranslatorCacheTest.php index ec909aaa38..5d437ff76c 100644 --- a/vendor/symfony/translation/Tests/TranslatorCacheTest.php +++ b/vendor/symfony/translation/Tests/TranslatorCacheTest.php @@ -104,7 +104,7 @@ public function testCatalogueIsReloadedWhenResourcesAreNoLongerFresh() $loader ->expects($this->exactly(2)) ->method('load') - ->will($this->returnValue($catalogue)) + ->willReturn($catalogue) ; // 1st pass @@ -249,11 +249,11 @@ public function testRefreshCacheWhenResourcesAreNoLongerFresh() { $resource = $this->getMockBuilder('Symfony\Component\Config\Resource\SelfCheckingResourceInterface')->getMock(); $loader = $this->getMockBuilder('Symfony\Component\Translation\Loader\LoaderInterface')->getMock(); - $resource->method('isFresh')->will($this->returnValue(false)); + $resource->method('isFresh')->willReturn(false); $loader ->expects($this->exactly(2)) ->method('load') - ->will($this->returnValue($this->getCatalogue('fr', [], [$resource]))); + ->willReturn($this->getCatalogue('fr', [], [$resource])); // prime the cache $translator = new Translator('fr', null, $this->tmpDir, true); diff --git a/vendor/symfony/translation/Tests/fixtures/resources-multi-files.xlf b/vendor/symfony/translation/Tests/fixtures/resources-multi-files.xlf new file mode 100644 index 0000000000..5f451508bc --- /dev/null +++ b/vendor/symfony/translation/Tests/fixtures/resources-multi-files.xlf @@ -0,0 +1,27 @@ + + + + + + foo + bar + + + + + + + extra + + + key + + + + test + with + note + + + + diff --git a/vendor/symfony/translation/Tests/fixtures/resources.po b/vendor/symfony/translation/Tests/fixtures/resources.po index a20e619828..68e0f2d7e0 100644 --- a/vendor/symfony/translation/Tests/fixtures/resources.po +++ b/vendor/symfony/translation/Tests/fixtures/resources.po @@ -9,3 +9,16 @@ msgstr "bar" msgid "bar" msgstr "foo" + +# Comment 1 +# Comment 2 +#, fuzzy,another +#: src/file_1 src/file_2:50 +msgid "foo_bar" +msgstr "foobar" + +# Comment +#, fuzzy +#: src/file_1 +msgid "bar_foo" +msgstr "barfoo" diff --git a/vendor/symfony/translation/Tests/fixtures/resources.ts b/vendor/symfony/translation/Tests/fixtures/resources.ts index 40e18522c8..29e6a6fadf 100644 --- a/vendor/symfony/translation/Tests/fixtures/resources.ts +++ b/vendor/symfony/translation/Tests/fixtures/resources.ts @@ -6,5 +6,16 @@ foo bar + + + + foo_bar + foobar + + + + bar_foo + barfoo + diff --git a/vendor/symfony/translation/Tests/fixtures/withnote.xlf b/vendor/symfony/translation/Tests/fixtures/withnote.xlf index c045e21e23..f98cf7f97b 100644 --- a/vendor/symfony/translation/Tests/fixtures/withnote.xlf +++ b/vendor/symfony/translation/Tests/fixtures/withnote.xlf @@ -7,8 +7,8 @@ bar foo - - extra + + extrasource bar diff --git a/vendor/symfony/translation/Translator.php b/vendor/symfony/translation/Translator.php index 8a2b2dd9d0..f5ce39ef0d 100644 --- a/vendor/symfony/translation/Translator.php +++ b/vendor/symfony/translation/Translator.php @@ -395,7 +395,10 @@ private function getCatalogueCachePath($locale) return $this->cacheDir.'/catalogue.'.$locale.'.'.strtr(substr(base64_encode(hash('sha256', serialize($this->fallbackLocales), true)), 0, 7), '/', '_').'.php'; } - private function doLoadCatalogue($locale): void + /** + * @internal + */ + protected function doLoadCatalogue($locale): void { $this->catalogues[$locale] = new MessageCatalogue($locale); diff --git a/vendor/symfony/translation/composer.json b/vendor/symfony/translation/composer.json index 809625b5ad..fd25a4cf04 100644 --- a/vendor/symfony/translation/composer.json +++ b/vendor/symfony/translation/composer.json @@ -17,8 +17,8 @@ ], "require": { "php": "^7.1.3", - "symfony/contracts": "^1.0.2", - "symfony/polyfill-mbstring": "~1.0" + "symfony/polyfill-mbstring": "~1.0", + "symfony/translation-contracts": "^1.1.2" }, "require-dev": { "symfony/config": "~3.4|~4.0", @@ -26,6 +26,7 @@ "symfony/dependency-injection": "~3.4|~4.0", "symfony/http-kernel": "~3.4|~4.0", "symfony/intl": "~3.4|~4.0", + "symfony/service-contracts": "^1.1.2", "symfony/var-dumper": "~3.4|~4.0", "symfony/yaml": "~3.4|~4.0", "symfony/finder": "~2.8|~3.0|~4.0", @@ -37,7 +38,7 @@ "symfony/yaml": "<3.4" }, "provide": { - "symfony/translation-contracts-implementation": "1.0" + "symfony/translation-implementation": "1.0" }, "suggest": { "symfony/config": "", @@ -53,7 +54,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } } } diff --git a/vendor/symfony/validator/Resources/translations/validators.be.xlf b/vendor/symfony/validator/Resources/translations/validators.be.xlf new file mode 100644 index 0000000000..ab3845ee20 --- /dev/null +++ b/vendor/symfony/validator/Resources/translations/validators.be.xlf @@ -0,0 +1,367 @@ + + + + + + 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. + Гэты пароль быў выкрадзены ў выніку ўзлому дадзеных, таму яго нельга выкарыстоўваць. Калі ласка, выкарыстоўвайце іншы пароль. + + + + diff --git a/vendor/symfony/yaml/CHANGELOG.md b/vendor/symfony/yaml/CHANGELOG.md index cfd81fad27..1bc5561ba3 100644 --- a/vendor/symfony/yaml/CHANGELOG.md +++ b/vendor/symfony/yaml/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +4.3.0 +----- + + * Using a mapping inside a multi-line string is deprecated and will throw a `ParseException` in 5.0. + 4.2.0 ----- diff --git a/vendor/symfony/yaml/Parser.php b/vendor/symfony/yaml/Parser.php index cfec2de670..4519edb840 100644 --- a/vendor/symfony/yaml/Parser.php +++ b/vendor/symfony/yaml/Parser.php @@ -390,6 +390,11 @@ private function doParse(string $value, int $flags) if (0 === $this->offset && !$deprecatedUsage && isset($line[0]) && ' ' === $line[0]) { throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); } + + if (false !== strpos($line, ': ')) { + @trigger_error('Support for mapping keys in multi-line blocks is deprecated since Symfony 4.3 and will throw a ParseException in 5.0.', E_USER_DEPRECATED); + } + if ('' === trim($line)) { $value .= "\n"; } elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) { diff --git a/vendor/symfony/yaml/Tests/ParserTest.php b/vendor/symfony/yaml/Tests/ParserTest.php index 7e465595ca..6fc02e1d1f 100644 --- a/vendor/symfony/yaml/Tests/ParserTest.php +++ b/vendor/symfony/yaml/Tests/ParserTest.php @@ -525,6 +525,20 @@ public function testObjectsSupportDisabledWithExceptions() $this->parser->parse($yaml, Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE); } + /** + * @group legacy + * @expectedDeprecation Support for mapping keys in multi-line blocks is deprecated since Symfony 4.3 and will throw a ParseException in 5.0. + */ + public function testMappingKeyInMultiLineStringTriggersDeprecationNotice() + { + $yaml = <<<'EOF' +data: + dbal:wrong + default_connection: monolith +EOF; + $this->parser->parse($yaml); + } + public function testCanParseContentWithTrailingSpaces() { $yaml = "items: \n foo: bar"; diff --git a/vendor/symfony/yaml/composer.json b/vendor/symfony/yaml/composer.json index c8b7123f23..2338728efe 100644 --- a/vendor/symfony/yaml/composer.json +++ b/vendor/symfony/yaml/composer.json @@ -37,7 +37,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } } } diff --git a/vendor/twig/twig/CHANGELOG b/vendor/twig/twig/CHANGELOG index 3deb94544c..0468803bad 100644 --- a/vendor/twig/twig/CHANGELOG +++ b/vendor/twig/twig/CHANGELOG @@ -1,3 +1,27 @@ +* 2.11.2 (2019-06-05) + + * fixed macro auto-import + +* 2.11.1 (2019-06-04) + + * added support for "Twig\Markup" instances in the "in" test (again) + * allowed string operators as variables names in assignments + * fixed support for macros defined in parent templates + +* 2.11.0 (2019-05-31) + + * added the possibility to register classes/interfaces as being safe for the escaper ("EscaperExtension::addSafeClass()") + * deprecated CoreExtension::setEscaper() and CoreExtension::getEscapers() in favor of the same methods on EscaperExtension + * macros are now auto-imported in the template they are defined (under the ``_self`` variable) + * added support for macros on "is defined" tests + * fixed macros "import" when using the same name in the parent and child templates + * fixed recursive macros + * macros imported "globally" in a template are now available in macros without re-importing them + * fixed the "filter" filter when the argument is \Traversable but does not implement \Iterator (\SimpleXmlElement for instance) + * fixed a PHP fatal error when calling a macro imported in a block in a nested block + * fixed a PHP fatal error when calling a macro imported in the template in another macro + * fixed wrong error message on "import" and "from" + * 2.10.0 (2019-05-14) * deprecated "if" conditions on "for" tags @@ -225,7 +249,23 @@ * improved the performance of the filesystem loader * removed features that were deprecated in 1.x -* 1.41.0 (2019-XX-XX) +* 1.42.2 (2019-XX-XX) + + * n/a + +* 1.42.1 (2019-06-04) + + * added support for "Twig\Markup" instances in the "in" test (again) + * allowed string operators as variables names in assignments + +* 1.42.0 (2019-05-31) + + * fixed the "filter" filter when the argument is \Traversable but does not implement \Iterator (\SimpleXmlElement for instance) + * fixed a PHP fatal error when calling a macro imported in a block in a nested block + * fixed a PHP fatal error when calling a macro imported in the template in another macro + * fixed wrong error message on "import" and "from" + +* 1.41.0 (2019-05-14) * fixed support for PHP 7.4 * added "filter", "map", and "reduce" filters (and support for arrow functions) diff --git a/vendor/twig/twig/composer.json b/vendor/twig/twig/composer.json index c78e1db9d2..b0c5836f80 100644 --- a/vendor/twig/twig/composer.json +++ b/vendor/twig/twig/composer.json @@ -29,7 +29,7 @@ "symfony/polyfill-ctype": "^1.8" }, "require-dev": { - "symfony/phpunit-bridge": "^3.4.19|^4.1.8", + "symfony/phpunit-bridge": "^3.4.19|^4.1.8|^5.0", "symfony/debug": "^2.7", "psr/container": "^1.0" }, @@ -48,7 +48,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.10-dev" + "dev-master": "2.11-dev" } } } diff --git a/vendor/twig/twig/doc/advanced.rst b/vendor/twig/twig/doc/advanced.rst index 9bce7a6574..4d22fcce61 100644 --- a/vendor/twig/twig/doc/advanced.rst +++ b/vendor/twig/twig/doc/advanced.rst @@ -863,49 +863,6 @@ It is now possible to move the runtime logic to a new } } -Overloading ------------ - -To overload an already defined filter, test, operator, global variable, or -function, re-define it in an extension and register it **as late as -possible** (order matters):: - - class MyCoreExtension extends \Twig\Extension\AbstractExtension - { - public function getFilters() - { - return [ - new \Twig\TwigFilter('date', [$this, 'dateFilter']), - ]; - } - - public function dateFilter($timestamp, $format = 'F j, Y H:i') - { - // do something different from the built-in date filter - } - } - - $twig = new \Twig\Environment($loader); - $twig->addExtension(new MyCoreExtension()); - -Here, we have overloaded the built-in ``date`` filter with a custom one. - -If you do the same on the ``\Twig\Environment`` itself, beware that it takes -precedence over any other registered extensions:: - - $twig = new \Twig\Environment($loader); - $twig->addFilter(new \Twig\TwigFilter('date', function ($timestamp, $format = 'F j, Y H:i') { - // do something different from the built-in date filter - })); - // the date filter will come from the above registration, not - // from the registered extension below - $twig->addExtension(new MyCoreExtension()); - -.. caution:: - - Note that overloading the built-in Twig elements is not recommended as it - might be confusing. - Testing an Extension -------------------- diff --git a/vendor/twig/twig/doc/api.rst b/vendor/twig/twig/doc/api.rst index 0f0abcee95..5c0294c20b 100644 --- a/vendor/twig/twig/doc/api.rst +++ b/vendor/twig/twig/doc/api.rst @@ -415,6 +415,24 @@ The escaping rules are implemented as follows: {% set text = "Twig
" %} {{ foo ? text|escape : "
Twig" }} {# the result of the expression won't be escaped #} +* Objects with a ``__toString`` method are converted to strings and + escaped. You can mark some classes and/or interfaces as being safe for some + strategies via ``EscaperExtension::addSafeClass()``: + + .. code-block:: twig + + // mark object of class Foo as safe for the HTML strategy + $escaper->addSafeClass('Foo', ['html']); + + // mark object of interface Foo as safe for the HTML strategy + $escaper->addSafeClass('FooInterface', ['html']); + + // mark object of class Foo as safe for the HTML and JS strategies + $escaper->addSafeClass('Foo', ['html', 'js']); + + // mark object of class Foo as safe for all strategies + $escaper->addSafeClass('Foo', ['all']); + * Escaping is applied before printing, after any other filter is applied: .. code-block:: twig diff --git a/vendor/twig/twig/doc/deprecated.rst b/vendor/twig/twig/doc/deprecated.rst index bc19efdcc7..d7a6145ed4 100644 --- a/vendor/twig/twig/doc/deprecated.rst +++ b/vendor/twig/twig/doc/deprecated.rst @@ -93,6 +93,13 @@ Interfaces * As of Twig 2.7, the ``Twig\Extension\InitRuntimeInterface`` interface is deprecated and will be removed in Twig 3.0. +Extensions +---------- + +* As of Twig 2.11, the ``Twig\Extension\CoreExtension::setEscaper()`` and + ``Twig\Extension\CoreExtension::getEscapers()`` are deprecated. Use the same + methods on ``Twig\Extension\EscaperExtension`` instead. + Miscellaneous ------------- diff --git a/vendor/twig/twig/doc/filters/filter.rst b/vendor/twig/twig/doc/filters/filter.rst index a6c1c132db..310e63a73e 100644 --- a/vendor/twig/twig/doc/filters/filter.rst +++ b/vendor/twig/twig/doc/filters/filter.rst @@ -2,7 +2,7 @@ ========= .. versionadded:: 1.41 - The ``filter`` filter was added in Twig 1.41. + The ``filter`` filter was added in Twig 1.41 and 2.10. The ``filter`` filter filters elements of a sequence or a mapping using an arrow function. The arrow function receives the value of the sequence or mapping: @@ -11,11 +11,22 @@ function. The arrow function receives the value of the sequence or mapping: {% set sizes = [34, 36, 38, 40, 42] %} + {{ sizes|filter(v => v > 38)|join(', ') }} + {# output 40, 42 #} + +Combined with the ``for`` tag, it allows to filter the items to iterate over: + +.. code-block:: twig + {% for v in sizes|filter(v => v > 38) -%} {{ v }} {% endfor %} {# output 40 42 #} +It also works with mappings: + +.. code-block:: twig + {% set sizes = { xs: 34, s: 36, diff --git a/vendor/twig/twig/doc/filters/map.rst b/vendor/twig/twig/doc/filters/map.rst index b4849a6297..777b05a7c3 100644 --- a/vendor/twig/twig/doc/filters/map.rst +++ b/vendor/twig/twig/doc/filters/map.rst @@ -2,7 +2,7 @@ ======= .. versionadded:: 1.41 - The ``map`` filter was added in Twig 1.41. + The ``map`` filter was added in Twig 1.41 and 2.10. The ``map`` filter applies an arrow function to the elements of a sequence or a mapping. The arrow function receives the value of the sequence or mapping: diff --git a/vendor/twig/twig/doc/filters/reduce.rst b/vendor/twig/twig/doc/filters/reduce.rst index 10a0d5acb5..7d04d6c7b0 100644 --- a/vendor/twig/twig/doc/filters/reduce.rst +++ b/vendor/twig/twig/doc/filters/reduce.rst @@ -2,7 +2,7 @@ ========= .. versionadded:: 1.41 - The ``reduce`` filter was added in Twig 1.41. + The ``reduce`` filter was added in Twig 1.41 and 2.10. The ``reduce`` filter iteratively reduces a sequence or a mapping to a single value using an arrow function, so as to reduce it to a single value. The arrow diff --git a/vendor/twig/twig/doc/tags/for.rst b/vendor/twig/twig/doc/tags/for.rst index 77b1dc82d6..8e9b4e5824 100644 --- a/vendor/twig/twig/doc/tags/for.rst +++ b/vendor/twig/twig/doc/tags/for.rst @@ -84,6 +84,13 @@ Variable Description Adding a condition ------------------ +.. tip:: + + As of Twig 2.10, use the :doc:`filter <../filters/filter>` filter instead, + or an ``if`` condition inside the ``for`` body (if your condition depends on + a variable updated inside the loop and you are not using the ``loop`` + variable). + Unlike in PHP, it's not possible to ``break`` or ``continue`` in a loop. You can however filter the sequence during iteration which allows you to skip items. The following example skips all the users which are not active: diff --git a/vendor/twig/twig/doc/tags/from.rst b/vendor/twig/twig/doc/tags/from.rst index 39334fdde2..96c439aa74 100644 --- a/vendor/twig/twig/doc/tags/from.rst +++ b/vendor/twig/twig/doc/tags/from.rst @@ -3,6 +3,4 @@ The ``from`` tag imports :doc:`macro<../tags/macro>` names into the current namespace. The tag is documented in detail in the documentation for the -:doc:`import<../tags/import>` tag. - -.. seealso:: :doc:`macro<../tags/macro>`, :doc:`import<../tags/import>` +:doc:`macro<../tags/macro>` tag. diff --git a/vendor/twig/twig/doc/tags/import.rst b/vendor/twig/twig/doc/tags/import.rst index 255a586928..f217479fb3 100644 --- a/vendor/twig/twig/doc/tags/import.rst +++ b/vendor/twig/twig/doc/tags/import.rst @@ -1,65 +1,6 @@ ``import`` ========== -Twig supports putting often used code into :doc:`macros<../tags/macro>`. These -macros are defined in regular templates. - -Imagine having a generic helper template that define how to render forms via -macros (called ``forms.html``): - -.. code-block:: twig - - {% macro input(name, value, type, size) %} - - {% endmacro %} - - {% macro textarea(name, value, rows, cols) %} - - {% endmacro %} - -There are two ways to import macros. You can import the complete template -containing the macros into a local variable or only import specific macros from -the template. - -The easiest and most flexible is importing the whole module into a local -variable: - -.. code-block:: twig - - {% import 'forms.html' as forms %} - -
-
Username
-
{{ forms.input('username') }}
-
Password
-
{{ forms.input('password', null, 'password') }}
-
-

{{ forms.textarea('comment') }}

- -Alternatively you can import names from the template into the current -namespace: - -.. code-block:: twig - - {% from 'forms.html' import input as input_field, textarea %} - -
-
Username
-
{{ input_field('username') }}
-
Password
-
{{ input_field('password', '', 'password') }}
-
-

{{ textarea('comment') }}

- -.. note:: - - Importing macros using ``import`` or ``from`` is **local** to the current - file. The imported macros are not available in included templates or child - templates; you need to explicitely re-import macros in each file. - -.. tip:: - - To import macros from the current file, use the special ``_self`` variable - for the source. - -.. seealso:: :doc:`macro<../tags/macro>`, :doc:`from<../tags/from>` +The ``import`` tag imports :doc:`macro<../tags/macro>` names in a local +variable. The tag is documented in detail in the documentation for the +:doc:`macro<../tags/macro>` tag. diff --git a/vendor/twig/twig/doc/tags/macro.rst b/vendor/twig/twig/doc/tags/macro.rst index c173f9f7f9..33808f5a8f 100644 --- a/vendor/twig/twig/doc/tags/macro.rst +++ b/vendor/twig/twig/doc/tags/macro.rst @@ -2,10 +2,12 @@ ========= Macros are comparable with functions in regular programming languages. They -are useful to put often used HTML idioms into reusable elements to not repeat -yourself. +are useful to reuse template fragments to not repeat yourself. -Here is a small example of a macro that renders a form element: +Macros are defined in regular templates. + +Imagine having a generic helper template that define how to render HTML forms +via macros (called ``forms.html``): .. code-block:: twig @@ -13,8 +15,12 @@ Here is a small example of a macro that renders a form element: {% endmacro %} -Each argument can have a default value (here ``text`` is the default value for -``type`` if not provided in the call). + {% macro textarea(name, value, rows = 10, cols = 40) %} + + {% endmacro %} + +Each macro argument can have a default value (here ``text`` is the default value +for ``type`` if not provided in the call). Macros differ from native PHP functions in a few ways: @@ -31,53 +37,135 @@ variables. You can pass the whole context as an argument by using the special ``_context`` variable. -Import ------- +Importing Macros +---------------- -Macros can be defined in any template, and need to be "imported" before being -used (see the documentation for the :doc:`import<../tags/import>` tag for more -information): +There are two ways to import macros. You can import the complete template +containing the macros into a local variable (via the ``import`` tag) or only +import specific macros from the template (via the ``from`` tag). + +To import all macros from a template into a local variable, use the ``import`` +tag: .. code-block:: twig {% import "forms.html" as forms %} -The above ``import`` call imports the "forms.html" file (which can contain only -macros, or a template and some macros), and import the functions as items of +The above ``import`` call imports the ``forms.html`` file (which can contain +only macros, or a template and some macros), and import the macros as items of the ``forms`` local variable. -The macro can then be called at will in the current template: +The macros can then be called at will in the *current* template: .. code-block:: twig

{{ forms.input('username') }}

{{ forms.input('password', null, 'password') }}

-If macros are defined and used in the same template, you can use the -special ``_self`` variable to import them: +Alternatively you can import names from the template into the current namespace +via the ``from`` tag: .. code-block:: twig - {% import _self as forms %} + {% from 'forms.html' import input as input_field, textarea %} -

{{ forms.input('username') }}

+

{{ input_field('password', '', 'password') }}

+

{{ textarea('comment') }}

-When you want to use a macro in another macro from the same file, you need to -import it locally: +.. tip:: -.. code-block:: twig + When macro usages and definitions are in the same template, you don't need to + import the macros as they are automatically available under the special + ``_self`` variable: - {% macro input(name, value, type, size) %} - - {% endmacro %} + .. code-block:: twig + +

{{ _self.input('password', '', 'password') }}

+ + {% macro input(name, value, type = "text", size = 20) %} + + {% endmacro %} + + Auto-import is only available as of Twig 2.11. For older versions, import + macros using the special ``_self`` variable for the template name: + + .. code-block:: twig - {% macro wrapped_input(name, value, type, size) %} {% import _self as forms %} -
- {{ forms.input(name, value, type, size) }} -
- {% endmacro %} +

{{ forms.input('username') }}

+ +.. note:: + + Before Twig 2.11, when you want to use a macro in another macro from the + same file, you need to import it locally: + + .. code-block:: twig + + {% macro input(name, value, type, size) %} + + {% endmacro %} + + {% macro wrapped_input(name, value, type, size) %} + {% import _self as forms %} + +
+ {{ forms.input(name, value, type, size) }} +
+ {% endmacro %} + +Macros Scoping +-------------- + +.. versionadded:: 2.11 + + The scoping rules described in this paragraph are implemented as of Twig + 2.11. + +The scoping rules are the same whether you imported macros via ``import`` or +``from``. + +Imported macros are always **local** to the current template. It means that +macros are available in all blocks and other macros defined in the current +template, but they are not available in included templates or child templates; +you need to explicitely re-import macros in each template. + +When calling ``import`` or ``from`` from a ``block`` tag, the imported macros +are only defined in the current block and they override macros defined at the +template level with the same names. + +When calling ``import`` or ``from`` from a ``macro`` tag, the imported macros +are only defined in the current macro and they override macros defined at the +template level with the same names. + +.. note:: + + Before Twig 2.11, it was possible to use macros imported in a block in a + "sub-block". When upgrading to 2.11, you need to either move the import in + the global scope or reimport the macros explicitly in the "sub-blocks". + +Checking if a Macro is defined +------------------------------ + +.. versionadded:: 2.11 + + Support for the ``defined`` test on macros was added in Twig 2.11. + +You can check if a macro is defined via the ``defined`` test: + +.. code-block:: twig + + {% import "macros.twig" as macros %} + + {% from "macros.twig" import hello %} + + {% if macros.hello is defined -%} + OK + {% endif %} + + {% if hello is defined -%} + OK + {% endif %} Named Macro End-Tags -------------------- @@ -92,5 +180,3 @@ readability: {% endmacro input %} Of course, the name after the ``endmacro`` word must match the macro name. - -.. seealso:: :doc:`from<../tags/from>`, :doc:`import<../tags/import>` diff --git a/vendor/twig/twig/doc/templates.rst b/vendor/twig/twig/doc/templates.rst index f8f57c9e4f..3aa3ea96f9 100644 --- a/vendor/twig/twig/doc/templates.rst +++ b/vendor/twig/twig/doc/templates.rst @@ -494,52 +494,9 @@ For bigger sections it makes sense to mark a block Macros ------ -Macros are comparable with functions in regular programming languages. They -are useful to reuse often used HTML fragments to not repeat yourself. - -A macro is defined via the :doc:`macro` tag. Here is a small example -(subsequently called ``forms.html``) of a macro that renders a form element: - -.. code-block:: twig - - {% macro input(name, value, type, size) %} - - {% endmacro %} - -Macros can be defined in any template, and need to be "imported" via the -:doc:`import` tag before being used: - -.. code-block:: twig - - {% import "forms.html" as forms %} - -

{{ forms.input('username') }}

- -Alternatively, you can import individual macro names from a template into the -current namespace via the :doc:`from` tag and optionally alias them: - -.. code-block:: twig - - {% from 'forms.html' import input as input_field %} - -
-
Username
-
{{ input_field('username') }}
-
Password
-
{{ input_field('password', '', 'password') }}
-
- -A default value can also be defined for macro arguments when not provided in a -macro call: - -.. code-block:: twig - - {% macro input(name, value = "", type = "text", size = 20) %} - - {% endmacro %} - -If extra positional arguments are passed to a macro call, they end up in the -special ``varargs`` variable as a list of values. +Macros are comparable with functions in regular programming languages. They are +useful to reuse HTML fragments to not repeat yourself. They are described in the +:doc:`macro` tag documentation. .. _twig-expressions: diff --git a/vendor/twig/twig/src/Environment.php b/vendor/twig/twig/src/Environment.php index 89828b7b0a..926ce1a05e 100644 --- a/vendor/twig/twig/src/Environment.php +++ b/vendor/twig/twig/src/Environment.php @@ -38,11 +38,11 @@ */ class Environment { - const VERSION = '2.10.0'; - const VERSION_ID = 21000; + const VERSION = '2.11.2'; + const VERSION_ID = 21102; const MAJOR_VERSION = 2; - const MINOR_VERSION = 10; - const RELEASE_VERSION = 0; + const MINOR_VERSION = 11; + const RELEASE_VERSION = 2; const EXTRA_VERSION = ''; private $charset; diff --git a/vendor/twig/twig/src/ExpressionParser.php b/vendor/twig/twig/src/ExpressionParser.php index 4c346406af..2c5df63f14 100644 --- a/vendor/twig/twig/src/ExpressionParser.php +++ b/vendor/twig/twig/src/ExpressionParser.php @@ -635,7 +635,13 @@ public function parseAssignmentExpression() $stream = $this->parser->getStream(); $targets = []; while (true) { - $token = $stream->expect(/* Token::NAME_TYPE */ 5, null, 'Only variables can be assigned to'); + $token = $this->parser->getCurrentToken(); + if ($stream->test(/* Token::OPERATOR_TYPE */ 8) && preg_match(Lexer::REGEX_NAME, $token->getValue())) { + // in this context, string operators are variable names + $this->parser->getStream()->next(); + } else { + $stream->expect(/* Token::NAME_TYPE */ 5, null, 'Only variables can be assigned to'); + } $value = $token->getValue(); if (\in_array(strtolower($value), ['true', 'false', 'none', 'null'])) { throw new SyntaxError(sprintf('You cannot assign a value to "%s".', $value), $token->getLine(), $stream->getSourceContext()); @@ -679,6 +685,11 @@ private function parseTestExpression(Node $node): TestExpression $arguments = $this->parseArguments(true); } + if ('defined' === $name && $node instanceof NameExpression && null !== $alias = $this->parser->getImportedSymbol('function', $node->getAttribute('name'))) { + $node = new MethodCallExpression($alias['node'], $alias['name'], new ArrayExpression([], $node->getTemplateLine()), $node->getTemplateLine()); + $node->setAttribute('safe', true); + } + return new $class($node, $name, $arguments, $this->parser->getCurrentToken()->getLine()); } diff --git a/vendor/twig/twig/src/Extension/CoreExtension.php b/vendor/twig/twig/src/Extension/CoreExtension.php index f0aa6265b2..eda0487276 100644 --- a/vendor/twig/twig/src/Extension/CoreExtension.php +++ b/vendor/twig/twig/src/Extension/CoreExtension.php @@ -48,6 +48,7 @@ use Twig\Node\Expression\Unary\NegUnary; use Twig\Node\Expression\Unary\NotUnary; use Twig\Node\Expression\Unary\PosUnary; +use Twig\NodeVisitor\MacroAutoImportNodeVisitor; use Twig\TokenParser\ApplyTokenParser; use Twig\TokenParser\BlockTokenParser; use Twig\TokenParser\DeprecatedTokenParser; @@ -82,9 +83,13 @@ final class CoreExtension extends AbstractExtension * * @param string $strategy The strategy name that should be used as a strategy in the escape call * @param callable $callable A valid PHP callable + * + * @deprecated since Twig 2.11, to be removed in 3.0; use the same method on EscaperExtension instead */ public function setEscaper($strategy, callable $callable) { + @trigger_error(sprintf('The "%s" method is deprecated since Twig 2.11; use "%s::setEscaper" instead.', __METHOD__, EscaperExtension::class), E_USER_DEPRECATED); + $this->escapers[$strategy] = $callable; } @@ -92,9 +97,15 @@ public function setEscaper($strategy, callable $callable) * Gets all defined escapers. * * @return callable[] An array of escapers + * + * @deprecated since Twig 2.11, to be removed in 3.0; use the same method on EscaperExtension instead */ - public function getEscapers() + public function getEscapers(/* $triggerDeprecation = true */) { + if (0 === \func_num_args() || func_get_arg(0)) { + @trigger_error(sprintf('The "%s" method is deprecated since Twig 2.11; use "%s::getEscapers" instead.', __METHOD__, EscaperExtension::class), E_USER_DEPRECATED); + } + return $this->escapers; } @@ -243,10 +254,6 @@ public function getFilters() // iteration and runtime new TwigFilter('default', '_twig_default_filter', ['node_class' => DefaultFilter::class]), new TwigFilter('keys', 'twig_get_array_keys_filter'), - - // escaping - new TwigFilter('escape', 'twig_escape_filter', ['needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe']), - new TwigFilter('e', 'twig_escape_filter', ['needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe']), ]; } @@ -281,6 +288,11 @@ public function getTests() ]; } + public function getNodeVisitors() + { + return [new MacroAutoImportNodeVisitor()]; + } + public function getOperators() { return [ @@ -333,8 +345,6 @@ class_alias('Twig\Extension\CoreExtension', 'Twig_Extension_Core'); use Twig\Extension\CoreExtension; use Twig\Extension\SandboxExtension; use Twig\Markup; - use Twig\Node\Expression\ConstantExpression; - use Twig\Node\Node; use Twig\Source; use Twig\Template; @@ -915,6 +925,9 @@ function twig_in_filter($value, $compare) if ($value instanceof Markup) { $value = (string) $value; } + if ($compare instanceof Markup) { + $compare = (string) $compare; + } if (\is_array($compare)) { return \in_array($value, $compare, \is_object($value) || \is_resource($value)); @@ -976,250 +989,6 @@ function twig_spaceless($content) return trim(preg_replace('/>\s+<', $content)); } -/** - * Escapes a string. - * - * @param mixed $string The value to be escaped - * @param string $strategy The escaping strategy - * @param string $charset The charset - * @param bool $autoescape Whether the function is called by the auto-escaping feature (true) or by the developer (false) - * - * @return string - */ -function twig_escape_filter(Environment $env, $string, $strategy = 'html', $charset = null, $autoescape = false) -{ - if ($autoescape && $string instanceof Markup) { - return $string; - } - - if (!\is_string($string)) { - if (\is_object($string) && method_exists($string, '__toString')) { - $string = (string) $string; - } elseif (\in_array($strategy, ['html', 'js', 'css', 'html_attr', 'url'])) { - return $string; - } - } - - if ('' === $string) { - return ''; - } - - if (null === $charset) { - $charset = $env->getCharset(); - } - - switch ($strategy) { - case 'html': - // see https://secure.php.net/htmlspecialchars - - // Using a static variable to avoid initializing the array - // each time the function is called. Moving the declaration on the - // top of the function slow downs other escaping strategies. - static $htmlspecialcharsCharsets = [ - 'ISO-8859-1' => true, 'ISO8859-1' => true, - 'ISO-8859-15' => true, 'ISO8859-15' => true, - 'utf-8' => true, 'UTF-8' => true, - 'CP866' => true, 'IBM866' => true, '866' => true, - 'CP1251' => true, 'WINDOWS-1251' => true, 'WIN-1251' => true, - '1251' => true, - 'CP1252' => true, 'WINDOWS-1252' => true, '1252' => true, - 'KOI8-R' => true, 'KOI8-RU' => true, 'KOI8R' => true, - 'BIG5' => true, '950' => true, - 'GB2312' => true, '936' => true, - 'BIG5-HKSCS' => true, - 'SHIFT_JIS' => true, 'SJIS' => true, '932' => true, - 'EUC-JP' => true, 'EUCJP' => true, - 'ISO8859-5' => true, 'ISO-8859-5' => true, 'MACROMAN' => true, - ]; - - if (isset($htmlspecialcharsCharsets[$charset])) { - return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, $charset); - } - - if (isset($htmlspecialcharsCharsets[strtoupper($charset)])) { - // cache the lowercase variant for future iterations - $htmlspecialcharsCharsets[$charset] = true; - - return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, $charset); - } - - $string = iconv($charset, 'UTF-8', $string); - $string = htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); - - return iconv('UTF-8', $charset, $string); - - case 'js': - // escape all non-alphanumeric characters - // into their \x or \uHHHH representations - if ('UTF-8' !== $charset) { - $string = iconv($charset, 'UTF-8', $string); - } - - if (!preg_match('//u', $string)) { - throw new RuntimeError('The string to escape is not a valid UTF-8 string.'); - } - - $string = preg_replace_callback('#[^a-zA-Z0-9,\._]#Su', function ($matches) { - $char = $matches[0]; - - /* - * A few characters have short escape sequences in JSON and JavaScript. - * Escape sequences supported only by JavaScript, not JSON, are ommitted. - * \" is also supported but omitted, because the resulting string is not HTML safe. - */ - static $shortMap = [ - '\\' => '\\\\', - '/' => '\\/', - "\x08" => '\b', - "\x0C" => '\f', - "\x0A" => '\n', - "\x0D" => '\r', - "\x09" => '\t', - ]; - - if (isset($shortMap[$char])) { - return $shortMap[$char]; - } - - // \uHHHH - $char = twig_convert_encoding($char, 'UTF-16BE', 'UTF-8'); - $char = strtoupper(bin2hex($char)); - - if (4 >= \strlen($char)) { - return sprintf('\u%04s', $char); - } - - return sprintf('\u%04s\u%04s', substr($char, 0, -4), substr($char, -4)); - }, $string); - - if ('UTF-8' !== $charset) { - $string = iconv('UTF-8', $charset, $string); - } - - return $string; - - case 'css': - if ('UTF-8' !== $charset) { - $string = iconv($charset, 'UTF-8', $string); - } - - if (!preg_match('//u', $string)) { - throw new RuntimeError('The string to escape is not a valid UTF-8 string.'); - } - - $string = preg_replace_callback('#[^a-zA-Z0-9]#Su', function ($matches) { - $char = $matches[0]; - - return sprintf('\\%X ', 1 === \strlen($char) ? \ord($char) : mb_ord($char, 'UTF-8')); - }, $string); - - if ('UTF-8' !== $charset) { - $string = iconv('UTF-8', $charset, $string); - } - - return $string; - - case 'html_attr': - if ('UTF-8' !== $charset) { - $string = iconv($charset, 'UTF-8', $string); - } - - if (!preg_match('//u', $string)) { - throw new RuntimeError('The string to escape is not a valid UTF-8 string.'); - } - - $string = preg_replace_callback('#[^a-zA-Z0-9,\.\-_]#Su', function ($matches) { - /** - * This function is adapted from code coming from Zend Framework. - * - * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (https://www.zend.com) - * @license https://framework.zend.com/license/new-bsd New BSD License - */ - $chr = $matches[0]; - $ord = \ord($chr); - - /* - * The following replaces characters undefined in HTML with the - * hex entity for the Unicode replacement character. - */ - if (($ord <= 0x1f && "\t" != $chr && "\n" != $chr && "\r" != $chr) || ($ord >= 0x7f && $ord <= 0x9f)) { - return '�'; - } - - /* - * Check if the current character to escape has a name entity we should - * replace it with while grabbing the hex value of the character. - */ - if (1 === \strlen($chr)) { - /* - * While HTML supports far more named entities, the lowest common denominator - * has become HTML5's XML Serialisation which is restricted to the those named - * entities that XML supports. Using HTML entities would result in this error: - * XML Parsing Error: undefined entity - */ - static $entityMap = [ - 34 => '"', /* quotation mark */ - 38 => '&', /* ampersand */ - 60 => '<', /* less-than sign */ - 62 => '>', /* greater-than sign */ - ]; - - if (isset($entityMap[$ord])) { - return $entityMap[$ord]; - } - - return sprintf('&#x%02X;', $ord); - } - - /* - * Per OWASP recommendations, we'll use hex entities for any other - * characters where a named entity does not exist. - */ - return sprintf('&#x%04X;', mb_ord($chr, 'UTF-8')); - }, $string); - - if ('UTF-8' !== $charset) { - $string = iconv('UTF-8', $charset, $string); - } - - return $string; - - case 'url': - return rawurlencode($string); - - default: - static $escapers; - - if (null === $escapers) { - $escapers = $env->getExtension(CoreExtension::class)->getEscapers(); - } - - if (isset($escapers[$strategy])) { - return $escapers[$strategy]($env, $string, $charset); - } - - $validStrategies = implode(', ', array_merge(['html', 'js', 'url', 'css', 'html_attr'], array_keys($escapers))); - - throw new RuntimeError(sprintf('Invalid escaping strategy "%s" (valid ones: %s).', $strategy, $validStrategies)); - } -} - -/** - * @internal - */ -function twig_escape_filter_is_safe(Node $filterArgs) -{ - foreach ($filterArgs as $arg) { - if ($arg instanceof ConstantExpression) { - return [$arg->getAttribute('value')]; - } - - return []; - } - - return ['html']; -} - function twig_convert_encoding($string, $to, $from) { return iconv($from, $to, $string); @@ -1311,6 +1080,25 @@ function twig_capitalize_string_filter(Environment $env, $string) return mb_strtoupper(mb_substr($string, 0, 1, $charset), $charset).mb_strtolower(mb_substr($string, 1, null, $charset), $charset); } +/** + * @internal + */ +function twig_call_macro(Template $template, string $method, array $args, int $lineno, array $context, Source $source) +{ + if (!method_exists($template, $method)) { + $parent = $template; + while ($parent = $parent->getParent($context)) { + if (method_exists($parent, $method)) { + return $parent->$method(...$args); + } + } + + throw new RuntimeError(sprintf('Macro "%s" is not defined in template "%s".', substr($method, \strlen('macro_')), $template->getTemplateName()), $lineno, $source); + } + + return $template->$method(...$args); +} + /** * @internal */ @@ -1734,19 +1522,11 @@ function twig_array_column($array, $name): array function twig_array_filter($array, $arrow) { - if (\is_array($array)) { - if (\PHP_VERSION_ID >= 50600) { - return array_filter($array, $arrow, \ARRAY_FILTER_USE_BOTH); + foreach ($array as $k => $v) { + if ($arrow($v, $k)) { + yield $k => $v; } - - return array_filter($array, $arrow); } - - while ($array instanceof \IteratorAggregate) { - $array = $array->getIterator(); - } - - return new \CallbackFilterIterator($array, $arrow); } function twig_array_map($array, $arrow) diff --git a/vendor/twig/twig/src/Extension/EscaperExtension.php b/vendor/twig/twig/src/Extension/EscaperExtension.php index b024ab11b3..b8dabccf11 100644 --- a/vendor/twig/twig/src/Extension/EscaperExtension.php +++ b/vendor/twig/twig/src/Extension/EscaperExtension.php @@ -18,6 +18,13 @@ final class EscaperExtension extends AbstractExtension { private $defaultStrategy; + private $escapers = []; + + /** @internal */ + public $safeClasses = []; + + /** @internal */ + public $safeLookup = []; /** * @param string|false|callable $defaultStrategy An escaping strategy @@ -42,6 +49,8 @@ public function getNodeVisitors() public function getFilters() { return [ + new TwigFilter('escape', 'twig_escape_filter', ['needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe']), + new TwigFilter('e', 'twig_escape_filter', ['needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe']), new TwigFilter('raw', 'twig_raw_filter', ['is_safe' => ['all']]), ]; } @@ -80,12 +89,63 @@ public function getDefaultStrategy($name) return $this->defaultStrategy; } + + /** + * Defines a new escaper to be used via the escape filter. + * + * @param string $strategy The strategy name that should be used as a strategy in the escape call + * @param callable $callable A valid PHP callable + */ + public function setEscaper($strategy, callable $callable) + { + $this->escapers[$strategy] = $callable; + } + + /** + * Gets all defined escapers. + * + * @return callable[] An array of escapers + */ + public function getEscapers() + { + return $this->escapers; + } + + public function setSafeClasses(array $safeClasses = []) + { + $this->safeClasses = []; + $this->safeLookup = []; + foreach ($safeClasses as $class => $strategies) { + $this->addSafeClass($class, $strategies); + } + } + + public function addSafeClass(string $class, array $strategies) + { + $class = ltrim($class, '\\'); + if (!isset($this->safeClasses[$class])) { + $this->safeClasses[$class] = []; + } + $this->safeClasses[$class] = array_merge($this->safeClasses[$class], $strategies); + + foreach ($strategies as $strategy) { + $this->safeLookup[$strategy][$class] = true; + } + } } class_alias('Twig\Extension\EscaperExtension', 'Twig_Extension_Escaper'); } namespace { +use Twig\Environment; +use Twig\Error\RuntimeError; +use Twig\Extension\CoreExtension; +use Twig\Extension\EscaperExtension; +use Twig\Markup; +use Twig\Node\Expression\ConstantExpression; +use Twig\Node\Node; + /** * Marks a variable as being safe. * @@ -97,4 +157,271 @@ function twig_raw_filter($string) { return $string; } + +/** + * Escapes a string. + * + * @param mixed $string The value to be escaped + * @param string $strategy The escaping strategy + * @param string $charset The charset + * @param bool $autoescape Whether the function is called by the auto-escaping feature (true) or by the developer (false) + * + * @return string + */ +function twig_escape_filter(Environment $env, $string, $strategy = 'html', $charset = null, $autoescape = false) +{ + if ($autoescape && $string instanceof Markup) { + return $string; + } + + if (!\is_string($string)) { + if (\is_object($string) && method_exists($string, '__toString')) { + if ($autoescape) { + $c = \get_class($string); + $ext = $env->getExtension(EscaperExtension::class); + if (!isset($ext->safeClasses[$c])) { + $ext->safeClasses[$c] = []; + foreach (class_parents($string) + class_implements($string) as $class) { + if (isset($ext->safeClasses[$class])) { + $ext->safeClasses[$c] = array_unique(array_merge($ext->safeClasses[$c], $ext->safeClasses[$class])); + foreach ($ext->safeClasses[$class] as $s) { + $ext->safeLookup[$s][$c] = true; + } + } + } + } + if (isset($ext->safeLookup[$strategy][$c]) || isset($ext->safeLookup['all'][$c])) { + return (string) $string; + } + } + + $string = (string) $string; + } elseif (\in_array($strategy, ['html', 'js', 'css', 'html_attr', 'url'])) { + return $string; + } + } + + if ('' === $string) { + return ''; + } + + if (null === $charset) { + $charset = $env->getCharset(); + } + + switch ($strategy) { + case 'html': + // see https://secure.php.net/htmlspecialchars + + // Using a static variable to avoid initializing the array + // each time the function is called. Moving the declaration on the + // top of the function slow downs other escaping strategies. + static $htmlspecialcharsCharsets = [ + 'ISO-8859-1' => true, 'ISO8859-1' => true, + 'ISO-8859-15' => true, 'ISO8859-15' => true, + 'utf-8' => true, 'UTF-8' => true, + 'CP866' => true, 'IBM866' => true, '866' => true, + 'CP1251' => true, 'WINDOWS-1251' => true, 'WIN-1251' => true, + '1251' => true, + 'CP1252' => true, 'WINDOWS-1252' => true, '1252' => true, + 'KOI8-R' => true, 'KOI8-RU' => true, 'KOI8R' => true, + 'BIG5' => true, '950' => true, + 'GB2312' => true, '936' => true, + 'BIG5-HKSCS' => true, + 'SHIFT_JIS' => true, 'SJIS' => true, '932' => true, + 'EUC-JP' => true, 'EUCJP' => true, + 'ISO8859-5' => true, 'ISO-8859-5' => true, 'MACROMAN' => true, + ]; + + if (isset($htmlspecialcharsCharsets[$charset])) { + return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, $charset); + } + + if (isset($htmlspecialcharsCharsets[strtoupper($charset)])) { + // cache the lowercase variant for future iterations + $htmlspecialcharsCharsets[$charset] = true; + + return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, $charset); + } + + $string = iconv($charset, 'UTF-8', $string); + $string = htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); + + return iconv('UTF-8', $charset, $string); + + case 'js': + // escape all non-alphanumeric characters + // into their \x or \uHHHH representations + if ('UTF-8' !== $charset) { + $string = iconv($charset, 'UTF-8', $string); + } + + if (!preg_match('//u', $string)) { + throw new RuntimeError('The string to escape is not a valid UTF-8 string.'); + } + + $string = preg_replace_callback('#[^a-zA-Z0-9,\._]#Su', function ($matches) { + $char = $matches[0]; + + /* + * A few characters have short escape sequences in JSON and JavaScript. + * Escape sequences supported only by JavaScript, not JSON, are ommitted. + * \" is also supported but omitted, because the resulting string is not HTML safe. + */ + static $shortMap = [ + '\\' => '\\\\', + '/' => '\\/', + "\x08" => '\b', + "\x0C" => '\f', + "\x0A" => '\n', + "\x0D" => '\r', + "\x09" => '\t', + ]; + + if (isset($shortMap[$char])) { + return $shortMap[$char]; + } + + // \uHHHH + $char = twig_convert_encoding($char, 'UTF-16BE', 'UTF-8'); + $char = strtoupper(bin2hex($char)); + + if (4 >= \strlen($char)) { + return sprintf('\u%04s', $char); + } + + return sprintf('\u%04s\u%04s', substr($char, 0, -4), substr($char, -4)); + }, $string); + + if ('UTF-8' !== $charset) { + $string = iconv('UTF-8', $charset, $string); + } + + return $string; + + case 'css': + if ('UTF-8' !== $charset) { + $string = iconv($charset, 'UTF-8', $string); + } + + if (!preg_match('//u', $string)) { + throw new RuntimeError('The string to escape is not a valid UTF-8 string.'); + } + + $string = preg_replace_callback('#[^a-zA-Z0-9]#Su', function ($matches) { + $char = $matches[0]; + + return sprintf('\\%X ', 1 === \strlen($char) ? \ord($char) : mb_ord($char, 'UTF-8')); + }, $string); + + if ('UTF-8' !== $charset) { + $string = iconv('UTF-8', $charset, $string); + } + + return $string; + + case 'html_attr': + if ('UTF-8' !== $charset) { + $string = iconv($charset, 'UTF-8', $string); + } + + if (!preg_match('//u', $string)) { + throw new RuntimeError('The string to escape is not a valid UTF-8 string.'); + } + + $string = preg_replace_callback('#[^a-zA-Z0-9,\.\-_]#Su', function ($matches) { + /** + * This function is adapted from code coming from Zend Framework. + * + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (https://www.zend.com) + * @license https://framework.zend.com/license/new-bsd New BSD License + */ + $chr = $matches[0]; + $ord = \ord($chr); + + /* + * The following replaces characters undefined in HTML with the + * hex entity for the Unicode replacement character. + */ + if (($ord <= 0x1f && "\t" != $chr && "\n" != $chr && "\r" != $chr) || ($ord >= 0x7f && $ord <= 0x9f)) { + return '�'; + } + + /* + * Check if the current character to escape has a name entity we should + * replace it with while grabbing the hex value of the character. + */ + if (1 === \strlen($chr)) { + /* + * While HTML supports far more named entities, the lowest common denominator + * has become HTML5's XML Serialisation which is restricted to the those named + * entities that XML supports. Using HTML entities would result in this error: + * XML Parsing Error: undefined entity + */ + static $entityMap = [ + 34 => '"', /* quotation mark */ + 38 => '&', /* ampersand */ + 60 => '<', /* less-than sign */ + 62 => '>', /* greater-than sign */ + ]; + + if (isset($entityMap[$ord])) { + return $entityMap[$ord]; + } + + return sprintf('&#x%02X;', $ord); + } + + /* + * Per OWASP recommendations, we'll use hex entities for any other + * characters where a named entity does not exist. + */ + return sprintf('&#x%04X;', mb_ord($chr, 'UTF-8')); + }, $string); + + if ('UTF-8' !== $charset) { + $string = iconv('UTF-8', $charset, $string); + } + + return $string; + + case 'url': + return rawurlencode($string); + + default: + static $escapers; + + if (null === $escapers) { + // merge the ones set on CoreExtension for BC (to be removed in 3.0) + $escapers = array_merge( + $env->getExtension(CoreExtension::class)->getEscapers(false), + $env->getExtension(EscaperExtension::class)->getEscapers() + ); + } + + if (isset($escapers[$strategy])) { + return $escapers[$strategy]($env, $string, $charset); + } + + $validStrategies = implode(', ', array_merge(['html', 'js', 'url', 'css', 'html_attr'], array_keys($escapers))); + + throw new RuntimeError(sprintf('Invalid escaping strategy "%s" (valid ones: %s).', $strategy, $validStrategies)); + } +} + +/** + * @internal + */ +function twig_escape_filter_is_safe(Node $filterArgs) +{ + foreach ($filterArgs as $arg) { + if ($arg instanceof ConstantExpression) { + return [$arg->getAttribute('value')]; + } + + return []; + } + + return ['html']; +} } diff --git a/vendor/twig/twig/src/Node/BlockNode.php b/vendor/twig/twig/src/Node/BlockNode.php index cafceb95b3..4da6e6ff77 100644 --- a/vendor/twig/twig/src/Node/BlockNode.php +++ b/vendor/twig/twig/src/Node/BlockNode.php @@ -32,6 +32,7 @@ public function compile(Compiler $compiler) ->addDebugInfo($this) ->write(sprintf("public function block_%s(\$context, array \$blocks = [])\n", $this->getAttribute('name')), "{\n") ->indent() + ->write("\$macros = \$this->macros;\n") ; $compiler diff --git a/vendor/twig/twig/src/Node/Expression/MethodCallExpression.php b/vendor/twig/twig/src/Node/Expression/MethodCallExpression.php index 7450c51802..d5287f85f3 100644 --- a/vendor/twig/twig/src/Node/Expression/MethodCallExpression.php +++ b/vendor/twig/twig/src/Node/Expression/MethodCallExpression.php @@ -17,7 +17,7 @@ class MethodCallExpression extends AbstractExpression { public function __construct(AbstractExpression $node, string $method, ArrayExpression $arguments, int $lineno) { - parent::__construct(['node' => $node, 'arguments' => $arguments], ['method' => $method, 'safe' => false], $lineno); + parent::__construct(['node' => $node, 'arguments' => $arguments], ['method' => $method, 'safe' => false, 'is_defined_test' => false], $lineno); if ($node instanceof NameExpression) { $node->setAttribute('always_defined', true); @@ -26,11 +26,24 @@ public function __construct(AbstractExpression $node, string $method, ArrayExpre public function compile(Compiler $compiler) { + if ($this->getAttribute('is_defined_test')) { + $compiler + ->raw('method_exists($macros[') + ->repr($this->getNode('node')->getAttribute('name')) + ->raw('], ') + ->repr($this->getAttribute('method')) + ->raw(')') + ; + + return; + } + $compiler - ->subcompile($this->getNode('node')) - ->raw('->') - ->raw($this->getAttribute('method')) - ->raw('(') + ->raw('twig_call_macro($macros[') + ->repr($this->getNode('node')->getAttribute('name')) + ->raw('], ') + ->repr($this->getAttribute('method')) + ->raw(', [') ; $first = true; foreach ($this->getNode('arguments')->getKeyValuePairs() as $pair) { @@ -41,7 +54,10 @@ public function compile(Compiler $compiler) $compiler->subcompile($pair['value']); } - $compiler->raw(')'); + $compiler + ->raw('], ') + ->repr($this->getTemplateLine()) + ->raw(', $context, $this->getSourceContext())'); } } diff --git a/vendor/twig/twig/src/Node/Expression/Test/DefinedTest.php b/vendor/twig/twig/src/Node/Expression/Test/DefinedTest.php index e87b835777..7a898406cc 100644 --- a/vendor/twig/twig/src/Node/Expression/Test/DefinedTest.php +++ b/vendor/twig/twig/src/Node/Expression/Test/DefinedTest.php @@ -18,6 +18,7 @@ use Twig\Node\Expression\ConstantExpression; use Twig\Node\Expression\FunctionExpression; use Twig\Node\Expression\GetAttrExpression; +use Twig\Node\Expression\MethodCallExpression; use Twig\Node\Expression\NameExpression; use Twig\Node\Expression\TestExpression; use Twig\Node\Node; @@ -47,6 +48,8 @@ public function __construct(Node $node, string $name, Node $arguments = null, in $node->setAttribute('is_defined_test', true); } elseif ($node instanceof ConstantExpression || $node instanceof ArrayExpression) { $node = new ConstantExpression(true, $node->getTemplateLine()); + } elseif ($node instanceof MethodCallExpression) { + $node->setAttribute('is_defined_test', true); } else { throw new SyntaxError('The "defined" test only works with simple variables.', $lineno); } diff --git a/vendor/twig/twig/src/Node/ImportNode.php b/vendor/twig/twig/src/Node/ImportNode.php index e66954bf99..b661f4313a 100644 --- a/vendor/twig/twig/src/Node/ImportNode.php +++ b/vendor/twig/twig/src/Node/ImportNode.php @@ -22,20 +22,28 @@ */ class ImportNode extends Node { - public function __construct(AbstractExpression $expr, AbstractExpression $var, int $lineno, string $tag = null) + public function __construct(AbstractExpression $expr, AbstractExpression $var, int $lineno, string $tag = null, bool $global = true) { - parent::__construct(['expr' => $expr, 'var' => $var], [], $lineno, $tag); + parent::__construct(['expr' => $expr, 'var' => $var], ['global' => $global], $lineno, $tag); } public function compile(Compiler $compiler) { $compiler ->addDebugInfo($this) - ->write('') - ->subcompile($this->getNode('var')) - ->raw(' = ') + ->write('$macros[') + ->repr($this->getNode('var')->getAttribute('name')) + ->raw('] = ') ; + if ($this->getAttribute('global')) { + $compiler + ->raw('$this->macros[') + ->repr($this->getNode('var')->getAttribute('name')) + ->raw('] = ') + ; + } + if ($this->getNode('expr') instanceof NameExpression && '_self' === $this->getNode('expr')->getAttribute('name')) { $compiler->raw('$this'); } else { diff --git a/vendor/twig/twig/src/Node/MacroNode.php b/vendor/twig/twig/src/Node/MacroNode.php index 8e927a51de..6bb190937d 100644 --- a/vendor/twig/twig/src/Node/MacroNode.php +++ b/vendor/twig/twig/src/Node/MacroNode.php @@ -63,9 +63,7 @@ public function compile(Compiler $compiler) ->raw(")\n") ->write("{\n") ->indent() - ; - - $compiler + ->write("\$macros = \$this->macros;\n") ->write("\$context = \$this->env->mergeGlobals([\n") ->indent() ; diff --git a/vendor/twig/twig/src/Node/ModuleNode.php b/vendor/twig/twig/src/Node/ModuleNode.php index 745ec3a397..b23a342e86 100644 --- a/vendor/twig/twig/src/Node/ModuleNode.php +++ b/vendor/twig/twig/src/Node/ModuleNode.php @@ -166,7 +166,8 @@ protected function compileClassHeader(Compiler $compiler) ->raw(sprintf(" extends %s\n", $compiler->getEnvironment()->getBaseTemplateClass(false))) ->write("{\n") ->indent() - ->write("private \$source;\n\n") + ->write("private \$source;\n") + ->write("private \$macros = [];\n\n") ; } @@ -310,6 +311,7 @@ protected function compileDisplay(Compiler $compiler) $compiler ->write("protected function doDisplay(array \$context, array \$blocks = [])\n", "{\n") ->indent() + ->write("\$macros = \$this->macros;\n") ->subcompile($this->getNode('display_start')) ->subcompile($this->getNode('body')) ; diff --git a/vendor/twig/twig/src/NodeVisitor/MacroAutoImportNodeVisitor.php b/vendor/twig/twig/src/NodeVisitor/MacroAutoImportNodeVisitor.php new file mode 100644 index 0000000000..684e73af4a --- /dev/null +++ b/vendor/twig/twig/src/NodeVisitor/MacroAutoImportNodeVisitor.php @@ -0,0 +1,76 @@ + + */ +final class MacroAutoImportNodeVisitor implements NodeVisitorInterface +{ + private $inAModule = false; + private $hasMacroCalls = false; + + public function enterNode(Node $node, Environment $env) + { + if ($node instanceof ModuleNode) { + $this->inAModule = true; + $this->hasMacroCalls = false; + } + + return $node; + } + + public function leaveNode(Node $node, Environment $env) + { + if ($node instanceof ModuleNode) { + $this->inAModule = false; + if ($this->hasMacroCalls) { + $body = [new ImportNode(new NameExpression('_self', 0), new AssignNameExpression('_self', 0), 0, 'import', true)]; + foreach ($node->getNode('body') as $n) { + $body[] = $n; + } + $node->setNode('body', new Node($body)); + } + } elseif ($this->inAModule) { + if ( + $node instanceof GetAttrExpression && + $node->getNode('node') instanceof NameExpression && + '_self' === $node->getNode('node')->getAttribute('name') && + $node->getNode('attribute') instanceof ConstantExpression + ) { + $this->hasMacroCalls = true; + + $name = $node->getNode('attribute')->getAttribute('value'); + $node = new MethodCallExpression($node->getNode('node'), 'macro_'.$name, $node->getNode('arguments'), $node->getTemplateLine()); + $node->setAttribute('safe', true); + } + } + + return $node; + } + + public function getPriority() + { + // we must be ran before auto-escaping + return -10; + } +} diff --git a/vendor/twig/twig/src/Parser.php b/vendor/twig/twig/src/Parser.php index e93df8d0be..8a937e329d 100644 --- a/vendor/twig/twig/src/Parser.php +++ b/vendor/twig/twig/src/Parser.php @@ -207,7 +207,7 @@ public function getBlockStack() public function peekBlockStack() { - return $this->blockStack[\count($this->blockStack) - 1]; + return isset($this->blockStack[\count($this->blockStack) - 1]) ? $this->blockStack[\count($this->blockStack) - 1] : null; } public function popBlockStack() @@ -279,11 +279,8 @@ public function addImportedSymbol($type, $alias, $name = null, AbstractExpressio public function getImportedSymbol($type, $alias) { - foreach ($this->importedSymbols as $functions) { - if (isset($functions[$type][$alias])) { - return $functions[$type][$alias]; - } - } + // if the symbol does not exist in the current scope (0), try in the main/global scope (last index) + return $this->importedSymbols[0][$type][$alias] ?? ($this->importedSymbols[\count($this->importedSymbols) - 1][$type][$alias] ?? null); } public function isMainScope() diff --git a/vendor/twig/twig/src/TokenParser/ExtendsTokenParser.php b/vendor/twig/twig/src/TokenParser/ExtendsTokenParser.php index a2e6b54b07..a44980fbc7 100644 --- a/vendor/twig/twig/src/TokenParser/ExtendsTokenParser.php +++ b/vendor/twig/twig/src/TokenParser/ExtendsTokenParser.php @@ -27,8 +27,10 @@ public function parse(Token $token) { $stream = $this->parser->getStream(); - if (!$this->parser->isMainScope()) { - throw new SyntaxError('Cannot extend from a block.', $token->getLine(), $stream->getSourceContext()); + if ($this->parser->peekBlockStack()) { + throw new SyntaxError('Cannot use "extend" in a block.', $token->getLine(), $stream->getSourceContext()); + } elseif (!$this->parser->isMainScope()) { + throw new SyntaxError('Cannot use "extend" in a macro.', $token->getLine(), $stream->getSourceContext()); } if (null !== $this->parser->getParent()) { diff --git a/vendor/twig/twig/src/TokenParser/FilterTokenParser.php b/vendor/twig/twig/src/TokenParser/FilterTokenParser.php index ad2ac33f1a..e57fc90aa7 100644 --- a/vendor/twig/twig/src/TokenParser/FilterTokenParser.php +++ b/vendor/twig/twig/src/TokenParser/FilterTokenParser.php @@ -30,21 +30,24 @@ final class FilterTokenParser extends AbstractTokenParser { public function parse(Token $token) { - @trigger_error('The "filter" tag is deprecated since Twig 2.9, use the "apply" tag instead.', E_USER_DEPRECATED); + $stream = $this->parser->getStream(); + $lineno = $token->getLine(); + + @trigger_error(sprintf('The "filter" tag in "%s" at line %d is deprecated since Twig 2.9, use the "apply" tag instead.', $stream->getSourceContext()->getName(), $lineno), E_USER_DEPRECATED); $name = $this->parser->getVarName(); - $ref = new BlockReferenceExpression(new ConstantExpression($name, $token->getLine()), null, $token->getLine(), $this->getTag()); + $ref = new BlockReferenceExpression(new ConstantExpression($name, $lineno), null, $lineno, $this->getTag()); $filter = $this->parser->getExpressionParser()->parseFilterExpressionRaw($ref, $this->getTag()); - $this->parser->getStream()->expect(/* Token::BLOCK_END_TYPE */ 3); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); $body = $this->parser->subparse([$this, 'decideBlockEnd'], true); - $this->parser->getStream()->expect(/* Token::BLOCK_END_TYPE */ 3); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); - $block = new BlockNode($name, $body, $token->getLine()); + $block = new BlockNode($name, $body, $lineno); $this->parser->setBlock($name, $block); - return new PrintNode($filter, $token->getLine(), $this->getTag()); + return new PrintNode($filter, $lineno, $this->getTag()); } public function decideBlockEnd(Token $token) diff --git a/vendor/twig/twig/src/TokenParser/ForTokenParser.php b/vendor/twig/twig/src/TokenParser/ForTokenParser.php index 1d0c84d7ef..34430f00b0 100644 --- a/vendor/twig/twig/src/TokenParser/ForTokenParser.php +++ b/vendor/twig/twig/src/TokenParser/ForTokenParser.php @@ -43,7 +43,7 @@ public function parse(Token $token) $ifexpr = null; if ($stream->nextIf(/* Token::NAME_TYPE */ 5, 'if')) { - @trigger_error(sprintf('Using an "if" condition on "for" tag is deprecated since Twig 2.10.0, use a "filter" filter or an "if" condition inside the "for" body instead (if your condition depends on a variable updated inside the loop).', __CLASS__), E_USER_DEPRECATED); + @trigger_error(sprintf('Using an "if" condition on "for" tag in "%s" at line %d is deprecated since Twig 2.10.0, use a "filter" filter or an "if" condition inside the "for" body instead (if your condition depends on a variable updated inside the loop).', $stream->getSourceContext()->getName(), $lineno), E_USER_DEPRECATED); $ifexpr = $this->parser->getExpressionParser()->parseExpression(); } diff --git a/vendor/twig/twig/src/TokenParser/FromTokenParser.php b/vendor/twig/twig/src/TokenParser/FromTokenParser.php index de03586f68..dd49f2fd33 100644 --- a/vendor/twig/twig/src/TokenParser/FromTokenParser.php +++ b/vendor/twig/twig/src/TokenParser/FromTokenParser.php @@ -26,7 +26,7 @@ public function parse(Token $token) { $macro = $this->parser->getExpressionParser()->parseExpression(); $stream = $this->parser->getStream(); - $stream->expect('import'); + $stream->expect(/* Token::NAME_TYPE */ 5, 'import'); $targets = []; do { @@ -46,10 +46,11 @@ public function parse(Token $token) $stream->expect(/* Token::BLOCK_END_TYPE */ 3); - $node = new ImportNode($macro, new AssignNameExpression($this->parser->getVarName(), $token->getLine()), $token->getLine(), $this->getTag()); + $var = new AssignNameExpression($this->parser->getVarName(), $token->getLine()); + $node = new ImportNode($macro, $var, $token->getLine(), $this->getTag(), $this->parser->isMainScope()); foreach ($targets as $name => $alias) { - $this->parser->addImportedSymbol('function', $alias, 'macro_'.$name, $node->getNode('var')); + $this->parser->addImportedSymbol('function', $alias, 'macro_'.$name, $var); } return $node; diff --git a/vendor/twig/twig/src/TokenParser/ImportTokenParser.php b/vendor/twig/twig/src/TokenParser/ImportTokenParser.php index 4b8c3db89a..b5674c1998 100644 --- a/vendor/twig/twig/src/TokenParser/ImportTokenParser.php +++ b/vendor/twig/twig/src/TokenParser/ImportTokenParser.php @@ -25,13 +25,13 @@ final class ImportTokenParser extends AbstractTokenParser public function parse(Token $token) { $macro = $this->parser->getExpressionParser()->parseExpression(); - $this->parser->getStream()->expect('as'); + $this->parser->getStream()->expect(/* Token::NAME_TYPE */ 5, 'as'); $var = new AssignNameExpression($this->parser->getStream()->expect(/* Token::NAME_TYPE */ 5)->getValue(), $token->getLine()); $this->parser->getStream()->expect(/* Token::BLOCK_END_TYPE */ 3); $this->parser->addImportedSymbol('template', $var->getAttribute('name')); - return new ImportNode($macro, $var, $token->getLine(), $this->getTag()); + return new ImportNode($macro, $var, $token->getLine(), $this->getTag(), $this->parser->isMainScope()); } public function getTag() diff --git a/vendor/twig/twig/src/TokenParser/SpacelessTokenParser.php b/vendor/twig/twig/src/TokenParser/SpacelessTokenParser.php index c4fd36e865..601e476fc8 100644 --- a/vendor/twig/twig/src/TokenParser/SpacelessTokenParser.php +++ b/vendor/twig/twig/src/TokenParser/SpacelessTokenParser.php @@ -30,13 +30,14 @@ final class SpacelessTokenParser extends AbstractTokenParser { public function parse(Token $token) { - @trigger_error('The spaceless tag is deprecated since Twig 2.7, use the spaceless filter instead.', E_USER_DEPRECATED); - + $stream = $this->parser->getStream(); $lineno = $token->getLine(); - $this->parser->getStream()->expect(/* Token::BLOCK_END_TYPE */ 3); + @trigger_error(sprintf('The spaceless tag in "%s" at line %d is deprecated since Twig 2.7, use the spaceless filter instead.', $stream->getSourceContext()->getName(), $lineno), E_USER_DEPRECATED); + + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); $body = $this->parser->subparse([$this, 'decideSpacelessEnd'], true); - $this->parser->getStream()->expect(/* Token::BLOCK_END_TYPE */ 3); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); return new SpacelessNode($body, $lineno, $this->getTag()); } diff --git a/vendor/twig/twig/src/TokenStream.php b/vendor/twig/twig/src/TokenStream.php index a1ecd5669c..3fb9e86e3d 100644 --- a/vendor/twig/twig/src/TokenStream.php +++ b/vendor/twig/twig/src/TokenStream.php @@ -73,9 +73,10 @@ public function expect($type, $value = null, string $message = null): Token $token = $this->tokens[$this->current]; if (!$token->test($type, $value)) { $line = $token->getLine(); - throw new SyntaxError(sprintf('%sUnexpected token "%s" of value "%s" ("%s" expected%s).', + throw new SyntaxError(sprintf('%sUnexpected token "%s"%s ("%s" expected%s).', $message ? $message.'. ' : '', - Token::typeToEnglish($token->getType()), $token->getValue(), + Token::typeToEnglish($token->getType()), + $token->getValue() ? sprintf(' of value "%s"', $token->getValue()) : '', Token::typeToEnglish($type), $value ? sprintf(' with value "%s"', $value) : ''), $line, $this->source diff --git a/vendor/twig/twig/test/Twig/Tests/EnvironmentTest.php b/vendor/twig/twig/test/Twig/Tests/EnvironmentTest.php index 27fdc99ccf..a164a0cde6 100644 --- a/vendor/twig/twig/test/Twig/Tests/EnvironmentTest.php +++ b/vendor/twig/twig/test/Twig/Tests/EnvironmentTest.php @@ -55,7 +55,7 @@ public function escapingStrategyCallback($name) public function testGlobals() { $loader = $this->getMockBuilder(LoaderInterface::class)->getMock(); - $loader->expects($this->any())->method('getSourceContext')->will($this->returnValue(new Source('', ''))); + $loader->expects($this->any())->method('getSourceContext')->willReturn(new Source('', '')); // globals can be added after calling getGlobals $twig = new Environment($loader); @@ -189,10 +189,10 @@ public function testAutoReloadCacheMiss() // skipped. $cache->expects($this->once()) ->method('generateKey') - ->will($this->returnValue('key')); + ->willReturn('key'); $cache->expects($this->once()) ->method('getTimestamp') - ->will($this->returnValue(0)); + ->willReturn(0); $loader->expects($this->never()) ->method('isFresh'); $cache->expects($this->once()) @@ -218,13 +218,13 @@ public function testAutoReloadCacheHit() // the loader returns true for isFresh(). $cache->expects($this->once()) ->method('generateKey') - ->will($this->returnValue('key')); + ->willReturn('key'); $cache->expects($this->once()) ->method('getTimestamp') - ->will($this->returnValue($now)); + ->willReturn($now); $loader->expects($this->once()) ->method('isFresh') - ->will($this->returnValue(true)); + ->willReturn(true); $cache->expects($this->atLeastOnce()) ->method('load'); @@ -244,13 +244,13 @@ public function testAutoReloadOutdatedCacheHit() $cache->expects($this->once()) ->method('generateKey') - ->will($this->returnValue('key')); + ->willReturn('key'); $cache->expects($this->once()) ->method('getTimestamp') - ->will($this->returnValue($now)); + ->willReturn($now); $loader->expects($this->once()) ->method('isFresh') - ->will($this->returnValue(false)); + ->willReturn(false); $cache->expects($this->once()) ->method('write'); $cache->expects($this->once()) @@ -315,7 +315,7 @@ public function testInitRuntimeWithAnExtensionUsingInitRuntimeNoDeprecation() { $loader = $this->getMockBuilder(LoaderInterface::class)->getMock(); $twig = new Environment($loader); - $loader->expects($this->once())->method('getSourceContext')->will($this->returnValue(new Source('', ''))); + $loader->expects($this->once())->method('getSourceContext')->willReturn(new Source('', '')); $twig->addExtension(new Twig_Tests_EnvironmentTest_ExtensionWithoutDeprecationInitRuntime()); $twig->load(''); @@ -339,7 +339,7 @@ public function testOverrideExtension() public function testAddRuntimeLoader() { $runtimeLoader = $this->getMockBuilder(RuntimeLoaderInterface::class)->getMock(); - $runtimeLoader->expects($this->any())->method('load')->will($this->returnValue(new Twig_Tests_EnvironmentTest_Runtime())); + $runtimeLoader->expects($this->any())->method('load')->willReturn(new Twig_Tests_EnvironmentTest_Runtime()); $loader = new ArrayLoader([ 'func_array' => '{{ from_runtime_array("foo") }}', @@ -379,11 +379,11 @@ protected function getMockLoader($templateName, $templateContent) $loader->expects($this->any()) ->method('getSourceContext') ->with($templateName) - ->will($this->returnValue(new Source($templateContent, $templateName))); + ->willReturn(new Source($templateContent, $templateName)); $loader->expects($this->any()) ->method('getCacheKey') ->with($templateName) - ->will($this->returnValue($templateName)); + ->willReturn($templateName); return $loader; } diff --git a/vendor/twig/twig/test/Twig/Tests/Extension/CoreTest.php b/vendor/twig/twig/test/Twig/Tests/Extension/CoreTest.php index 06e55ef3ee..5dc0685241 100644 --- a/vendor/twig/twig/test/Twig/Tests/Extension/CoreTest.php +++ b/vendor/twig/twig/test/Twig/Tests/Extension/CoreTest.php @@ -10,7 +10,6 @@ */ use Twig\Environment; -use Twig\Extension\CoreExtension; use Twig\Loader\LoaderInterface; class Twig_Tests_Extension_CoreTest extends \PHPUnit\Framework\TestCase @@ -126,34 +125,6 @@ public function testReverseFilterOnNonUTF8String() $this->assertEquals($output, 'éÄ'); } - /** - * @dataProvider provideCustomEscaperCases - */ - public function testCustomEscaper($expected, $string, $strategy) - { - $twig = new Environment($this->getMockBuilder(LoaderInterface::class)->getMock()); - $twig->getExtension(CoreExtension::class)->setEscaper('foo', 'foo_escaper_for_test'); - - $this->assertSame($expected, twig_escape_filter($twig, $string, $strategy)); - } - - public function provideCustomEscaperCases() - { - return [ - ['fooUTF-8', 'foo', 'foo'], - ['UTF-8', null, 'foo'], - ['42UTF-8', 42, 'foo'], - ]; - } - - /** - * @expectedException \Twig\Error\RuntimeError - */ - public function testUnknownCustomEscaper() - { - twig_escape_filter(new Environment($this->getMockBuilder(LoaderInterface::class)->getMock()), 'foo', 'bar'); - } - /** * @dataProvider provideTwigFirstCases */ @@ -280,11 +251,6 @@ public function provideSliceFilterCases() } } -function foo_escaper_for_test(Environment $env, $string, $charset) -{ - return $string.$charset; -} - final class CoreTestIteratorAggregate implements \IteratorAggregate { private $iterator; diff --git a/vendor/twig/twig/test/Twig/Tests/Extension/EscaperTest.php b/vendor/twig/twig/test/Twig/Tests/Extension/EscaperTest.php new file mode 100644 index 0000000000..98f5a07842 --- /dev/null +++ b/vendor/twig/twig/test/Twig/Tests/Extension/EscaperTest.php @@ -0,0 +1,403 @@ + ''', + '"' => '"', + '<' => '<', + '>' => '>', + '&' => '&', + ]; + + protected $htmlAttrSpecialChars = [ + '\'' => ''', + /* Characters beyond ASCII value 255 to unicode escape */ + 'Ā' => 'Ā', + '😀' => '😀', + /* Immune chars excluded */ + ',' => ',', + '.' => '.', + '-' => '-', + '_' => '_', + /* Basic alnums excluded */ + 'a' => 'a', + 'A' => 'A', + 'z' => 'z', + 'Z' => 'Z', + '0' => '0', + '9' => '9', + /* Basic control characters and null */ + "\r" => ' ', + "\n" => ' ', + "\t" => ' ', + "\0" => '�', // should use Unicode replacement char + /* Encode chars as named entities where possible */ + '<' => '<', + '>' => '>', + '&' => '&', + '"' => '"', + /* Encode spaces for quoteless attribute protection */ + ' ' => ' ', + ]; + + protected $jsSpecialChars = [ + /* HTML special chars - escape without exception to hex */ + '<' => '\\u003C', + '>' => '\\u003E', + '\'' => '\\u0027', + '"' => '\\u0022', + '&' => '\\u0026', + '/' => '\\/', + /* Characters beyond ASCII value 255 to unicode escape */ + 'Ā' => '\\u0100', + '😀' => '\\uD83D\\uDE00', + /* Immune chars excluded */ + ',' => ',', + '.' => '.', + '_' => '_', + /* Basic alnums excluded */ + 'a' => 'a', + 'A' => 'A', + 'z' => 'z', + 'Z' => 'Z', + '0' => '0', + '9' => '9', + /* Basic control characters and null */ + "\r" => '\r', + "\n" => '\n', + "\x08" => '\b', + "\t" => '\t', + "\x0C" => '\f', + "\0" => '\\u0000', + /* Encode spaces for quoteless attribute protection */ + ' ' => '\\u0020', + ]; + + protected $urlSpecialChars = [ + /* HTML special chars - escape without exception to percent encoding */ + '<' => '%3C', + '>' => '%3E', + '\'' => '%27', + '"' => '%22', + '&' => '%26', + /* Characters beyond ASCII value 255 to hex sequence */ + 'Ā' => '%C4%80', + /* Punctuation and unreserved check */ + ',' => '%2C', + '.' => '.', + '_' => '_', + '-' => '-', + ':' => '%3A', + ';' => '%3B', + '!' => '%21', + /* Basic alnums excluded */ + 'a' => 'a', + 'A' => 'A', + 'z' => 'z', + 'Z' => 'Z', + '0' => '0', + '9' => '9', + /* Basic control characters and null */ + "\r" => '%0D', + "\n" => '%0A', + "\t" => '%09', + "\0" => '%00', + /* PHP quirks from the past */ + ' ' => '%20', + '~' => '~', + '+' => '%2B', + ]; + + protected $cssSpecialChars = [ + /* HTML special chars - escape without exception to hex */ + '<' => '\\3C ', + '>' => '\\3E ', + '\'' => '\\27 ', + '"' => '\\22 ', + '&' => '\\26 ', + /* Characters beyond ASCII value 255 to unicode escape */ + 'Ā' => '\\100 ', + /* Immune chars excluded */ + ',' => '\\2C ', + '.' => '\\2E ', + '_' => '\\5F ', + /* Basic alnums excluded */ + 'a' => 'a', + 'A' => 'A', + 'z' => 'z', + 'Z' => 'Z', + '0' => '0', + '9' => '9', + /* Basic control characters and null */ + "\r" => '\\D ', + "\n" => '\\A ', + "\t" => '\\9 ', + "\0" => '\\0 ', + /* Encode spaces for quoteless attribute protection */ + ' ' => '\\20 ', + ]; + + public function testHtmlEscapingConvertsSpecialChars() + { + $twig = new \Twig\Environment($this->getMockBuilder(\Twig\Loader\LoaderInterface::class)->getMock()); + foreach ($this->htmlSpecialChars as $key => $value) { + $this->assertEquals($value, twig_escape_filter($twig, $key, 'html'), 'Failed to escape: '.$key); + } + } + + public function testHtmlAttributeEscapingConvertsSpecialChars() + { + $twig = new \Twig\Environment($this->getMockBuilder(\Twig\Loader\LoaderInterface::class)->getMock()); + foreach ($this->htmlAttrSpecialChars as $key => $value) { + $this->assertEquals($value, twig_escape_filter($twig, $key, 'html_attr'), 'Failed to escape: '.$key); + } + } + + public function testJavascriptEscapingConvertsSpecialChars() + { + $twig = new \Twig\Environment($this->getMockBuilder(\Twig\Loader\LoaderInterface::class)->getMock()); + foreach ($this->jsSpecialChars as $key => $value) { + $this->assertEquals($value, twig_escape_filter($twig, $key, 'js'), 'Failed to escape: '.$key); + } + } + + public function testJavascriptEscapingReturnsStringIfZeroLength() + { + $twig = new \Twig\Environment($this->getMockBuilder(\Twig\Loader\LoaderInterface::class)->getMock()); + $this->assertEquals('', twig_escape_filter($twig, '', 'js')); + } + + public function testJavascriptEscapingReturnsStringIfContainsOnlyDigits() + { + $twig = new \Twig\Environment($this->getMockBuilder(\Twig\Loader\LoaderInterface::class)->getMock()); + $this->assertEquals('123', twig_escape_filter($twig, '123', 'js')); + } + + public function testCssEscapingConvertsSpecialChars() + { + $twig = new \Twig\Environment($this->getMockBuilder(\Twig\Loader\LoaderInterface::class)->getMock()); + foreach ($this->cssSpecialChars as $key => $value) { + $this->assertEquals($value, twig_escape_filter($twig, $key, 'css'), 'Failed to escape: '.$key); + } + } + + public function testCssEscapingReturnsStringIfZeroLength() + { + $twig = new \Twig\Environment($this->getMockBuilder(\Twig\Loader\LoaderInterface::class)->getMock()); + $this->assertEquals('', twig_escape_filter($twig, '', 'css')); + } + + public function testCssEscapingReturnsStringIfContainsOnlyDigits() + { + $twig = new \Twig\Environment($this->getMockBuilder(\Twig\Loader\LoaderInterface::class)->getMock()); + $this->assertEquals('123', twig_escape_filter($twig, '123', 'css')); + } + + public function testUrlEscapingConvertsSpecialChars() + { + $twig = new \Twig\Environment($this->getMockBuilder(\Twig\Loader\LoaderInterface::class)->getMock()); + foreach ($this->urlSpecialChars as $key => $value) { + $this->assertEquals($value, twig_escape_filter($twig, $key, 'url'), 'Failed to escape: '.$key); + } + } + + /** + * Range tests to confirm escaped range of characters is within OWASP recommendation. + */ + + /** + * Only testing the first few 2 ranges on this prot. function as that's all these + * other range tests require. + */ + public function testUnicodeCodepointConversionToUtf8() + { + $expected = ' ~ޙ'; + $codepoints = [0x20, 0x7e, 0x799]; + $result = ''; + foreach ($codepoints as $value) { + $result .= $this->codepointToUtf8($value); + } + $this->assertEquals($expected, $result); + } + + /** + * Convert a Unicode Codepoint to a literal UTF-8 character. + * + * @param int $codepoint Unicode codepoint in hex notation + * + * @return string UTF-8 literal string + */ + protected function codepointToUtf8($codepoint) + { + if ($codepoint < 0x80) { + return \chr($codepoint); + } + if ($codepoint < 0x800) { + return \chr($codepoint >> 6 & 0x3f | 0xc0) + .\chr($codepoint & 0x3f | 0x80); + } + if ($codepoint < 0x10000) { + return \chr($codepoint >> 12 & 0x0f | 0xe0) + .\chr($codepoint >> 6 & 0x3f | 0x80) + .\chr($codepoint & 0x3f | 0x80); + } + if ($codepoint < 0x110000) { + return \chr($codepoint >> 18 & 0x07 | 0xf0) + .\chr($codepoint >> 12 & 0x3f | 0x80) + .\chr($codepoint >> 6 & 0x3f | 0x80) + .\chr($codepoint & 0x3f | 0x80); + } + throw new \Exception('Codepoint requested outside of Unicode range.'); + } + + public function testJavascriptEscapingEscapesOwaspRecommendedRanges() + { + $twig = new \Twig\Environment($this->getMockBuilder(\Twig\Loader\LoaderInterface::class)->getMock()); + $immune = [',', '.', '_']; // Exceptions to escaping ranges + for ($chr = 0; $chr < 0xFF; ++$chr) { + if ($chr >= 0x30 && $chr <= 0x39 + || $chr >= 0x41 && $chr <= 0x5A + || $chr >= 0x61 && $chr <= 0x7A) { + $literal = $this->codepointToUtf8($chr); + $this->assertEquals($literal, twig_escape_filter($twig, $literal, 'js')); + } else { + $literal = $this->codepointToUtf8($chr); + if (\in_array($literal, $immune)) { + $this->assertEquals($literal, twig_escape_filter($twig, $literal, 'js')); + } else { + $this->assertNotEquals( + $literal, + twig_escape_filter($twig, $literal, 'js'), + "$literal should be escaped!"); + } + } + } + } + + public function testHtmlAttributeEscapingEscapesOwaspRecommendedRanges() + { + $twig = new \Twig\Environment($this->getMockBuilder(\Twig\Loader\LoaderInterface::class)->getMock()); + $immune = [',', '.', '-', '_']; // Exceptions to escaping ranges + for ($chr = 0; $chr < 0xFF; ++$chr) { + if ($chr >= 0x30 && $chr <= 0x39 + || $chr >= 0x41 && $chr <= 0x5A + || $chr >= 0x61 && $chr <= 0x7A) { + $literal = $this->codepointToUtf8($chr); + $this->assertEquals($literal, twig_escape_filter($twig, $literal, 'html_attr')); + } else { + $literal = $this->codepointToUtf8($chr); + if (\in_array($literal, $immune)) { + $this->assertEquals($literal, twig_escape_filter($twig, $literal, 'html_attr')); + } else { + $this->assertNotEquals( + $literal, + twig_escape_filter($twig, $literal, 'html_attr'), + "$literal should be escaped!"); + } + } + } + } + + public function testCssEscapingEscapesOwaspRecommendedRanges() + { + $twig = new \Twig\Environment($this->getMockBuilder(\Twig\Loader\LoaderInterface::class)->getMock()); + // CSS has no exceptions to escaping ranges + for ($chr = 0; $chr < 0xFF; ++$chr) { + if ($chr >= 0x30 && $chr <= 0x39 + || $chr >= 0x41 && $chr <= 0x5A + || $chr >= 0x61 && $chr <= 0x7A) { + $literal = $this->codepointToUtf8($chr); + $this->assertEquals($literal, twig_escape_filter($twig, $literal, 'css')); + } else { + $literal = $this->codepointToUtf8($chr); + $this->assertNotEquals( + $literal, + twig_escape_filter($twig, $literal, 'css'), + "$literal should be escaped!"); + } + } + } + + /** + * @dataProvider provideCustomEscaperCases + */ + public function testCustomEscaper($expected, $string, $strategy) + { + $twig = new Environment($this->getMockBuilder(LoaderInterface::class)->getMock()); + $twig->getExtension(EscaperExtension::class)->setEscaper('foo', 'foo_escaper_for_test'); + + $this->assertSame($expected, twig_escape_filter($twig, $string, $strategy)); + } + + public function provideCustomEscaperCases() + { + return [ + ['fooUTF-8', 'foo', 'foo'], + ['UTF-8', null, 'foo'], + ['42UTF-8', 42, 'foo'], + ]; + } + + /** + * @expectedException \Twig\Error\RuntimeError + */ + public function testUnknownCustomEscaper() + { + twig_escape_filter(new Environment($this->getMockBuilder(LoaderInterface::class)->getMock()), 'foo', 'bar'); + } + + /** + * @dataProvider provideObjectsForEscaping + */ + public function testObjectEscaping(string $escapedHtml, string $escapedJs, array $safeClasses) + { + $obj = new Twig_Tests_Extension_TestClass(); + $twig = new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock()); + $twig->getExtension('\Twig\Extension\EscaperExtension')->setSafeClasses($safeClasses); + $this->assertSame($escapedHtml, twig_escape_filter($twig, $obj, 'html', null, true)); + $this->assertSame($escapedJs, twig_escape_filter($twig, $obj, 'js', null, true)); + } + + public function provideObjectsForEscaping() + { + return [ + ['<br />', '
', ['\Twig_Tests_Extension_TestClass' => ['js']]], + ['
', '\u003Cbr\u0020\/\u003E', ['\Twig_Tests_Extension_TestClass' => ['html']]], + ['<br />', '
', ['\Twig_Tests_Extension_SafeHtmlInterface' => ['js']]], + ['
', '
', ['\Twig_Tests_Extension_SafeHtmlInterface' => ['all']]], + ]; + } +} + +function foo_escaper_for_test(Environment $twig, $string, $charset) +{ + return $string.$charset; +} + +interface Twig_Tests_Extension_SafeHtmlInterface +{ +} +class Twig_Tests_Extension_TestClass implements Twig_Tests_Extension_SafeHtmlInterface +{ + public function __toString() + { + return '
'; + } +} diff --git a/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/string_operator_as_var_assignment.test b/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/string_operator_as_var_assignment.test new file mode 100644 index 0000000000..478d4eb5e5 --- /dev/null +++ b/vendor/twig/twig/test/Twig/Tests/Fixtures/expressions/string_operator_as_var_assignment.test @@ -0,0 +1,18 @@ +--TEST-- +Twig supports the string operators as variable names in assignments +--TEMPLATE-- +{% for matches in [1, 2] %} + {{- matches }} +{% endfor %} + +{% set matches = [1, 2] %} + +OK +--DATA-- +return [] +--EXPECT-- +1 +2 + + +OK diff --git a/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/filter.test b/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/filter.test index af1b8cc116..3d3fdcc68b 100644 --- a/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/filter.test +++ b/vendor/twig/twig/test/Twig/Tests/Fixtures/filters/filter.test @@ -26,10 +26,20 @@ {% for k, v in ita|filter(v => v > offset) -%} {{ k }} = {{ v }} {% endfor %} + +{% for k, v in xml|filter(x => true) %} +{{ k }}/{{ v }} +{% endfor %} + +{# we can iterate more than once #} +{% for k, v in xml|filter(x => true) %} +{{ k }}/{{ v }} +{% endfor %} --DATA-- return [ 'it' => new \ArrayIterator(['a' => 1, 'b' => 2, 'c' => 5, 'd' => 8]), 'ita' => new IteratorAggregateStub(['a' => 1, 'b' => 2, 'c' => 5, 'd' => 8]), + 'xml' => new \SimpleXMLElement('foobarbaz'), ] --EXPECT-- 1 = 5 @@ -50,3 +60,11 @@ d = 8 c = 5 d = 8 + +elem/foo +elem/bar +elem/baz + +elem/foo +elem/bar +elem/baz diff --git a/vendor/twig/twig/test/Twig/Tests/Fixtures/macros/unknown_macro.test b/vendor/twig/twig/test/Twig/Tests/Fixtures/macros/unknown_macro.test new file mode 100644 index 0000000000..10ea6c62a7 --- /dev/null +++ b/vendor/twig/twig/test/Twig/Tests/Fixtures/macros/unknown_macro.test @@ -0,0 +1,10 @@ +--TEST-- +macro +--TEMPLATE-- +{% import _self as macros %} + +{{ macros.unknown() }} +--DATA-- +return [] +--EXCEPTION-- +Twig\Error\RuntimeError: Macro "unknown" is not defined in template "index.twig" in "index.twig" at line 4. diff --git a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/block/capturing_block.test b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/block/capturing_block.test index 1456fe3f64..4182b8c361 100644 --- a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/block/capturing_block.test +++ b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/block/capturing_block.test @@ -1,7 +1,7 @@ --TEST-- capturing "block" tag --DEPRECATION-- -The spaceless tag is deprecated since Twig 2.7, use the spaceless filter instead. +The spaceless tag in "index.twig" at line 4 is deprecated since Twig 2.7, use the spaceless filter instead. --TEMPLATE-- {% set foo %}{% block foo %}FOO{% endblock %}{% endset %} {{ foo }} diff --git a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/basic.legacy.test b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/basic.legacy.test index 1efedb94ad..28f12c1abc 100644 --- a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/basic.legacy.test +++ b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/basic.legacy.test @@ -1,7 +1,7 @@ --TEST-- "filter" tag applies a filter on its children --DEPRECATION-- -The "filter" tag is deprecated since Twig 2.9, use the "apply" tag instead. +The "filter" tag in "index.twig" at line 2 is deprecated since Twig 2.9, use the "apply" tag instead. --TEMPLATE-- {% filter upper %} Some text with a {{ var }} diff --git a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/json_encode.legacy.test b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/json_encode.legacy.test index 7ec6be3bd7..1da38498f8 100644 --- a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/json_encode.legacy.test +++ b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/json_encode.legacy.test @@ -1,7 +1,7 @@ --TEST-- "filter" tag applies a filter on its children --DEPRECATION-- -The "filter" tag is deprecated since Twig 2.9, use the "apply" tag instead. +The "filter" tag in "index.twig" at line 2 is deprecated since Twig 2.9, use the "apply" tag instead. --TEMPLATE-- {% filter json_encode|raw %}test{% endfilter %} --DATA-- diff --git a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/multiple.legacy.test b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/multiple.legacy.test index 0e8d251e14..f7aa54fb38 100644 --- a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/multiple.legacy.test +++ b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/multiple.legacy.test @@ -1,7 +1,7 @@ --TEST-- "filter" tags accept multiple chained filters --DEPRECATION-- -The "filter" tag is deprecated since Twig 2.9, use the "apply" tag instead. +The "filter" tag in "index.twig" at line 2 is deprecated since Twig 2.9, use the "apply" tag instead. --TEMPLATE-- {% filter lower|title %} {{ var }} diff --git a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/nested.legacy.test b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/nested.legacy.test index 1d894fea6b..a8c69066ba 100644 --- a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/nested.legacy.test +++ b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/nested.legacy.test @@ -1,8 +1,8 @@ --TEST-- "filter" tags can be nested at will --DEPRECATION-- -The "filter" tag is deprecated since Twig 2.9, use the "apply" tag instead. -The "filter" tag is deprecated since Twig 2.9, use the "apply" tag instead. +The "filter" tag in "index.twig" at line 2 is deprecated since Twig 2.9, use the "apply" tag instead. +The "filter" tag in "index.twig" at line 4 is deprecated since Twig 2.9, use the "apply" tag instead. --TEMPLATE-- {% filter lower|title %} {{ var }} diff --git a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/scope.legacy.test b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/scope.legacy.test index 6a7720e847..6ee1ac835b 100644 --- a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/scope.legacy.test +++ b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/scope.legacy.test @@ -1,7 +1,7 @@ --TEST-- "scope" tag creates a new scope --DEPRECATION-- -The "filter" tag is deprecated since Twig 2.9, use the "apply" tag instead. +The "filter" tag in "index.twig" at line 2 is deprecated since Twig 2.9, use the "apply" tag instead. --TEMPLATE-- {% filter spaceless %} {% set item = 'foo' %} diff --git a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/with_for_tag.legacy.test b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/with_for_tag.legacy.test index 76985646be..5f3ab7bc84 100644 --- a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/with_for_tag.legacy.test +++ b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/with_for_tag.legacy.test @@ -1,7 +1,7 @@ --TEST-- "filter" tag applies the filter on "for" tags --DEPRECATION-- -The "filter" tag is deprecated since Twig 2.9, use the "apply" tag instead. +The "filter" tag in "index.twig" at line 2 is deprecated since Twig 2.9, use the "apply" tag instead. --TEMPLATE-- {% filter upper %} {% for item in items %} diff --git a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/with_if_tag.legacy.test b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/with_if_tag.legacy.test index 74a4bb5773..034caaa538 100644 --- a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/with_if_tag.legacy.test +++ b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/filter/with_if_tag.legacy.test @@ -1,7 +1,7 @@ --TEST-- "filter" tag applies the filter on "if" tags --DEPRECATION-- -The "filter" tag is deprecated since Twig 2.9, use the "apply" tag instead. +The "filter" tag in "index.twig" at line 2 is deprecated since Twig 2.9, use the "apply" tag instead. --TEMPLATE-- {% filter upper %} {% if items %} diff --git a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/condition.legacy.test b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/condition.legacy.test index 690207e429..a35047c062 100644 --- a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/condition.legacy.test +++ b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/for/condition.legacy.test @@ -1,7 +1,7 @@ --TEST-- "for" tag takes a condition --DEPRECATION-- -Using an "if" condition on "for" tag is deprecated since Twig 2.10.0, use a "filter" filter or an "if" condition inside the "for" body instead (if your condition depends on a variable updated inside the loop). +Using an "if" condition on "for" tag in "index.twig" at line 2 is deprecated since Twig 2.10.0, use a "filter" filter or an "if" condition inside the "for" body instead (if your condition depends on a variable updated inside the loop). --TEMPLATE-- {% for i in 1..5 if i is odd -%} {{ loop.index }}.{{ i }}{{ foo.bar }} diff --git a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/extends_in_block.test b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/extends_in_block.test new file mode 100644 index 0000000000..a372ea1c81 --- /dev/null +++ b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/extends_in_block.test @@ -0,0 +1,10 @@ +--TEST-- +"extends" tag in a block +--TEMPLATE-- +{% block foo %} + {% extends "foo.twig" %} +{% endblock %} +--DATA-- +return [] +--EXCEPTION-- +Twig\Error\SyntaxError: Cannot use "extend" in a block in "index.twig" at line 3. diff --git a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/extends_in_macro.test b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/extends_in_macro.test new file mode 100644 index 0000000000..dc87b2a8c2 --- /dev/null +++ b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/inheritance/extends_in_macro.test @@ -0,0 +1,10 @@ +--TEST-- +"extends" tag in a macro +--TEMPLATE-- +{% macro foo() %} + {% extends "foo.twig" %} +{% endmacro %} +--DATA-- +return [] +--EXCEPTION-- +Twig\Error\SyntaxError: Cannot use "extend" in a macro in "index.twig" at line 3. diff --git a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/auto_import.test b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/auto_import.test new file mode 100644 index 0000000000..14e0afe00a --- /dev/null +++ b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/auto_import.test @@ -0,0 +1,16 @@ +--TEST-- +"macro" tag +--TEMPLATE-- +{{ _self.hello('Fabien') }} + +{% macro hello(name) -%} + Hello {{ _self.up(name) }} +{% endmacro %} + +{% macro up(name) -%} + {{ name|upper }} +{% endmacro %} +--DATA-- +return [] +--EXPECT-- +Hello FABIEN diff --git a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/auto_import_blocks.test b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/auto_import_blocks.test new file mode 100644 index 0000000000..59b739377b --- /dev/null +++ b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/auto_import_blocks.test @@ -0,0 +1,18 @@ +--TEST-- +"macro" tag +--TEMPLATE-- +{% block content %} + {{ _self.hello('Fabien') }} +{% endblock %} + +{% macro hello(name) -%} + Hello {{ _self.up(name) }} +{% endmacro %} + +{% macro up(name) -%} + {{ name|upper }} +{% endmacro %} +--DATA-- +return [] +--EXPECT-- +Hello FABIEN diff --git a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/from_embed_with_global_macro.test b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/from_embed_with_global_macro.test new file mode 100644 index 0000000000..f06c31c938 --- /dev/null +++ b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/from_embed_with_global_macro.test @@ -0,0 +1,21 @@ +--TEST-- +"macro" tag +--TEMPLATE-- +{% from _self import input %} + +{% embed 'embed' %} + {% block foo %} + {{ input("username") }} + {% endblock %} +{% endembed %} + +{% macro input(name) -%} + +{% endmacro %} +--TEMPLATE(embed)-- + {% block foo %} + {% endblock %} +--DATA-- +return [] +--EXCEPTION-- +Twig\Error\SyntaxError: Unknown "input" function in "index.twig" at line 6. diff --git a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/from_in_block_is_local.test b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/from_in_block_is_local.test new file mode 100644 index 0000000000..0c89ce62a8 --- /dev/null +++ b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/from_in_block_is_local.test @@ -0,0 +1,18 @@ +--TEST-- +"macro" tag +--TEMPLATE-- +{% block foo %} + {%- from _self import input as linput %} +{% endblock %} + +{% block bar %} + {{- linput('username') }} +{% endblock %} + +{% macro input(name) -%} + +{% endmacro %} +--DATA-- +return [] +--EXCEPTION-- +Twig\Error\SyntaxError: Unknown "linput" function in "index.twig" at line 7. diff --git a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/from_local_override.test b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/from_local_override.test new file mode 100644 index 0000000000..27bfbaee1f --- /dev/null +++ b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/from_local_override.test @@ -0,0 +1,28 @@ +--TEST-- +"macro" tag +--TEMPLATE-- +{%- from _self import input %} + +{% block foo %} + {%- from "macros" import input %} + {{- input('username') }} +{% endblock %} + +{% block bar %} + {{- input('username') }} +{% endblock %} + +{% macro input(name) -%} + +{% endmacro %} +--TEMPLATE(macros)-- +{% macro input(name) %} + +{% endmacro %} +--DATA-- +return [] +--EXPECT-- + + + + diff --git a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/from_macro_in_a_macro.test b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/from_macro_in_a_macro.test new file mode 100644 index 0000000000..168c9b3a90 --- /dev/null +++ b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/from_macro_in_a_macro.test @@ -0,0 +1,18 @@ +--TEST-- +"from" tag with syntax error +--TEMPLATE-- +{% from _self import another, foo %} + +{{ foo() }} + +{% macro foo() %} + {{ another() }} +{% endmacro %} + +{% macro another() %} + OK +{% endmacro %} +--DATA-- +return [] +--EXPECT-- +OK diff --git a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/from_macros_in_parent.test b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/from_macros_in_parent.test new file mode 100644 index 0000000000..57cff7ab17 --- /dev/null +++ b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/from_macros_in_parent.test @@ -0,0 +1,16 @@ +--TEST-- +"macro" tag +--TEMPLATE-- +{% from "macros" import hello %} + +{{ hello() }} +--TEMPLATE(macros)-- +{% extends "parent" %} +--TEMPLATE(parent)-- +{% macro hello() %} + Test +{% endmacro %} +--DATA-- +return [] +--EXPECT-- +Test diff --git a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/from_nested_blocks.test b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/from_nested_blocks.test new file mode 100644 index 0000000000..8ede5db509 --- /dev/null +++ b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/from_nested_blocks.test @@ -0,0 +1,18 @@ +--TEST-- +"macro" tag +--TEMPLATE-- +{% block foo %} + {%- from _self import input as linput %} + + {% block bar %} + {{- linput('username') }} + {% endblock %} +{% endblock %} + +{% macro input(name) -%} + +{% endmacro %} +--DATA-- +return [] +--EXCEPTION-- +Twig\Error\SyntaxError: Unknown "linput" function in "index.twig" at line 6. diff --git a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/from_nested_blocks_with_global_macro.test b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/from_nested_blocks_with_global_macro.test new file mode 100644 index 0000000000..384b02d8fe --- /dev/null +++ b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/from_nested_blocks_with_global_macro.test @@ -0,0 +1,18 @@ +--TEST-- +"macro" tag +--TEMPLATE-- +{%- from _self import input %} + +{% block foo %} + {% block bar %} + {{- input('username') }} + {% endblock %} +{% endblock %} + +{% macro input(name) -%} + +{% endmacro %} +--DATA-- +return [] +--EXPECT-- + diff --git a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/from_recursive.test b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/from_recursive.test new file mode 100644 index 0000000000..09a29839d0 --- /dev/null +++ b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/from_recursive.test @@ -0,0 +1,27 @@ +--TEST-- +"import" tag +--TEMPLATE-- +{% from _self import recursive_macro %} + +{{ recursive_macro(10) }} + +{% macro recursive_macro(n) %} + {% if n > 0 %} + {{- recursive_macro(n - 1) -}} + {% endif %} + {{- n }} +{% endmacro %} +--DATA-- +return [] +--EXPECT-- +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 diff --git a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/from_self_parent.test b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/from_self_parent.test new file mode 100644 index 0000000000..2a19871192 --- /dev/null +++ b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/from_self_parent.test @@ -0,0 +1,20 @@ +--TEST-- +"macro" tag +--TEMPLATE-- +{% extends "parent" %} + +{% block test %} + {{ _self.hello() }} +{% endblock test %} +--TEMPLATE(parent)-- +{% block test %} +Hello +{% endblock test %} + +{% macro hello() %} + Test +{% endmacro %} +--DATA-- +return [] +--EXPECT-- +Test diff --git a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/from_syntax_error.test b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/from_syntax_error.test new file mode 100644 index 0000000000..6223cfe947 --- /dev/null +++ b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/from_syntax_error.test @@ -0,0 +1,8 @@ +--TEST-- +"from" tag with syntax error +--TEMPLATE-- +{% from 'forms.twig' %} +--DATA-- +return [] +--EXCEPTION-- +Twig\Error\SyntaxError: Unexpected token "end of statement block" ("name" expected with value "import") in "index.twig" at line 2. diff --git a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/import_and_blocks.test b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/import_and_blocks.test new file mode 100644 index 0000000000..721f5506a5 --- /dev/null +++ b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/import_and_blocks.test @@ -0,0 +1,36 @@ +--TEST-- +"macro" tag +--TEMPLATE-- +{% import _self as macros %} +{% from _self import input %} + +{% block foo %} + {{- macros.input('username') }} + {{- input('username') }} + + {%- import _self as lmacros %} + {%- from _self import input as linput %} + + {{- lmacros.input('username') }} + {{- linput('username') }} +{% endblock %} + +{% block bar %} + {{- macros.input('username') }} + {{- input('username') }} +{% endblock %} + +{% macro input(name) -%} + +{% endmacro %} +--DATA-- +return [] +--EXPECT-- + + + + + + + + diff --git a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/import_embed_with_global_macro.test b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/import_embed_with_global_macro.test new file mode 100644 index 0000000000..3609881f0d --- /dev/null +++ b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/import_embed_with_global_macro.test @@ -0,0 +1,21 @@ +--TEST-- +"macro" tag +--TEMPLATE-- +{% import _self as macros %} + +{% embed 'embed' %} + {% block foo %} + {{ macros.input("username") }} + {% endblock %} +{% endembed %} + +{% macro input(name) -%} + +{% endmacro %} +--TEMPLATE(embed)-- + {% block foo %} + {% endblock %} +--DATA-- +return [] +--EXCEPTION-- +Twig\Error\RuntimeError: Variable "macros" does not exist in "index.twig" at line 6. diff --git a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/import_in_block_is_local.test b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/import_in_block_is_local.test new file mode 100644 index 0000000000..9443e12221 --- /dev/null +++ b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/import_in_block_is_local.test @@ -0,0 +1,18 @@ +--TEST-- +"macro" tag +--TEMPLATE-- +{% block foo %} + {%- import _self as lmacros %} +{% endblock %} + +{% block bar %} + {{- lmacros.input('username') }} +{% endblock %} + +{% macro input(name) -%} + +{% endmacro %} +--DATA-- +return [] +--EXCEPTION-- +Twig\Error\RuntimeError: Variable "lmacros" does not exist in "index.twig" at line 7. diff --git a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/import_local_override.test b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/import_local_override.test new file mode 100644 index 0000000000..7cf0552f83 --- /dev/null +++ b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/import_local_override.test @@ -0,0 +1,28 @@ +--TEST-- +"macro" tag +--TEMPLATE-- +{%- import _self as macros %} + +{% block foo %} + {%- import "macros" as macros %} + {{- macros.input('username') }} +{% endblock %} + +{% block bar %} + {{- macros.input('username') }} +{% endblock %} + +{% macro input(name) -%} + +{% endmacro %} +--TEMPLATE(macros)-- +{% macro input(name) %} + +{% endmacro %} +--DATA-- +return [] +--EXPECT-- + + + + diff --git a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/import_macro_in_a_macro.test b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/import_macro_in_a_macro.test new file mode 100644 index 0000000000..1851f09749 --- /dev/null +++ b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/import_macro_in_a_macro.test @@ -0,0 +1,18 @@ +--TEST-- +"import" tag with syntax error +--TEMPLATE-- +{% import _self as foo %} + +{{ foo.foo() }} + +{% macro foo() %} + {{ foo.another() }} +{% endmacro %} + +{% macro another() %} + OK +{% endmacro %} +--DATA-- +return [] +--EXPECT-- +OK diff --git a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/import_macros_in_parent.test b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/import_macros_in_parent.test new file mode 100644 index 0000000000..d8d5d1639a --- /dev/null +++ b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/import_macros_in_parent.test @@ -0,0 +1,16 @@ +--TEST-- +"macro" tag +--TEMPLATE-- +{% import "macros" as m %} + +{{ m.hello() }} +--TEMPLATE(macros)-- +{% extends "parent" %} +--TEMPLATE(parent)-- +{% macro hello() %} + Test +{% endmacro %} +--DATA-- +return [] +--EXPECT-- +Test diff --git a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/import_nested_blocks.test b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/import_nested_blocks.test new file mode 100644 index 0000000000..821f64bf75 --- /dev/null +++ b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/import_nested_blocks.test @@ -0,0 +1,18 @@ +--TEST-- +"macro" tag +--TEMPLATE-- +{% block foo %} + {%- import _self as lmacros %} + + {% block bar %} + {{- lmacros.input('username') }} + {% endblock %} +{% endblock %} + +{% macro input(name) -%} + +{% endmacro %} +--DATA-- +return [] +--EXCEPTION-- +Twig\Error\RuntimeError: Variable "lmacros" does not exist in "index.twig" at line 6. diff --git a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/import_nested_blocks_with_global_macro.test b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/import_nested_blocks_with_global_macro.test new file mode 100644 index 0000000000..697d665f84 --- /dev/null +++ b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/import_nested_blocks_with_global_macro.test @@ -0,0 +1,18 @@ +--TEST-- +"macro" tag +--TEMPLATE-- +{%- import _self as macros %} + +{% block foo %} + {% block bar %} + {{- macros.input('username') }} + {% endblock %} +{% endblock %} + +{% macro input(name) -%} + +{% endmacro %} +--DATA-- +return [] +--EXPECT-- + diff --git a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/import_same_parent_and_child.test b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/import_same_parent_and_child.test new file mode 100644 index 0000000000..8d9b3caa49 --- /dev/null +++ b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/import_same_parent_and_child.test @@ -0,0 +1,30 @@ +--TEST-- +"import" tag +--TEMPLATE-- +{% extends "parent" %} + +{% macro anotherThing() -%} + Do it too +{% endmacro %} + +{% import _self as macros %} +{% block content %} + {{ parent() }} + {{ macros.anotherThing() }} +{% endblock %} +--TEMPLATE(parent)-- +{% macro thing() %} + Do it +{% endmacro %} + +{% import _self as macros %} +{% block content %} + {{ macros.thing() }} +{% endblock %} +--DATA-- +return [] +--EXPECT-- +Do it + + + Do it too diff --git a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/import_self_parent.test b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/import_self_parent.test new file mode 100644 index 0000000000..24a8cdb50b --- /dev/null +++ b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/import_self_parent.test @@ -0,0 +1,23 @@ +--TEST-- +"macro" tag +--TEMPLATE-- +{% extends "parent" %} +{% import _self as me %} + +{% block test %} + {{ me.hello() }} +{% endblock test %} +--TEMPLATE(parent)-- +{% import _self as me %} + +{% block test %} +Hello +{% endblock test %} + +{% macro hello() %} + Test +{% endmacro %} +--DATA-- +return [] +--EXPECT-- +Test diff --git a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/import_syntax_error.test b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/import_syntax_error.test new file mode 100644 index 0000000000..b9817f0eed --- /dev/null +++ b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/import_syntax_error.test @@ -0,0 +1,10 @@ +--TEST-- +"import" tag with reserved name +--TEMPLATE-- +{% import 'forms.twig' %} + +{{ macros.parent() }} +--DATA-- +return [] +--EXCEPTION-- +Twig\Error\SyntaxError: Unexpected token "end of statement block" ("name" expected with value "as") in "index.twig" at line 2. diff --git a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/spaceless/root_level_in_child.legacy.test b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/spaceless/root_level_in_child.legacy.test index 8b74ef35bf..9ed406e6fb 100644 --- a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/spaceless/root_level_in_child.legacy.test +++ b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/spaceless/root_level_in_child.legacy.test @@ -1,7 +1,7 @@ --TEST-- "spaceless" tag in the root level of a child template --DEPRECATION-- -The spaceless tag is deprecated since Twig 2.7, use the spaceless filter instead. +The spaceless tag in "index.twig" at line 3 is deprecated since Twig 2.7, use the spaceless filter instead. Using the spaceless tag at the root level of a child template in "index.twig" at line 3 is deprecated since Twig 2.5.0 and will become a syntax error in 3.0. Nesting a block definition under a non-capturing node in "index.twig" at line 4 is deprecated since Twig 2.5.0 and will become a syntax error in 3.0. --TEMPLATE-- diff --git a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/spaceless/simple.test b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/spaceless/simple.test index 1159fffb82..8dbe660e5d 100644 --- a/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/spaceless/simple.test +++ b/vendor/twig/twig/test/Twig/Tests/Fixtures/tags/spaceless/simple.test @@ -1,7 +1,7 @@ --TEST-- "spaceless" tag removes whites between HTML tags --DEPRECATION-- -The spaceless tag is deprecated since Twig 2.7, use the spaceless filter instead. +The spaceless tag in "index.twig" at line 2 is deprecated since Twig 2.7, use the spaceless filter instead. --TEMPLATE-- {% spaceless %} diff --git a/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/defined_for_macros.test b/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/defined_for_macros.test new file mode 100644 index 0000000000..1aa45fc826 --- /dev/null +++ b/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/defined_for_macros.test @@ -0,0 +1,41 @@ +--TEST-- +"defined" support for macros +--TEMPLATE-- +{% import _self as macros %} +{% from _self import hello, bar %} + +{% if macros.hello is defined -%} + OK +{% endif %} + +{% if macros.foo is not defined -%} + OK +{% endif %} + +{% if hello is defined -%} + OK +{% endif %} + +{% if bar is not defined -%} + OK +{% endif %} + +{% if foo is not defined -%} + OK +{% endif %} + +{% macro hello(name) %} + Hello {{ name }} +{% endmacro %} +--DATA-- +return [] +--EXPECT-- +OK + +OK + +OK + +OK + +OK diff --git a/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/in.test b/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/in.test index b34fd65df6..f84465ee32 100644 --- a/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/in.test +++ b/vendor/twig/twig/test/Twig/Tests/Fixtures/tests/in.test @@ -56,6 +56,7 @@ Twig supports the in operator {{ '5.5' in 125.5 ? 'KO' : 'OK' }} {{ safe in ['foo', 'bar'] ? 'OK' : 'KO' }} +{{ 'fo' in safe ? 'OK' : 'KO' }} --DATA-- return ['bar' => 'bar', 'foo' => ['bar' => 'bar'], 'dir_object' => new \SplFileInfo(__DIR__), 'object' => new \stdClass(), 'resource' => opendir(__DIR__), 'safe' => new \Twig\Markup('foo', 'UTF-8')] --EXPECT-- @@ -114,3 +115,4 @@ OK OK OK +OK diff --git a/vendor/twig/twig/test/Twig/Tests/Loader/ChainTest.php b/vendor/twig/twig/test/Twig/Tests/Loader/ChainTest.php index 9830d3ef40..a71cc96db3 100644 --- a/vendor/twig/twig/test/Twig/Tests/Loader/ChainTest.php +++ b/vendor/twig/twig/test/Twig/Tests/Loader/ChainTest.php @@ -79,11 +79,11 @@ public function testAddLoader() public function testExists() { $loader1 = $this->getMockBuilder(LoaderInterface::class)->getMock(); - $loader1->expects($this->once())->method('exists')->will($this->returnValue(false)); + $loader1->expects($this->once())->method('exists')->willReturn(false); $loader1->expects($this->never())->method('getSourceContext'); $loader2 = $this->getMockBuilder(LoaderInterface::class)->getMock(); - $loader2->expects($this->once())->method('exists')->will($this->returnValue(true)); + $loader2->expects($this->once())->method('exists')->willReturn(true); $loader2->expects($this->never())->method('getSourceContext'); $loader = new ChainLoader(); diff --git a/vendor/twig/twig/test/Twig/Tests/Node/BlockTest.php b/vendor/twig/twig/test/Twig/Tests/Node/BlockTest.php index 8b4a1eca4a..c91a4d0f75 100644 --- a/vendor/twig/twig/test/Twig/Tests/Node/BlockTest.php +++ b/vendor/twig/twig/test/Twig/Tests/Node/BlockTest.php @@ -34,6 +34,7 @@ public function getTests() // line 1 public function block_foo(\$context, array \$blocks = []) { + \$macros = \$this->macros; echo "foo"; } EOF diff --git a/vendor/twig/twig/test/Twig/Tests/Node/ImportTest.php b/vendor/twig/twig/test/Twig/Tests/Node/ImportTest.php index 2118bff59b..f811bfb4cd 100644 --- a/vendor/twig/twig/test/Twig/Tests/Node/ImportTest.php +++ b/vendor/twig/twig/test/Twig/Tests/Node/ImportTest.php @@ -36,7 +36,7 @@ public function getTests() $tests[] = [$node, <<loadTemplate("foo.twig", null, 1)->unwrap(); +\$macros["macro"] = \$this->macros["macro"] = \$this->loadTemplate("foo.twig", null, 1)->unwrap(); EOF ]; diff --git a/vendor/twig/twig/test/Twig/Tests/Node/MacroTest.php b/vendor/twig/twig/test/Twig/Tests/Node/MacroTest.php index 1dda543cdd..afa68adf43 100644 --- a/vendor/twig/twig/test/Twig/Tests/Node/MacroTest.php +++ b/vendor/twig/twig/test/Twig/Tests/Node/MacroTest.php @@ -43,6 +43,7 @@ public function getTests() // line 1 public function macro_foo(\$__foo__ = null, \$__bar__ = "Foo", ...\$__varargs__) { + \$macros = \$this->macros; \$context = \$this->env->mergeGlobals([ "foo" => \$__foo__, "bar" => \$__bar__, diff --git a/vendor/twig/twig/test/Twig/Tests/Node/ModuleTest.php b/vendor/twig/twig/test/Twig/Tests/Node/ModuleTest.php index 1e2d2b640c..812b1ef0b4 100644 --- a/vendor/twig/twig/test/Twig/Tests/Node/ModuleTest.php +++ b/vendor/twig/twig/test/Twig/Tests/Node/ModuleTest.php @@ -74,6 +74,7 @@ public function getTests() class __TwigTemplate_%x extends \Twig\Template { private \$source; + private \$macros = []; public function __construct(Environment \$env) { @@ -89,6 +90,7 @@ public function __construct(Environment \$env) protected function doDisplay(array \$context, array \$blocks = []) { + \$macros = \$this->macros; // line 1 echo "foo"; } @@ -100,7 +102,7 @@ public function getTemplateName() public function getDebugInfo() { - return array ( 35 => 1,); + return array ( 37 => 1,); } public function getSourceContext() @@ -136,6 +138,7 @@ public function getSourceContext() class __TwigTemplate_%x extends \Twig\Template { private \$source; + private \$macros = []; public function __construct(Environment \$env) { @@ -155,8 +158,9 @@ protected function doGetParent(array \$context) protected function doDisplay(array \$context, array \$blocks = []) { + \$macros = \$this->macros; // line 2 - \$context["macro"] = \$this->loadTemplate("foo.twig", "foo.twig", 2)->unwrap(); + \$macros["macro"] = \$this->macros["macro"] = \$this->loadTemplate("foo.twig", "foo.twig", 2)->unwrap(); // line 1 \$this->parent = \$this->loadTemplate("layout.twig", "foo.twig", 1); \$this->parent->display(\$context, array_merge(\$this->blocks, \$blocks)); @@ -174,7 +178,7 @@ public function isTraitable() public function getDebugInfo() { - return array ( 41 => 1, 39 => 2, 33 => 1,); + return array ( 43 => 1, 41 => 2, 34 => 1,); } public function getSourceContext() @@ -215,6 +219,7 @@ public function getSourceContext() class __TwigTemplate_%x extends \Twig\Template { private \$source; + private \$macros = []; public function __construct(Environment \$env) { @@ -234,6 +239,7 @@ protected function doGetParent(array \$context) protected function doDisplay(array \$context, array \$blocks = []) { + \$macros = \$this->macros; // line 4 \$context["foo"] = "foo"; // line 2 @@ -252,7 +258,7 @@ public function isTraitable() public function getDebugInfo() { - return array ( 41 => 2, 39 => 4, 33 => 2,); + return array ( 43 => 2, 41 => 4, 34 => 2,); } public function getSourceContext()