MediaWiki master
SiteImporter.php
Go to the documentation of this file.
1<?php
21namespace MediaWiki\Site;
22
23use DOMDocument;
24use DOMElement;
25use Exception;
26use InvalidArgumentException;
27use RuntimeException;
28use Wikimedia\RequestTimeout\TimeoutException;
29
40
44 private $store;
45
49 private $exceptionCallback;
50
54 public function __construct( SiteStore $store ) {
55 $this->store = $store;
56 }
57
61 public function getExceptionCallback() {
62 return $this->exceptionCallback;
63 }
64
68 public function setExceptionCallback( $exceptionCallback ) {
69 $this->exceptionCallback = $exceptionCallback;
70 }
71
75 public function importFromFile( $file ) {
76 $xml = file_get_contents( $file );
77
78 if ( $xml === false ) {
79 throw new RuntimeException( 'Failed to read ' . $file . '!' );
80 }
81
82 $this->importFromXML( $xml );
83 }
84
90 public function importFromXML( $xml ) {
91 $document = new DOMDocument();
92
93 $oldLibXmlErrors = libxml_use_internal_errors( true );
94 // phpcs:ignore Generic.PHP.NoSilencedErrors -- suppress deprecation per T268847
95 $oldDisable = @libxml_disable_entity_loader( true );
96 $ok = $document->loadXML( $xml, LIBXML_NONET );
97
98 if ( !$ok ) {
99 $errors = libxml_get_errors();
100 libxml_use_internal_errors( $oldLibXmlErrors );
101 // phpcs:ignore Generic.PHP.NoSilencedErrors
102 @libxml_disable_entity_loader( $oldDisable );
103
104 foreach ( $errors as $error ) {
106 throw new InvalidArgumentException(
107 'Malformed XML: ' . $error->message . ' in line ' . $error->line
108 );
109 }
110
111 throw new InvalidArgumentException( 'Malformed XML!' );
112 }
113
114 libxml_use_internal_errors( $oldLibXmlErrors );
115 // phpcs:ignore Generic.PHP.NoSilencedErrors
116 @libxml_disable_entity_loader( $oldDisable );
117 $sites = $this->makeSiteList( $document->documentElement );
118 $this->store->saveSites( $sites );
119 }
120
126 private function makeSiteList( DOMElement $root ) {
127 $sites = [];
128
129 // Old sites, to get the row IDs that correspond to the global site IDs.
130 // TODO: Get rid of internal row IDs, they just get in the way. Get rid of ORMRow, too.
131 $oldSites = $this->store->getSites();
132
133 $current = $root->firstChild;
134 while ( $current ) {
135 if ( $current instanceof DOMElement && $current->tagName === 'site' ) {
136 try {
137 $site = $this->makeSite( $current );
138 $key = $site->getGlobalId();
139
140 if ( $oldSites->hasSite( $key ) ) {
141 $oldSite = $oldSites->getSite( $key );
142 $site->setInternalId( $oldSite->getInternalId() );
143 }
144
145 $sites[$key] = $site;
146 } catch ( TimeoutException $e ) {
147 throw $e;
148 } catch ( Exception $ex ) {
149 $this->handleException( $ex );
150 }
151 }
152
153 $current = $current->nextSibling;
154 }
155
156 return $sites;
157 }
158
164 public function makeSite( DOMElement $siteElement ) {
165 if ( $siteElement->tagName !== 'site' ) {
166 throw new InvalidArgumentException( 'Expected <site> tag, found ' . $siteElement->tagName );
167 }
168
169 $type = $this->getAttributeValue( $siteElement, 'type', Site::TYPE_UNKNOWN );
170 $site = Site::newForType( $type );
171
172 $site->setForward( $this->hasChild( $siteElement, 'forward' ) );
173 $site->setGlobalId( $this->getChildText( $siteElement, 'globalid' ) );
174 $site->setGroup( $this->getChildText( $siteElement, 'group', Site::GROUP_NONE ) );
175 $site->setSource( $this->getChildText( $siteElement, 'source', Site::SOURCE_LOCAL ) );
176
177 $pathTags = $siteElement->getElementsByTagName( 'path' );
178 for ( $i = 0; $i < $pathTags->length; $i++ ) {
179 $pathElement = $pathTags->item( $i );
180 '@phan-var DOMElement $pathElement';
181 $pathType = $this->getAttributeValue( $pathElement, 'type' );
182 $path = $pathElement->textContent;
183
184 $site->setPath( $pathType, $path );
185 }
186
187 $idTags = $siteElement->getElementsByTagName( 'localid' );
188 for ( $i = 0; $i < $idTags->length; $i++ ) {
189 $idElement = $idTags->item( $i );
190 '@phan-var DOMElement $idElement';
191 $idType = $this->getAttributeValue( $idElement, 'type' );
192 $id = $idElement->textContent;
193
194 $site->addLocalId( $idType, $id );
195 }
196
197 // @todo: import <data>
198 // @todo: import <config>
199
200 return $site;
201 }
202
210 private function getAttributeValue( DOMElement $element, $name, $default = false ) {
211 $node = $element->getAttributeNode( $name );
212
213 if ( !$node ) {
214 if ( $default !== false ) {
215 return $default;
216 } else {
217 throw new RuntimeException(
218 'Required ' . $name . ' attribute not found in <' . $element->tagName . '> tag'
219 );
220 }
221 }
222
223 return $node->textContent;
224 }
225
233 private function getChildText( DOMElement $element, $name, $default = false ) {
234 $elements = $element->getElementsByTagName( $name );
235
236 if ( $elements->length < 1 ) {
237 if ( $default !== false ) {
238 return $default;
239 } else {
240 throw new RuntimeException(
241 'Required <' . $name . '> tag not found inside <' . $element->tagName . '> tag'
242 );
243 }
244 }
245
246 $node = $elements->item( 0 );
247 return $node->textContent;
248 }
249
256 private function hasChild( DOMElement $element, $name ) {
257 return $this->getChildText( $element, $name, null ) !== null;
258 }
259
263 private function handleException( Exception $ex ) {
264 if ( $this->exceptionCallback ) {
265 call_user_func( $this->exceptionCallback, $ex );
266 } else {
267 wfLogWarning( $ex->getMessage() );
268 }
269 }
270
271}
272
274class_alias( SiteImporter::class, 'SiteImporter' );
wfLogWarning( $msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
Utility for importing site entries from XML.
setExceptionCallback( $exceptionCallback)
__construct(SiteStore $store)
makeSite(DOMElement $siteElement)
const SOURCE_LOCAL
Definition Site.php:45
static newForType( $siteType)
Definition Site.php:620
const TYPE_UNKNOWN
Definition Site.php:37
Interface for storing and retrieving Site objects.
Definition SiteStore.php:32