Monday, October 30, 2006

Two quick accessibility notes

  1. When trying to wake up in a hotel lobby, reading news and friends' posts via elinks (ssh-ed home), I really wish comics had long descriptions. Not as funny as seeing the actual comic, but blind people and text browsers never will... Seeing "Foxtrot: []" just doesn't have the same impact.
  2. I don't understand how Blogger doesn't seem to like my Firefox setup, but it works perfectly (and fast) in elinks. I think I may actually prefer this to their GUI. Never mind...I can save a draft, but posting doesn't actually work, even though it thinks it does...

(Finally posted twelve hours later)

Thursday, October 26, 2006

Getting the hang of writing s9y plugins

Continuing on with the custom s9y hack to allow multiple users, I've noticed that I can create plugins that display different content, depending on the currently displayed user, by looking at the global variable s9y generates: $uInfo[0]['authorid']. This holds the authorid of the user whose page you have open, rather than the currently logged-in user. Using this, I can create all sorts of user preferences (Wish List links, a picture, etc.) and have the author's preference displayed by my own custom serendipity_plugin.

That makes things a hell of a lot easier than trying to hack things like that into s9y's main codebase, while still getting all of the access I need to my own global variables and database connection. The plugin I just wrote takes up all of 40 lines, with only 14 lines to the method that checks for that ID, loads the user's preference, and displays the formatted link.

Saturday, October 21, 2006

Hacking s9y into a multi-user, centrally controlled tool, continued

Now that authors can only manage their own posts and have everything managed centrally, I just have to display filtered results, so each author has their own main page. Since s9y already has a way to filter by author, we just need to trigger that without the full query having to appear in the URL request.

The index.php in whatever directory you want. For this install, I'll have it as /username/index.php.

// Get the journal contents for the current journal
$_SERVER['REQUEST_URI'] .= '?/authors/(authorid)-(Hyphenated-Full-Name)';
require 'includes/journals.php';

Then, journals.php simple follows the standard, embedding practice for s9y:

$pwd = dirname(__FILE__);
chdir($p . 'journals/');
include 'index.php';
chdir($pwd);
Since my own code already logs you into s9y and, in doing so, starts the session, I just need to quiet the notice that will otherwise pepper the error log from serendipity_config.inc.php:

Index: serendipity_config.inc.php
===================================================================
--- serendipity_config.inc.php  (revision 162)
+++ serendipity_config.inc.php  (working copy)
@@ -3,7 +3,7 @@
 # All rights reserved.  See LICENSE file for licensing details
 
 if (!headers_sent()) {
-    session_start();
+    @session_start();
 }

Since s9y didn't get built with this kind of behavior in mind, this definitely falls into the realm of sketchiness. But it works solidly and since any distribution of this will get included with my own web application (source code and license for s9y included, of course), only I have to worry about upgrade paths.

Until, of course, they do this for real.

Labels:

Hacking s9y into a multi-user, centrally controlled tool

Warning: I did these changes under my own source control tree so I could roll back changes and keep track of things. Don't do this unless you either know exactly what this means and does, or if you actively want to break your own installation of s9y. I've included svn diffs of my changes to s9y version 1.0 in order to make this work.

Once I got my own authentication to create the session for s9y to allow authors in for posting and editing, I next had to make s9y do two things different from its normal behavior for base-level authors:

  1. Keep authors from editing their information that gets managed by my own admin tool.
  2. Allow authors to only edit their own entries

First off, authors only have the ability to edit their info using the tool I wrote, since it also updates their central user record, as well as the Phorum user record. Luckily, s9y makes this practically as easy as pushing a button. Just a couple of quick permission changes in s9y/include/tpl/config_personal.inc.php:

Index: config_personal.inc.php
===================================================================
--- config_personal.inc.php     (revision 115)
+++ config_personal.inc.php     (working copy)
@@ -13,28 +13,28 @@
                                           'description' => USERCONF_USERNAME_DESC,
                                           'type'        => 'string',
                                           'default'     => 'johndoe',
-                                          'permission'  => 'personalConfiguration'),
+                                          'permission'  => 'adminUsersMaintainOthers'),
 
                                     array('var'         => 'password',
                                           'title'       => USERCONF_PASSWORD,
                                           'description' => USERCONF_PASSWORD_DESC,
                                           'type'        => 'protected',
                                           'default'     => '',
-                                          'permission'  => 'personalConfiguration'),
+                                          'permission'  => 'adminUsersMaintainOthers'),
 
                                     array('var'         => 'check_password',
                                           'title'       => USERCONF_CHECK_PASSWORD,
                                           'description' => USERCONF_CHECK_PASSWORD_DESC,
                                           'type'        => 'protected',
                                           'default'     => '',
-                                          'permission'  => 'personalConfiguration'),
+                                          'permission'  => 'adminUsersMaintainOthers'),
 
                                     array('var'         => 'realname',
                                           'title'       => USERCONF_REALNAME,
                                           'description' => USERCONF_REALNAME_DESC,
                                           'type'        => 'string',
                                           'default'     => 'John Doe',
-                                          'permission'  => 'personalConfiguration'),
+                                          'permission'  => 'adminUsersMaintainOthers'),
 
                                     array('var'         => 'userlevel',
                                           'title'       => USERCONF_USERLEVEL,
@@ -57,7 +57,7 @@
                                           'description' => USERCONF_EMAIL_DESC,
                                           'type'        => 'string',
                                           'default'     => 'john@example.com',
-                                          'permission'  => 'personalConfiguration'),
+                                          'permission'  => 'adminUsersMaintainOthers'),
 
                                     array('var'         => 'lang',
                                           'title'       => INSTALL_LANG,

This way, admins can still go in and fix things without much fuss if something manages to somehow fall out of sync with the central record for some reason.

Next, authors shouldn't have the ability to edit other authors' entries for this site. That means that s9y/include/admin/entries.inc.php has a few quick changes:

Index: entries.inc.php
===================================================================
--- entries.inc.php (revision 115)
+++ entries.inc.php (working copy)
@@ -59,7 +59,9 @@
 
     $filter = array();
 
-    if (!empty($serendipity['GET']['filter']['author'])) {
+    if (!serendipity_checkPermission('adminEntriesMaintainOthers')) {
+               $filter[] = "e.authorid = '" . serendipity_db_escape_string($serendipity['authorid']) . "'";
+       } else if (!empty($serendipity['GET']['filter']['author'])) {
         $filter[] = "e.authorid = '" . serendipity_db_escape_string($serendipity['GET']['filter']['author']) . "'";
     }
 
@@ -110,7 +112,10 @@
         <tr>
             <td valign="top" width="80"><?php echo AUTHOR ?></td>
             <td valign="top">
-                <select name="serendipity[filter][author]">
+<?php
+                      if (serendipity_checkPermission('adminEntriesMaintainOthers')) {
+?>
+                      <select name="serendipity[filter][author]">
                     <option value="">--</option>
 <?php
                     $users = serendipity_fetchUsers();
@@ -119,7 +124,16 @@
                             echo '<option value="' . $user['authorid'] . '" ' . (isset($serendipity['GET']['filter']['author']) && $serendipity['GET']['filter']['author'] == $user['authorid'] ? 'selected="selected"' : '') . '>' . $user['realname'] . '</option>' . "\n";
                         }
                     }
-?>              </select> <select name="serendipity[filter][isdraft]">
+?>
+                      </select>
+<?php
+                      } else {
+?>
+                       <?php echo htmlentities($serendipity['user']); ?>
+                       <?php
+                      }
+?>
+                      <select name="serendipity[filter][isdraft]">
                     <option value="all"><?php echo COMMENTS_FILTER_ALL; ?></option>
                     <option value="draft"   <?php echo (isset($serendipity['GET']['filter']['isdraft']) &&  $serendipity['GET']['filter']['isdraft'] == 'draft' ? 'selected="selected"' : ''); ?>><?php echo DRAFT; ?></option>
                     <option value="publish" <?php echo (isset($serendipity['GET']['filter']['isdraft']) &&  $serendipity['GET']['filter']['isdraft'] == 'publish' ? 'selected="selected"' : ''); ?>><?php echo PUBLISH; ?></option>

Basically, this just looks at whether or not you have permission to edit others. If you don't, it automatically filters by your authorid and doesn't allow you to view any others. If you try to save an entry owned by someone else, and you do cannot adminEntriesMaintainOthers, you'll get an error anyway, but it looks a lot cleaner if you can't open them up for editing in the first place.

*edited for formatting fixes

Labels:

Thursday, October 19, 2006

A little metadata and object inheritance...

...and suddenly pieces fall together. A file management part of the admin tool now not only imports new sets, but merges your new file selection with the current list, and remembers its association with the database record even if you move or rename the directory. I've stored a bit of metadata in each directory so the path, while stored, doesn't have anything to do with the actual identification of either the record or the directory. That way, users can organize the directory structure the way they like rather than getting forced into a structure that may or may not make sense. When you look at a set of directories in an FTP client, the PHP simply has no way of informing you that you'll break something by renaming folder to folder1 or moving cool stuff into a new my stuff directory. And when it comes down to it, it shouldn't really have to care.

Also, a simple little 16-line database object later, and it has persistent user preferences, and a way to keep you from having to drill down to the same directory every time you open the file manager. Happily, since I've kept the markup for the layout moderately semantic, viewing the contents of a directory with 100 usable files (triple the norm for the current usage) renders a page weighing in at only 40k, including file information and controls for each file. Later on, I'll add some pagination, but at this point I need to finish more than I need to polish.

Saturday, October 14, 2006

Zend/PHP Conference and The Book

On a side note, I've had options one, two, and three from my list combine in the most spectacular manner possible.

Also, the book deal has gone official (signed contract and everything) and I've received my own, complementary copy of Joshua Eichorn's book, Understanding AJAX, in order to: a) make sure I don't tread on any toes; and b) make sure to overlap just enough for a smooth transition, since his book very nicely serves as a pre-requisite to mine. I've made it about halfway through so far and only feel more encouraged in the direction I will take. He has so far covered everything I would have in a book of this nature, and should not need to cover at more advanced level.

Labels:

Coding now for future functionality

I have a major challenge in my current non-day-job project in that it needs to go live quite soon, but I need the ability to very easily add functionality to each aspect of it. The following practices make it infinitely easier to do this:

3NF
The Rails1 library defaults to using database schemas (schemata? whatever) following the 3rd normal form quite strictly, and does so for very good reasons. Relationships can easily get added or removed without affecting the stored data. Updating data in one table does not affect any other table's data. This means that I can easily add on new ways to use existing data without impacting the original data or the code that manages it.
Modular Design
The fewer files touched by an upgrade, the better. When fixing bugs, of course, some files have to get patched. But when adding functionality, existing files shouldn't have to change. A live site can have thousands, if not millions, of live users and needs to keep downtime to an absolute minimum. Additional functionality should come at the flick of a switch, rather than the replacement of dozens of files getting hit several times each second. Central controllers help (me, at least) immensely in following modular design.
Laziness Kills
At least when it comes to performance. When writing a web app, performance starts dragging when you cut corners and then add more functionality to a single hit. It inevitably happens, but when you keep it to a minimum, you can at least keep it under control. Instead of relying on low-performance functions to achieve something (PHP's include_once and preg_match, I mean you), you can create high-performance alternative calls without even doing much work. Sure, sometimes you actually need to use the functions as written, but 90% of the time I've seen low-performance calls (those two functions especially), an alternate could have replaced it with a trivial amount of work.

1I do not use Rails, but it provides a very good, very prominent example of 3NF enforced in a framework.

Sunday, October 08, 2006

Zend/PHP Conference and Expo 2006

One way or another, I'll make it out there for the conference! My options, in order of preference:

Press Pass
I just need to get myself there and then I get all access, all benefits, and a special lunch with presenters. Since I've started writing a book that completely follows the theme of Creating Modern Web Applications with PHP, I don't see any reason why I would get turned down for this one.
IBM Pays
Almost as good as the Press Pass, but I miss out on the special luncheon and the pure snottiness of receiving a Press Pass in the first place. I still get all of the discounts and goodies given to ZCEs attending the conference. Since my job description completely follows the theme of Creating Modern Web Applications with PHP, I don't see any reason why I would get turned down for this one, either.
Reimbursement
Even if I end up paying for this myself, I could get the money back from IBM under my professional development budget. Not as good as either of the other options, but I think I'll manage.

Whichever way it happens, I will still get to take my Zend PHP 5 Certification exam for free, and still get access to all of the expo goodness and tutorials. I haven't chosen which track to take, though...maybe I can mix and match.

As an added bonus, San Francisco and its wonderful food awaits! Plane tickets purchased, with ample time around the conference in San jose in which to binge at Axum, Phuket, Golden Era, Squat & Gobble, and maybe even Greens. Yum.

Labels:

Wednesday, October 04, 2006

Book deal!

I just need to receive the final contract from the publisher, but my book agent has closed the terms with the publisher and they want me to write their next book on Ajax! I don't really know at this point how much I can say about the content of the book, yet, so I'll err on the side of caution and simply say that I won't write just another so-you-want-to-get-started-with-Ajax book.

I feel very excited to have this opportunity, but I also feel more than slightly nervous, as I've never written so much as an article before, let alone an entire book. Luckily, I shouldn't have any problem whatsoever getting a month or so off from work (once we ship this next version of the product) in order to write full-time.

Labels: