public static XDoc RestoreDeletedPage(uint pageid, Title newRootPath, string revertReason) { //Retrieve initial revisions of pages to restore //First item in the list is the page that initiated the transaction. //Talk pages are included. uint initialDeleteTranId = 0; IList <ArchiveBE> pagesToRestore = DbUtils.CurrentSession.Archive_GetPagesInTransaction(pageid, out initialDeleteTranId); TransactionBE initialDeleteTrans = DbUtils.CurrentSession.Transactions_GetById(initialDeleteTranId); //Validate deleted page + transaction if (pagesToRestore.Count == 0) { throw new PageArchiveLogicNotFoundException(pageid); } if (initialDeleteTrans == null) { throw new PageArchiveBadTransactionFatalException(initialDeleteTranId, pageid); } //TODO MaxM: move the above gathering of what pages to restore to another method. Make this private. //Look for title conflicts List <Title> titles = new List <Title>(); List <ulong> pageidsToRestore = new List <ulong>(); Dictionary <ulong, PageBE> restoredPagesById = null; DateTime utcTimestamp = DateTime.UtcNow; foreach (ArchiveBE p in pagesToRestore) { Title t = p.Title; if (newRootPath != null) { t = BuildNewTitlesForMovedPage(pagesToRestore[0].Title, p.Title, newRootPath); } titles.Add(t); pageidsToRestore.Add(p.LastPageId); } IList <PageBE> currentPages = DbUtils.CurrentSession.Pages_GetByTitles(titles.ToArray()); if (currentPages.Count > 0) { //Remove all conflicting redirect pages from target of restore if all conflicting pages are redirects List <PageBE> conflictingRedirects = new List <PageBE>(); foreach (PageBE p in currentPages) { if (p.IsRedirect) { conflictingRedirects.Add(p); } } if (currentPages.Count == conflictingRedirects.Count && conflictingRedirects.Count > 0) { //Remove existing redirects and refresh the conflicting pages list PageBL.DeletePages(conflictingRedirects.ToArray(), utcTimestamp, 0, false); currentPages = DbUtils.CurrentSession.Pages_GetByTitles(titles.ToArray()); } } if (currentPages.Count > 0) { //return the name(s) of the conflicting page title(s) StringBuilder conflictTitles = new StringBuilder(); foreach (PageBE p in currentPages) { if (conflictTitles.Length > 0) { conflictTitles.Append(", "); } conflictTitles.Append(p.Title.AsPrefixedUserFriendlyPath()); } throw new PageArchiveRestoreNamedPageConflictException(conflictTitles.ToString()); } //Gather revisions for all pages to be restored. //Revisions are sorted by timestamp: oldest first. Dictionary <ulong, IList <ArchiveBE> > revisionsByPageId = DbUtils.CurrentSession.Archive_GetRevisionsByPageIds(pageidsToRestore); uint restoredPageTranId = 0; try { TransactionBE newTrans = new TransactionBE(); newTrans.UserId = DekiContext.Current.User.ID; newTrans.PageId = pageid; newTrans.Title = pagesToRestore[0].Title; newTrans.Type = RC.PAGERESTORED; newTrans.TimeStamp = DateTime.UtcNow; restoredPageTranId = DbUtils.CurrentSession.Transactions_Insert(newTrans); //Pages must be restored in correct order (alphabetical ensures parent pages are restored before children). //pagesToRestore must be in alphabetical title order bool minorChange = false; foreach (ArchiveBE pageToRestore in pagesToRestore) { IList <ArchiveBE> revisions = null; if (revisionsByPageId.TryGetValue(pageToRestore.LastPageId, out revisions)) { //Optionally restore page to different title Title restoreToTitle = pageToRestore.Title; if (newRootPath != null) { restoreToTitle = BuildNewTitlesForMovedPage(pagesToRestore[0].Title, pageToRestore.Title, newRootPath); } RestorePageRevisionsForPage(revisions.ToArray(), restoreToTitle, restoredPageTranId, minorChange, utcTimestamp); DbUtils.CurrentSession.Archive_Delete(revisions.Select(e => e.Id).ToList()); } minorChange = true; } //Retrieve the restored pages restoredPagesById = PageBL.GetPagesByIdsPreserveOrder(pageidsToRestore).AsHash(e => e.ID); // Restore attachments IList <ResourceBE> attachmentsToRestore = ResourceBL.Instance.GetResourcesByChangeSet(initialDeleteTrans.Id, ResourceBE.Type.FILE); foreach (ResourceBE at in attachmentsToRestore) { PageBE restoredPage; if (restoredPagesById.TryGetValue(at.ParentPageId.Value, out restoredPage)) { AttachmentBL.Instance.RestoreAttachment(at, restoredPage, utcTimestamp, restoredPageTranId); } } //Update the old transaction as reverted initialDeleteTrans.Reverted = true; initialDeleteTrans.RevertTimeStamp = utcTimestamp; initialDeleteTrans.RevertUserId = DekiContext.Current.User.ID; initialDeleteTrans.RevertReason = revertReason; DbUtils.CurrentSession.Transactions_Update(initialDeleteTrans); } catch (Exception) { DbUtils.CurrentSession.Transactions_Delete(restoredPageTranId); throw; } //Build restore summary XDoc ret = new XDoc("pages.restored"); foreach (ulong restoredPageId in pageidsToRestore) { PageBE restoredPage = null; if (restoredPagesById.TryGetValue((uint)restoredPageId, out restoredPage)) { ret.Add(PageBL.GetPageXml(restoredPage, string.Empty)); } } return(ret); }