Exemple #1
0
        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;
        }
Exemple #2
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);
        }
Exemple #3
0
        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();
        }
Exemple #4
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;
 }
Exemple #5
0
 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;
 }