r54246 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r54245‎ | r54246 | r54247 >
Date:23:41, 2 August 2009
Author:jeroendedauw
Status:deferred
Tags:
Comment:
Refactoring for 0.3
Modified paths:
  • /trunk/extensions/SemanticMaps/GoogleMaps/SM_GoogleMaps.php (modified) (history)
  • /trunk/extensions/SemanticMaps/SM_MapPrinter.php (modified) (history)
  • /trunk/extensions/SemanticMaps/SM_Mapper.php (modified) (history)
  • /trunk/extensions/SemanticMaps/SemanticMaps.php (modified) (history)
  • /trunk/extensions/SemanticMaps/YahooMaps/SM_YahooMaps.php (modified) (history)

Diff [purge]

Index: trunk/extensions/SemanticMaps/YahooMaps/SM_YahooMaps.php
@@ -19,127 +19,65 @@
2020 wfLoadExtensionMessages('SemanticMaps');
2121 return wfMsg('sm_yahoomaps_printername');
2222 }
23 -
 23+
2424 /**
25 - * Returns the Yahoo Map Control type for the provided a general map control
26 - * type. When no match is found, the provided control name will be used.
 25+ * @see SMMapPrinter::setQueryPrinterSettings()
 26+ *
2727 */
28 - public static function getExtraMapControls($controls, $yahooMapsOnThisPage) {
29 - global $egMapsYMapControls;
 28+ protected function setQueryPrinterSettings() {
 29+ global $egMapsYahooMapsZoom;
3030
31 - $extraMapControls = '';
32 - $panAdded = false; $zoomAdded = false;
 31+ $this->elementNamePrefix = 'map_yahoo';
 32+ $this->defaultZoom = $egMapsYahooMapsZoom;
 33+ }
 34+
 35+ /**
 36+ * @see SMMapPrinter::doMapServiceLoad()
 37+ *
 38+ */
 39+ protected function doMapServiceLoad() {
 40+ global $egYahooMapsOnThisPage;
3341
34 - if (count($controls) < 1) $controls = $egMapsYMapControls; // In case no controls are provided, use the default
 42+ MapsYahooMaps::addYMapDependencies($this->output);
 43+ $egYahooMapsOnThisPage++;
3544
36 - foreach ($controls as $control) { // Loop through the controls, and add the JS needed to add them
37 - switch (strtolower($control)) {
38 - case 'pan' :
39 - if (!$panAdded) {$extraMapControls .= "yahoo_$yahooMapsOnThisPage.addPanControl(); "; $panAdded = true; }
40 - break;
41 - case 'zoom' :
42 - if (!$zoomAdded) {$extraMapControls .= "yahoo_$yahooMapsOnThisPage.addZoomLong(); "; $zoomAdded = true; }
43 - break;
44 - }
45 - }
46 -
47 - return $extraMapControls;
48 - }
 45+ $this->elementNr = $egYahooMapsOnThisPage;
 46+ }
4947
50 - protected function getResultText($res, $outputmode) {
51 - parent::getResultText($res, $outputmode);
 48+ /**
 49+ * @see SMMapPrinter::addSpecificMapHTML()
 50+ *
 51+ */
 52+ protected function addSpecificMapHTML() {
 53+ global $wgJsMimeType;
5254
53 - // Go through the array with map parameters and create new variables
54 - // with the name of the key and value of the item.
55 - foreach($this->m_params as $paramName => $paramValue) {
56 - if (empty(${$paramName})) ${$paramName} = $paramValue;
57 - }
 55+ $this->type = MapsYahooMaps::getYMapType($this->type);
 56+ $this->controls = MapsYahooMaps::createControlsString($this->controls);
5857
59 - $result = "";
 58+ MapsUtils::makePxValue($this->width);
 59+ MapsUtils::makePxValue($this->height);
6060
61 - global $egYahooMapsOnThisPage, $egMapsYahooMapsZoom;
 61+ $markerItems = array();
6262
63 - if (strlen($zoom) < 1) $zoom = $egMapsYahooMapsZoom;
64 -
65 - if (empty($egYahooMapsOnThisPage)) {
66 - $egYahooMapsOnThisPage = 0;
67 - MapsYahooMaps::addYMapDependencies($result);
 63+ foreach ($this->m_locations as $location) {
 64+ // Create a string containing the marker JS
 65+ list($lat, $lon, $title, $label, $icon) = $location;
 66+ $title = str_replace("'", "\'", $title);
 67+ $label = str_replace("'", "\'", $label);
 68+ $markerItems[] = "getYMarkerData($lat, $lon, '$title', '$label', '')";
6869 }
6970
70 - $egYahooMapsOnThisPage++;
 71+ $markersString = implode(',', $markerItems);
7172
72 - // Get the Yahoo Maps names for the control and map types
73 - $type = MapsYahooMaps::getYMapType($type);
74 - $extraMapControls = self::getExtraMapControls($controls, $egYahooMapsOnThisPage);
 73+ $this->output .= "
 74+ <div id='$this->mapName' style='width: $this->width; height: $this->height;'></div>
7575
76 - $map_text = "";
77 -
78 - if (count($this->m_locations) > 0) {
79 - if (empty($centre)) {
80 - // If the center is not set, it needs to be determined, together with the bounds
81 - // This is done with the getBestZoomAndCenter function of the Y! Maps API
82 - $map_text .= "var ymap_locations_$egYahooMapsOnThisPage = Array();";
83 -
84 - foreach ($this->m_locations as $i => $location) {
85 - // Add the markers to the map
86 - list($lat, $lon, $title, $label, $icon) = $location;
87 - $title = str_replace("'", "\'", $title);
88 - $label = str_replace("'", "\'", $label);
89 - $map_text .= "
90 - yahoo_$egYahooMapsOnThisPage.addOverlay(createYMarker(new YGeoPoint($lat, $lon), '$title', '$label'));
91 - ymap_locations_$egYahooMapsOnThisPage.push(new YGeoPoint($lat, $lon));";
92 - }
93 -
94 - $map_text .= "var centerAndZoom = yahoo_$egYahooMapsOnThisPage.getBestZoomAndCenter(ymap_locations_$egYahooMapsOnThisPage);
95 - yahoo_$egYahooMapsOnThisPage.drawZoomAndCenter(centerAndZoom.YGeoPoint, centerAndZoom.zoomLevel);";
96 - }
97 - else {
98 - //if ($centre == null) {
99 - // $centre_lat = 0;
100 - // $centre_lon = 0;
101 - //}
102 - //else {
103 - // If the center is set, get the coordinates
104 - list($centre_lat, $centre_lon) = MapsUtils::getLatLon($centre);
105 - //}
106 -
107 - foreach ($this->m_locations as $i => $location) {
108 - // Add the markers to the map
109 - list($lat, $lon, $title, $label, $icon) = $location;
110 - $title = str_replace("'", "\'", $title);
111 - $label = str_replace("'", "\'", $label);
112 - $map_text .= "yahoo_$egYahooMapsOnThisPage.addOverlay(createYMarker(new YGeoPoint($lat, $lon), '$title', '$label'));";
113 - }
114 -
115 - $map_text .= " yahoo_$egYahooMapsOnThisPage.drawZoomAndCenter(new YGeoPoint($centre_lat, $centre_lon), $zoom);";
116 - }
117 - }
118 -
119 - // Disbale the scroll wheel zoom when autozoom is set to off
120 - switch($autozoom) {
121 - case 'no' : case 'off' :
122 - $disbaleKeyControlCode = "yahoo_$egYahooMapsOnThisPage.disableKeyControls();";
123 - break;
124 - default:
125 - $disbaleKeyControlCode = '';
126 - break;
127 - }
128 -
129 - $width = $width . 'px';
130 - $height = $height . 'px';
131 -
132 - $result .= "
133 - <div id='map-yahoo-$egYahooMapsOnThisPage' style='width: $width; height: $height;'></div>
134 -
135 - <script type='text/javascript'>/*<![CDATA[*/
136 - var yahoo_locations_$egYahooMapsOnThisPage = new YGeoPoint($lat, $lon);
137 - var yahoo_$egYahooMapsOnThisPage = new YMap(document.getElementById('map-yahoo-$egYahooMapsOnThisPage'));
138 - yahoo_$egYahooMapsOnThisPage.addTypeControl(); $extraMapControls
139 - yahoo_$egYahooMapsOnThisPage.setMapType($type);
140 - $disbaleKeyControlCode $map_text /*]]>*/</script>";
141 -
142 - return array($result, 'noparse' => 'true', 'isHTML' => 'true');
143 - }
 76+ <script type='$wgJsMimeType'>/*<![CDATA[*/
 77+ addLoadEvent(
 78+ initializeYahooMap('$this->mapName', $this->centre_lat, $this->centre_lon, $this->zoom, $this->type, [$this->controls], $this->autozoom, [$markersString])
 79+ );
 80+ /*]]>*/</script>";
14481
 82+ }
14583
14684 }
\ No newline at end of file
Index: trunk/extensions/SemanticMaps/SM_MapPrinter.php
@@ -18,21 +18,71 @@
1919 abstract class SMMapPrinter extends SMWResultPrinter {
2020 // TODO: make class and child's more OOP, in a way similair to class MapsBaseMap in Maps
2121
 22+ /**
 23+ * Sets the map service specific element name
 24+ */
 25+ protected abstract function setQueryPrinterSettings();
 26+
 27+ /**
 28+ * Map service spesific map count and loading of dependencies
 29+ */
 30+ protected abstract function doMapServiceLoad();
 31+
 32+ /**
 33+ * Gets the query result
 34+ */
 35+ protected abstract function addSpecificMapHTML();
 36+
2237 protected $m_locations = array();
2338
24 - public function getResult($results, $params, $outputmode) {
 39+ protected $defaultZoom;
 40+ protected $elementNr;
 41+ protected $elementNamePrefix;
 42+
 43+ protected $mapName;
 44+
 45+ protected $centre_lat;
 46+ protected $centre_lon;
 47+
 48+ protected $output = '';
 49+
 50+ /**
 51+ *
 52+ *
 53+ */
 54+ public final function getResultText($res, $outputmode) {
 55+ $this->formatResultData($res, $outputmode);
 56+
 57+ $this->manageMapProperties();
 58+
 59+ $this->setQueryPrinterSettings();
 60+
 61+ $this->doMapServiceLoad();
 62+
 63+ $this->mapName = $this->elementNamePrefix.'_'.$this->elementNr;
 64+
 65+ $this->autozoom = ($this->autozoom == 'no' || $this->autozoom == 'off') ? 'false' : 'true';
 66+
 67+ $this->setZoom();
 68+
 69+ $this->setCentre();
 70+
 71+ $this->addSpecificMapHTML();
 72+
 73+ return array($this->output, 'noparse' => 'true', 'isHTML' => 'true');
 74+ }
 75+
 76+ public final function getResult($results, $params, $outputmode) {
2577 // Skip checks, results with 0 entries are normal
2678 $this->readParameters($params, $outputmode);
2779 return $this->getResultText($results, SMW_OUTPUT_HTML);
2880 }
2981
30 - protected function getResultText($res, $outputmode) {
 82+ private function formatResultData($res, $outputmode) {
3183 while ( ($row = $res->getNext()) !== false ) {
3284 $this->addResultRow($outputmode, $row);
3385 }
34 -
35 - $this->m_params = MapsMapper::setDefaultParValues($this->m_params, true);
36 - }
 86+ }
3787
3888 /**
3989 * This function will loop through all properties (fields) of one record (row),
@@ -112,4 +162,46 @@
113163 return $icon;
114164 }
115165
 166+ private function manageMapProperties() {
 167+ $this->m_params = MapsMapper::setDefaultParValues($this->m_params, true);
 168+
 169+ // Go through the array with map parameters and create new variables
 170+ // with the name of the key and value of the item if they don't exist on class level yet.
 171+ foreach($this->m_params as $paramName => $paramValue) {
 172+ if (!property_exists('SMMapPrinter', $paramName)) {
 173+ $this->{$paramName} = $paramValue;
 174+ }
 175+ }
 176+ }
 177+
 178+ /**
 179+ * Sets the zoom level to the provided value, or when not set, to the default.
 180+ *
 181+ */
 182+ private function setZoom() {
 183+ if (strlen($this->zoom) < 1) {
 184+ if (count($this->m_locations) > 1) {
 185+ $this->zoom = 'null';
 186+ }
 187+ else {
 188+ $this->zoom = $this->defaultZoom;
 189+ }
 190+ }
 191+ }
 192+
 193+ /**
 194+ * Sets the $centre_lat and $centre_lon fields.
 195+ * Note: this needs to be done AFTRE the maker coordinates are set.
 196+ *
 197+ */
 198+ private function setCentre() {
 199+ if (strlen($this->centre) > 0) {
 200+ list($this->centre_lat, $this->centre_lon) = MapsUtils::getLatLon($this->centre);
 201+ }
 202+ else {
 203+ $this->centre_lat = 'null';
 204+ $this->centre_lon = 'null';
 205+ }
 206+ }
 207+
116208 }
Index: trunk/extensions/SemanticMaps/SM_Mapper.php
@@ -13,26 +13,19 @@
1414 die( 'Not an entry point.' );
1515 }
1616
17 -final class SMMapper extends SMMapPrinter {
 17+final class SMMapper extends SMWResultPrinter {
1818
1919 protected function getResultText($res, $outputmode) {
20 - global $egMapsDefaultService;
 20+ global $egMapsDefaultService, $egMapsServices;
2121
2222 // TODO: allow service parameter to override the default
 23+ if ($this->mFormat == 'map') $this->mFormat = $egMapsDefaultService;
2324
24 - switch ($egMapsDefaultService) {
25 - case 'openlayers' : case 'layers' :
26 - $output = SMOpenLayers::getResultText($res, $outputmode);
27 - break;
28 - case 'yahoomaps' : case 'yahoo' :
29 - $output = SMYahooMaps::getResultText($res, $outputmode);
30 - break;
31 - default:
32 - $output = SMGoogleMaps::getResultText($res, $outputmode);
33 - break;
34 - }
 25+ $service = MapsMapper::getValidService($this->mFormat);
3526
36 - return $output;
 27+ $queryPrinter = new $egMapsServices[$service]['qp']['class']();
 28+
 29+ return $queryPrinter->getResultText($res, $outputmode);
3730 }
3831
3932 }
\ No newline at end of file
Index: trunk/extensions/SemanticMaps/SemanticMaps.php
@@ -40,6 +40,10 @@
4141 $egMapsServices['openlayers']['qp'] = array('class' => 'SMOpenLayers', 'file' => 'OpenLayers/SM_OpenLayers.php');
4242 $egMapsServices['openlayers']['fi'] = array('class' => 'SMOpenLayersFormInput', 'file' => 'OpenLayers/SM_OpenLayersFormInput.php');
4343
 44+/**
 45+ * Initialization function for the Semantic Maps extension
 46+ *
 47+ */
4448 function smfSetup() {
4549 global $wgExtensionCredits, $egMapsServices;
4650
@@ -81,9 +85,29 @@
8286 }
8387
8488 /**
 89+ * Add the result format for a mapping service or alias
 90+ *
 91+ * @param unknown_type $format
 92+ * @param unknown_type $qp
 93+ */
 94+function smfInitFormat($format, $qp) {
 95+ global $wgAutoloadClasses, $smwgResultFormats, $smgIP;
 96+
 97+ if (! array_key_exists($qp['class'], $wgAutoloadClasses)) $wgAutoloadClasses[$qp['class']] = $smgIP . '/' . $qp['file'];
 98+
 99+ if (isset($smwgResultFormats)) {
 100+ $smwgResultFormats[$format] = $qp['class'];
 101+ }
 102+ else {
 103+ SMWQueryProcessor::$formats[$format] = $qp['class'];
 104+ }
 105+}
 106+
 107+/**
85108 * Adds a mapping service's form hook
86109 *
87110 * @param unknown_type $service
 111+ * @param unknown_type $fi
88112 * @param unknown_type $mainName
89113 */
90114 function smfInitFormHook($service, $fi = null, $mainName = '') {
@@ -100,25 +124,6 @@
101125 }
102126
103127 /**
104 - * Add the result format for a mapping service or alias
105 - *
106 - * @param unknown_type $format
107 - * @param unknown_type $qp
108 - */
109 -function smfInitFormat($format, $qp) {
110 - global $wgAutoloadClasses, $smwgResultFormats, $smgIP;
111 -
112 - if (! array_key_exists($qp['class'], $wgAutoloadClasses)) $wgAutoloadClasses[$qp['class']] = $smgIP . '/' . $qp['file'];
113 -
114 - if (isset($smwgResultFormats)) {
115 - $smwgResultFormats[$format] = $qp['class'];
116 - }
117 - else {
118 - SMWQueryProcessor::$formats[$format] = $qp['class'];
119 - }
120 -}
121 -
122 -/**
123128 * Class for the form input type 'map'. The relevant form input class is called depending on the provided service.
124129 *
125130 * @param unknown_type $coordinates
Index: trunk/extensions/SemanticMaps/GoogleMaps/SM_GoogleMaps.php
@@ -21,135 +21,74 @@
2222 wfLoadExtensionMessages('SemanticMaps');
2323 return wfMsg('sm_googlemaps_printername');
2424 }
25 -
26 - protected function getResultText($res, $outputmode) {
27 - parent::getResultText($res, $outputmode);
 25+
 26+ /**
 27+ * @see SMMapPrinter::setQueryPrinterSettings()
 28+ *
 29+ */
 30+ protected function setQueryPrinterSettings() {
 31+ global $egMapsGoogleMapsZoom;
2832
29 - // Go through the array with map parameters and create new variables
30 - // with the name of the key and value of the item.
31 - foreach($this->m_params as $paramName => $paramValue) {
32 - if (empty(${$paramName})) ${$paramName} = $paramValue;
 33+ $this->elementNamePrefix = 'map_google';
 34+
 35+ $this->defaultZoom = $egMapsGoogleMapsZoom;
 36+ }
 37+
 38+ /**
 39+ * @see SMMapPrinter::doMapServiceLoad()
 40+ *
 41+ */
 42+ protected function doMapServiceLoad() {
 43+ global $egGoogleMapsOnThisPage;
 44+
 45+ if (empty($egGoogleMapsOnThisPage)) {
 46+ $egGoogleMapsOnThisPage = 0;
 47+ MapsGoogleMaps::addGMapDependencies($this->output);
3348 }
3449
35 - global $wgJsMimeType, $egGoogleMapsKey, $egGoogleMapsOnThisPage, $egMapsGoogleMapsZoom;
36 - global $wgLang;
 50+ $egGoogleMapsOnThisPage++;
3751
38 - $result = "";
 52+ $this->elementNr = $egGoogleMapsOnThisPage;
 53+ }
 54+
 55+ /**
 56+ * @see SMMapPrinter::getQueryResult()
 57+ *
 58+ */
 59+ protected function addSpecificMapHTML() {
 60+ global $wgJsMimeType;
 61+
 62+ $enableEarth = MapsGoogleMaps::getEarthValue($this->earth);
 63+ $this->earth = MapsGoogleMaps::getJSEarthValue($enableEarth);
3964
40 - if (strlen($zoom) < 1) $zoom = $egMapsGoogleMapsZoom;
41 -
42 - // TODO: autozoom does not work (accuratly) for GE?
43 - switch($earth) {
44 - case 'on' : case 'yes' :
45 - $earthCode = "map.addMapType(G_SATELLITE_3D_MAP);";
46 - break;
47 - default :
48 - $earthCode = '';
49 - break;
50 - }
51 -
5265 // Get the Google Maps names for the control and map types
53 - $type = MapsGoogleMaps::getGMapType($type, strlen($earthCode) > 0);
54 - $control_class = MapsGoogleMaps::getGControlType($controls);
 66+ $this->type = MapsGoogleMaps::getGMapType($this->type, $enableEarth);
 67+ $control = MapsGoogleMaps::getGControlType($this->controls);
5568
56 - $map_text = '';
 69+ $markerItems = array();
5770
58 - if (empty($egGoogleMapsOnThisPage)) {
59 - $egGoogleMapsOnThisPage = 0;
60 - MapsGoogleMaps::addGMapDependencies($map_text);
 71+ foreach ($this->m_locations as $location) {
 72+ // Create a string containing the marker JS
 73+ list($lat, $lon, $title, $label, $icon) = $location;
 74+ $title = str_replace("'", "\'", $title);
 75+ $label = str_replace("'", "\'", $label);
 76+ $markerItems[] = "getGMarkerData($lat, $lon, '$title', '$label')";
6177 }
62 - $egGoogleMapsOnThisPage++;
63 -
64 - // Enable the scroll wheel zoom when autozoom is not set to off
65 - switch($autozoom) {
66 - case 'no' : case 'off' :
67 - $autozoomCode = '';
68 - break;
69 - default:
70 - $autozoomCode = 'map.enableScrollWheelZoom();';
71 - break;
72 - }
7378
74 - $map_text .= <<<END
75 -<div id="map$egGoogleMapsOnThisPage" class="$class"></div>
76 -<script type="text/javascript">
77 -function makeMap{$egGoogleMapsOnThisPage}() {
78 - if (GBrowserIsCompatible()) {
79 - var map = new GMap2(document.getElementById("map$egGoogleMapsOnThisPage"), {size: new GSize('$width', '$height')});
80 - map.setMapType($type);
81 - map.addControl(new {$control_class}());
82 - map.addControl(new GMapTypeControl());
83 - $autozoomCode $earthCode
84 -END;
85 -
86 - if (count($this->m_locations) > 0) {
87 - if (empty($centre)) {
88 - // If the center is not set, it needs to be determined, together with the bounds
89 - // This is done by extending the bounds with every point (Google Maps API)
90 - // and then getting the center and zoom level
91 - $map_text .= "var bounds = new GLatLngBounds();";
92 -
93 - foreach ($this->m_locations as $i => $location) {
94 - // Add the markers to the map
95 - list($lat, $lon, $title, $label, $icon) = $location;
96 - $title = str_replace("'", "\'", $title);
97 - $label = str_replace("'", "\'", $label);
98 - $map_text .= "
99 - var point = new GLatLng($lat, $lon);
100 - bounds.extend(point);
101 - map.addOverlay(createGMarker(point, '$title', '$label', '$icon'));";
102 - }
103 -
104 - $map_text .= "map.setCenter(bounds.getCenter(), map.getBoundsZoomLevel(bounds));";
105 - }
106 - else {
107 - if ($centre == null) {
108 - $centre_lat = 0;
109 - $centre_lon = 0;
110 - }
111 - else {
112 - // If the center is set, get the coordinates
113 - // GLatLng class expects only numbers, no letters or degree symbols
114 - list($centre_lat, $centre_lon) = MapsUtils::getLatLon($centre);
115 - }
116 -
117 - foreach ($this->m_locations as $i => $location) {
118 - // Add the markers to the map
119 - list($lat, $lon, $title, $label, $icon) = $location;
120 - $title = str_replace("'", "\'", $title);
121 - $label = str_replace("'", "\'", $label);
122 - $map_text .= "map.addOverlay(createGMarker(new GLatLng($lat, $lon), '$title', '$label', '$icon'));";
123 - }
124 -
125 - $map_text .= " map.setCenter(new GLatLng($centre_lat, $centre_lon), $zoom);\n";
126 - }
127 - }
 79+ $markersString = implode(',', $markerItems);
12880
129 - $map_text .=<<<END
130 - }
131 -}
132 -addLoadEvent(makeMap{$egGoogleMapsOnThisPage});
133 -</script>
134 -END;
 81+ $this->output .= <<<END
 82+<div id="$this->mapName" class="$this->class" style="$this->style" ></div>
 83+<script type="$wgJsMimeType"> /*<![CDATA[*/
 84+addLoadEvent(
 85+ initializeGoogleMap('$this->mapName', $this->width, $this->height, $this->centre_lat, $this->centre_lon, $this->zoom, $this->type, new $control(), $this->autozoom, $this->earth, [$markersString])
 86+);
 87+/*]]>*/ </script>
13588
136 - $result .= $map_text;
137 -
138 - // print further results footer
139 - // getSearchLabel() method was added in SMW 1.3
140 - if (method_exists($this, 'getSearchLabel')) {
141 - $search_label = $this->getSearchLabel(SMW_OUTPUT_HTML);
142 - } else {
143 - $search_label = $this->mSearchlabel;
144 - }
145 - if ( $this->mInline && $res->hasFurtherResults() && $search_label !== '') {
146 - $link = $res->getQueryLink();
147 - $link->setCaption($search_label);
148 - $result .= "\t<tr class=\"smwfooter\"><td class=\"sortbottom\" colspan=\"" . $res->getColumnCount() . '"> ' . $link->getText($outputmode,$this->mLinker) . "</td></tr>\n";
149 - }
150 - return array($result, 'noparse' => 'true', 'isHTML' => 'true');
151 -
 89+END;
 90+
15291 }
 92+
15393
154 -
15594 }
15695

Status & tagging log