Textpattern 5
From Textpattern CMS User Documentation
Deprecated page! Page will be kept until content is recycled, then terminated.
Textpattern 5 (also known as TXP5) is a rewrite of Textpattern version 4. It is based on the [Spark/Plug Framework] developed by Sam Weiss.
Textpattern 5 development is hosted at the [Textpattern 5 Google Code site]. Developers from both in and outside the Textpattern user community are invited to participate.
Progress reports and notes on Textpattern 5 will be posted on the [Textpattern Blog]. There are also open discussions at the Textpattern forum ([example]).
Contents |
Textpattern 5 Developer Guidelines: Admin side
Setting up a dev environment
Firstly you will need to verify that Mercurial is installed and available on your web server of choice, and of course a virtual server so you can test what’s going on in your browser. We use local.txp5.com. For help with setting up Mercurial and/or virtual servers, Google is your friend. There are many fine video tutorials on Youtube as well ([Mercurial example], [virtual server example])
There are two elements to Textpattern 5: The [Spark/Plug Framework] The [Textpattern 5 Google Code site]
If you intend to work locally you need to use Mercurial to check out the code from both the above links and put them side-by-side in your docroot web directory (wherever that is in your server).
Mercurial / Google Code Workflow Description
You should create a clone of textpattern5 directly on the Google Code pages, check it out to your local environment, work on your mods, push them to your clone and then submit a pull request for us to merge your changes into the core.
Once you have pulled the code down, use it with a fresh database from a clean install of the latest Textpattern 4.x release. For the meantime we are using the same tables: they will be incrementally altered as necessary during development. Spark/Plug supports MySQL as well as other databases such as SQLite, so choose which you prefer.
The next step is to visit your local textpattern5/config and duplicate admin-config-dist.php and site-config-dist.php. Call them admin-config.php and site-config.php respectively. Ensure you do not put these under Mercurial’s control (Read [how to exclude files from Mercurial's control]).
Edit admin-config.php next and change the following settings:
-
sparkplug_dir: /full/path/to/sparkplug-framework/sparkplug
Edit site-config.php and change the following settings:
-
use_index_file: set to false if your web server supports clean URLs -
adapter: if you are using something other than MySQL -
dsn: the database type and name you are using -
username: the user account assigned to the database (must have all privs) -
password: the password for database access
You should then be able to browse to textpattern5/admin in your browser and see Textpattern 5 running. A version of these instructions (with more detail on commands to use) can also be found in the Google Code repository.
The MVC file system as it relates to Txp 5
Compare the current Textpattern version 4 admin-side folder structure:
-
/include/txp_admin.php- renders the entire Admin:Users tab and its database interaction -
/include/txp_page.php- renders the entire Presentation:Pages tab and its database interaction - ...
With Txp 5 things look very different, as the tasks to interact with any page are split into the distinct M-V-C phases that take place:
-
/core/admin/controllers/admin.php- handles any URLs of the format '/admin/action/...' -
/core/admin/models/users.php- handles database interaction for the Admin:Users tab -
/core/admin/views/admin/users.php- renders the Admin:Users UI content only -
/core/admin/controllers/presentation.php- handles any URLs of the format '/presentation/action/...' -
/core/admin/models/pages.php- handles database interaction for the Presentation:Pages tab -
/core/admin/views/presentation/pages.php- renders the Presentation:Pages UI content only - ...
In addition:
-
/core/admin/views/default.php- renders a common template for ALL tabs, from <!DOCTYPE to </html> -
/core/admin/views/default_form.php- renders a common layout for ALL UI content (the bit between header and footer)
The difference is subtle in terms of what we see on the screen but very different behind the scenes insofar as we should strive for commonality across tabs if possible. That is, in the code we only need to deviate from the default view if a tab really, really requires it. i.e. if that tab is massively different from the norm.
Now, while we could have a dedicated View per tab, we should aim for commonality across tabs to make the code simpler (and probably faster). We can then rely on the framework to do the heavy lifting.
With that goal in mind we want to try and minimize the variability between tabs. More on Views later.
Description of Files and Directories in Textpattern 5
-
/admin:-
/js: holds admin-wide javascript files -
/theme: admin-side theme directories, one per theme, containing CSS and images -
index.php: main admin-side starting point for rendering the interface
-
-
/config: the configuration files as mentioned above -
/core:-
/admin: the MVC files for the admin interface in three separate directories-
/controllers: one file per primary tab (a.k.a. ‘area’ in Txp 4-speak) -
/models: one file per tab to hold the DB interaction -
/views: one dir per primary tab, then one file per secondary tab to render that tab’s content -
default.php: default tab layout -
header.php: the tab navigation system and page bar (equivalent to pagetop() in Txp 4) -
footer.php: the page footer (equivalent to end_page() in Txp 4) -
notify.php: the message display area -
default_form.php: template for rendering the input fields in the main content area on each tab -
app.php: the app jumpoff point / install validator / plugin loader
-
-
/shared: library classes that both admin-side and public-side share-
txp_admin_controller.php: defines the tab structure and the app-wide default HTML form -
txp_base.php: version and version checker -
/languages: lang files for the interface
-
-
A lot of the really cool Spark/plug functionality is down to naming convention. This will be covered in the subsequent topics where necessary.
Subscribe / notify (a.k.a. callback_event / register_callback)
Textpattern 5 uses the notion of subscribing to events / steps in much the same way as Txp4. But there are differences.
Callbacks are no longer just strings, they are colon-separated paths. For example, the path:
txp:site_change:presentation:styles:edit
refers to the admin-side’s Presentation->Style tab (Txp4’s ‘event’), and the ‘edit’ action (Txp4’s ‘step’). Thus if you raised a callback like this:
$this->observer->observe( array($this, 'my_custom_style_edit'), 'txp:site_change:presentation:styles:edit' );
it would call my_custom_style_edit() when anyone clicked to edit a Stylesheet.
The major departure from Textpattern 4 is that you can elect to subscribe to any path in the hierarchy and be notified of all events below it. Thus you could subscribe to:
txp:site_change:presentation:styles
and you would be notified when any action (add, edit, delete, ...) on the Presentation->Styles tab was taken. This helps plugin authors target things more easily too and provides more flexibility over Txp 4.
In addition to ‘txp’ events, Spark/Plug has its own internal event paths that you can observe. These are:
| SparkAuthController |
login |
|
|
|
logout |
|
| SparkLang |
load |
|
| SparkSessionStore |
validate_ip |
fail |
|
|
|
success |
| warning |
SparkValidator |
norule |
| Spark |
cache |
flush |
|
|
|
request_flush |
|
|
manufacture |
|
| SparkPageCache |
request_flush |
|
|
|
request_disable |
|
| SparkPlug |
redirect |
before |
|
|
|
after |
|
|
|
pre |
|
|
|
post |
| SparkView |
render |
before |
|
|
|
after |
|
|
|
pre |
|
|
|
post |
| SparkApplication |
run |
exception |
|
|
|
exit |
|
|
|
before |
|
|
|
after |
|
|
|
pre |
|
|
|
post |
|
|
display |
before |
|
|
|
after |
|
|
|
pre |
|
|
|
post |
|
|
dispatch |
before |
|
|
|
after |
|
|
|
pre |
|
|
|
post |
Textpattern 5 MVC Components in Depth
Models
Models contain database interaction. They should not care about such things as "display columns" and "required fields": they should be concerned only with efficiently fetching and storing data on behalf of controllers.
One quick mental test for whether something should go in the model: if you were to create an alternate data store that uses XML files instead of a database, would you need to change this model code?
The best place to see what database interaction methods are available is in sparkplug/plugs/sparkdb/sparkdb.php. As well as the usual selectRows(), insertRow(), deleteRow(), etc, there are lots of useful build*() methods for combining and conjoining query parts. Use them!
Controllers
Controllers contain instructions on what actions to take when a particular URL is detected.
Only methods prefixed with action_ can run when the corresponding URL is accessed. e.g. action_pages() inside /controllers/presentation.php is automatically called when the URL is /presentation/pages. Usually this method is a jumpoff that tests the URL parameters and calls a dedicated method to handle that action (a.k.a.‘step’ in Txp 4 jargon). $params['pv'] is essentially the $_POST array which can be tested and checked.
You usually start each action method with:
$this->getCommonVars($vars);
self::$lang->load('name-of-tab');
The first sets up the default things like app title, tab title, the selected tab name, body class, default language, locale, file paths, the tab names, etc: that is, the stuff from site-config.php and a few other handy items (see /core/shared/txp_admin_controller.php). Feel free to augment this list on a tab-by-tab basis or add any sensible default values to the GetCommonVars() method in txp_admin_controller.php.
self::$lang is available to grab strings from the currently set language (default: en_GB). Use the load() method to read the strings in and the get() method to request a particular string translation, passing any replacement vars as subsequent parameters. Whereas Txp 4 uses one file per language, Txp 5 uses one directory per language so we could separate them into smaller files if desired. At the moment it’s one file per tab but that might get a little unwieldy so maybe one per primary tab (presentation, admin, content) is better. It will depend how the RPC server is used (if at all).
$vars holds stuff you want the view to have access to. Each key gets extracted into a variable in the view. e.g. $var['notice'] becomes $notice in the view.
Field-level validation is handled as a parameter to each form field. For example, required_if[copy] can automatically throw error messages if the conditions are not met, in this case if the field is not filled out when the button named ‘copy’ is hit. Pipe-delimit the rules.
Spark/Plug defines some key validation rules but you can define your own. See the methods beginning validate_ in /sparkplug/plugs/sparkvalidator/sparkvalidator.php (and for a quick list, consult /sparkplug/languages/en_US/spark_validator.php file which contains the error messages thrown when certain conditions fail).
If you want to validate form fields, add a ->validator($this) to your form. For example:
$form = $this->newForm($isNew ? 'style_new' : 'style_edit', 'Style') ->validationCallback(array($this, $isNew ? '_validateForm_newStyle' : '_validateForm_editStyle')) ->processingCallback(array($this, $isNew ? '_processForm_newStyle' : '_processForm_editStyle')) ->validator($this) ;
Then, any methods in the controller that begin with validate_ automatically become validation rules in input fields. e.g. validate_abc means that you can use ‘abc’ like this in an input field:
$form->text('name', 'Some label:', NULL, NULL, true, 'abc');
If the validate_abc() method returns true/false then it indicates a pass/fail condition. If it returns a string then that is used as the value for the input. In this way you can sanitize input data or indicate success/failure. Some central validation routines have been imported from Txp 4 and put in /core/shared/input.php but they need work yet.
Flash message are items that only exists for the next page request, i.e. to pass stuff like error messages to the next page. So:
$this->session->flashSet('notice', self::$lang->get('css_created', $this->newStyleName));
will flash a ‘notice’ message up on completion of the current task. Defined types so far are:
- notice
- warning
- error
You can either use $this->session->flashGet(‘type’); to retrieve the latest message that’s been set or just assign the message directly to the $vars[message_type] array, e.g. $vars[‘notice’] = “Action done”. It’s up to the logic whether you prefer one or the other, bearing in mind that $this->redirect() can be used to jump to a different action step, which may or may not automatically render notices that have not been explicitly set using $this->session->flashSet(). Consult /core/admin/views/notify.php to see how display of the messages is handled.
A few other rules and tips:
- Don’t use inline forms because you won’t benefit from the built-in CSRF protection
-
$form->input()sets up all form fields. -
$form->defaults()handles error cases or empty URL vars -
$form->run()'s parameters are callbacks for additional checks. -
$this->render('default', $vars)is usually the last line in any controller’s action sequence to pass control to the View to draw the interface
And a couple of handy methods:
-
$this->dropParam: remove an item from the beginning of an array. Handy for dropping the action from controller URLs ready to pass to the action method. -
$this->redirect: useful for jumping to another action. For example, after copying an item you can jump to the ‘edit’ action for the clone. Paths for the various screens are held in$vars[‘urls’]but note they are not configurable as that would break the controllers.
Views (via controller vars)
Views render the interface as seen on-screen. We’re validating to HTML 5, at least as far as the spec has been ratified.
Views should be as human-readable as possible and contain mostly HTML, i.e. no heredoc! Escape into and out of PHP at will using <?= … ?> to echo contents of variables, and <? foreach($thingy as $item) ?> or <? if (some condition): ?> [ note the colon at the end ] with corresponding <? endforeach; ?> and <? endif; ?> lines.
Try and keep form field names the same as the column in the database that each represents.
To add HTML form fields to the interface, create a new form in the controller:
$form = $this->newForm(‘form_name’, ‘label’)
chain some validator/processor methods to it as shown above, then chain form fields to it:
->textArea(
‘field_name’,
‘label’,
‘default_content’,
array(‘additional_attribute’ => ‘value’),
required (true/false),
‘validation_rules’
)
->submit(
‘field_name’,
‘label’,
‘default_content’,
array(‘additional_attribute’ => ‘value’),
default_if_empty (true/false),
)
and so on. See /sparkplug/plugs/sparkform/sparkform.php for the available HTML field methods.
Assign the $form to $vars[‘form’] so the default_form.php can access it. The view can then iterate over the form field elements and display them in the primary nav area. Lists of things that appear in the secondary nav area can be assigned likewise so they can be iterated over and displayed with inline PHP. No naming convention has been set yet for this, but it would probably be prudent to pick one.
By default, each form field will be rendered as a list item (li) inside an unordered list. The class of the list item will be the field’s input type, e.g. text, hidden, textarea, submit, etc. If you want to group items, add a ->fieldset_open(‘name’) and ->fieldset_close() pair around the elements in question. The grouped items will be rendered inside their own unordered list (ul) and will be inline instead of stacked above each other. If you want to deviate from this layout you can elect not to use the default form and use your own instead, but the more tabs that can use the default form, the less code we need to write. If the default form needs tweaking so it is applicable to more than one type of tab then feel free to modify it to suit.
Bear in mind that the goal of the interface is to have each major element directly addressable from CSS/javascript without descendant selectors. If we can use existing class names and IDs from Txp 4 (where they make sense) then do so. These will be augmented or replaced by aria roles and HTML 5 elements where appropriate.
Views can render() views, and all sub-views get access to $vars.




