public uint Transactions_Insert(TransactionBE trans) { return Catalog.NewQuery(@" /* Transactions_Insert */ insert into `transactions` (`t_timestamp`,`t_user_id`,`t_page_id`,`t_title`,`t_namespace`,`t_type`) values ( ?TS, ?USERID, ?PAGEID, ?TITLE, ?NS, ?TYPE); select last_insert_id();" ).With("TS", trans.TimeStamp) .With("USERID", trans.UserId) .With("PAGEID", trans.PageId) .With("TITLE", trans.Title.AsUnprefixedDbPath()) .With("NS", (int) trans.Title.Namespace) .With("TYPE", (int) trans.Type) .ReadAsUInt() ?? 0; }
//--- Methods --- public virtual TransactionBE Copy() { TransactionBE transaction = new TransactionBE(); transaction._Namespace = _Namespace; transaction._Title = _Title; transaction._Type = _Type; transaction.Id = Id; transaction.PageId = PageId; transaction.Reverted = Reverted; transaction.RevertReason = RevertReason; transaction.RevertTimeStamp = RevertTimeStamp; transaction.RevertUserId = RevertUserId; transaction.TimeStamp = TimeStamp; transaction.UserId = UserId; return(transaction); }
public void Transactions_Update(TransactionBE trans) { string revertuserid = null; if (trans.RevertUserId != null) revertuserid = trans.RevertUserId.Value.ToString(); DataCommand cmd = Catalog.NewQuery(@" /* Transactions_Update */ update `transactions` set `t_user_id`= ?USERID, `t_page_id`= ?PAGEID, `t_title`= ?TITLE, `t_namespace`= ?NS, `t_type`= ?TYPE, `t_reverted`= ?REVERTED, `t_revert_user_id` = ?REVERT_USERID, `t_revert_timestamp` = ?REVERT_TS, `t_revert_reason` = ?REVERT_REASON where `t_id`= ?TID;" ).With("TID", trans.Id) .With("USERID", trans.UserId) .With("PAGEID", trans.PageId) .With("TITLE", trans.Title.AsUnprefixedDbPath()) .With("NS", (int) trans.Title.Namespace) .With("TYPE", (int) trans.Type) .With("REVERTED", trans.Reverted) .With("REVERT_REASON", trans.RevertReason); //TODO MaxM: This is a workaround for Datacommand.with not taking nullables. if (trans.RevertUserId.HasValue) cmd = cmd.With("REVERT_USERID", trans.RevertUserId.Value); else cmd = cmd.With("REVERT_USERID", DBNull.Value); if (trans.RevertTimeStamp.HasValue) cmd = cmd.With("REVERT_TS", trans.RevertTimeStamp.Value); else cmd = cmd.With("REVERT_TS", DBNull.Value); cmd.Execute(); }
//--- Methods --- public virtual TransactionBE Copy() { TransactionBE transaction = new TransactionBE(); transaction._Namespace = _Namespace; transaction._Title = _Title; transaction._Type = _Type; transaction.Id = Id; transaction.PageId = PageId; transaction.Reverted = Reverted; transaction.RevertReason = RevertReason; transaction.RevertTimeStamp = RevertTimeStamp; transaction.RevertUserId = RevertUserId; transaction.TimeStamp = TimeStamp; transaction.UserId = UserId; return transaction; }
private TransactionBE Transactions_Populate(IDataReader dr) { TransactionBE transaction = new TransactionBE(); transaction._Namespace = dr.Read<ushort>("t_namespace"); transaction._Title = dr.Read<string>("t_title"); transaction._Type = dr.Read<uint>("t_type"); transaction.Id = dr.Read<uint>("t_id"); transaction.PageId = dr.Read<uint>("t_page_id"); transaction.Reverted = dr.Read<bool>("t_reverted"); transaction.RevertReason = dr.Read<string>("t_revert_reason"); transaction.RevertTimeStamp = dr.Read<DateTime?>("t_revert_timestamp"); transaction.RevertUserId = dr.Read<uint?>("t_revert_user_id"); transaction.TimeStamp = dr.Read<DateTime>("t_timestamp"); transaction.UserId = dr.Read<uint>("t_user_id"); return transaction; }
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 DreamAbortException(DreamMessage.NotFound(string.Format(DekiResources.RESTORE_PAGE_ID_NOT_FOUND, pageid))); } if (initialDeleteTrans == null) { throw new DreamAbortException(DreamMessage.InternalError(string.Format("Unable to find transaction id {0} for deleted page id {1}", 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 DreamAbortException(DreamMessage.Conflict(string.Format(DekiResources.CANNOT_RESTORE_PAGE_NAMED, 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<AttachmentBE> attachmentsToRestore = AttachmentBL.Instance.GetResourcesByChangeSet(initialDeleteTrans.Id); foreach (AttachmentBE at in attachmentsToRestore) { PageBE restoredPage = null; if (restoredPagesById.TryGetValue(at.ParentPageId, out restoredPage)) { at.ParentResource = new PageWrapperBE(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; }
public static IList<PageBE> MovePage(PageBE sourcePage, Title title) { //Get a list of pages (including child pages) //Can all pages be moved (Authorization/Namespace check/Homepage) //Does page exist at proposed title(s)? (including all child titles) // IsRedirect: Delete it! // Not a redirect: Exit with error. //Update page (title/ns/parent_id) and children (title/ns) //Update Olds, Archive (title + ns) //Write to RC //Update links IList<PageBE> ret = null; // page path did not change but display name is changed: just change the displayname if(sourcePage.Title == title && !StringUtil.EqualsInvariant(title.DisplayName, sourcePage.Title.DisplayName)) { // Path isn't being modified but only the displayname: Change the displayname and return. sourcePage = SetPageDisplayTitle(sourcePage, title.DisplayName); List<PageBE> r = new List<PageBE>(); r.Add(sourcePage); return r; } //Validation of move based on source page + destination title/ns PageBL.ValidatePageMoveForRootNode(sourcePage, title); //Retrieve all child pages of source page and associated talk pages, including source page List<PageBE> pagesToMoveList = new List<PageBE>(); List<PageBE> childRedirectsToRecreate = new List<PageBE>(); //Retrieve all descendant pages including redirects. Separate out the redirects to be deleted and readded later. foreach(PageBE p in GetDescendants(sourcePage, false)) { if(p.IsRedirect) { childRedirectsToRecreate.Add(p); } else { pagesToMoveList.Add(p); } } // Ensure that redirect text is populated since the page is deleted before it would be lazy loaded foreach(PageBE redirect in childRedirectsToRecreate) { redirect.GetText(DbUtils.CurrentSession); } pagesToMoveList.AddRange(GetTalkPages(pagesToMoveList)); PageBE[] pagesToMove = pagesToMoveList.ToArray(); //Determine new page titles for every moved page Dictionary<ulong, Title> newTitlesByPageId = BuildNewTitlesForMovedPages(sourcePage.Title, pagesToMove, title); //Will throw an unauthorized exception PermissionsBL.FilterDisallowed(DekiContext.Current.User, pagesToMove, true, Permissions.UPDATE); Title[] newTitles = new Title[newTitlesByPageId.Count]; newTitlesByPageId.Values.CopyTo(newTitles, 0); TransactionBE newTrans = new TransactionBE(); newTrans.UserId = DekiContext.Current.User.ID; newTrans.PageId = sourcePage.ID; newTrans.Title = sourcePage.Title; newTrans.Type = RC.MOVE; newTrans.TimeStamp = DateTime.UtcNow; uint transId = DbUtils.CurrentSession.Transactions_Insert(newTrans); try { //Target titles can only be redirects or talk pages (which are deleted before the move) EnsureTargetTitlesAvailableForMove(newTitles); //Retrieve or create the new parent page of the root node PageBE newParentPage = EnsureParent(sourcePage.IsRedirect, title); //Ensure user has CREATE access on the proposed new parent page PermissionsBL.CheckUserAllowed(DekiContext.Current.User, newParentPage, Permissions.CREATE); //Perform update on titles in pages and olds. parent id in pages is updated as well. DbUtils.CurrentSession.Pages_UpdateTitlesForMove(sourcePage.Title, newParentPage.Title.IsRoot ? 0 : newParentPage.ID, title, DreamContext.Current.StartTime); DbUtils.CurrentSession.Pages_UpdateTitlesForMove(sourcePage.Title.AsTalk(), 0, title.AsTalk(), DreamContext.Current.StartTime); // Perform displayname update to page after the location change if(!StringUtil.EqualsInvariant(title.DisplayName, sourcePage.Title.DisplayName)) { PageBE updatedRootPage = GetPageById(sourcePage.ID); if(updatedRootPage != null) { SetPageDisplayTitle(updatedRootPage, title.DisplayName); } } // Build return value from current state in the db List<PageBE> movedPages = DbUtils.CurrentSession.Pages_GetByIds(from p in pagesToMove select p.ID).ToList(); ret = new List<PageBE>(from p in movedPages select p.Copy()); // All pages have now been moved. Process recent changes, redirects, and events on another thread Async.Fork(() => { // Create redirects from previous titles to new titles try { MovePageProcessRedirects(pagesToMoveList, newTitlesByPageId, childRedirectsToRecreate); } catch(Exception x) { _log.WarnExceptionFormat(x, "Move page failure: redirect processing"); } // Add to recent changes and trigger notification try { MovePageProcessRecentChanges(pagesToMoveList, movedPages, newTitlesByPageId, transId); } catch(Exception x) { _log.WarnExceptionFormat(x, "Move page failure: recentchange processing"); } }, new Result()); } catch { DbUtils.CurrentSession.Transactions_Delete(transId); throw; } return ret; }
public static PageBE[] DeletePage(PageBE startNode, bool deleteChildren) { // BUGBUGBUG (steveb): if deleteChildren is false; don't fetch the children (duh!) Dictionary<ulong, PageBE> pagesById = null; startNode = PageBL.PopulateDescendants(startNode, null, out pagesById); //Apply permissions to all descendant pages and their corresponding talk pages PageBE[] allowedPages = PermissionsBL.FilterDisallowed(DekiContext.Current.User, pagesById.Values, false, new Permissions[] { Permissions.DELETE }); IList<PageBE> allTalkPages = GetTalkPages(allowedPages); PageBE[] allowedTalkPages = PermissionsBL.FilterDisallowed(DekiContext.Current.User, allTalkPages, false, new Permissions[] { Permissions.DELETE }); //Determine reset/delete list Dictionary<ulong, PageBE> allowedPagesById = allowedPages.AsHash(e => e.ID); Dictionary<ulong, PageBE> pagesToResetHash = new Dictionary<ulong, PageBE>(); Dictionary<Title, PageBE> allTalkPagesByTitle = allTalkPages.AsHash(e => e.Title); Dictionary<Title, PageBE> allowedTalkPagesByTitle = allowedTalkPages.AsHash(e => e.Title); PageBL.RemoveParentsOfDisallowedChildrenAndTalk(startNode, allTalkPagesByTitle, allowedTalkPagesByTitle, allowedPagesById, pagesToResetHash); PageBE[] pagesToDelete = null; PageBE[] pagesToReset = null; PageBE[] talkPagesToDelete = null; List<PageBE> talkPagesToDeleteList = new List<PageBE>(); //If children exist and are deleteable but not asked to delete children, reset the startnode instead if(!deleteChildren && startNode.ChildPages != null && startNode.ChildPages.Length > 0) { pagesToResetHash[startNode.ID] = startNode; allowedPagesById.Remove(startNode.ID); } if(deleteChildren) { pagesToDelete = allowedPagesById.Values.ToArray(); pagesToReset = pagesToResetHash.Values.ToArray(); talkPagesToDelete = allowedTalkPagesByTitle.Values.ToArray(); } else { if(allowedPagesById.ContainsKey(startNode.ID)) { pagesToDelete = new PageBE[] { startNode }; } if(pagesToResetHash.ContainsKey(startNode.ID)) { pagesToReset = new PageBE[] { startNode }; } if(allowedTalkPagesByTitle.ContainsKey(startNode.Title.AsTalk())) { talkPagesToDelete = new PageBE[] { startNode }; } } //Perform deletes and resets List<PageBE> deletedPages = new List<PageBE>(); TransactionBE newTrans = new TransactionBE(); newTrans.UserId = DekiContext.Current.User.ID; newTrans.PageId = startNode.ID; newTrans.Title = startNode.Title; newTrans.Type = RC.PAGEDELETED; newTrans.TimeStamp = DateTime.UtcNow; uint transId = DbUtils.CurrentSession.Transactions_Insert(newTrans); try { SortPagesByTitle(pagesToDelete); SortPagesByTitle(pagesToReset); //Perform deletes and resets if(pagesToDelete != null && pagesToDelete.Length > 0) { DeletePages(pagesToDelete, newTrans.TimeStamp, transId, true); deletedPages.AddRange(pagesToDelete); } if(pagesToReset != null && pagesToReset.Length > 0) { DeleteByResettingPages(pagesToReset, newTrans.TimeStamp, transId); deletedPages.AddRange(pagesToReset); } //Talk pages get deleted in same transaction if(talkPagesToDelete != null && talkPagesToDelete.Length > 0) { DeletePages(allowedTalkPages, newTrans.TimeStamp, transId, true); deletedPages.AddRange(allowedTalkPages); } } catch { DbUtils.CurrentSession.Transactions_Delete(transId); throw; } // Deleted user homepages are recreated foreach(PageBE p in deletedPages) { if(p.Title.IsUser && p.Title.GetParent().IsHomepage) { // NOTE (maxm): This is determining the owner of the page by the title which // may not always be reliable. UserBE pageOwner = UserBL.GetUserByName(p.Title.AsSegmentName()); if(pageOwner != null) { PageBL.CreateUserHomePage(pageOwner); } } } return deletedPages.ToArray(); }
public void Transactions_Update(TransactionBE trans) { Stopwatch sw = Stopwatch.StartNew(); _next.Transactions_Update(trans); LogQuery(CATEGORY_TRANSACTIONS, "Transactions_Update", sw, "trans", trans); }
public uint Transactions_Insert(TransactionBE trans) { Stopwatch sw = Stopwatch.StartNew(); var ret = _next.Transactions_Insert(trans); LogQuery(CATEGORY_TRANSACTIONS, "Transactions_Insert", sw, "trans", trans); return ret; }