A few weeks ago I was testing some plugins and realized it was about time I write an update to Stephen Rider’s Log Deprecated Calls plugin. I don’t like reinventing the wheel, but there were a few reasons I wanted to start anew.
I spent quite a bit of time early in the WordPress 3.0 development cycle improving our deprecated notices. We added reporting of deprecated arguments and I tediously went back through the SVN history to find the version numbers for every deprecated function, both when the function was introduced and when it was deprecated. We also formally deprecated a number of functions, such as the really old widget API — e.g. use
register_sidebar_widget() — and the old escaping aliases — e.g. use
esc_attr() instead of
So, I wanted to make sure there was a plugin to fully leverage all of this. Stephen indicated he wanted to update his plugin to handle arguments (originally released for WordPress 2.6), but that he was also working on integrating it into his own plugin framework. I wanted to scrap the extra table and leverage a custom post type for both the storage and the UI, and see how far that would get me.
The results were interesting. I’ve made some pretty interesting customizations to allow me to leverage the custom post type UI, and the plugin is scattered with notes on possible enhancements for WordPress 3.1 that can help a lot of plugin developers, which I’ll revisit when we start development in September. In hindsight I think leveraging the UI was a wildly successful experiment, even with the hacks I employed to make it work to my liking, because I didn’t have to worry about any overhead.
- Backtraces. The plugin tracks down exactly where the function was called from, the argument was used, or the file was included. It also provides special context and handling for unique situations: deprecated functions used as a hook callback, usage of user levels instead of capabilities, unregistered settings, and other quirky things that WordPress reports.
- Filters. Filter notices by date, by type of notice, even by version. Oh, and you can search too.
- Logical counts. Each unique log entry is only recorded once. After that, it keeps a count. Keeps the log neat and organized, and keeps the database size at O(1) levels instead of O(n).
- Muting. Is a notice a known issue? Mute it and you won’t be bothered by it again. There are also bulk actions for muting and deleting notices.
- Unread count. New unmuted notices since last time you visited the Deprecated Calls screen? You’ll see a bubble on the submenu item with the unread count.
What’s to Come
I’ve paused development while I work on other projects, but the next big step will be identifying plugins that cause notices. I think the ability to filter notices by a file or plugin will be an important drill-down. I’m also going to experiment with adding an unobtrusive note on the plugins page next to plugins throwing notices.
I’d like to offer better multisite support, though I’m not entirely sure what that entails. Since plugins are network-wide, then I imagine the log should be network-wide as well (at least for plugins activated for the network), but the logs are stored in posts tables, so I’ll have to think about that some more. (Let me know if you have any ideas.)
I want to consider offering the ability to auto-purge the log. Since the plugin only stores unique notices, and keeps a running tally, then the database won’t grow in size, assuming it’s the same notices being triggered over and over. And the log is paginated, searchable, and filterable. So I’m not convinced it is a necessary feature.
The plugin is arguably okay for a production environment, as it uses the API, thus the few queries it makes from the frontend are mostly cached. (It’s designed for a development environment, however.) To improve performance further (especially cutting down on queries to update counts), I will probably delay logging operations until the shutdown hook, and do it all at once.
It would be great to hear how individuals use it in WordPress plugin development and site management — that will help drive new features. Contact me with suggestions.
The Making Of
As I said, I employed some very interesting techniques to get what I wanted, helpfully noted in the source code with the phrase “cheap hack.” I wouldn’t encourage some of these in your own plugins because there isn’t a guarantee it’ll work in the future. Do as I say, not as I do? Pretty much, but that’s only because I either write or read every commit, so I can adjust the plugin quite quickly to ensure compatibility. Some of these hacks, however, are simply inventive ways to fully leverage a custom post type.
As I wrote above, I made a list of improvements for core. In hindsight, I wished I didn’t use
_deprecated_argument() in dubious ways, because each scenario needed special handling. I expect to introduce
_deprecated_message() in 3.1. (Also in store — deprecated notices for hooks? Maybe.)
Here’s some of the more clever hacks and tricks I used:
- The “Muted” state is actually the Trash. The trash post status excludes it from the main list of notices, allowed me to use the “Trash” action link and “Move to Trash” bulk action, and allowed me to use the easy undo/untrash functionality. Then I performed some renaming: the Trash became “Muted,” and “Mute” as a verb, and ” Move to Trash” became “Mute.” Even the “Empty Trash” button simply became “Clear Log,” and with two small hacks that was made visible and functional when viewing the unmuted entries too.
- Custom post type in a submenu. While this is something we definitely plan to do in 3.1, it is currently unsupported. No problem. I just unset the top-level menu and add it myself to the Tools menu.
- Permissions. I wanted to show administrators the UI, but no one needed to edit or add anything. So, I utilized the activate_plugins permission, and simply disabled the edit and add new screens. The “Add New” button is hidden through CSS (as is a few other things). Introducing an add_posts capability would help, but really only for custom post types, so I’m not sure how to make this better.
- Querying the filters. I wrote some nifty code to handle querying by more than one key/value postmeta pair.
- Searching. The details of each notice are stored in postmeta, but they’re only used for querying when applying filters, not to actually display the log. Instead, the description of the notice (and the alternative) is generated when the notice is logged, and stored in post_content. This allows searching, and prevents querying hundreds of postmeta rows to display the log. But since I have the postmeta values, I can rely on them, and even rebuild the post_content field on a future upgrade. (I do have a note in the plugin that maybe this isn’t the best idea, but it works well so far.)
You’ll have to review the code yourself if you want to discover some of the hacks I’d rather not mention. (Just don’t use them! 🙂 )