The scenario/A case study
WP_List_Table, but still were added as a different sub-page. My goal was to integrate it as additional view to the default “list” and “excerpt” screens on top right side of the default table on the main post list screen. During this task, I encountered an endless number of mine traps and stumbling blocks. To give you a brief insight: Just to add the additional view icon, I had to generate a new extending list table, hijack two globals and then attach the new view on top of the new extending list table.
As I was done (the plugin didn’t need to be a fully fledged piece of code, but just the view), I moved on and tried to fully integrate with the WordPress core API (or in other words: work around it) and it’s UI styling. So I added lots of what a Google Calendar delivers, thanks to UI improvement tips by Robert “Wyck” Ellison and Tom J. Nowell: It now has…
- calendar weeks added to the first day of a week
- different styling for the days of the previous and next month
- a pagination that let you jump month and years, but bypass those that have no posts
- a link back to the current month
- responsive styles
The last thing missing was a working Quick Edit box. After some fiddling and doing endless
var_dump()s in core, I even got around that. But then I realized that the response of the Quick Edit-AJAX callback was somehow off in regards of the styling. It simply threw back the default cores
WP_Posts_List_Table styling. So I went back into core and dumped the global filter array:
$GLOBALS['wp_filter'][ 'wp_ajax_' . $_REQUEST['action'] ]
This way I was able to dig up
wp_ajax_inline_save() which allowed me to see the mess in there. No filters, no actions, everything hard coded and then a call to
_get_list_table(), which will filter out every class that isn’t defined by core. So I had absolutely no chance to get WordPress to return the MarkUp that was already in place.
Of course there’s a way around that problem as well: Remove the action. This would sadly mean that I had to clone 99% of the core function, but it was better than nothing. So I opened up
~/wp-admin/admin-ajax.php, which processes all AJAX requests and noticed that the core
wp_ajax_inline_save()-callback gets attached right inside there.
// Note: line length reworked for readability if ( ! empty( $_POST['action'] ) && in_array( $_POST['action'], $core_actions_post ) ) add_action( 'wp_ajax_'.$_POST['action'], 'wp_ajax_'.str_replace( "-", "_", $_POST['action'] ), 1 ); add_action( 'wp_ajax_nopriv_autosave', 'wp_ajax_nopriv_autosave', 1 ); // Authenticated actions if ( is_user_logged_in() ) do_action( 'wp_ajax_'.$_REQUEST['action'] );
I went back into cores deep dark edges and looked for a way that would allow me to remove the action early enough, before core fires the actual callback. The only line in between is, as you can see from above code,
is_user_logged_in(). And this function calls…
- which then calls
- which calls
- which gladly(!) has one action:
So – obviously – one has to hook into
set_current_user to remove core AJAX callbacks (when their returned data can’t be changed with a filter). Imagine my
facepalm wallpalm. Short reminder: I just wanted to get the original MarkUp back in place.
Summoned up, I need to remove the core action from within a non-obvious, accidentally in place hook, replace a core function just to replace a class and add in a custom action that is a 99% clone of core. Did I already mention that there’s exactly not a single method left that I haven’t had to replace/overwrite in my extending calendar table class? And did I already mention that I had to intercept the global
WP_Query object to alter the query to fit the output? And yes, then there was another case where I had to intercept and overwrite another global and so on.
After walking through all this, there’re some lessons I learned the hard way (again). One is that WordPress can be changed in a lot of ways and in lots of cases. If one ignores the fact that WordPress exists and just throws a bunch of custom code at it, that runs alongside core, then it’s even easy to reach your goals with WP. Another one is that you can try as hard as you want to stay in line with core code and it still will at the end be some sort of hack. The plugin I wrote and rewrote is written in a fashion that it will be as reliable during updates as possible. Still I’m not 100% sure if core won’t simply change a single line somewhere and I’ll need to do a massive rewrite (again). Was it worth it? Yes, as I know that not everything will fall apart with a change of core and it simply integrates the most seamless way, I’ve so far seen. Would I do it again? No, never ever.
Why core can’t change
There’re lots of reasons why core will be what it is: A misuse of OOP as namespaces where every view consists out of hard coded HTML syntax with random plain function calls in between.
The reason most developers would tell you is that WordPress simply is the business of Automattic and will only change when either the competition product Tumblr brings a new feature or Automattics core developers will implement a new feature (heartbeat API, editorial workflow, you name it).
After the effort I’ve taken to bring this plugin to life and after the amount of core files I had to walk through, I’ll give you another reason: It’s because core is too tightly coupled with itself. List tables are called into global variables. Those then call their functions directly into admin UI template files. But when a list table is called and rendered, there’re numerous other files involved. For example
~/wp-admin/includes/screen.php handles column filters,
~/wp-admin/admin-ajax.php handles attaching and processing AJAX callbacks, while
~/wp-admin/ajax-actions.php holds them and
~/wp-admin/includes/template.php brings the necessary MarkUp and so on. So if you’d just would want to change the way admin tables are generated, rendered, their output handled, etc. you’d have to invest at least two release cycles just to tear things apart and identify every part where core references itself or is possibly involved. And when you’re done so, then WordPress is left with hundreds of broken plugins and themes.
If someone would ask me if I want to rewrite WordPress, then it would be a simple “No”. At least, I wouldn’t ever go and try to change things that are already in place in core. But what I can always imagine (and have already tried to create) is actual replacements for core stuff. One example, to bring in something new, is the admin UI menu system. It is in fact just an array, that has some sort of API that isn’t used by core, but worked around with stuffing keys and values into the global array to make it harder for us developers to change things (I consider it to be one of the Easter Eggs). Now we could as well use the already existing Nav Menu “API” and rebuild it. The same goes for pages in on the admin side. In fact it would be possible to replace it with a complete theme.
But every time I rethink such an idea, I remember that there’s still Symfony2, which has the FOSUserBundle to manage users, roles and capabilities, the KnpMenuBundle to easily handle menu generation and even the LiipThemeBundle for themes. On top of that it works with Twig, which is much easier to understand and use than WPs templating system and hierarchy. Finally there’re bundles, which you can imagine like Plugins. The only difference is that you can write them any way you want and they’d still seamlessly integrate with core. So when I look at how much effort it is to rewrite parts of WordPress core (not to mention that not a single change would make it through trac) compared to the effort to write a whole new CMS with Symfony2, I’d go down that route.
So it’s as simple as that: WordPress can’t change. WordPress won’t change. But maybe one day parts of it will be replaced like it already happened. But don’t expect it to be tomorrow. Or next year.