Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
84 / 84
100.00% covered (success)
100.00%
8 / 8
CRAP
100.00% covered (success)
100.00%
1 / 1
EntitySchemaEncoder
100.00% covered (success)
100.00%
84 / 84
100.00% covered (success)
100.00%
8 / 8
20
100.00% covered (success)
100.00%
1 / 1
 getPersistentRepresentation
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
1 / 1
1
 validateParameters
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
1
 validateLangCodes
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
2
 validateParameterTypes
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 validateIdentifyingInfoMaxLength
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 validateLDAMaxLength
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 validateSchemaMaxLength
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 isSequentialArrayOfStrings
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2
3declare( strict_types = 1 );
4
5namespace EntitySchema\DataAccess;
6
7use EntitySchema\Domain\Model\EntitySchemaId;
8use InvalidArgumentException;
9use MediaWiki\MediaWikiServices;
10
11/**
12 * @license GPL-2.0-or-later
13 */
14class EntitySchemaEncoder {
15
16    /**
17     * @param EntitySchemaId $id
18     * @param array    $labels       labels  with langCode as key, e.g. [ 'en' => 'Cat' ]
19     * @param array    $descriptions descriptions with langCode as key, e.g. [ 'en' => 'A cat' ]
20     * @param array    $aliases      aliases with langCode as key, e.g. [ 'en' => [ 'tiger' ], ]
21     * @param string   $schemaText
22     *
23     * @throws InvalidArgumentException if bad parameters are passed
24     *
25     * @return string
26     */
27    public static function getPersistentRepresentation(
28        EntitySchemaId $id,
29        array $labels,
30        array $descriptions,
31        array $aliases,
32        string $schemaText
33    ): string {
34        self::validateParameters(
35            $labels,
36            $descriptions,
37            $aliases,
38            $schemaText
39        );
40        EntitySchemaCleaner::cleanupParameters(
41            $labels,
42            $descriptions,
43            $aliases,
44            $schemaText
45        );
46        return json_encode(
47            [
48                'id' => $id->getId(),
49                'serializationVersion' => '3.0',
50                'labels' => $labels,
51                'descriptions' => $descriptions,
52                'aliases' => $aliases,
53                'schemaText' => $schemaText,
54                'type' => 'ShExC',
55            ]
56        );
57    }
58
59    /**
60     * @param string[] $labels
61     * @param string[] $descriptions
62     * @param array<string,string[]> $aliasGroups
63     * @param string $schemaText
64     */
65    private static function validateParameters(
66        array $labels,
67        array $descriptions,
68        array $aliasGroups,
69        string $schemaText
70    ): void {
71        self::validateLangCodes( $labels, $descriptions, $aliasGroups );
72        self::validateParameterTypes(
73            $labels,
74            $descriptions,
75            $aliasGroups
76        );
77        self::validateIdentifyingInfoMaxLength(
78            $labels,
79            $descriptions,
80            $aliasGroups
81        );
82        self::validateSchemaMaxLength( $schemaText );
83    }
84
85    private static function validateLangCodes(
86        array $labels,
87        array $descriptions,
88        array $aliasGroups
89    ): void {
90        $providedLangCodes = array_unique(
91            array_merge(
92                array_keys( $labels ),
93                array_keys( $descriptions ),
94                array_keys( $aliasGroups )
95            )
96        );
97        $languageNameUtils = MediaWikiServices::getInstance()->getLanguageNameUtils();
98        $invalidLangCodes = array_filter(
99            $providedLangCodes,
100            static function ( $langCode ) use ( $languageNameUtils ) {
101                return !$languageNameUtils->isSupportedLanguage( $langCode );
102            }
103        );
104        if ( count( $invalidLangCodes ) > 0 ) {
105            throw new InvalidArgumentException( 'language codes must be valid!' );
106        }
107    }
108
109    private static function validateParameterTypes(
110        array $labels,
111        array $descriptions,
112        array $aliasGroups
113    ): void {
114        if ( count( array_filter( $labels, 'is_string' ) ) !== count( $labels )
115            || count( array_filter( $descriptions, 'is_string' ) ) !== count( $descriptions )
116            || count( array_filter( $aliasGroups, [ self::class, 'isSequentialArrayOfStrings' ] ) )
117            !== count( $aliasGroups )
118        ) {
119            throw new InvalidArgumentException(
120                'language, label and description must be strings '
121                . 'and aliases must be an array of strings'
122            );
123        }
124    }
125
126    private static function validateIdentifyingInfoMaxLength(
127        array $labels,
128        array $descriptions,
129        array $aliasGroups
130    ): void {
131        foreach ( $labels as $label ) {
132            self::validateLDAMaxLength( $label );
133        }
134
135        foreach ( $descriptions as $description ) {
136            self::validateLDAMaxLength( $description );
137        }
138
139        foreach ( $aliasGroups as $aliasGroup ) {
140            self::validateLDAMaxLength( implode( '', $aliasGroup ) );
141        }
142    }
143
144    private static function validateLDAMaxLength( string $localizedString ): void {
145        $maxLengthChars = MediaWikiServices::getInstance()->getMainConfig()
146            ->get( 'EntitySchemaNameBadgeMaxSizeChars' );
147        if ( mb_strlen( $localizedString ) > $maxLengthChars ) {
148            throw new InvalidArgumentException(
149                'Identifying information is longer than the allowed max of ' . $maxLengthChars . ' characters!'
150            );
151        }
152    }
153
154    private static function validateSchemaMaxLength( string $schemaText ): void {
155        $maxLengthBytes = MediaWikiServices::getInstance()->getMainConfig()
156            ->get( 'EntitySchemaSchemaTextMaxSizeBytes' );
157        if ( strlen( $schemaText ) > $maxLengthBytes ) {
158            throw new InvalidArgumentException(
159                'Schema text is longer than the allowed max of ' . $maxLengthBytes . ' bytes!'
160            );
161        }
162    }
163
164    private static function isSequentialArrayOfStrings( array $array ): bool {
165        $values = array_values( $array );
166        if ( $array !== $values ) {
167            // Array is associative or sparse. Fast solution from
168            // https://stackoverflow.com/questions/173400/how-to-check-if-php-array-is-associative-or-sequential
169            return false;
170        }
171        foreach ( $values as $value ) {
172            if ( !is_string( $value ) ) {
173                return false;
174            }
175        }
176        return true;
177    }
178
179}