Liberating WordPress

Posted on Sun, 15th January 2006 at 06:00 under Software, WordPress, Education, Coding, Publicity

A Libertus brand mini-project to re-jig the WordPress security system and add a forum-like feature I’ve wanted from the first day I used it.

Primarily this post and the comments are my work notes. Those interested in my approach to code surgery, or code surgery in general, are encouraged to observe but comments shall be held until the project is completed. I’m sure my rather unconventional use of neuro-linguistic programming to reinforce design metaphors will draw some discussion.

To anyone who sees their own code here, behold the true meaning of the phrase open source and sieze an opportunity to reap some of the benefits you so richly deserve for your gift of generousity. Thank you, whoever you are. Before taking any criticism personally, consider why I’m putting such considerable effort into this.

Requirements

  1. Attributable identities
    you are whom you appear to be
  2. Limited self-censorship with moderation
    to combat staircase wit
  3. Limited access to administrative interface
    especially my handy Zeitgeist screen which shows all recent posts, comments and commenters, with extensive linkage
  4. Protected credentials
    no-one else can appear to be you
  5. Comment styles by user level
    and do something about the CRAP comment styles, especially for lists
  6. Remove extranous security levels
    reduce margins for error
  7. Ensure I retain and reserve all publication responsibilities
    otherwise this ceases to be entirely my blog and therefore entirely my liability

Deliverables

  1. The software itself, launched
  2. A manual and training guide, in progress

Highlights

  1. Impatience, Laziness and Hubris - a tribute to Larry Wall
  2. Meeting my past self and repelling the French invaders
  3. Comment chronology capers causes consternation, and decisive action from the design team.
  4. Programmers find manual readers sexy
  5. No-one tells God to slow down, cowboy!

57 Responses

  1. Attributable identities

    People whose identity I can verify may, by invitation or on request, have a registered name. I shall set and provide a password by private means. Any future activity using the credentials I have supplied will be considered activity by the person whose identity I originally verified until I am informed by that person that the blog identity is no longer secure.

    If you use the site while logged in, you will only be able to post comments using your registered name. Unregistered users may post using any name except that of a registered user. You may not change your registered name.

    Reply
  2. Step 1

    Make a backup

    wget used to mirror the website files

    Place website under formal revision control (finally)

    cvs as ever for revision control
    I know there are superior tools but unless someone shows me how to use one, I’m stuck with what I know to work.

    Reply
  3. Step 2

    Analyse the security code

    wp-register.php to assess for deletion, as I don’t want to allow anyone to register themselves. OK - registration can be disabled with an administrative setting.

    wp-login.php to assess for change. Doesn’t look like it’ll need any, nor do I want to do twiddling in there without cause!

    I’m happy with the existing user management code. All I need is to set the user levels to my liking. I only need two; me and anyone else.

    All user levels other than 0, 1 and 10 will be removed. Introduce user level -1 for machine users such as search bots.

    Analyse the front-end code

    User level 0 (anonymous, unregistered or not logged in) can never edit comments. User level 1 (registered and logged-in users) shall see an “edit” symbol next to their own comments that they are allowed to edit. User level 10 (me) sees it for all comments.

    themes/default/comments.php seems a decent starting place. I’ve been in here before. Function edit_comment_link() is what I’m after, in wp_includes/template_function_links.php. This references function user_can_edit_post_comments() which is the security check I need to adapt. The check I require is user_can_edit_this_comment().

    Off to wp_includes/functions-post.php to find that a user can edit comments for a post if they can also edit the post. No use to me, but I still need to know how the security check is implemented. On to function user_can_edit_post() which has some user data checking to analyse.

    I need to know the currently logged in user ($user_id), their level, the user who posted the comment, when the comment was last edited and the current time.

    wp_includes/functions-post.php is where I shall begin. A chucklesome extract from the code, given that the permission system is anything but simplistic:

    // query user capabilities
    // rather simplistic. shall evolve with future permission system overhaul

    It’s going to evolve, for sure.

    sleep

    Reply
  4. Now, where was I? Oh yes.

    Step 2 (continued)

    Analyse the front-end code

    A function is required to determine whether the current user may edit any particular comment. The function will be user_can_edit_comment($user_id, $post_id, $comment_id) in source file wp-includes/functions-post.php. Although post ID can be derived from comment ID, I prefer both to be passed as a sanity check.

    Code for new security levels

    wp-include/functions-post.php attacked first.

    Function get_userdata() is called a lot - what does it do? Returns the cached user table row for the supplied ID - perfect. Likewise get_currentuserinfo() for the current user, although it populates global vars and returns a string based on some “idmode” condition, which looks dodgy.

    Implemented simple security model; God, known to God, unknown and special. Updated all security functions to refer to model. Began code for user_can_edit_comment($user_id, $comment_id=0, $post_id=0) when I discover the global $comment - a variable set during the comment output loop to each comment against the post in the table. So, a comment id of zero means “the current global comment” likewise for post id of zero. Makes the simple case very simple. What comes out of the comment table is now relevant. I need an author user ID if possible.

    WordPress already must do some kind of user matching in order to display “Your comment is in moderation”. I want to link in to that. Back to wp-content/themes/default/comments.php and the front-end comment display loop. Ideally, I would like comment editing to be done in-place, like posting a new comment, without requiring the administration interface. I know WP doesn’t work like that, so the first priority is making the link to the admin interface and allowing known users self-editing priviledges.

    Hmmm… the condition is $comment->comment_approved=='0' which means that user matching must be done in the database query so I need to track it down, starting with how comments.php is executed. It is included wherever function comments_template() is called. Predictably enough, it is called in many of the theme pages and by wp.php, which is where I’m looking next. Not so fast! The SQL is in the comments_template() function, selects all columns from the comments table and does indeed do user matching based on the current user’s name and e-mail address against that of the comment. That is sufficient for unknown users but insufficient for known users. I need the author user ID - is it actually stored in the table?

    Via wp-admin/install.php to wp-admin/upgrade-functions.php and function make_db_current_silent(). I hope to find the table definitions there. Yep. At the top is included upgrade-schema.php and there we find CREATE TABLE $wpdb->comments and yes, there is the column user_id. Sweet. I’ll look in my database and see what range of values it has.

    Just as I expected. All comment user IDs are 0 except God’s, even those of a newly registered test user I logged in and commented with. How not to code, indeed. So, I’ll have to fix the comment user ID storage before anything else.

    Reply
  5. Step 1a

    Fix Wordpress bug

    Why put a user_id column in a table then not store known user IDs in it? My task is not to ask but to mend. I already know the file I need is wp-comments-post.php. The user ID is certainly part of the data gathered, so on to function wp_new_comment() which brings us neatly back to the file already under modification - functions-post.php. I love programming!

    Odd. The code looks fine and seems to try to handle user IDs. Either the storage fails or the incoming value is incorrect. Verify the storage first. The user ID is processed through a ‘pre_user_id’ filter so see if any are set up by default. File wp-includes/default-filters.php contains no ‘pre_user_id’ filter, so examine function apply_filters() to see what would be returned for a user ID, presumably numeric.

    Oooh… wp-includes/functions.php sounds like I’m getting fundamental. Any changes here I’d expect to have major consequences. Phew - it just returns the the passed value if no filter is found.

    Back to analysing the storage path. A promising call to function get_userdata(). Aha! The approval code where new comments are placed in moderation if necessary. I need to make changes here anyway, so bookmark this and function check_comment(). Still looks like the user ID remains intact as passed. Looks like the post_author column in the posts table is a user ID, as a comparison is made. And the user ID is, without a doubt, intended to be stored in the table.

    Odd. Perhaps the input value isn’t being set correctly. Perhaps my assumptions are wrong. I assumed that the user id passed around in the script would match that of the logged-in user. Verify. Back to the database.

    OK, logged in as user Paul, which has user ID 4. Post a comment and check the database. Comment entry box shows I’m logged in as Paul and doesn’t allow entry of details (which is good - no change required). Comment posts ok with no moderation required. Giggle at Google ad for “Gay Galapagos” but resist temptation to click.

    Also odd is that nowhere in the administration interface is made mention of the fact that the comment comes from a registered, logged-in user. Might explain why all the user matching is done by name and e-mail, which effectively means name on my blog. My nose starts to detect the faint whiff of a major design flaw or my own bullshit. I soldier on regardless, holding my breath a little.

    Now to look at the database record. I predict user_id of 0 where I expect user_id of 4. Roll the dice… SELECT user_id WHERE comment_author = ‘Paul’; results in… 4s. Wow. I must be blind. Look more closely.

    Sigh. There it is… on the second page of the query output right at the bottom… ‘Paul 4′. What a twit. I just wasted hours delving into perfectly working code because I’m prejudiced and lazy. Hey ho. Now, back to work.

    Complain about PHPMyAdmin bug

    And apologise to the WordPress coders. The bug is that PHPMyAdmin rewrites my SQL queries without my permission adding ‘LIMIT 0,30′ automatically. The result I was looking for was result 60. I am used to being in control, not countermanded by my database tools. I can write SQL perfectly. Perhaps there is an option that can switch the damned feature off.

    Reply
  6. Step 2 (continued… again)

    Complete security code

    Resume work on new function user_can_edit_comment() knowing that all data I need are available.

    The security check function complete, update the calling function edit_comment_link(). Done. Time to check CVS to see the damage done, test the changes on my development blog, then commit.

    Wow, all that time for ~40 changed lines over 2 files. This had better work. :)

    Enable administrative interface to comment editing

    The edit link suitably appearing, now users to whom it appears should be given access to the comment editing interface. I don’t care if their comment is in moderation, only that they are allowed to submit an edit.

    File wp-admin/post.php, a multi-purpose URL-driven script, with action=editcomment. This currently refuses access to my known, registered user. Hmm… two different global variable names used to hold comment data. Adapted my security function to select between them and use my function to control whether a user may edit a comment.

    The more I look at the code, the more poncey it becomes. It works, but with a touch of panache works better. For instance:

    	user_can_edit_comment($user_ID)
    		or die( 'You are not allowed to edit that comment.' );
    

    or

    	if (!user_can_edit_post_comments($user_ID, $commentdata['comment_post_ID'])) {
    		die( __('You are not allowed to edit comments on this post.') );
    	}
    

    I meet my past self

    So I’m browsing through wp-admin/post.php and find that I’ve been here before and not in a very good mood. Verbatim.

    	if (user_can_edit_post_date($user_ID, $post_ID) && (!empty($_POST['edit_date']))) {
    		// Note by PM, December 26, 2005
    		// This code was clearly written by an arrogant French person due to the
    		// use of French abbreviations with no explanatory commentary.
    		// aa = ans = years, jj = jours = days
    		$aa = $_POST['aa'];
    		$mm = $_POST['mm'];
    		$jj = $_POST['jj'];
    		$hh = $_POST['hh'];
    		$mn = $_POST['mn'];
    		$ss = $_POST['ss'];
    
    		// Fix by PM, December 26, 2005
    		// Also update comment_date_gmt which was inexplicably missed out
    		// Removed the horrific "date validation" code. Now falls instead on
    		// the strtotime() function (via the current_time() function) to check
    		// for valid input.
    		$new_date = current_time('mysql',0,"$aa-$mm-$jj $hh:$mn:$ss");
    		if(!$new_date) die('Invalid timestamp supplied for comment');
    
    		$new_date_gmt = current_time('mysql',1,"$aa-$mm-$jj $hh:$mn:$ss GMT");
    
    		$datemodif = ", comment_date = '$new_date', comment_date_gmt = '$new_date_gmt'";
    

    I have to re-issue my security check here, in action=editedcomment but it doesn’t load the current version of the comment - it never needed to. I’m into major change territory, so it’s time to…

    sleep

    Reply
  7. Slow down, cowboy!

    Aside: no-one tells God to slow down, cowboy! when posting comments (file functions-post.php function wp_new_comment()). Remove flood defences against God. God doesn’t flood things. Anyone who says oh yes He does gets the response unless you give Him a reason. :)

    Aside aside: Of course, I am not surpised to find the word cowboy in the WordPress source. Now that I’m working on it, it is anachronistic, so out!

    Aside aside aside: While removing the flood defences, I cannot help but be tempted to other unGodlike security checks. I give in and do one. God comments are automatically approved as are those of registered users. Anyone else has to go through the check_comment() function.
    Aside aside aside aside: This is leading directly to what I’m supposed to be doing. Odd. Continue.

    Now! Protected credentials!

    Step 1

    Analyse the code

    Umm… first, I’ll just commit those aside changes for testing. CVS reports only file function-post.php with a few lines changed. Removal of flood defences against God and automatic approval of new messages posted by God and registered users. I also prettied up the flood defence message to include the remaining time the user has to wait. Commit and test.

    God can comment at any speed - ok. Other users must wait 15 seconds between comments but gets a timer - ok. Registered user comment auto-approval - ok. Hmm… why can I still see “Your comment is awaiting moderation” when I’m logged out? Posting a comment as “nobody” clears it. Niggle, doesn’t require repair. Nobody posts are still auto-moderated. Edited comments are still auto-moderated. Sweet.

    What do I get for free?

    My aside into the flood defences required the code analysis necessary for the next bit - disallowing nobodies from impersonating somebodies. The first free test is to just do it - be a nobody trying to be a somebody. What does WordPress do? Well, what else does it do? The comment goes into moderation. So it is down to God to know the difference between a registered user and a nobody impersonating a registered user. No, that’s too much like hard work. Even though I plan to highlight the difference in the admin interface eventually (as it is simple to do so), why should I even have to deal with it?

    Investigate the check_comment() function in file comment-functions.php. No, this isn’t the right function. I don’t even want the comment to be inserted if any typed credentials clash with any registered credentials. There’s already a condition checking for a registered user - hook a new SQL query into the false path of that.

    First cut done and ready for testing. CVS looks good. Commit. Over to laptop for testing.

    Test and polish protected credentials

    Oops… check in changes made during last testing round. No damage. Now, a nobody should not be able to use God’s name - ok. But now nobody can’t post! Debug printout time for my SQL. Oh… blank e-mail and URL are matching! Duh! Twiddle, twiddle to the SQL to eliminate blanks and… damn those header() warnings! OK, nobody can post as a nobody. Nobody cannot post with God’s name. Nobody cannot post as known user name - ok. Email - ok. URL - ok. Now, there are still a bunch of things an arsehole can do to impersonate someone else but at least the simple ones won’t bug me as a moderator. Sweet.

    Oops! Quick hack based on server admin email address to disable footer Google ads on the test blog. Consider promotion to mainline after analysis of live webserver.

    Sit back, glass of wine, smoke a phatty and reflect

    Ahhh… let that all sink in for a bit. Go review other things that have been backing up. Let the work settle.

    Hehehe… and post a link to the WordPress user forum. Nowt wrong with a little publicity, is there? ;)

    sleep

    Reply
  8. Limited self-censorship with moderation

    More often than not, I think of how to say something better moments after hitting the “Submit” button. In face-to-face intercourse, the phenomenon is called “staircase wit” and is amusing because of the immutable nature of the past. On the internet, everything is mutable so disallowing eloquence through technological contraint is unfair.

    I want to permit people I know to participate with the same tools for self-expression I possess - immediate publication and the ability to revise previous utterances. On the other hand, I do not want anyone to arbitrarily change the meaning of past utterances as that is a known temptation to abuse and the burden of resistance should fall on me alone.

    I shall adopt the following policy (in addition to that imposed by the design of WordPress):

    1. All registered users may publish new comments without moderation.
    2. All registered users may edit their own comments without moderation under the following circumstances;
      1. the comment was originally published no more than six hours ago, or
      2. there are no subsequent comments to the same post.
    Reply
  9. Step 1

    Analyse the comment editing code

    I’m in wp-admin/post.php?action=editedcomment. The user has submitted a comment edition. Should it be stored and, if so, how?

    Some readers may ask “but you’ve already done the security check in the editing interface! how can the user submit an edition to a comment if you don’t link them to the admin interface?” An excellent question. The answer is that the command to submit an edited comment is issued via a URL, and a URL can come from anywhere at anytime for any reason. Each time WordPress processes a URL, it does so for the very first time. That means the security check to allow the user access to the comment editing interface never happened. If there were no security check at comment submission, a malicious person could post a comment at any time simply by issuing an appropriately formed URL. When programming for the web, assume nothing, forget everything.

    So, back to the code. It loads the submitted comment into variables, tests whether the user is allowed to edit comments, deals with the special case of the user being allowed to set comment dates (containing my Francophobic notes), applies pre-save filters to the comment text, issued the UPDATE query, redirects the browser back to the posted comment and finally calls the plug-ins for edited comments.

    Hehehe… I’ve just had an evil thought. I may be able to exploit the two global variables used for comment data to effect a neat comparison-based response in my security function. global $comment holds the current comment in front-end code, global $comment_data holds the current comment in the admin code, and my security function already has to deal with that because it is called from both. Now, the existing comment editing code doesn’t load the data for the comment being edited because it doesn’t have to. My security requirements now force the issue - the existing comment data, at least the user_id, must be available. However, I also have moderation requirements related to the difference between the existing and edited comments, so if both were available in my security function, all the work can be done there. So, if I load the comment from the database into $comment and the submitted edited data into $comment_data, I create a unique corner-case where both are set in my security function and this will flag a different decision process.

    I’m pretty much decided on what the surgery will be now. I’ll just quickly check around the area I’m working on to see if there’s anything obvious that I will affect, or can also easily improve.

    There are a number of actions related to comments;

    • editcomment (comment editing screen)
    • confirmdeletecomment (confirmation screen)
    • deletecomment (deletes comment from database)
    • unapprovecomment
    • mailapprovecomment
    • approvecomment
    • editedcomment (updates comment in database)

    Now the every time is like the first time nature of the web is an advantage. Any action that includes admin-header.php is a user interface, so those that do not are functional. I’m not changing the database structure or the structure or meaning of any global variables. It is safe to proceed, after checking that the lastest revision is safe in CVS. Good thing too - I’ve made changes since the last check-in. I don’t want to mix two different surgical interventions! It’s OK - my tiny changes from last night to allow access to the admin interface. I’ll commit them even though they are untested in the computer, as all that code is likely to change now.

    So the design is; load the submitted comment into global $commentdata, adapt current code to work like that, then code to load the database comment into global $comment, modify the security function for a comparison check, then implement the function to guard comment editing.

    Reply
  10. Step 2

    Modify the code

    For loading the submitted comment data, block-copy and adapt similar code from function get_commentdata() as I’ll be calling it and therefore need the same data structure.

    No, fuck this. This code is too manky to be polished into working. Rip it out and rebuild from scratch.

    Much as I love PHP, it does have some irritating language limitations, such as i want to construct this array out of these keys in that array, with no thing for non-existent keys. I can’t find it in the manual. compact() is close.

    Code to load submitted and existing comments and perform security check done. Now to deal with the comment date, which will be the current timestamp, unless the user is allowed to set a specfic date and has provided a valid specific date.

    And here are these fucking French date part abbreviations! They must be names of controls on the source form. Mental note to clean that shit up before leaving. Whoever allowed that into the product is a twit, and the arrogant bugger who wrote it deserves to be spanked. Écrivez en Anglais, paysan!

    Date code rewritten, on to the content filtering, which remains as is, and the SQL UPDATE query which now always includes comment dates. Tidied up the browser redirect, from

    $referredby = $_POST['referredby'];
    if (!empty($referredby)) {
    	header('Location: ' . $referredby);
    } else {
    	header ("Location: edit.php?p=$newcmt[comment_post_ID]&c=1#comments");
    }
    

    to

    // redirect browser to whence it came, or to editing the comment
    header( 'Location: '
    	.	($referredby = $_POST['referredby'])
    		? $referredby
    		: "edit.php?p=$newcmt[comment_post_ID]&c=1#comments" );
    

    The SQL is pretty easy - just add both dates. However, all the parameter values reference the old variables, and I can’t be arsed typing in commentdata a lot, so I create a smaller variable name to alias the enforced global variable name.

    Hmm… more complications. Only God can change author names, emails and URLs. No probs, just build the query dynamically.

    Happy with the (thus far not executed) new comment editing code, I proceed to the security check function and adapt it to the corner-case of an actual comment edit, which may involve comparison. Oh! Could this be done by the database - refusal to update if the old comment user ID does not match the new comment user ID? Think it over.

    Well, a mismatch in the user IDs would indicate some critical error and definately not the expected operation of the script. I think it is acceptable for a condition of comment update for the new and old user IDs to match. There is no function available which allows them to change. Go with it.

    By expressing the user ID check as a condition in the SQL UPDATE query, any attempt by a non-God user to edit a comment that does not bear their user id will fail enigmatically with Database cannot update comment. I like that. Now, review the security function to ensure it still works when called from this new environment.

    All the security function needed was a sequence change - give primacy to the existing comment when checking user id. Essentially the database query reinforces the function, so even if the function is compromised, the database is still protected. I can’t think of anything else that is needed. Registered users should now be able to edit their own comments, and if they do, they will automatically go into moderation (a development safety feature). Time to…

    Test

    Check damage. CVS reports 2 changed files; post.php with rewritten editedcomment action (lines 643-698 now 643-729) and functions-post.php with 1 changed line.

    Changes committed, it’s over to the laptop to test the new code against the development blog.

    It’s always the first test that reveals the most. A couple of typos in my code. Editing a comment doesn’t work - I’m still locked out so last night’s changes didn’t work. I find that the variable name $comment is potentially both a global variable and a local variable in the same scope. I can see that the theme support for WordPress was a feat of hack-and-slash design, not just coding. Tidy by correcting all traces of variable $comment in file edit-form-comment.php. If $comment is being set unnecessarily, and it was, my security check function is bound to fail. Test again.

    Still no. Need to know if security function is working correctly. Add debug output. Fuck! Change from PHP’s wannabe-object-oriented syntax to the true array syntax and it works - the comment editing interface appears. Test on.

    My first observation is that editing of the author’s name, email and URL is allowed and should not be, but that is to be fixed in the next part. Can I edit and successfully update the comment? No, I can only edit comments that exist - my own error message. Back to the code. Yes, variable name typo. Correct and re-test. That fixed, the next error - function name type. Correct and re-test. SQL error - blank comment id - typo. Correct and re-test. Might have worked but debug output buggers HTTP headers. Remove debug output and re-test. I think the comment is saved but my rewritten redirect code is faulty. Debug. PHP interpreter gotcha (”literal string, concatenate, tertiary boolean condition, true string, false string” requires the entire tertiary conditional construct in parentheses to work as expected). Re-test. Redirect works and comment is in moderation, but now PHP complains of my using an object as an array. No surprise - that’s my security function. Onwards and backwards.

    Quick but functional hack to the security function. Getting the comment user id is more complicated now but still comprehensible. Fixed and now my edited comment shows as being in moderation. Now for some abuse testing via URLS. Edit a comment that doesn’t exist - fails. Edit someone else’s comment while logged in - fails. Edit a comment while not logged in - goes to login screen and then to comment editing screen on successful login. Sweet. Commit the changes and on to the next bit.

    Reply
  11. Limited Access to Administrative Interface

    In public performances, a lot happens behind the scenes. A “backstage pass” gives one privileges a mere audience member lacks and is only granted to those who a) have a use for it, b) will use it wisely, and therefore c) will appreciate it. They are not handed out to all and sundry as much mischief can be made of such priviledge.

    So until I complete my front-stage comment editing I must allow people a limited access to the backstage - the WordPress administrative interface. This is already supported, of course. The problem is one of surplus - too many knobs and buttons are displayed, some even that the system knows the user is not allowed to use. That has to go.

    My policy shall be as follows. A registered user may use the following administrative functions;

    • Zeitgeist (it’s cool)
    • Edit comment (of course)
    • Post management (read only)
    • Category management (read only)
    • Comment management (read only)
    • User personal profile
    • Logout

    The appearance of a control shall be deemed authority to use it.

    Reply
  12. I am aware, at this point, that I have not completed all my required functionality for comment editing, specifically the six-hour grace period. I have also not extensively tested the new comment date setting code or regression tested comment editing in general. These are small tasks as I know what code changes were made and their effect, so they will be left to simmer and completed once the admin interface changes are made, also a small task.

    Step 1

    Analyse the code

    These are the fun parts; taking things out. Getting the admin interface into a form I can confidently offer to registered users is an exercise in disabling and removing things. From those available to registered users…

    • Zeitgeist has a SQL bug when there are no recent comments (empty IN()) so it will need a look before delivery. There is a small todo list for Zeitgeist I’ve been itching to get to so this is my chance. Also take this opportunity to rename “Dashboard”.
    • The “Write” option goes completely.
    • Comment moderation goes from the “Manage” menu.

    File wp-admin/index.php seems the place to start for messing with the menu system. This includes admin.php which includes menu.php and bingo!

    Oh my God! User level numbers everywhere and not a symbolic constant in sight! The user levels really were an afterthought. Sorry guys, but this gets rejected. All those numbers have to go.

    Check-in tried and tested code

    CVS reports 3 changed files; edit-form-comment.php (1 changed line), functions-post.php (7 changed lines) and post.php (10 changed lines). Not bad for the leap from freshly-written to working. Commit. Back to Linux box for code changes. CVS integration in KDE is nice and usable, if a little rough around the edges but I’m still learning. WinCVS is clunky but pretty much spot-on in its support for moment-by-moment, random use.

    Reply
  13. Step 2

    Tidy up mess left by WordPress programmers

    I mentioned I wanted a simple security system. A user may have one of 4 security levels; USER_UNKNOWN, USER_KNOWN, USER_GOD and USER_SPECIAL. WordPress has 0, 1, 5, 6 and 8 from the looks of this menu. So, I need to include my constants into this file and set the level for each menu accordingly. Ah well, of course that depends on what the number WordPress have used actually means. Is it the actual user level number allowed to use the menu or something else? The comments say it is the minimum level, so I’ll go with that. I can use my constants direct but I don’t think they’re available at this point in the code path.

    Before creating a new file, find the WordPress constants file and put my constants there. Blimey… not obvious. Eventually I find a promising route through wp-config.php to wp-settings.php. Good, wp-includes/functions-post.php is always included so my constants are already available. On to the menu.

    Hmm… file upload. Anyway, menu changes made, review and send for testing.

    Testing limited administrative menu access

    Registered user menu looks good. Zeitgeist ok. Manage posts ok. Manage categories ok but pointless. Manage comments not ok, as edit link is not available for my comments. Investigate and repair.

    File wp-admin/edit-comments.php, looking for the comment query and output. I need the comment user_id available and a call to my new security function. Done. Retest. Oh wait! There are two modes so two security checks. More change. Ok, done this time. Commit and retest.

    Hah! Edit comment appears. Fuck! You do not have sufficient permissions to access this page. I’ve screwed the menu. Investigate. I remember seeing that somewhere recently, maybe the menu. Yes, there it is, at the bottom, part of a security check that I cleaned up. Better check that I didn’t break that. I have been able to access other admin pages just fine, so maybe this is just security levels. However, there isn’t a security level for editing a comment, so check the security function itself, user_can_access_admin_page().

    Now, this is exactly the kind of nonsense you end up writing if you don’t have a clear idea of what it is you’re trying to achieve. How the fuck am I supposed to make sense of this? My problem is that this function returns false, so where is my problem? It returns false in two places and true in two places. Now, a likely problem is the use of a “less than” operator when dealing with user levels. Analyse that logic first.

    When does user_can_access_admin_page() return false? Nevermore!

    If user level is lower than that needed to access parent page, return false.
    Umm… do I really need this? Perhaps I can trust that if I don’t offer the user the page, they won’t be tempted to access the one’s they’re not shown and if they do, the per-function level security will kick in. I’m tempted just to return true and be done with it. Ah, fuck it, why not? Fix it later.

    Comment editing interface - controls

    Now my registered user is editing his own comment. He can see text inputs for his name, e-mail and URL. There is the comment text and an “Edit Comment” button. Then there is an “Advanced” panel with a comment status radio group and a delete comment option. Nowhere is the title of the post displayed.

    My user is not allowed to change their details except through the user profile page, so the first three can go, as can the advanced options as they are disallowed. I shall add the post title.

    File edit-form-comment.php (via post.php action editcomment) needs user level checks. Plump for simple is_user_god() around the bits registered users don’t need to see namely the fieldsets “namediv”, “emaildiv”, “uridiv” (lol, you’re a div) and oh! look! there’s a “user level > 4″ check around the comment date. I’ll cut and paste with that. Quick hack for the post title. I’m expecting to do some polish on the test platform, so commit and go there.

    Hmph! Forgot to pass the user ID to the security check functions but my post title SQL worked first time. Fix and re-test. Put focus in comment text first, and say that user is editing the comment. Done.

    Expand testing to regression level. First ensure that all God functions remain intact. Login as me. A quick shake-test shows that all the functions I care about appear to still work, as expected.

    Reply
  14. Protected Credentials

    In order to gain the power of authenticity, registered users must relinquish the liberty to impersonate. Unknown users have no authenticity and should not be able to gain even the whiff of it by virtue of their freedom to impersonate.

    Unknown users are not allowed to use the names, e-mail addresses or URLs of any known users when posting comments.

    Reply
  15. Comment Styling

    Now that there exists a clear distinction between known and unknown people, it is reasonable to reinforce that distinction visually, a common idiom of discussion software. It should be obvious to any participant which comments come from themselves, the host, registered users and others.

    Use CSS to avoid enforcing any particular visual implementation.

    Reply
  16. Impatience, Laziness and Hubris

    This is just the kind of wooly, vague specification I hate to work with. There is no clearly defined outcome, so the client clearly doesn’t know what they want. I know what I think they want but I don’t know if what I think they want is really what they want because I know they don’t know what they want. Frustrating, eh? A common idiom of clients, I tell you.

    To make matters worse, they do know they want technical constraints. No idea of the outcome but they do know they want to use CSS. Pah! Tables are easier to hack with in front of the client, so they’ll save me time during the inevitable tweaking after delivery. They don’t know what they want, after all, so how can they decide on CSS?

    A useful approach in these circumstances is to just build something to show the client and hope it yields some creative feedback. That doesn’t mean build blindly, though. The client’s lack of direction is not an excuse to lower standards of quality. Thinking through the problem should yield a design that, although not ideal, should be adaptable to any reasonable change requested by the client. If not, then you definately didn’t guess your client’s intentions properly and you have to start again.

    Design

    [Editor’s note: apologies for the table formatting, it really sucks. I’ll polish later.]

    Grumble. This isn’t my job. I’m a coder not a mind-reader.

    OK, visual distinction for comments. User levels unknown, known and God. User levels apply to both comments and readers, so there is an obvious decision matrix.

    What will they see?
    Comment
    Reader Unknown Known God
    Unknown ? ? ?
    Known ? ? ?
    God ? ? ?

    This is like playing sudoku. Fill in the blanks so that they add up to something meaningful. First the obvious ones. There is only one God, so God reading God’s comments sees “me” or “mine”. Unknown reading known’s comments sees “known”. Anyone reading unknown’s comments sees “unknown”.

    What will they see?
    Comment
    Reader Unknown Known God
    Unknown unknown known ?
    Known unknown ? ?
    God unknown ? mine

    What should known and unknown see for God’s comments? God, of course! What should God see for known’s comments? Known, of course! This is easy!

    What will they see?
    Comment
    Reader Unknown Known God
    Unknown unknown known god
    Known unknown ? god
    God unknown known mine

    So, one last cell to fill should take no time. What does a known see for another known’s comments? Known, of course!

    Oh… wait. What if a known is looking at one of their own comments? Should’t they see “mine”, just like God does? And what about unknowns who always use the same credentials? They can also see their “own” comments although the connection is weaker than for knowns.

    So, factoring all this new complication in, I get:

    What will they see?
    Comment
    Reader Unknown Known God
    Unknown unknown
    ?
    mine
    known god
    Known unknown known
    ?
    mine
    god
    God unknown known mine

    Aw man! The last step introduced a new question mark! I’m further away from the solution than I was before. I need to take a step back.

    I started with “unknown”, “known”, “God” and 9 question marks. I now have “unknown”, “known”, “God”, “mine” and 2 question marks. Co-incidentally, both question marks share “mine” in common. Is that a clue to something?

    But of course. It means that there is another decision to be made as part of this decision. Whether a user “owns” the comment they are looking at is relevant, just as it was for the comment editing changes. There is only one God, many knowns and many more unknowns, all different, but they all share the concept of “mine”. If they all share one thing, shouldn’t I focus on that first? They all share the comment in question.

    So, either the comment in question is mine or not. That seems to me to be entirely separate from what user level I am, so treat it as such. Now, if we remove “mine” from the decision table, what is left?

    What will they see?
    Comment
    Reader Unknown Known God
    Unknown unknown known god
    Known unknown known god
    God unknown known ?

    If you can’t see the pattern in this table or cannot fill in the missing blank, do NOT stick your hand up. You’re on the wrong blog. You need to find something to read that is less intellectually demanding, such as your navel.

    So now I have two simple decisions; does the reader own the comment, and what user level owns the comment. Hey! Maybe CSS would work for this. I just remembered something I read in that dreadful document they call the standard. An element may be styled with multiple CSS classes and selector rules can be written to control the presentation of multi-class elements to a very fine level. However, no such fine level is required here. When a comment is emitted the division in which it is wrapped can be classed according to both the comment user level and the reader ownership. If the classes I output are different depending on what decisions are made then the client can get a visual distinction of their own pleasing and in their own time rather than having to bug me to tinker with the tables code for days getting it “just right”. Wow. Multiple CSS classes. I bet the client never thought of that.

    Will this work? Write it out in pseudo-HTML and see…

    Me reading gets

    [div class='comment god mine'] Libertus says: woo!
    [div class='comment known'] Paul says: yeah!
    [div class='comment'] Nobody says: alright!
    

    Paul (a known user) reading gets

    [div class='comment god'] Libertus says: woo!
    [div class='comment known mine'] Paul says: yeah!
    [div class='comment'] Nobody says: alright!
    

    Nobody reading gets

    [div class='comment god'] Libertus says: woo!
    [div class='comment known'] Paul says: yeah!
    [div class='comment mine'] Nobody says: alright!
    

    Anyone else reading gets

    [div class='comment god'] Libertus says: woo!
    [div class='comment known'] Paul says: yeah!
    [div class='comment'] Nobody says: alright!
    

    Check the brief. Could this be adapted to what the client doesn’t really know they want? Looks that way, plus it’s simple and can be extended to cope with almost anything their fevered imaginations can come up with, like changing the colours. Maybe CSS won’t be a technical constraint after all. It would please the client to know that they were right about something. Not that I’ll tell them, of course. If it wasn’t for all my design work that they couldn’t possibly do, they wouldn’t have known for sure.

    Yeah, I can code from this. Doddle. All the client needs to do is write the stylesheet. Muahahahaha. Ker-ching!

    Reply
  17. Step 1

    Assess resources

    Design - CSS class for comment divisions by comment user level and reader level - check. Data - comment data, user id - check. Cvs clear - check. Code points - anywhere a comment is output. Hmm… I don’t think that is just one place.

    Analyse the code

    Find all the places where comments are output. OK, so what does output of a comment look like? Start with the common case - the blog front-end. I recall something about a comment template.

    Files wp-includes/comment-functions.php and template-functions-post.php are my first candidates. As the comment template is used by themes, I also target all relevant theme files; comments.php and comments-popup.php.

    Start in the theme, during the comment output loop for a post. I’m pretty sure that’s comments.php. Yes, this is it. And right away evidence of ignorance or laziness introducing problems that otherwise wouldn’t exist. Please do not load this page directly. Why not? Include-only files should not impersonate PHP scripts. Name include-only files *.inc. Problem solved.. In fact, mental note, do just that, if time permits.

    Now, comment output. OH SHIT!!! What the hell is that? There’s a PHP script processing start tag hanging in space! Right in the middle of a PHP script! No processing stop tag, similarly indented or otherwise, in sight. JE…!!! OK. OK. Calm down. Look again. Phew. It’s OK - just funny indenting.

    OK, if there are comments; output the comment count header, for each comment; output comment class, aha! Nice - already built. All I have to do is affect the value of $oddcomment. And its name, obviously. There is nothing odd about any comment except its sequence number, unless I am the author.

    Reply
  18. Step 2

    Create Conditional Comment CSS Class Code

    Now, what is this $oddcomment nonsense? Oh lovely, children have been writing code. Rather than test the oddness of the comment sequence value, the programmer has used a textual toggle flag that happens to co-incide with the necessary alternating CSS class name and blank. Who reviewed this? Instant rejection for laziness, obfuscation and simple, basic wrongness.

    OK, alternating comment class, comment user level class and reader comment ownership class, all in one string.

    Alternating is easy - so long as there is already a counter. If not, make one.

    Clean your room!

    Oh dear, I just have to clean up the this isn’t really an include file mess. It is too distracting. And because I am forced to do this, my resistance to changing the die() message is gone. It now tells the truth.

    if ('comments.php' == basename($_SERVER['SCRIPT_FILENAME']))
    die ('Naughty user! You caught us being lazy and stupid! Please die( "Thanks!" );');
    

    If you are going to chide the user for doing something that is only possible because of your error, do it properly. Don’t beat about the bush.

    With that monster tamed a little, get something done about the classes.

    What does comment output look like?

    Jeez! Jeez Louise! The comments are output as an ordered list! No wonder they look like shit. Come on guys! What where you expecting people to leave as comments? “Woo! Yeah! Alright!”? Look at how people are using your software now.

    Shit. What can I do about this? I understand the ordered list design but it disturbs me somehow. It wasn’t what I was expecting, but if I change the element type that comments have, I bet I’ll break eleventy-million perfectly functional WordPress themes already out there that depend on comments being an ordered list. If there isn’t a standard, go with what’s already out there unless, of course, you’re prepared to go out on a limb. And do all the work.

    Simplify

    Actually, do I need to bother with the odd/even alternating style? Isn’t that a CSS selectable attribute of an ordered list item? Quick check… can’t find anything suitable in the spec. I have to stick with ‘alt’ anyway to maintain theme compatibility.

    Code

    Start with string containg base class ‘comment’. Add alternating class. Check to see if the array of comments is keyed by ordinal or by id. The array is the row set from a SQL statement - I’ll gamble on ordinal, so I have a free row number for which to determine oddness.

    Ownership check is straightforward, although seeing the same set of comparisons time and time again is starting to irritate me. I don’t like repeating myself. It is a simple function; is_current_user_this_user( $this_user_id, $this_name, $this_email, $this_url ). Or in OO parlance; $current_user->is_user( $id, $name, $email, $url). Perhaps current_user_match() to move away from a strictly boolean condition.

    For now, copy the code from elsewhere. It can be canonised as a function later. C’mon, these few lines of code are taking HOURS to write!

    Heck, it’s just a direct name and email check elsewhere. All I have to do is add the user ID. Oh, and find out in which global variables the necessary user credentials are stored. It seems I have a choice.

    Unintended consequences

    OK, authorship conditional completed. Nearly done. Just a simple case of determining whether the comment belongs to a registered user or God. The trouble is, I don’t know the user level of the comment author, only the user ID. Damn, the level is in a whole different table.

    I can’t issue a query against the user table for every comment - that is excessive. I could concoct some elaborate caching scheme to lower the burden but that’s a lot of code to write and test.

    I can cheat using my knowledge of the user IDs. I know what my user ID is so I could put in a magic number check. But that subverts the user level logic. God is a unique user level, not a unique user. Someday, someone else may be God and if God’s user number changes I’ve broken things.

    Finally, I can change the comment retrieval SQL to also retrieve the appropriate user level but as this introduces a join, it seems a little excessive.

    In fact, this whole task is getting excessive, whichever way I go.

    Or is it? What is the brief? All comments output are going to be classified in this way, so really the user level is going to become a natural part of all queries related to comments. I think the SQL route is becoming appropriate, but I should verify the extra resource requirement with the client. A whole new table join for almost every time a post is retrieved is a major performance change and has to be authorised. I cannot presume they intended for what seems to be a side-effect.

    So I was right and wrong. The brief is defective, but not for the reasons I thought and the client couldn’t possibly have known up-front. There is no ker-ching about this - it is my professional duty to inform my client of any potentially unintended consequences of their instructions.

    Join and polish no. 1

    Authority to continue duly sought and received, I can now update the comment selection query to join with the user table.

    Ah, but of course there are TWO queries that retrieve comments, so I have to change them both, identically. Then find others and do the same.

    Sleep…

    Reply
  19. Step 2 (continued)

    Complete Comment CSS Class Code

    Writing SQL statements first thing in the morning should probably be avoided but I’m running out of time. Straight into PHPMyAdmin to craft the SQL.

    I need to join the user table with the comments table on matching user IDs except 0. If the comment user ID is 0 or NULL I need the joined user level to be 0 or NULL, That dosn’t sound like an INNER join, which would restrict the result set to only those comments with a user id that exists in the users table - in other words, no unregistered user comments.Indeed, this is a comments LEFT OUTER JOIN users on user_id = ID.

    Updating the SQL. Hmmm… the duplicate query was simply sloppy original coding - it is now a single query, dynamically built. What exactly is the intent of the original query for comments in moderation? Dunno - I’d prefer to solve that problem during the output loop than with the database.

    So, query done and user level available for comments, complete the classification code. I can stare at it all day if I like, but I had better…

    Commit and test

    What does CVS show for such a simple modification? themes/comments.php with my main changes and wp-includes/comment-functions.php with the SQL changes. Looks good so commit and move to laptop for testing.

    Oh look - I forgot to check in a change from testing - the condition for displaying Google ads based on the HTTP server admin e-mail. I need to keep this for testing and promote to mainline. Ignore for now.

    Dev blog loads. Look at a post with comments - no comments. SQL error no doubt. Check page source. Mental note: get RID of those stylesheet comments from the header - what a waste of bandwidth! Yep - errors in my SQL that WordPress silently ignores. It is crazy programming to silently hide common errors, especially with a language that most people don’t fully understand. Mental note: correct wpdb to emit errors and abend the script. If the programmers had done this to begin with, the SQL wouldn’t be so shocking. I recently had to apologise to a student for providing exactly the same bad code - a SQL query function that failed silently.

    SQL fixed, so retest. Still no comments. Is my SQL failing or not? Check the database library. Oh gee - error showing can be toggled on of off. Why? What possible reason is there to continue running a script if bad SQL is provided? Kill the program, tell the programmer about their error and get THEM to fix it. Had I been gatekeeper for this project, most of the code would have been rejected.

    So, errors duly enabled, what is wrong with my SQL? A typo, but still no errors output. What a crock. Heh, well, it was never executing my SQL due to a typo in the variable name passed to db-$gt;query(). Yes, I’m a dick too! Finally, a database error. Lots of typos in my SQL. Definately shouldn’t write these early in the morning. I seem to be dyslexic, unable to distinguish between “cmt” and “cmd”.

    I can see the comments. View page source to check for styling. Perfect.

    Check in the changes.

    Now to find all the other places where comments can be output. Or shall I bother? Yeah, the brief says so.

    Find the remaining comments

    I’m not going to mess about now. Find all the SQL statements that make mention of $wpdb->comments. So, guess how many individual SELECT statements reference the comments table.

    Too many. I’m not wasting time figuring all this out. Zeitgeist needs to be updated but that comes later. The admin interface need to be updated too, but I think the client should specify that separately. The front-end is done.

    Reply
  20. Remove Extraneous Security Levels

    As only registered users and the host are recognised as security levels, remove all others. Ensure all security level checks are based on equality comparison with symbolically defined level.

    Reply
  21. More removals - what fun! If there’s one thing guaranteed about programming, it is “less code, fewer problems”.

    In this case, there’s probably not much of code to remove but many conditionals to find and change. Also, the admin interface should not allow user level promotion beyond level 1.

    Step 1

    Analyse the code

    A project-wide search on user_level should reveal the scope of the changes. Yep, many tens of occurrences but I’m only intested in numeric comparisons at the moment.

    Load the 24 files earmarked for changes; admin-functions.php, bookmarklet.php, categories.php, edit_form_advanced.php, edit_page_form.php, edit_pages.php, edit.php, link_categories.php, link-import.php, link-manager.php, menu-header.php, moderation.php, options.php, page-new.php, plugin-editor.php, profile.php, sidebar.php, templates.php, theme-editor.php, upload.php, user-edit.php, users.php, xmlrpc.php, header.php.

    The irony? If WordPress security had been designed and built by professionals, I would only have to change one file, and so would they. Clearly they wanted to make as much work for themselves as possible. Or maybe not. Perhaps the security system in WordPress 2.0 has been properly designed and coded? Does anyone want to know?

    Carefully make the changes

    24 file changes! If I didn’t use revision control, I would not do this. I have to be careful here because there is no way I can regression-test this. Either the code I write is correct, first-time or application security is broken. In that case, all new code must be fail-secure.

    Fortunately, most of the checks are simple. All admin functions are reserved for God. Other checks are the user being known or unknown.

    Change notes

    • admin-functions.php: users may edit their own posts (but only God can post), only God may delete posts
    • bookmarklet.php: although it may be cheating for a low-level logged-in user to access certain functions via URL, telling the user they are cheating is outrageous. What if the user has a page bookmarked, guys? I bet I’ll see the text “Cheatin’ uh?” in so many places that I’ll spit, but a common function doesn’t quite fit. Change the message. Oh wait! This is even worse! Why not just send the HTTP header “permission denied”? Fuck it, I can’t make changes that deep even though they’re correct. Carry on.

    Stop! is_user_god() takes a user id that I’m not passing, so I can’t use it. I need to check against the global $userdata->user_level and my changes so far are crap. Start again.

    • Quick mods the the files already changed.
    • Heh, user levels are compared in both PHP and SQL. I like belt and braces but this is going a bit far.
    • Why do people write if( 1 < x ) when if( x > 1 ) means the same? Variables on the left, constants on the right. Don’t fuck with that unless you know why you’re doing it or you read left-to-right. And if you do read left-to-right, recall from my Francophbic rant that you must always code using the language of the program, not the one you accidentally happen think with.
    • Can’t fix menu-header.php but it fails secure, I think.
    • users.php $user_ID = $wpdb->get_var(”SELECT ID FROM $wpdb->users ORDER BY ID DESC LIMIT 1″) + 1; Oh look! Did you forget to make the ID column of the user table AUTOINCREMENT? And look! The same SQL statement to update the user level is duplicated depending on which value is going to be inserted. Clean that shit up. I’m also disabling the code that deletes all posts when a user is deleted.

    Phew! They may only be tiny changes but so many. Check the damage in CVS; 24 files, all with minor changes. Well, I’m almost done and all the changes need a good thrash test, so incorporate a full going-over of the admin functions to check the security level settings. No doubt I will have inverted the logic somewhere.

    Commit and Test

    Simple testing reveals one typo and access to admin functions intact. I need to test that all database functions are enabled and that limited user access is still intact.

    Reply
  22. But I don’t like spam!

    Race to the Finish

    Mental notes

    “Edit comment” should redirect to comment in-situ using fragment identifier
    Remember the six-hour moderation grace period and subsequent comment requirements
    Remember to deFrancify
    Remember to reflect
    Remember to change names of all include files to *.inc

    Conditional moderation for edited comments

    Oh boy oh boy. This sounds really easy but testing it is a right royal pain in the arse. Fiddly. The code itself isn’t a problem so I think I might do a spot-check validation rather than a full test.

    So, back to the development safety feature of immediate moderation for edited comments. I can’t remember which file that is in, but my code comment is still there, so I’ll search for that. Find safety default for testing in file post.php. Go there.

    And a slap in the face with a wet fish to anyone who thinks that writing any quantities of commentary into code, voluminous or not, offers little benefit. Commentary in code isn’t like spam, you know. The more the better and generally no-one gets pissed off with you distributing it. You can always get rid of the junk later, if you have time. I just saved inestimable time by finding exactly the place I need to start in vast quantities of someone else’s code.

    Now why do you think my comments are easy to find in the WordPress code? Slap!

    I don’t know how other professionals judge, but my lines are drawn as follows. I am happy to read and write source that is 80% commentary and 20% code. I am unhappy to read and write source that is 20% commentary and 80% code. Anything else is exceptional and my happiness is decided case-by-case. I do make myself very unhappy at times but sometimes that is the correct way to proceed. With caution, of course.

    OK, rant over, back to the code. post.php action editedcomment. At this point, a check is required on the current date against the last date held for the comment. Oh! This will be a sliding window, possibly, unless the original timestamp of the comment is preserved. In fact, I’m sure of it. A six-hour sliding window could be a problem. Think it through.

    Think… think… uh oh!

    Reply
  23. Retain and Reserve All Responsibility

    It is here, at the end, where all three facets meet to discuss this issue and how it can be satisfied. Here we must speak as one, the one who will get his arse kicked if this change fucks things up.

    This is my blog, mine alone and I want it to stay that way. My words, my code, my problem. My precious.

    You will not find a copyright notice on this blog because there is no need for one. Copyright to anything belongs to those who create it unless otherwise legally transferred. Posting a comment on my blog in no way constitutes a legal transfer of copyright over its content to me. Quite the contrary. It is a request for publication that I almost always readily honour, verbatim.

    Fundamentally, you have sent a letter to the editor. That is how I treat it. I have a limited right to change your letter if I choose to publish it. To publish it I must store it. That storage is a either a trustworthy record of the goings-on on this blog, or it is not. If the distant past can be changed too radically, it is not trustworthy. Therefore, as my blog, I am the only person who is trusted to change its history. I was there at all times, after all.

    As this is a real-time publication and fairly low volume, I can make many such publication choices every day. I am about to complete a modification to the publication software that fundamentally changes my relationship in time to what is published, and that must be rigorously examined to ensure that I retain reasonable enough controls over my publication to also retain full responsibility for it.

    In other words, I need to do a feasibility study. Anyone who smirks at it coming immediately before the delivery deadline (admittedly not a place you’ll find it in any software development life-cycle product manual) should ask themselves on how many projects they have participated in one, and whether that means anything.

    Reply
  24. Feasibility Study

    OK, we’ll have to split up again so that we can all have our say without disturbance or interruption. I will be I, Libertus (the blog moderator) will be L and Paul (the programmer) will be “P”. We’re meeting on IM to discuss the feasibility of the project. Next step will be launch so this is a bit like a launch rehearsal.

    L: If we’re ready, shall we begin? Are we ready for launch? Programming?
    P: No go!
    L: No go from programming noted. Responsibility?
    I: No go!
    L: No go from responsibility noted. Moderation?
    (there is a short pause)
    L: GO! We are ready for launch!
    P & I (in unison): OBJECT!!!
    L (laughs): I’m just kidding! I’m no-go too, really. Three no-gos means we’re going nowhere. One no-go means we’re going nowhere. You know I know that. We need to talk.
    P & I (in unison): Agree.
    L: Programming, why no-go?
    P: I have completed 5 of the 7 requirements. My remaining programming directly involves this issue, requirement 7. I can complete the code for automatic moderation but I cannot take responsibility for it satisfying requirement 7 - it is outside my remit. If I get the go-ahead from this meeting to do that programming, I should still have sufficient time remaining before launch to do paint-job and polish.
    L: Critical path milestone acknowledged by Programming. Responsibility, why no-go?
    I: I’m only saying no-go because I have no reason to say GO!. Nothing I have observed during the development has changed my mind from my original position - this project has nothing to do with me. I trust you guys implicitly. However, since you insisted that is was important I be here, my standard is that you have to convince me to GO! rather than insufficiently convince me not to no-go.
    L: Denial of responsibility acknowledged by Responsibility. No surprise there. Moderation, why no-go?
    L (clears throat): Well, for starters, the code isn’t finished and …
    L (interrupts): Point of order! Wait a minute guys. THREE NO-GOS? There are only three stakeholders in this project and they’re all here. How can we ALL be saying no-go to our own project that some of us have worked so hard on? Consensus negative with the deadline so close- it doesn’t make sense!
    P: It makes perfect sense to me.
    L: How so, smart-arse?
    P: What you just said.
    L: What, How so, smart-arse??
    P: No. the code isn’t finished. Since when did you care about code being finished before putting it on the blog? That’s what doesn’t make sense. Usually I have to refuse to write something simply to prevent you from ever being able to publish it. You don’t even have CVS access! The code is perfectly fine as it is, you know it and you’re vacillating on publishing it. That’s what doesn’t make sense. What’s wrong with you?
    L: OK, I’ll come clean. You got me. Your code is taking part of my job away and giving it to a machine. That bothers me.
    P: But you want people to be able to edit their own comments!
    L: Yes, but I’m moderator. I moderate comments. That’s what I do.
    P: True, but you don’t moderate them all.
    L: Yes I do.
    P: No you don’t.
    L: I don’t understand.
    P: No, you’ve just forgotten about something. There’s a WordPress setting that allows limited auto-moderation. You have that switched on. It doesn’t work all that well, but it does work sometimes. You don’t explicitly moderate all comments.
    L: Oh, yes. I see what you mean. Personally, I do moderate all comments but technically I don’t.
    P: Precisely. You use the publication software as a tool to perform the task of moderator. The underlying computerised database table row “approved” column value held against each comment is not moderation. Unfortunately, it is difficult to think of another term that would easily separate the technical concept of moderation (setting a value in a database) from your responsibility to personally scrutinise each and every piece of content on here.
    L: The computer isn’t doing my job, in other words.
    P: I hate being the technical one. You get all the attention.
    L: Indeed. But, enough about me, let’s get back to the issue. I need you for that. I need you to explain how this change is going to affect my job so that I can adapt my work practice around it. I cannot say “GO!” until I know how to go, kinda. Train me, please.
    P: Your wish is my command. About time too.

    (time passes)

    L: Did you guys see that? Aren’t you impressed? Doesn’t that solve all our problems?
    P: Unbelievable.
    I: Unbelievable.
    L: Fucking unbelievable. Responsibility, are you in now?
    I: Damn. I just didn’t realise people could be so vicious! Yes, I’m in.
    L: OK. I propose we allow people to say one thing at a time and in strict sequential order. All those in favour say aye.
    P: AYE!!
    I: AYE!!
    L: Fuckin’ AYE!! It’s carried then. The feasibility study is over. Programming, after your rest day, are you clear on what you need to do?
    P: Yes. Crystal. I’m in. Have been from the start. Why do you think I called the meeting?
    L: And I’m in because there’s no way I’ll accept responsibility for moderating the blog any other way. Are we together?

    L, I & P (in unison): AYE!

    Reply
  25. Blog Publication With Liberated WordPress 1.5.2

    By Paul the Programmer, age 371/2, 19th January, 2006

    Table of Contents

    1. You are smart, clever, funny and intensely attractive. Find out why!
    2. The basic facts for everyone
    3. What publishers need to know
    4. What moderators need to know
    5. What readers and commenters need to know
    6. Incredible sexual pleasure at your fingertips. Find out why!
    7. Reference, Technical Specifications and Further Reading

    Introduction

    Thanks for reading. Programmers find manual readers sexy.

    Things NOT to do now. Do NOT stop reading!

    Things to do next. Decide which of the following choices describes you best as a person and click on it. Read them all before you click, remember.

    1. I am Paul Mitchell. I own, publish and am responsible for the blog called “libertus” and the actions of its moderator, Libertus.
    2. 2. I am Libertus. I am the moderator of the internet discussion forum “libertus”.
    3. 3. I am neither Paul Mitchell not Libertus, thank God! I do read “libertus” or comment on it.

    Basic training for everyone

    First, for lost people. If you have no idea what a blog is, or what WordPress is, then you probably have no idea where you are, and this is not a good place to start. If, by any chance you are seeking basic training on liberating people from some kind of oppression, please go here first and make sure you’re going about things the right way. If, on the other hand, you just want to chat, read on.

    Liberated WordPress is made mostly from WordPress and shares the majority of features. This manual only deals with the differences.

    The homepage has changed to warmly greet you, personally if possible. If you have clicked away from the homepage, clicking on the title at the top of the page brings you back.

    Things to do next. Decide which of the following choices describes you best as a person and click on it. Read them all before you click, remember.

    1. I am the publisher. I am very important and very powerful.
    2. I am the moderator. I am important and powerful.
    3. I know my place.

    Training for Publishers

    What running Liberated WordPress 1.5.2 instead of WordPress 1.5.2 changes for you. What you need to be aware of before installing. What new powers have been granted, how they are expected to be used, by whom, and for what reasons. How the moderator is expected to fail during change-over.

    Training for Moderators

    What running Liberated WordPress 1.5.2 instead of WordPress 1.5.2 changes for you. What you need to be aware of before change-over. What tasks are no longer necessary? What are the new tasks? How you are expected to fail during change-over. How the administration interface has changed and how to use it. How the user experience has changed and what questions to expect. How the users are expected to fail.

    Training for Registered Users

    You have been entrusted by the publisher with access to better tools for leaving comments. Every attempt has been made to give you tools that explain their own operation. However, the rules and regulations of how to use these tools is only explained here. That is why the moderator gave you this link. You are expected to have taken this training course before making use of your new powers.

    Becoming registered

    What changes when you are logged in?
    Homepage welcomes you by name.

    Logging in
    Click the link down the right sidebar on the homepage.

    Why go backstage?
    Text-only, low bandwidth, ad-free, specific search, quick access.

    Moving around backstage
    Zeitgeist
    Review Posts, Comments
    Users
    Logout

    Training for Readers and Commenters

    How has your blog changed? What’s different. What’s gone. What’s new.

    Comments are displayed in different styles depending on who made them. Comments by the moderator and registered users, who may both edit their own comments, are clearly marked. Comments that you have left may now appear differently from those of others.

    You may now use a broader range of HTML tags when leaving comments. If you do use tags, be careful to close them properly as if you don’t, the moderator will have to clean up after you. Be warned, moderators don’t like to clean up.

    The following HTML tags (and attributes) are allowed; A (href and title), ABBR (title), B, BLOCKQUOTE (cite), CODE, EM, I, LI, OL, PRE, Q, STRIKE, STRONG, SUB, SUP and UL. All other tags are stripped out before your comment is published.

    Because some users can and do edit their comments, occasionally a comment you previously read will either disappear, move down in the list or change its text. The moderator is responsible for ensuring that this facility is not used for mischief, so contact them if you encounter mischief.

    This blog carries a small amount of advertising from Google, limited to a skyscraper on the home page and a banner along the bottom of most post pages. Although these arrangements can be profitable, the primary purpose of the ads here is entertainment.

    Credit Where Credit Is Due

    Every time you tap those keys, someone, somewhere, gets a thrill. You’re touching their precious. You are using their software.

    Only a minute fraction of Liberated WordPress 1.5.2 is my work. There may be tens, hundreds, thousands, who knows what power of people, real living people, who have generously donated their skill, knowledge, time, effort and love to build the foundation on which I have built. Many have also contributed their inexperience, incompetence, insouciance, sloppiness, impatience, laziness and hubris. You can be assured that programmers of the highest quality from across our fine globe have worked on this fine product. Let’s hunt them down and kill them. Start here.

    1. Me, naturally
    2. WordPress

    Appendix

    Reference, technical specifications and further reading.

    Editable Comments

    Comment editing use and abuse

    What about the other part of the brief - auto-moderation of edited comments with subsequent comments? If that is in place, it obviates the sliding window effect.

    If anyone is asking “what are you on about? what’s the problem?” it is a matter of providing an environment reasonably safe from deliberate abuse of tools provided for legitimate use. There are situations in which the ability to edit a comment can be abused, such as changing the text of a prior remark to make a subsequent comment by another user look foolish or like an unprovoked attack.

    As moderator, I am the final arbiter as to what constitutes use and abuse of the facilities I provide. As publisher of this blog, unless explicitly decreed otherwise by UK law, I consider myself to bear full and sole responsibility for what is said and published here, including all comments. I liken myself to the Speaker in Parliament. If you have a problem with something said in Parliament, you have to go through the Speaker first. If you have a problem with something published on “libertus”, you have to go through Libertus (me) to get anything done.

    Reply
  26. Paul…If you have a moment might you explain the code modifications you used to place the comment fields and comment results on the index page…? Thanks in advance for any assustance!

    Reply
  27. William,

    Welcome! Are you referring to what I call “Zeitgeist”? If so, I already plan to publish the source code once I’ve put the finishing touches to it.

    In the meantime, if you explain what you want to achieve, maybe I can help you find something that has already been published that will suit your needs.

    I’ll try to set up a page with a static version of what Zeitgeist currently looks like. That should help you decide if want to wait or not.

    Reply
  28. William,

    I have now set up a page describing Zeitgeist for WordPress and welcome your comments.

    Reply
  29. Paul,

    Thanks for the rapid response…I would simply like to accomplish what you
    have done with this homepage:

    1. Place the “Leave a Reply” fields on the homepage.
    2. Place the “Responses” between the posting and reply fields on the homepage.

    Thanks for any help!
    William

    Reply
  30. William,

    Sorry, I misunderstood your question. Yes, you are definately talking about modifying themes. The WordPress support forum, as I say below in my original comment, is your best bet. I do not offer programming advice on this blog.

    With themes, you can make your WordPress blog appear pretty much any way you like. There are many talented indidividuals out there with mad CSS, HTML and PHP skills who publicise their work. More information is available on the WordPress website.

    — original comment —

    As far as I know, all that happens because of the theme I’m using, which is the default with WordPress 1.5.2. The one based on “Kubrick”. Admittedly I have modified it, but I do not recall doing so to create the effects you describe. I believe they were already there.

    For further help, your best source of advice is the WordPress support forum. I can only provide detailed advice about my own stuff.

    Reply
  31. William,

    If you can figure out my email address, you are welcome to get in touch that way. I would prefer to leave this post clear until my work is complete. You may have to be tenacious. I don’t completely trust my email system at the moment. Are you OK with that?

    Reply
  32. Day of Rest

    I hate rest days - they are so unproductive.

    I fidget. What do? Dum de dum. Drink coffee. Dum dum, dum dum. Listen music. Dum de dum de dum. Sit computer. Dum diddly yum dum. Stare. Dumb.

    It is against the rules to code on a rest day but the temptation is there, always. On rest days, all those niggly little flaws on the computer that are so easily ignored on other days become luscious fruits on the tree of sin - ripe, sweet and ready for the plucking. Projects shelved for later years rustle and grumble in the dusty recesses on my mind. I am reminded of the presence of the so many things that still need a bit of attention. Just a little.

    Then I break the computer’s hypnotic hold as I recall those other little things that require a lot of attention. People. My good friends out there. I perk up. Yes, there are some things I need to go back to.

    So start with my go-back-to list on del.icio.us. By the way, if you haven’t tried it, I recommend you take a look at del.icio.us. I find it incredibly useful for many reasons.

    Jennifer! Yes, how is she doing? Just fine, I see. All nice and settled since my last intervention. Time to stir things up a little. Stir. The result - I read the Bible, “The Song of Songs”. Lovely.

    A bit of blog moderation. Tidy up some content. Update a few figures. Analyse stats.

    Reflect on the project. That is allowed - it isn’t coding!

    Ideas. MetaKernel and the omni-languages “explicit” and “implicit”. I keep hitting walls on every route I try. This needs divine intervention if I’m going to progress, I believe. I know I need this but buggered if I can work out how to build it. I can’t even adequately explain them to myself! For some strange reason, computers acting like organisms just doesn’t make sense.

    Reply
  33. Day of Work

    After a refreshing day off it’s back to work with a late start but a clear purpose. The end is only a short step. Libertus’ mischief on the WordPress support forum finally brought the consensus needed. My brief is open - I am entrusted to design and build whatever I can within the boundaries of what is allowed.

    The Design

    What does the system already support?
    Everyone may speak.
    Many people may speak about the same topic.
    One person may speak about a topic at a time.
    The record of people speaking is in strict chronological order.
    Some automatic moderation is in effect (comments are auto-accepted from any previously accepted commenter on a topic).

    What is new?

    Some people will change what they say. Those changes may be immediate or past. Those changes may be corrections, revisions that do not change meaning and revisions that do change meaning.

    What are the restrictions?

    The moderator does not want to allow people to change their past comments without his scrutiny. Until someone else has commented, a commenter with editing privileges may edit their comment as they wish. The moderator accepts that as he comments a lot, the presence of subsequent moderator comments should not deny editing rights. The moderator wants people to know, if possible before posting a comment or edit, whether it will go into moderation or be automatically published, to limit his liability. In cases where this is not possible to predict (i.e. subsequent comment posted during editing), a short grace period may be allowed but err on the side of caution and sent the edit to moderation.

    Work it through.

    Post “The Launch of Editable Comments”

    Libertus says: Editable comments are now enabled.
    Paul says: Excellent! About time to!

    At this point, Paul’s comment is directly published as he is a registered user. He also has editing rights, so he can edit his comment at will.

    Paul says: I’m so happy.

    Whether Paul edited his last comment or not, this comment is immediately published also and now allowed editing, but the previous comment has changed its status. There is a subsequent comment not by the moderator. Paul can no longer automatically publish edits to the previous comment, they must be moderated.

    So, what this boils down to is that if you weren’t the last person to speak on a topic, any edits you make must be moderated. Yep, that works. If you do edit a comment in the past, it disappears from the record until the moderator approves it. Even better. Now, is there any flexibility?

    Well yes, one. The date of the comment. Once change already made so far is to automatically update the timestamp of an edited comment. This is something I needed to keep some order when using the blog for work notes. However, two use cases for editing past comments have already been identified; corrections and revisions. What’s the difference?

    Corrections correct, revisions revise. Hmm… but in real life? A correction implies that the original comment is not correct for some reason, a revision implies correctness. Only the editor of the comment can know the difference. Is there any flexibility in this, if all past edited comments are to be moderated? Yes, the moderators task can be made easier if the user makes a choice based on the intention of the edit. Correct or revise?

    Does the time change? For a correction it does not, for a revision it must. By choosing the intent for their edit, the user also chooses whether to update the timestamp or not.

    So how does this work? When editing a comment, the user is presented with; the title of the post, the date and time on which the comment was posted, whether the comment is in moderation, the editable text of the comment and one or two buttons, depending on whether the comment is in the past. If the comment is in the past, two buttons; “Submit as Correction” and “Submit as Revision”. If the comment is in the present, one button marked “Publish Edited Comment”. A present comment may be past by the time of submission. All button texts imply the type of moderation that will result.

    Past or present? That is the question.

    Reply
  34. The Brief

    Users with authority may edit their own comments at any time.

    A comment is in the present if;
    a) no-one except the moderator has commented on the same post since

    If a comment is not in the present, it is in the past.

    Edited present comments are published immediately.

    Edited past comments go to moderation as corrections or revisions. The timestamp on a revised comment is updated and left unchanged for a corrected comment.

    Reply
  35. Step 1

    Analyse the code with design feedback

    Aaaah! Back in code at last. Where to begin? The essense of this change is to conditionally relax the development safety feature of auto-moderating all edited comments, depending on whether the comment was the last thing said against a post. Find “safety” in the files and get wp-admin/post.php action editedcomment where the comment is automatically unapproved.

    The test here is “has anyone else except God commented?” and if not, approve the comment.

    Up a little bit there is the date setting code. This has to change to react to the different button presses possible when editing a past comment. Only God can set the date to something specific, but an edited comment can have either its original timestamp (a correction) or the current timestamp (a revision).

    Go back to the design. Anything missing? Past, present, correction, revision, immediate publication, no. All the technicals are there. Now for the user interface.

    At the moment, the admin interface presents a single button when editing a comment, marked “Edit Comment”. This is still the only action truly available - only subtleties are introduced. The user if offered a choice of intent for edits to past comments.

    So here also, a check for past/present is needed. As time is guaranteed to move on, time-based checks must be done time and time again.

    The user interface code is in wp-admin/edit-form-comment.php, an included non-include file. The changes are; show moderation status and current timestamp of comment (in addition to the title, which I added previously), determine contemporaneousness of comment (using its timestamp, presumably), output buttons based on contemporaneity of comment.

    Nothing changes for God, except to rid his interface of those damned huge name, email and URL inputs. Put them all in one fieldset instead of separate divisions. Anything.

    I’m tempted into the user interface work first. I have to develop the past/present check here, and the back-end code is responsive so naturally dependent. Go!

    Reply
  36. Step 2

    Modify the code

    God changes first. Fix those ugly, unnecessary inputs. Yes, a severe case of over-division. One fieldset, three inputs with labels is a fine start.

    Show comment moderation status and current timestamp. The post title is in the legend of the fieldset, descriptively. Perhaps add “(unapproved)” for a comment in moderation and my combined relative/absolute time idea I used for Zeitgeist (text is “3 mins ago” with HTML ABBR title containing the full timestamp).

    Code skeleton to synthesise the legend done, fill in the human time difference (”x periods ago”) with popup timestamp (”<abbr title=’timestamp’>”). I’m not sure if I had to borrow the human time difference algorithm for Zeitgeist or if WordPress provides it. I’ll check.

    I cannot resist the temptation to “cherry pick” (cut and paste) the code from Zeitgeist. It works and simply needs adaptation to the different variables. Function human_time_diff() must be provided by WordPress. Hack! Slash!

    Incorporate, adapt and tidy up the sourced code. The new legend looks good. On to the buttons.

    Here we are. Submit button and the passed-on referrer URL that I’m itching to change (adding the comment fragment id). Tabindexes have to change. There are two possibilities; two buttons or one, depening on past/presentness. Here’s that check I have to write, at some point. Fluff it for now.

    Any good reason to have a hidden input inside a CSS-styled paragraph? No - out! Hmmm… can buttons have titles? Try it out - I need to give the user sufficient information as to the consequences of their actions, more than just “Correction” and “Revision”.

    Split into two paths; past and present. Two buttons for past, one for present, which will remain as is but for its mark. Add text around the two buttons. HTML name attribute remains same for all but id attributes must be unique, so change. Also tabindexes, not worrying about the knock-on effect on the God controls underneath - they don’t work with Tab anyway. Mental note: fix.

    What else can I tell the user? Well, if the comment isn’t already in moderation and it is past, then it will go into moderation, so make sure they know that. Inform also of immediate publication unless someone else got in first. The subtlety about the moderator being ignored isn’t important.

    I’m not entirely happy with the “gazumping” of publication rights that this introduces but it is better than absolute chaos for any period of time. No, your edit may not be published immediately, even if it has only been seconds. The rules are the same for everyone. No overlapping.

    Happy enough with the code, I turn now to the past/present check. Is this the most recent comment on the post ignoring God? Hmm… what about comments in moderation? And the commenters own comments?

    Any of your own comments into the future - past.
    Any visible, non-God comments into the future - past.
    Any comments on same post with date greater than this one either by this commenter or (not by God and approved).

    Sounds about right. Try to poke through with some negatives. Only future comments are unapproved and by other users. Only future comments are God’s. No future comments. Sound.

    I’m pretty sure this check isn’t currently done so it is a new SQL query. It is issues in two places; at the time of presenting the comment editing UI and during edited comment submission for approval status setting. Both are called via actions in post.php, so a function there would seem to be the best solution. I don’t like repeating myself.

    What does this function need to know? Fundamentally, only the ID of the comment in question. However, the post ID, being known, saves asking the database again, so pass that too. Heck, why not just pass the entire comment data structure? That yields the date too. No need to ask what I already know for sure.

    Begin function is_comment_in_past() which will return true if it is and false if not.

    The query. Using current comment, select count of records from comments table that are all of; a) in the same post, and b) are dated later, and c) are either; c.a) by the same user, or c.b) are all of; c.b.a) not by a God-level user, and c.b.b) approved.

    Ouch. There’s that user table join again. However, I’m not going to seek approval this time as it is a new query and necessary.

    Dammit! Normally I’d use a subquery (to get the user_ids for God level) here instead of formal join syntax but MySQL doesn’t support that until version 4.1 which isn’t widely implemented yet. The list is small and there always has to be at least one. I’d prefer to do that as a standalone query first rather than join. Sigh.

    I end up with…

    $god_users = SELECT id FROM $wpdb->users WHERE user_level = 10
    
    SELECT
    	COUNT(*)
    FROM
    	$wpdb->comments
    WHERE
    	comment_post_ID = $comment[comment_post_ID]
    AND
    	comment_date > $comment[comment_date]
    AND
    (
    	user_id = $comment[user_id]
    	OR
    	(
    		user_id IN ($god_users)
    	AND
    		comment_approved = '1'
    	)
    )
    

    I’m not sure about my formatting. I’ll work on it. But it reads OK, doesn’t it?

    Now, place this SQL into some PHP plumbing. Hmm… dare I implode() the result from $wpdb->query()? No, I’m not happy giving a potentially associative array to implode(). I think I’ll check to see if there’s a database function dedicated to this purpose first. Yes! get_col(). Glad I checked.

    Gotta love that heredoc syntax. My SQL query appears in the PHP script identically as above and so is very easy read, being obvious. Function complete.

    Now, while I’m at the edit comment buttons, the referring URL really needs to have a fragment id for the current comment imposed if it isn’t already there. Finding an edited comment in the middle of a long discussion is difficult. Mental note: you have been working on CSS styles for comments, you know.

    Back to the comment editing form and that referrer URL hidden input field I mentioned earlier. I’ll implement the past check function in a minute. First, move it to the top of the form with the other hidden things. “hidden” means from the user, not other programmers, to whom they should be made glaringly obvious.

    Remove any existing fragment id from the URL and append one identifiying the current comment. As my code commentary says, “blunt, but shouldn’t hurt anyone”. Strip from last hash in URL if any, append my bit. Easy.

    Duly satisfied with my blunt code, finally to implementing the past check. Once for the UI buttons and once at the “safety” check which will now be approprately commented. At this point, the date of the comment has been set and it is on the non-God path. If the edited comment is in the past, disapprove of it.

    Last is to effect no change of comment date if user has requested a correction. Ach… I need the old comment loaded earlier now - move it.

    Done. There are still limitations. The moderator cannot review the changes, only decide on approving the new edition. However, that wasn’t part of the brief. Check the brief just in case… check.

    Look for mental notes and other changes that I can make while I’m here… the French abbreviations - ok, include files - pass, clean up styles in blog header - ok, wpdb abend - pass, God edit comment tabindexes - pass, CSS styles for comments - ok, blog footer conditional Google ads for known server admin email address - ok.

    Commit current changes and make these minor ones. I’m going to test the lot in one go.

    Reply
  37. Step 3

    Survey the Damage

    CVS reports 2 changed files; wp-admin/post.php (6 differences) and edit-form-comments.php (8 differences). All seems well. Commit.

    Spit and Polish

    List the remaining items and order them according to taste;
    remove French abbreviations from comment editing interface
    remove redundant commentary (and perhaps style specifications) from blog header
    remove Google ads from blog footer unless hosted on libertini.net
    make some nice sample CSS styles for the new comment classes, and fix the lists

    Defrancify

    This allows me to remove my sarky comments, so it is a worthwhile change as I don’t like them. So, where are the controls generated? Check wp-admin/edit-form-comment.php in those God-only controls at the bottom as the submissible date is there. Uh oh… the date controls are generated by a function called touch_time(). Will I look at the code first, or find out how many times it is called? Calls first. Twice. Keep going and look at the code. Oh boy, this is a load of effete nonsense - way over the top. I’m ripping this out and replacing it with a simple text edit for the date. It’s only for me, after all. Fix the two callers et Bob est votre oncle!.

    Oh dear. No, too much work. Leave the grouchy comments for now. On to the next one.

    Clean up blog header, footer and comment styles

    There’s a blog stylesheet I control, yet extra style information is emitted with every request for a blog page. That style information includes loads of comments that are a complete waste of bandwidth. The whole thing smacks of laziness on my part. Do better, man! Clean your crap up. Check the footer while you’re at it and make sure you don’t serve Google ads from the development blog.

    Open wp-content/themes/default/header.php, footer.php and style.css. As the stylesheet is open, do all the style things at once.

    The footer change is already made in testing - review it. Hmm… show ads only if server admin is webmaster at libertini. Well, that’s a bit restrictive. Don’t serve ads from the development blog (identified by a special email address). Google ads could really do with being a plugin and a configurable option. Mental note to look for one or create one, recall Google supply script and demand its verbatim use.

    Now, styles. In the header. Strip LINKs to feeds I don’t understand and the wierd list of archives. They’re easy enough to find if you want them. Strip style comments. Hmm… now… what if I remove the theme images? Will the blog switch to full broswer width? Will it look less cool? Maybe. Can’t afford that. Can I move this stuff into the stylesheet where it belongs? Try that first. Leave the image conditional on the sidebar - move the rest.

    Quickly add the additional link in the header to the admin homepage for God user. That’s been an annoyance. Clean up a little. Mental note to introduce alink_if() nifty function.

    Back to the stylesheet and integrate the theme styles into their proper places with images reference by relative urls. I have such a bad f