/**
  * 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;
 }