store = $store; } /** * @return callable */ public function getExceptionCallback() { return $this->exceptionCallback; } /** * @param callable $exceptionCallback */ public function setExceptionCallback( $exceptionCallback ) { $this->exceptionCallback = $exceptionCallback; } /** * @param string $file */ public function importFromFile( $file ) { $xml = file_get_contents( $file ); if ( $xml === false ) { throw new RuntimeException( 'Failed to read ' . $file . '!' ); } $this->importFromXML( $xml ); } /** * @param string $xml * * @throws InvalidArgumentException */ public function importFromXML( $xml ) { $document = new DOMDocument(); $oldLibXmlErrors = libxml_use_internal_errors( true ); $ok = $document->loadXML( $xml, LIBXML_NONET ); if ( !$ok ) { $errors = libxml_get_errors(); libxml_use_internal_errors( $oldLibXmlErrors ); foreach ( $errors as $error ) { /** @var LibXMLError $error */ throw new InvalidArgumentException( 'Malformed XML: ' . $error->message . ' in line ' . $error->line ); } throw new InvalidArgumentException( 'Malformed XML!' ); } libxml_use_internal_errors( $oldLibXmlErrors ); $this->importFromDOM( $document->documentElement ); } /** * @param DOMElement $root */ private function importFromDOM( DOMElement $root ) { $sites = $this->makeSiteList( $root ); $this->store->saveSites( $sites ); } /** * @param DOMElement $root * * @return Site[] */ private function makeSiteList( DOMElement $root ) { $sites = array(); // Old sites, to get the row IDs that correspond to the global site IDs. // TODO: Get rid of internal row IDs, they just get in the way. Get rid of ORMRow, too. $oldSites = $this->store->getSites(); $current = $root->firstChild; while ( $current ) { if ( $current instanceof DOMElement && $current->tagName === 'site' ) { try { $site = $this->makeSite( $current ); $key = $site->getGlobalId(); if ( $oldSites->hasSite( $key ) ) { $oldSite = $oldSites->getSite( $key ); $site->setInternalId( $oldSite->getInternalId() ); } $sites[$key] = $site; } catch ( Exception $ex ) { $this->handleException( $ex ); } } $current = $current->nextSibling; } return $sites; } /** * @param DOMElement $siteElement * * @return Site * @throws InvalidArgumentException */ public function makeSite( DOMElement $siteElement ) { if ( $siteElement->tagName !== 'site' ) { throw new InvalidArgumentException( 'Expected tag, found ' . $siteElement->tagName ); } $type = $this->getAttributeValue( $siteElement, 'type', Site::TYPE_UNKNOWN ); $site = Site::newForType( $type ); $site->setForward( $this->hasChild( $siteElement, 'forward' ) ); $site->setGlobalId( $this->getChildText( $siteElement, 'globalid' ) ); $site->setGroup( $this->getChildText( $siteElement, 'group', Site::GROUP_NONE ) ); $site->setSource( $this->getChildText( $siteElement, 'source', Site::SOURCE_LOCAL ) ); $pathTags = $siteElement->getElementsByTagName( 'path' ); for ( $i = 0; $i < $pathTags->length; $i++ ) { $pathElement = $pathTags->item( $i ); $pathType = $this->getAttributeValue( $pathElement, 'type' ); $path = $pathElement->textContent; $site->setPath( $pathType, $path ); } $idTags = $siteElement->getElementsByTagName( 'localid' ); for ( $i = 0; $i < $idTags->length; $i++ ) { $idElement = $idTags->item( $i ); $idType = $this->getAttributeValue( $idElement, 'type' ); $id = $idElement->textContent; $site->addLocalId( $idType, $id ); } //@todo: import //@todo: import return $site; } /** * @param DOMElement $element * @param $name * @param string|null|bool $default * * @return null|string * @throws MWException If the attribute is not found and no default is provided */ private function getAttributeValue( DOMElement $element, $name, $default = false ) { $node = $element->getAttributeNode( $name ); if ( !$node ) { if ( $default !== false ) { return $default; } else { throw new MWException( 'Required ' . $name . ' attribute not found in <' . $element->tagName . '> tag' ); } } return $node->textContent; } /** * @param DOMElement $element * @param string $name * @param string|null|bool $default * * @return null|string * @throws MWException If the child element is not found and no default is provided */ private function getChildText( DOMElement $element, $name, $default = false ) { $elements = $element->getElementsByTagName( $name ); if ( $elements->length < 1 ) { if ( $default !== false ) { return $default; } else { throw new MWException( 'Required <' . $name . '> tag not found inside <' . $element->tagName . '> tag' ); } } $node = $elements->item( 0 ); return $node->textContent; } /** * @param DOMElement $element * @param string $name * * @return bool * @throws MWException */ private function hasChild( DOMElement $element, $name ) { return $this->getChildText( $element, $name, null ) !== null; } /** * @param Exception $ex */ private function handleException( Exception $ex ) { if ( $this->exceptionCallback ) { call_user_func( $this->exceptionCallback, $ex ); } else { wfLogWarning( $ex->getMessage() ); } } }