An undo button for Drupal

💬 7

Every time that you perform any action in a desktop application, you can hit the trusty 'undo' button, to un-wreak any havoc that you may have just wreaked. Undo and redo functionality comes free with any text-based desktop application that you might develop, because it's a stock standard feature in virtually every standard GUI library on the planet. It can also be found in most other desktop applications, such as graphics editors, animation packages, and even some games. Programming this functionality into applications such as live client-side editors is extremely easy, because the data is all stored directly in temporary memory, and so keeping track of the last 'however many' changes is no big deal.

One of the biggest shortcomings of web applications in general, is that they lack this crucial usability (and arguably security) feature. This is because web applications generally work with databases (or with other permanent storage systems, such as text files) when handling data between multiple requests. They have no other choice, since all temporary memory is lost as soon as a single page request finishes executing. However, despite this, implementing an 'undo' (and 'redo') system in Drupal should be a relatively simple task - much simpler, in fact, than you might at first think.

Consider this: virtually all data in Drupal is stored in a database - generally, a single database; and all queries on that database are made through the db_query() function, which is the key interface in Drupal's database abstraction layer. Also, all INSERT, UPDATE, and DELETE queries in Drupal are (supposed to be) constructed with placeholders for actual values, and with variables passed in separately, to be checked before actually getting embedded into a query.

It would therefore be a simple task to change the db_query() function, so that it recorded all INSERT, UPDATE, and DELETE queries, and the values that they affect, somewhere in the database (obviously, the queries for keeping track of all other queries would have to be excluded from this, to prevent infinite loops from occurring). This could even be done with Drupal's existing watchdog system, but a separate system with its own properly-structured database table(s) would be preferable.

Once this base system is in place, an administrative front-end could be developed, to browse through the 'recently executed changes' list, to undo or redo the last 'however many' changes, and to set the amount of time for which changes should be stored (just as can be done for logs and statistics already in Drupal), among other things. Because it is possible to put this system in place for all database queries in Drupal, undo and redo functionality could apply not just to the obvious 'content data' (e.g. nodes, comments, users, terms / vocabularies, profiles), but also to things that are more 'system data' (e.g. variables, sequences, installed modules / themes).

An 'undo / redo' system would put Drupal at the bleeding edge of usability in the world of web applications. It would also act as a very powerful in-built data auditing and monitoring system, which is an essential feature for many of Drupal's enterprise-level clientele. And, of course, it would provide top-notch data security, as it would virtually guarantee that any administrative blunder, no matter how fatal, can always be reverted. Perhaps there could even be a special 'emergency undo' interface (e.g. an 'undo.php' page, similar to 'update.php'), for times when a change has rendered your site inaccessible. Think of it as Drupal's 'emergency boot disk'.

This is definitely something to add to my todo list, hopefully for getting done between now and the 4.8 code freeze. However, with my involvement in the Google Summer of Code seeming very likely, I may not have much time on my hands for it.

Post a comment

💬   7 comments

Sami Khan

This has already been implemented as a patch by hunmonk

I look forward to working with your on your SoC project :)

Jaza

I am aware of the trash can patch by hunmonk, and I am very excited about it going in. But the trash can only covers the undoing of delete operations, and it only covers the data structures and modules that implement it. This idea, on the other hand, covers every ins / upd / del operation in the database, for all data structures and modules (except for those that are specifically excluded). It is a more low-level plan than the trash can idea.

This idea is not designed to be in competition with the trash can. I think that it works in a different way and serves a different purpose, and I think that both ideas potentially belong in Drupal core (the trash can definitely belongs there, not sure if this one does). The two features could even work together - i.e. even after emptying the trash can, there is still a way to find and restore your deleted data, albeit a more 'crude' way.

PS: great to hear that you're my mentor! Great that I got in! I look forward to starting work with you.

Sami Khan

I actually skimmed over it before, but reading it I see where you're coming from and I can see how it may be different in many cases from a trash can. My major concern would be that it would cause the number of queries to increase two-fold... Hmm, I wonder what rest of the group thinks, seems like a very novel idea for sure and I can see quite a bit of application for something like this, at times I have deleted things, not just content, but run queries that I wish I hadn't. Perhaps initally a patch that would allow this to exist as a module might be a good first step...?

greggles

I agree with Sami that performance is perhaps the biggest issue here (aside from a implementation...). However, if this functionality were configurable and were turned off by default with a "Note: enabling this feature will adversely affect performance" then I think it makes great sense.

clouseau

What you're really proposing is a journaling system for Drupal. It's a very powerful idea, but a tough nut to crack.

We have talked about this at a higher level, with the actions module. In short:

The problem with this is when you think it through it becomes highly complex, especially given that your request does not have exclusive access to the data store (something that desktop applications don't have to deal with).

The Seaside demo in Vancouver showed some of this, but it has the benefit of smalltalk behind it.

It may ultimately be less complicated (but certainly less user-friendly) to put the journaling layer closer to the database -- maybe even inside it.

Jaza

One solution to the complexity problem, and to the problem of rolling back only parts of data structures that are interrelated, is to force the undo operations to be 'linear'. That is, you can only undo the last operation, or the last 10 operations, etc - not the 35th to the 58th last operations.

This would be analogous to the way that undo works in desktop apps: with Photoshop's history feature, for example, you can't undo that crop that you performed 5 actions ago, and not undo the other 4 actions that you performed since then; thus the problem of some operations depending on others is drastically reduced. However, as you said, your request does not have exclusive access to the data store, so this kind of inflexible rule might not work in a database-driven context.

Something else to make this work better would be to group queries together logically, by passing some kind of 'group query ID' as an argument to db_query(). You would then only be able to undo an entire group of queries, rather than just undoing a single query. This would be a semi-simulation of a database-level transaction ROLLBACK system, which MySQL unfortunately doesn't fully support. Speaking of MySQL problems, it would also be nice if MySQL supported triggers, which could be used to perform these 'journaling' queries, rather than handling it at the PHP level.

sigh - It's such a pity that cross-DB compatibility comes at the price of sacrificing virtually all of the really cool features that modern databases offer. It seems that cross-DB applications (many of which are web applications) have had to emulate things like stored procedures, triggers, views, transactions, and even constraints, at the server-side code level. This is IMO unfortunate, inefficient, and completely defeating the purpose of bothering to utilise databases in the first place. But this issue is something that I could write a whole new article about, so I should stop ranting now. :-p

Ramdak

I have found myself wishing often for this kind of feature, but each time I thought it was simply wishful thinking.

My host (Site5) offered/offers a subversion-based Flashback system (seemingly never fully implemented) that gives the option of rolling back any kind of change to the files, so potentially, a file or any change to a file is never ever lost. I don't know if this handles stuff linearly or not.

I wonder if this can be an alternative to the limitations of a database-based undo system.