Invision Power Services, Inc. * @copyright (c) Invision Power Services, Inc. * @license https://www.invisioncommunity.com/legal/standards/ * @package Invision Community * @since 18 Feb 2013 */ namespace IPS\Xml; /* To prevent PHP errors (extending class does not exist) revealing path */ if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) ) { header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' ); exit; } /** * Class for managing small XML files */ class _SimpleXML extends \SimpleXMLElement { /** * Create New XML Document * * @param string $rootElement Name of Root Element * @param string|NULL $xmlns Namespace * @return static */ public static function create( $rootElement, $xmlns=NULL ) { if ( $xmlns ) { return static::loadString( "<{$rootElement} xmlns='{$xmlns}'>" ); } else { return static::loadString( "<{$rootElement}>" ); } } /** * Load File * * @param string $filename Filename of XML file * @return static * @throws \InvalidArgumentException */ public static function loadFile( $filename ) { return static::loadString( file_get_contents( $filename ) ); } /** * Load String * * @param string $xml XML * @return static * @throws \InvalidArgumentException */ public static function loadString( $xml ) { if ( version_compare( PHP_VERSION, '8.0.0' ) >= 0 ) { $class = @simplexml_load_string( $xml, \get_called_class() ); } else { /* Turn off external entity loader to prevent XXE in PHP less than 8.0 */ $entityLoaderValue = libxml_disable_entity_loader( TRUE ); /* Load it */ $class = @simplexml_load_string( $xml, \get_called_class() ); /* Turn external entity loader back to what it was before so we're not messing with other PHP scripts on this server */ libxml_disable_entity_loader( $entityLoaderValue ); } /* Return */ if ( $class === FALSE ) { throw new \InvalidArgumentException; } return $class->subtype(); } /** * Get appropriate subtype * * @return static */ protected function subtype() { if ( \get_called_class() === 'IPS\Xml\SimpleXML' ) { if ( $this->getName() === 'rss' ) { return \IPS\Xml\Rss::loadString( $this->asXml() ); } if ( $this->getName() === 'feed' ) { return \IPS\Xml\Atom::loadString( $this->asXml() ); } if ( mb_strtolower( $this->getName() ) === 'rdf' ) { /* Verify this is an RSS 1.0 document */ if( \in_array( 'http://purl.org/rss/1.0/', $this->getNamespaces( true ) ) ) { return \IPS\Xml\Rss1::loadString( $this->asXml() ); } } } return $this; } /** * Get articles * * @param mixed $guidKey In previous versions, we encoded a key with the GUID. For legacy purposes, this can be passed here. * @return array * @note Subtypes (ATOM and RSS) define this method * @throws \BadMethodCallException */ public function articles( $guidKey=NULL ) { throw new \BadMethodCallException; } /** * Add Child * Modified to support passing other data types (including multi-dimensional arrays and other \IPS\XML\SimpleXML objects) as values * * @see SimpleXMLElement::addChild * @param string $name Element Name * @param mixed $value Value * @param string|null $ns Namespace * @return void */ public function addChild( $name, $value=NULL, $ns=NULL ) { /* Arrays are possibly numerically indexed, which XML won't have */ if( \gettype( $name ) === 'integer' ) { $name = $this->getName(); /* If the name ends in s (e.g. ) - we'll name it's children without the s (e.g. ) */ if( mb_substr( $name, -2 ) === 'es' ) { $name = mb_substr( $name, 0, mb_strlen( $name ) - 2 ); } else if( mb_substr( $name, -1 ) === 's' ) { $name = mb_substr( $name, 0, mb_strlen( $name ) - 1 ); } } /* If it's not an array, we can just let the default SimpleXML method handle this */ if( !\is_array( $value ) and !( $value instanceof \IPS\Xml\SimpleXML ) ) { /* Unless it's a boolean value, then we should cast that to an integer */ if( \gettype( $value ) === 'boolean' ) { $value = \intval( $value ); } /* Needs CDATA? */ if ( preg_match( '/<|>|&/', $value ) ) { $element = parent::addChild( $name, '', $ns ); $node = dom_import_simplexml( $element ); $no = $node->ownerDocument; $node->appendChild( $no->createCDATASection( $value ) ); return $element; } else { return parent::addChild( $name, $value, $ns ); } /* Return */ } /* If it is an array, we need to be a little clever */ else { /* Create an element with a blank value */ $element = parent::addChild( $name, '', $ns ); /* And loop through each value and rerun this method for each */ foreach ( $value as $k => $v ) { /* If the value is an XML text element, just get the value */ if ( $v instanceof \IPS\Xml\SimpleXML and \count( $v->children() ) === 0 ) { $v = (string) $v; } /* And add it */ $element->addChild( $k, $v, $ns ); } } } /** * Remove Node * * @return void */ public function removeNode() { } /** * Format XML in a readable way (normally only used for debugging purposes) * * @return string */ public function format() { $dom = new \DOMDocument( '1.0', 'UTF-8' ); $dom->preserveWhiteSpace = false; $dom->formatOutput = true; @$dom->loadXML( $this->asXML() ); return $dom->saveXML(); } }