Current File : /home/jvzmxxx/wiki1/extensions/Kartographer/includes/SimpleStyleParser.php
<?php

namespace Kartographer;

use FormatJson;
use Parser;
use PPFrame;
use Status;
use stdClass;

/**
 * Parses and sanitizes text properties of GeoJSON/simplestyle by putting them through parser
 */
class SimpleStyleParser {
	private static $parsedProps = [ 'title', 'description' ];

	/** @var Parser */
	private $parser;

	/**
	 * @var PPFrame
	 */
	private $frame;

	/**
	 * Constructor
	 *
	 * @param Parser $parser Parser used for wikitext processing
	 * @param PPFrame|null $frame
	 */
	public function __construct( Parser $parser, PPFrame $frame = null ) {
		$this->parser = $parser;
		$this->frame = $frame;
	}

	/**
	 * Parses string into JSON and performs validation/sanitization
	 *
	 * @param string|null $input
	 * @return Status
	 */
	public function parse( $input ) {
		$input = trim( $input );
		$status = Status::newGood( [] );
		if ( $input !== '' && $input !== null ) {
			$status = FormatJson::parse( $input, FormatJson::TRY_FIXING | FormatJson::STRIP_COMMENTS );
			if ( $status->isOK() ) {
				$json = $status->getValue();
				if ( !is_array( $json ) ) {
					$json = [ $json ];
				}
				$status = $this->validateContent( $json );
				if ( $status->isOK() ) {
					$this->sanitize( $json );
					$status = Status::newGood( $json );
				}
			}
		}

		return $status;
	}

	/**
	 * @param stdClass[] $values
	 * @param stdClass $counters counter-name -> integer
	 * @return bool|array [ marker, marker properties ]
	 */
	public static function doCountersRecursive( array &$values, &$counters ) {
		$firstMarker = false;
		if ( !is_array( $values ) ) {
			return $firstMarker;
		}
		foreach ( $values as $item ) {
			if ( property_exists( $item, 'properties' ) &&
				 property_exists( $item->properties, 'marker-symbol' )
			) {
				$marker = $item->properties->{'marker-symbol'};
				// all special markers begin with a dash
				// both 'number' and 'letter' have 6 symbols
				$type = substr( $marker, 0, 7 );
				$isNumber = $type === '-number';
				if ( $isNumber || $type === '-letter' ) {
					// numbers 1..99 or letters a..z
					$count = property_exists( $counters, $marker ) ? $counters->$marker : 0;
					if ( $count < ( $isNumber ? 99 : 26 ) ) {
						$counters->$marker = ++$count;
					}
					$marker = $isNumber ? strval( $count ) : chr( ord( 'a' ) + $count - 1 );
					$item->properties->{'marker-symbol'} = $marker;
					if ( $firstMarker === false ) {
						// GeoJSON is in lowercase, but the letter is shown as uppercase
						$firstMarker = [ mb_strtoupper( $marker ), $item->properties ];
					}
				}
			}
			if ( !property_exists( $item, 'type' ) ) {
				continue;
			}
			$type = $item->type;
			if ( $type === 'FeatureCollection' && property_exists( $item, 'features' ) ) {
				$tmp = self::doCountersRecursive( $item->features, $counters );
				if ( $firstMarker === false ) {
					$firstMarker = $tmp;
				}
			} elseif ( $type === 'GeometryCollection' && property_exists( $item, 'geometries' ) ) {
				$tmp = self::doCountersRecursive( $item->geometries, $counters );
				if ( $firstMarker === false ) {
					$firstMarker = $tmp;
				}
			}
		}
		return $firstMarker;
	}


	/**
	 * @param mixed $json
	 * @return Status
	 */
	private function validateContent( $json ) {
		// The content must be a non-associative array of values or an object
		if ( !is_array( $json ) ) {
			return Status::newFatal( 'kartographer-error-bad_data' );
		}

		return Status::newGood();
	}

	/**
	 * Performs recursive sanitizaton.
	 * Does not attempt to be smart, just recurses through everything that can be dangerous even
	 * if not a valid GeoJSON.
	 *
	 * @param object|array $json
	 */
	private function sanitize( &$json ) {
		if ( is_array( $json ) ) {
			foreach ( $json as &$element ) {
				$this->sanitize( $element );
			}
			return;
		} elseif ( !is_object( $json ) ) {
			return;
		}

		foreach ( array_keys( get_object_vars( $json ) ) as $prop ) {
			// https://phabricator.wikimedia.org/T134719
			if ( $prop[0] === '_' ) {
				unset( $json->$prop );
			} else {
				$this->sanitize( $json->$prop );
			}
		}

		if ( property_exists( $json, 'properties' ) && is_object( $json->properties ) ) {
			$this->sanitizeProperties( $json->properties );
		}
	}

	/**
	 * Sanitizes properties
	 * @param object $properties
	 */
	private function sanitizeProperties( &$properties ) {
		foreach ( self::$parsedProps as $prop ) {
			if ( property_exists( $properties, $prop ) ) {
				if ( !is_string( $properties->$prop ) ) {
					unset( $properties->$prop ); // Dunno what the hell it is, ditch
				} else {
					$properties->$prop = trim( Parser::stripOuterParagraph(
						$this->parser->recursiveTagParseFully( $properties->$prop, $this->frame )
					) );
				}
			}
		}
	}
}