From e7d600adf0ec536ecf15d4a9697c4f77453df5c6 Mon Sep 17 00:00:00 2001 From: secretr Date: Sun, 15 Nov 2009 20:24:55 +0000 Subject: [PATCH] admin UI: moving code to lower level (allow reusability) - better batch handling (separate methods), before/after delete/edit/create custom event triggers (e_admin_ui only) --- e107_admin/image.php | 205 ++++-- e107_handlers/admin_handler.php | 1201 +++++++++++++++++++------------ e107_handlers/form_handler.php | 8 +- 3 files changed, 869 insertions(+), 545 deletions(-) diff --git a/e107_admin/image.php b/e107_admin/image.php index 2dc0ef0f8..c7ec422dc 100644 --- a/e107_admin/image.php +++ b/e107_admin/image.php @@ -9,9 +9,9 @@ * Image Administration Area * * $Source: /cvs_backup/e107_0.8/e107_admin/image.php,v $ - * $Revision: 1.29 $ - * $Date: 2009-11-14 04:13:10 $ - * $Author: e107coders $ + * $Revision: 1.30 $ + * $Date: 2009-11-15 20:24:55 $ + * $Author: secretr $ * */ require_once("../class2.php"); @@ -92,81 +92,6 @@ class media_admin extends e_admin_dispatcher protected $menuTitle = LAN_MEDIAMANAGER; - function init() - { - $this->observeUploaded(); - } - - - function observeUploaded() - { - $sql = e107::getDb(); - $mes = e107::getMessage(); - - if(!varset($_POST['uploadfiles']) && !varset($_POST['etrigger_submit'])) - { - return; - } - - $pref['upload_storagetype'] = "1"; - require_once(e_HANDLER."upload_handler.php"); //TODO - still not a class! - $uploaded = process_uploaded_files(e_MEDIA.'temp/'); - - foreach($uploaded as $upload) - { - $oldpath = 'temp/'.$upload['name']; - $newpath =$this->getPath($upload['type']).'/'.$upload['name']; - - $insert = array( - 'media_type' => $upload['type'], - 'media_name' => $upload['name'], - 'media_description' => '', - 'media_url' => "{e_MEDIA}".$newpath, - 'media_datestamp' => time(), - 'media_size' => $upload['size'], - 'media_usedby' => '', - 'media_tags' => '', - 'media_author' => USERID - ); - - // Temporary workaround for class limitation. - /* - * We need to process the data before it's saved to the DB. - * When the upload is done with ajax, we need to prepopulate the form with the data above. - */ - - if(rename(e_MEDIA.$oldpath, e_MEDIA.$newpath)) - { - if($sql->db_Insert('core_media',$insert)) - { - $mes->add("Added ".$upload['name']." to DB", E_MESSAGE_SUCCESS); - } - } - } - - $message .= print_a($uploaded,TRUE); - - $mes->add($message, E_MESSAGE_DEBUG); - - - } - - function onDelete() // call when 'delete' is executed. - delete the file with the db record (optional pref) - { - - } - - function getPath($type) - { - list($pmime,$tmp) = explode('/',$type); - $dir = $this->mimePaths[$pmime]."/".date("Y-m"); - if(!is_dir(e_MEDIA.$dir)) - { - mkdir(e_MEDIA.$dir,0755); - } - return $dir; - } - } @@ -233,8 +158,132 @@ class media_admin_ui extends e_admin_ui 'pref_name' => array('title'=> 'name', 'type' => 'text') );*/ + /** + * Invoked just before item create event + * @return array + */ + public function beforeCreate() + { + // return data to be merged with posted model data + return $this->observeUploaded(); + } + + /** + * Same as beforeCreate() but invoked on edit + * @return + */ + public function beforeUpdate() + { + // return data to be merged with posted model data + return $this->observeUploaded(); + } + function observeUploaded() + { + $pref['upload_storagetype'] = "1"; + require_once(e_HANDLER."upload_handler.php"); //TODO - still not a class! + $uploaded = process_uploaded_files(e_MEDIA.'temp/'); + + foreach($uploaded as $upload) + { + $oldpath = 'temp/'.$upload['name']; + $newpath = $this->getPath($upload['type']).'/'.$upload['name']; + + $upload_data = array( + 'media_type' => $upload['type'], + 'media_name' => $upload['name'], + 'media_datestamp' => time(), + 'media_url' => "{e_MEDIA}".$newpath, + 'media_size' => $upload['size'], + 'media_author' => USERID + ); + + // only one upload? Not sure what's the idea here + // we are currently creating one media item + break; + } + + // remove 'media_upload' from save field list +// $this->getRequest()->setPosted('media_upload', null); +// $dataFields = $this->getModel()->getDataFields(); +// unset($dataFields['media_upload']); +// $this->getModel()->setDataFields($dataFields); + + // return data to be merged with posted model data + return $upload_data; + + /*$sql = e107::getDb(); + $mes = e107::getMessage(); + + // not needed + if(!varset($_POST['uploadfiles']) && !varset($_POST['etrigger_submit'])) + { + return; + } + + $pref['upload_storagetype'] = "1"; + require_once(e_HANDLER."upload_handler.php"); //TODO - still not a class! + $uploaded = process_uploaded_files(e_MEDIA.'temp/'); + + foreach($uploaded as $upload) + { + $oldpath = 'temp/'.$upload['name']; + $newpath = $this->getPath($upload['type']).'/'.$upload['name']; + + $insert = array( + 'media_type' => $upload['type'], + 'media_name' => $upload['name'], + 'media_description' => '', + 'media_url' => "{e_MEDIA}".$newpath, + 'media_datestamp' => time(), + 'media_size' => $upload['size'], + 'media_usedby' => '', + 'media_tags' => '', + 'media_author' => USERID + );*/ + + // Temporary workaround for class limitation. + /* + * We need to process the data before it's saved to the DB. + * When the upload is done with ajax, we need to prepopulate the form with the data above. + */ + + /*if(rename(e_MEDIA.$oldpath, e_MEDIA.$newpath)) + { + if($sql->db_Insert('core_media',$insert)) + { + $mes->add("Added ".$upload['name']." to DB", E_MESSAGE_SUCCESS); + } + } + } + + $message .= print_a($uploaded,TRUE); + $mes->add($message, E_MESSAGE_DEBUG);*/ + + + } + + function beforeDelete($id) // call before 'delete' is executed. - return false to prevent delete execution (e.g. some dependencies check) + { + return true; + } + + function afterDelete($deleted_data) // call after 'delete' is successfully executed. - delete the file with the db record (optional pref) + { + + } + + function getPath($type) + { + list($pmime,$tmp) = explode('/',$type); + $dir = $this->mimePaths[$pmime]."/".date("Y-m"); + if(!is_dir(e_MEDIA.$dir)) + { + mkdir(e_MEDIA.$dir,0755); + } + return $dir; + } } diff --git a/e107_handlers/admin_handler.php b/e107_handlers/admin_handler.php index c13905cda..c719ebb43 100644 --- a/e107_handlers/admin_handler.php +++ b/e107_handlers/admin_handler.php @@ -9,8 +9,8 @@ * Administration UI handlers, admin helper functions * * $Source: /cvs_backup/e107_0.8/e107_handlers/admin_handler.php,v $ - * $Revision: 1.26 $ - * $Date: 2009-11-14 14:52:26 $ + * $Revision: 1.27 $ + * $Date: 2009-11-15 20:24:55 $ * $Author: secretr $ */ @@ -2009,6 +2009,25 @@ class e_admin_controller_ui extends e_admin_controller */ protected $prefs = array(); + /** + * Data required for _modifyListQry() to automate + * db query building + * @var array + */ + protected $tableJoin = array(); + + /** + * Main model table alias + * @var string + */ + protected $tableAlias; + + /** + * Default (db) limit value + * @var integer + */ + protected $perPage = 20; + /** * @var e_admin_model */ @@ -2045,11 +2064,15 @@ class e_admin_controller_ui extends e_admin_controller * @param mixed $default default value if not set, default is null * @return mixed */ - public function getFieldAttr($field, $key, $default = null) + public function getFieldAttr($field, $key = null, $default = null) { - if(isset($this->fields[$field][$key])) + if(isset($this->fields[$field])) { - return $this->fields[$field][$key]; + if(null !== $key) + { + return isset($this->fields[$field][$key]) ? $this->fields[$field][$key] : $default; + } + return $this->fields[$field]; } return $default; } @@ -2072,6 +2095,15 @@ class e_admin_controller_ui extends e_admin_controller return $this->prefs; } + public function getPerPage() + { + return $this->perPage; + } + + public function getPrimaryName() + { + return $this->getModel()->getFieldIdName(); + } /** * Get column preference array @@ -2121,16 +2153,66 @@ class e_admin_controller_ui extends e_admin_controller return $this; } + /** + * Get model validation array + * @return array + */ public function getValidationRules() { return $this->getModel()->getValidationRules(); } + /** + * Get model data field array + * @return array + */ public function getDataFields() { return $this->getModel()->getDataFields(); } + /** + * Get model table or alias + * @param boolean $alias get table alias on true, default false + * @param object $prefix add e107 special '#' prefix, default false + * @return string + */ + public function getTableName($alias = false, $prefix = false) + { + if($alias) return ($this->tableAlias ? $this->tableAlias : ''); + return ($prefix ? '#' : '').$this->getModel()->getModelTable(); + } + + /** + * Get join table data + * @param string $table if null all data will be returned + * @param string $att_name search for specific attribute, default null (no search) + * @return mixed + */ + public function getJoinData($table = null, $att_name = null, $default_att = null) + { + if(null === $table) + { + return $this->tableJoin; + } + if(null === $att_name) + { + return (isset($this->tableJoin[$table]) ? $this->tableJoin[$table] : array()); + } + return (isset($this->tableJoin[$table][$att_name]) ? $this->tableJoin[$table][$att_name] : $default_att); + } + + public function setJoinData($table, $data) + { + if(null === $data) + { + unset($this->tableJoin[$table]); + return $this; + } + $this->tableJoin[$table] = (array) $data; + return $this; + } + /** * User defined model setter * @return e_admin_controller_ui @@ -2239,6 +2321,495 @@ class e_admin_controller_ui extends e_admin_controller { return $this; } + + /** + * Handle posted batch options routine + * @param string $batch_trigger + * @return e_admin_controller_ui + */ + protected function _handleListBatch($batch_trigger) + { + $tp = e107::getParser(); + //$multi_name = vartrue($this->fields['checkboxes']['toggle'], 'multiselect'); + $multi_name = $this->getFieldAttr('checkboxes', 'toggle', 'multiselect'); + $selected = array_values($this->getPosted($multi_name, array())); + + if(empty($selected)) return $this; + + $selected = array_map('intval', $selected); + $trigger = $tp->toDB(explode('__', $batch_trigger)); + + $this->triggersEnabled(false); //disable further triggering + + switch($trigger[0]) + { + case 'delete': //FIXME - confirmation screen + //something like handleListDeleteBatch(); for custom handling of 'delete' batch + $method = 'handle'.$this->getRequest()->getActionName().'DeleteBatch'; + if(method_exists($this, $method)) // callback handling + { + $this->$method($selected); + } + break; + + case 'bool': + $field = $trigger[1]; + $value = $trigger[2] ? 1 : 0; + //something like handleListBoolBatch(); for custom handling of 'bool' batch + $method = 'handle'.$this->getRequest()->getActionName().'BoolBatch'; + if(method_exists($this, $method)) // callback handling + { + $this->$method($selected, $field, $value); + break; + } + break; + + case 'boolreverse': + $field = $trigger[1]; + //something like handleListBoolreverseBatch(); for custom handling of 'boolreverse' batch + $method = 'handle'.$this->getRequest()->getActionName().'BoolreverseBatch'; + if(method_exists($this, $method)) // callback handling + { + $this->$method($selected, $field); + break; + } + break; + + default: + $field = $trigger[0]; + $value = $trigger[1]; //TODO - errors + //something like handleListUrlTypeBatch(); for custom handling of 'url_type' field name + $method = 'handle'.$this->getRequest()->getActionName().$this->getRequest()->camelize($field).'Batch'; + if(method_exists($this, $method)) // callback handling + { + $this->$method($selected, $value); + break; + } + //handleListBatch(); for custom handling of all field names + $method = 'handle'.$this->getRequest()->getActionName().'Batch'; + if(method_exists($this, $method)) + { + $this->$method($selected, $field, $value); + } + break; + } + return $this; + } + + /** + * Handle requested filter dropdown value + * @param string $value + * @return array field -> value + */ + protected function _parseFilterRequest($filter_value) + { + $tp = e107::getParser(); + if(!$filter_value || $filter_value === '___reset___') + { + return array(); + } + $filter = $tp->toDB(explode('__', $filter_value)); + $res = array(); + switch($filter[0]) + { + case 'bool': + // direct query + $res = array($filter[1], $filter[2]); + break; + + default: + //something like handleListUrlTypeFilter(); for custom handling of 'url_type' field name filters + $method = 'handle'.$this->getRequest()->getActionName().$this->getRequest()->camelize($filter[0]).'Filter'; + if(method_exists($this, $method)) // callback handling + { + return $this->$method($filter[1], $selected); + } + else // default handling + { + $res = array($filter[0], $filter[1]); + } + break; + } + return $res; + } + + + /** + * Convert posted to model values after submit (based on field type) + * @param array $data + * @return void + */ + protected function convertToData(&$data) + { + foreach ($this->getFields() as $key => $attributes) + { + if(!isset($data[$key])) + { + continue; + } + switch($attributes['type']) + { + case 'datestamp': + if(!is_numeric($data[$key])) + { + $data[$key] = e107::getDateConvert()->toTime($data[$key], 'input'); + } + break; + + case 'ip': // TODO - ask Steve if this check is required + if(strpos($data[$key], '.') !== FALSE) + { + $data[$key] = e107::getInstance()->ipEncode($data[$key]); + } + break; + } + } + $this->toData($data); + } + + /** + * User defined method for converting POSTED to MODEL data + * @param array $data posted data + * @param string $type current action type - edit, create, list or user defined + * @return void + */ + protected function toData(&$data, $type) + { + } + + /** + * Take approproate action after successfull submit + * + * @param integer $id optional, needed only if redirect action is 'edit' + * @param string $noredirect_for don't redirect if action equals to its value + */ + protected function doAfterSubmit($id = 0, $noredirect_for = '') + { + if($noredirect_for && $noredirect_for == $this->getPosted('__after_submit_action') && $noredirect_for == $this->getAction()) + { + return; + } + + $choice = $this->getPosted('__after_submit_action', 0); + switch ($choice) { + case 'create': // create + $this->redirectAction('create', 'id'); + break; + + case 'edit': // edit + $this->redirectAction('edit', '', 'id='.$id); + break; + + case 'list': // list + $this->redirectAction('list', 'id'); + break; + + default: + $choice = explode('|', str_replace('{ID}', $id, $choice), 3); + $this->redirectAction(preg_replace('/[^\w\-]/', '', $choice[0]), vartrue($choice[1]), vartrue($choice[2])); + break; + } + return; + } + + /** + * Build ajax auto-complete filter response + * @return string response markup + */ + protected function renderAjaxFilterResponse($listQry = '') + { + $ret = ''; + return $ret; + } + + /** + * Parses all available field data, adds internal attributes for handling join requests + * @return e_admin_controller_ui + */ + protected function parseAliases() + { + if($this->getJoinData()) + { + foreach ($this->getJoinData() as $table => $att) + { + if(strpos($table, '.') !== false) + { + $tmp = explode('.', $table, 2); + $this->setJoinData($table, null); + $att['alias'] = $tmp[0]; + $att['table'] = $tmp[1]; + $att['__tablePath'] = $att['alias'].'.'; + $att['__tableFrom'] = '`#'.$att['table'].'` AS '.$att['alias']; + $this->setJoinData($att['alias'], $att); + unset($tmp); + continue; + } + $att['table'] = $table; + $att['alias'] = ''; + $att['__tablePath'] = '`#'.$att['table'].'`.'; + $att['__tableFrom'] = '`#'.$att['table'].'`'; + $this->setJoinData($table, $att); + } + } + + // check for table aliases + $fields = array(); // preserve order + foreach ($this->fields as $field => $att) + { + if(strpos($field, '.') !== false) + { + $tmp = explode('.', $field, 2); + $att['alias'] = $tmp[0]; + $fields[$tmp[1]] = $att; + $field = $tmp[1]; + unset($tmp); + } + else + { + $att['alias'] = $this->getTableName(true); + $fields[$field] = $att; + } + if($fields[$field]['alias']) + { + + if($fields[$field]['alias'] == $this->getTableName(true)) + { + $fields[$field]['__tableField'] = $this->getTableName(true).'.'.$field; + } + else + { + $fields[$field]['__tableField'] = $this->getJoinData($fields[$field]['alias'], '__tablePath').$field; + } + } + else + { + $fields[$field]['__tableField'] = '`'.$this->getTableName(false, true).'`.'.$field; + } + } + $this->fields = $fields; + + return $this; + } + + // TODO - abstract, array return type, move to parent? + protected function _modifyListQry($raw = false, $isfilter = false, $forceFrom = false, $forceTo = false, $listQry) + { + $searchQry = array(); + $filterFrom = array(); + $request = $this->getRequest(); + $tp = e107::getParser(); + $tablePath = '`'.$this->getTableName(false, true).'`.'; + $tableFrom = '`'.$this->getTableName(false, true).'`'; + $tableSFields = '`'.$this->getTableName(false, true).'`.*'; + // check for alias + if($this->getTableName(true)) + { + $tablePath = $this->getTableName(true).'.'; + $tableFrom = '`'.$this->getTableName(false, true).'` AS '.$this->getTableName(true); + $tableSFields = ''.$this->getTableName(true).'.*'; + } + + $searchQuery = $tp->toDB($request->getQuery('searchquery', '')); + $searchFilter = $this->_parseFilterRequest($request->getQuery('filter_options', '')); + list($filterField, $filterValue) = $searchFilter; + + if($filterField && $filterValue !== '' && isset($this->fields[$filterField])) + { + $searchQry[] = $this->fields[$filterField]['__tableField']." = '".$filterValue."'"; + } + + + $filter = array(); + + // Commented for now - we should search in ALL searchable fields, not only currently active. Discuss. + //foreach($this->fieldpref as $key) + foreach($this->getFields() as $key => $var) + { + //if(!vartrue($this->fields[$key])) continue; + //$var = $this->fields[$key]; + $searchable_types = array('text', 'textearea', 'bbarea', 'user'); //method? + + if(trim($searchQuery) !== '' && !vartrue($var['nolist']) && in_array($var['type'], $searchable_types)) + { + $filter[] = $var['__tableField']." REGEXP ('".$searchQuery."')"; + if($isfilter) + { + $filterFrom[] = $var['__tableField']; + } + } + } + if($isfilter) + { + if(!$filterFrom) return false; + $tableSFields = implode(', ', $filterFrom); + } + + $jwhere = array(); + $joins = array(); + + if($this->getJoinData()) + { + $qry = "SELECT SQL_CALC_FOUND_ROWS ".$tableSFields; + foreach ($this->getJoinData() as $jtable => $tparams) + { + + // Select fields + if(!$isfilter) + { + $fields = vartrue($tparams['fields']); + if('*' === $fields) + { + $qry .= ", {$tparams['__tablePath']}*"; + } + else + { + $fields = explode(',', $fields); + foreach ($fields as $field) + { + $qry .= ", {$tparams['__tablePath']}`".trim($field).'`'; + } + } + } + + // Prepare Joins + $joins[] = " + ".vartrue($tparams['joinType'], 'LEFT JOIN')." {$tparams['__tableFrom']} ON ".(vartrue($tparams['leftTable']) ? $tparams['leftTable'].'.' : $tablePath)."`".vartrue($tparams['leftField'])."` = {$tparams['__tablePath']}`".vartrue($tparams['rightField'])."`".(vartrue($tparams['whereJoin']) ? ' '.$tparams['whereJoin'] : ''); + + // Prepare Where + if(vartrue($tparams['where'])) + { + $jwhere[] = $tparams['where']; + } + } + + //From + $qry .= " FROM ".$tableFrom; + + // Joins + if(count($joins) > 0) + { + $qry .= "\n".implode("\n", $joins); + } + } + else + { + $qry = $listQry ? $listQry : "SELECT SQL_CALC_FOUND_ROWS ".$tableSFields." FROM ".$tableFrom; + } + + if($raw) + { + $rawData = array('joinWhere' => $jwhere, 'filter' => $filter, 'filterFrom' => $filterFrom, 'search' => $searchQry, 'tableFrom' => $tableFrom); + $rawData['orderField'] = isset($this->fields[$orderField]) ? $this->fields[$orderField]['__tableField'] : ''; + $rawData['orderType'] = $request->getQuery('asc') == 'desc' ? 'DESC' : 'ASC'; + $rawData['limitFrom'] = false === $forceFrom ? intval($request->getQuery('from', 0)) : intval($forceFrom); + $rawData['limitTo'] = false === $forceTo ? intval($this->getPerPage()) : intval($forceTo); + return $rawData; + } + + // join where + if(count($jwhere) > 0) + { + $searchQry[] = " (".implode(" AND ",$jwhere)." )"; + } + // filter where + if(count($filter) > 0) + { + $searchQry[] = " ( ".implode(" OR ",$filter)." ) "; + } + + // where query + if(count($searchQry) > 0) + { + $qry .= " WHERE ".implode(" AND ", $searchQry); + } + + $orderField = $request->getQuery('field', $this->getPrimaryName()); + if(isset($this->fields[$orderField])) + { + // no need of sanitize - it's found in field array + $qry .= ' ORDER BY '.$this->fields[$orderField]['__tableField'].' '.($request->getQuery('asc') == 'desc' ? 'DESC' : 'ASC'); + } + + if($this->getPerPage() || false !== $forceTo) + { + $from = false === $forceFrom ? intval($request->getQuery('from', 0)) : intval($forceFrom); + if(false === $forceTo) $forceTo = $this->getPerPage(); + $qry .= ' LIMIT '.$from.', '.intval($forceTo); + } + + return $qry; + } + + /** + * Manage submit item + * @param array $posted [optional] additional model data + * @return + */ + protected function _manageSubmit($posted = array(), $noredirectAction = '') + { + // Scenario I - use request owned POST data - toForm already exeuted + $_posted = $this->getPosted(); + $this->convertToData($_posted); + if($posted && is_array($posted)) + { + $_posted = array_merge($_posted, $posted); + } + $this->getModel()->setPostedData($_posted, null, false, false) + ->save(true); + // Scenario II - inner model sanitize + //$this->getModel()->setPosted($this->convertToData($_POST(, null, false, true); + + // Copy model messages to the default message stack + $this->getModel()->setMessages(); + + // Take action based on use choice after success + if(!$this->getModel()->hasError()) + { + $this->doAfterSubmit($this->getModel()->getId(), $noredirectAction); + return true; + } + return false; + } } class e_admin_ui extends e_admin_controller_ui @@ -2251,14 +2822,11 @@ class e_admin_ui extends e_admin_controller_ui protected $pluginName; protected $listQry; - protected $tableJoin; protected $editQry; protected $table; - protected $tableAlias; protected $pid; protected $pluginTitle; - protected $perPage = 20; protected $batchDelete = true; /** @@ -2300,6 +2868,90 @@ class e_admin_ui extends e_admin_controller_ui if($batch_trigger && !$this->hasTrigger()) $this->_handleListBatch($batch_trigger); } + /** + * Batch delete trigger + * @param array $selected + * @return void + */ + protected function handleListDeleteBatch($selected) + { + if(!$this->getBatchDelete()) + { + e107::getMessage()->add('Batch delete not allowed!', E_MESSAGE_WARNING); + return; + } + // delete one by one - more control, less performance + // TODO - pass afterDelete() callback to tree delete method? + foreach ($selected as $id) + { + if($this->beforeDelete($id)) + { + $data = array(); + $model = $this->getTreeModel()->getNode($id); + if($model) + { + $data = $model->getData(); + } + if($this->getTreeModel()->delete($id)) + { + $this->afterDelete($data); + } + } + } + + //$this->getTreeModel()->delete($selected); + $this->getTreeModel()->setMessages(); + } + + /** + * Batch boolean trigger + * @param array $selected + * @return void + */ + protected function handleListBoolBatch($selected, $field, $value) + { + $cnt = $this->getTreeModel()->update($field, $value, $selected, $value, false); + if($cnt) + { + $this->getTreeModel()->addMessageSuccess($cnt.' records successfully updated.'); + } + $this->getTreeModel()->setMessages(); + } + + /** + * Batch boolean reverse trigger + * @param array $selected + * @return void + */ + protected function handleListBoolreverseBatch($selected, $field, $value) + { + $tree = $this->getTreeModel(); + $cnt = $tree->update($field, "1-{$field}", $selected, null, false); + if($cnt) + { + $tree->addMessageSuccess($cnt.' records successfully reversed.'); + //sync models + $tree->load(true); + } + $this->getTreeModel()->setMessages(); + } + + /** + * Batch default (field) trigger + * @param array $selected + * @return void + */ + protected function handleListBatch($selected, $field, $value) + { + $cnt = $this->getTreeModel()->update($field, "'".$value."'", $selected, $value, false); + if($cnt) + { + $vttl = $this->getUI()->renderValue($field, $value, $this->getFieldAttr($field)); + $this->getTreeModel()->addMessageSuccess(''.$vttl.' set for '.$cnt.' record(s).'); + } + $this->getTreeModel()->setMessages(); + } + /** * Catch fieldpref submit * @param string $batch_trigger @@ -2334,8 +2986,35 @@ class e_admin_ui extends e_admin_controller_ui { $this->triggersEnabled(false); $id = intval(array_shift($posted)); - $this->getTreeModel()->delete($id); - $this->getTreeModel()->setMessages(); + if($this->beforeDelete($id)) + { + $data = array(); + $model = $this->getTreeModel()->getNode($id); + if($model) + { + $data = $model->getData(); + } + if($this->getTreeModel()->delete($id)) + { + $this->afterDelete($data); + } + $this->getTreeModel()->setMessages(); + } + } + + /** + * User defined pre-delete logic + */ + public function beforeDelete($id) + { + return true; + } + + /** + * User defined after-create logic + */ + public function afterDelete($deleted_data) + { } /** @@ -2354,10 +3033,19 @@ class e_admin_ui extends e_admin_controller_ui */ public function ListObserver() { - $this->getTreeModel()->setParam('db_query', $this->_modifyListQry())->load(); + $this->getTreeModel()->setParam('db_query', $this->_modifyListQry(false, false, false, false, $this->listQry))->load(); $this->addTitle('List'); // FIXME - get captions from dispatch list } + /** + * Filter response ajax page + * @return string + */ + public function FilterAjaxPage() + { + return $this->renderAjaxFilterResponse($this->listQry); //listQry will be used only if available + } + /** * Generic List action page * @return string @@ -2367,59 +3055,13 @@ class e_admin_ui extends e_admin_controller_ui return $this->getUI()->getList(); } - public function FilterAjaxPage() - { - $ret = ''; - return $ret; - } - /** * List action observer * @return void */ public function ListAjaxObserver() { - $this->getTreeModel()->setParam('db_query', $this->_modifyListQry(false, 0))->load(); + $this->getTreeModel()->setParam('db_query', $this->_modifyListQry(false, false, 0, false, $this->listQry))->load(); } /** @@ -2453,7 +3095,10 @@ class e_admin_ui extends e_admin_controller_ui */ public function EditSubmitTrigger() { - $this->CreateSubmitTrigger(); + if($this->_manageSubmit($this->beforeUpdate(), 'edit')) + { + $this->afterUpdate(); + } } /** @@ -2498,23 +3143,38 @@ class e_admin_ui extends e_admin_controller_ui */ public function CreateSubmitTrigger() { - // Scenario I - use request owned POST data - toForm already exeuted - $posted = $this->getPosted(); - $this->convertToData($posted); - $this->getModel()->setPostedData($posted, null, false, false) - ->save(true); - // Scenario II - inner model sanitize - //$this->getModel()->setPosted($this->convertToData($_POST(, null, false, true); - - // Copy model messages to the default message stack - $this->getModel()->setMessages(); - - // Take action based on use choice after success - if(!$this->getModel()->hasError()) + if($this->_manageSubmit($this->beforeCreate(), '')) { - $this->doAfterSubmit($this->getModel()->getId(), 'edit'); + $this->afterCreate(); } - + } + + /** + * User defined pre-create logic + */ + public function beforeCreate() + { + } + + /** + * User defined after-create logic + */ + public function afterCreate() + { + } + + /** + * User defined pre-update logic + */ + public function beforeUpdate() + { + } + + /** + * User defined after-update logic + */ + public function afterUpdate() + { } /** @@ -2554,123 +3214,9 @@ class e_admin_ui extends e_admin_controller_ui } /** - * Handle posted batch options - * @param string $batch_trigger - * @return void + * Parent overload + * @return e_admin_ui */ - protected function _handleListBatch($batch_trigger) - { - $tp = e107::getParser(); - $multi_name = vartrue($this->fields['checkboxes']['toggle'], 'multiselect'); - $selected = array_values($this->getPosted($multi_name, array())); - - if(empty($selected)) return; - - $selected = array_map('intval', $selected); - $trigger = $tp->toDB(explode('__', $batch_trigger)); - - $this->triggersEnabled(false); //disable further triggering - - switch($trigger[0]) - { - case 'delete': //FIXME - confirmation screen - if(!$this->getBatchDelete()) - { - e107::getMessage()->add('Batch delete not allowed!', E_MESSAGE_WARNING); - return; - } - $this->getTreeModel()->delete($selected); - $this->getTreeModel()->setMessages(); - break; - - case 'bool': - // direct query - $field = $trigger[1]; - $value = $trigger[2] ? 1 : 0; - $cnt = $this->getTreeModel()->update($field, $value, $selected, $value, false); - if($cnt) - { - $this->getTreeModel()->addMessageSuccess($cnt.' records successfully updated.'); - } - $this->getTreeModel()->setMessages(); - break; - - case 'boolreverse': - // direct query - $field = $trigger[1]; //TODO - errors - $tree = $this->getTreeModel(); - $cnt = $tree->update($field, "1-{$field}", $selected, null, false); - if($cnt) - { - $tree->addMessageSuccess($cnt.' records successfully reversed.'); - //sync models - $tree->load(true); - } - $this->getTreeModel()->setMessages(); - break; - - default: - //something like handleListUrlTypeBatch(); for custom handling of 'url_type' field name - $method = 'handle'.$this->getRequest()->getActionName().$this->getRequest()->camelize($trigger[0]).'Batch'; - if(method_exists($this, $method)) // callback handling - { - $this->$method($trigger[1], $selected); - } - else // default handling - { - $field = $trigger[0]; - $value = $trigger[1]; //TODO - errors - - $cnt = $this->getTreeModel()->update($field, "'".$value."'", $selected, $value, false); - if($cnt) - { - $vttl = $this->getUI()->renderValue($field, $value, $this->getFieldAttr($field)); - $this->getTreeModel()->addMessageSuccess(''.$vttl.' set for '.$cnt.' records.'); - } - $this->getTreeModel()->setMessages(); - //$this->redirectAction(); - } - break; - } - } - - /** - * Handle requested filter dropdown value - * @param string $value - * @return array field -> value - */ - protected function _parseFilterRequest($filter_value) - { - $tp = e107::getParser(); - if(!$filter_value || $filter_value === '___reset___') - { - return array(); - } - $filter = $tp->toDB(explode('__', $filter_value)); - $res = array(); - switch($filter[0]) - { - case 'bool': - // direct query - $res = array($filter[1], $filter[2]); - break; - - default: - //something like handleListUrlTypeFilter(); for custom handling of 'url_type' field name filters - $method = 'handle'.$this->getRequest()->getActionName().$this->getRequest()->camelize($filter[0]).'Filter'; - if(method_exists($this, $method)) // callback handling - { - return $this->$method($filter[1], $selected); - } - else // default handling - { - $res = array($filter[0], $filter[1]); - } - break; - } - return $res; - } - protected function parseAliases() { // parse table @@ -2682,276 +3228,11 @@ class e_admin_ui extends e_admin_controller_ui unset($tmp); } - if($this->tableJoin) - { - foreach ($this->tableJoin as $table => $att) - { - if(strpos($table, '.') !== false) - { - $tmp = explode('.', $table, 2); - unset($this->tableJoin[$table]); - $att['alias'] = $tmp[0]; - $att['table'] = $tmp[1]; - $att['__tablePath'] = $att['alias'].'.'; - $att['__tableFrom'] = '`#'.$att['table'].'` AS '.$att['alias']; - $this->tableJoin[$att['alias']] = $att; - unset($tmp); - continue; - } - $this->tableJoin[$table]['table'] = $table; - $this->tableJoin[$table]['alias'] = ''; - $this->tableJoin[$table]['__tablePath'] = '`#'.$this->tableJoin[$table]['table'].'`.'; - $this->tableJoin[$table]['__tableFrom'] = '`#'.$this->tableJoin[$table]['table'].'`'; - } - } - - // check for table aliases - $fields = array(); // preserve order - foreach ($this->fields as $field => $att) - { - if(strpos($field, '.') !== false) - { - $tmp = explode('.', $field, 2); - $att['alias'] = $tmp[0]; - $fields[$tmp[1]] = $att; - $field = $tmp[1]; - unset($tmp); - } - else - { - $att['alias'] = $this->tableAlias; - $fields[$field] = $att; - } - if($fields[$field]['alias']) - { - - if($fields[$field]['alias'] == $this->tableAlias) - { - $fields[$field]['__tableField'] = $this->tableAlias.'.'.$field; - } - else - { - $fields[$field]['__tableField'] = $this->tableJoin[$fields[$field]['alias']]['__tablePath'].$field; - } - } - else - { - $fields[$field]['__tableField'] = '`#'.$this->table.'`.'.$field; - } - } - $this->fields = $fields; + parent::parseAliases(); return $this; } - - /** - * Take approproate action after successfull submit - * - * @param integer $id optional, needed only if redirect action is 'edit' - * @param string $noredirect_for don't redirect if action equals to its value - */ - protected function doAfterSubmit($id = 0, $noredirect_for = '') - { - if($noredirect_for && $noredirect_for == $this->getPosted('__after_submit_action') && $noredirect_for == $this->getAction()) - { - return; - } - - $choice = $this->getPosted('__after_submit_action', 0); - switch ($choice) { - case 'create': // create - $this->redirectAction('create', 'id'); - break; - - case 'edit': // edit - $this->redirectAction('edit', '', 'id='.$id); - break; - - case 'list': // list - $this->redirectAction('list', 'id'); - break; - default: - $choice = explode('|', str_replace('{ID}', $id, $choice), 3); - $this->redirectAction(preg_replace('/[^\w\-]/', '', $choice[0]), vartrue($choice[1]), vartrue($choice[2])); - break; - } - return; - } - - /** - * Convert posted values when needed (based on field type) - * @param array $data - * @return void - */ - protected function convertToData(&$data) - { - foreach ($this->getFields() as $key => $attributes) - { - if(!isset($data[$key])) - { - continue; - } - switch($attributes['type']) - { - case 'datestamp': - if(!is_numeric($data[$key])) - { - $data[$key] = e107::getDateConvert()->toTime($data[$key], 'input'); - } - break; - - case 'ip': // TODO - ask Steve if this check is required - if(strpos($data[$key], '.') !== FALSE) - { - $data[$key] = e107::getInstance()->ipEncode($data[$key]); - } - break; - //more to come - } - } - } - - protected function _modifyListQry($isfilter = false, $forceFrom = false, $forceTo = false) - { - $searchQry = array(); - $filterFrom = array(); - $request = $this->getRequest(); - $tp = e107::getParser(); - $tablePath = '`#'.$this->table.'`.'; - $tableFrom = '`#'.$this->table.'`'; - $tableSFields = '`#'.$this->table.'`.*'; - if($this->tableAlias) - { - $tablePath = $this->tableAlias.'.'; - $tableFrom = '`#'.$this->table.'` AS '.$this->tableAlias; - $tableSFields = ''.$this->tableAlias.'.*'; - } - - $searchQuery = $tp->toDB($request->getQuery('searchquery', '')); - $searchFilter = $this->_parseFilterRequest($request->getQuery('filter_options', '')); - list($filterField, $filterValue) = $searchFilter; - - // FIXME - currently broken - if($filterField && $filterValue !== '' && isset($this->fields[$filterField])) - { - $searchQry[] = $this->fields[$filterField]['__tableField']." = '".$filterValue."'"; - } - - - $filter = array(); - - // Commented for now - we should search in ALL searchable fields, not only currently active. Discuss. - //foreach($this->fieldpref as $key) - foreach($this->fields as $key => $var) - { - //if(!vartrue($this->fields[$key])) continue; - //$var = $this->fields[$key]; - $searchable_types = array('text', 'textearea', 'bbarea', 'user'); //method? - - if(trim($searchQuery) !== '' && !vartrue($var['nolist']) && in_array($var['type'], $searchable_types)) - { - $filter[] = $var['__tableField']." REGEXP ('".$searchQuery."')"; - if($isfilter) - { - $filterFrom[] = $var['__tableField']; - } - } - } - if($isfilter) - { - if(!$filterFrom) return false; - $tableSFields = implode(', ', $filterFrom); - } - - $jwhere = array(); - $joins = array(); - if($this->tableJoin) - { - $qry = "SELECT SQL_CALC_FOUND_ROWS ".$tableSFields; - foreach ($this->tableJoin as $jtable => $tparams) - { - // Select fields - if(!$isfilter) - { - $fields = vartrue($tparams['fields']); - if('*' === $fields) - { - $qry .= ", {$tparams['__tablePath']}*"; - } - else - { - $fields = explode(',', $fields); - foreach ($fields as $field) - { - $qry .= ", {$tparams['__tablePath']}`".trim($field).'`'; - } - } - } - // Prepare Joins - $joins[] = " - ".vartrue($tparams['joinType'], 'LEFT JOIN')." {$tparams['__tableFrom']} ON ".(vartrue($tparams['leftTable']) ? $tparams['leftTable'].'.' : $tablePath)."`".vartrue($tparams['leftField'])."` = {$tparams['__tablePath']}`".vartrue($tparams['rightField'])."`".(vartrue($tparams['whereJoin']) ? ' '.$tparams['whereJoin'] : ''); - - // Prepare Where - if(vartrue($tparams['where'])) - { - $jwhere[] = $tparams['where']; - } - } - - //From - $qry .= " FROM ".$tableFrom; - - // Joins - if(count($joins) > 0) - { - $qry .= "\n".implode("\n", $joins); - } - } - else - { - $qry = $this->listQry ? $this->listQry : "SELECT SQL_CALC_FOUND_ROWS ".$tableSFields." FROM ".$tableFrom; - } - - // join where - if(count($jwhere) > 0) - { - $searchQry[] = " (".implode(" AND ",$jwhere)." )"; - } - // filter where - if(count($filter) > 0) - { - $searchQry[] = " ( ".implode(" OR ",$filter)." ) "; - } - - // where query - if(count($searchQry) > 0) - { - $qry .= " WHERE ".implode(" AND ", $searchQry); - } - - $orderField = $request->getQuery('field', $this->getPrimaryName()); - if(isset($this->fields[$orderField])) - { - // no need of sanitize - it's found in field array - $qry .= ' ORDER BY '.$this->fields[$orderField]['__tableField'].' '.($request->getQuery('asc') == 'desc' ? 'DESC' : 'ASC'); - } - - if($this->getPerPage() || false !== $forceTo) - { - $from = false === $forceFrom ? intval($request->getQuery('from', 0)) : intval($forceFrom); - if(false === $forceTo) $forceTo = $this->getPerPage(); - $qry .= ' LIMIT '.$from.', '.intval($forceTo); - } - - return $qry; - } - - public function getPerPage() - { - return $this->perPage; - } - public function getPrimaryName() { if(!varset($this->pid) && vartrue($this->fields)) @@ -2974,13 +3255,7 @@ class e_admin_ui extends e_admin_controller_ui public function getTableName($alias = false, $prefix = false) { - if($alias && $this->tableAlias) return $this->tableAlias; - return ($prefix ? '#.' : '').$this->table; - } - - public function getJoinTable($alias = false, $prefix = false) - { - if($alias && $this->tableAlias) return $this->tableAlias; + if($alias) return ($this->tableAlias ? $this->tableAlias : ''); return ($prefix ? '#' : '').$this->table; } diff --git a/e107_handlers/form_handler.php b/e107_handlers/form_handler.php index 4e808f0ed..25da57d8f 100644 --- a/e107_handlers/form_handler.php +++ b/e107_handlers/form_handler.php @@ -9,9 +9,9 @@ * Form Handler * * $Source: /cvs_backup/e107_0.8/e107_handlers/form_handler.php,v $ - * $Revision: 1.80 $ - * $Date: 2009-11-14 04:13:10 $ - * $Author: e107coders $ + * $Revision: 1.81 $ + * $Date: 2009-11-15 20:24:55 $ + * $Author: secretr $ * */ @@ -1154,7 +1154,7 @@ class e_form case 'method': // Custom Function $method = $field; - $value = call_user_func_array(array($this, $method), array($value, 'read', $parms)); + $value = call_user_func_array(array($this, $method), array($value, 'read', $parms)); break; //TODO - order