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 Kartographer\SimpleStyleParser;
5use MediaWiki\Json\FormatJson;
6use MediaWiki\Language\Language;
7use MediaWiki\MediaWikiServices;
8use MediaWiki\Parser\ParserOptions;
9use MediaWiki\User\User;
10
11class JCMapDataContent extends JCDataContent {
12
13    public function validateContent() {
14        parent::validateContent();
15
16        if ( !$this->thorough() ) {
17            // We are not doing any modifications to the original, so no need to validate it
18            return;
19        }
20
21        $this->testOptional( 'zoom', 3, JCValidators::isInt() );
22        $this->testOptional( 'latitude', 0, JCValidators::isNumber() );
23        $this->testOptional( 'longitude', 0, JCValidators::isNumber() );
24
25        $this->test( 'data', self::isValidData() );
26
27        $this->test( [], JCValidators::noExtraValues() );
28    }
29
30    /**
31     * @inheritDoc
32     */
33    public function getSafeData( $data ) {
34        $parser = MediaWikiServices::getInstance()->getParser();
35
36        // In case the parser hasn't been used yet
37        if ( !$parser->getOptions() ) {
38            $options = new ParserOptions( new User() );
39            $parser->startExternalParse( null, $options, OT_HTML );
40        }
41
42        $data = parent::getSafeData( $data );
43
44        $ssp = SimpleStyleParser::newFromParser( $parser );
45        $ssp->normalizeAndSanitize( $data->data );
46
47        return $data;
48    }
49
50    /**
51     * @return callable
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 Language|false $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    /**
108     * @param \stdClass $obj
109     * @param string $property
110     * @param Language|false $lang
111     * @param int $maxlength
112     * @return bool
113     */
114    private static function isValidStringOrLocalized( $obj, $property, $lang, $maxlength = 400 ) {
115        if ( property_exists( $obj, $property ) ) {
116            $value = $obj->$property;
117            if ( !$lang ) {
118                return is_object( $value ) ? JCUtils::isLocalizedArray( (array)$value, $maxlength )
119                    : JCUtils::isValidLineString( $value, $maxlength );
120            } elseif ( is_object( $value ) ) {
121                $obj->$property = JCUtils::pickLocalizedString( $value, $lang );
122            }
123        }
124        return true;
125    }
126
127    protected function localizeData( $result, Language $lang ) {
128        parent::localizeData( $result, $lang );
129
130        $data = $this->getData();
131
132        if ( isset( $data->zoom ) ) {
133            $result->zoom = $data->zoom;
134        }
135        if ( isset( $data->latitude ) ) {
136            $result->latitude = $data->latitude;
137        }
138        if ( isset( $data->longitude ) ) {
139            $result->longitude = $data->longitude;
140        }
141
142        $geojson = FormatJson::decode( FormatJson::encode( $data->data, false, FormatJson::ALL_OK ) );
143        self::recursiveWalk( $geojson, $lang );
144
145        $result->data = $geojson;
146    }
147
148    protected function createDefaultView() {
149        return new JCMapDataContentView();
150    }
151}