Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.74% covered (success)
94.74%
54 / 57
57.14% covered (warning)
57.14%
4 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
JCMapDataContent
94.74% covered (success)
94.74%
54 / 57
57.14% covered (warning)
57.14%
4 / 7
29.12
0.00% covered (danger)
0.00%
0 / 1
 validateContent
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
2.01
 getSafeData
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 isValidData
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 recursiveWalk
92.31% covered (success)
92.31%
12 / 13
0.00% covered (danger)
0.00%
0 / 1
11.06
 isValidStringOrLocalized
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
5
 localizeData
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
4
 createDefaultView
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2namespace JsonConfig;
3
4use FormatJson;
5use Kartographer\SimpleStyleParser;
6use Language;
7use MediaWiki\MediaWikiServices;
8use MediaWiki\User\User;
9use ParserOptions;
10
11/**
12 * @package JsonConfig
13 */
14class JCMapDataContent extends JCDataContent {
15
16    public function validateContent() {
17        parent::validateContent();
18
19        if ( !$this->thorough() ) {
20            // We are not doing any modifications to the original, so no need to validate it
21            return;
22        }
23
24        $this->testOptional( 'zoom', 3, JCValidators::isInt() );
25        $this->testOptional( 'latitude', 0, JCValidators::isNumber() );
26        $this->testOptional( 'longitude', 0, JCValidators::isNumber() );
27
28        $this->test( 'data', self::isValidData() );
29
30        $this->test( [], JCValidators::noExtraValues() );
31    }
32
33    /**
34     * @inheritDoc
35     */
36    public function getSafeData( $data ) {
37        $parser = MediaWikiServices::getInstance()->getParser();
38
39        // In case the parser hasn't been used yet
40        if ( !$parser->getOptions() ) {
41            $options = new ParserOptions( new User() );
42            $parser->startExternalParse( null, $options, OT_HTML );
43        }
44
45        $data = parent::getSafeData( $data );
46
47        $ssp = SimpleStyleParser::newFromParser( $parser );
48        $ssp->normalizeAndSanitize( $data->data );
49
50        return $data;
51    }
52
53    private static function isValidData() {
54        return static function ( JCValue $v, array $path ) {
55            $value = $v->getValue();
56
57            if ( ( !is_object( $value ) && !is_array( $value ) ) ||
58                !JCMapDataContent::recursiveWalk( $value, false )
59            ) {
60                $v->error( 'jsonconfig-err-bad-geojson', $path );
61                return false;
62            }
63
64            // TODO: decide if this is needed. We would have to alter the above code to localize props
65            // // Use SimpleStyleParser to verify the data's validity
66            // $ssp = new \Kartographer\SimpleStyleParser( MediaWikiServices::getInstance()->getParser() );
67            // $status = $ssp->parseObject( $value );
68            // if ( !$status->isOK() ) {
69            // $v->status( $status );
70            // }
71            // return $status->isOK();
72            return true;
73        };
74    }
75
76    /**
77     * Recursively walk the geojson to replace localized "title" and "description" values
78     * with the single string corresponding to the $lang language, or if $lang is not set,
79     * validates those values and returns true/false if valid
80     * @param \stdClass|array &$json
81     * @param bool|Language $lang
82     * @return bool
83     */
84    public static function recursiveWalk( &$json, $lang = false ) {
85        if ( is_array( $json ) ) {
86            foreach ( $json as &$element ) {
87                if ( !self::recursiveWalk( $element, $lang ) ) {
88                    return false;
89                }
90            }
91        } elseif ( is_object( $json ) ) {
92            foreach ( array_keys( get_object_vars( $json ) ) as $prop ) {
93                if ( $prop === 'properties' && is_object( $json->properties ) ) {
94                    if ( !self::isValidStringOrLocalized( $json->properties, 'title', $lang ) ||
95                        !self::isValidStringOrLocalized( $json->properties, 'description', $lang )
96                    ) {
97                        return false;
98                    }
99                } elseif ( !self::recursiveWalk( $json->$prop, $lang ) ) {
100                    return false;
101                }
102            }
103        }
104        return true;
105    }
106
107    private static function isValidStringOrLocalized( $obj, $property, $lang, $maxlength = 400 ) {
108        if ( property_exists( $obj, $property ) ) {
109            $value = $obj->$property;
110            if ( !$lang ) {
111                return is_object( $value ) ? JCUtils::isLocalizedArray( (array)$value, $maxlength )
112                    : JCUtils::isValidLineString( $value, $maxlength );
113            } elseif ( is_object( $value ) ) {
114                $obj->$property = JCUtils::pickLocalizedString( $value, $lang );
115            }
116        }
117        return true;
118    }
119
120    protected function localizeData( $result, Language $lang ) {
121        parent::localizeData( $result, $lang );
122
123        $data = $this->getData();
124
125        if ( isset( $data->zoom ) ) {
126            $result->zoom = $data->zoom;
127        }
128        if ( isset( $data->latitude ) ) {
129            $result->latitude = $data->latitude;
130        }
131        if ( isset( $data->longitude ) ) {
132            $result->longitude = $data->longitude;
133        }
134
135        $geojson = FormatJson::decode( FormatJson::encode( $data->data, false, FormatJson::ALL_OK ) );
136        self::recursiveWalk( $geojson, $lang );
137
138        $result->data = $geojson;
139    }
140
141    protected function createDefaultView() {
142        return new JCMapDataContentView();
143    }
144}