Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
11.95% covered (danger)
11.95%
27 / 226
5.56% covered (danger)
5.56%
1 / 18
CRAP
0.00% covered (danger)
0.00%
0 / 1
BaseMapService
11.95% covered (danger)
11.95%
27 / 226
5.56% covered (danger)
5.56%
1 / 18
5870.43
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 render
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
12
 getMapData
100.00% covered (success)
100.00%
27 / 27
100.00% covered (success)
100.00%
1 / 1
14
 parse
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
56
 addMapElement
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
182
 addElementMarker
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
30
 addElementLine
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
30
 addElementPolygon
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
30
 addElementRectangle
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
30
 addElementCircle
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
42
 __set
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setProperty
0.00% covered (danger)
0.00%
0 / 37
0.00% covered (danger)
0.00%
0 / 1
272
 __get
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getProperty
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
20
 addDependencies
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 reset
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
 getErrorMessages
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 pushErrorMessage
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2namespace MultiMaps;
3
4use FormatJson;
5use Html;
6use Parser;
7
8/**
9 * Base class for collection of services
10 *
11 * @file BaseMapService.php
12 * @ingroup MultiMaps
13 * @author Pavel Astakhov <pastakhov@yandex.ru>
14 * @license GPL-2.0-or-later
15 *
16 * @property float $zoom Map scale
17 * @property float $minzoom Minimum scale map
18 * @property float $maxzoom Maximum scale map
19 * @property string $center Center of the map
20 * @property string $bounds The visible bounds of the map
21 * @property string $width
22 * @property string $height
23 * @property-read string $classname Class name for tag "<div>" of map
24 */
25abstract class BaseMapService {
26
27    /**
28     * class name for tag "<div>" of map
29     * @var string
30     */
31    protected $classname = '';
32
33    /**
34     * Array of the defined modules that be loaded during the output
35     * @var array
36     */
37    protected $resourceModules;
38
39    /**
40     * Text for adding to the "<head>" during the output
41     * @var string
42     */
43    protected $headerItem;
44
45    /**
46     * Map property "width" used for tag "<div>"
47     * @var string
48     */
49    protected $width;
50
51    /**
52     * Map property "height" used for tag "<div>"
53     * @var string
54     */
55    protected $height;
56
57    /**
58     * An array that is used to accumulate the error messages
59     * @var array
60     */
61    protected $errormessages;
62
63    /**
64     * Array of elements map marker
65     * @var Marker[]
66     */
67    protected $markers;
68
69    /**
70     * Array of elements map line
71     * @var Line[]
72     */
73    protected $lines;
74
75    /**
76     * Array of elements map polygon
77     * @var Polygon[]
78     */
79    protected $polygons;
80
81    /**
82     * Array of elements map rectangle
83     * @var Rectangle[]
84     */
85    protected $rectangles;
86
87    /**
88     * Array of elements map circle
89     * @var Circle[]
90     */
91    protected $circles;
92
93    /**
94     * The boundaries of the map elements
95     * @var Bounds
96     */
97    protected $elementsBounds;
98
99    /**
100     * Array of map properties
101     * @var array
102     */
103    protected $properties;
104
105    /**
106     * Array of map elements availables for adding
107     * @var array
108     */
109    protected $availableMapElements = [
110        'marker',
111        'markers',
112        'line',
113        'lines',
114        'polygon',
115        'polygons',
116        'rectangle',
117        'rectangles',
118        'circle',
119        'circles',
120    ];
121
122    /**
123     * Array of map properties available for definition
124     * @var array
125     */
126    protected $availableMapProperties = [
127        'width',
128        'height',
129        'zoom',
130        'minzoom',
131        'maxzoom',
132        'center',
133        'bounds',
134        'title',
135        'text',
136        'icon',
137        'color',
138        'weight',
139        'opacity',
140        'fillcolor',
141        'fillopacity',
142        'fill',
143    ];
144
145    /**
146     * Array of map properties definition of which should not cause an error
147     * @var array
148     */
149    protected $ignoreProperties = [
150        'service',
151    ];
152
153    public function __construct() {
154        $this->resourceModules = [ 'ext.MultiMaps' ];
155        $this->headerItem = '';
156
157        $this->reset();
158    }
159
160    /**
161     * Returns html data for rendering map
162     * @return string
163     */
164    public function render() {
165        static $mapid = 0;
166
167        $output = Html::rawElement(
168            'div',
169            [
170                'id' => 'multimaps_map' . $mapid++,
171                'style' => 'width:' . htmlspecialchars( $this->width ) . '; height:' . htmlspecialchars( $this->height ) . '; background-color: #cccccc; overflow: hidden;',
172                'class' => 'multimaps-map' . ( $this->classname != '' ? " multimaps-map-$this->classname" : '' ),
173            ],
174            Html::element( 'p', [], wfMessage( 'multimaps-loading-map' )->escaped() ) .
175            Html::rawElement(
176                'div',
177                [ 'class' => 'multimaps-mapdata', 'style' => 'display: none;' ],
178                FormatJson::encode( $this->getMapData() )
179            )
180        );
181
182        $errors = $this->getErrorMessages();
183        if ( count( $errors ) > 0 ) {
184            $output .= "\n" .
185                Html::rawElement(
186                    'div',
187                    [ 'class' => 'multimaps-errors' ],
188                    wfMessage( 'multimaps-had-following-errors' )->escaped() .
189                    '<br />' .
190                    implode( '<br />', $this->getErrorMessages() )
191                );
192        }
193
194        return $output;
195        // return array( $output, 'noparse' => true, 'isHTML' => true );
196    }
197
198    /**
199     * Returns array of map data
200     * @param array $param Optional, if sets - parse param before returns data of map
201     * @return array
202     */
203    public function getMapData( array $param = [] ) {
204        global $egMultiMaps_DefaultZoom;
205
206        if ( count( $param ) != 0 ) {
207            $this->parse( $param );
208        }
209
210        $calculatedProperties = [];
211
212        if ( $this->bounds === null ) {
213            if ( $this->center === null ) {
214                $bounds = $this->elementsBounds;
215                if ( $bounds->isValid() ) {
216                    if ( $bounds->ne == $bounds->sw ) {
217                        if ( $this->zoom === null ) {
218                            $calculatedProperties['zoom'] = $egMultiMaps_DefaultZoom;
219                        }
220                        $calculatedProperties['center'] = $bounds->getCenter()->getData();
221                    } elseif ( $bounds->isValid() ) {
222                        if ( $this->zoom === null ) {
223                            $calculatedProperties['bounds'] = $bounds->getData();
224                        } else {
225                            $calculatedProperties['center'] = $bounds->getCenter()->getData();
226                        }
227                    }
228                }
229            } else {
230                // TODO
231            }
232        }
233
234        $return = [];
235
236        foreach ( $this->markers as $marker ) {
237            $return['markers'][] = $marker->getData();
238        }
239        foreach ( $this->lines as $line ) {
240            $return['lines'][] = $line->getData();
241        }
242        foreach ( $this->polygons as $polygon ) {
243            $return['polygons'][] = $polygon->getData();
244        }
245        foreach ( $this->rectangles as $rectangle ) {
246            $return['rectangles'][] = $rectangle->getData();
247        }
248        foreach ( $this->circles as $circle ) {
249            $return['circles'][] = $circle->getData();
250        }
251
252        return array_merge( $return, $calculatedProperties, $this->properties );
253    }
254
255    /**
256     * Parse params and fill map data
257     * @param array $param
258     * @param bool $reset Reset service before parse data
259     */
260    public function parse( array $param, $reset = true ) {
261        if ( $reset ) {
262            $this->reset();
263        }
264
265        $matches = [];
266        foreach ( $param as $value ) {
267            if ( preg_match( '/^\s*(\w+)\s*=\s*(.+)\s*$/s', $value, $matches ) ) {
268                $name = strtolower( $matches[1] );
269                if ( array_search( $name, $this->availableMapElements ) !== false ) {
270                    $this->addMapElement( $name, $matches[2] );
271                } elseif ( array_search( $name, $this->availableMapProperties ) !== false ) {
272                    $this->setProperty( $name, $matches[2] );
273                    // TODO exception
274                } else {
275                    if ( array_search( $name, $this->ignoreProperties ) === false ) {
276                        $this->errormessages[] = wfMessage( 'multimaps-unknown-parameter', $matches[1] )->escaped();
277                    }
278                }
279                continue;
280            } else {
281                // Default map element = 'marker'
282                $this->addMapElement( 'marker', $value );
283            }
284        }
285    }
286
287    /**
288     * Add new map element to map
289     * @param string $name
290     * @param string $value
291     * @return bool|null
292     */
293    public function addMapElement( $name, $value ) {
294        if ( trim( $value ) === '' ) {
295            return null;
296        }
297        $name = strtolower( $name );
298
299        switch ( $name ) {
300            case 'marker':
301            case 'markers':
302                return $this->addElementMarker( $value );
303            case 'line':
304            case 'lines':
305                return $this->addElementLine( $value );
306            case 'polygon':
307            case 'polygons':
308                return $this->addElementPolygon( $value );
309            case 'rectangle':
310            case 'rectangles':
311                return $this->addElementRectangle( $value );
312            case 'circle':
313            case 'circles':
314                return $this->addElementCircle( $value );
315            default:
316                break;
317        }
318        return null;
319    }
320
321    /**
322     * Add marker to map
323     * @param string $value
324     * @return bool
325     */
326    public function addElementMarker( $value ) {
327        global $egMultiMaps_SeparatorItems;
328
329        $return = true;
330        $stringsmarker = explode( $egMultiMaps_SeparatorItems, $value );
331        foreach ( $stringsmarker as $markervalue ) {
332            if ( trim( $markervalue ) == '' ) {
333                continue;
334            }
335            $marker = new Marker();
336            if ( !$marker->parse( $markervalue, $this->classname ) ) {
337                $return = false;
338                $this->errormessages = array_merge( $this->errormessages, $marker->getErrorMessages() );
339            }
340            if ( !$marker->isValid() ) {
341                continue;
342            }
343            $this->markers[] = $marker;
344            $this->elementsBounds->extend( $marker->pos );
345        }
346        return $return;
347    }
348
349    /**
350     * Add line to map
351     * @param string $value
352     * @return bool
353     */
354    public function addElementLine( $value ) {
355        global $egMultiMaps_SeparatorItems;
356
357        $return = true;
358        $stringsline = explode( $egMultiMaps_SeparatorItems, $value );
359        foreach ( $stringsline as $linevalue ) {
360            if ( trim( $linevalue ) == '' ) {
361                continue;
362            }
363            $line = new Line();
364            if ( !$line->parse( $linevalue, $this->classname ) ) {
365                $return = false;
366                $this->errormessages = array_merge( $this->errormessages, $line->getErrorMessages() );
367            }
368            if ( !$line->isValid() ) {
369                continue;
370            }
371            $this->lines[] = $line;
372            $this->elementsBounds->extend( $line->pos );
373        }
374        return $return;
375    }
376
377    /**
378     * Add polygon to map
379     * @param string $value
380     * @return bool
381     */
382    public function addElementPolygon( $value ) {
383        global $egMultiMaps_SeparatorItems;
384
385        $return = true;
386        $stringspolygon = explode( $egMultiMaps_SeparatorItems, $value );
387        foreach ( $stringspolygon as $polygonvalue ) {
388            if ( trim( $polygonvalue ) == '' ) {
389                continue;
390            }
391            $polygon = new Polygon();
392            if ( !$polygon->parse( $polygonvalue, $this->classname ) ) {
393                $return = false;
394                $this->errormessages = array_merge( $this->errormessages, $polygon->getErrorMessages() );
395            }
396            if ( !$polygon->isValid() ) {
397                continue;
398            }
399            $this->polygons[] = $polygon;
400            $this->elementsBounds->extend( $polygon->pos );
401        }
402        return $return;
403    }
404
405    /**
406     * Add rectangle to map
407     * @param string $value
408     * @return bool
409     */
410    public function addElementRectangle( $value ) {
411        global $egMultiMaps_SeparatorItems;
412
413        $return = true;
414        $stringsrectangle = explode( $egMultiMaps_SeparatorItems, $value );
415        foreach ( $stringsrectangle as $rectanglevalue ) {
416            if ( trim( $rectanglevalue ) == '' ) {
417                continue;
418            }
419            $rectangle = new Rectangle();
420            if ( !$rectangle->parse( $rectanglevalue, $this->classname ) ) {
421                $return = false;
422                $this->errormessages = array_merge( $this->errormessages, $rectangle->getErrorMessages() );
423            }
424            if ( !$rectangle->isValid() ) {
425                continue;
426            }
427            $this->rectangles[] = $rectangle;
428            $this->elementsBounds->extend( $rectangle->pos );
429        }
430        return $return;
431    }
432
433    /**
434     * Add circle to map
435     * @param string $value
436     * @return bool
437     */
438    public function addElementCircle( $value ) {
439        global $egMultiMaps_SeparatorItems;
440
441        $return = true;
442        $stringscircle = explode( $egMultiMaps_SeparatorItems, $value );
443        foreach ( $stringscircle as $circlevalue ) {
444            if ( trim( $circlevalue ) == '' ) {
445                continue;
446            }
447            $circle = new Circle();
448            if ( !$circle->parse( $circlevalue, $this->classname ) ) {
449                $return = false;
450                $this->errormessages = array_merge( $this->errormessages, $circle->getErrorMessages() );
451            }
452            if ( !$circle->isValid() ) {
453                continue;
454            }
455            $this->circles[] = $circle;
456            $circlescount = count( $circle->pos );
457            for ( $index = 0; $index < $circlescount; $index++ ) {
458                $ne = new Point( $circle->pos[$index]->lat, $circle->pos[$index]->lon );
459                $sw = new Point( $circle->pos[$index]->lat, $circle->pos[$index]->lon );
460                $ne->move( $circle->radiuses[$index], $circle->radiuses[$index] );
461                $sw->move( -$circle->radiuses[$index], -$circle->radiuses[$index] );
462                $this->elementsBounds->extend( [ $ne, $sw ] );
463            }
464        }
465        return $return;
466    }
467
468    public function __set( $name, $value ) {
469        $this->setProperty( $name, $value );
470    }
471
472    public function setProperty( $name, $value ) {
473        // TODO available properties
474        $name = strtolower( $name );
475
476        switch ( $name ) {
477            case 'center':
478                $center = GeoCoordinate::getLatLonFromString( $value );
479                if ( $center ) {
480                    $this->properties['center'] = $center;
481                } else {
482                    $this->errormessages[] = wfMessage( 'multimaps-unable-parse-parameter', $name, $value )->escaped();
483                }
484                return true;
485            case 'icon':
486                $marker = new Marker();
487                if ( $marker->setProperty( 'icon', $value ) ) {
488                    if ( $marker->icon !== null ) {
489                        $this->properties['icon'] = $marker->icon;
490                    }
491                    if ( $marker->size !== null ) {
492                        $this->properties['iconSize'] = $marker->size;
493                    }
494                    if ( $marker->anchor !== null ) {
495                        $this->properties['iconAnchor'] = $marker->anchor;
496                    }
497                    if ( $marker->shadow !== null ) {
498                        $this->properties['iconShadow'] = $marker->shadow;
499                    }
500                    if ( $marker->shSize !== null ) {
501                        $this->properties['iconShSize'] = $marker->shSize;
502                    }
503                    if ( $marker->shAnchor !== null ) {
504                        $this->properties['iconShAnchor'] = $marker->shAnchor;
505                    }
506                } else {
507                    $this->errormessages = array_merge( $this->errormessages, $marker->getErrorMessages() );
508                }
509                return true;
510            case 'height':
511                $this->height = $value;
512                return true;
513            case 'width':
514                $this->width = $value;
515                return true;
516            case 'bounds':
517                // TODO
518                break;
519
520            default:
521                if ( is_string( $value ) ) {
522                    $this->properties[$name] = htmlspecialchars( $value, ENT_NOQUOTES );
523                } else {
524                    $this->properties[$name] = $value;
525                }
526                return true;
527        }
528        return false;
529    }
530
531    public function __get( $name ) {
532        return $this->getProperty( $name );
533    }
534
535    public function getProperty( $name ) {
536        $name = strtolower( $name );
537
538        switch ( $name ) {
539            case 'classname':
540                return $this->classname;
541            default:
542                return isset( $this->properties[$name] ) ? $this->properties[$name] : null;
543        }
544    }
545
546    /**
547     * Add dependencies (resourceModules, headerItem) to Parser output
548     * @param Parser &$parser
549     */
550    public function addDependencies( Parser &$parser ) {
551        $output = $parser->getOutput();
552        foreach ( $this->resourceModules as $modules ) {
553            $output->addModules( $modules );
554        }
555
556        if ( $this->headerItem != '' ) {
557            $output->addHeadItem( $this->headerItem, "multimaps_{$this->classname}" );
558        }
559    }
560
561    /**
562     * Initializes the object again
563     */
564    public function reset() {
565        global $egMultiMaps_Width, $egMultiMaps_Height;
566
567        $this->elementsBounds = new Bounds();
568        $this->width = $egMultiMaps_Width;
569        $this->height = $egMultiMaps_Height;
570        $this->properties = [];
571
572        $this->markers = [];
573        $this->lines = [];
574        $this->polygons = [];
575        $this->rectangles = [];
576        $this->circles = [];
577
578        $this->errormessages = [];
579    }
580
581    /**
582     * Returns an error messages
583     * @return array
584     */
585    public function getErrorMessages() {
586        return $this->errormessages;
587    }
588
589    /**
590     * Push error message into error messages
591     * @param string $string
592     */
593    public function pushErrorMessage( $string ) {
594        $this->errormessages[] = $string;
595    }
596
597}