Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
92.31% covered (success)
92.31%
72 / 78
60.00% covered (warning)
60.00%
3 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
MessagesProcessor
92.31% covered (success)
92.31%
72 / 78
60.00% covered (warning)
60.00%
3 / 5
27.33
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getMessages
81.82% covered (warning)
81.82%
9 / 11
0.00% covered (danger)
0.00%
0 / 1
4.10
 mapJsonPointerToMessageKey
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 getControlMessages
33.33% covered (danger)
33.33%
2 / 6
0.00% covered (danger)
0.00%
0 / 1
3.19
 computeEditorMessageKeys
100.00% covered (success)
100.00%
55 / 55
100.00% covered (success)
100.00%
1 / 1
19
1<?php
2
3namespace MediaWiki\Extension\CommunityConfiguration\EditorCapabilities;
4
5use Error;
6use Iterator;
7use MediaWiki\Extension\CommunityConfiguration\Schema\JsonSchema;
8use MediaWiki\Extension\CommunityConfiguration\Schema\JsonSchemaIterator;
9use MessageLocalizer;
10use Psr\Log\LoggerAwareTrait;
11use Psr\Log\NullLogger;
12use RuntimeException;
13
14class MessagesProcessor {
15    use LoggerAwareTrait;
16
17    private MessageLocalizer $messageLocalizer;
18
19    public function __construct( MessageLocalizer $messageLocalizer ) {
20        $this->messageLocalizer = $messageLocalizer;
21        $this->setLogger( new NullLogger() );
22    }
23
24    /**
25     * @param string $providerId
26     * @param Iterator $schema
27     * @param string $messagePrefix
28     * @return array
29     */
30    public function getMessages( string $providerId, Iterator $schema, string $messagePrefix ): array {
31        if ( !$schema instanceof JsonSchemaIterator ) {
32            $this->logger->debug( __CLASS__ . ' skipped schema Iterator, because it is not a JsonSchemaIterator.' );
33            return [];
34        }
35
36        $messages = [];
37        $keys = $this->computeEditorMessageKeys( $providerId, $schema, $messagePrefix );
38        // Present for any schema a used in the editor summary dialog
39        array_unshift( $keys, strtolower( $messagePrefix . '-' . $providerId . '-title' ) );
40        foreach ( $keys as $key ) {
41            $msg = $this->messageLocalizer->msg( $key );
42            if ( $msg->exists() ) {
43                $messages[$key] = $msg->plain();
44            }
45        }
46
47        return $messages;
48    }
49
50    /**
51     * @param string $jsonPointer
52     * @param string $prefix
53     * @return string
54     */
55    private function mapJsonPointerToMessageKey( string $jsonPointer, string $prefix = '' ): string {
56        // Remove initial #
57        $jsonPointer = str_replace( '#', '', $jsonPointer );
58        // Remove /properties and /items occurrences since we omit them in the message key
59        $messageKey = str_replace( [ '/properties', '/items' ], '', $jsonPointer );
60        // Replace slashes by hyphens to follow message key convention
61        $messageKey = str_replace( [ '/' ], '-', $messageKey );
62        return strtolower( $prefix . $messageKey );
63    }
64
65    /**
66     * Returns the value of a given field in the given class.
67     *
68     * @param string $className
69     * @param string $fieldName
70     * @return array The value of the class field
71     */
72    private function getControlMessages( string $className, string $fieldName ): array {
73        try {
74            $value = constant( "$className::$fieldName" );
75        } catch ( Error $e ) {
76            throw new RuntimeException(
77                "Failed resolving reference $fieldName in $className."
78            );
79        }
80        return $value;
81    }
82
83    /**
84     * @param string $providerId
85     * @param JsonSchemaIterator $schemas
86     * @param string $messagePrefix
87     * @return array
88     */
89    private function computeEditorMessageKeys(
90        string $providerId, JsonSchemaIterator $schemas, string $messagePrefix
91    ): array {
92        $messages = [];
93        foreach ( $schemas as $sub_schema ) {
94            [
95                'parentType' => $parentType,
96                'pointer' => $pointer,
97                'schema' => $schema
98            ] = $sub_schema;
99
100            if ( $pointer === '#' ) {
101                // We don't generate labels for the root object
102                continue;
103            }
104
105            $schemaBaseKey = $this->mapJsonPointerToMessageKey( $pointer, $messagePrefix . '-' . $providerId );
106            if ( isset( $schema->{JsonSchema::CONTROL} ) ) {
107                $messages = array_merge( $messages, $this->getControlMessages(
108                    $schema->{JsonSchema::CONTROL}, 'MESSAGES'
109                ) );
110            }
111            if ( isset( $schema->{JsonSchema::ENUM} ) ) {
112                $messages[] = $schemaBaseKey . '-label';
113                $messages[] = $schemaBaseKey . '-help-text';
114                $messages[] = $schemaBaseKey . '-description';
115                if ( $parentType === JsonSchema::TYPE_ARRAY ) {
116                    if ( $schema->{JsonSchema::TYPE} === JsonSchema::TYPE_STRING ) {
117                        $messages[] = 'mw-widgets-titlesmultiselect-placeholder';
118                        $messages[] = 'communityconfiguration-editor-chip-control-aria-chip-description';
119                    }
120                    // We don't generate labels for arrays of simple types
121                    continue;
122                }
123                foreach ( $schema->{JsonSchema::ENUM} as $enumValue ) {
124                    $messages[] = $schemaBaseKey . '-option-' . $enumValue . '-label';
125                }
126                // Do not process the schema type for enums as it would generate
127                // undesired keys, eg: placeholders
128                continue;
129            }
130            if (
131                $schema->{JsonSchema::TYPE} === JsonSchema::TYPE_STRING ||
132                $schema->{JsonSchema::TYPE} === JsonSchema::TYPE_NUMBER ||
133                $schema->{JsonSchema::TYPE} === JsonSchema::TYPE_INTEGER
134            ) {
135                if ( $parentType === JsonSchema::TYPE_ARRAY ) {
136                    if ( $schema->{JsonSchema::TYPE} === JsonSchema::TYPE_STRING ) {
137                        $messages[] = 'mw-widgets-titlesmultiselect-placeholder';
138                        $messages[] = 'communityconfiguration-editor-chip-control-aria-chip-description';
139                    }
140                    // We don't generate labels for arrays of simple types
141                    continue;
142                }
143                $messages[] = $schemaBaseKey . '-label';
144                $messages[] = $schemaBaseKey . '-help-text';
145                $messages[] = $schemaBaseKey . '-placeholder';
146                $messages[] = $schemaBaseKey . '-description';
147            }
148            if ( $schema->{JsonSchema::TYPE} === JsonSchema::TYPE_BOOLEAN ) {
149                $messages[] = $schemaBaseKey . '-label';
150                $messages[] = $schemaBaseKey . '-help-text';
151                $messages[] = $schemaBaseKey . '-control-label';
152                $messages[] = $schemaBaseKey . '-description';
153            }
154            if (
155                $schema->{JsonSchema::TYPE} === JsonSchema::TYPE_OBJECT ||
156                $schema->{JsonSchema::TYPE} === JsonSchema::TYPE_ARRAY
157            ) {
158                $messages[] = $schemaBaseKey . '-label';
159                $messages[] = $schemaBaseKey . '-help-text';
160                $messages[] = $schemaBaseKey . '-description';
161                if (
162                    $schema->{JsonSchema::TYPE} === JsonSchema::TYPE_ARRAY &&
163                    // Arrays of strings are rendered on a single field with chips
164                    $schema->{JsonSchema::ITEMS}->{JsonSchema::TYPE} !== JsonSchema::TYPE_STRING &&
165                    // Assume array types with a custom control will handle its own item labels
166                    !isset( $schema->{JsonSchema::CONTROL} )
167                ) {
168                    $messages[] = $schemaBaseKey . '-item-label';
169                    $messages[] = $schemaBaseKey . '-add-element-button-label';
170                    $messages[] = 'communityconfiguration-editor-array-fallback-add-element-button-label';
171                }
172            }
173        }
174        return $messages;
175    }
176}