/** * @see EntityLookup::hasEntity * * @param EntityId $entityId * * @throws EntityLookupException * @return bool */ public function hasEntity(EntityId $entityId) { try { return $this->lookup->getLatestRevisionId($entityId) !== false; } catch (EntityLookupException $ex) { throw $ex; } catch (\Exception $ex) { // TODO: catch more specific exception once EntityRevisionLookup contract gets clarified throw new EntityLookupException($entityId, null, $ex); } }
/** * @param PropertyId $propertyId * @param User $user User to attribute the changes made to. * @param string $dataTypeId * * @throws InvalidArgumentException * @throws StorageException */ public function changeDataType(PropertyId $propertyId, User $user, $dataTypeId) { $entityRevision = $this->entityRevisionLookup->getEntityRevision($propertyId, EntityRevisionLookup::LATEST_FROM_MASTER); if ($entityRevision === null) { throw new StorageException("Could not load property: " . $propertyId->getSerialization()); } /* @var $property Property */ $property = $entityRevision->getEntity(); $oldDataTypeId = $property->getDataTypeId(); $this->assertDataTypesCompatible($oldDataTypeId, $dataTypeId); $property->setDataTypeId($dataTypeId); $this->entityStore->saveEntity($property, 'Changed data type from ' . $oldDataTypeId . ' to ' . $dataTypeId, $user, EDIT_UPDATE, $entityRevision->getRevisionId()); }
/** * Get the entity using the id, site and title params passed to the api * * @param array $params * * @return EntityRevision Found existing entity */ protected function getEntityRevisionFromApiParams(array $params) { $entityRevision = null; $entityId = $this->getEntityIdFromParams($params); // Things that use this method assume null means we want a new entity if ($entityId !== null) { $baseRevisionId = isset($params['baserevid']) ? (int) $params['baserevid'] : 0; if ($baseRevisionId === 0) { $baseRevisionId = EntityRevisionLookup::LATEST_FROM_MASTER; } try { $entityRevision = $this->revisionLookup->getEntityRevision($entityId, $baseRevisionId); } catch (EntityLookupException $ex) { $this->errorReporter->dieException($ex, 'no-such-entity'); } catch (StorageException $ex) { // @fixme EntityRevisionLookup still throws BadRevisionException, which // is a subclass of StorageException, so we still have some inconsistency // and need to check both. $this->errorReporter->dieException($ex, 'no-such-entity'); } if ($entityRevision === null) { $this->errorReporter->dieError("Can't access entity " . $entityId . ', revision may have been deleted.', 'no-such-entity'); } } return $entityRevision; }
/** * Loads the requested Entity. Redirects are resolved if no specific revision * is requested. * * @param EntityId $id * @param int $revision The revision ID (use 0 for the current revision). * * @return array list( EntityRevision, RedirectRevision|null ) * @throws HttpError */ private function getEntityRevision(EntityId $id, $revision) { $prefixedId = $id->getSerialization(); if ($revision === 0) { $revision = EntityRevisionLookup::LATEST_FROM_SLAVE; } $redirectRevision = null; try { $entityRevision = $this->entityRevisionLookup->getEntityRevision($id, $revision); if ($entityRevision === null) { wfDebugLog(__CLASS__, __FUNCTION__ . ": entity not found: {$prefixedId}"); throw new HttpError(404, wfMessage('wikibase-entitydata-not-found')->params($prefixedId)); } } catch (RevisionedUnresolvedRedirectException $ex) { $redirectRevision = new RedirectRevision(new EntityRedirect($id, $ex->getRedirectTargetId()), $ex->getRevisionId(), $ex->getRevisionTimestamp()); if (is_string($revision)) { // If no specific revision is requested, resolve the redirect. list($entityRevision, ) = $this->getEntityRevision($ex->getRedirectTargetId(), $revision); } else { // The requested revision is a redirect wfDebugLog(__CLASS__, __FUNCTION__ . ": revision {$revision} of {$prefixedId} is a redirect: {$ex}"); $msg = wfMessage('wikibase-entitydata-bad-revision'); throw new HttpError(400, $msg->params($prefixedId, $revision)); } } catch (BadRevisionException $ex) { wfDebugLog(__CLASS__, __FUNCTION__ . ": could not load revision {$revision} or {$prefixedId}: {$ex}"); $msg = wfMessage('wikibase-entitydata-bad-revision'); throw new HttpError(404, $msg->params($prefixedId, $revision)); } catch (StorageException $ex) { wfDebugLog(__CLASS__, __FUNCTION__ . ": failed to load {$prefixedId}: {$ex} (revision {$revision})"); $msg = wfMessage('wikibase-entitydata-storage-error'); throw new HttpError(500, $msg->params($prefixedId, $revision)); } return array($entityRevision, $redirectRevision); }
/** * @see ModifyEntity::modifyEntity */ protected function modifyEntity(EntityDocument &$entity, array $params, $baseRevId) { $this->validateDataParameter($params); $data = json_decode($params['data'], true); $this->validateDataProperties($data, $entity, $baseRevId); $exists = $this->entityExists($entity->getId()); if ($params['clear']) { if ($params['baserevid'] && $exists) { $latestRevision = $this->revisionLookup->getLatestRevisionId($entity->getId(), EntityRevisionLookup::LATEST_FROM_MASTER); if (!$baseRevId === $latestRevision) { $this->errorReporter->dieError('Tried to clear entity using baserevid of entity not equal to current revision', 'editconflict'); } } $entity = $this->clearEntity($entity); } // if we create a new property, make sure we set the datatype if (!$exists && $entity instanceof Property) { if (!isset($data['datatype'])) { $this->errorReporter->dieError('No datatype given', 'param-illegal'); } else { $entity->setDataTypeId($data['datatype']); } } $changeOps = $this->getChangeOps($data, $entity); $this->applyChangeOp($changeOps, $entity); $this->buildResult($entity); return $this->getSummary($params); }
/** * Load the entity content of the given revision. * * Will fail by calling dieException() $this->errorReporter if the revision * cannot be found or cannot be loaded. * * @since 0.5 * * @param EntityId $entityId EntityId of the page to load the revision for * @param int|string $revId revision to load. If not given, the current revision will be loaded. * * @throws UsageException * @throws LogicException * * @return EntityRevision */ public function loadEntityRevision(EntityId $entityId, $revId = EntityRevisionLookup::LATEST_FROM_MASTER) { try { $revision = $this->entityRevisionLookup->getEntityRevision($entityId, $revId); if (!$revision) { $this->errorReporter->dieError('Entity ' . $entityId->getSerialization() . ' not found', 'cant-load-entity-content'); } return $revision; } catch (RevisionedUnresolvedRedirectException $ex) { $this->errorReporter->dieException($ex, 'unresolved-redirect'); } catch (BadRevisionException $ex) { $this->errorReporter->dieException($ex, 'nosuchrevid'); } catch (StorageException $ex) { $this->errorReporter->dieException($ex, 'cant-load-entity-content'); } throw new LogicException('ApiErrorReporter::dieException did not throw a UsageException'); }
/** * @param EntityId $entityId * @param bool $resolveRedirects * * @return null|EntityRevision */ private function getEntityRevision(EntityId $entityId, $resolveRedirects = false) { $entityRevision = null; try { $entityRevision = $this->entityRevisionLookup->getEntityRevision($entityId); } catch (RevisionedUnresolvedRedirectException $ex) { if ($resolveRedirects) { $entityId = $ex->getRedirectTargetId(); $entityRevision = $this->getEntityRevision($entityId, false); } } return $entityRevision; }
/** * Either throws an exception or returns a EntityDocument object. * * @param ItemId $itemId * * @return EntityDocument * @throws ItemMergeException */ private function loadEntity(ItemId $itemId) { try { $revision = $this->entityRevisionLookup->getEntityRevision($itemId, EntityRevisionLookup::LATEST_FROM_MASTER); if (!$revision) { throw new ItemMergeException("Entity {$itemId} not found", 'no-such-entity'); } return $revision->getEntity(); } catch (StorageException $ex) { throw new ItemMergeException($ex->getMessage(), 'cant-load-entity-content', $ex); } catch (RevisionedUnresolvedRedirectException $ex) { throw new ItemMergeException($ex->getMessage(), 'cant-load-entity-content', $ex); } }
/** * Loads the entity for this entity id. * * @since 0.5 * * @param EntityId $id * @param int|string $revisionId * * @throws MessageException * @throws UserInputException * @return EntityRevision */ protected function loadEntity(EntityId $id, $revisionId = EntityRevisionLookup::LATEST_FROM_MASTER) { try { $entity = $this->entityRevisionLookup->getEntityRevision($id, $revisionId); if ($entity === null) { throw new UserInputException('wikibase-wikibaserepopage-invalid-id', array($id->getSerialization()), 'Entity id is unknown'); } } catch (RevisionedUnresolvedRedirectException $ex) { throw new UserInputException('wikibase-wikibaserepopage-unresolved-redirect', array($id->getSerialization()), 'Entity id refers to a redirect'); } catch (StorageException $ex) { throw new MessageException('wikibase-wikibaserepopage-storage-exception', array($id->getSerialization(), $ex->getMessage()), 'Entity could not be loaded'); } return $entity; }
/** * @see EntityRevisionLookup::getLatestRevisionId * * @note: If this lookup is configured to verify revisions, this just delegates * to the underlying lookup. Otherwise, it may return the ID of a cached * revision. * * @param EntityId $entityId * @param string $mode * * @return int|false */ public function getLatestRevisionId(EntityId $entityId, $mode = self::LATEST_FROM_SLAVE) { // If we do not need to verify the revision, and the revision isn't // needed for an update, we can get the revision from the cached object. // XXX: whether this is actually quicker depends on the cache. if (!($this->shouldVerifyRevision || $mode === self::LATEST_FROM_MASTER)) { $key = $this->getCacheKey($entityId); /** @var EntityRevision $entityRevision */ $entityRevision = $this->cache->get($key); if ($entityRevision) { return $entityRevision->getRevisionId(); } } return $this->lookup->getLatestRevisionId($entityId, $mode); }
/** * @param EntityId $entityId * * @throws RedirectCreationException */ private function checkEmpty(EntityId $entityId) { try { $revision = $this->entityRevisionLookup->getEntityRevision($entityId, EntityRevisionLookup::LATEST_FROM_MASTER); if (!$revision) { throw new RedirectCreationException("Entity {$entityId} not found", 'no-such-entity'); } $entity = $revision->getEntity(); if (!$entity->isEmpty()) { throw new RedirectCreationException("Entity {$entityId} is not empty", 'target-not-empty'); } } catch (RevisionedUnresolvedRedirectException $ex) { // Nothing to do. It's ok to override a redirect with a redirect. } catch (StorageException $ex) { throw new RedirectCreationException($ex->getMessage(), 'cant-load-entity-content', $ex); } }
/** * @see EntityInfoBuilder::removeMissing */ public function removeMissing($redirects = 'keep-redirects') { foreach (array_keys($this->entityInfo) as $key) { $id = $this->parseId($key); try { $rev = $this->entityRevisionLookup->getEntityRevision($id); } catch (RevisionedUnresolvedRedirectException $ex) { if ($redirects === 'keep-redirects') { continue; } else { $rev = null; } } if (!$rev) { unset($this->entityInfo[$key]); } } }
/** * Combines a set of changes into one change. All changes are assume to have been performed * by the same user on the same entity. They are further assumed to be UPDATE actions * and sorted in causal (chronological) order. * * If $changes is empty, this method returns null. If $changes contains exactly one change, * that change is returned. Otherwise, a combined change is returned. * * @param EntityChange[] $changes The changes to combine. * * @throws MWException * @return Change a combined change representing the activity from all the original changes. */ private function mergeChanges(array $changes) { if (empty($changes)) { return null; } elseif (count($changes) === 1) { return reset($changes); } // we now assume that we have a list if EntityChanges, // all done by the same user on the same entity. /* @var EntityChange $last */ /* @var EntityChange $first */ $last = end($changes); $first = reset($changes); $minor = true; $bot = true; $ids = array(); foreach ($changes as $change) { $ids[] = $change->getId(); $meta = $change->getMetadata(); $minor &= isset($meta['minor']) && (bool) $meta['minor']; $bot &= isset($meta['bot']) && (bool) $meta['bot']; } $lastmeta = $last->getMetadata(); $firstmeta = $first->getMetadata(); $entityId = $first->getEntityId(); $parentRevId = $firstmeta['parent_id']; $latestRevId = $lastmeta['rev_id']; $entityRev = $this->entityRevisionLookup->getEntityRevision($entityId, $latestRevId); if (!$entityRev) { throw new MWException("Failed to load revision {$latestRevId} of {$entityId}"); } $parentRev = $parentRevId ? $this->entityRevisionLookup->getEntityRevision($entityId, $parentRevId) : null; //XXX: we could avoid loading the entity data by merging the diffs programatically // instead of re-calculating. $change = $this->changeFactory->newFromUpdate($parentRev ? EntityChange::UPDATE : EntityChange::ADD, $parentRev === null ? null : $parentRev->getEntity(), $entityRev->getEntity()); $change->setFields(array('revision_id' => $last->getField('revision_id'), 'user_id' => $last->getField('user_id'), 'object_id' => $last->getField('object_id'), 'time' => $last->getField('time'))); $change->setMetadata(array_merge($lastmeta, array('parent_id' => $parentRevId, 'minor' => $minor, 'bot' => $bot))); $info = $change->hasField('info') ? $change->getField('info') : array(); $info['change-ids'] = $ids; $info['changes'] = $changes; $change->setField('info', $info); return $change; }
/** * Generates HTML of the term box, to be injected into the entity page. * * @param EntityId $entityId * @param int $revisionId * * @throws InvalidArgumentException * @return string HTML */ public function renderTermBox(EntityId $entityId, $revisionId) { $languages = array_merge(array($this->uiLanguage->getCode()), $this->getExtraUserLanguages()); try { // we may want to cache this... $entityRev = $this->entityRevisionLookup->getEntityRevision($entityId, $revisionId); } catch (StorageException $ex) { // Could not load entity revision, $revisionId might be a deleted revision return ''; } if (!$entityRev) { // Could not load entity revision, entity might not exist for $entityId. return ''; } $entity = $entityRev->getEntity(); $entityTermsView = new EntityTermsView($this->templateFactory, null, $this->languageNameLookup, $this->uiLanguage->getCode()); // FIXME: assumes all entities have a fingerprint $html = $entityTermsView->getEntityTermsForLanguageListView($entity->getFingerprint(), $languages, $this->targetPage); return $html; }
/** * Produces RDF dump of the entity * * @param EntityId $entityId * * @throws EntityLookupException * @throws StorageException * @return string|null RDF */ protected function generateDumpForEntityId(EntityId $entityId) { try { $entityRevision = $this->entityRevisionLookup->getEntityRevision($entityId); if (!$entityRevision) { throw new EntityLookupException($entityId, 'Entity not found: ' . $entityId->getSerialization()); } $this->rdfBuilder->addEntityRevisionInfo($entityRevision->getEntity()->getId(), $entityRevision->getRevisionId(), $entityRevision->getTimestamp()); $this->rdfBuilder->addEntity($entityRevision->getEntity()); } catch (MWContentSerializationException $ex) { throw new StorageException('Deserialization error for ' . $entityId->getSerialization()); } catch (RevisionedUnresolvedRedirectException $e) { if ($e->getRevisionId() > 0) { $this->rdfBuilder->addEntityRevisionInfo($entityId, $e->getRevisionId(), $e->getRevisionTimestamp()); } $this->rdfBuilder->addEntityRedirect($entityId, $e->getRedirectTargetId()); } $rdf = $this->rdfBuilder->getRDF(); return $rdf; }
/** * @param EntityId $entityId * * @throws RedirectCreationException */ private function checkCanCreateRedirect(EntityId $entityId) { try { $revision = $this->entityRevisionLookup->getEntityRevision($entityId, EntityRevisionLookup::LATEST_FROM_MASTER); if (!$revision) { $title = $this->entityTitleLookup->getTitleForId($entityId); if (!$title || !$title->isDeleted()) { throw new RedirectCreationException("Couldn't get Title for {$entityId} or Title is not deleted", 'no-such-entity'); } } else { $entity = $revision->getEntity(); if (!$entity->isEmpty()) { throw new RedirectCreationException("Can't create redirect on non empty item {$entityId}", 'origin-not-empty'); } } } catch (RevisionedUnresolvedRedirectException $ex) { // Nothing to do. It's ok to override a redirect with a redirect. } catch (StorageException $ex) { throw new RedirectCreationException($ex->getMessage(), 'cant-load-entity-content', $ex); } }
/** * Returns the edits base revision. * If no base revision was supplied to the constructor, this will return null. * In the trivial non-conflicting case, this will be the same as $this->getLatestRevision(). * * @return EntityRevision|null * @throws MWException */ private function getBaseRevision() { if ($this->baseRev === null) { $baseRevId = $this->getBaseRevisionId(); if ($baseRevId === false) { return null; } elseif ($baseRevId === $this->getLatestRevisionId()) { $this->baseRev = $this->getLatestRevision(); } else { if ($baseRevId === 0) { $baseRevId = EntityRevisionLookup::LATEST_FROM_MASTER; } $id = $this->newEntity->getId(); $this->baseRev = $this->entityRevisionLookup->getEntityRevision($id, $baseRevId); if ($this->baseRev === null) { throw new MWException('Base revision ID not found: rev ' . $baseRevId . ' of ' . $id->getSerialization()); } } } return $this->baseRev; }
/** * @return Item|null */ private function getItem() { $params = $this->getParams(); try { $itemId = new ItemId($params['entityId']); } catch (InvalidArgumentException $ex) { wfDebugLog('UpdateRepo', __FUNCTION__ . ": Invalid ItemId serialization " . $params['entityId'] . " given."); return null; } try { $entityRevision = $this->entityRevisionLookup->getEntityRevision($itemId, EntityRevisionLookup::LATEST_FROM_MASTER); } catch (StorageException $ex) { wfDebugLog('UpdateRepo', __FUNCTION__ . ": EntityRevision couldn't be loaded for " . $itemId->getSerialization() . ": " . $ex->getMessage()); return null; } if ($entityRevision) { return $entityRevision->getEntity(); } wfDebugLog('UpdateRepo', __FUNCTION__ . ": EntityRevision not found for " . $itemId->getSerialization()); return null; }