kolibri-discuss team mailing list archive
-
kolibri-discuss team
-
Mailing list archive
-
Message #00027
[Merge] lp:~asteinlein/kolibri/validation-improved into lp:kolibri
Anders Steinlein has proposed merging lp:~asteinlein/kolibri/validation-improved into lp:kolibri.
Requested reviews:
Kolibri Dev (kolibri-dev)
See branch whiteboard and commit logs for details.
--
https://code.launchpad.net/~asteinlein/kolibri/validation-improved/+merge/5355
Your team Kolibri Discuss is subscribed to branch lp:kolibri.
=== modified file 'examples/wishlist/actions/Index.php'
--- examples/wishlist/actions/Index.php 2008-12-03 02:22:07 +0000
+++ examples/wishlist/actions/Index.php 2009-04-07 22:06:43 +0000
@@ -1,14 +1,16 @@
<?php
/**
- * Action for the display of the front page. Retrieves the wishlist items and returns the XSL page as the
- * result.
+ * Action for the display of the front page. Retrieves the wishlist items and returns the XSL
+ * page as the result. We also implement ModelAware so any validation errors when adding items
+ * are displayed.
*/
-class Index extends ActionSupport {
+class Index extends ActionSupport implements ModelAware {
+ public $model;
public $items;
/**
- * As the name implies, doGet() is called for GET request. It must return an instance of a Result class, in
- * this case a XsltResult for a XSL transformation.
+ * As the name implies, doGet() is called for GET request. It must return an instance of a
+ * Result class, in this case a XsltResult for a XSL transformation.
*/
public function doGet () {
$dbSetup = new DatabaseSetup();
@@ -18,8 +20,10 @@
}
$items = Models::init('Item');
- $this->items = $items->objects->findAll(); // Notice that this calls findAll() in the ItemDao class
- return new XsltResult($this, '/index'); // Path relative to views directory, extension omitted
+ // Notice that this calls findAll() in the ItemDao class
+ $this->items = $items->objects->findAll();
+ // Path is relative to views directory, extension omitted
+ return new XsltResult($this, '/index');
}
}
?>
=== modified file 'examples/wishlist/actions/items/Add.php'
--- examples/wishlist/actions/items/Add.php 2008-10-20 16:41:10 +0000
+++ examples/wishlist/actions/items/Add.php 2009-04-07 20:59:58 +0000
@@ -1,40 +1,35 @@
<?php
/**
- * Action for adding new item. We implement ModelAware to have the model object populated by request
- * data, and ValidationAware to have it automatically validated.
+ * Action for adding new item. We implement ModelAware to have the model object populated by
+ * request data, and ValidationAware to have it automatically validated.
*/
class Add extends ActionSupport implements ModelAware, ValidationAware {
/**
- * Defines the model class to instantiate, which will be populated by request data and put back into
- * this variable.
+ * Defines the model class to instantiate, which will be populated with request data and
+ * put back into this property.
*/
public $model = 'Item';
/**
- * Any validation errors are contained herein. If emtpy, the model is valid according to its rules.
- */
- public $errors;
-
- /**
- * As the name implies, this handles POST.
+ * As the name implies, this handles POST. It will only be called if the model validates,
+ * else validationFailed() will be called instead.
*/
public function doPost () {
- if (empty($this->errors)) {
- /*
- * No validation errors are reported, so we can go ahead and save the model. Notice that $this->model
- * now is a fully prepared model.
- */
- if ($this->model->save()) {
- $this->msg->setMessage('Item successfully added.');
- }
- return new RedirectResult($this, '/');
- }
+ $this->model->save();
+ $this->msg->setMessage('Item successfully added.');
+ return new RedirectResult($this, '/');
+ }
- /*
- * Validation errors found, so return the page again to display errors with the form populated. If we
- * redirect, error messages and form data will be lost.
- */
- return new XsltResult($this, '/index');
+ /**
+ * This is called when validation fails, in order for us to redirect back to where the
+ * form is presented. By using redirect instead of simply displaying the form now,
+ * we conform to the Post-Redirect-Get webapp pattern, which among other things lets
+ * users safely go Back/Forward and Refresh.
+ */
+ public function validationFailed () {
+ // We could set a custom error message here if we want to override the default. I.e.:
+ // $this->msg->setMessage('The item could not be added to the wishlist', false);
+ return new RedirectResult($this, '/');
}
}
?>
=== modified file 'examples/wishlist/conf/config.php'
--- examples/wishlist/conf/config.php 2009-03-27 23:31:04 +0000
+++ examples/wishlist/conf/config.php 2009-04-07 23:17:37 +0000
@@ -4,8 +4,8 @@
* get by calling Config::get('key'), where key is the setting you want to return, i.e. 'mail'.
*/
$config = array(
- 'webRoot' => '', // Change if not on root level. Prefix with slash if not empty, but no trailing!
- 'staticRoot' => '/static', // URI of static resources (can be another host as http://static.example.com)
+ 'webRoot' => 'http://localhost', // Must be absolute URI including scheme. No trailing slash!
+ 'staticRoot' => '/static', // URI of static resources (can be another host as http://static.example.com)
'locale' => 'en_US.utf8',
'logging' => array(
'enabled' => false, // When logging is disabled, errors are outputted directly. When enabled...
=== added file 'examples/wishlist/views/snippets/forms.xsl'
--- examples/wishlist/views/snippets/forms.xsl 1970-01-01 00:00:00 +0000
+++ examples/wishlist/views/snippets/forms.xsl 2009-04-07 22:06:43 +0000
@@ -0,0 +1,553 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<xsl:stylesheet version="1.0"
+ xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:exsl="http://exslt.org/common"
+ xmlns:func="http://exslt.org/functions"
+ xmlns:k="http://kolibriproject.com/xml"
+ extension-element-prefixes="exsl func">
+
+ <!--
+ Executes the current form template, retrieving the Kolibri form definition which is subsequently
+ parsed into an XHTML form.
+
+ @return NodeSet The XHTML form described by the current form template.
+ -->
+ <func:function name="k:form">
+ <xsl:variable name="structure">
+ <xsl:call-template name="form" />
+ </xsl:variable>
+
+ <func:result>
+ <xsl:apply-templates select="exsl:node-set($structure)/k:form" />
+ </func:result>
+ </func:function>
+
+ <!--
+ Simple function to check if a context node is one of the supported Kolibri form field
+ elements.
+
+ @return Boolean true() if the context node is a Kolibri form field.
+ -->
+ <func:function name="k:is-form-field">
+ <func:result select="self::k:input or self::k:radio or self::k:checkbox or self::k:textarea
+ or self::k:select or self::k:hidden" />
+ </func:function>
+
+ <!--
+ Generates attributes on the surrounding element of a form field.
+ A named template is used to create attributes conditionally.
+ -->
+ <xsl:template name="form-element-attributes">
+ <xsl:variable name="classes">
+ <!-- Add descriptive class names if the form field is required or contains errors -->
+ <xsl:if test="not(@required) or @required != 'false'">
+ <class>required</class>
+ </xsl:if>
+ <xsl:if test="k:has-error()">
+ <class>error</class>
+ </xsl:if>
+ <!-- Add class name for radio buttons, checkboxes and hidden fields (TODO: All form fields?) -->
+ <xsl:if test="self::k:radio or self::k:checkbox or self::k:hidden">
+ <class><xsl:value-of select="substring-after(name(), ':')" /></class>
+ </xsl:if>
+ <xsl:if test="position() mod 2 = 0">
+ <class>even</class>
+ </xsl:if>
+ </xsl:variable>
+
+ <!-- Only create the attribute if $classes contains a string with nodes -->
+ <xsl:if test="string($classes)">
+ <xsl:attribute name="class">
+ <xsl:value-of select="k:string-list(exsl:node-set($classes)/*, ' ', ' ')" />
+ </xsl:attribute>
+ </xsl:if>
+ </xsl:template>
+
+ <!--
+ Generates attributes on a form field, from the more general attributes like id and name to
+ the more specific like size or value.
+ -->
+ <xsl:template name="input-field-attributes">
+ <!-- Set ID attribute if specified -->
+ <xsl:if test="@id">
+ <xsl:attribute name="id"><xsl:value-of select="@id" /></xsl:attribute>
+ </xsl:if>
+
+ <!--
+ Set name attribute to name attribute or id attribute. Store in variable to use as
+ identifier for the value attribute.
+ -->
+ <xsl:variable name="name">
+ <xsl:choose>
+ <xsl:when test="@name"><xsl:value-of select="@name" /></xsl:when>
+ <xsl:otherwise><xsl:value-of select="@id" /></xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:attribute name="name"><xsl:value-of select="$name" /></xsl:attribute>
+
+ <!-- Set type attribute automatically for radio buttons, check boxes and hidden fields. -->
+ <xsl:if test="self::k:radio or self::k:checkbox or self::k:hidden">
+ <xsl:attribute name="type"><xsl:value-of select="substring-after(name(), ':')" /></xsl:attribute>
+ </xsl:if>
+ <xsl:if test="self::k:input">
+ <xsl:attribute name="type">
+ <xsl:choose>
+ <xsl:when test="@type"><xsl:value-of select="@type" /></xsl:when>
+ <xsl:otherwise>text</xsl:otherwise>
+ </xsl:choose>
+ </xsl:attribute>
+ <xsl:attribute name="size">
+ <xsl:choose>
+ <xsl:when test="@size"><xsl:value-of select="@size" /></xsl:when>
+ <xsl:otherwise>30</xsl:otherwise>
+ </xsl:choose>
+ </xsl:attribute>
+ <xsl:if test="@maxlength">
+ <xsl:attribute name="maxlength"><xsl:value-of select="@maxlength" /></xsl:attribute>
+ </xsl:if>
+ </xsl:if>
+ <xsl:if test="self::k:select">
+ <xsl:attribute name="size">
+ <xsl:choose>
+ <xsl:when test="@size"><xsl:value-of select="@size" /></xsl:when>
+ <xsl:otherwise>1</xsl:otherwise>
+ </xsl:choose>
+ </xsl:attribute>
+ <xsl:if test="@multiple">
+ <xsl:attribute name="multiple">multiple</xsl:attribute>
+ </xsl:if>
+ </xsl:if>
+
+ <!--
+ Set value attribute conditionally, it is required for radio buttons and check boxes.
+ For textareas we set the content of the generated element.
+ -->
+ <xsl:choose>
+ <xsl:when test="self::k:checkbox">
+ <xsl:variable name="value">
+ <xsl:choose>
+ <xsl:when test="@value"><xsl:value-of select="@value" /></xsl:when>
+ <xsl:otherwise>true</xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+
+ <!--
+ Default value of a check box is simply 'true', which is also
+ supported as value of model property to automatically select the check box.
+ -->
+ <xsl:attribute name="value"><xsl:value-of select="$value" /></xsl:attribute>
+ <xsl:if test="k:model-value($name) = $value or @checked = 'true'">
+ <xsl:attribute name="checked">checked</xsl:attribute>
+ </xsl:if>
+ </xsl:when>
+ <xsl:when test="self::k:radio">
+ <xsl:attribute name="value"><xsl:value-of select="@value" /></xsl:attribute>
+ <xsl:if test="(@value and k:model-value($name) = @value) or @checked = 'true'">
+ <xsl:attribute name="checked">checked</xsl:attribute>
+ </xsl:if>
+ </xsl:when>
+ <xsl:when test="not(self::k:textarea) and not(self::k:select)">
+ <!--
+ For every normal form field the value is set through 'value' attribute,
+ model property value or simply the Kolibri form element's text content.
+ -->
+ <xsl:choose>
+ <xsl:when test="@value">
+ <xsl:attribute name="value"><xsl:value-of select="@value" /></xsl:attribute>
+ </xsl:when>
+ <xsl:when test="$model and string(k:model-value($name))">
+ <xsl:attribute name="value"><xsl:value-of select="k:model-value($name)" /></xsl:attribute>
+ </xsl:when>
+ <xsl:when test="string(text())">
+ <xsl:attribute name="value"><xsl:value-of select="text()" /></xsl:attribute>
+ </xsl:when>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:when test="self::k:textarea">
+ <!-- Textareas has their text content as the value of the form field. -->
+ <xsl:choose>
+ <xsl:when test="$model and string(k:model-value($name))">
+ <xsl:value-of select="k:model-value($name)" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="." />
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ </xsl:choose>
+
+ <!-- Set disabled status if specified -->
+ <xsl:if test="not(self::k:hidden) and (@disabled = 'disabled' or @disabled = 'true')">
+ <xsl:attribute name="disabled">disabled</xsl:attribute>
+ </xsl:if>
+
+ <!-- Set custom class attribute if specified -->
+ <xsl:variable name="cssClasses">
+ <xsl:if test="@class">
+ <class><xsl:value-of select="@class" /></class>
+ </xsl:if>
+ <xsl:choose>
+ <xsl:when test="@type">
+ <class><xsl:value-of select="@type" /></class>
+ </xsl:when>
+ <!-- Default type of an input field is text if @type doesn't exist -->
+ <xsl:when test="self::k:input">
+ <class>text</class>
+ </xsl:when>
+ <!-- Radio buttons and check boxes get 'radio' or 'checkbox' as class as well -->
+ <xsl:when test="self::k:radio or self::k:checkbox">
+ <class><xsl:value-of select="substring-after(name(), ':')" /></class>
+ </xsl:when>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:if test="string($cssClasses)">
+ <xsl:attribute name="class">
+ <xsl:value-of select="k:string-list(exsl:node-set($cssClasses)/*, ' ', ' ')" />
+ </xsl:attribute>
+ </xsl:if>
+ </xsl:template>
+
+ <!--
+ Generates the content of a form element; either a standalone form field, custom HTML or plain text.
+ -->
+ <xsl:template name="form-element-content">
+ <xsl:variable name="inCustomDiv" select="boolean(self::k:div)" />
+ <xsl:for-each select="*|text()">
+ <xsl:choose>
+ <xsl:when test="k:is-form-field() and $inCustomDiv">
+ <xsl:apply-templates select="." mode="standalone" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:apply-templates select="." />
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each>
+ </xsl:template>
+
+ <!--
+ Prints out error messages for a form field. Radio buttons are a special case, where
+ errors will only be printed after the last radio button in a button group.
+ -->
+ <xsl:template name="field-errors">
+ <xsl:choose>
+ <xsl:when test="self::k:radio">
+ <!-- Only print out errors for the last radio button in a group -->
+ <xsl:if test=". = //k:radio[@name = current()/@name and last()]">
+ <xsl:apply-templates select="k:get-errors(@name)" />
+ </xsl:if>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:variable name="id">
+ <xsl:choose>
+ <xsl:when test="@name or @id">
+ <xsl:choose>
+ <xsl:when test="@name"><xsl:value-of select="@name" /></xsl:when>
+ <xsl:otherwise><xsl:value-of select="@id" /></xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:when test="self::k:div">
+ <xsl:choose>
+ <xsl:when test="descendant::*/@name[1]">
+ <xsl:value-of select="descendant::*/@name[1]" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="descendant::*/@id[1]" />
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:apply-templates select="k:get-errors($id)" />
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+
+ <!--
+ Template for parsing <k:form> elements, describing the structure of an XHTML form, optionally
+ representing a Kolibri model.
+ -->
+ <xsl:template match="k:form">
+ <form action="{@action}" method="post">
+ <xsl:if test="@id">
+ <xsl:attribute name="id"><xsl:value-of select="@id" /></xsl:attribute>
+ </xsl:if>
+ <xsl:if test="@method">
+ <xsl:attribute name="method"><xsl:value-of select="@method" /></xsl:attribute>
+ </xsl:if>
+ <xsl:if test="@enctype">
+ <xsl:attribute name="enctype"><xsl:value-of select="@enctype" /></xsl:attribute>
+ </xsl:if>
+
+ <!-- Generate form fields with k:form templates -->
+ <xsl:apply-templates select="*" />
+ </form>
+ </xsl:template>
+
+ <!--
+ Simple fieldset template. Supports adding a legend through using a legend attribute on
+ k:fieldset (when no separate legend element exists). Also supports adding error class attribute
+ if a typical Kolibri ul element with errors exists within the fieldset.
+ -->
+ <xsl:template match="k:form/k:fieldset">
+ <fieldset>
+ <xsl:if test="@id">
+ <xsl:attribute name="id"><xsl:value-of select="@id" /></xsl:attribute>
+ </xsl:if>
+ <xsl:if test="*[name() = 'ul' and @class = 'errors']">
+ <xsl:attribute name="class">error</xsl:attribute>
+ </xsl:if>
+ <xsl:if test="@legend and not(*[name() = 'legend'])">
+ <legend><xsl:value-of select="@legend" /></legend>
+ </xsl:if>
+
+ <!-- Generate form fields with k:form templates -->
+ <xsl:apply-templates select="*|text()" />
+ </fieldset>
+ </xsl:template>
+
+ <!--
+ Template for k:div elements. Creates a div with a label element. The label will be linked to
+ the form field if it's the only field inside the k:div, otherwise a 'for' attribute will need
+ to be provided for the k:div element to indicate which field the label should be linked to.
+ -->
+ <xsl:template match="k:div">
+ <xsl:variable name="fields" select="descendant::*[self::k:input or self::k:textarea or
+ self::k:select or self::k:radio or self::k:checkbox]" />
+
+ <div>
+ <xsl:call-template name="form-element-attributes" />
+
+ <xsl:choose>
+ <xsl:when test="count($fields) > 1 and @label">
+ <label>
+ <xsl:if test="@for">
+ <xsl:attribute name="for"><xsl:value-of select="@for" /></xsl:attribute>
+ </xsl:if>
+ <xsl:value-of select="@label" />
+ </label>
+ </xsl:when>
+ <xsl:when test="count($fields) = 1">
+ <label>
+ <xsl:attribute name="for">
+ <xsl:choose>
+ <xsl:when test="@for"><xsl:value-of select="@for" /></xsl:when>
+ <xsl:otherwise><xsl:value-of select="$fields/@id" /></xsl:otherwise>
+ </xsl:choose>
+ </xsl:attribute>
+ <xsl:value-of select="$fields/@label" />
+ </label>
+ </xsl:when>
+ </xsl:choose>
+
+ <!-- Create the actual form element(s) -->
+ <xsl:call-template name="form-element-content" />
+
+ <!-- List all validation errors if there are any -->
+ <xsl:call-template name="field-errors" />
+ </div>
+ </xsl:template>
+
+ <!--
+ Template for all Kolibri form fields not wrapped in a k:div, except hidden fields.
+ -->
+ <xsl:template match="*[not(parent::k:div) and k:is-form-field() and not(self::k:hidden)]">
+ <!-- Fetch label preferably from attribute, otherwise from text content of form field -->
+ <xsl:variable name="labelContent">
+ <xsl:choose>
+ <xsl:when test="@label"><xsl:value-of select="@label" /></xsl:when>
+ <xsl:when test="string(text())"><xsl:value-of select="text()" /></xsl:when>
+ </xsl:choose>
+ </xsl:variable>
+ <div>
+ <xsl:call-template name="form-element-attributes" />
+
+ <!-- Only create label if there's defined content for it -->
+ <xsl:if test="$labelContent">
+ <label>
+ <xsl:if test="@id">
+ <xsl:attribute name="for"><xsl:value-of select="@id" /></xsl:attribute>
+ </xsl:if>
+ <!-- Generate form field inside label for radio buttons and check boxes -->
+ <xsl:if test="self::k:radio or self::k:checkbox">
+ <xsl:apply-templates select="." mode="standalone" />
+ </xsl:if>
+ <xsl:value-of select="$labelContent" />
+ </label>
+ </xsl:if>
+
+ <!--
+ Generate form field outside label for fields other than radio buttons and check boxes,
+ or for radio buttons and check boxes without a label
+ -->
+ <xsl:if test="not($labelContent) or (not(self::k:radio) and not(self::k:checkbox))">
+ <xsl:apply-templates select="." mode="standalone" />
+ </xsl:if>
+
+ <!-- List all validation errors if there are any -->
+ <xsl:call-template name="field-errors" />
+ </div>
+ </xsl:template>
+
+ <!--
+ Simple template for creating a hidden form element outside k:div.
+ -->
+ <xsl:template match="k:hidden">
+ <xsl:apply-templates select="." mode="standalone" />
+ </xsl:template>
+
+ <!--
+ Creates a select box from a k:select element with either Kolibri's k:option element
+ or a simple custom XML structure as data for the option elements.
+ If a custom XML structure is used the 'value' and 'text' attributes need to be supplied
+ for the k:select element, containing the name of the XML node or attribute which contains
+ each option's value and text.
+ -->
+ <xsl:template match="k:select" mode="standalone">
+ <!-- Find model property identifier and value of the selected option -->
+ <xsl:variable name="name">
+ <xsl:choose>
+ <xsl:when test="@name"><xsl:value-of select="@name" /></xsl:when>
+ <xsl:otherwise><xsl:value-of select="@id" /></xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:variable name="selected" select="k:model-value($name)" />
+
+ <select>
+ <xsl:call-template name="input-field-attributes" />
+
+ <xsl:choose>
+ <xsl:when test="k:optgroup or k:option">
+ <!-- Explicit k:option elements defined -->
+ <xsl:apply-templates select="*[self::k:optgroup or self::k:option]">
+ <xsl:with-param name="selected" select="$selected" />
+ </xsl:apply-templates>
+ </xsl:when>
+ <xsl:otherwise>
+ <!--
+ Use the node set inside k:select as data providers for option elements,
+ using the 'value' and 'text' attribute on k:select to get child node values
+ for option elements.
+ -->
+ <xsl:variable name="valueNode" select="@value" />
+ <xsl:variable name="textNode" select="@text" />
+
+ <xsl:for-each select="*">
+ <xsl:variable name="value">
+ <xsl:choose>
+ <xsl:when test="$valueNode">
+ <xsl:value-of select="descendant::*[name() = $valueNode]" />
+ </xsl:when>
+ <xsl:otherwise><xsl:value-of select="text()" /></xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:variable name="text">
+ <xsl:choose>
+ <xsl:when test="$textNode">
+ <xsl:value-of select="descendant::*[name() = $textNode]" />
+ </xsl:when>
+ <xsl:otherwise><xsl:value-of select="text()" /></xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+
+ <option value="{$value}">
+ <xsl:if test="$value = $selected">
+ <xsl:attribute name="selected">selected</xsl:attribute>
+ </xsl:if>
+ <xsl:value-of select="$text" />
+ </option>
+ </xsl:for-each>
+ </xsl:otherwise>
+ </xsl:choose>
+ </select>
+ </xsl:template>
+
+ <xsl:template match="k:select/k:optgroup">
+ <xsl:param name="selected" />
+ <optgroup label="{@label}">
+ <xsl:apply-templates select="k:option">
+ <xsl:with-param name="selected" select="$selected" />
+ </xsl:apply-templates>
+ </optgroup>
+ </xsl:template>
+
+ <xsl:template match="k:option">
+ <xsl:param name="selected" />
+ <option value="{@value}">
+ <xsl:if test="@selected = 'true' or @value = $selected">
+ <xsl:attribute name="selected">selected</xsl:attribute>
+ </xsl:if>
+ <xsl:choose>
+ <xsl:when test="string(.)">
+ <xsl:value-of select="." />
+ </xsl:when>
+ <xsl:otherwise><xsl:value-of select="@value" /></xsl:otherwise>
+ </xsl:choose>
+ </option>
+ </xsl:template>
+
+ <!--
+ Creates a textarea from k:textarea.
+ -->
+ <xsl:template match="k:textarea" mode="standalone">
+ <textarea cols="30" rows="10">
+ <!-- Allow overriding default values for cols and rows -->
+ <xsl:if test="@cols">
+ <xsl:attribute name="cols"><xsl:value-of select="@cols" /></xsl:attribute>
+ </xsl:if>
+ <xsl:if test="@rows">
+ <xsl:attribute name="rows"><xsl:value-of select="@rows" /></xsl:attribute>
+ </xsl:if>
+ <xsl:call-template name="input-field-attributes" />
+ </textarea>
+ </xsl:template>
+
+ <!--
+ Creates input elements for text/password fields, check boxes, radio buttons and hidden fields from
+ k:input, k:checkbox, k:radio and k:hidden fields.
+ -->
+ <xsl:template match="k:input|k:checkbox|k:radio|k:hidden" mode="standalone">
+ <input>
+ <xsl:call-template name="input-field-attributes" />
+ </input>
+ </xsl:template>
+
+ <!--
+ Convenience template for creating the submit section of a form. Supplies the
+ ID of a model object through a hidden field if a model object exists.
+ -->
+ <xsl:template match="k:submit">
+ <div class="submit">
+ <!-- If a current model element exists, supply the model ID through a hidden 'original' field -->
+ <xsl:if test="$model and $model/original">
+ <input type="hidden" name="original" value="{$model/original}" />
+ </xsl:if>
+ <!-- Generate any other hidden fields -->
+ <xsl:apply-templates select="k:hidden" />
+
+ <span class="submit">
+ <button type="submit" name="{@name}">
+ <xsl:attribute name="name">
+ <xsl:choose>
+ <xsl:when test="@name"><xsl:value-of select="@name" /></xsl:when>
+ <xsl:when test="@id"><xsl:value-of select="@id" /></xsl:when>
+ <xsl:otherwise>save</xsl:otherwise>
+ </xsl:choose>
+ </xsl:attribute>
+ <xsl:value-of select="@value|@label" />
+ </button>
+ </span>
+ </div>
+ </xsl:template>
+
+ <!--
+ Special templates to allow normal (X)HTML elements to be mixed in with
+ k:form and it's related elements.
+ -->
+ <xsl:template match="*[ancestor::k:form]" priority="-0.5">
+ <xsl:element name="{name()}">
+ <xsl:copy-of select="@*" />
+ <xsl:call-template name="form-element-content" />
+ </xsl:element>
+ </xsl:template>
+</xsl:stylesheet>
=== modified file 'examples/wishlist/views/snippets/kolibri.xsl'
--- examples/wishlist/views/snippets/kolibri.xsl 2008-10-20 16:41:10 +0000
+++ examples/wishlist/views/snippets/kolibri.xsl 2009-04-07 22:13:36 +0000
@@ -12,9 +12,11 @@
xmlns:k="http://kolibriproject.com/xml"
extension-element-prefixes="exsl func">
- <!-- Makes the XML structures for model and errors available for the custom element templates -->
+ <xsl:include href="message.xsl" />
+ <xsl:include href="forms.xsl" />
+
+ <!-- Makes the XML structures for model available for the custom element templates -->
<xsl:variable name="model" select="/result/model" />
- <xsl:variable name="errors" select="/result/errors" />
<func:function name="k:number">
<xsl:param name="value" />
@@ -61,6 +63,21 @@
</func:result>
</func:function>
+ <func:function name="k:truncate">
+ <xsl:param name="str" />
+ <xsl:param name="maxlen" />
+
+ <xsl:choose>
+ <xsl:when test="($maxlen + 1) > string-length($str)">
+ <func:result select="$str" />
+ </xsl:when>
+ <xsl:otherwise>
+ <func:result>
+ <xsl:value-of select="substring($str, 1, $maxlen - 1)" />â¦
+ </func:result>
+ </xsl:otherwise>
+ </xsl:choose>
+ </func:function>
<!--
Convenience function for linking to an external CSS file from the configured root of static files.
@@ -78,7 +95,11 @@
<!--
Fetches the value of a model attribute.
-
+
+ TODO: Generalize to k:object-value, where object by default is $model. This way,
+ :: attributes could be returned recursively (since objects/arrays are the same in XML).
+ And the function could be used for more than models.
+
@param String attribute The name of the attribute in the model.
@return NodeSet The nodeset for the value of the model attribute.
-->
@@ -86,6 +107,12 @@
<xsl:param name="attribute" select="@id" />
<xsl:choose>
+ <xsl:when test="contains($attribute, '[') and not(contains($attribute, '[]'))">
+ <xsl:variable name="prop" select="substring-before($attribute, '[')" />
+ <xsl:variable name="child" select="substring-before(substring-after($attribute, '['), ']')" />
+
+ <func:result select="$model/*[name() = $prop]/*[name() = $child]" />
+ </xsl:when>
<xsl:when test="contains($attribute, '::')">
<xsl:variable name="prop" select="substring-before($attribute, '::')" />
<xsl:variable name="child">
@@ -118,7 +145,17 @@
<func:result select="$model/*[name() = $prop]/*[position() = $pos]/*[name() = $child]" />
</xsl:when>
<xsl:otherwise>
- <func:result select="$model/*[name() = $attribute]" />
+ <xsl:variable name="pureName">
+ <xsl:choose>
+ <xsl:when test="contains($attribute, '[')">
+ <xsl:value-of select="substring-before($attribute, '[')" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$attribute" />
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+ <func:result select="$model/*[name() = $pureName]" />
</xsl:otherwise>
</xsl:choose>
</func:function>
@@ -136,7 +173,7 @@
<xsl:value-of disable-output-escaping="yes" select="$errors/*[name() = $attribute]" />
</xsl:variable-->
- <func:result select="$errors/*[name() = $attribute]" />
+ <func:result select="$model/errors/*[name() = $attribute]" />
</func:function>
<!--
@@ -156,409 +193,17 @@
</xsl:choose>
</xsl:param>
- <func:result select="boolean($errors/*[name() = $attribute])" />
- </func:function>
-
- <!--
- Executes the current form template, retrieving the Kolibri form definition which is subsequently
- parsed into an XHTML form.
-
- @return NodeSet The XHTML form described by the current form template.
- -->
- <func:function name="k:form">
- <xsl:variable name="structure">
- <xsl:call-template name="form" />
- </xsl:variable>
-
- <func:result>
- <xsl:apply-templates select="exsl:node-set($structure)" />
- </func:result>
- </func:function>
-
- <!-- General attributes on the surrounding element for a complete form field -->
- <xsl:attribute-set name="form-field">
-
- <!-- Add descriptive class names if the form field is required or contains errors -->
- <xsl:attribute name="class">
- <xsl:choose>
- <xsl:when test="@required and k:has-error()">
- <xsl:text>required error</xsl:text>
- </xsl:when>
- <xsl:when test="@required">
- <xsl:text>required</xsl:text>
- </xsl:when>
- <xsl:when test="k:has-error()">
- <xsl:text>error</xsl:text>
- </xsl:when>
- </xsl:choose>
- <xsl:if test="self::k:radio or self::k:checkbox or self::k:hidden">
- <xsl:text> </xsl:text><xsl:value-of select="substring-after(name(), ':')" />
- </xsl:if>
- <xsl:if test="position() mod 2 = 0">
- <xsl:text> even</xsl:text>
- </xsl:if>
- </xsl:attribute>
- </xsl:attribute-set>
-
- <!--
- Template for parsing <k:form> elements, describing the structure of an XHTML form, optionally
- representing a Kolibri model.
- -->
- <xsl:template match="k:form">
- <!-- Normal form attributes default to the attributes on the k:form element -->
- <xsl:param name="action" select="@action" />
- <xsl:param name="method" select="@method" />
- <xsl:param name="enctype" select="@enctype" />
-
- <form action="{$action}" method="post">
- <xsl:if test="@id">
- <xsl:attribute name="id"><xsl:value-of select="@id" /></xsl:attribute>
- </xsl:if>
- <xsl:if test="$method">
- <xsl:attribute name="method"><xsl:value-of select="$methd" /></xsl:attribute>
- </xsl:if>
- <xsl:if test="$enctype">
- <xsl:attribute name="enctype"><xsl:value-of select="$enctype" /></xsl:attribute>
- </xsl:if>
-
- <!-- Generate form fields for elements we support -->
- <xsl:apply-templates select="k:fieldset|k:div|k:input|k:select|k:radio|k:checkbox|k:textarea|k:hidden|k:submit" />
- </form>
- </xsl:template>
-
- <xsl:template match="k:fieldset">
- <xsl:variable name="customErrors" select="*[local-name() = 'ul'][@class = 'errors']" />
-
- <fieldset>
- <xsl:if test="@id">
- <xsl:attribute name="id"><xsl:value-of select="@id" /></xsl:attribute>
- </xsl:if>
- <xsl:if test="$customErrors">
- <xsl:attribute name="class">error</xsl:attribute>
- </xsl:if>
- <xsl:if test="@legend">
- <legend><xsl:value-of select="@legend" /></legend>
- </xsl:if>
- <xsl:if test="k:legend">
- <legend>
- <label>
- <xsl:apply-templates select="k:legend/*[1]/self::node()" mode="standalone" />
- <xsl:copy-of select="k:legend/text()" />
- </label>
- </legend>
- </xsl:if>
-
- <xsl:apply-templates select="k:div|k:input|k:select|k:radio|k:checkbox|k:textarea|k:hidden" />
-
- <xsl:copy-of select="$customErrors" />
- </fieldset>
- </xsl:template>
-
- <!--
- Matches k:div elements, and k:input or k:textarea which are not contained in a k:div.
- -->
- <xsl:template match="k:div |
- *[name() != 'k:div']/*[self::k:input or self::k:select or self::k:textarea or
- self::k:radio or self::k:checkbox or self::k:hidden]">
- <xsl:variable name="fields" select="k:input|k:select|k:radio|k:checkbox|k:textarea|k:hidden" />
- <xsl:variable name="content">
- <xsl:choose>
- <xsl:when test="self::k:div">
- <!-- Create the first element with ID equal to the label "for" attribute -->
- <xsl:apply-templates select="$fields[position() = 1]" mode="standalone">
- <xsl:with-param name="id" select="@id" />
- </xsl:apply-templates>
- <!-- Create the rest of the field elements, if any -->
- <xsl:apply-templates select="$fields[position() > 1]" mode="standalone" />
- </xsl:when>
- <xsl:otherwise>
- <xsl:apply-templates select="." mode="standalone" />
- </xsl:otherwise>
- </xsl:choose>
- </xsl:variable>
-
- <div xsl:use-attribute-sets="form-field">
- <xsl:choose>
- <xsl:when test="self::k:div or self::k:input or self::k:select or self::k:textarea">
- <label for="{@id}"><xsl:value-of select="@label" /></label>
- <xsl:copy-of select="$content" />
- </xsl:when>
- <xsl:when test="self::k:hidden">
- <xsl:copy-of select="$content" />
- </xsl:when>
- <xsl:otherwise>
- <xsl:variable name="label">
- <xsl:choose>
- <xsl:when test="@label"><xsl:value-of select="@label" /></xsl:when>
- <xsl:otherwise><xsl:value-of select="text()" /></xsl:otherwise>
- </xsl:choose>
- </xsl:variable>
-
- <label for="{@id}">
- <xsl:copy-of select="$content" />
- <xsl:value-of select="$label" />
- </label>
- </xsl:otherwise>
- </xsl:choose>
-
- <!-- Show inline info for the form field -->
- <xsl:apply-templates select="k:info" />
-
- <!-- List all validation errors if there are any -->
- <xsl:choose>
- <xsl:when test="self::k:radio">
- <!--
- Only print out errors if this radio button is the last one of those
- grouped together with it.
- -->
- <xsl:variable name="grouping" select="@name" />
- <xsl:if test=". = //k:radio[@name = $grouping][last()]">
- <xsl:apply-templates select="k:get-errors($grouping)" />
- </xsl:if>
- </xsl:when>
- <xsl:when test="self::k:div">
- <xsl:variable name="id">
- <xsl:choose>
- <xsl:when test="$fields[1]/@id"><xsl:value-of select="$fields[1]/@id" /></xsl:when>
- <xsl:otherwise><xsl:value-of select="$fields[1]/@name" /></xsl:otherwise>
- </xsl:choose>
- </xsl:variable>
- <xsl:apply-templates select="k:get-errors($id)" />
- </xsl:when>
- <xsl:otherwise>
- <xsl:apply-templates select="k:get-errors(@id)" />
- </xsl:otherwise>
- </xsl:choose>
- </div>
- </xsl:template>
-
- <xsl:template match="k:info">
- <p class="info">
- <xsl:copy-of select="./child::node()" />
- </p>
- </xsl:template>
-
- <xsl:template match="k:input" mode="standalone">
- <xsl:param name="id" select="@id" />
-
- <xsl:variable name="name">
- <xsl:choose>
- <xsl:when test="@name"><xsl:value-of select="@name" /></xsl:when>
- <xsl:otherwise><xsl:value-of select="@id" /></xsl:otherwise>
- </xsl:choose>
- </xsl:variable>
- <xsl:variable name="type">
- <xsl:choose>
- <xsl:when test="@type"><xsl:value-of select="@type" /></xsl:when>
- <xsl:otherwise>text</xsl:otherwise>
- </xsl:choose>
- </xsl:variable>
- <xsl:variable name="value">
- <xsl:choose>
- <xsl:when test="@value"><xsl:value-of select="@value" /></xsl:when>
- <xsl:otherwise><xsl:value-of select="text()" /></xsl:otherwise>
- </xsl:choose>
- </xsl:variable>
- <xsl:variable name="class">
- <xsl:if test="@class"><xsl:value-of select="@class" /></xsl:if>
- <xsl:value-of select="concat(' ', $type)" />
- </xsl:variable>
-
- <input id="{$id}" name="{$name}" type="{$type}" size="30" value="{$value}" class="{$class}">
- <!-- Allow overriding default values for name, type and size -->
- <xsl:if test="@size">
- <xsl:attribute name="size"><xsl:value-of select="@size" /></xsl:attribute>
- </xsl:if>
-
- <!-- Set optional attributes -->
- <xsl:if test="@disabled = 'disabled'">
- <xsl:attribute name="disabled">disabled</xsl:attribute>
- </xsl:if>
- <xsl:if test="@maxlength">
- <xsl:attribute name="maxlength"><xsl:value-of select="@maxlength" /></xsl:attribute>
- </xsl:if>
-
- <!-- If a current model exists we override the default field value -->
- <xsl:if test="$model and k:model-value($name)">
- <xsl:attribute name="value"><xsl:value-of select="k:model-value($name)" /></xsl:attribute>
- </xsl:if>
- </input>
- </xsl:template>
-
- <xsl:template match="k:select" mode="standalone">
- <xsl:variable name="name">
- <xsl:choose>
- <xsl:when test="@name"><xsl:value-of select="@name" /></xsl:when>
- <xsl:otherwise><xsl:value-of select="@id" /></xsl:otherwise>
- </xsl:choose>
- </xsl:variable>
- <xsl:variable name="size">
- <xsl:choose>
- <xsl:when test="@size"><xsl:value-of select="@size" /></xsl:when>
- <xsl:otherwise>1</xsl:otherwise>
- </xsl:choose>
- </xsl:variable>
-
- <select name="{$name}" size="{$size}">
- <xsl:if test="@id">
- <xsl:attribute name="id"><xsl:value-of select="@id" /></xsl:attribute>
- </xsl:if>
- <xsl:variable name="selected">
- <xsl:value-of select="$model/*[local-name() = $name]" />
- </xsl:variable>
-
- <xsl:choose>
- <xsl:when test="k:option">
- <!-- Explicit k:option elements defined -->
- <xsl:for-each select="k:option">
- <option value="{@value}">
- <xsl:if test="@value = $selected">
- <xsl:attribute name="selected">selected</xsl:attribute>
- </xsl:if>
- <xsl:value-of select="." />
- </option>
- </xsl:for-each>
- </xsl:when>
- <xsl:otherwise>
- <!--
- Use the node set inside k:select as data providers for option elements,
- using the 'value' and 'text' attribute on k:select to get child node values
- for option elements.
- -->
- <xsl:variable name="valueNode" select="@value" />
- <xsl:variable name="textNode" select="@text" />
-
- <xsl:for-each select="*">
- <xsl:variable name="value" select="*[local-name() = $valueNode]" />
- <option value="{$value}">
- <xsl:if test="$value = $selected">
- <xsl:attribute name="selected">selected</xsl:attribute>
- </xsl:if>
- <xsl:value-of select="*[local-name() = $text]" />
- </option>
- </xsl:for-each>
- </xsl:otherwise>
- </xsl:choose>
- </select>
- </xsl:template>
-
- <xsl:template match="k:textarea" mode="standalone">
- <textarea id="{@id}" name="{@id}" cols="30" rows="10">
- <!-- Allow overriding default values for cols and rows -->
- <xsl:if test="@cols">
- <xsl:attribute name="cols"><xsl:value-of select="@cols" /></xsl:attribute>
- </xsl:if>
- <xsl:if test="@rows">
- <xsl:attribute name="rows"><xsl:value-of select="@rows" /></xsl:attribute>
- </xsl:if>
-
- <!-- Set optional attributes -->
- <xsl:if test="@disabled = 'disabled'">
- <xsl:attribute name="disabled">disabled</xsl:attribute>
- </xsl:if>
- <xsl:if test="@class">
- <xsl:attribute name="class"><xsl:value-of select="@class" /></xsl:attribute>
- </xsl:if>
-
- <!-- If a current model exists we override the default field value -->
- <xsl:choose>
- <xsl:when test="$model">
- <xsl:value-of select="k:model-value(@id)" />
- </xsl:when>
- <xsl:otherwise>
- <xsl:value-of select="@value | ." />
- </xsl:otherwise>
- </xsl:choose>
- </textarea>
- </xsl:template>
-
- <!--
- Creates a simple checkbox from a k:checkbox element.
- -->
- <xsl:template match="k:checkbox" mode="standalone">
- <xsl:variable name="name">
- <xsl:choose>
- <xsl:when test="@name"><xsl:value-of select="@name" /></xsl:when>
- <xsl:otherwise><xsl:value-of select="@id" /></xsl:otherwise>
- </xsl:choose>
- </xsl:variable>
-
- <input type="checkbox" name="{$name}" value="true">
- <xsl:if test="@id">
- <xsl:attribute name="id"><xsl:value-of select="@id" /></xsl:attribute>
- </xsl:if>
- <xsl:if test="@value">
- <xsl:attribute name="value"><xsl:value-of select="@value" /></xsl:attribute>
- </xsl:if>
- <xsl:if test="k:model-value(@id) = 'true' or @checked = 'true'">
- <xsl:attribute name="checked">checked</xsl:attribute>
- </xsl:if>
- <xsl:if test="@disabled = 'disabled' or @disabled = 'true'">
- <xsl:attribute name="disabled">disabled</xsl:attribute>
- </xsl:if>
- </input>
- </xsl:template>
-
- <xsl:template match="k:radio" mode="standalone">
- <xsl:variable name="name">
- <xsl:choose>
- <xsl:when test="@name"><xsl:value-of select="@name" /></xsl:when>
- <xsl:otherwise><xsl:value-of select="@id" /></xsl:otherwise>
- </xsl:choose>
- </xsl:variable>
-
- <input type="radio" name="{$name}" value="{@value}">
- <xsl:if test="@id">
- <xsl:attribute name="id"><xsl:value-of select="@id" /></xsl:attribute>
- </xsl:if>
- <xsl:if test="@checked and @checked = 'true'">
- <xsl:attribute name="checked">checked</xsl:attribute>
- </xsl:if>
- <xsl:if test="@disabled = 'disabled' or @disabled = 'true'">
- <xsl:attribute name="disabled">disabled</xsl:attribute>
- </xsl:if>
- </input>
- </xsl:template>
-
- <!-- Creates a hidden form field described by a k:hidden field. Exists mostly for completeness right now -->
- <xsl:template match="k:hidden" mode="standalone">
- <xsl:variable name="name">
- <xsl:choose>
- <xsl:when test="@name"><xsl:value-of select="@name" /></xsl:when>
- <xsl:otherwise><xsl:value-of select="@id" /></xsl:otherwise>
- </xsl:choose>
- </xsl:variable>
- <xsl:variable name="value">
- <xsl:choose>
- <xsl:when test="@value"><xsl:value-of select="@value" /></xsl:when>
- <xsl:otherwise><xsl:value-of select="k:model-value($name)" /></xsl:otherwise>
- </xsl:choose>
- </xsl:variable>
-
- <input type="hidden" name="{$name}" value="{$value}" />
- </xsl:template>
-
- <xsl:template match="k:submit">
- <xsl:variable name="cssClass">
- <xsl:choose>
- <xsl:when test="@class"><xsl:value-of select="@class" /></xsl:when>
- <!-- TODO: Change to something more generic than BEV classes -->
- <xsl:otherwise>knapp kjop</xsl:otherwise>
- </xsl:choose>
- </xsl:variable>
-
- <div class="submit">
- <!-- If a current model element exists, supply the model ID through a hidden 'original' field -->
- <xsl:if test="$model">
- <input type="hidden" name="original" value="{$model/original}" />
- </xsl:if>
- <!-- Generate any other hidden fields -->
- <xsl:apply-templates select="k:hidden" mode="standalone" />
-
- <!-- TODO: Remove custom HTML for bev and replace with a general override mechanism -->
- <span class="{$cssClass}">
- <button type="submit" name="{@name}"><xsl:value-of select="@value" /></button>
- </span>
- </div>
+ <func:result select="boolean($model/errors/*[name() = $attribute])" />
+ </func:function>
+
+ <!--
+ Prints out a list of errors for an element.
+ -->
+ <xsl:template match="errors/*">
+ <ul class="errors">
+ <xsl:for-each select="*">
+ <li><xsl:value-of select="." /></li>
+ </xsl:for-each>
+ </ul>
</xsl:template>
</xsl:stylesheet>
=== modified file 'src/actions/ModelAware.php'
--- src/actions/ModelAware.php 2008-10-20 16:41:10 +0000
+++ src/actions/ModelAware.php 2009-04-07 22:06:43 +0000
@@ -1,22 +1,22 @@
<?php
/**
- * This interface is used by actions that want a model auto-instantiated and populated with values from
- * request parameters. The action must expose a public <code>$model</code> property which will hold the
- * populated model. The <code>ModelInterceptor</code> must be configured for the action for this to have
- * any effect.
- *
- * @version $Id: ModelAware.php 1523 2008-07-09 23:32:14Z anders $
+ * This interface is used by actions that want a model auto-instantiated and populated with
+ * values from request parameters (or after a redirect from validation, the session). The
+ * action must expose a public <code>$model</code> property which will hold the populated model.
+ *
+ * For a model to be instantiated and populated, the <code>ModelInterceptor</code> must be
+ * configured for the action, and the action must provide the name (or object) of the model
+ * to use. This can be done either by setting a default value on the $model property like so:
+ *
+ * public $model = 'ModelName';
+ *
+ * Or, if a model with inner models should be created:
+ *
+ * public $model = array('MainModelName', 'propertyInModel' => array('AnotherModelName'));
+ *
+ * Alternatively, you can return an already instantiated model by implementing a
+ * <code>getModel()</code> method. If present, this takes precedence over names specified in
+ * $model.
*/
-interface ModelAware {
- /**
- * Returns the name of the model to instantiate and populate, or an array with the names and structure
- * of models if the main model contains other models. If an array is to be returned, it must have a
- * structure similar to the following example (it can be as deep as you want).
- *
- * array('MainModelName', 'propertyInModel' => array('AnotherModelName'))
- *
- * @return mixed Model class to instantiate.
- */
- //public function getModelName ();
-}
+interface ModelAware {}
?>
=== modified file 'src/conf/autoload.php'
--- src/conf/autoload.php 2008-12-08 20:36:09 +0000
+++ src/conf/autoload.php 2009-04-08 15:34:28 +0000
@@ -28,7 +28,7 @@
'SessionInterceptor' => '/interceptors/SessionInterceptor.php',
'UploadInterceptor' => '/interceptors/UploadInterceptor.php',
'UtilsInterceptor' => '/interceptors/UtilsInterceptor.php',
- 'ValidatorInterceptor' => '/interceptors/ValidatorInterceptor.php',
+ 'ValidationInterceptor' => '/interceptors/ValidationInterceptor.php',
'TransactionInterceptor' => '/interceptors/TransactionInterceptor.php',
'DataProvided' => '/models/DataProvided.php',
'ModelProxy' => '/models/ModelProxy.php',
=== modified file 'src/conf/config.php'
--- src/conf/config.php 2009-02-22 02:42:02 +0000
+++ src/conf/config.php 2009-04-07 23:17:37 +0000
@@ -5,8 +5,8 @@
* Config::get('key'), where key is the setting you want to return, i.e. 'mail'.
*/
$config = array(
- 'webRoot' => '', // Change if not on root level. No trailing slash!
- 'staticRoot' => '/static', // URI of static resources (can be another host as http://static.example.com)
+ 'webRoot' => 'http://localhost', // Must be absolute URI including scheme. No trailing slash!
+ 'staticRoot' => '/static', // URI of static resources (can be another host as http://static.example.com)
'locale' => 'en_US.utf8',
'logging' => array(
'enabled' => false
=== modified file 'src/conf/interceptors.php'
--- src/conf/interceptors.php 2008-12-11 15:10:26 +0000
+++ src/conf/interceptors.php 2009-04-08 15:34:28 +0000
@@ -6,7 +6,7 @@
*/
$interceptors = array(
'message' => 'MessageInterceptor',
- 'validation' => 'ValidatorInterceptor',
+ 'validation' => 'ValidationInterceptor',
'error' => array(
'ErrorInterceptor' => array('result' => 'PhpResult', 'view' => '/error')
),
=== modified file 'src/interceptors/MessageInterceptor.php'
--- src/interceptors/MessageInterceptor.php 2008-10-20 16:41:10 +0000
+++ src/interceptors/MessageInterceptor.php 2009-04-07 22:47:54 +0000
@@ -2,11 +2,8 @@
require(ROOT . '/lib/Message.php');
/**
- * Interceptor which provides the target action, if <code>MessageAware</code>, with a facility to give
- * the user status messages. This interceptor should be defined early in the interceptor stack,
- * ideally after the <code>SessionInterceptor</code> and before the <code>ErrorInterceptor</code>.
- *
- * @version $Id: MessageInterceptor.php 1518 2008-06-30 23:43:38Z anders $
+ * Interceptor which provides the target action, if <code>MessageAware</code>, with a facility
+ * to give the user status messages.
*/
class MessageInterceptor extends AbstractInterceptor {
/**
@@ -16,25 +13,31 @@
$action = $dispatcher->getAction();
if ($action instanceof MessageAware) {
- $action->msg = Message::getInstance();
+ // If a previous message is set in the session, put it into the action
+ if ($action instanceof SessionAware && isset($action->session['message'])) {
+ $action->msg = $action->session['message'];
+ $action->session->remove('message');
+ }
+ // Otherwise create a new instance
+ else {
+ $action->msg = Message::getInstance();
+ }
}
$result = $dispatcher->invoke();
- $this->checkMessageInSession($action);
+
+ /*
+ * If we are about to redirect and a message has been set, save it temporarily in the
+ * session so it can be retrieved in the new location.
+ */
+ if ($result instanceof RedirectResult
+ && $action instanceof SessionAware
+ && $action instanceof MessageAware
+ && !$action->msg->isEmpty()) {
+ $action->session['message'] = $action->msg;
+ }
+
return $result;
}
-
- /**
- * Checks to see if the session has a message stored while the action do not. If so, the
- * message is injected into the action message and removed from the session.
- */
- private function checkMessageInSession ($action) {
- if ($action instanceof SessionAware && $action instanceof MessageAware && $action->msg->isEmpty()) {
- if (isset($action->session['message'])) {
- $action->msg = $action->session['message'];
- unset($action->session['message']);
- }
- }
- }
}
?>
=== modified file 'src/interceptors/ModelInterceptor.php'
--- src/interceptors/ModelInterceptor.php 2008-10-20 16:41:10 +0000
+++ src/interceptors/ModelInterceptor.php 2009-04-07 22:06:43 +0000
@@ -1,12 +1,18 @@
<?php
/**
- * Interceptor which prepares a model with data from the request parameters.
- *
- * The target action must be <code>ModelAware</code> and return the names of the model (with any
- * inner models) we should prepare. After instantiating the specified model we loop through request
- * parameters and populate the model before it is set back into the action.
- *
- * @version $Id: ModelInterceptor.php 1554 2008-09-25 15:35:37Z anders $
+ * Interceptor which prepares a model with data from the request parameters, or extracts a model
+ * which was temporarily stored in the session. The target action must be
+ * <code>ModelAware</code> to subscribe to any of this interceptor's functionality.
+ *
+ * This interceptor works in two "modes": It either extracts a model already present in the
+ * session if the request is a GET request, or it instantiates and populates a model with data
+ * from the request parameters. For the latter to work, the target action must provide the name
+ * of the model (along with any inner models) in a public <code>$model</code> property, or
+ * return a pre-instantiated model from a <code>getModel()</code> method. We then loop through
+ * request parameters and populate the model.
+ *
+ * Regardless of the "mode", the model found/prepared is put into the <code>$model</code>
+ * property of the action.
*/
class ModelInterceptor extends AbstractInterceptor {
private $modelNames = array();
@@ -18,28 +24,54 @@
$action = $dispatcher->getAction();
if ($action instanceof ModelAware) {
- if (method_exists($action, 'getModel')) {
- // The action supplies an already instantiated model
- $model = $action->getModel();
- }
+ // We depend on a $model-property in ModelAware actions
+ if (!property_exists($action, 'model')) {
+ $class = get_class($action);
+ throw new Exception('Action ' . $class
+ . ' is ModelAware and must define a public $model property.');
+ }
+
+ /*
+ * If model is availible from session we use that, but only if this is a GET
+ * request. The reason for this is that any new POST-submit of a form should
+ * take precedence, to stop any invalid model in the session to override the newly
+ * POSTed.
+ */
+ if ($dispatcher->getRequest()->getMethod() == 'GET'
+ && $action instanceof SessionAware && isset($action->session['model'])) {
+ $action->model = $action->session['model'];
+ // Model has been extracted, remove it from session
+ $action->session->remove('model');
+ }
+ // Otherwise prepare a model from request parameters
else {
- // The action supplies model class name(s), so we must instantiate
- $model = $this->instantiateModel($action->model);
- }
-
- foreach ($dispatcher->getRequest() as $param => $value) {
- if (strpos($param, '::') !== false) {
- // Parameter is a property path to inner models. Explode the path and populate.
- $exploded = explode('::', $param);
- $this->populate($model, $exploded, $value);
+ if (method_exists($action, 'getModel')) {
+ // The action supplies an already instantiated model
+ $model = $action->getModel();
}
else {
- $model->$param = $this->convertType($value);
+ // The action supplies model class name(s), so we must instantiate
+ $model = $this->instantiateModel($action->model);
+ }
+
+ if ($model !== null) {
+ foreach ($dispatcher->getRequest() as $param => $value) {
+ if (strpos($param, '::') !== false) {
+ // Parameter is a property path to inner models. Explode the path and populate.
+ $exploded = explode('::', $param);
+ $this->populate($model, $exploded, $value);
+ }
+ else {
+ if (property_exists($model, $param) || $param == 'original') {
+ $model->$param = $this->convertType($value);
+ }
+ }
+ }
+
+ // Prepare a ModelProxy for the model
+ $action->model = Models::getModel($model);
}
}
-
- // Prepare a ModelProxy for the model
- $action->model = Models::getModel($model);
}
return $dispatcher->invoke();
@@ -93,6 +125,8 @@
* of the form <code>outerProperty::innerProperty</code> in which case <code>outerProperty</code> in
* the model must be another model with the an <code>innerProperty</code> property to be populated
* with the value.
+ *
+ * TODO: This must be better documented and possibly add property_exists()-checks
*
* @param object $model Model object to populate.
* @param string $property Property to populate.
=== renamed file 'src/interceptors/ValidatorInterceptor.php' => 'src/interceptors/ValidationInterceptor.php'
--- src/interceptors/ValidatorInterceptor.php 2008-10-20 16:41:10 +0000
+++ src/interceptors/ValidationInterceptor.php 2009-04-08 15:34:28 +0000
@@ -2,66 +2,53 @@
/**
* Interceptor handling model validation and its corresponding error messages.
*
- * The target action must be <code>ValidationAware</code> and return a fully populated model which
- * we are to validate. This model is usually populated by a <code>ModelInterceptor</code>. If any
- * validation errors occures, error messages are put into the action so the view can display them.
- *
- * @version $Id: ValidatorInterceptor.php 1526 2008-07-14 16:07:05Z anders $
+ * The target action must be <code>ValidationAware</code> and have a fully populated model
+ * which we are to validate in a public <code>$model</code> property. This model is usually
+ * populated by a <code>ModelInterceptor</code>. If validation fails, the
+ * <code>validationFailed()</code> method on the action is called for the action to determine
+ * the result and set any custom error message.
*/
-class ValidatorInterceptor extends AbstractInterceptor {
+class ValidationInterceptor extends AbstractInterceptor {
/**
* Invokes and processes the interceptor.
*/
public function intercept ($dispatcher) {
$action = $dispatcher->getAction();
+ $valid = true;
- if ($action instanceof ValidationAware && $dispatcher->getRequest()->getMethod() == 'POST'
- && isset($action->model) && is_object($action->model)) {
- /*
- * Action is ValidationAware, request is POSTed and a model is prepared. Create a validator,
- * do the validation and put errors into the action.
- */
- $conf = Config::getValidationConfig();
- $validator = new Validator($conf['classes'], $conf['messages']);
- $action->errors = $validator->validate($action->model);
- }
-
- $result = $dispatcher->invoke();
-
- if ($action instanceof ValidationAware && $action instanceof MessageAware) {
- // Report errors if action has any errors registered
- if (!empty($action->errors)) {
- $action->msg->setMessage('Submitted form contains errors. Please correct any errors listed
- in the form and try again.', false);
+ // Validate model if action wants validation and a validateable model is prepared
+ if ($action instanceof ValidationAware
+ && $action->model instanceof ValidateableModelProxy) {
+ if (!$action->model->validate()) {
+ $valid = false;
+ // Retrieve the result we want to return
+ $result = $action->validationFailed();
+ }
+ }
+
+ if ($valid) {
+ $result = $dispatcher->invoke();
+ }
+ else {
+ /*
+ * If validationFailed() didn't set a specific message, we give a general
+ * error message.
+ */
+ if ($action instanceof MessageAware && $action->msg->isEmpty()) {
+ $action->msg->setMessage('Submitted form contains errors. Please correct
+ any errors listed in the form and try again.', false);
+ }
+
+ /*
+ * If the result is a redirect and sessions are enabled, store model for
+ * retrieval after redirect.
+ */
+ if ($result instanceof RedirectResult && $action instanceof SessionAware) {
+ $action->session['model'] = $action->model;
}
}
return $result;
-// TODO: Do we want to fix this? Errors in session is pretty useless as is below, as the invalid data
-// isn't present after a redirect. If we do want this, submitted model data should probably be stored.
-// $this->checkErrorsInSession($action);
-// return $result;
}
-
- /**
- * Checks to see if the session has error messages stored while the action do not. If so, the
- * errors are injected into the action and removed from the session.
- */
-// private function checkErrorsInSession ($dispatcher) {
-// $session = $dispatcher->getRequest()->getSession();
-//
-// if ($session !== null) {
-// $action = $dispatcher->getAction();
-//
-// if ($action instanceof ValidationAware && empty($action->errors)) {
-// $errorsInSession = $session->get('errors');
-//
-// if (!empty($errorsInSession)) {
-// $action->errors = $errorsInSession;
-// $session->remove('errors');
-// }
-// }
-// }
-// }
}
?>
=== modified file 'src/models/ModelProxy.php'
--- src/models/ModelProxy.php 2009-02-22 03:44:58 +0000
+++ src/models/ModelProxy.php 2009-04-04 18:03:16 +0000
@@ -42,6 +42,12 @@
* @var object
*/
protected $current;
+
+ /**
+ * Flag to indicate whether we have proxified inner models.
+ * @var bool
+ */
+ protected $isInnerProxied;
/**
* Creates a <code>ModelProxy</code> instance for the model supplied.
@@ -58,65 +64,45 @@
$model = current($this->models);
}
+ $this->isInnerProxied = false;
$this->initDaoProxy($model);
}
-
+
/**
* Iterates the contained models and updates dirty models or inserts new models. The number of
* actually saved rows in the database is returned.
*
- * @return int Number of saved rows in the database.
+ * @return mixed Number of saved rows in the database, or <code>false</code> if a
+ * preSave() method on a model returned false.
*/
public function save () {
+ if (!isset($this->objects)) {
+ $type = get_class($this->current);
+ throw new Exception("Model $type is not DataProvided and cannot be saved.");
+ }
+
+ $this->proxifyInnerModels();
$numAffected = 0;
-
- if (isset($this->objects)) {
- foreach ($this->models as $idx => $model) {
- $proceed = true;
-
- // If model has preSave()-method, call it to determine if we should continue
- // XXX: Should this be here, or is it more appropriate in DataAccessProxy?
- if (method_exists($model, 'preSave')) {
- $proceed = $model->preSave();
- }
-
- if ($proceed !== false) {
- if (!empty($model->original)) {
- if (property_exists($model, 'isDirty') && $model->isDirty) {
- $numAffected += $this->objects->update($model);
- }
- }
- else {
- $numAffected += $this->objects->insert($model);
- }
- }
-
- // Loop through model properties and save any inner models
- foreach ($model as $property => &$innerModel) {
- /*
- * If $innerModel is an array or object, we try to proxy it. If it succeeds, it is
- * indeed one or more models we might want to save below, so we put the proxy back
- * into the model object.
- */
- if (is_array($innerModel) || is_object($innerModel)) {
- $proxy = Models::getModel($innerModel);
- if ($proxy !== null) {
- $innerModel = $proxy;
- }
- }
-
- if ($innerModel instanceof ModelProxy) {
- $this->propagateKey($innerModel, $model);
- // XXX: We do nothing with the number of affected rows for inner saves. Should we?
- $innerModel->save();
- }
- }
-
- // The model might have been updated or inserted, so set as not-dirty regardless
- $model->isDirty = false;
+
+ foreach ($this->models as $model) {
+ // Checks if this model is approved for processing to continue
+ if (!$this->preSaveModel($model)) {
+ // XXX: Should we return false, or make more noise with an exception?
+ return false;
+ }
+
+ // Process this model (save and/or validate)
+ $numAffected += $this->saveModel($model);
+
+ foreach ($model as $property) {
+ if ($property instanceof ModelProxy) {
+ // Propagate primary key into inner model and recurse save
+ $this->propagateKey($innerModel, $model);
+ $property->save();
+ }
}
}
-
+
return $numAffected;
}
@@ -152,7 +138,7 @@
return $numAffected;
}
-
+
/**
* Checks if the specified property on the current model is empty.
*
@@ -198,7 +184,7 @@
if (property_exists($model, $name)) {
if ($model->$name !== $value) {
$model->$name = $value;
- $model->isDirty = true;
+ $this->modelChanged($model);
}
}
}
@@ -214,7 +200,7 @@
*/
public function __call ($name, $args) {
if (!empty($args)) {
- $this->current->isDirty = true;
+ $this->modelChanged($this->current);
}
$reflection = new ReflectionMethod(get_class($this->current), $name);
@@ -252,7 +238,7 @@
if (is_object($value)) {
if ($offset !== null) {
$this->models[$offset] = $value;
- $this->models[$offset]->isDirty = true;
+ $this->modelChanged($this->models[$offset]);
}
else {
$this->models[] = $value;
@@ -341,7 +327,57 @@
}
return $this->models;
}
-
+
+ /**
+ * Flag the model as dirty, as changes have been made to its state.
+ *
+ * @param object $model The model whose state has changed.
+ */
+ protected function modelChanged ($model) {
+ $model->isDirty = true;
+ }
+
+ /**
+ * Calls any existing <code>preSave()</code> method on the supplied model before
+ * <code>saveModel()</code> is invoked. This makes it possible for the model itself to hook
+ * into the save process.
+ *
+ * @param object $model The model to invoke any preSave() on.
+ * @return bool <code>true</code> if we should call saveModel() next,
+ * <code>false</code> if we should stop the saving.
+ */
+ protected function preSaveModel ($model) {
+ // If model has preSave()-method, call it to determine if it approves saving
+ if (method_exists($model, 'preSave')) {
+ return $model->preSave();
+ }
+ return true;
+ }
+
+ /**
+ * Saves the supplied model by calling the <code>update()</code> DAO method if the model isn't
+ * new and changes have been made on its data, or the <code>insert()</code> DAO method if it's
+ * new.
+ *
+ * @param object $model The model to save.
+ * @return int Number of rows affected in the database.
+ */
+ protected function saveModel ($model) {
+ $numAffected = 0;
+
+ if (!empty($model->original)) {
+ if (property_exists($model, 'isDirty') && $model->isDirty) {
+ $numAffected = $this->objects->update($model);
+ }
+ }
+ else {
+ $numAffected = $this->objects->insert($model);
+ }
+
+ $model->isDirty = false;
+ return $numAffected;
+ }
+
/**
* Initializes a proxy to the data access object of the model, if it is <code>DataProvided</code>.
*
@@ -352,21 +388,51 @@
$this->objects = new DataAccessProxy($this, get_class($model));
}
}
-
- /**
- * Checks every model object in a ModelProxy for the existance of a foreign key to the supplied model
- * and updates it's value if it's empty.
+
+ /**
+ * Iterates over the contained models and their properties, and proxifies any inner models.
+ * This makes it possible for us to automatically validate and save them along with the
+ * outermost models.
+ */
+ protected function proxifyInnerModels () {
+ if ($this->isInnerProxied) {
+ return;
+ }
+
+ foreach ($this->models as $model) {
+ foreach ($model as &$innerModel) {
+ /*
+ * If $innerModel is an array or object, we try to proxy it. If it succeeds, it is
+ * indeed one or more models, so set the created proxy back into the model object.
+ */
+ if (is_array($innerModel) || is_object($innerModel)) {
+ $proxy = Models::getModel($innerModel);
+ if ($proxy !== null) {
+ $innerModel = $proxy;
+ // Recurse to proxify even more deeply nested models
+ $innerModel->proxifyInnerModels();
+ }
+ }
+ }
+ }
+
+ $this->isInnerProxied = true;
+ }
+
+ /**
+ * Checks every model object in a ModelProxy for the existance of a foreign key to the supplied
+ * model and updates it's value if it's empty.
*
* @param ModelProxy $proxy The ModelProxy with model objects to update.
- * @param object $model The model whose primary key defines the foreign key to look for.
+ * @param object $model The model whose primary key defines the foreign key to update.
*/
private function propagateKey ($proxy, $model) {
$reflection = new ReflectionObject($model);
$pk = $reflection->getConstant('PK');
foreach ($proxy as $innerModel) {
+ // Update the foreign key to main model if it exists and is empty
if (property_exists($innerModel, $pk) && empty($innerModel->$pk)) {
- // Inner model has an empty foreign key to the main model, initialize before saving
$innerModel->$pk = $model->$pk;
}
}
=== modified file 'src/models/ValidateableModelProxy.php'
--- src/models/ValidateableModelProxy.php 2008-10-20 16:41:10 +0000
+++ src/models/ValidateableModelProxy.php 2009-04-04 18:03:16 +0000
@@ -1,28 +1,108 @@
<?php
/**
- * This class is a validateable model proxy. This model proxy is used for <code>Validateable</code> models in order to
- * correctly expose their functionality.
- *
- * @version $Id: ValidateableModelProxy.php 1542 2008-08-12 18:46:42Z anders $
+ * This class is a validateable model proxy. This model proxy is used for <code>Validateable</code>
+ * models in order to add support for validation.
*/
-class ValidateableModelProxy extends ModelProxy implements Validateable {
- /**
- * Creates a <code>ValidateableModelProxy</code> instance for the model supplied. It is assumed that
- * the model has been verified <code>Validateable</code>.
+class ValidateableModelProxy extends ModelProxy {
+
+ /**
+ * @var Validator
+ */
+ private $validator;
+
+ /**
+ * Creates a <code>ValidateableModelProxy</code> instance for the model supplied. It is assumed
+ * that the model has been verified <code>Validateable</code>.
*
* @param object $model Model to proxy.
*/
- public function __construct ($model/*, $dirty*/) {
- parent::__construct($model/*, $dirty*/);
- }
-
- /**
- * Calls <code>rules()</code> on the current model and returns its result.
+ public function __construct ($model) {
+ parent::__construct($model);
+ }
+
+ /**
+ * Overrides ModelProxy::save() by making sure contained models are valid before they
+ * are saved. Contained models that have already been validated are not validated again.
+ *
+ * @return mixed Number of saved rows in the database, or <code>false</code> if a
+ * a model contains invalid data or a preSave() method on a model returned
+ * false.
+ *
+ */
+ public function save () {
+ if (!$this->validate()) {
+ return false;
+ }
+ return parent::save();
+ }
+
+ /**
+ * Validates the contained models and returns <code>true</code> if all are valid or
+ * <code>false</code> if one or more are invalid.
+ *
+ * @return bool <code>true</code> if all models are valid, <code>false</code> if not.
+ */
+ public function validate () {
+ $this->proxifyInnerModels();
+ $this->initValidator();
+ $isValid = true; // We start out with valid state
+
+ foreach ($this->models as $model) {
+ // And set invalid for invalid objects, but never again valid
+ $isValid = ($this->validateModel($model) ? $isValid : false);
+ foreach ($model as $property) {
+ if ($property instanceof ValidateableModelProxy) {
+ // Recurse to validate inner models
+ $property->validate();
+ }
+ }
+ }
+
+ return $isValid;
+ }
+
+ /**
+ * Alias of validate() to accommodate for more readable code (i.e. for tests).
+ *
+ * @return bool
+ */
+ public function isValid () {
+ return $this->validate();
+ }
+
+ /**
+ * Validates the supplied model. If the model has already been validated (and is unchanged
+ * since) its previous result is returned, else the Validator is invoked to validate the model.
+ *
+ * @param object $model The model to validate.
+ * @return bool
+ */
+ protected function validateModel ($model) {
+ if (property_exists($model, 'isValid')) {
+ return $model->isValid;
+ }
+ return $this->validator->validate($model);
+ }
+
+ /**
+ * Remove validated flag, as changes have been made to its state and it's unknown whether it
+ * is valid or not.
*
- * @return array Validation rules for the current model.
- */
- public function rules () {
- return $this->current->rules();
+ * @param object $model The model whose state has changed.
+ */
+ protected function modelChanged ($model) {
+ parent::modelChanged($model);
+ unset($model->isValid);
+ }
+
+ /**
+ * Initialized the validator if not already initialized.
+ */
+ private function initValidator () {
+ if (!isset($this->validator)) {
+ $conf = Config::getValidationConfig();
+ $this->validator = new Validator($conf['classes'], $conf['messages']);
+ }
}
}
?>
=== modified file 'src/results/RedirectResult.php'
--- src/results/RedirectResult.php 2008-12-17 15:50:47 +0000
+++ src/results/RedirectResult.php 2009-04-07 23:17:37 +0000
@@ -1,44 +1,29 @@
<?php
/**
- * Provides the implementation of a result set which when rendered sends a redirect to the client.
+ * Provides the implementation of a result set which when rendered sends a redirect to the
+ * client. It defaults to a 303 (See Other) status code, but this can be overridden.
*/
class RedirectResult extends AbstractResult {
private $location;
+ private $code;
/**
* Constructor.
*
* @param string $location Location of the redirect relative to the web root.
+ * @param int $code HTTP status code to use. Defaults to 303.
*/
- public function __construct ($action, $location) {
+ public function __construct ($action, $location, $code = 303) {
parent::__construct($action);
$this->location = Config::get('webRoot') . $location;
+ $this->code = $code;
}
/**
* Sends the redirect to the client.
*/
public function render ($request) {
- $action = $this->getAction();
-
- /*
- * If a session is active and the action has a message, store them temporarily in the
- * session through the redirect.
- */
- if ($action instanceof SessionAware) {
-
- if ($action instanceof MessageAware && !$action->msg->isEmpty()) {
- $action->session['message'] = $action->msg;
- }
- // See ValidationInterceptor for reason this is commented away
- //if ($action instanceof ValidationAware && !empty($action->errors)) {
- // $session->put('errors', $action->errors);
- //}
-
- $action->session->write();
- }
-
- header("Location: $this->location");
+ header("Location: $this->location", true, $this->code);
exit;
}
}
=== modified file 'src/validation/Validator.php'
--- src/validation/Validator.php 2008-10-20 16:41:10 +0000
+++ src/validation/Validator.php 2009-04-03 22:25:13 +0000
@@ -34,12 +34,15 @@
}
/**
- * Validates a model and returns a two-dimensional array with the specifics of any failures.
- * The array keys refer to the property of the model whose validation failed, while the value is
- * an array containing human-readable messages of the specifics.
+ * Validates a model and returns <code>true</code> if the model validates, <code>false</code>
+ * if not.
+ *
+ * Any validation errors are put into a two-dimensional array with the specifics and set in
+ * $errors on the model. The array keys refer to the property of the model whose validation
+ * failed, while the value is an array containing human-readable messages of the specifics.
*
- * @param object $model The model to validate.
- * @return array Specifying any validation errors, or empty if none.
+ * @param object $model The model to validate.
+ * @return bool Indicating success or failure.
*/
public function validate ($model) {
$ruleSet = $model->rules();
@@ -74,8 +77,14 @@
}
}
}
-
- return $errors;
+
+ if (!empty($errors)) {
+ $model->errors = $errors;
+ $model->isValid = false;
+ }
+ else $model->isValid = true;
+
+ return $model->isValid;
}
/**
=== modified file 'src/views/snippets/kolibri.xsl'
--- src/views/snippets/kolibri.xsl 2009-04-07 21:01:00 +0000
+++ src/views/snippets/kolibri.xsl 2009-04-07 22:09:03 +0000
@@ -15,9 +15,8 @@
<xsl:include href="message.xsl" />
<xsl:include href="forms.xsl" />
- <!-- Makes the XML structures for model and errors available for the custom element templates -->
+ <!-- Makes the XML structures for model available for the custom element templates -->
<xsl:variable name="model" select="/result/model" />
- <xsl:variable name="errors" select="/result/errors" />
<func:function name="k:number">
<xsl:param name="value" />
@@ -174,7 +173,7 @@
<xsl:value-of disable-output-escaping="yes" select="$errors/*[name() = $attribute]" />
</xsl:variable-->
- <func:result select="$errors/*[name() = $attribute]" />
+ <func:result select="$model/errors/*[name() = $attribute]" />
</func:function>
<!--
@@ -194,7 +193,7 @@
</xsl:choose>
</xsl:param>
- <func:result select="boolean($errors/*[name() = $attribute])" />
+ <func:result select="boolean($model/errors/*[name() = $attribute])" />
</func:function>
<!--
Follow ups