This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | declare( strict_types = 1 ); |
||
4 | |||
5 | namespace Wikibase\Repo\Api; |
||
6 | |||
7 | use ApiBase; |
||
8 | use ApiMain; |
||
9 | use Site; |
||
10 | use SiteList; |
||
11 | use Status; |
||
12 | use Wikibase\DataModel\Entity\Item; |
||
13 | use Wikibase\DataModel\SiteLink; |
||
14 | use Wikibase\DataModel\SiteLinkList; |
||
15 | use Wikibase\Lib\Store\EntityRevisionLookup; |
||
16 | use Wikibase\Lib\Store\LookupConstants; |
||
17 | use Wikibase\Lib\Summary; |
||
18 | use Wikibase\Repo\SiteLinkTargetProvider; |
||
19 | use Wikibase\Repo\Store\Store; |
||
20 | use Wikibase\Repo\WikibaseRepo; |
||
21 | |||
22 | /** |
||
23 | * API module to associate two pages on two different sites with a Wikibase item. |
||
24 | * Requires API write mode to be enabled. |
||
25 | * |
||
26 | * @license GPL-2.0-or-later |
||
27 | * @author John Erling Blad < [email protected] > |
||
28 | * @author Addshore |
||
29 | */ |
||
30 | class LinkTitles extends ApiBase { |
||
31 | |||
32 | /** |
||
33 | * @var SiteLinkTargetProvider |
||
34 | */ |
||
35 | private $siteLinkTargetProvider; |
||
36 | |||
37 | /** |
||
38 | * @var ApiErrorReporter |
||
39 | */ |
||
40 | private $errorReporter; |
||
41 | |||
42 | /** |
||
43 | * @var string[] |
||
44 | */ |
||
45 | private $siteLinkGroups; |
||
46 | |||
47 | /** |
||
48 | * @var EntityRevisionLookup |
||
49 | */ |
||
50 | private $revisionLookup; |
||
51 | |||
52 | /** |
||
53 | * @var ResultBuilder |
||
54 | */ |
||
55 | private $resultBuilder; |
||
56 | |||
57 | /** |
||
58 | * @var EntitySavingHelper |
||
59 | */ |
||
60 | private $entitySavingHelper; |
||
61 | |||
62 | public function __construct( |
||
63 | ApiMain $mainModule, |
||
64 | string $moduleName, |
||
65 | SiteLinkTargetProvider $siteLinkTargetProvider, |
||
66 | ApiErrorReporter $errorReporter, |
||
67 | array $siteLinkGroups, |
||
68 | EntityRevisionLookup $revisionLookup, |
||
69 | callable $resultBuilderInstantiator, |
||
70 | callable $entitySavingHelperInstantiator |
||
71 | ) { |
||
72 | parent::__construct( $mainModule, $moduleName ); |
||
73 | |||
74 | $this->siteLinkTargetProvider = $siteLinkTargetProvider; |
||
75 | $this->errorReporter = $errorReporter; |
||
76 | $this->siteLinkGroups = $siteLinkGroups; |
||
77 | $this->revisionLookup = $revisionLookup; |
||
78 | $this->resultBuilder = $resultBuilderInstantiator( $this ); |
||
79 | $this->entitySavingHelper = $entitySavingHelperInstantiator( $this ); |
||
80 | } |
||
81 | |||
82 | public static function factory( ApiMain $mainModule, string $moduleName ): self { |
||
83 | $wikibaseRepo = WikibaseRepo::getDefaultInstance(); |
||
84 | $settings = $wikibaseRepo->getSettings(); |
||
85 | $apiHelperFactory = $wikibaseRepo->getApiHelperFactory( $mainModule->getContext() ); |
||
86 | |||
87 | $siteLinkTargetProvider = new SiteLinkTargetProvider( |
||
88 | $wikibaseRepo->getSiteLookup(), |
||
89 | $settings->getSetting( 'specialSiteLinkGroups' ) |
||
90 | ); |
||
91 | |||
92 | return new self( |
||
93 | $mainModule, |
||
94 | $moduleName, |
||
95 | $siteLinkTargetProvider, |
||
96 | $apiHelperFactory->getErrorReporter( $mainModule ), |
||
97 | $settings->getSetting( 'siteLinkGroups' ), |
||
98 | $wikibaseRepo->getEntityRevisionLookup( Store::LOOKUP_CACHING_DISABLED ), |
||
99 | function ( $module ) use ( $apiHelperFactory ) { |
||
100 | return $apiHelperFactory->getResultBuilder( $module ); |
||
101 | }, |
||
102 | function ( $module ) use ( $apiHelperFactory ) { |
||
103 | return $apiHelperFactory->getEntitySavingHelper( $module ); |
||
104 | } |
||
105 | ); |
||
106 | } |
||
107 | |||
108 | /** |
||
109 | * Main method. Does the actual work and sets the result. |
||
110 | */ |
||
111 | public function execute(): void { |
||
112 | $lookup = $this->revisionLookup; |
||
113 | |||
114 | $params = $this->extractRequestParams(); |
||
115 | $this->validateParameters( $params ); |
||
116 | |||
117 | // Sites are already tested through allowed params ;) |
||
118 | $sites = $this->siteLinkTargetProvider->getSiteList( $this->siteLinkGroups ); |
||
119 | |||
120 | /** @var Site $fromSite */ |
||
121 | list( $fromSite, $fromPage ) = $this->getSiteAndNormalizedPageName( |
||
122 | $sites, |
||
123 | $params['fromsite'], |
||
124 | $params['fromtitle'] |
||
125 | ); |
||
126 | /** @var Site $toSite */ |
||
127 | list( $toSite, $toPage ) = $this->getSiteAndNormalizedPageName( |
||
128 | $sites, |
||
129 | $params['tosite'], |
||
130 | $params['totitle'] |
||
131 | ); |
||
132 | |||
133 | $siteLinkStore = WikibaseRepo::getDefaultInstance()->getStore()->newSiteLinkStore(); |
||
134 | $fromId = $siteLinkStore->getItemIdForLink( $fromSite->getGlobalId(), $fromPage ); |
||
135 | $toId = $siteLinkStore->getItemIdForLink( $toSite->getGlobalId(), $toPage ); |
||
136 | |||
137 | $siteLinkList = new SiteLinkList(); |
||
138 | $flags = 0; |
||
139 | $item = null; |
||
140 | |||
141 | $summary = new Summary( $this->getModuleName() ); |
||
142 | $summary->addAutoSummaryArgs( |
||
143 | $fromSite->getGlobalId() . ':' . $fromPage, |
||
144 | $toSite->getGlobalId() . ':' . $toPage ); |
||
145 | |||
146 | //FIXME: use ChangeOps for consistency! |
||
147 | |||
148 | // Figure out which parts to use and what to create anew |
||
149 | if ( $fromId === null && $toId === null ) { |
||
150 | // create new item |
||
151 | $item = new Item(); |
||
152 | $toLink = new SiteLink( $toSite->getGlobalId(), $toPage ); |
||
153 | $item->addSiteLink( $toLink ); |
||
154 | $siteLinkList->addSiteLink( $toLink ); |
||
155 | $fromLink = new SiteLink( $fromSite->getGlobalId(), $fromPage ); |
||
156 | $item->addSiteLink( $fromLink ); |
||
157 | $siteLinkList->addSiteLink( $fromLink ); |
||
158 | |||
159 | $flags |= EDIT_NEW; |
||
160 | $summary->setAction( 'create' ); |
||
161 | } elseif ( $fromId === null && $toId !== null ) { |
||
162 | // reuse to-site's item |
||
163 | /** @var Item $item */ |
||
164 | $itemRev = $lookup->getEntityRevision( $toId, 0, LookupConstants::LATEST_FROM_MASTER ); |
||
165 | $item = $itemRev->getEntity(); |
||
166 | '@phan-var Item $item'; |
||
167 | $fromLink = new SiteLink( $fromSite->getGlobalId(), $fromPage ); |
||
168 | $item->addSiteLink( $fromLink ); |
||
169 | $siteLinkList->addSiteLink( $fromLink ); |
||
170 | $summary->setAction( 'connect' ); |
||
171 | } elseif ( $fromId !== null && $toId === null ) { |
||
172 | // reuse from-site's item |
||
173 | /** @var Item $item */ |
||
174 | $itemRev = $lookup->getEntityRevision( $fromId, 0, LookupConstants::LATEST_FROM_MASTER ); |
||
175 | $item = $itemRev->getEntity(); |
||
176 | '@phan-var Item $item'; |
||
177 | $toLink = new SiteLink( $toSite->getGlobalId(), $toPage ); |
||
178 | $item->addSiteLink( $toLink ); |
||
179 | $siteLinkList->addSiteLink( $toLink ); |
||
180 | $summary->setAction( 'connect' ); |
||
181 | } elseif ( $fromId->equals( $toId ) ) { |
||
182 | // no-op |
||
183 | $this->errorReporter->dieError( 'Common item detected, sitelinks are both on the same item', 'common-item' ); |
||
0 ignored issues
–
show
|
|||
184 | } else { |
||
185 | // dissimilar items |
||
186 | $this->errorReporter->dieError( 'No common item detected, unable to link titles', 'no-common-item' ); |
||
0 ignored issues
–
show
The method
Wikibase\Repo\Api\ApiErrorReporter::dieError() has been deprecated with message: Use dieWithError() instead.
This method has been deprecated. The supplier of the class has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead. ![]() |
|||
187 | } |
||
188 | |||
189 | $this->resultBuilder->addSiteLinkList( $siteLinkList, 'entity' ); |
||
190 | $status = $this->getAttemptSaveStatus( $item, $summary, $flags ); |
||
191 | $this->buildResult( $item, $status ); |
||
192 | } |
||
193 | |||
194 | /** |
||
195 | * @param SiteList $sites |
||
196 | * @param string $site |
||
197 | * @param string $pageTitle |
||
198 | * |
||
199 | * @return array ( Site $site, string $pageName ) |
||
200 | * @phan-return array{0:Site,1:string} |
||
201 | */ |
||
202 | private function getSiteAndNormalizedPageName( SiteList $sites, string $site, string $pageTitle ): array { |
||
203 | $siteObj = $sites->getSite( $site ); |
||
204 | $page = $siteObj->normalizePageName( $pageTitle ); |
||
205 | if ( $page === false ) { |
||
206 | $this->errorReporter->dieWithError( |
||
207 | [ 'wikibase-api-no-external-page', $site, $pageTitle ], |
||
208 | 'no-external-page' |
||
209 | ); |
||
210 | } |
||
211 | |||
212 | return [ $siteObj, $page ]; |
||
213 | } |
||
214 | |||
215 | private function getAttemptSaveStatus( ?Item $item, Summary $summary, int $flags ): Status { |
||
216 | if ( $item === null ) { |
||
217 | // to not have an Item isn't really bad at this point |
||
218 | return Status::newGood( true ); |
||
219 | } else { |
||
220 | // Do the actual save, or if it don't exist yet create it. |
||
221 | return $this->entitySavingHelper->attemptSaveEntity( $item, $summary, $flags ); |
||
222 | } |
||
223 | } |
||
224 | |||
225 | private function buildResult( ?Item $item, Status $status ): void { |
||
226 | if ( $item !== null ) { |
||
227 | $this->resultBuilder->addRevisionIdFromStatusToResult( $status, 'entity' ); |
||
228 | $this->resultBuilder->addBasicEntityInformation( $item->getId(), 'entity' ); |
||
229 | } |
||
230 | |||
231 | $this->resultBuilder->markSuccess( $status->isOK() ); |
||
232 | } |
||
233 | |||
234 | /** |
||
235 | * @see ModifyEntity::validateParameters |
||
236 | * |
||
237 | * @param array $params |
||
238 | */ |
||
239 | protected function validateParameters( array $params ): void { |
||
240 | if ( $params['fromsite'] === $params['tosite'] ) { |
||
241 | $this->errorReporter->dieError( 'The from site cannot match the to site', 'param-illegal' ); |
||
0 ignored issues
–
show
The method
Wikibase\Repo\Api\ApiErrorReporter::dieError() has been deprecated with message: Use dieWithError() instead.
This method has been deprecated. The supplier of the class has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead. ![]() |
|||
242 | } |
||
243 | } |
||
244 | |||
245 | /** |
||
246 | * @inheritDoc |
||
247 | */ |
||
248 | public function isWriteMode(): bool { |
||
249 | return true; |
||
250 | } |
||
251 | |||
252 | /** |
||
253 | * @see ApiBase::needsToken |
||
254 | * |
||
255 | * @return string |
||
256 | */ |
||
257 | public function needsToken(): string { |
||
258 | return 'csrf'; |
||
259 | } |
||
260 | |||
261 | /** |
||
262 | * @inheritDoc |
||
263 | */ |
||
264 | protected function getAllowedParams(): array { |
||
265 | $sites = $this->siteLinkTargetProvider->getSiteList( $this->siteLinkGroups ); |
||
266 | |||
267 | return array_merge( parent::getAllowedParams(), [ |
||
268 | 'tosite' => [ |
||
269 | self::PARAM_TYPE => $sites->getGlobalIdentifiers(), |
||
270 | self::PARAM_REQUIRED => true, |
||
271 | ], |
||
272 | 'totitle' => [ |
||
273 | self::PARAM_TYPE => 'string', |
||
274 | self::PARAM_REQUIRED => true, |
||
275 | ], |
||
276 | 'fromsite' => [ |
||
277 | self::PARAM_TYPE => $sites->getGlobalIdentifiers(), |
||
278 | self::PARAM_REQUIRED => true, |
||
279 | ], |
||
280 | 'fromtitle' => [ |
||
281 | self::PARAM_TYPE => 'string', |
||
282 | self::PARAM_REQUIRED => true, |
||
283 | ], |
||
284 | 'token' => null, |
||
285 | 'bot' => false, |
||
286 | ] ); |
||
287 | } |
||
288 | |||
289 | /** |
||
290 | * @inheritDoc |
||
291 | */ |
||
292 | protected function getExamplesMessages(): array { |
||
293 | return [ |
||
294 | 'action=wblinktitles&fromsite=enwiki&fromtitle=Hydrogen&tosite=dewiki&totitle=Wasserstoff' |
||
295 | => 'apihelp-wblinktitles-example-1', |
||
296 | ]; |
||
297 | } |
||
298 | |||
299 | } |
||
300 |
This method has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.