MediaWiki master
SiteImporter.php
Go to the documentation of this file.
1<?php
7namespace MediaWiki\Site;
8
9use DOMDocument;
10use DOMElement;
11use Exception;
12use InvalidArgumentException;
13use RuntimeException;
14use Wikimedia\RequestTimeout\TimeoutException;
15
26
30 private $store;
31
35 private $exceptionCallback;
36
37 public function __construct( SiteStore $store ) {
38 $this->store = $store;
39 }
40
44 public function getExceptionCallback() {
45 return $this->exceptionCallback;
46 }
47
51 public function setExceptionCallback( $exceptionCallback ) {
52 $this->exceptionCallback = $exceptionCallback;
53 }
54
58 public function importFromFile( $file ) {
59 $xml = file_get_contents( $file );
60
61 if ( $xml === false ) {
62 throw new RuntimeException( 'Failed to read ' . $file . '!' );
63 }
64
65 $this->importFromXML( $xml );
66 }
67
71 public function importFromXML( $xml ) {
72 $document = new DOMDocument();
73
74 $oldLibXmlErrors = libxml_use_internal_errors( true );
75 // phpcs:ignore Generic.PHP.NoSilencedErrors -- suppress deprecation per T268847
76 $oldDisable = @libxml_disable_entity_loader( true );
77 $ok = $document->loadXML( $xml, LIBXML_NONET );
78
79 if ( !$ok ) {
80 $errors = libxml_get_errors();
81 libxml_use_internal_errors( $oldLibXmlErrors );
82 // phpcs:ignore Generic.PHP.NoSilencedErrors
83 @libxml_disable_entity_loader( $oldDisable );
84
85 foreach ( $errors as $error ) {
87 throw new InvalidArgumentException(
88 'Malformed XML: ' . $error->message . ' in line ' . $error->line
89 );
90 }
91
92 throw new InvalidArgumentException( 'Malformed XML!' );
93 }
94
95 libxml_use_internal_errors( $oldLibXmlErrors );
96 // phpcs:ignore Generic.PHP.NoSilencedErrors
97 @libxml_disable_entity_loader( $oldDisable );
98 $sites = $this->makeSiteList( $document->documentElement );
99 $this->store->saveSites( $sites );
100 }
101
107 private function makeSiteList( DOMElement $root ) {
108 $sites = [];
109
110 // Old sites, to get the row IDs that correspond to the global site IDs.
111 // TODO: Get rid of internal row IDs, they just get in the way. Get rid of ORMRow, too.
112 $oldSites = $this->store->getSites();
113
114 $current = $root->firstChild;
115 while ( $current ) {
116 if ( $current instanceof DOMElement && $current->tagName === 'site' ) {
117 try {
118 $site = $this->makeSite( $current );
119 $key = $site->getGlobalId();
120
121 if ( $oldSites->hasSite( $key ) ) {
122 $oldSite = $oldSites->getSite( $key );
123 $site->setInternalId( $oldSite->getInternalId() );
124 }
125
126 $sites[$key] = $site;
127 } catch ( TimeoutException $e ) {
128 throw $e;
129 } catch ( Exception $ex ) {
130 $this->handleException( $ex );
131 }
132 }
133
134 $current = $current->nextSibling;
135 }
136
137 return $sites;
138 }
139
145 public function makeSite( DOMElement $siteElement ) {
146 if ( $siteElement->tagName !== 'site' ) {
147 throw new InvalidArgumentException( 'Expected <site> tag, found ' . $siteElement->tagName );
148 }
149
150 $type = $this->getAttributeValue( $siteElement, 'type', Site::TYPE_UNKNOWN );
151 $site = Site::newForType( $type );
152
153 $site->setForward( $this->hasChild( $siteElement, 'forward' ) );
154 $site->setGlobalId( $this->getChildText( $siteElement, 'globalid' ) );
155 $site->setGroup( $this->getChildText( $siteElement, 'group', Site::GROUP_NONE ) );
156 $site->setSource( $this->getChildText( $siteElement, 'source', Site::SOURCE_LOCAL ) );
157
158 $pathTags = $siteElement->getElementsByTagName( 'path' );
159 for ( $i = 0; $i < $pathTags->length; $i++ ) {
160 $pathElement = $pathTags->item( $i );
161 '@phan-var DOMElement $pathElement';
162 $pathType = $this->getAttributeValue( $pathElement, 'type' );
163 $path = $pathElement->textContent;
164
165 $site->setPath( $pathType, $path );
166 }
167
168 $idTags = $siteElement->getElementsByTagName( 'localid' );
169 for ( $i = 0; $i < $idTags->length; $i++ ) {
170 $idElement = $idTags->item( $i );
171 '@phan-var DOMElement $idElement';
172 $idType = $this->getAttributeValue( $idElement, 'type' );
173 $id = $idElement->textContent;
174
175 $site->addLocalId( $idType, $id );
176 }
177
178 // @todo: import <data>
179 // @todo: import <config>
180
181 return $site;
182 }
183
191 private function getAttributeValue( DOMElement $element, $name, $default = false ) {
192 $node = $element->getAttributeNode( $name );
193
194 if ( !$node ) {
195 if ( $default !== false ) {
196 return $default;
197 } else {
198 throw new RuntimeException(
199 'Required ' . $name . ' attribute not found in <' . $element->tagName . '> tag'
200 );
201 }
202 }
203
204 return $node->textContent;
205 }
206
214 private function getChildText( DOMElement $element, $name, $default = false ) {
215 $elements = $element->getElementsByTagName( $name );
216
217 if ( $elements->length < 1 ) {
218 if ( $default !== false ) {
219 return $default;
220 } else {
221 throw new RuntimeException(
222 'Required <' . $name . '> tag not found inside <' . $element->tagName . '> tag'
223 );
224 }
225 }
226
227 $node = $elements->item( 0 );
228 return $node->textContent;
229 }
230
237 private function hasChild( DOMElement $element, $name ) {
238 return $this->getChildText( $element, $name, null ) !== null;
239 }
240
241 private function handleException( Exception $ex ) {
242 if ( $this->exceptionCallback ) {
243 ( $this->exceptionCallback )( $ex );
244 } else {
245 wfLogWarning( $ex->getMessage() );
246 }
247 }
248
249}
250
252class_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:31
static newForType( $siteType)
Definition Site.php:595
const TYPE_UNKNOWN
Definition Site.php:23
Interface for storing and retrieving Site objects.
Definition SiteStore.php:18