The Decline of Drupal, or How to Fix Drupal 8

Prologue

Almost 4 years ago I wrote a controversial post entitled "17 Reasons WordPress is a Better CMS than Drupal" that caused me to be persona non grata among some of my prior Drupal friends.

But while some of the issues I mentioned have been addressed by the Drupal community most of the issues remain in Drupal 7, and WordPress has continued to gain strength as a CMS.

Unlike almost 4 years ago, I’m now seeing many people replacing Drupal solutions with WordPress and the end users becoming happier. My team is even bidding on replacing a website so we can build a member’s-only private site to go with it after the Drupal developers have not been able to deliver on the private site for over 2 years.

The Impetus

What triggered me to write this post was I was composing a long reply to a comment on the other post and it became clear it would be better as a new post.

In the comment the commenter asserted:

With Drupal 8 coming up, I am sure the difference in number of users between Drupal and WordPress will come down.

However, I think that the commenter will find that the exact opposite happens. Why do I think this? I started college shortly before the IBM PC was released so I’ve seen enough computer industry history firsthand to know a bit about the patterns that repeat related to software platforms.

Will Drupal 8 grow Drupal’s User Base?

I highly doubt it, and I think there is strong evidence in the history of software platforms that would support my view. Those patterns I mentioned above indicate to me that the strategy of changing Drupal’s architecture in Drupal 8 will be a failing strategy.

Let me explain.

Fans Like Things As They Are

As with all software products and platforms that gain a notable level of success, Drupal 7 and earlier appealed to people who valued what Drupal had to offer. Some of those things include ease of end-user configuration and other things include hierarchical software architecture and the hook-based extensions mechanisms. Or at least those are what originally appealed to me in Drupal before I discovered all the downsides I explained in the prior post.

Now Drupal 8 promises to be a lot more "modern frameworks and platforms," adopting "modern PHP concepts and standards, object-oriented programming, and the Symfony framework." Now that sounds awesome, and on the surface should cause almost any Drupal fan to cheer.

But those stated aspects require a lot more programmer skill to work with yet one of the things that appealed to a lot of Drupal users-cum-developers is that they did not have to understand object-oriented programming, nor modern frameworks and techniques. To quote Jennifer Lea Lampton:

Back in the day, Drupal used to be hackable. And by "hackable" I mean that any semi-technical yahoo (that’s me, btw) who needed a website could get it up and running, and then poke around in the code to see how it all worked. he code was fairly uncomplicated, though often somewhat messy, and that was fine. At the end of the day, it did what you needed.

Given the fact far fewer people have a high-level of programming skill many of those who do NOT see themselves as professional programmers do not want to improve their coding ability, they would rather just focus on their chosen career where Drupal is only a tool to help them.

So Drupal 8 will be will be alienating all those users and they will feel abandoned. Or as Ms. Lampton says:

Today, the majority of the people in our Drupal Community aren’t CS engineers. They are self-taught Drupal experts, people less technical than myself, and people who can get by using this awesome software we’ve developed to help make their lives easier. What is the transition to Drupal 8 going to be like to them? Well, I asked some non-core developers, and I didn’t like what I heard.

A lot of professional Drupal developers already have exit strategies.

And my guess is that most of those alienated users and new users who would have otherwise chosen old Drupal will move to WordPress.

But Pros Want to be Pros

And on the other end of the spectrum are those who DO see themselves as professional programmers and those people (almost) always want to increase their coding skills. They will start asking themselves why they are working on a platform (Drupal) that still has lots of "impurities" when the could just more over to a "real" framework such as Symfony or even Rails or Node.js, and not have to deal with all the legacy issues of Drupal?

Or as Ms. Lampton continues (emphasis on WordPress mine):

They may even have a day job building or maintaining Drupal 6 and/or Drupal 7 sites, but they go home at night and study Ruby, Node.js, Angular.js, even some are looking into WordPress. They want to be "out" before they have to learn Drupal 8. These are smart, capable people, who I’m sure - if they wanted to - would be able to pick up Drupal 8. So, why are they leaving? Because Drupal 8 has become different enough that learning it feels like learning something new. If they are going to invest in learning something new, why not Ruby, or Node.js, or something else?

What Visual Basic’s History Can Teach Us

What makes me think the above scenario is likely? Because I saw it happen with Visual Basic and C#. Visual Basic pre .NET was easy to use and became arguably the world’s most widely used programming language for a time. But it was a ugly language with many inconsistencies and was very limited in what it could do compared to C++ so it was always looked down on by "real" programmers ignoring how Visual Basic empowered so many people who never would or could develop using C++.

So Microsoft envisioned a "better" way; a .NET platform on which both Visual Basic and a new language called C# would live making Visual Basic a "proper" programming language, almost on par with C+.

Fast forward to today and what happened was that those who valued Visual Basic’s simplicity continued to use the old Visual Basic (for a while), abandoned it for other tools that were easier, or just quit developing and focused on other parts of their career.

Those who wanted to become better professional programmers asked themselves "why stay with VB?" so most everyone just moved up and over to C#. This migration effectively killed off what 10 years ago was once the most popular programming language in was the world.

And I believe a pattern similar to the Visual Basic decline will occur with Drupal starting at version 8.

When Upgrades are Challenging People Evaluate Options

And then there are those who will stick with their current version of Drupal until they can no longer maintain the solution and still get the evolving solutions they need for web and mobile.

At which point these people will be forced with a choice; migrate to the newer Drupal, or migrate to a different platform? And given how little interest the Drupal core team places in 1.) "Being backward compatible" and 2.) "Creating an interface that is usable for end-users" the choice will often not be "Move to newer Drupal."

True Believers will be True Believers

Of course there will still be people who love Drupal 8. And unlike proprietary software like from Microsoft, Drupal 8+ will continue to exist as long as a group exists who are passionate enough to maintain it. But I am almost certain Drupal’s market share will drop significantly and lose most of it to WordPress (which BTW won’t make that much different to WordPress’ marketshare, by comparison.)

Don’t Mess With My Status Quo!

And this being the open-source world, Drupal has already been forked and the fork is called Backdrop from the same Ms. Lampton quoted above as well as Nate Haug. Assuming Ms. Lampton and Mr. Haug and team executes at least reasonably well then some of the more fervent believers in "Drupal Classic" will move over to Backdrop, and Drupal 8 will loose more marketshare from yet another source.

But Backdrop will almost assuredly never be more than a footnote because it won’t have the marketing muscle in IT shops that Acquia has, and IT shops have been the primary drivers of Drupal adoption from best I can tell looking in from the other side. And Backdrop being a fork won’t have the 10+ years of supporting organization that Drupal now has. Plus, Backdrop has an unknown brand at this time and building up that brand will take time.

Old Doesn’t Inspire, It Just Fades Away

Given that Backdrop is basically a stake in the ground to avoid evolving Backdrop is highly unlikely to become "the hot new thing" but will instead be like FoxPro that for years after Microsoft acquired it was "a user base Microsoft could not grow and Microsoft could not kill"; that’s a direct quote from a former marketing manager at Microsoft.

The Shrinking Girth: Traveling Up the Pyramid

So Drupal 8 will be pushed by Acquia into IT shops, but it will be used by an increasingly narrow user base until the user base becomes so small that Acquia can no longer survive.

This long tail may take a really long time, but I am certain it is inevitable, unless of course Drupal/Acquia/Dries change strategy.

What SHOULD Acquia/Drupal Do Instead?

So here’s where I’ll divert from my criticism of Drupal and advocacy of WordPress; I’ll actually recommend what I think Drupal/Acquia/Dries should do and how they could potentially grow their business even if they do not catch WordPress in marketshare.

Announce the Drupal 8 Will Be "Drupal 7 Enhanced"

Dries Buytaert should do an about-face and announce that Drupal 8 will NOT be based on a new architecture but will instead simply be an enhanced Drupal 7, much like the about-face Tim Berners-Lee famously did when he announced XHTML was no longer the future of the web.

Adopt the Backdrop Team for Drupal 8 and Beyond

Dries should then work the Backdrop team and any of the Drupal 8 team who want to continue the status quo albeit with evolutionary improvements, much like how Merb broke off from and was later merged back into Ruby on Rails.

Further, adopt a no-breakage policy for future Drupal releases and work to ensure backward compatibility so that people are not forced into painful upgrades if they do not want to invest a significant amount into redevelopment. Learn from WordPress how to evolve without introducing breaking changes.

Announce a New CMS Called "Acquia"

Then, take all the ideas and lessons learned with Drupal that were destined for Drupal 8 and create a clean from-the-ground-up implementation of a next generation CMS targeting those who work rather program at the level of a framework but prefer to have more of the features needs for content management ready-built and available so as not to require people to reinvent the wheel.

Launching an Acquia CMS would have the benefit of being new in a way that could appeal to more than just the existing Drupal user base that does want to level up but not abandon Drupal. And Acquia is already a very strong company that has a stellar enterprise sales and support team so they would be in a great position to market a new CMS, and launching it would give them a stronger offer to sell to and support for their customers.

Acquia CMS could become the better alternative to Symfony that offers more functionality without all the legacy cruft of Drupal instead of Symfony being viewed as the better alternative to the Drupal CMS that carries so much baggage which is where I think things are headed.

Give Developers Something NEW To Adopt

And this branding is not just for technical improvements, it’s more important for positioning reasons.

Acquia CMS could have none of the negative associations developed by prior users of Drupal. Acquia CMS would be free to address all the problems I outlined in my prior blog post. And Acquia could once again become the CMS mindshare leader, a position that Drupal previously held IMO.

But Wait, Don’t Listen to Me!

If Drupal/Acquia/Dries does follow my advice, it would probably mean that I’d loose opportunities to work on certain future projects. The type of work I do with WordPress is most often competitive with Drupal in the minds the stakeholders deciding the platform the project will use. So I really hope they do not listen. :)

But hell, if they do follow this advice I would evaluate Acquia CMS and might even consider using it instead of WordPress in the future.

But really Dries if you are listening, please don’t! I’m currently really happy with the progression of WordPress and doing this would just throw a monkey wrench into my future works.

So nothing to see here; just carry on as planned. Nothing to see. :)

UPDATE

In the first version of this post I incorrectly referred to the fork as "Backstory", not "Backdrop" and I did not include a link to the Backdrop website nor mentioned Nate Haug. I have corrected the post.

Thanks to commenters Doug Vann, Brian and Jen Lampton for pointing out my error.

Presentation Notes on Sunrise for Metamorphosis Notes (WordPress Post Meta Team)

Post Meta Meeting

Tuesday December 3rd & 10th 2013

What

Where

Who

  • Mike Schinkel
  • Micah Wood (wpcholar)
  • Andrey (Rarst) Savchenko
  • Taras Mankovski

Why

  • We built a company’s products
    • Platform for building large lawfirm website
  • Client didn’t respond well to: "Well we can’t (easily) to do"

Architecture

  • Focused on programmer productivity

    • Source code solution for version control
    • Sunrise was on 2nd generation code
    • ASIDE: We are now on 5th generation code style
      • See: https://github.com/newclarity/exowp
      • Exo is an MVC Skeleton for WordPress, w/mixins
        • Not Forms/Fields, but lower level for MVC
        • Redesign of Sunrise would follow Exo architecture
  • Core OOP layer

    • Fields
    • Forms
    • etc.
  • Sitebuilder layer

    • register_form()
    • register_field()
    • Virtual Field Types
  • Themer layer

    • get_field()
    • the_field()

Admin User Interface

  • Should be able to completely replace metaboxes in admin UI
    • Especially Title, URL and TinyMCE editor
  • Most common use-case; a single metabox
    • Optional "modules" should be able to add to that metabox
      • vs. adding another metabox.
    • Adding to metabox is not hard with Sunrise architecture.

Storage Format

  • For Post Meta Key & Value
  • See SQL Query

Field Features Objects

Fields generate collection of HTML elements.

Made sense to create "field feature" class to subclass because all shared so much code.

  • Entry
  • Label
  • Help
  • InfoBox
  • Message

get_field()/the_field()

  • Simple naming
  • Filters:

    • 'pre_get_field'
    • 'get_field'
    • 'empty_field'
    • 'the_field'
  • Virtual fields

    • "nested dolls"
  • Issue to address with naming conventions or $args type: Return values of string/numeric vs. object/array vs. HTML

Virtual Fields

  • Especially useful to output Schema.org valid markup
  • Postal Address: ‘postal_address’
    • Stored as:
      • ’street’
      • ‘city’
      • ’state’
      • ‘zip’
      • ‘country’

Virtual Field Types

  • "Real" types defined by class
  • Virtual types based on existing type w/set of arguments.
    • Like cascading properties of CSS

Storage Types

Field storage defined by field, themer need not no storage location.

  • Meta storage
  • Core storage
  • Table storage (custom sql)
  • Taxonomy (term) storage
  • Future/Plugins
    • Pods
    • MongoDB
    • Redis
    • Amazon S3
    • Other…?

Dynamic Forms within Forms

  • Form Selector Field
  • One field could display a form within a field
  • Created a ‘form_type’ meta field to control which form type

Repeating Fields

  • Overuse leads to madness
  • Should support simpler use-cases
  • Repeating should be in core framework, not its own field type
  • Complex use-cases should use object/post relationships (hint/hint)

Other Considerations

  • Minimize nested arrays when initializing
  • Separate Declaration from Fixup
  • Multiple register_field() vs. one register_fields()
  • Then there is MVC + Mixins…

Demo Code

RESTful Web Services in a WordPress Plugin?

UPDATE (2011-04-15):

Since I wrote this post I’ve learned a tremendous amount about WordPress plugin development; so much so that I can’t overstate how much more I know today than when I wrote this post years ago.  So, while the following post might be a novelty to read, I highly recommend that you don’t use this approach.

The only way that a RESTful approach to web services in WordPress would make sense to me is if a team of rockstar WordPress plugin developers were to create a fully fleshed-out extension to WordPress that offered a complete RESTful web service implementation including one that addressed all edge cases and security concerns; only then I would consider not defaulting to the non-RESTful approach WordPress uses for AJAX.  

In summary I recommend not trying to swim upstream today and instead use the approach provided by WordPress. Who knows, maybe in the future there will be a viable method of doing RESTful web services in WordPress.


So I’ve got a project where I need to have a Flash component built in Flex to call to a WordPress blog and get information about it’s latest post. Should be no problem right?  The easy way to do this would be to just write a "rest.php" file and brute-force all the setup that WordPress does but I thought it would be so much more valuable to implement this as a plug-in.

I figured that I’d just quickly learn how to build a WordPress plug-in and create one for exposing RESTful web services; after all with a year of programming Drupal modules WordPress’ plug-in API can’t be that hard, right?  Well turns out it wasn’t that easy and I think I have run into a design limitation with WordPress and I’m beginning to wish I’d just taken the brute-force approach and said to hell with writing a plug-in.

Although I am not 100% certain, and I hope someone can point out that I’m just doing something wrong, it seems sadly like I’m pushing the edges of the WordPress API and exposing where it’s design falls short. By the way, I’m working with WordPress v2.5 because why upgrade mid-project when god knows if WordPress will release another in the remaining days before this project is done and I’ll just have to do again before deployment?

Here’s the details.  I started writing a plug-in called "RESTful Services" with a goal of implementing URLs that behave in the following fashion; {format} could potentially be html, xhtml, json, xml, rss, atom, etc.:

http://example.com/services.{format}
Provide a list of RESTful services in specified {format}, defaults to html
http://example.com/services/{service}.{format}/{data}?{params
Provide a RESTful service in specified {format}, defaults to html, with optional provided data and parameters.

But before I got all those options working I just wanted to service a page from my RESTful Services plugin where Content-Type: text/plain. I found this page that professes to explain how to hook into the URL routing and after a few fits and starts I can came up with the following code for my plugin that would indeed response to my http://example.com/services URL:

wp-content/plugins/restful-services/restful-web-services.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
add_action('init', 'restful_services_flush_rewrite_rules');
function restful_services_flush_rewrite_rules() {    
  global $wp_rewrite;
  $wp_rewrite->flush_rules();
}       
 
add_filter('generate_rewrite_rules', 'restful_services_add_rewrite_rules');
function restful_services_add_rewrite_rules( $wp_rewrite ) {      
  $new_rules = array(        
    'services' => 'wp-content/plugins/restful-services/rest.php',     
  );
  $wp_rewrite->rules = $new_rules + $wp_rewrite->rules;
}

The problem with the above was that it wouldn’t call "wp-content/plugins/restful-services/rest.php"; it would simply continued to call "index.php" and display the home page!  After literally hours and hours of debugging with my trusty PhpEd IDE & debugger I was able to find that the code on lines 737 & 738 of "wp-includes/query.php" told WordPress that my service was the home page! It is almost seems like the "generate_rewrite_rules" was implemented as "a good idea" yet no testing has ever been done on it because for the best I can tell it doesn’t work. (Note I’ve reformatted the code to multiple lines so that it is easier to read and does not extend past the right margin of my blog):

wp-includes/query.php:

if ( !(  $this->is_singular                           
      || $this->is_archive                           
      || $this->is_search                           
      || $this->is_feed                           
      || $this->is_trackback                           
      || $this->is_404                           
      || $this->is_admin                           
      || $this->is_comments_popup ) )   
       $this->is_home = true;

I could possibly hack it to get past this by setting one of those to "true", but none of them are really appropriate; there is nothing there quite like an "is_service" instance variable. Setting something like "this->is_singular" or "this->is_feed" might work but it could manifest incompatibility problems with other plugins or future versions of WordPress. Frankly it was rather disappointing to discover this because it tells me that WordPress has hard-coded all the potential scenarios and doesn’t really have a way around it. Seems to me there should really be a hook here and the type of pages should be allowed to be expanded by plugins rather than be hardcoded as only one of ’singular’, ‘archive’, ’search’, ‘feed’, … and ‘home.’

Anyway, where this manifests itself is "wp-includes/template-loader.php" file which I have included in it’s entirety below.  It is on lines 24 and 25 where the template loader loaded the home page because it’s not possible to specify otherwise:

wp-includes/template-loader.php:

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
/**
 * Loads the correct template based on the visitor's url
 * @package WordPress
 */
if ( defined('WP_USE_THEMES') && constant('WP_USE_THEMES') ) {
  do_action('template_redirect');
  $is_home = is_home() ;
  if ( is_robots() ) {
    do_action('do_robots');
    return;
  } else if ( is_feed() ) {
    do_feed();
    return;
  } else if ( is_trackback() ) {
    include(ABSPATH . 'wp-trackback.php');
    return;
  } else if ( is_404() && $template = get_404_template() ) {
    include($template);
    return;
  } else if ( is_search() && $template = get_search_template() ) {
    include($template);
    return;
  } else if ( is_home() && $template = get_home_template() ) {
    include($template);
    return;
  } else if ( is_attachment() && $template = get_attachment_template() ) {
    remove_filter('the_content', 'prepend_attachment');
    include($template);
    return;
  } else if ( is_single() && $template = get_single_template() ) {
    include($template);
    return;
  } else if ( is_page() && $template = get_page_template() ) {
    include($template);
    return;
  } else if ( is_category() && $template = get_category_template()) {
    include($template);
    return;
  } else if ( is_tag() && $template = get_tag_template()) {
    include($template);
    return;
  } else if ( is_tax() && $template = get_taxonomy_template()) {
    include($template);
    return;
  } else if ( is_author() && $template = get_author_template() ) {
    include($template);
    return;
  } else if ( is_date() && $template = get_date_template() ) {
    include($template);
    return;
  } else if ( is_archive() && $template = get_archive_template() ) {
    include($template);
    return;
  } else if ( is_comments_popup() && $template = get_comments_popup_template() ) {
    include($template);
    return;
  } else if ( is_paged() && $template = get_paged_template() ) {
    include($template);
    return;
  } else if ( file_exists(TEMPLATEPATH . "/index.php") ) {
    include(TEMPLATEPATH . "/index.php");
    return;
  }
} else {
  // Process feeds and trackbacks even if not using themes.
  if ( is_robots() ) {
    do_action('do_robots');
    return;
  } else if ( is_feed() ) {
    do_feed();
    return;
  } else if ( is_trackback() ) {
    include(ABSPATH . 'wp-trackback.php');
    return;
  }
}

Still another problem in this puzzle is the $wp->send_headers() method shown being called here on line 293 of "wp-includes/classes.php":

wp-includes/classes.php:

290
291
292
293
294
295
296
297
298
function main($query_args = '') {   
  $this->init();   
  $this->parse_request($query_args);   
  $this->send_headers(); 
  $this->query_posts();  
  $this->handle_404();   
  $this->register_globals();   
  do_action_ref_array('wp', array(&$this));   
}

The problem with the $wp->send_headers(), also from "wp-includes/classes.php", is that it seems to have the option of either serving an HTML content type on line 183 and 185, or a content type based on a feed (the content types for the feeds are set in their respective "wp-includes/feed-*.php" files) but no custom content types as far as I can determine as there seems to be no way to override calling this function or the logic path contained within:

wp-includes/classes.php:

175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
function send_headers() {
  @header('X-Pingback: '. get_bloginfo('pingback_url'));
  if ( is_user_logged_in() )
    nocache_headers();
  if ( !empty($this->query_vars['error']) && '404' == $this->query_vars['error'] ) {
    status_header( 404 );
    if ( !is_user_logged_in() )
      nocache_headers();
    @header('Content-Type: ' . get_option('html_type') . '; charset=' . get_option('blog_charset'));
  } else if ( empty($this->query_vars['feed']) ) {
    @header('Content-Type: ' . get_option('html_type') . '; charset=' . get_option('blog_charset'));
  } else {
    // We're showing a feed, so WP is indeed the only thing that last changed
    if ( !empty($this->query_vars['withcomments'])
      || ( empty($this->query_vars['withoutcomments'])
        && ( !empty($this->query_vars['p'])
          || !empty($this->query_vars['name'])
          || !empty($this->query_vars['page_id'])
          || !empty($this->query_vars['pagename'])
          || !empty($this->query_vars['attachment'])
          || !empty($this->query_vars['attachment_id'])
        )
      )
    )
      $wp_last_modified = mysql2date('D, d M Y H:i:s', get_lastcommentmodified('GMT'), 0).' GMT';
    else
      $wp_last_modified = mysql2date('D, d M Y H:i:s', get_lastpostmodified('GMT'), 0).' GMT';
    $wp_etag = '"' . md5($wp_last_modified) . '"';
    @header("Last-Modified: $wp_last_modified");
    @header("ETag: $wp_etag");
 
    // Support for Conditional GET
    if (isset($_SERVER['HTTP_IF_NONE_MATCH']))
      $client_etag = stripslashes(stripslashes($_SERVER['HTTP_IF_NONE_MATCH']));
    else $client_etag = false;
 
    $client_last_modified = empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? '' : trim($_SERVER['HTTP_IF_MODIFIED_SINCE']);
    // If string is empty, return 0. If not, attempt to parse into a timestamp
    $client_modified_timestamp = $client_last_modified ? strtotime($client_last_modified) : 0;
 
    // Make a timestamp for our most recent modification...
    $wp_modified_timestamp = strtotime($wp_last_modified);
 
    if ( ($client_last_modified && $client_etag) ?
         (($client_modified_timestamp >= $wp_modified_timestamp) && ($client_etag == $wp_etag)) :
         (($client_modified_timestamp >= $wp_modified_timestamp) || ($client_etag == $wp_etag)) ) {
      status_header( 304 );
      exit;
    }
  }
 
  do_action_ref_array('send_headers', array(&$this));
}

Still, I was able to come up with a solution although it is so very hackish.  My solution was to hook the "template_redirect" action on line 7 of "wp-includes/template-loader.php" (see code from that file above.) Though it seems to works thus far, my solution just feels wrong for the following reasons:

  1. It ignores the fact that WordPress continues to think that my web service URL is the home page,
  2. It first lets "$wp->send_headers()" set the content type before it overrides it,
  3. It uses an "exit" rather than a return to keep WordPress from serving up the home page template, and
  4. It doesn’t use the routing mechanism apparent built into WordPress (see "null" on line 30 of "wp-content/plugins/restful-services/restful-web-services.php" below, I assume it should have been the URL of the .php file I plan to execute but WordPress doesn’t see to use what I put there.)

The function "restful_web_services_exec_service()" is what is called to execute the appropriate web service:

wp-content/plugins/restful-services/restful-web-services.php:

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/*
Plugin Name: RESTful Web Services
Plugin URI: http://mikeschinkel.com/wordpress/restful-web-services/
Description: This plugin enables REST-based API web services for WordPress.
Author: Mike Schinkel
Version: 0.1
Author URI: http://mikeschinkel.com/
*/
 
define('RESTFUL_WEB_SERVICES_DIR', dirname(__FILE__));
define('RESTFUL_WEB_SERVICES_URL_PATTERN','services(/?.*)?');
$abspath = trim(str_replace('\\','/',ABSPATH),'/');
$rest_services_dir = str_replace('\\','/',RESTFUL_WEB_SERVICES_DIR);
$rest_services_path = trim(str_replace($abspath,'',$rest_services_dir),'/');
define('RESTFUL_WEB_SERVICES_PATH', $rest_services_path);
 
// NOTE, See: http://codex.wordpress.org/Custom_Queries#Permalinks_for_Custom_Archives
add_action('init', 'restful_web_services_flush_rewrite_rules');
add_filter('generate_rewrite_rules', 'restful_web_services_add_rewrite_rules');
add_action('template_redirect', 'restful_web_services_exec_service');
 
add_action('init', 'restful_web_services_flush_rewrite_rules');
function restful_web_services_flush_rewrite_rules() {
 global $wp_rewrite;
 $wp_rewrite->flush_rules();
}
 
add_filter('generate_rewrite_rules', 'restful_web_services_add_rewrite_rules');
function restful_web_services_add_rewrite_rules( $wp_rewrite ) {
  $new_rules = array(
    RESTFUL_WEB_SERVICES_URL_PATTERN => null,
  );
  $wp_rewrite->rules = $new_rules + $wp_rewrite->rules;
}
 
function restful_web_services_exec_service() {
  global $wp;
  if ($wp->matched_rule==RESTFUL_WEB_SERVICES_URL_PATTERN) {
    if ($wp->request == 'services') {
      header('Content-Type: text/plain');
      print 'TODO: Generate a list of RESTful Web Services here for this WordPress Blog.';
    } else {
      list($dummy,$service_name) = explode('/',$wp->request);
      if (file_exists($service_php = (RESTFUL_WEB_SERVICES_DIR . '/services/' . $service_name . '.php'))) {
        include_once $service_php;
      } else {
        header('Content-Type: text/plain');
        status_header(404);
        print '404 - Service not found.';
      }
    }
    exit;
  }
}

You’ll note that my function "restful_web_services_exec_service()" is very bare-bones at the moment serving only a plain text message "TODO:" for the path http://example.com/services, and assuming that any path http://example.com/services/{service} will execute a same-named .php file in the services subdirectory, i.e. for http://example.com/services/latest-post it will look for "wp-content/plugins/restful-web-services/services/lastest-post.php" and then delegate all the work to that .php file.

wp-content/plugins/restful-services/services/latest-vidclip.php:

2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*
Filename: latest-post.php
Service Name: Latest Post
*/
global $wp_query;
$post = $wp_query->post;
$file = get_attached_file($post->ID);
if (empty($file)) {
  list($file)= get_enclosed($post->ID);
}
$charset = get_option('blog_charset');
header('Content-Type: text/xml; charset=' . get_option('blog_charset'), true);
$link = get_permalink($post->ID);
$html = &lt; &lt;<post></post><post id="{$post-&gt;ID}">      <video>$file</video>   <link />$link  </post>  POST; print $html;

Here is an example output returned by calling http://example.com/services/latest-post:

1
2
&lt; ?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt; 
<post id="2">      <video>http://videos example.org/video1.flv</video>   <link />http://example.org/sample-post-1/  </post>

While bare-bones, that will meet my needs for the moment since I really only have the need for one service that responds to the HTTP GET verb. However, I can see where I might end up fleshing this out and create lots of helper functions that would streamline the creation of web services for RESTful access to the entirety of the WordPress database. If I do so I’ll be happy to donate this plugin to the community.  In the mean time if you’d like to use this for your own use feel free but caveat emptor. However, if you’d like to use this code to create a plugin to contribute to the community, please contact me for collaboration.  Of course if you’d like to retain me to fully flesh it out for you, I’m always open to that too! :-)

Finally, if anyone who knows the WordPress API better than I do can tell me where I erred in my analysis, and I really hope I did, please let me know so I can architect this thing better. On the other hand, if I was spot-on in my analysis maybe this will help the WordPress team understand what needs to be done so they can empower WordPress to generate any arbitrary content type without having to resort to hacks or bypassing the WordPress index.php startup code. 

FCKEditor’s ShowBlocks Feature ROCKS!

FCKeditor ShowBlocks Feature

FCKeditor ShowBlocks Feature

The other day I wrote about how I was using Dean’s FCKeditor Plugin for Wordpress to solve the problems with WordPress’ default TinyMCE editor eating my hand-coded HTML tags.  Well Joe Banks left a great and detailed comment giving several tips including one about the ShowBlocks button. Unfortunately the default comment system in WordPress ate part of his comment so I couldn’t see what he was talking about and went exploring. 

Well it turns out he was right; ShowBlocks is awesome!  It shows the <p> tags, the <div> tags, and more. Check the screen shot above to see a larger version.

Joe Banks forgot to include his blog URL on his comment (or at least I think he forgot), but I was able to dig it up via a quick google.

WordPress as Wiki?

Interesting.  I guess my recognition of the similarity of WordPress’ new Revisions control to Mediawiki/Wikipedia was not unique.  Andrew Hyde proposed Wikify, as a plugin for WordPress to allow readers to revise posts for facts, spelling errors, et. al. Great idea. 

Of course it becaming popular would be a mixed blessing as we’d have yet another thing for which we’d need to manage spam. :-)

Fixing WordPress’ Eating of HTML div Tags

FCK Editor Logo
TinyMCE Logo

I should have known that not all would be rosy in my move to WordPress. It seems that core WordPress’ default implementation of TinyMCE consumes <div> tags in hand-coded HTML posts for breakfast, lunch, and dinner. That behavior has caused me no end of headache as I’ve tried porting the HTML pages from my old completely hand-coded website as WordPress has mangling those finely-coded masterpieces <sic> beyond all normal recognition.

A little googling and I uncovered lots of discontent on the topic along with a few hacks and a hackish plugin, none of which I really want to implement in given WordPress constant upgrade cycle. The suggestions to fix this in core were so well received that I was surprised that the topic does not appear to have been addressed by the core WordPress team.

However, I was finally able to stumble upon a plug-in called Dean’s FCKEditor For Wordpress that appears like may solve the problem by simply replacing TinyMCE with FCKEditor.  Don’t know if this is going to be my panacea or not, but after installing it the first thing I notice is how FCKEditor’s normal fonts are too small for my tastes (I guess I’m going to have to dive in and figure out how to change that.)

If Dean’s plugin does not ultimate solve that problem I ran across two other plugins worth considering. The heavyweight TextControl Plugin and the lightweight Disable wpautop Plugin. I’ll revisit this issue and those plugins in the future if the topic becomes obviously worth revisiting.

WordPress, Finally!

It’s been a really long time since I last blogged, and it’s all because I got totally fed up with my old blog software and vowed never again to blog until I replaced it with WordPress. Well as you can guess getting around to replacing it took far longer than I planned, but now it is finally here! I’ve still have other non-blog related things that were housed at my domain I still need to fix such as this but now that the domain is switched over to WordPress I’ll have a bit more urgency to get those fixed. I look forward to rejoining to ranks of the blogging community. 

What’s more, a lot has happened since I last blogged so I have lots of things to blog about in the coming weeks and months. Of course I have plenty of billable work that needs to get done so for all those of you who are waiting with baited breath for me to blog (LOL!), future blog posts won’t be coming as fast and furious as I’d like but at least with the new blog they can start to trickle out.

Long Time, No Blog

Yes I know, it’s a blogger’s cardinal sin to post about why he hasn’t posted in a while. But live with it.

The irony is I’ve had so much to blog about. The reason I haven’t is because a while back I finally gave up on dasBlog and decided I’d switch to WordPress before I blogged again. dasBlog makes so many things difficult that are either easy or trival on WordPress, such as commenting and monitoring spam. After years of putting up with dasBlog I just finally got fed up and decided I’d wait to switch to Wordpress. Sadly I’ve waited a long time, and it’s possible it may still be a while before I can move everything over.

Of course I could have tried upgrading dasBlog, but it’s so much harder to enhance dasBlog with it’s limited templating system that requires compiled .NET plugins vs. WordPress’ PHP scripting (reminiscient of classic ASP+VBScript, only better) that I was finally able to shed my programmer’s guilt for not learning how to write usable .NET plugins just as I was able to shed my guilt for never becoming proficient in x86 assembler back in the late 80’s.

I’ve got a huge backlog of posts that are anywhere from 10% to 99% complete, many of which will never see the light of day because they just won’t be appropriately timely enough by the time I’m ready to finish and post them. Ah well, story of my life; I can envision far more than I ever have time to complete.

Anyway, the reason for this post is to introduce the next post about a module I’m writing for Drupal. I’ve spend a lot of time recently with Drupal and am getting quite good at it, even if I do say so myself. I would have liked to have posted several Drupal related posts as a recursor but if I waited for that I doubt I’d ever manage to post about the module!

So without further adieu, on to the next post!

P.S. It may actually be a few days before I get that post finalized, but if it is not posted yes I am working diligently on it so just hold your breath… :-)

Learning about Adobe AIR in Atlanta…

I’m at the Fox Theatre in my hometown of Atlanta today checking out the Adobe AIR Bus Tour Summer 07. It’s nice to be at the first event nationwide. I’m attending at the behest of a friend who thinks it going to be the "next big thing." I’m skeptical. I fear yet another proprietary attempt to empower developers to craft unique custom web interfaces to provide desktop functionality as a layer over web technologies, and that’s not a compliment. These types of things, especially when looking at the black box nature of opaque Flash SWF files, do their best to ignore those things that make the web work, i.e. stateless URL-addressed resources. The reality of Adobe AIR remains to be seen… P.S. It would have been nice if Adobe had consulted me to ensure that this event was more convenient for me. I mean, I actually had to leave my home and cross the street to attend. Adobe, Please! ‘-)

Carson Workshop’s “Future of Web Apps” Conference was Incredible!

Place of the Fine Arts; inside and out The past two days I attended Carson Workshop’s "The Future of Web Apps" presented at The Palace of Fine Arts in San Francisco and I must say it was one of the best conferences I’ve been to in years!  Every one of the speakers was excellent each providing invaluable insight, and the energy level was just electric! 

I really liked the venure too; an ~800 seat auditorium where the entire single-track conference was held. It had so much better feel than getting stuffed into lots of little breakout rooms at a hotel or a convention center.

Not everything was perfect, i.e. not enough networking opportunities, flaky WiFi, and no exhibit hall, but at $2951 for two days the event was otherwise so incredible that I feel really bad2 even mentioning any negatives! OTOH, Ryan Carson was made fully aware of those problems by people other than me and I get the sense that next time it will be corrected.

Lastly, Ryan announced plans to publish online the audio ala T.E.D. for each presentation which the presenter the agrees, which Ryan definitely encouraged! That’s a very "Creative Commons" approach, and oh so right for a Web 2.0 conference (or any other future conference, for that matter.)

Maybe I had such a good time because I was burned out on 12+ years of Microsoft-oriented conferences and just needed something new.

Whatever the case; Bravo Carson, you definitely made a fan!  If you get a chance to attend one of there future conferences on a subject of interest to you, don’t hesitate, don’t think about it; just do it! I’m sure you won’t be disappointed.

Footnotes

  1. The $295 price was also so very in line with the ethos of "Web 2.0"; created high value for little money, and benefitting from the goodwill that creates. I so totally feel like they practice what they preach at Carson!
  2. Of course if it had been a Microsoft TechEd for $1000+ I was have been totally pissy about any lack of perfection, but not at $295 for the quality that Carson delivered!