* @license PHP License * @package WB * @subpackage content */ /** * Load required class */ WBClass::load('WBContent'); /** * Content component: TableEditor * * @version 1.8.1 * @package WB * @subpackage content */ class WBContent_TableEditor extends WBContent { /** * my parameter list * * - action either "moveup", "movedown", "list", "add", "edit", "rm", "display" * - requiredgroup (tableeditor) * - requiredgroupmanager (tableadmin) * - showsomeoneelses (1) display other users records, too * - table (blog) * - tableenv if table env is set, it will be used to load config and templates * - limit (20) * - goto pager's page (0) * - id (__new) * - managesortcolumn column name or empty ("") * - levels number of levels to move, if managesortcolumn applies (1) * - order custom order column (only if managesortcolumn is empty) * - addcurrentuser either "new", "always" or "never" ("new") * - searchfields (array()) list of columns that are allowed to search in using LIKE * - usefilter (0) filter lists, display filter form, use form definition * in filter.xml and filter.tmpl to render form. * - translator ("no"), "translate", "update", "auto" * * @var array */ protected $config = array( 'action' => 'list', 'requiredgroup' => 'tableeditor', 'requiredgroupmanager' => 'tableadmin', 'showsomeoneelses' => 1, 'table' => 'blog', 'tableenv' => '', 'limit' => '20', 'goto' => '0', 'id' => '__new', 'managesortcolumn' => '', 'levels' => 1, 'order' => '', 'addcurrentuser' => 'new', 'searchfields' => array(), 'usefilter' => 0, 'translator' => 'no', 'database' => '__default', 'manadator' => '' ); /** * table * @var WBDatasource_Table */ protected $table; /** * id of current record of table * @var string */ protected $id = '__new'; /** * relation table * @var WBDatasource_RelationTable */ protected $relTable; /** * List of relation Data * @var array */ protected $relList; /** * search and filter clause * @var array */ private $clause = array(); /** * Select options for pager * @var array */ private $options = array(); /** * Environment was set up * @var bool */ private $envOK = false; /** * Message Translations * @var patI18n_Importer */ private $nlsImp; /** * NLS Saver * @var WBDatasource_NLS_Saver */ protected $nlsSaver; /** * Mandator * @var WBMandator */ protected $mandator; /** * Pager Count Column Options * @var array */ protected $pagerCountColumn = array(); /** * Get Current Mandator Id * * @deprecated use $mandator->getId() * @return string */ protected function getMandator() { return $this->mandator->getId(); } /** * 2nd constructor */ protected function init() { $this->cachable = false; $this->startTable(); $params = array( 'table' => $this->table ); $this->mandator = WBClass::create('WBMandator', $params); $this->nlsSaver = WBClass::create('WBDatasource_NLS_Saver', $params); // allow to "become" other mandator if ($this->mandator->isMaster()) { $this->tmpl->addGlobalVar('mandator_master', 1); // change mandator? $mId = $this->req->get('filter_' . $this->mandator->getIdentifier()); if (is_array($mId)) { $mId = array_shift($mId); } if ($this->mandator->getId() != $mId) { $this->mandator->setId($mId); } } $this->config['mandator'] = $this->mandator->getId(); } /** * */ protected function addFiddler4Mandator() { if (!$this->mandator->isEnabled()) { return; } $parameter = array( 'mandator' => $this->mandator, // 'table' => $this->table ); /** @var WBFormProcessor_Fiddler_Mandator */ $fiddler = WBClass::create('WBFormProcessor_Fiddler_Mandator', $parameter); $this->addFiddler($fiddler); $this->addFilterFiddler($fiddler); } /** * run * * run component * * @return array parameter list */ public function run() { $this->addConfigAsGlobalVars(); if (!$this->setupPermission()) { return $this->config; } $tmplDir = $this->setupEnv(); $foreign = $this->setupForeign(); $this->setupSortColumn(); $p = $this->setupId($foreign); $this->nlsSaver->setTableName($this->config['table']); $this->nlsSaver->setId($this->id); $this->nlsSaver->enable(false); if (in_array($this->config['translator'], array('auto', 'on'))) { $this->nlsSaver->enable(true); } $this->addConfigAsGlobalVars(); $this->initFilter(); $this->runAction($p, $foreign, $tmplDir); return $this->config; } /** * Actually Run Action * * Switch-case for action parameter * * @param array * @param string * @param string */ protected function runAction($p, $foreign, $tmplDir) { switch ($this->config['action']) { case 'moveup': $this->moveUp($p); $this->loadTemplates('list'); break; case 'movedown': $this->moveUp($p, false); $this->loadTemplates('list'); break; case 'add': $this->runAdd($p, $foreign, $tmplDir); break; case 'clone': $this->runClone($p, $foreign, $tmplDir); break; case 'edit': $this->runEdit($p, $foreign, $tmplDir); break; case 'rm': if ($this->runRm($p, $foreign, $tmplDir)) { return; } $this->loadTemplates('list'); break; case 'display': $this->runDisplay($p, $foreign, $tmplDir); break; case 'list': default: $this->loadTemplates('list'); break; } $this->displayList($foreign); } /** * Actually Run Action: Display * * @param array * @param string * @param string */ protected function runDisplay($p, $foreign, $tmplDir) { $this->tmpl->addGlobalVars($p); $this->loadTemplates('display'); } /** * Actually Run Action: Add * * @param array * @param string * @param string */ protected function runAdd($p, $foreign, $tmplDir) { $this->addRelatedData($p); $this->id = '__new'; $this->runPreparedEdit($p); } /** * Actually Run Action: Clone * * Pretty much the same as editing, but create a new record on save * * @see runEdit() * @param array * @param string * @param string */ protected function runClone($p, $foreign, $tmplDir) { $this->addRelatedData($p); $p['id'] = '__new'; $this->id = '__new'; $this->runPreparedEdit($p); } /** * Actually Run Action: Edit * * @param array * @param string * @param string */ protected function runEdit($p, $foreign, $tmplDir) { $this->addRelatedData($p); $this->runPreparedEdit($p); } /** * Auxiliary Function to Show Edit form * * @used by runClone() * @used by runEdit() * @param array */ protected function runPreparedEdit($p) { $tmp = array(); foreach ($p as $k => $v) { if (is_scalar($v)) { $tmp[$k] = $v; } } $this->tmpl->addGlobalVars($tmp); $this->processForm('edit', $p); } /** * Actually Run Action: Delete * * @param array * @param string * @param string * @return bool */ protected function runRm($p, $foreign, $tmplDir) { $this->tmpl->addGlobalVars($p); // action and id required if ($this->config['action'] != 'rm' || $this->id == '__new') { return false; } // Ask: Do you really want to delete this record? if ($this->req->get('force', 'no') != 'yes') { $this->loadTemplates('rm'); $this->config['tmplDir'] = $tmplDir; return true; } // delete for real $this->table->delete($this->config['table'], $this->id, $foreign); $this->loadTemplates('rmDeleted'); $this->config['tmplDir'] = $tmplDir; return true; } /** * Check Permissions * * Check required group according to action and load "anon" template * @return bool true if access is granted */ protected function setupPermission() { if ($this->isUserInGroup($this->config['requiredgroupmanager'])) { $this->tmpl->addGlobalVar('user_current_requiredgroupmanager', 1); } if ($this->isUserInGroup($this->config['requiredgroup'])) { return true; } if (in_array($this->config['action'], array('display'))) { return true; } $this->loadTemplates('anon'); $this->setStatusCode(401); return false; } /** * Setup Foreign Key * * Utilize user id as foreign key if user is not "requiredgroupmanager". * * @return null|string */ protected function setupForeign() { // administrators may manage other's entries if ($this->isUserInGroup($this->config['requiredgroupmanager']) || WBContent::GROUP_ANON == $this->config['requiredgroupmanager']) { return null; } if (in_array($this->config['action'], array('display', 'list')) && $this->config['showsomeoneelses']) { return null; } if (!in_array($this->config['addcurrentuser'], array('new', 'always'))) { return null; } return $this->user->getId(); } /** * Setup Sort Column * * Check config and prepare action of sort columns */ protected function setupSortColumn() { // managesortcolumn is required if (!empty($this->config['managesortcolumn'])) { $this->config['level_' . $this->config['levels'] . '_checked'] = 'selected="selected" checked="checked"'; } else if (in_array($this->config['action'], array('moveup', 'movedown'))) { $this->config['action'] = 'edit'; } } /** * Setup Id * * Load record if a proper id is given * * @param string $foreign * @return array */ protected function setupId($foreign) { // reset id $this->id = $this->config['id']; $this->config['id'] = '__new'; if (empty($this->id)) { $this->id = '__new'; } // load record $p = array(array()); if($this->id != '__new') { $p = $this->table->get($this->config['table'], $this->id, $foreign); } if (count($p) != 1) { $p = array(array()); $this->id = '__new'; } $p = $p[0]; $this->config['id'] = $this->id; $this->tmpl->addGlobalVar('id', $this->id); return $p; } /** * Setup Environment * * Configure table env and template dir. Also check required group for switching translation * @return string $tmplDir */ protected function setupEnv() { if ($this->envOK) { return; } // by default, table env is the same as table if (empty($this->config['tableenv'])) { $this->config['tableenv'] = $this->config['table']; } // patch tmplDir with table name $tmplDir = $this->config['tmplDir']; $this->config['tmplDir'] = $tmplDir . '/' . $this->config['tableenv']; if ($this->isUserInGroup($this->config['requiredgroup'])) { $this->table->switchTranslation(false); } $p = null; $this->config['action'] = strtolower($this->config['action']); $this->envOK = true; return $tmplDir; } /** * display list of records in table * * In case there is a template named "list_entry", it will be filled with paged * rows of table * * @param string $foreign key of user table */ protected function displayList($foreign) { if (!$this->tmpl->exists('list_entry')) { return; } $clause = $this->filter->getClause(); $options = $this->filter->getOptions(); $this->tweakFilter($clause, $options); $this->mergeDisplayListOptions($options); // order if (empty($this->config['managesortcolumn']) && !empty($this->config['order'])) { $options['order'] = array( 'field' => $this->config['order'], 'asc' => 1 ); if ('!' == $this->config['order'][0]) { $options['order']['field'] = substr($this->config['order'], 1); $options['order']['asc'] = 0; } } $id = __CLASS__ . '-' . $this->part . '-'. $this->config['table'] . '-' . $this->config['tableenv']; if ($this->config['limit'] && intval($this->config['limit']) > 0) { $options['limit'] = intval($this->config['limit']); } $pager = $this->table->getPager($id, $this->config['table'], $foreign, $clause, $options); $pager->setCountColumn($this->pagerCountColumn); $this->tmpl->addGlobalVars($pager->browse($this->config['goto']), 'pager_'); $this->tmpl->addGlobalVars($pager->getCount(), 'list_'); $this->tmpl->addRows('list_entry', $this->prepareListEntry($pager->get())); } /** * Tweak Table Filter On Demand * * Implement this method to mange with filter clause and options * * @param array * @param option */ protected function tweakFilter(&$clause, &$option) { } /** * Prepare List Entries 4 Template * * @param array * @return array */ protected function prepareListEntry($list) { return $list; } /** * Merge Options to Display List * * @param array */ protected function mergeDisplayListOptions(&$options) { } /** * Save user's primary data * * Update user's forename and surname * * @param patForms $form * @param array $values * @return bool true on success */ public function onEditValid($form, $values) { $new = false; if ('__new' == $this->id) { $new = true; } // set user id if (!empty($this->config['addcurrentuser'])) { switch ($this->config['addcurrentuser']) { case 'new': if (!$new) { break; } // fall through case 'always': $values[$this->table->getIdentifier(WBDatasource::TABLE_USER, true)] = $this->user->getId(); break; default: case 'never': break; } } if ($this->initRelationTable()) { $this->relTable->extract($values); } $this->addGlobalVars($values); // add value for sortcolumn if ($new && !empty($this->config['managesortcolumn'])) { $col = $this->config['managesortcolumn']; $options = array( 'limit' => 1, 'order' => array( 'field' => $col, 'asc' => 0 ), 'column' => array($col) ); $clause = array(); $primary = $this->table->getIdentifier($this->config['table']); if (is_array($primary)) { array_pop($primary); foreach ($primary as $p) { $clause[] = array( 'field' => $p, 'value' => $values[$p] ); } } $last = $this->table->get($this->config['table'], null, null, $clause, $options); if (empty($last)) { $values[$col] = 0; } else { $values[$col] = ++$last[0][$col]; } } $id = $this->saveRecord($new, $values); if (!$new) { return true; } // make id avaible in template after new record was saved $this->tmpl->addGlobalVar('id', $id); // more complex keys $primary = $this->table->getIdentifier($this->config['table']); if (!is_array($primary)) { $primary = array($primary); } $id = explode('-', $id); foreach ($primary as $i => $p) { if (empty($id[$i])) { continue; } $this->tmpl->addGlobalVar($p, $id[$i]); } return true; } /** * Save Primary Records in DB * * Store simple record in table * * @param bool true if database record is new * @param array validated form values to be saved * @return string id of saved record */ protected function saveRecord($new, $values) { // delete old relations if (!$new && $this->initRelationTable()) { $this->relTable->delete($this->id); } $this->nlsSaver->strip($values); if (!$new) { $this->nlsSaver->save($values); } $id = $this->table->save($this->config['table'], $this->id, $values); if ($new) { $this->nlsSaver->setId($id); $this->nlsSaver->save($values); } if ($this->initRelationTable()) { $this->relTable->add($id); } $this->triggerEvent($id, $new, $values); return $id; } /** * Trigger Event * * Generic table editor event for each table * * @param string $id * @param bool $new * @param array $data */ protected function triggerEvent($id, $new, $values) { $eData = array( 'table' => $this->config['table'], 'id' => $id, 'new' => intval($new), 'save' => $values ); WBEvent::trigger('tableeditor:save:' . $this->config['table'], 'Saved record in table', $eData); } /** * Load data from related tables * * Resolve foreign keys stored in other tables and merge widata * * @param array &$data */ protected function addRelatedData(&$data) { if ('__new' == $this->id) { return; } if (!$this->initRelationTable()) { return; } $this->relList = $this->relTable->inject($data); } /** * Start relation table * * If relations are configured, start relation table object * * @todo improve persformace if called twice * @return bool */ protected function initRelationTable() { if ($this->relTable) { return true; } // load relation config $config = WBClass::create('WBConfig'); $config->load($this->getFormConfigDir(). '/edit'); $conf = $config->get('relation', array()); if (empty($conf) || !is_array($conf)) { return false; } $this->relTable = WBClass::create('WBDatasource_RelationTable'); $this->relTable->setTable($this->table); $this->relTable->setConfig($this->config['table'], $conf); return true; } /** * move record up in list * * Aktually swap position with next record to move current record * up or down. * * @param array $data record to move * @param bool $up move up (true) or down (false) */ private function moveUp($data, $up = true) { if (empty($this->config['managesortcolumn'])) { return; } $col = $this->config['managesortcolumn']; // shortcut applies for moving up first row (which is on top already) if ($up && 1 > $data[$col]) { return; } $levels = intval($this->config['levels']); if ($levels < 1) { $levels = 1; } // find other record to swap with $options = array( 'limit' => $levels, 'order' => array( 'field' => $col, 'asc' => 0 ), 'column' => array($col) ); $clause = array(); $clause[] = array( 'field' => $col, 'relation' => 'lt', 'value' => $data[$col] ); if (!$up) { $clause[0]['relation'] = 'gt'; $options['order']['asc'] = 1; } $other = $this->table->get($this->config['table'], null, null, $clause, $options); // if there is not upper entry, skip the rest if (empty($other)) { return; } // selected too many if (count($other) > $levels) { return; } // save current records in new position $last = array_pop($other); $save = array( $col => $last[$col] ); $this->table->save($this->config['table'], $this->id, $save); $primary = $this->table->getIdentifier($this->config['table']); if (!is_array($primary)) { $primary = array($primary); } // move all other items to the next position while (0 < count($other)) { $clause = array(); foreach ($primary as $p) { $clause[] = array( 'field' => $p, 'value' => $last[$p] ); } $last = array_pop($other); $save = array( $col => $last[$col] ); $this->table->save($this->config['table'], null, $save, $clause); } // save last item in current item's previous position $clause = array(); foreach ($primary as $p) { $clause[] = array( 'field' => $p, 'value' => $last[$p] ); } $save = array( $col => $data[$col] ); $this->table->save($this->config['table'], null, $save, $clause); } /** * location of form config * * Return sub directory where form element definitions are located * * @return string folder */ protected function getFormConfigDir() { return 'table/' . $this->config['tableenv']; } /** * Fetch List of Form Elements * * Use parent's method and add translators form elements * * @use WBDatasource_NLS_Saver::addFormElements() * @param string name of the xml- and template-filename * @return array $elements */ protected function getFormElementList($name) { $elements = parent::getFormElementList($name); $this->nlsSaver->addFormElements($elements); return $elements; } /** * Add form rules to just created form * * * * @param patForms $form object * @param string name of the xml- and template-filename * @param array $elements list of elements current form * @return bool true on success */ protected function insertFormRules(&$form, $name, $elements) { return true; } /** * Add form to template engine * * @param patForms $form * @param array $values * @return bool true */ protected function renderForm($form, $values = array()) { if (!is_array($this->relList)) { return parent::renderForm($form, $values); } foreach ($this->relList as $k => $l) { $this->tmpl->addGlobalVar($k . '_list_count', count($l)); if ($this->tmpl->exists($k . '_list_entry')) { $this->tmpl->addRows($k . '_list_entry', $l); } } return parent::renderForm($form, $values); } /** * Initialize Table Object * * Well, start if not there yet */ protected function startTable() { if (!empty($this->table)) { return; } $params = array(); if ('__default' != $this->config['database']) { /** @var WBConfig */ $config = WBClass::create('WBConfig'); // load default dabatabse $config->load('config'); $params = $config->get('db'); // load config for additional databases $config->load('table/database'); $params = array_merge($params, $config->get('db/' . $this->config['database'], array())); } $this->table = WBClass::create('WBDatasource_Table', $params); } }