/** * 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; }
/** * Returns a EntityChange based on the old and new content object, taking * redirects into consideration. * * @todo: Notify the client about changes to redirects explicitly. * * @param EntityContent $oldContent * @param EntityContent $newContent * * @return EntityChange|null */ private function getChangeForModification(EntityContent $oldContent, EntityContent $newContent) { $oldEntity = $oldContent->isRedirect() ? null : $oldContent->getEntity(); $newEntity = $newContent->isRedirect() ? null : $newContent->getEntity(); if ($oldEntity === null && $newEntity === null) { // Old and new versions are redirects. Nothing to do. return null; } elseif ($newEntity === null) { // The new version is a redirect. For now, treat that as a deletion. $action = EntityChange::REMOVE; } elseif ($oldEntity === null) { // The old version is a redirect. For now, treat that like restoring the entity. $action = EntityChange::RESTORE; } else { // No redirects involved $action = EntityChange::UPDATE; } $change = $this->changeFactory->newFromUpdate($action, $oldEntity, $newEntity); return $change; }