 |
|
Sunday, October 24. 2004
I thought I'd comment on what we are trying to do with Syntax CMS and help potential users decide if it's right for them.
What Syntax CMS Is
Syntax CMS is a result of our custom development for Non Profit clients. Syntax CMS is the platform most of the sites that Forum One Communications has developed were built upon, either in its current form or one of the intermediate predecessors. Accordingly, you'll find it suited to people who have to build out a lot of custom features but don't want to reinvent the wheel each time. Syntax CMS and its ancestors have been used for everything from five or six-page sites without a lot of dynamic content to gargantuan information repositories to a environmental data collection and display Web application.
Syntax CMS is a starting point. Syntax CMS is built with the assumption that what you get out-of-the-box is not what you'll end up with as your finished site. We assume you'll not only want to change the templates around, but create new content types and program a couple of custom displays or even Web applications.
Syntax CMS is optimized for content administrators and programmers. Syntax CMS, as befits its primary role as our in-house development platform, is optimized for content administrators who only work with installed and configured instances as well as programmers who develop the applications the content administrators will later use. This is not to say it is the be-all and end-all of usability for either case, but this is where much of the effort we have put into the system has been. Accordingly, you'll see better documentation than in most in-house projects for programmers, and easier-to-use content interfaces than some more developer-centric Web applications. Improving both continues to be a priority for us, especially usability for content administrators.
Syntax CMS is content-based. Syntax CMS was developed with Forum One's clients in mind: policy organizations with a great amount of existing content either locked away in a static HTML Web site or offline in individual documents. Accordingly, the site assumes you'll have different content types, want to use the existing content anywhere it's appropriate, and maximize the ability of users to find the content no matter the strategy they use to find it: clicking on pages, filtering by category, or searching.
This is the main reason Syntax CMS exists--to support faceted classification and content discovery. My favorite metaphor, which I freely stole from one of our senior strategists, is finding salsa in the grocery store. You can look at the store directory and find "condiments," go to that aisle, and find salsa. But you can also find the same product under "ethnic food." And if there's a display of party supplies that includes tortilla chips, salsa will be there. It will also likely be near where tortilla chips themselves are sold. So the store wants to make it as easy as possible to find salsa, no matter how you might think of finding it. Syntax CMS wants to make it as easy as possible to find content items, no matter how you think of finding them.
What Syntax CMS Is Not
Syntax CMS is not a "product" Forum One sells. We sell and support solutions. There's no Syntax CMS box. We don't ship it. It's free. We don't provide support for the open source version the way we do the solutions we create for our customers, though of course you are welcome to make a special arrangement with Forum One to get support on a time and materials basis. It's open source because we thought it might be useful to the community, and really we have no reason to keep it closed. An active user community would be nice, as would contributions and improvements from that community, especially in areas that aren't our in-house priority (just because they're not a priority doesn't mean we wouldn't think they're useful or of value). However, our goal is not Syntax CMS on every server. We expect it will be useful for people with problems like ours. We expect to keep using it to provide smart solutions for policy organizations who come to us for strategy and development. We don't expect to make a lot of money from people who want a given feature and will pay us to implement it--though we of course would be happy to work something out. But bottom line, Syntax CMS is not a "product" and will not be for the foreseeable future.
Syntax CMS is not a CMS-in-a-box. In accordance with our "not a product" strategy, the open source download of Syntax CMS is not designed to be a finished application after installation. You should be able to do some things with it, but it's expected you'll want to tweak it and program it. This means we haven't put in a lot of time to things like a nice-looking default design, or swappable and extendable themes, or even making sure the thing generates zero errors when you first install it on a different server environment than ours (though it should be mostly useable, and getting to error-free is a goal of ours--just not a high priority).
Syntax CMS is not optimized for lone webmasters looking for a quick CMS. While we would love an installation wizard like Serendipity's, it's just not a priority of ours. After all, it's not a product we're trying to sell. So you'll find that while we try to reduce the dependencies, there are a number. It is not yet as easily configurable for different servers as we'd like. We haven't spent a lot of time making it work for other databases, though it is built to allow that possibility. These are areas we would actually encourage any outside users to contribute code or design advice. However it's not a priority inside our organization. But Syntax CMS is not designed for easy installation and configuration to put up a quick CMS for users to start in on. If you're the lone webmaster of an organization that wants a dynamic Web site without a lot of effort, you probably want one of the other Open Source CMSes out there. Even if we get it more easily installed, our not-a-product strategy means it is unlikely you'll see attractive default themes or every module your heart could desire ready and waiting for you. Unless, of course, you hire us to develop a really great Web site for you.
Syntax CMS is not (primarily) page-based. Some CMSes assume that all content will appear on pages, and pages will have authors, and everything on a page is configurable. Syntax CMS is not one of those CMSes. We assume users will come to our content from different directions: searching, browsing by some classification, exploring links of related content, or by following a heirarchical tree of pages. But for Syntax CMS, the content is the thing. We do provide the tools that let you use Syntax CMS to generate a site that mainly gives you dynamic control of a 5-page brochure site (and we have used it in this way). However, that's just not our focus.
In fact, it is this non-page-centric approach that has caused us to build and maintain our own solution, as I noted above. So if you're looking for a solution that is what Syntax CMS is and isn't what Syntax CMS isn't, give it a whirl and tell us what you think. But remember that it's not designed to be a one-size-fits-all product--it's just designed to be a flexible enough platform for you to turn it into the solution you need--just like we do on a daily basis.
Update: I have edited the entry to clarify our stance on the open source CMS we have made available versus the solutions we regularly provide to our clients.
Tuesday, August 17. 2004
When thinking about the architecture of a Syntax site, remember that just because someone may want a list of documents or events, it doesn't mean that ginning up a module and plunking it on the main navigation is the right answer. It has a lot of drawbacks:
- Your site navigation is not dynamic. This starts to resemble the Bad Old Days of static HTML. Once you pick a term, you're stuck with it. Never underestimate the psychological comfort that is had by having the option to change something, even if it's a bad idea to do it.
- Your page title is similarly static. This further resembles the Bad Old Days. It helps to get a project out the door when you can say "if you don't like it, you can change it." Again, this is true even if you've done killer information architecture and selected culturally appropriate and tested terms.
- You have limited options for dynamic text on the main listing page. You can embed a section call within the template through
F1CMS::module() (more on the power of that later), but that requires modifying not only the list template but also creating a custom template for each hidden section so it doesn't start listing other content items. Plus it turns your Section Navigation admin area into a nest of hidden sections that aren't really sections but content items. You could introduce a datatype that will function in that capacity, but that creates its own complications. How do you decide where each item will be published? Will you build its own module just for display?
Fortunately a much better option exists. You'll still have to create a custom section template, but it will be a logical, functional, and non-hidden section.
Continue reading "Sectional Thinking About Modules"
Monday, August 16. 2004
One of the cool features of Syntax, left over from its F1DB days, is the variable type field for each object.
The upshot is that, by default, when you use the type dbfield in an object, Syntax automagically looks for a picklist with the name of the datatype in it, in this format:
{name of object}types
So if you have a datatype called "article," the corresponding type picklist would be articletypes.
This is really useful, because it allows each datatype to have its own picklist of types--eventtypes, articletypes, puddingtypes--but only use one field in the master records table.
Otherwise, you'd have to make a new dbfield entry for each type you had, and they'd be available to other datatypes--which is silly when your datatype is blancmange and your list is puddingtypes (Blood, Yorkshire, Figgy).
However, with great power comes great responsibility. If you set up a new datatype and you choose to repurpose the type dbfield, you'll be up a creek.
But what if you'd like another such field?
Then you need to investigate the "Variable Table" feature of DBasis when creating that dbfield entry. So far, to my knowledge, it only accepts one variable:
$(objecttype)
The setup for the type dbfield is:
pickid variable table $(objecttype)types
Any text before or after $(objecttype) will be used when constructing the name of the pick table to look for. So my($objecttype)ingredients would result in myarticleingredients, myeventingredients, and of course, myblancmangeingredients.
Generally, of course, you'll find just the one default type dbfield more than sufficient.
Thursday, July 8. 2004
Through discussions at work, we've decided to target some additional improvements to Syntax for this quarter.
Restructuring Metadata
Though we have cached metadata already for the 1.0 release, our improvements were a hack. We're going to try to ease our transition to 2.0 by restructuring the metadata classes to have a central class that manages metadata collection and communication. This change should be invisible to the application author as well as be backward compatible with existing databases, so we're putting it in the 1.0 branch and not the 1.1 branch. They will require database changes to support, most likely, so we'll be incrementing the version number to trigger the upgrade script. However we're committed to keeping them backwards compatible.
Improving Filtering Performance
Actually, I'm pessimistic we can achieve that much here. I already have a design for a new, more optimized search class, and the changes we're making to metadata should help. However, it's a big project and it won't be easy. Additionally, it will be a new, coequal search class with some real limitations. The tradeoff will be increased speed for 80% of the work we do. This will also probably go into the 1.0 branch for 1.01 or 1.02, as it should be just an additional API for searching and filtering. No existing sites should break, but new code won't be back-portable without upgrading.
I've seen some other low-level things in the API that I think could be improved by not trying to be quite so general. There is a preg_match() and a eval() that get performed with alarming frequency in every syntax instance, and I'm not 100% convinced it's necessary, certainly for PHP 4+. Plus it annoyingly ties in Syntax data to the global scope, and that is always a Bad Thing. Fixing this will start us on the way to being able to root out the dependencies on register_globals that sadly exists in 1.0. Anybody interested in looking into it can examine the pxdb::import() and pxdb::import_var() methods.
Braindump complete.
Friday, June 11. 2004
With the changes we thought were going to be in 0.0.5, we found that the CMS was stable enough (and used in production internally enough) to be called 1.0. So here's a tentative roadmap:
0.0.4 (current)
On the public SourceForge site, this is only the API, and is fairly badly out of date. We've made a number of stability and feature enhancements to it. However, we're going to abandon 0.0.4 as is, because it's not worth updating singly.
1.0 (main trunk)
1.0 has lots of feature and performance enhancements from 0.0.4.
- A big honkin' CMS built with Syntax API technology. We'll put detailed feature lists in a separate announcement.
- Performance: We've achieved between a 2- and 4-fold increase in speed, mainly by optimizing the base classes and adding caching to commonly-requested data.
- Lots and lots and lots of bug fixes.
- A revamped interface for
Context, the content administration tool. It is now very stylable by CSS, has a tabbed interface for assigning privileges and sections, an export module to dump your content for each datatype automatically to Excel, an improved listing of picktables for editing, as well as an all-in-one view of approved and unapproved items, and it does not depend on JavaScript.
- Enhancements to
DBasis, including permissions for add, edit, and delete of datatypes as well as a separation of abstract datatype classes that you can safely replace when you reconfigure a datatype, leaving your user class with any encapsulated functionality intact.
1.1 (development branch)
- Add the ability to "feature" a content object in multiple sections while leaving it unfeatured in others.
- Event notification so you can have actions that occur when something is changed through the administrative classes.
This is tenative, but we hope to go to 1.0 publicly soon. 1.1 will be released...when it's released. We expect some point releases to 1.0 to occur as we fix bugs or release features that don't affect sites built with 1.0. If you just want the API, it's fairly easy to extract those elements from the source tree.
Friday, June 11. 2004
I updated the pxdb_collection class to no longer need to call pxdb_collection::find() after calling pxdb_collection::set_order(). Now you can either explicitly call pxdb_collection::execute() or simply start iterating through your list with pxdb_collection::fetch_record(), which calls pxdb_collection::execute() automatically if there's no result set.
Thursday, June 10. 2004
Riddle me this: Where, oh where, does the Syntax API register or even define the "check_date" validation function?
My answer? Dunno. Spent two hours looking for the little bugger. It must be defined in software somehow so that a grep for "check_date" reveals nothing. Somewhere there is a lambda function generating "check_" and "date", in some sort of bizarre obfuscation technique.
After all, it's not important that we know how we validate dates, is it? I mean, it's not like there are any errors in the code elsewhere, so why should I be concerned?
Update: I found it--it's not in the framework, where one might expect a default dbfield type to be validated. It's set and managed strictly in the Context tool, as basically client functionality.
It's nifty that you can extend the validation a second way like that, but that implies that such validation won't happen when you write an app that uses pxdb_commit and pxdb_input in the regular CMS--or any non Context application, for that matter.
The upshot is, I'm going to change this at some point. In the meantime, beware. Validation that seems like it should be framework level may be happening in client code.
Thursday, June 10. 2004
I had to re-figure this out today, so I thought I'd document it to save hair-pulling later.
If you are mucking about with Syntax's API innards (the pxdb_* classes) and make a structural change to the database, you need to create an upgrade script to handle it.
Background: Every time the Syntax API gets used, it calls PxDB/config/initialize.php. This checks to see if its sister-file run-once.php has been modified more recently than the database has been initialized. If so, it runs it.
initialize.php has a place to define the current version of the Syntax API. This is used by run-once to determine if an upgrade script needs to be run. If the version number is lower in the database than the file, it runs the script. If the version number is higher in the database than the file, it fires off a warning. If two version numbers are the same, it does not include an upgrade script.
The upgrade script goes in PxDB/config/install/ and should be named according to the ADODB name for the database you're using followed by a hyphen and the word "upgrade", e.g. mysql-upgrade.php. The script should ideally check the version number and retain previous upgrades, so theoretically someone could go from a version 0.0.1 to the absolute latest and catch all relevant upgrades.
You should also test for the existence of various columns and tables that you wish to affect so you don't get someone with a mismatched version number in too much trouble.
Once you've done all your testing, alter any tables and convert any data. Finally the script should set the version number in the DB pref key to the version number you've declared in initialize.php IF AND ONLY IF every SQL statement has executed successfully.
Note: This is true as of version 0.0.5. Oooh! Did I slip some news in? Maybe!
Tuesday, June 8. 2004
To access values in the widget parameters box ( the mysterious text input below the widget drop down in dbasis), in your widget script you have to access the $plugin_info['params'] array. It will have a hash of your key=>value pairs which are semicolon separated.
Example:
in the mystery text box your have
'watch=topic;relid=29'
in your widget script you can get the values via:
$watch = & $plugin_info['params']['watch'] ;
$relid = & $plugin_info['params']['relid'] ;
This sounds like something that should be in the syntax wiki...
-Oscar
OK, how about the blog instead?
Monday, June 7. 2004
Got this odd little bug forwarded to me from a client:
There seems to be a minor bug in the Article / Resource "add" page, and perhaps elsewhere, but I haven't tried to track down whether or not it comes up everywhere. When the time of day is between the hour and the half hour, the default posting time is 12:00am. After the half hour, the default posting time is the correct time of day.
I looked into it and it was a weird bug left over from the initial implementation. I fixed it on a test site and checked it into CVS.
The original author was making the number of minutes round down to the next lowest 15 minutes this way:
floor($minutes / 15) * 15
(and casting it to an int) but then he simply concatenated that value with the number of hours, which worked great 3/4 of the time. In the case of anything less than 15, however, that expression evaluates to zero, so after concatenation, you got:
10:0
instead of the
10:00
that you would expect. A quick sprintf() fixed it.
The upshot is, the bug only happens on the first 15 minutes of each hour, which is why nobody from our first Syntax client forward has noticed it until now, I'm guessing.
Anyway, if you see this bug, that's the class to update from CVS.
Update: Oops, that class I'm referring to is pxdb_widget in private/lib/ext/PxDB/classes/content/input/
Monday, June 7. 2004
I've had two instances of the same bug today. The symptom is this:
You use Context, the Syntax CMS administration tool, and it works fine except that once you login, any link you click on will take you back to the login screen. It just can't remember your login.
That's because it can't write the session files it needs. The solution is twofold: Make sure the directory private/tmp/session/ exists and is readable and writeable by the Web server. Then, in your public/.htaccess file, make sure that this line exists and points to your site:
php_value session.save_path "/path/to/your/Syntax/CMS/instance/private/tmp/session"
Once you do both of those, your admin tool should work (or at least be free of that problem).
Monday, June 7. 2004
One of our contractors today had an issue with the pxdb_auth class and login behavior. He wanted to submit a login form but stay on the members page he was submitting the login to (instead of $_SERVER['SCRIPT_URL'] as is expected). Even though the URL changed to reflect the fact he was POST-ing to a different URL, the section he'd logged in from stubbornly kept displaying.
It turns out that there are two hidden variables that by default are passed with login forms (you can see them in the login.tpl template). They are preserved_get_vars and preserved_post_vars. While the pxdb_auth class doesn't do anything different because of them, it does directly array_merge() them into the $_GET and $_POST arrays directly.
And in /private/init.php we see this line:
$Request->setPath($_GET['request']);
That means that the preserved_get_vars were encoding the current request path and passing them along. pxdb_auth->authenticate() was blythely putting them into the $_GET array, and the above line in init.php set the script to the previous request path.
The upshot being that no matter what you set the login form's action to, it would be clobbered by the preserved_get_vars setting for request.
I suppose the rationale behind this was to preserve any variables, such as ad-hoc search terms or sorting, and make sure that simply logging in to a URL didn't divert you from the exact page you were trying to view in the exact state you were trying to view it in. However, I think that's a kind of fragile way to do it, and maybe too limiting. I'm not sure if it was really intended to preserve the request path as well, and I'm not sure why the request path should always be expected to be in the $_GET variable.
So that's the way things work in the pre-release version of Syntax CMS, but I'd not rely on this behavior, as I think it's a prime candidate for correction in the near future.
Friday, June 4. 2004
This is an echange where Hans laid out his approach to error handling in Syntax CMS, and I added thoughts on error handling in general.
I know that error-handling, logging & the load that places on the server is becoming increasingly important. The error-handling/logging framework I adopted for GlobalEU should help eliminate excessive server load. Essentially, instead of sending an email or opening a log file each time an error is triggered, error messages (and debugging messages, if applicable) are collected and only written to the log file at the end of the script.
I used PEAR's error handling classes -- with a small PHP function that served to pass all PHP errors through the PEAR error handling mechanism. My error handler, Site::errorHandler(), knows to re-direct to an error page if error is above a configuration-specified threshold (i.e. fatal) and otherwise (if the error level meets the 'logable' criteria) will simply add the error to the Log queue.
That's all pretty standard. The different thing is that the log is only written at the end of the script. This is done by registering a shutdown function w/ PHP. My Site::shutdown() method performs all logging and any other cleanup actions, and is registered with PHP like this:
$Site = &Site::singleton(); // b/c you can't register static methods
register_shutdown_function(array($Site, 'shutdown'));
Doing logging at script termination will mean that an error email or mysql insert will only happen once per script execution, and not once per every error on the page. (Though, I think Jo would prefer we not use either of those methods for error logging.)
My reply:
The other good thing about Hans's approach here is that provides a hook for explicitly handling errors for the user. This is something we're starting to do but can do more of.
In my old classes, I would throw a warning as soon as an error occurred and then immediately return false from that function. The calling code would test for that, and either compensate or throw an error and return false itself, up the line until I got to the page level code, which would also verify that no function or method it called threw an error, otherwise it "handles" the error and triggers its own warning.
So even if you don't replace PHP's error functions with your own (ideal), you can still explicitly handle things at the warning level and provide non-ugly recovery or a friendly notice to your users. Either way, we should be testing for failure and providing code that works in case of failure so our users don't see broken-looking or error-ridden pages.
Sadly, pxdb_* classes were not built with that level of error handling. Also, we found out that PEAR::Error is a huge performance hog, and we sped up the sites noticeably by ditching it...and additionally, we got more reliable error reporting, especially for warnings that should have been dealt with before sites moved to production.
I think we need to move back to a Syntax CMS-level function that handles fatal errors (E_ERROR or E_USER_ERROR) and does the redirecting to an error page--but this time it won't be identical to the authentication failed page and the file not found (404) page.
Friday, June 4. 2004
Still going through my old inbox entries. This one talks about the history of filter classes and gives a bit of the rationale for the current design. Would it had been discussed a bit more (it could be smarter about left joins)...
From: hans@forumone.com
Subject: [classes] next generation filtering
Date: September 5, 2002 10:32:56 AM EDT
To: dev@forumone.com
Hi F1devers,
For those of you who have used the f1db_filter class, I have a new f1db
filter class available [built for PHlexDB, but does not depend on
PHlexDB framework] that is infinitely more flexible.
The current f1db_filters class has 1-level of filtering depth. I.e. you
can filter on docs related to a given author, or on authors related to a
given organization, but there is no way to filter on docs related to
people who are related to a given organization. For BYJ, Sandy hacked
the class to support 2-level deep filtering on picklists (I think it was
to add ability to filter on X related to X that has a certain picklist
value).
The new filtering paradigm has no limitations on levels of depth. Also
filter components can be re-used & re-combined to produce different
result sets.
It's pretty slick -- I've used this filtering framework extensively on
new project spaces "intranet" page, where you will be able to see, e.g.,
all documents which are related to a project of which you are a member.
Additionally, the new filtering class is actually a "search" class --
you can choose whether you want to filter (i.e. 'exclusive') or search
('inclusive') the records table.
Bear this in mind if you think you could use more flexible filtering
systems in a project.
Cheers,
Hans
Friday, June 4. 2004
Was cleaning out my inbox and found this note from the PhlexDB days. I figure it's worth capturing here, as it still applies to Syntax in its current form.
There's been a couple of questions about prefix & path in PHlexDB. Because
PHlexDB doesn't rely on any values set in scripts, it uses the pxdb_prefs
("preferences" tab in DBasis) table for things like the file upload prefix and
path. It is looking, in particular, for 2 preferences:
[name] [key] [value]
'datastore' 'path' '/var/www/vhosts/etf.www/private/files'
'datastore' 'url' '/files/'
Using the pxdb_prefs class in conjunction w/ documentInfo:
$prefs = &pxdb_prefs::singleton();
$docInfo->set_local_filestore_path($prefs->get_pref('datastore', 'path'));
$docInfo->set_local_filestore_prefix($prefs->get_pref('datastore', 'url'));
A general note about the preferences class. It's a pretty simple table
structure (3-cols) & it's indexed, so while there is someoverhead in getting
values out of this table, it's not very large. **Also, preferences are cached
so that subsequent requests for the same pref on a page have no db call.
You can store whatever values you want in the pxdb_prefs table (you can use
add_pref(), set_pref(), or set_default_pref() methods). It's purpose is to
allow several web apps (e.g. admin areay, dbasis tool, and public site) that
share the same database to know about site-specific settings.
Cheers,
Hans
|
|