Source for file DivisionEditor.class.php

Documentation is available at DivisionEditor.class.php

  1.  
  2. /**
  3.  * Содержит класс DivisionEditor
  4.  *
  5.  * @package energine
  6.  * @author dr.Pavka
  7.  * @copyright ColoCall 2006
  8.  * @version $Id: fsource_energine__modulessharecomponentsDivisionEditor.class.php.html,v 1.1 2007/09/17 14:32:31 pavka Exp $
  9.  */
  10.  
  11. require_once('core/modules/share/components/Grid.class.php');
  12. require_once('core/framework/TagManager.class.php');
  13.  
  14. /**
  15.  * Редактор разделов
  16.  *
  17.  * @package energine
  18.  * @subpackage share
  19.  * @final
  20.  */
  21. final class DivisionEditor extends Grid {
  22.     /**
  23.      * Редактор шаблонов
  24.      *
  25.      * @var TemplateEditor 
  26.      * @access private
  27.      */
  28.     private $templateEditor;
  29.  
  30.     /**
  31.      * Конструктор класса
  32.      *
  33.      * @return void 
  34.      */
  35.     public function __construct($name$moduleDocument $document,  array $params null{
  36.         parent::__construct($name$module$document,  $params);
  37.         $this->setTableName('share_Sitemap');
  38.         $this->setTitle($this->translate('TXT_DIVISION_EDITOR'));
  39.         $this->setOrder(array('smap_order_num'=>QAL::ASC));
  40.  
  41.         $this->setParam('recordsPerPage'false);
  42.  
  43.         //$this->setFilter(array('smap_pid'=>QAL::EMPTY_STRING));
  44.     }
  45.  
  46.     /**
  47.      * Метод выводит форму назначения прав
  48.      *
  49.      * @return void 
  50.      * @access protected
  51.      */
  52.  
  53.     protected function setPageRights({
  54.         $this->setType(self::COMPONENT_TYPE_FORM);
  55.         //$this->addCrumb('TXT_SET_RIGHTS');
  56.         $this->setDataSetAction('save-rights');
  57.         $this->prepare();
  58.     }
  59.  
  60.     /**
  61.      * Строит вкладку прав
  62.      *
  63.      * @return DOMNode 
  64.      * @access private
  65.      */
  66.  
  67.     private function buildRightsTab({
  68.         $builder new SimpleBuilder();
  69.  
  70.         $id $this->getFilter();
  71.         $id (!empty($id))?current($id):false;
  72.  
  73.         //получаем информацию о всех группах имеющихся в системе
  74.         $groups $this->dbh->select('user_Groups'array('group_id''group_name''group_default_rights'));
  75.         $groups convertDBResult($groups'group_id');
  76.         //создаем матрицу
  77.         //название группы/перечень прав
  78.         foreach ($groups as $groupID=>$groupInfo{
  79.             $res[array('right_id'=>($this->getAction(== 'add')?$groupInfo['group_default_rights']:0'group_id'=>$groupID);
  80.         }
  81.  
  82.         $resultData new Data();
  83.         $resultData->load($res);
  84.         $builder->setData($resultData);
  85.  
  86.         $rightsField $resultData->getFieldByName('right_id');
  87.         $groupsField $resultData->getFieldByName('group_id');
  88.  
  89.         if ($id{
  90.             //создаем переменную содержащую идентификторы групп в которые входит пользователь
  91.             $data $this->dbh->select('share_AccessLevel'truearray('smap_id'=>$id));
  92.             if(is_array($data)) {
  93.                 $data convertDBResult($data'group_id'true);
  94.  
  95.                 for ($i=0$i<$resultData->getRowCount()$i++{
  96.  
  97.                     //если установлены права для группы  - изменяем в объекте данных
  98.                     if (isset($data[$groupsField->getRowData($i)])) {
  99.                         $rightsField->changeRowData($i$data[$groupsField->getRowData($i)]['right_id']);
  100.                     }
  101.  
  102.                     $groupsField->setRowProperty($i'group_id'$groupsField->getRowData($i));
  103.                 }
  104.             }
  105.         }
  106.  
  107.         for ($i=0$i<$resultData->getRowCount()$i++{
  108.             $groupsField->setRowProperty($i'group_id'$groupsField->getRowData($i));
  109.             $groupsField->changeRowData($i$groups[$groupsField->getRowData($i)]['group_name']);
  110.         }
  111.  
  112.         $resultDD new DataDescription();
  113.         $fd new FieldDescription('group_id');
  114.         $fd->setSystemType(FieldDescription::FIELD_TYPE_STRING);
  115.         $fd->setMode(FieldDescription::FIELD_MODE_READ);
  116.         $fd->setLength(30);
  117.         $resultDD->addFieldDescription($fd);
  118.  
  119.         $fd new FieldDescription('right_id');
  120.         $fd->setSystemType(FieldDescription::FIELD_TYPE_SELECT);
  121.         $data $this->dbh->select('user_GroupRights'array('right_id''right_const as right_name'));
  122.         $data = array_map(create_function('$a''$a["right_name"] = DBWorker::_translate("TXT_".$a["right_name"]); return $a;')$data);
  123.         $data[array('right_id'=>0'right_name'=>$this->translate('TXT_NO_RIGHTS'));
  124.  
  125.         $fd->loadAvailableValues($data'right_id''right_name');
  126.         $resultDD->addFieldDescription($fd);
  127.  
  128.         $builder->setDataDescription($resultDD);
  129.         $builder->build();
  130.         $result $this->doc->createElement('rights');
  131.         $result->setAttribute('title'$this->translate('TXT_RIGHTS'));
  132.  
  133.         $result->appendChild($this->doc->importNode($builder->getResult()true));
  134.         return $result;
  135.     }
  136.  
  137.     /**
  138.       * Для setRole создаем свое описание данных
  139.       * Для поля smap_pid формируется Дерево разделов
  140.       *
  141.       * @return DataDescription 
  142.       * @access protected
  143.       */
  144.  
  145.     protected function createDataDescription({
  146.         $result parent::createDataDescription();
  147.  
  148.         //для редактирования и добавления нужно сформировать "красивое дерево разделов"
  149.         if (in_array($this->getAction()array('add''edit'))) {
  150.             $fd $result->getFieldDescriptionByName('smap_pid');
  151.             $fd->setType(FieldDescription::FIELD_TYPE_STRING);
  152.             $fd->setMode(FieldDescription::FIELD_MODE_READ);
  153.         }
  154.         else {
  155.  
  156.             //Для режима списка нам нужно выводить не значение а ключ
  157.             if ($this->getType(== self::COMPONENT_TYPE_LIST{
  158.                 $smapPIDFieldDescription $result->getFieldDescriptionByName('smap_pid');
  159.                 if ($smapPIDFieldDescription{
  160.                     $smapPIDFieldDescription->setType(FieldDescription::FIELD_TYPE_INT);
  161.                 }
  162.             }
  163.             if ($this->getAction(== 'getRawData'{
  164.                 $field new FieldDescription('smap_segment');
  165.                 $field->setType(FieldDescription::FIELD_TYPE_STRING);
  166.                 $field->addProperty('tableName'$this->getTableName());
  167.                 $result->addFieldDescription($field);
  168.             }
  169.         }
  170.         return $result;
  171.     }
  172.     /**
  173.      * Добавляет данные об УРЛ
  174.      *
  175.      * @return array 
  176.      * @access protected
  177.      */
  178.  
  179.     protected function loadData({
  180.         $result parent::loadData();
  181.         if($result && $this->getAction(== 'getRawData'{
  182.             $result = array_map(create_function('$val''$val["smap_segment"] = SiteMap::getInstance()->getURLByID($val["smap_id"]); return $val;')$result);
  183.         }
  184.  
  185.         return $result;
  186.     }
  187.  
  188.     /**
  189.      * Подменяем построитель для метода setPageRights
  190.      *
  191.      * @return Builder 
  192.      * @access protected
  193.      */
  194.  
  195.     protected function prepare({
  196.         parent::prepare();
  197.         $actionParams $this->getActionParams();
  198.         if ($this->getAction(== 'edit'{
  199.             $field $this->getData()->getFieldByName('smap_pid');
  200.  
  201.             $smapName simplifyDBResult($this->dbh->select($this->getTranslationTableName()array('smap_name')array('smap_id' => $field->getRowData(0)'lang_id' => $this->document->getLang()))'smap_name'true);
  202.  
  203.             for ($i 0$i < count(Language::getInstance()->getLanguages())$i++{
  204.                 $field->setRowProperty($i'data_name'$smapName);
  205.             }
  206.  
  207.             if ($this->getDataDescription()->getFieldDescriptionByName('tags')) {
  208.                 $field new Field('tags');
  209.                 $tags TagManager::getInstance()->getPageTags($this->getData()->getFieldByName('smap_id')->getRowData(0));
  210.                 $tags = implode(', '$tags);
  211.                 $field->setData(array_fill(0count(Language::getInstance()->getLanguages())$tags));
  212.                 $this->getData()->addField($field);
  213.             }
  214.         }
  215.         elseif ($this->getAction(== 'add' && !empty($actionParams)) {
  216.             $field $this->getData()->getFieldByName('smap_pid');
  217.             $res $this->dbh->select($this->getTranslationTableName()array('smap_name')array('smap_id' => $actionParams[0]'lang_id' => $this->document->getLang()));
  218.             if (!empty($res)) {
  219.                 $name simplifyDBResult($res'smap_name'true);
  220.                 for ($i 0$i < count(Language::getInstance()->getLanguages())$i++{
  221.                     $field->changeRowData($i$actionParams[0]);
  222.                     $field->setRowProperty($i'data_name'$name);
  223.                 }
  224.             }
  225.         }
  226.     }
  227.  
  228.     /**
  229.      * Переопределенный внешний метод сохранения
  230.      * добавлено значение урла страницы
  231.      * Вызывает внутренний метод сохранения saveData(), который и производит собственно все действия
  232.      *
  233.      * @return void 
  234.      * @access protected
  235.      */
  236.  
  237.     protected function save({
  238.         $transactionStarted $this->dbh->beginTransaction();
  239.         try {
  240.             $result $this->saveData();
  241.             if (is_int($result)) {
  242.                 $mode 'insert';
  243.                 $id $result;
  244.                 /*Тут пришлось пойти на извращаения для получения УРЛа страницы, поскольку новосозданная страница еще не присоединена к дереву*/
  245.                 $smapPID simplifyDBResult($this->dbh->select('share_Sitemap''smap_pid'array('smap_id'=>$id))'smap_pid'true);
  246.                 $url $_POST[$this->getTableName()]['smap_segment'].'/';
  247.                 if ($smapPID{
  248.                     $url Sitemap::getInstance()->getURLByID($smapPID).$url;
  249.                 }
  250.  
  251.             }
  252.             else {
  253.                 $mode 'update';
  254.                 $id $this->getFilter();
  255.                 $id $id['smap_id'];
  256.                 $url Sitemap::getInstance()->getURLByID($id);
  257.             }
  258.  
  259.  
  260.             $transactionStarted !($this->dbh->commit());
  261.  
  262.             $JSONResponse array(
  263.             'result' => true,
  264.             'url' => $url,
  265.             'mode' => $mode
  266.             );
  267.         }
  268.         catch (FormException $formError{
  269.             $this->dbh->rollback();
  270.             //Формируем JS массив ошибок который будет разбираться на клиенте
  271.             $errors $this->saver->getErrors();
  272.             foreach ($errors as $errorFieldName{
  273.                 $message['errors'][array(
  274.                 'field'=>$this->translate('FIELD_'.strtoupper($errorFieldName)),
  275.                 'message'=>$this->translate($this->saver->getDataDescription()->getFieldDescriptionByName($errorFieldName)->getPropertyValue('message'))
  276.                 );
  277.             }
  278.             $JSONResponse = array_merge(array('result'=>false'header'=>$this->translate('TXT_SHIT_HAPPENS'))$message);
  279.         }
  280.         catch (SystemException $e){
  281.             if ($transactionStarted{
  282.                 $this->dbh->rollback();
  283.             }
  284.             $message['errors'][array('message'=>$e->getMessage().current($e->getCustomMessage()));
  285.             $JSONResponse = array_merge(array('result'=>false'header'=>$this->translate('TXT_SHIT_HAPPENS'))$message);
  286.  
  287.         }
  288.         $this->response->write(json_encode($JSONResponse));
  289.         $this->response->commit();
  290.     }
  291.     /**
  292.       * Переопределенный метод сохранения
  293.       * Для того чтобы реализовать уникальность smap_default
  294.       *
  295.       * @param array 
  296.       * @return void 
  297.       * @access protected
  298.       */
  299.  
  300.     protected function saveData({
  301.         if (!isset($_POST['right_id']|| !is_array($_POST['right_id'])) {
  302.             throw new SystemException('ERR_BAD_DATA'SystemException::ERR_CRITICAL);
  303.         }
  304.  
  305.         if (isset($_POST[$this->getTableName()]['smap_default']&& $_POST[$this->getTableName()]['smap_default'!== '0'{
  306.             $this->dbh->modify(QAL::UPDATE$this->getTableName()array('smap_default'=>null));
  307.         }
  308.  
  309.         $result parent::saveData();
  310.  
  311.         $smapID (is_int($result))?$result:current($this->getFilter());
  312.         $rights $_POST['right_id'];
  313.  
  314.         //Удаляем все предыдущие записи в таблице прав
  315.         $this->dbh->modify(QAL::DELETE 'share_AccessLevel'nullarray('smap_id'=>$smapID));
  316.         foreach ($rights as $groupID => $rightID{
  317.             if ($rightID != ACCESS_NONE{
  318.                 $this->dbh->modify(QAL::INSERT'share_AccessLevel'array('smap_id'=>$smapID'right_id'=>$rightID'group_id'=>$groupID));
  319.             }
  320.         }
  321.  
  322.         //Изменяем smap_modified
  323.         $this->dbh->modify(QAL::UPDATE$this->getTableName()array('smap_modified' => date('Y-m-d H:i:s'))array('smap_id'=>$smapID));
  324.         if (is_int($result)) {
  325.             //для метода вставки выставляем порядок следования
  326.  
  327.             //определяем smap_pid для текущего smap_id
  328.             $res simplifyDBResult($this->dbh->select($this->getTableName()array('smap_pid')array('smap_id'=>$smapID))'smap_pid'true);
  329.  
  330.             if (!empty($res)) {
  331.                 $PID ' = '.$res;
  332.  
  333.             }
  334.             else {
  335.                 $PID ' IS NULL ';
  336.             }
  337.             //
  338.             $this->dbh->modifyRequest('UPDATE '.$this->getTableName().' SET smap_order_num = smap_order_num+1 WHERE smap_PID'.$PID.' AND smap_order_num IS NOT NULL');
  339.  
  340.             $this->dbh->modify(QAL::UPDATE$this->getTableName()array('smap_order_num'=>1)array('smap_id'=>$smapID));
  341.         }
  342.  
  343.         if (($field $this->getDataDescription()->getFieldDescriptionByName('tags')) && isset($_POST['tags']&& !empty($_POST['tags'])) {
  344.             $tags $_POST['tags'];
  345.             $tags = explode(','$tags);
  346.             $tags = array_map(create_function('$tag''return trim($tag);')$tags);
  347.             $tagManager TagManager::getInstance();
  348.             $tagManager->clearPageTags($smapID);
  349.  
  350.             foreach ($tags as $tagName{
  351.                 if (!$tagManager->tagExists($tagName)) {
  352.                     $tagManager->createTag($tagName);
  353.                 }
  354.  
  355.                 $tagManager->addTagToPage($tagName$smapID);
  356.             }
  357.         }
  358.         return $result;
  359.     }
  360.  
  361.     /**
  362.      * Добавляем перевод
  363.      *
  364.      * @return void 
  365.      * @access protected
  366.      */
  367.  
  368.     protected function add({
  369.         parent::add();
  370.         $this->addTranslation('MSG_START_EDITING');
  371.     }
  372.  
  373.     /**
  374.      * Добавлен перевод для корня дерева разделов
  375.      *
  376.      * @return void 
  377.      * @access protected
  378.      */
  379.  
  380.      protected function main({
  381.         parent::main();
  382.         $this->addTranslation('TXT_DIVISIONS');
  383.      }
  384.  
  385.  
  386.     /**
  387.      * Не позволяет удалить раздел по умолчанию а также системные разделы
  388.      *
  389.      * @param int 
  390.      * @return void 
  391.      * @access protected
  392.      */
  393.  
  394.     protected function deleteData($id{
  395.         $res $this->dbh->select('share_Sitemap'array('smap_is_system''smap_default')array($this->getPK()=>$id));
  396.         if (!is_array($res))
  397.         throw new SystemException('ERR_DEV_BAD_DATA'SystemException::ERR_CRITICAL);
  398.  
  399.         list($res$res;
  400.         if ($res['smap_is_system'|| $res['smap_default']{
  401.             throw new SystemException('ERR_DEFAULT_OR_SYSTEM_DIVISION'SystemException::ERR_NOTICE );
  402.         }
  403.  
  404.         $res $this->dbh->select($this->getTableName()array('smap_order_num''smap_pid')array('smap_id'=>$id));
  405.         list($res$res;
  406.         $orderNum $res['smap_order_num'];
  407.  
  408.         if (!is_null($res['smap_pid'])) {
  409.             $PID ' = '.$res['smap_pid'];
  410.         }
  411.         else {
  412.             $PID 'IS NULL';
  413.         }
  414.  
  415.         parent::deleteData($id);
  416.  
  417.         //После благополучного удаления раздела перестраиваем индекс сортировки
  418.         $this->dbh->modifyRequest('UPDATE '.$this->getTableName().' SET smap_order_num = smap_order_num - 1 WHERE smap_order_num > %s AND smap_pid '.$PID$orderNum);
  419.  
  420.     }
  421.  
  422.  
  423.     /**
  424.      * Для метода setPageRights если раздел который редактируется - системный то дизейблятся вкладки с правами
  425.      * Для метода show слешатся имена разделов
  426.      * Для формы редактирования делается неактивным переключатель smap_default
  427.      *
  428.      * @return DOMNode 
  429.      * @access public
  430.      */
  431.  
  432.     public function build({
  433.         if ($this->getAction(== 'showPageToolbar'{
  434.  
  435.             $result false;
  436.             // вызываем родительский метод построения
  437.             $result Component::build();
  438.             $result->documentElement->appendChild($result->importNode($this->buildJS()true));
  439.             $result->documentElement->appendChild($result->importNode($this->getToolBar()->build()true));
  440.  
  441.         }
  442.         elseif ($this->getAction(== 'showTemplate'{
  443.             $result $this->templateEditor->build();
  444.         }
  445.         else {
  446.  
  447.             if ($this->getType(== self::COMPONENT_TYPE_FORM_ALTER {
  448.                 if (($field $this->getData()->getFieldByName('smap_default')) && ($field->getRowData(0)=== true)) {
  449.                     if ($fieldDescription $this->getDataDescription()->getFieldDescriptionByName('smap_default')) {
  450.                         $fieldDescription->setMode(FieldDescription::FIELD_MODE_READ);
  451.                     }
  452.                 }
  453.             }
  454.  
  455.             $result parent::build();
  456.             if ($this->getType(!= self::COMPONENT_TYPE_LIST {
  457.                 $result->documentElement->appendChild($this->buildRightsTab());
  458.             }
  459.         }
  460.         return $result;
  461.     }
  462.  
  463.     /**
  464.      * Метод возвращает свойства узла
  465.      *
  466.      * @return void 
  467.      * @access protected
  468.      */
  469.  
  470.     protected function getDivisionName({
  471.         try {
  472.             $id $_POST['id'];
  473.             $langID $_POST['languageID'];
  474.             if (!$this->recordExists($id)) {
  475.                 throw new SystemException('ERR_404'SystemException::ERR_404);
  476.             }
  477.  
  478.             $this->setFilter(array('smap_id'=>$id'lang_id'=>$langID));
  479.             $result $this->dbh->select($this->getTranslationTableName()array('smap_name')$this->getFilter());
  480.  
  481.             $JSONResponse array(
  482.             'result'=>true,
  483.             'data'=>simplifyDBResult($result'smap_name'true)
  484.             );
  485.         }
  486.         catch (SystemException $e){
  487.             $JSONResponse $this->generateError($e->getCode()$e->getMessage());
  488.  
  489.         }
  490.         $this->response->write(json_encode($JSONResponse));
  491.         $this->response->commit();
  492.     }
  493.  
  494.     /**
  495.      * Изменяет порядок следования
  496.      *
  497.      * @param string 
  498.      * @return JSON String
  499.      * @access protected
  500.      */
  501.  
  502.     protected function changeOrder($direction{
  503.         try {
  504.             $id $this->getActionParams();
  505.             list($id$id;
  506.             if (!$this->recordExists($id)) {
  507.                 throw new SystemException('ERR_404'SystemException::ERR_404);
  508.             }
  509.             $order $this->getOrder();
  510.             if ($direction == Grid::DIR_UP{
  511.                 $order[key($order)($order[key($order)== QAL::ASC)?QAL::DESC:QAL::ASC;
  512.             }
  513.  
  514.             //Определяем PID
  515.             $res $this->dbh->select($this->getTableName()array('smap_pid')array('smap_id' => $id));
  516.             $PID simplifyDBResult($res'smap_pid'true);
  517.  
  518.             if (!is_null($PID)) {
  519.                 $PID ' = '.$PID;
  520.             }
  521.             else {
  522.                 $PID 'IS NULL';
  523.             }
  524.  
  525.             $orderFieldName = key($order);
  526.             $request = sprintf('SELECT %s, %s
  527.                 FROM %s
  528.                 WHERE %s %s= (
  529.                 SELECT %s
  530.                 FROM %s
  531.                 WHERE %s = %s )
  532.                 AND smap_pid %s
  533.                 %s
  534.                 LIMIT 2 ',
  535.             $this->getPK()$orderFieldName,
  536.             $this->getTableName(),
  537.             $orderFieldName$direction,
  538.             $orderFieldName,
  539.             $this->getTableName(),
  540.             $this->getPK()$id,
  541.             $PID,
  542.             $this->dbh->buildOrderCondition($order));
  543.  
  544.             $result $this->dbh->selectRequest($request);
  545.             if ($result === true || sizeof($result)<2{
  546.                 throw new SystemException('ERR_CANT_MOVE'SystemException::ERR_NOTICE);
  547.             }
  548.  
  549.             $result convertDBResult($result$this->getPK()true);
  550.  
  551.             /**
  552.              * @todo Тут нужно что то пооптимальней придумать для того чтобы осуществить операцию переноса значений между двумя элементами массива
  553.              *  $a = $b;
  554.              *  $b =$a;
  555.              */
  556.             $keys = array_keys($result);
  557.             $data array();
  558.  
  559.             $c $result[current($keys)];
  560.             $data[current($keys)$result[next($keys)];
  561.             $data[current($keys)$c;
  562.  
  563.             foreach ($data as $id2 => $value{
  564.                 $order $value['smap_order_num'];
  565.                 $this->dbh->modify(QAL::UPDATE$this->getTableName()array($orderFieldName=>$order)array($this->getPK()=>$id2));
  566.                 if ($id2 != $id{
  567.                     $result $id2;
  568.                 }
  569.             }
  570.             $JSONResponse array(
  571.             'result' => true,
  572.             'nodeID' => $result,
  573.             'dir' => $direction
  574.             );
  575.         }
  576.         catch (SystemException $e){
  577.             $JSONResponse $this->generateError($e->getCode()$e->getMessage());
  578.  
  579.         }
  580.         return json_encode($JSONResponse);
  581.     }
  582.  
  583.     /**
  584.       * Выводит панель управления страницей
  585.       *
  586.       * @return void 
  587.       * @access protected
  588.       */
  589.  
  590.     protected function showPageToolbar({
  591.         if (!$this->config->getCurrentMethodConfig()) {
  592.             throw new SystemException('ERR_DEV_TOOLBAR_MUST_HAVE_CONFIG'SystemException::ERR_DEVELOPER);
  593.         }
  594.         $this->setToolbar($this->createToolbar());
  595.     }
  596.  
  597.     /**
  598.      * Селектор
  599.      *
  600.      * @return void 
  601.      * @access protected
  602.      */
  603.  
  604.     protected function selector({
  605.         $this->addTranslation('TXT_DIVISIONS');
  606.         $this->prepare();
  607.     }
  608.  
  609.     /**
  610.      * Method Description
  611.      *
  612.      * @return type 
  613.      * @access protected
  614.      */
  615.  
  616.     protected function showTemplate({
  617.         $this->request->setPathOffset($this->request->getPathOffset(1);
  618.         $this->templateEditor $this->document->componentManager->createComponent('templateEditor''share''TemplateEditor'null);
  619.         //$this->fileLibrary->getAction();
  620.         $this->templateEditor->run();
  621.     }
  622. }

Documentation generated on Mon, 17 Sep 2007 13:27:58 +0300 by phpDocumentor 1.4.0a2