105 function &createObject($oid=null)
107 return new Article($oid);
112 function getTableName()
119 function getPkNames()
121 return array('id' => DATATYPE_IGNORE);
126 function getMyFKColumnNameImpl($parentType)
128 // start from the most specific
129 if ($this->getType() == 'Article' && $parentType == 'Author') return 'fk_author_id';
130 if ($parentType == 'Author') return 'fk_author_id';
136 function getOrderBy()
143 function getObjectDefinitionImpl()
146 $nodeDef['_properties'] = array
148 array('name' => 'is_searchable', 'value' => false),
150 $nodeDef['_datadef'] = array
155 array('name' => 'id', 'app_data_type' => DATATYPE_IGNORE, 'column_name' => 'id',
156 'db_data_type' => 'INT(11) NOT NULL', 'default' => '', 'restrictions_match' => '', 'restrictions_not_match' => '',
157 'restrictions_description' => '', 'is_editable' => false, 'input_type' => 'text', 'display_type' => 'text'),
161 array('name' => 'fk_author_id', 'app_data_type' => DATATYPE_IGNORE, 'column_name' => 'fk_author_id',
162 'db_data_type' => 'INT(11)', 'default' => '', 'restrictions_match' => '', 'restrictions_not_match' => '',
163 'restrictions_description' => '', 'is_editable' => false, 'input_type' => 'text', 'display_type' => 'text'),
167 array('name' => 'headline', 'app_data_type' => DATATYPE_ATTRIBUTE, 'column_name' => 'headline',
168 'db_data_type' => 'VARCHAR(255)', 'default' => '', 'restrictions_match' => '', 'restrictions_not_match' => '',
169 'restrictions_description' => '', 'is_editable' => false, 'input_type' => 'text', 'display_type' => 'text'),
173 $nodeDef['_ref'] = array
178 array('name' => 'author_name', 'ref_type' => 'Author', 'ref_value' => 'name', 'ref_table' => 'Author',
179 'id_column' => 'id', 'fk_columns' => 'fk_article_id', 'ref_column' => 'name')
181 $nodeDef['_parents'] = array
183 array('type' => 'Author', 'is_navigable' => false, 'table_name' => 'Author', 'pk_columns' => array('id'),
184 'fk_columns' => 'fk_author_id')
186 $nodeDef['_children'] = array
188 array('type' => 'Image', 'minOccurs' => 0, 'maxOccurs' => 'unbounded', 'aggregation' => false, 'composition' => true,
189 'is_navigable' => false, 'table_name' => 'Image', 'pk_columns' => array('id'), 'fk_columns' => 'fk_article_id',
190 'order_by' => array())
196 * The method NodeUnifiedRDBMapper::getObjectDefinitionImpl is the most important method. It supplies an
197 * associative array, in which the attributes of the domain class (@em _datadef) the parent and the child
198 * domain classes (@em _children) are defined. In addition the Article contains a reference to
199 * the name of the Author (@em _ref), which allows to access the Author without loading it.@n
200 * Detailed information on the implemetation can be found under NodeUnifiedRDBMapper::getObjectDefinitionImpl.
202 * For configuring the domain classes in the configuration file see @ref sectypemapping.
204 * @subsubsection howtodomainclass Creating the domain classes
206 * The framework's PersistenceMapper work with the class Node, a subclass of PersistentObject.
207 * Different domain classes are created by specifying the type attribute (Node::getType) and
208 * a set of attributes (Node::getValueNames).
209 * So the class Node is a generic data container, into which data can be put by the method
210 * Node::setValue and retrieved by the method Node::getValue. For most applications this will
213 * If however more specialized domain classes are required for the application, they must
214 * be created by the PersistenceMapper. In this case subclasses of NodeRDBMapper must
215 * override the method NodeRDBMapper::createObject, which in turn creates instances of the
216 * special domain class.
218 * @subsection howtonode Working with the class Node
220 * @subsubsection howtoload Loading a data structure
223 $persistenceFacade = &PersistenceFacade::getInstance();
226 $oid = PersistenceFacade::composeOID(array('type' => 'section', 'id' => array('1')));
227 $node = &$persistenceFacade->load($oid, BUILDDEPTH_INFINITE);
229 Message::error("A Node with object id ".$oid." does not exist.";
232 * The example loads the section node with id 1 and all children nodes. If the oid of the
233 * requested node is not known either use the appropriate methods of PersistenceFacade
234 * (e.g. PersistenceFacade::getOID) or in more complex cases do an ObjectQuery.
236 * @subsubsection howtoiter Traversing a data structure
239 $iterator = new NodeIterator($node);
240 while(!($iterator->isEnd()))
242 $currentObject = &$iterator->getCurrentObject();
243 Message::trace($currentObject->getOID());
244 $iterator->proceed();
248 * The example outputs the object ids of all nodes that are descendents of $node.
250 * @subsubsection howtooutput Output a data structure into different formats
253 $iterator = new NodeIterator($node);
255 // Output into dot format
256 $filename = "graph.dot";
257 $dotOS = new DotOutputStrategy($filename);
258 $ov = new OutputVisitor($dotOS);
259 $ov->startIterator($iterator);
261 iterator->reset($node);
263 // Output into XML format
264 $filename = "graph.xml";
265 $xmlOS = new XMLOutputStrategy($filename);
266 $ov->setOutputStrategy($xmlOS);
267 $ov->startIterator($iterator);
270 * @subsubsection howtomodify Modifying a data structure
273 $node->setValue('text', 'hello world!', DATATYPE_ATTRIBUTE);
276 // If one node has been modified
279 // If several connected nodes have been modified
280 $iterator = new NodeIterator($node);
281 $cv = new CommitVisitor();
282 $cv->startIterator($iterator);
285 * @subsection howtoviews Programming the views
287 * Views are implemented as HTML pages (defined in the view templates), which typically contain a form,
288 * which displays the data to be modified. For programming dynamic parts and to access
289 * application data the <a href="http://smarty.php.net/" target="_blank">Smarty</a> template
290 * language is used.@n
291 * By default the views are stored as .tpl files in the directory /application/inlcude/views
292 * (see @ref secsmarty). In the directory /wcmf/application/views those views are stored, which
293 * the framework uses for its standard application. These are the basis for the programming of
296 * In the view templates all data, which was passed to the view instance is accessible (see
297 * @ref howtocontroller). In the simplest case these can be displayed via @em {$variable}. In
298 * addition object data can be accessed by using @em {$object->getValue(...)}. By setting
299 * @em debugView = 1 (see @ref seccms) in the configuration file Smarty will display the data,
300 * which is available in the template, in an external window.@n
302 * The data displayed in the view's form is available to the following controller. Some
303 * (hidden) input fields should always exist. They are defined in the file
304 * /wcmf/application/views/formheader.tpl, which - to simplify matters - should be reused.@n
305 * For handling the form data some JavaScript functions are provided (and documented) in
306 * the file /wcmf/blank/script/common.js.
308 * In the directory /wcmf/lib/presentation/smarty_plugins the framework defines extensions
309 * of the Smarty template language:
311 * - Function @em translate for localization of strings@n
312 * e.g.: {translate text="Logged in as %1% since %2%" r0="$login" r1="$logindate"}
313 * - Function @em sessionvalue to get a session variable@n
314 * e.g.: {sessionvalue name="platform"}
315 * - Resource @em lib to integrate templates with a relative path to the framework's
316 * root directory (/wcmf)@n
317 * e.g.: {include file="lib:application/views/formheader.tpl"}@n
320 * @subsection howtocontroller Programming the controllers
322 * Controllers execute the user-defined actions. In order to implement custom controllers
323 * a class must be derived from the baseclass Controller, which implements the methods
324 * Controller::hasView and Controller::executeKernel.
326 * The Request instance passed to the Controller::initialize method provides all data of the
327 * preceeding view's input fields to the controller. The names of the input fields are the names
328 * of the request values. The controller in turn can pass data to the view by setting them on
329 * the Response instance.
331 * The method Controller::hasView returns @em true or @em false, whether a view is displayed
332 * or not (the return value can differ depending on the context or action, for an example see LoginController).@n
333 * The method Controller::executeKernel executes the actual action. In this method application
334 * data is loaded, modified, created, deleted and where required passed to the view for display
335 * or to the next controller to proceed.
336 * The method either returns @em false, which means, that the ActionMapper should call no further
337 * controller or true. In the latter case the ActionMapper determines the next controller
338 * from the context and action values of the response (see @ref actionkey).
339 * This means if a view should be displayed, the method must return @em false.
341 * While programming custom controllers often the methods Controller::initialize and
342 * Controller::validate are overridden in order to carry out initializations or to
343 * validate provided data.
345 * The framework's controllers are located in the directory /wcmf/application/controller.
347 * @subsection howtoapplication Programming the application
349 * A web application typically consists of several input masks (views), which
350 * are used to create, modify and delete data. The application is defined by the actions
351 * executable in the individual input masks. Thereby the framework makes no difference
352 * between actions used for data handling and those used to navigate or e.g. initiate the
355 * The definition of an action requires the following steps:
357 * -# @ref howtoapplication1
358 * -# @ref howtoapplication2
359 * -# @ref howtoapplication3
360 * -# @ref howtoapplication4
361 * -# @ref howtoapplication5
363 * As an example we use the action for displaying an @em article node in order to
364 * edit it. Let's look at the individual steps:
366 * @subsubsection howtoapplication1 Definition of the action name
367 * We name the action @em editArticle. This name need not to be unique in the whole
368 * application. The ActionMapper only requires the name (and the @ref actionkey defined
369 * by the action) to find the next appropriate controller.
371 * @subsubsection howtoapplication2 Creating the button to trigger the action
372 * In order to display the data the application must know which article is selected.
373 * This is exactly defined by it's @ref oid. The data transfer between the input
374 * masks is achieved by the HTTP POST mechanism, i.e. a (hidden) input field must exist,
375 * which contains the oid of the article to be displayed. Since for most applications it's often
376 * necessary to transfer an oid, the framework defines a standard field @em oid in each
377 * view (see file /wcmf/application/views/formheader.tpl), which can easily be set
378 * by the JavaScript function @em doDisplay (/wcmf/blank/script/common.js).
380 * The action is triggered upon submission of the input form. Another JavaScript function
381 * (@em submitAction) simplifies the execution. The form data is passed to the main.php
382 * script, which delegates the further execution to the ActionMapper. The link to execute
383 * the action could look like this:
384 * @verbatim <a href="javascript:setContext('article'); doDisplay('{$article->getOID()}');
385 submitAction('editArticle');">{translate text="edit"}</a> @endverbatim
386 * For details on programming the views see @ref howtoviews.
388 * @subsubsection howtoapplication3 Customizing the configuration file
389 * To determine the controller, which carries out the action, the ActionMapper requires
390 * an appropriate entry in the configuration file (see @ref secactionmapping). If
391 * the controllers name is @em ArticleController, the entry could look like this:
394 ??editArticle = ArticleController @endverbatim
395 * Don't forget to introduce the ArticleController in the configuration section @ref secclassmapping.
397 * Additionally the ArticleController should display a view for editing the article. If we
398 * name this view @em article.tpl, the configuration entry would look like the following (see
402 ArticleController?? = article.tpl @endverbatim
404 * @subsubsection howtoapplication4 Implementing the action as a controller
405 * The action is executed in the controller - in this example in the @em ArticleController class.
406 * Since the controller should display a view with the article's data, we first must specify that
407 * the controller has a view and second the data of the article must be passed to the view.@n
408 * At first however it must be assured, that the controller receives an oid. This happens in
409 * the method Controller::validate, which searches for the entry in the passed data:
413 if ($this->_request->getValue('oid') == '')
415 $this->setErrorMsg("No 'oid' given in data.");
421 * We declare the existence of a view in the method Controller::hasView:
428 * Finally the action is executed in the method @em Controller::executeKernel. Here the
429 * controller loads the data and provides it to the view for display by setting it on the
432 function executeKernel()
434 $persistenceFacade = &PersistenceFacade::getInstance();
437 $article = &$persistenceFacade->load($this->_request->getValue('oid'), BUILDDEPTH_INFINITE);
439 // assign model to view
440 $this->_response->setValue('article', $article);
442 // stop processing chain
446 * It's important that the method returns false, since this causes the ActionMapper
447 * to end the execution and wait for user input. The display of the view is done by
450 * @subsubsection howtoapplication5 Displaying data in the view
451 * After the controller has provided the view with the data, the view can display the data.
452 * In our case after the ArticleController has been executed a variable @em article is
453 * known to the view, which matches the article node.@n
454 * The programming of the views is done in HTML together with the Smarty template language.
455 * The file @em article.tpl could contain the following line:
456 * @verbatim article name: {$nodeUtil->getInputControl($article, "name")} @endverbatim
458 * In the curly brackets you can find Smarty code, which calls the method NodeUtil::getInputControl.
459 * This method displays the input control (in our case a textfield), which corresponds to the
460 * article's @em name attribute, in the HTML page. In the same manner the other attributes can be handled.
462 * @subsection howtorights Rights management
464 * Rights can be assigned to the execution of actions in the user interface or to the editing
465 * of domain classes and individual objects (instances of domain classes). For editing content the framework
466 * defines the rights @em read, @em modify, @em delete and @em create.@n
467 * The authorization for actions in the user interface is handled by the ActionMapper, for actions
468 * concerning the data the PersistenceMapper checks the permissions (and sets objects, for which
469 * the right @em modify is not set, to non-editable (@em is_editable = false) ).
470 * Both classes use the method RightsManager::authorize and generate a fatal error message,
471 * if authorization fails (see @ref howtoerror).
472 * To prevent this the rights can be retrieved directly in order to take appropriate messures:
474 $rightsManager = &RightsManager::getInstance();
475 if ($rightsManager->authorize($this->_request->getValue('oid'), '', ACTION_READ))
477 $object = &$persistenceFacade->load($this->_request->getValue('oid'), BUILDDEPTH_INFINITE);
481 // do something else if the user cannot read the object
484 * In the example the object is only loaded, if it's permitted. For the definition of rights
485 * see @ref secauthorization.
486 * @note If @em anonymous is set to one in the configuration file, the rights management
487 * is disabled (see @ref seccms).
489 * @subsection howtoconcurrency Concurrency
491 * If more than one user works with the application at the same time, conflicts can occur, if two
492 * users want to edit the same object concurrently. In this case the first user can request
493 * a lock on that object, which only allows reading access to succeeding users. This lock will
494 * be transfered to all instances loaded in the future as long until the user unlocks the object,
495 * which happens automatically upon execution of an action (call to main.php).
496 * In the sourcecode this looks as follows:
498 $lockManager = &LockManager::getInstance();
499 $object = &$persistenceFacade->load($this->_request->getValue('oid'), BUILDDEPTH_INFINITE);
501 // if the object is locked by another user we retrieve the lock to show a message
502 $lock = $object->getLock();
505 $lockMsg .= $lockManager->getLockMessage($lock, $recipe->getName());
509 // try to lock object
510 $lockManager->aquireLock($this->_request->getValue('oid'));
513 * The functionality described above is imlemented in the method LockManager::handleLocking,
514 * with the only difference that this method only acquires a lock in cases in which the
515 * user is allowed to modify the object.@n
516 * An object, which is loaded by another user is set to non-editable (@em is_editable = false)
517 * automatically upon loading.
519 * @subsection howtoi18n Multilanguage Support
521 * For localization of the application the method Message::get is provided. For a given (english)
522 * string this method searches for the required language's version. The language version can
523 * either be passed directly with the method call or can be set application wide (see documentation
524 * of Message::get).@n
525 * How the translation is retrieved depends on the parameter @em usegettext (see @ref seccms).
526 * If it is set to 1, the method uses the PHP function @em gettext. This in turn makes use of
527 * *.mo files in the directory /localeDir/language/LC_MESSAGES - e.g. /locale/de_DE/LC_MESSAGES/main.mo
528 * (see documentation of gettext).@n
529 * If gettext doesn't exist, the language version can also be taken from an associative array named
530 * messages_language (e.g. messages_de_DE).
531 * This must be defined in a file /localeDir/language/LC_MESSAGES/messages_language.php (e.g.
532 * /locale/de_DE/LC_MESSAGES/messages_de_DE.php). The keys of the array are the strings, which are
533 * passed to the method Message::get, the values are the corresponding translations.@n
534 * @note If a translation for a text doesn't exist, the string, which was passed to the method
535 * Message::get, will be used.
537 * For making the localization more comfortable some tools are provided in the directory /wcmf/tools/i18n:
538 * - @em locale.php extracts all strings to be localized (where the method Message::get is used) from the
539 * application (views, controllers) and creates a *.po file for use with gettext.
540 * - @em po2array.php generates the array definition described above from a *.po file.
542 * For localizing the view templates a Smarty plugin is provided, which is to be used as follows:
543 * @verbatim {translate text="Logged in as %1%" r0=$authUser->getLogin()} @endverbatim
545 * The @em parameters of the method Message::get are defined by the values of @em r0, @em r1, ...
547 * @subsection howtologdebug Debugging/Logging
549 * For debugging und logging output the <a href="http://logging.apache.org/log4php/" target="_blank">log4php</a>
550 * framework is used. For convenient usage wCMF defines a thin wrapper class called Log. To log a debug message
551 * in the category of the current class, just call:
553 * @verbatim Log::debug($message, __CLASS__); @endverbatim
555 * @subsection howtoerror Error handling
557 * In the application two types of errors are distinguished:
558 * - @em Fatal: Errors, which are so critical, that it's not possible to proceed with the current action
559 * (e.g. the missing of a controller).
560 * - @em Non-fatal: Errors, which merely demand a notification to the user (e.g. invalid input)
562 * These errors can be produced in the following ways:
564 * - @em Fatal: The use of WCMFException::throwEx calls a global error handling routine:
565 * @code onError($message, $file='', $line='') @endcode
566 * - @em Non-fatal: adding a message by calling Controller::appendErrorMsg makes this message - together with
567 * all accumulated messages before and those to follow - available in the next view's @em $errorMsg variable.@n
568 * In order to delete old messages the method Controller::setErrorMsg class must be called with an empty string
571 * Many classes define a method @em getErrorMsg, which provides more detailed information on the cause of an error.
573 * Back to the @ref intro | Previous section @ref dbschema | Next section @ref howtostart