parseRequest($request_string); } } /** * Parse request data * @param string|array $request_data * @return e_admin_request */ protected function parseRequest($request_data) { if(is_string($request_data)) { parse_str($request_data, $request_data); } $this->_request_qry = (array) $request_data; // Set current mode if(isset($this->_request_qry[$this->_mode_key])) { $this->_mode = preg_replace('/[\W]/', '', $this->_request_qry[$this->_mode_key]); } // Set current action if(isset($this->_request_qry[$this->_action_key])) { $this->_action = preg_replace('/[\W]/', '', $this->_request_qry[$this->_action_key]); } // Set current id if(isset($this->_request_qry[$this->_id_key])) { $this->_id = preg_replace('/[^\w\-:\.]/', '', $this->_request_qry[$this->_id_key]); } $this->_posted_qry =& $_POST; //raw? return $this; } /** * Retrieve variable from GET scope * If $key is null, all GET data will be returned * * @param string $key [optional] * @param mixed $default [optional] * @return mixed */ public function getQuery($key = null, $default = null) { if($key === null) { return $this->_request_qry; } return (isset($this->_request_qry[$key]) ? $this->_request_qry[$key] : $default); } /** * Set/Unset GET variable * If $key is array, $value is not used. * If $value is null, (string) $key is unset * * @param string|array $key * @param mixed $value [optional] * @return e_admin_request */ public function setQuery($key, $value = null) { if(is_array($key)) { foreach ($key as $k=>$v) { $this->setQuery($k, $v); } return $this; } if($value === null) { unset($this->_request_qry[$key], $_GET[$key]); return $this; } $this->_request_qry[$key] = $value; $_GET[$key] = $value; return $this; } /** * Retrieve variable from POST scope * If $key is null, all POST data will be returned * * @param string $key [optional] * @param mixed $default [optional] * @return mixed */ public function getPosted($key = null, $default = null) { if($key === null) { return $this->_posted_qry; } return (isset($this->_posted_qry[$key]) ? $this->_posted_qry[$key] : $default); } /** * Set/Unset POST variable * If $key is array, $value is not used. * If $value is null, (string) $key is unset * * @param string|array $key * @param object $value [optional] * @return e_admin_request */ public function setPosted($key, $value = null) { if(is_array($key)) { if(empty($key)) { $this->_posted_qry = array(); //POST reset return $this; } foreach ($key as $k=>$v) { $this->setPosted($k, $v); } return $this; } if($value === null) { unset($this->_posted_qry[$key]); return $this; } $tp = e107::getParser(); $this->_posted_qry[$tp->post_toForm($key)] = $tp->post_toForm($value); return $this; } /** * Get current mode * @return string */ public function getMode() { if(!$this->_mode) { return $this->getDefaultMode(); } return $this->_mode; } /** * Get default mode * @return string */ public function getDefaultMode() { return $this->_default_mode; } /** * Get current mode name * * @return string */ public function getModeName() { return strtolower(str_replace('-', '_', $this->getMode())); } /** * Reset current mode * @param string $mode * @return e_admin_request */ public function setMode($mode) { $this->_mode = preg_replace('/[\W]/', '', $mode); $this->setQuery($this->_mode_key, $this->_mode); return $this; } /** * Set default mode * @param string $mode * @return e_admin_request */ public function setDefaultMode($mode) { if($mode) { $this->_default_mode = $mode; } return $this; } /** * Set mode key name * @param string $key * @return e_admin_request */ public function setModeKey($key) { $this->_mode_key = $key; return $this; } /** * Get current action * @return string */ public function getAction() { if(!$this->_action) { return $this->getDefaultAction(); } return $this->_action; } /** * Get default action * @return string */ public function getDefaultAction() { return $this->_default_action; } /** * Get current action name * @return string camelized action */ public function getActionName() { return $this->camelize($this->getAction()); } /** * Reset current action * * @param string $action * @return e_admin_request */ public function setAction($action) { $this->_action = preg_replace('/[\W]/', '', $action); $this->setQuery($this->_action_key, $this->_action); return $this; } /** * Set default action * * @param string $action * @return e_admin_request */ public function setDefaultAction($action) { if($action) { $this->_default_action = $action; } return $this; } /** * Set action key name * @param string $key * @return e_admin_request */ public function setActionKey($key) { $this->_action_key = $key; return $this; } /** * Get current ID * @return integer */ public function getId() { return $this->_id; } /** * Reset current ID * @param string $id * @return e_admin_request */ public function setId($id) { $id = (int) $id; $this->_id = $id; $this->setQuery($this->_id_key, $id); return $this; } /** * Set id key name * @param string $key * @return e_admin_request */ public function setIdKey($key) { $this->_id_key = $key; return $this; } /** * Build query string from current request array * NOTE: changing url separator to & ($encode==true) (thus URL XHTML compliance) works in PHP 5.1.2+ environment * * @param string|array $merge_with [optional] override request values * @param boolean $encode if true & separator will be used, all values will be http encoded, default true * @param string|array $exclude_from_query numeric array/comma separated list of vars to be excluded from current query, true - don't use current query at all * @param boolean $keepSpecial don't exclude special vars as 'mode' and 'action' * @return string url encoded query string */ public function buildQueryString($merge_with = array(), $encode = true, $exclude_from_query = '', $keepSpecial = true) { $ret = $this->getQuery(); //special case - exclude all current if($exclude_from_query === true) { $exclude_from_query = array_keys($ret); } // to array if(is_string($exclude_from_query)) { $exclude_from_query = array_map('trim', explode(',', $exclude_from_query)); } if($exclude_from_query) { foreach ($exclude_from_query as $var) { if($keepSpecial && $var != $this->_action_key && $var != $this->_mode_key) { unset($ret[$var]); } } } if(is_string($merge_with)) { parse_str($merge_with, $merge_with); } $ret = array_merge($ret, (array) $merge_with); $separator = '&'; if($encode) { $separator = '&'; //$ret = array_map('rawurlencode', $ret); } $ret = http_build_query($ret, 'numeric_', $separator); if(!$encode) { return rawurldecode($ret); } return $ret; } /** * Convert string to CamelCase * * @param string $str * @return string */ public function camelize($str) { return implode('', array_map('ucfirst', explode('-', str_replace('_', '-', $str)))); } } /** * TODO - front response parent, should do all the header.php work */ class e_admin_response { /** * Body segments * * @var array */ protected $_body = array(); /** * Title segments * * @var unknown_type */ protected $_title = array(); /** * e107 meta title * * @var array */ protected $_e_PAGETITLE = array(); /** * e107 meta description * * @var array */ protected $_META_DESCRIPTION = array(); /** * e107 meta keywords * * @var array */ protected $_META_KEYWORDS = array(); /** * Render mods * * @var array */ protected $_render_mod = array(); /** * Meta title segment description * * @var string */ protected $_meta_title_separator = ' - '; /** * Title segment separator * * @var string */ protected $_title_separator = ' » '; /** * Constructor * */ public function __construct() { $this->_render_mod['default'] = 'admin_page'; } /** * Set body segments for a namespace * * @param string $content * @param string $namespace segment namesapce * @return e_admin_response */ public function setBody($content, $namespace = 'default') { $this->_body[$namespace] = $content; return $this; } /** * Append body segment to a namespace * * @param string $content * @param string $namespace segment namesapce * @return e_admin_response */ public function appendBody($content, $namespace = 'default') { if(!isset($this->_body[$namespace])) { $this->_body[$namespace] = array(); } $this->_body[$namespace][] = $content; return $this; } /** * Prepend body segment to a namespace * * @param string $content * @param string $namespace segment namespace * @return e_admin_response */ public function prependBody($content, $namespace = 'default') { if(!isset($this->_body[$namespace])) { $this->_body[$namespace] = array(); } $this->_body[$namespace] = array_merge(array($content), $this->_body[$namespace]); return $this; } /** * Get body segments from a namespace * * @param string $namespace segment namesapce * @param boolean $reset reset segment namespace * @param string|boolean $glue if false return array, else return string * @return string|array */ public function getBody($namespace = 'default', $reset = false, $glue = '') { $content = vartrue($this->_body[$namespace], array()); if($reset) { $this->_body[$namespace] = array(); } if(is_bool($glue)) { return ($glue ? $content : implode('', $content)); } return implode($glue, $content); } /** * Set title segments for a namespace * * @param string $title * @param string $namespace * @return e_admin_response */ public function setTitle($title, $namespace = 'default') { $this->_title[$namespace] = array($title); return $this; } /** * Append title segment to a namespace * * @param string $title * @param string $namespace segment namesapce * @return e_admin_response */ public function appendTitle($title, $namespace = 'default') { if(empty($title)) { return $this; } if(!isset($this->_title[$namespace])) { $this->_title[$namespace] = array(); } $this->_title[$namespace][] = $title; return $this; } /** * Prepend title segment to a namespace * * @param string $title * @param string $namespace segment namespace * @return e_admin_response */ public function prependTitle($title, $namespace = 'default') { if(empty($title)) { return $this; } if(!isset($this->_title[$namespace])) { $this->_title[$namespace] = array(); } $this->_title[$namespace] = array_merge(array($title), $this->_title[$namespace]); return $this; } /** * Get title segments from namespace * * @param string $namespace * @param boolean $reset * @param boolean|string $glue * @return string|array */ public function getTitle($namespace = 'default', $reset = false, $glue = ' ') { $content = array(); if(isset($this->_title[$namespace]) && is_array($this->_title[$namespace])) { $content = $this->_title[$namespace]; } if($reset) { unset($this->_title[$namespace]); } if(is_bool($glue) || empty($glue)) { return ($glue ? $content : implode($this->_title_separator, $content)); } $glue = deftrue('SEP',' - '); // Defined by admin theme. // admin-ui used only by bootstrap. return implode($glue, $content); // return $head. implode($glue, $content).$foot; } /** * Set render mode for a namespace * * @param string $render_mod * @param string $namespace * @return e_admin_response */ public function setRenderMod($render_mod, $namespace = 'default') { $this->_render_mod[$namespace] = $render_mod; return $this; } /** * Set render mode for namespace * * @param string $namespace * @return string */ public function getRenderMod($namespace = 'default') { return varset($this->_render_mod[$namespace], null); } /** * Add meta title, description and keywords segments * * @param string $meta property name * @param string $content meta content * @return e_admin_response */ public function addMetaData($meta, $content) { $tp = e107::getParser(); $meta = '_' . $meta; if(isset($this->{$meta}) && !empty($content)) { $this->{$meta}[] = strip_tags($content); } return $this; } /** * Add meta title segment * * @param string $title * @return e_admin_response */ public function addMetaTitle($title) { $this->addMetaData('e_PAGETITLE', $title); return $this; } /** * Set the Meta-title (overrides existing) * @param string $title * @return e_admin_response */ public function setMetaTitle($title) { $meta = '_e_PAGETITLE'; $this->{$meta} = array(strip_tags($title)); return $this; } /** * Add meta description segment * * @param string $description * @return e_admin_response */ public function addMetaDescription($description) { $this->addMetaData('META_DESCRIPTION', $description); return $this; } /** * Add meta keywords segment * * @param string $keyword * @return e_admin_response */ public function addMetaKeywords($keyword) { $this->addMetaData('META_KEYWORDS', $keyword); return $this; } /** * Send e107 meta-data * * @return e_admin_response */ public function sendMeta() { //HEADERF already included or meta content already sent if(e_AJAX_REQUEST || defined('HEADER_INIT') || defined('e_PAGETITLE')) { return $this; } if(!defined('e_PAGETITLE') && !empty($this->_e_PAGETITLE)) { define('e_PAGETITLE', implode($this->_meta_title_separator, $this->_e_PAGETITLE)); } if(!defined('META_DESCRIPTION') && !empty($this->_META_DESCRIPTION)) { define('META_DESCRIPTION', implode(' ', $this->_META_DESCRIPTION)); } if(!defined('META_KEYWORDS') && !empty($this->_META_KEYWORDS)) { define('META_KEYWORDS', implode(', ', $this->_META_KEYWORDS)); } return $this; } /** * Add content segment to the header namespace * * @param string $content * @return e_admin_response */ public function addHeaderContent($content) { $this->appendBody($content, 'header_content'); return $this; } /** * Get page header namespace content segments * * @param boolean $reset * @param boolean $glue * @return string */ public function getHeaderContent($reset = true, $glue = "\n\n") { return $this->getBody('header_content', $reset, $glue); } /** * Switch to iframe mod * FIXME - implement e_IFRAME to frontend - header_default.php * * @return e_admin_response */ public function setIframeMod() { global $HEADER, $FOOTER, $CUSTOMHEADER, $CUSTOMFOOTER; $FOOTER = ''; $HEADER = $FOOTER; $CUSTOMFOOTER = array(); $CUSTOMHEADER = $CUSTOMFOOTER; //TODO generic $_GET to activate for any page of admin. // New if(!defined('e_IFRAME')) { define('e_IFRAME', true); } return $this; } /** * Send Response Output * * @param string $name segment * @param array $options valid keys are: messages|render|meta|return|raw|ajax * @return array|string|null */ public function send($name = 'default', $options = array()) { if(is_string($options)) { parse_str($options, $options); } // Merge with all available default options $options = array_merge(array( 'messages' => true, 'render' => true, 'meta' => false, 'return' => false, 'raw' => false, 'ajax' => false ), $options); $content = $this->getBody($name, true); $title = $this->getTitle($name, true); $return = $options['return']; if($options['ajax'] || e_AJAX_REQUEST) { $type = $options['ajax'] && is_string($options['ajax']) ? $options['ajax'] : ''; $this->getJsHelper()->sendResponse($type); } if($options['messages']) { $content = e107::getMessage()->render().$content; } if($options['meta']) { $this->sendMeta(); } // raw output expected - force return array if($options['raw']) { return array($title, $content, $this->getRenderMod($name)); } //render disabled by the controller if(!$this->getRenderMod($name)) { $options['render'] = false; } if($options['render']) { return e107::getRender()->tablerender($title, $content, $this->getRenderMod($name), $return); } if($return) { return $content; } print($content); return ''; } /** * Get JS Helper instance * * @return e_jshelper */ public function getJsHelper() { return e107::getSingleton('e_jshelper', true, 'admin_response'); } } /** * TODO - request related code should be moved to core * request handler */ class e_admin_dispatcher { /** * @var e_admin_request */ protected $_request; /** * @var e_admin_response */ protected $_response; /** * @var e_admin_controller */ protected $_current_controller; /** * Required (set by child class). * Controller map array in format * 'MODE' => array('controller' =>'CONTROLLER_CLASS_NAME'[, 'path' => 'CONTROLLER SCRIPT PATH', 'ui' => extend of 'comments_admin_form_ui', 'uipath' => 'path/to/ui/']); * * @var array */ protected $modes = array(); /** * Optional - access restrictions per action * Access array in format (similar to adminMenu) * 'MODE/ACTION' => e_UC_* (userclass constant, or custom userclass ID if dynamically set) * * @var array */ protected $access = array(); /** * Optional - generic entry point access restriction (via getperms()) * Value of this for plugins would be always 'P'. * When an array is detected, route mode/action = admin perms is used. (similar to $access) * More detailed access control is granted with $access and $modes[MODE]['perm'] or $modes[MODE]['userclass'] settings * * @var string|array */ protected $perm; /** * @var string */ protected $defaultMode = ''; /** * @var string */ protected $defaultAction = ''; /** * Optional - map 'mode/action' pair to 'modeAlias/actionAlias' * @var string */ protected $adminMenuAliases = array(); /** * Optional (set by child class). * Required for admin menu render * Format: 'mode/action' => array('caption' => 'Link title'[, 'perm' => '0', 'url' => '{e_PLUGIN}plugname/admin_config.php'], ...); * Note that 'perm' and 'userclass' restrictions are inherited from the $modes, $access and $perm, so you don't have to set that vars if * you don't need any additional 'visual' control. * All valid key-value pair (see e107::getNav()->admin function) are accepted. * @var array */ protected $adminMenu = array(); protected $adminMenuIcon; /** * Optional (set by child class). * Page titles for pages not in adminMenu (e.g. main/edit) * Format array(mod/action => Page Title) * @var string */ protected $pageTitles = array( 'main/edit' => LAN_MANAGE, ); /** * Optional (set by child class). * @var string */ protected $menuTitle = 'Menu'; /** * @var string */ protected $pluginTitle = ''; /** * Constructor * * @param string|array|e_admin_request $request [optional] * @param e_admin_response $response */ public function __construct($auto_observe = true, $request = null, $response = null) { // we let know some admin routines we are in UI mod - related with some legacy checks and fixes if(!defined('e_ADMIN_UI')) { define('e_ADMIN_UI', true); } if(!empty($_GET['iframe']) && !defined('e_IFRAME')) { define('e_IFRAME', true); } require_once(e_ADMIN.'boot.php'); if($request === null || !is_object($request)) { $request = new e_admin_request($request); } if($response === null) { $response = new e_admin_response(); } $this->setRequest($request)->setResponse($response)->init(); if(!$this->defaultMode || !$this->defaultAction) { $this->setDefaults(); } // current user does not have access to default route, so find a new one. if(!$hasAccess = $this->hasRouteAccess($this->defaultMode.'/'.$this->defaultAction)) { if($newRoute = $this->getApprovedAccessRoute()) { list($this->defaultMode,$this->defaultAction) = explode('/',$newRoute); } } $request->setDefaultMode($this->defaultMode)->setDefaultAction($this->defaultAction); // register itself e107::setRegistry('admin/ui/dispatcher', $this); // permissions and restrictions $this->checkAccess(); if($auto_observe) { $this->runObservers(); } } /** * User defined constructor - called before _initController() method * @return void */ public function init() { } /** * @return bool */ public function checkAccess() { $request = $this->getRequest(); $currentMode = $request->getMode(); // access based on mode setting - general controller access if(!$this->hasModeAccess($currentMode)) { $request->setAction('e403'); e107::getMessage()->addError(LAN_NO_PERMISSIONS) ->addDebug('Mode access restriction triggered.'); return false; } // access based on $access settings - access per action $currentAction = $request->getAction(); $route = $currentMode.'/'.$currentAction; if(!$this->hasRouteAccess($route)) { $request->setAction('e403'); e107::getMessage()->addError(LAN_NO_PERMISSIONS) ->addDebug('Route access restriction triggered:'.$route); return false; } return true; } /** * @param $mode * @return bool */ public function hasModeAccess($mode) { // mode userclass (former check_class()) if(isset($this->modes[$mode]['userclass']) && !e107::getUser()->checkClass($this->modes[$mode]['userclass'], false)) { return false; } // mode admin permission (former getperms()) if(isset($this->modes[$mode]['perm']) && !e107::getUser()->checkAdminPerms($this->modes[$mode]['perm'])) { return false; } // generic dispatcher admin permission (former getperms()) if($this->perm !== null && is_string($this->perm) && !e107::getUser()->checkAdminPerms($this->perm)) { return false; } return true; } /** * @param $route * @return bool */ public function hasRouteAccess($route) { if(empty($route)) { return true; } if(isset($this->access[$route]) && !check_class($this->access[$route])) { e107::getMessage()->addDebug('Userclass Permissions Failed: ' .$this->access[$route]); return false; } if(is_array($this->perm) && isset($this->perm[$route]) && !getperms($this->perm[$route])) { e107::getMessage()->addDebug('Admin Permissions Failed.' .$this->perm[$route]); return false; } return true; } /** * Retrieve missing default action/mode * @return e_admin_dispatcher */ public function setDefaults() { // try Admin menu first if($this->adminMenu) { reset($this->adminMenu); list($mode, $action) = explode('/', key($this->adminMenu), 3); } else { reset($this->modes); $mode = key($this->modes); $action = $this->modes[$mode]['index']; } if(!$this->defaultMode) { $this->defaultMode = $mode; } if(!$this->defaultAction) { $this->defaultAction = $action; } return $this; } /** * Search through access for an approved route. * Returns false if no approved route found. * * @return string|bool */ private function getApprovedAccessRoute() { if(empty($this->access)) { return false; } foreach($this->access as $route=>$uclass) { if(check_class($uclass)) { return $route; } } return false; } /** * Get admin menu array * @return array */ public function getMenuData() { return $this->adminMenu; } /** * Sets the administrative menu data. * * @param array $menu The menu data to set. * @return $this */ public function setMenuData($menu) { $this->adminMenu = $menu; return $this; } /** * Get admin menu array * @return array */ public function getPageTitles() { return $this->pageTitles; } /** * Get admin menu array * @return array */ public function getMenuAliases() { return $this->adminMenuAliases; } /** * Retrieves the menu icon associated with the admin panel. * * @return string The menu icon. */ public function getMenuIcon() { return $this->adminMenuIcon; } /** * Retrieves the menu title. * * @return string The title of the menu. */ public function getMenuTitle() { return defset($this->menuTitle,$this->menuTitle); } /** * Get request object * @return e_admin_request */ public function getRequest() { return $this->_request; } /** * Set request object * @param e_admin_request $request * @return e_admin_dispatcher */ public function setRequest($request) { $this->_request = $request; return $this; } /** * Get response object * @return e_admin_response */ public function getResponse() { return $this->_response; } /** * Set response object * @param e_admin_response $response * @return e_admin_dispatcher */ public function setResponse($response) { $this->_response = $response; return $this; } /** * Dispatch & render all * * @param boolean $run_header see runObservers() * @param boolean $return see runPage() * @return string|array current admin page body */ public function run($run_header = true, $return = 'render') { return $this->runObservers()->runPage($return); } /** * Run observers/headers only, should be called before header.php call * * @return e_admin_dispatcher */ public function runObservers($run_header = true) { //search for $actionName.'Observer' method. Additional $actionName.$triggerName.'Trigger' methods will be called as well $this->getController()->dispatchObserver(); //search for $actionName.'Header' method, js manager should be used inside for sending JS to the page, // meta information should be created there as well if($run_header) { $this->getController()->dispatchHeader(); } return $this; } /** * Run page action. * If return type is array, it should contain allowed response options (see e_admin_response::send()) * Available return type string values: * - render_return: return rendered content ( see e107::getRender()->tablerender()), add system messages, send meta information * - render: outputs rendered content ( see e107::getRender()->tablerender()), add system messages * - response: return response object * - raw: return array(title, content, render mode) * - ajax: force ajax output (and exit) * * @param string|array $return_type expected string values: render|render_out|response|raw|ajax[_text|_json|_xml] * @return array|e_admin_response|string|null */ public function runPage($return_type = 'render') { $response = $this->getController()->dispatchPage(); if(is_array($return_type)) { return $response->send('default', $return_type); } switch($return_type) { case 'render_return': $options = array( 'messages' => true, 'render' => true, 'meta' => true, 'return' => true, 'raw' => false ); break; case 'raw': $options = array( 'messages' => false, 'render' => false, 'meta' => false, 'return' => true, 'raw' => true ); break; case 'ajax': case 'ajax_text': case 'ajax_xml'; case 'ajax_json'; $options = array( 'messages' => false, 'render' => false, 'meta' => false, 'return' => false, 'raw' => false, 'ajax' => str_replace(array('ajax_', 'ajax'), array('', 'text'), $return_type) ); break; case 'response': return $response; break; case 'render': default: $options = array( 'messages' => true, 'render' => true, 'meta' => false, 'return' => false, 'raw' => false ); break; } return $response->send('default', $options); } /** * Get perms * @return array|string */ public function getPerm() { return $this->perm; } /** * Proxy method * * @return string */ public function getHeader() { return $this->getController()->getHeader(); } /** * Get current controller object * @return e_admin_controller */ public function getController() { if($this->_current_controller === null) { $this->_initController(); } return $this->_current_controller; } /** * Try to init Controller from request using current controller map * * @return e_admin_dispatcher */ protected function _initController() { $request = $this->getRequest(); $response = $this->getResponse(); if(isset($this->modes[$request->getModeName()]) && isset($this->modes[$request->getModeName()]['controller'])) { $class_name = $this->modes[$request->getModeName()]['controller']; $class_path = vartrue($this->modes[$request->getModeName()]['path']); if($class_path) { require_once(e107::getParser()->replaceConstants($class_path)); } if($class_name && class_exists($class_name))//NOTE: autoload in the play { $this->_current_controller = new $class_name($request, $response); //give access to current request object, user defined init $this->_current_controller->setRequest($this->getRequest())->init(); } // Known controller (found in e_admin_dispatcher::$modes), class not found exception else { // TODO - admin log // get default controller $this->_current_controller = $this->getDefaultController(); // add messages e107::getMessage()->add('Can\'t find class "'.($class_name ? $class_name : 'n/a').'" for controller "'.ucfirst($request->getModeName()).'"', E_MESSAGE_ERROR) ->add('Requested: '.e_REQUEST_SELF.'?'.$request->buildQueryString(), E_MESSAGE_DEBUG); // $request->setMode($this->getDefaultControllerName())->setAction('e404'); $this->_current_controller->setRequest($request)->init(); } if(!empty($this->modes[$request->getModeName()]['ui'])) { $class_name = $this->modes[$request->getModeName()]['ui']; $class_path = vartrue($this->modes[$request->getModeName()]['uipath']); if($class_path) { require_once(e107::getParser()->replaceConstants($class_path)); } if(class_exists($class_name))//NOTE: autoload in the play { $this->_current_controller->setParam('ui', new $class_name($this->_current_controller)); } } $this->_current_controller->setParam('modes', $this->modes); } // Not known controller (not found in e_admin_dispatcher::$modes) exception else { // TODO - admin log $this->_current_controller = $this->getDefaultController(); // add messages e107::getMessage()->add('Can\'t find class for controller "'.ucfirst($request->getModeName()).'"', E_MESSAGE_ERROR) ->add('Requested: '.e_REQUEST_SELF.'?'.$request->buildQueryString(), E_MESSAGE_DEBUG); // go to not found page $request->setMode($this->getDefaultControllerName())->setAction('e404'); $this->_current_controller->setRequest($request)->init(); } return $this; } /** * Default controller object - needed if controller not found * @return e_admin_controller */ public function getDefaultController() { $class_name = $this->getDefaultControllerName(); return new $class_name($this->getRequest(), $this->getResponse()); } /** * Default controller name - needed if controller not found * @return string name of controller */ public function getDefaultControllerName() { return 'e_admin_controller'; } public function hasPerms($perms) { return getperms($perms); } public function setAccess($access) { $this->access = $access; return $this; } /** * Generic Admin Menu Generator * * @return string|array */ public function renderMenu($debug = false) { $tp = e107::getParser(); $adminMenu = $this->getMenuData(); $var = []; $selected = false; // First loop: Build $var without permissions checks foreach($adminMenu as $key => $val) { // $parentKey = ''; $tmp = explode('/', trim($key, '/'), 2); // mode/action $isSubItem = !empty($val['group']); if($isSubItem) { $parentKey = $val['group'] ?? ''; } $processedItem = $this->processMenuItem($val, $key, $tmp); $processedItem['link_id'] = str_replace('/', '-', $key); if(!empty($val['selected'])) // Custom selector. { $selected = $val['selected'] === true ? $key : $val['selected']; } elseif(!empty($processedItem['selected'])) // match detected in 'uri' { $selected = $processedItem['selected']; } if($isSubItem) { if(empty($var[$parentKey])) { $var[$parentKey] = [ 'text' => 'Unknown', 'image_src' => e_navigation::guessMenuIcon($parentKey), 'link_id' => str_replace('/', '-', $parentKey) ]; } // Use full key for sub-items to match $adminMenu $subKey = $key; if(!is_array($var[$parentKey])) { $var[$parentKey] = []; } if(!isset($var[$parentKey]['sub'][$subKey])) { $var[$parentKey]['sub'][$subKey] = $processedItem; } if(!isset($var[$parentKey]['link_class'])) { $var[$parentKey]['link_class'] = ''; } if(!empty($processedItem['badge']) && strpos($var[$parentKey]['link_class'], 'has-badge') === false) { $var[$parentKey]['link_class'] .= ' has-badge'; } } else { if(!isset($var[$key])) { $var[$key] = $processedItem; } } } if(!$selected) { $request = $this->getRequest(); $selected = $request->getMode() . '/' . $request->getAction(); // e107::getMessage()->addDebug('No selected item found, using default: ' . $selected); } // Apply permissions restrictions $var = $this->restrictMenuAccess($var, $adminMenu); // Second loop: Handle links and collapse attributes without permissions checks foreach($var as $key => &$item) { if(!empty($item['sub'])) { $item['link'] = '#'; $item['link_caret'] = true; $item['link_data'] = [ 'data-toggle' => 'collapse', 'data-target' => '#sub-' . $item['link_id'], 'role' => 'button' ]; $item['sub_class'] = 'collapse'; $item['caret'] = true; foreach($item['sub'] as $subKey => &$subItem) { if($selected === $subKey && !empty($subItem['group'])) { $parent = $subItem['group']; $var[$parent]['link_data']['aria-expanded'] = 'true'; $item['sub_class'] = 'collapse in'; } } } elseif(!isset($item['link'])) { $tmp = explode('/', trim($key, '/'), 2); $item['link'] = e_REQUEST_SELF . '?mode=' . $tmp[0] . '&action=' . ($tmp[1] ?? 'main'); } } if(empty($var)) { return ''; } $adminMenuAliases = $this->getMenuAliases(); $selected = vartrue($adminMenuAliases[$selected], $selected); $icon = ''; $adminMenuIcon = $this->getMenuIcon(); if(!empty($adminMenuIcon)) { $icon = e107::getParser()->toIcon($adminMenuIcon); } elseif(deftrue('e_CURRENT_PLUGIN')) { $icon = e107::getPlug()->load(e_CURRENT_PLUGIN)->getIcon(24); } $toggle = ""; $var['_extras_'] = ['icon' => $icon, 'return' => true]; if($debug) { return $var; } return $toggle . e107::getNav()->admin($this->getMenuTitle(), $selected, $var); } /** * Restrict menu items based on permissions and access. * * @param array $var The menu structure to filter. * @param array $adminMenu The original menu data for reference. * @return array The filtered menu structure. */ private function restrictMenuAccess($var, $adminMenu) { foreach($var as $key => &$item) { // Check top-level item permissions $val = $adminMenu[$key] ?? []; // Handle single-segment keys (e.g., 'treatment') by using the key as the mode $mode = strpos($key, '/') !== false ? explode('/', trim($key, '/'), 2)[0] : $key; // Default to true for hasPerms if perm is unset or empty $hasPerms = isset($val['perm']) && $val['perm'] !== '' ? $this->hasPerms($val['perm']) : true; if(!$hasPerms || !$this->hasModeAccess($mode) || !$this->hasRouteAccess($key)) { unset($var[$key]); continue; } if(!empty($item['sub'])) { $hasValidSubItems = false; $parentKey = $key; foreach($item['sub'] as $subKey => &$subItem) { $subVal = $adminMenu[$subKey] ?? []; // Log permissions check for sub-item only when removed if(!isset($subVal['group']) || $subVal['group'] !== $parentKey) { unset($item['sub'][$subKey]); // fwrite(STDOUT, "B. restrictMenuAccess: removing subKey=$subKey, parent=$parentKey, group=" . ($subVal['group'] ?? 'none') . ", perm=" . ($subVal['perm'] ?? 'none') . ", hasPerms=" . var_export(isset($subVal['perm']) && $subVal['perm'] !== '' ? $this->hasPerms($subVal['perm']) : true, true) . ", hasModeAccess=" . var_export($this->hasModeAccess($subMode ?? $subKey), true) . ", hasRouteAccess=" . var_export($this->hasRouteAccess($subKey), true) . ", parentRouteAccess=" . var_export($this->hasRouteAccess($parentKey), true) . "\n"); continue; } $subMode = strpos($subKey, '/') !== false ? explode('/', trim($subKey, '/'), 2)[0] : $subKey; // Default to true for hasPerms if perm is unset or empty $hasPerms = isset($subVal['perm']) && $subVal['perm'] !== '' ? $this->hasPerms($subVal['perm']) : true; if($hasPerms && $this->hasModeAccess($subMode) && $this->hasRouteAccess($subKey) && $this->hasRouteAccess($parentKey)) { $hasValidSubItems = true; } else { unset($item['sub'][$subKey]); } } // Remove parent if no valid sub-items or sub-items array is empty if(!$hasValidSubItems || empty($item['sub'])) { unset($var[$key]); } } } return $var; } /** * @param $val * @param $key * @param $tmp * @return array */ private function processMenuItem($val, $key, $tmp) { $tp = e107::getParser(); $item = array(); foreach($val as $k => $v) { switch($k) { case 'caption': $k2 = 'text'; $v = defset($v, $v); break; case 'url': $k2 = 'link'; $qry = (isset($val['query'])) ? $val['query'] : '?mode=' . $tmp[0] . '&action=' . ($tmp[1] ?? 'main') . (isset($tmp[2]) ? '&sub=' . $tmp[2] : ''); $v = $tp->replaceConstants($v, 'abs') . $qry; break; case 'uri': $k2 = 'link'; $v = $tp->replaceConstants($v, 'abs'); if($this->compareUris($v,e_REQUEST_URL)) { $item['selected'] = $key; } break; case 'badge': $k2 = 'badge'; $v = (array) $v; break; case 'icon': $k2 = 'image_src'; $v = (string) $v . '.glyph'; // required, even if empty. break; default: $k2 = $k; break; } $item[$k2] = $v; } if(!isset($item['image_src'])) { $item['image_src'] = e_navigation::guessMenuIcon($key); } if(!empty($item['group'])) // empty icon for group items. { $item['image_src'] = '.glyph'; } if(!vartrue($item['link'])) { $item['link'] = e_REQUEST_SELF . '?mode=' . $tmp[0] . '&action=' . ($tmp[1] ?? 'main') . (isset($tmp[2]) ? '&sub=' . $tmp[2] : ''); } if(varset($val['tab'])) { $item['link'] .= '&tab=' . $val['tab']; } if(!empty($val['modal'])) { $item['link_class'] = ' e-modal'; if(!empty($val['modal-caption'])) { $item['link_data'] = array_merge($item['link_data'] ?? [], ['data-modal-caption' => $val['modal-caption']]); } } if(!empty($val['class'])) { $item['link_class'] = ($item['link_class'] ?? '') . ' ' . $val['class']; } return $item; } /** * Compares two URIs after normalizing their paths and query parameters. * Specific query parameters ('asc', 'from', 'field') are excluded from comparison. * * @param string $uri1 The first URI to compare. * @param string $uri2 The second URI to compare. * @return bool Returns true if the normalized URIs are equivalent; otherwise, false. */ private function compareUris($uri1, $uri2) { if(empty($uri1) || empty($uri2)) { return false; } $parsedUri1 = parse_url($uri1); $parsedUri2 = parse_url($uri2); $path1 = $parsedUri1['path'] ?? ''; $path2 = $parsedUri2['path'] ?? ''; $query1 = []; $query2 = []; if(isset($parsedUri1['query'])) { parse_str($parsedUri1['query'], $query1); } if(isset($parsedUri2['query'])) { parse_str($parsedUri2['query'], $query2); } // Remove asc, from and field from queries foreach(['asc', 'from', 'field'] as $param) { unset($query1[$param], $query2[$param]); } $cleanQuery1 = http_build_query($query1); $cleanQuery2 = http_build_query($query2); $cleanUri1 = $path1 . ($cleanQuery1 ? '?' . $cleanQuery1 : ''); $cleanUri2 = $path2 . ($cleanQuery2 ? '?' . $cleanQuery2 : ''); return $cleanUri1 === $cleanUri2 ; } /** * Render Help Text in
' . $jsonDebugInfo . ''); } return $qry; } /** * Checks whether the field array should be searched oor not. * @param array $var * @param string $searchQuery * @return bool */ private function _isSearchField($field, $searchQuery): bool { $searchable_types = array('text', 'textarea', 'bbarea', 'url', 'ip', 'tags', 'email', 'int', 'integer', 'str', 'safestr', 'string', 'number'); //method? 'user', if($field['type'] === 'method' && !empty($field['data']) && ($field['data'] === 'string' || $field['data'] === 'str' || $field['data'] === 'safestr' || $field['data'] === 'int')) { $searchable_types[] = 'method'; } return !empty($field['__tableField']) && trim($searchQuery) !== '' && in_array($field['type'], $searchable_types); } } /** * */ class e_admin_ui extends e_admin_controller_ui { protected $fieldTypes = array(); protected $dataFields = array(); protected $fieldInputTypes = array(); protected $validationRules = array(); protected $table; protected $pid; protected $listQry; protected $editQry; protected $sortField; protected $sortParent; protected $orderStep; protected $treePrefix; /** * Markup to be auto-inserted before List filter * @var string */ public $preFilterMarkup = ''; /** * Markup to be auto-inserted after List filter * @var string */ public $postFilterMarkup = ''; /** * Markup to be auto-inserted at the top of Create form * @var string */ public $headerCreateMarkup = ''; /** * Markup to be auto-inserted at the bottom of Create form * @var string */ public $footerCreateMarkup = ''; /** * Markup to be auto-inserted at the top of Update form * @var string */ public $headerUpdateMarkup = ''; /** * Markup to be auto-inserted at the bottom of Update form * @var string */ public $footerUpdateMarkup = ''; /** * Show confirm screen before (batch/single) delete * @var boolean */ public $deleteConfirmScreen = false; /** * Confirm screen custom message * @var string */ public $deleteConfirmMessage; /** * Constructor * @param e_admin_request $request * @param e_admin_response $response * @param array $params [optional] */ public function __construct($request, $response, $params = array()) { $this->setDefaultAction($request->getDefaultAction()); $params['enable_triggers'] = true; // override parent::__construct($request, $response, $params); if(!$this->pluginName) { $this->pluginName = 'core'; } /* $ufieldpref = $this->getUserPref(); if($ufieldpref) { $this->fieldpref = $ufieldpref; }*/ $pluginTitle = $this->getPluginTitle(); $this->addTitle($pluginTitle)->parseAliases(); $this->initAdminAddons(); if($help = $this->renderHelp()) { if(!empty($help)) { e107::setRegistry('core/e107/adminui/help',$help); } } } /** * @return void */ private function initAdminAddons() { $tmp = e107::getAddonConfig('e_admin', null, 'config', $this); if(empty($tmp)) { return; } $opts = null; foreach($tmp as $plug=>$config) { $form = e107::getAddon($plug, 'e_admin', $plug. '_admin_form'); // class | false. if(!empty($config['fields'])) { if(!empty($this->fields['options'])) { $opts = $this->fields['options']; unset($this->fields['options']); } foreach($config['fields'] as $k=>$v) { $v['data'] = false; // disable data-saving to db table. . $fieldName = 'x_'.$plug.'_'.$k; e107::getDebug()->log($fieldName. ' initiated by ' .$plug); if($v['type'] === 'method' && method_exists($form,$fieldName)) { $v['method'] = $plug. '_admin_form::' .$fieldName; //echo "Found method ".$fieldName." in ".$plug."_menu_form"; //echo $form->$fieldName(); } $this->fields[$fieldName] = $v; // ie. x_plugin_key } if(!empty($opts)) // move options field to the end. { $this->fields['options'] = $opts; } } if(!empty($config['batchOptions'])) { $opts = array(); foreach($config['batchOptions'] as $k=>$v) { $fieldName = 'batch_'.$plug.'_'.$k; $opts[$fieldName] = $v; // ie. x_plugin_key } $batchCat = deftrue('LAN_PLUGIN_'.strtoupper($plug).'_NAME', $plug); $this->batchOptions[$batchCat] = $opts; } if(!empty($config['tabs'])) { foreach($config['tabs'] as $t=>$tb) { $this->tabs[$t] = $tb; } } } } /** * Catch fieldpref submit * @return void */ public function ListEcolumnsTrigger() { $this->setTriggersEnabled(false); //disable further triggering parent::manageColumns(); } /** * Detect if a batch function has been fired. * @param $batchKey * @return bool */ public function batchTriggered($batchKey) { return (!empty($_POST['e__execute_batch']) && (varset($_POST['etrigger_batch']) == $batchKey)); } /** * Catch batch submit * @param string $batch_trigger * @return null */ public function ListBatchTrigger($batch_trigger) { $this->setPosted('etrigger_batch'); if($this->getPosted('etrigger_cancel')) { $this->setPosted(array()); return; // always break on cancel! } $this->deleteConfirmScreen = true; // Confirm screen ALWAYS enabled when multi-deleting! // proceed ONLY if there is no other trigger, except delete confirmation if($batch_trigger && !$this->hasTrigger(array('etrigger_delete_confirm'))) { $this->_handleListBatch($batch_trigger); } } /** * Catch batch submit * @param string $batch_trigger * @return null */ public function GridBatchTrigger($batch_trigger) { $this->setPosted('etrigger_batch'); if($this->getPosted('etrigger_cancel')) { $this->setPosted(array()); return; // always break on cancel! } $this->deleteConfirmScreen = true; // Confirm screen ALWAYS enabled when multi-deleting! // proceed ONLY if there is no other trigger, except delete confirmation if($batch_trigger && !$this->hasTrigger(array('etrigger_delete_confirm'))) { $this->_handleListBatch($batch_trigger); } } /** * Batch delete trigger * @param array $selected * @return void */ protected function handleListDeleteBatch($selected) { $tp = e107::getParser(); if(!$this->getBatchDelete()) { e107::getMessage()->add(LAN_UI_BATCHDEL_ERROR, E_MESSAGE_WARNING); return; } if($this->deleteConfirmScreen) { if(!$this->getPosted('etrigger_delete_confirm')) { // ListPage will show up confirmation screen $this->setPosted('delete_confirm_value', implode(',', $selected)); return; } else { // already confirmed, resurrect selected values $selected = explode(',', $this->getPosted('delete_confirm_value')); foreach ($selected as $i => $_sel) { $selected[$i] = preg_replace('/[^\w\-:.]/', '', $_sel); } } } // delete one by one - more control, less performance // pass afterDelete() callback to tree delete method $set_messages = true; $delcount = 0; $nfcount = 0; foreach ($selected as $id) { $data = array(); $model = $this->getTreeModel()->getNode($id); if($model) { $data = $model->getData(); if($this->table !== 'admin_history') { $this->backupToHistory($this->table, $this->pid, $id, 'delete', $data); } if($this->beforeDelete($data, $id)) { $check = $this->getTreeModel()->delete($id); if($check) { $delcount++; } if(!$this->afterDelete($data, $id, $check)) { $set_messages = false; } } } else { $set_messages = true; $nfcount++; } } //$this->getTreeModel()->delete($selected); if($set_messages) { $this->getTreeModel()->setMessages(); // FIXME lan if($delcount) { e107::getMessage()->addSuccess($tp->lanVars(LAN_UI_DELETED, $delcount, true)); } if($nfcount) { e107::getMessage()->addError($tp->lanVars(LAN_UI_DELETED_FAILED, $nfcount, true)); } } //$this->redirect(); } /** * Batch copy trigger * @param array $selected * @return void */ protected function handleListCopyBatch($selected) { // Batch Copy $res = $this->getTreeModel()->copy($selected); // callback $this->afterCopy($res, $selected); // move messages to default stack $this->getTreeModel()->setMessages(); // send messages to session e107::getMessage()->moveToSession(); // redirect $this->redirect(); } /** * Batch Export trigger * @param array $selected * @return void */ protected function handleListExportBatch($selected) { // Batch Copy $res = $this->getTreeModel()->export($selected); // callback // $this->afterCopy($res, $selected); // move messages to default stack $this->getTreeModel()->setMessages(); // send messages to session e107::getMessage()->moveToSession(); // redirect $this->redirect(); } /** * Batch Export trigger * @param array $selected * @return void */ protected function handleListSefgenBatch($selected, $field, $value) { $tree = $this->getTreeModel(); $c= 0; foreach($selected as $id) { if(!$tree->hasNode($id)) { e107::getMessage()->addError('Item #ID '.htmlspecialchars($id).' not found.'); continue; } $model = $tree->getNode($id); $name = $model->get($value); $sef = eHelper::title2sef($name,'dashl'); $model->set($field, $sef); $model->save(); $data = $model->getData(); if($model->isModified()) { $this->getModel()->setData($data)->save(false,true); $c++; } } $caption = e107::getParser()->lanVars(LAN_UI_BATCH_BOOL_SUCCESS, $c, true); e107::getMessage()->addSuccess($caption); // e107::getMessage()->moveToSession(); // redirect // $this->redirect(); } /** * Batch URL trigger * @param array $selected * @return void */ protected function handleListUrlBatch($selected) { if($this->_add2nav($selected)) { e107::getMessage()->moveToSession(); $this->redirect(); } } /** TODO * Batch Featurebox Transfer * @param array $selected * @return void */ protected function handleListFeatureboxBatch($selected) { if($this->_add2featurebox($selected)) { e107::getMessage()->moveToSession(); $this->redirect(); } } /** * @param $selected * @return false|int */ protected function _add2nav($selected) { if(empty($selected)) { return false; }// TODO warning message if(!is_array($selected)) { $selected = array($selected); } $sql = e107::getDb(); $urlData = $this->getUrl(); $allData = $this->getTreeModel()->url($selected, array('sc' => true), true); e107::getMessage()->addDebug('Using Url Route:'.$urlData['route']); $scount = 0; foreach($allData as $id => $data) { $name = $data['name']; $desc = $data['description']; $link = $data['url']; $link = str_replace('{e_BASE}', '', $link); // TODO temporary here, discuss // _FIELD_TYPES auto created inside mysql handler now $linkArray = array( 'link_name' => $name, 'link_url' => $link, 'link_description' => e107::getParser()->toDB($desc), // retrieved field type is string, we might need todb here 'link_button' => '', 'link_category' => 255, // Using an unassigned template rather than inactive link-class, since other inactive links may already exist. 'link_order' => 0, 'link_parent' => 0, 'link_open' => '', 'link_class' => 0, 'link_sefurl' => e107::getParser()->toDB($urlData['route'].'?'.$id), ); $res = $sql->insert('links', $linkArray); if($res !== FALSE) { e107::getMessage()->addSuccess(LAN_CREATED. ': ' .LAN_NAVIGATION. ': ' .($name ? $name : 'n/a')); $scount++; } else { if($sql->getLastErrorNumber()) { e107::getMessage()->addError(LAN_CREATED_FAILED. ': ' .LAN_NAVIGATION. ': ' .$name. ': ' .LAN_SQL_ERROR); e107::getMessage()->addDebug('SQL Link Creation Error #'.$sql->getLastErrorNumber().': '.$sql->getLastErrorText()); } else { e107::getMessage()->addError(LAN_CREATED_FAILED. ': ' .LAN_NAVIGATION. ': ' .$name. ': ' .LAN_UNKNOWN_ERROR);//Unknown Error } } } if($scount > 0) { e107::getMessage()->addSuccess(LAN_CREATED. ' (' .$scount. ') ' .LAN_NAVIGATION_LINKS); e107::getMessage()->addSuccess("".LAN_CONFIGURE. ' ' .LAN_NAVIGATION. ''); return $scount; } return false; } /** * @param $selected * @return false|int */ protected function _add2featurebox($selected) { // FIX - don't allow if plugin not installed if(!e107::isInstalled('featurebox')) { return false; } if(empty($selected)) { return false; }// TODO warning message if(!is_array($selected)) { $selected = array($selected); } $sql = e107::getDb(); $tree = $this->getTreeModel(); $urlData = $this->getTreeModel()->url($selected, array('sc' => true)); $data = $this->featurebox; $scount = 0; $category = 0; foreach($selected as $id) { if(!$tree->hasNode($id)) { e107::getMessage()->addError('Item #ID '.htmlspecialchars($id).' not found.'); continue; // TODO message } $model = $tree->getNode($id); if($data['url'] === true) { $url = $urlData[$id]; } else { $url = $model->get($data['url']); } $name = $model->get($data['name']); $category = e107::getDb()->retrieve('featurebox_category', 'fb_category_id', "fb_category_template='unassigned'"); $fbArray = array ( 'fb_title' => $name, 'fb_text' => $model->get($data['description']), 'fb_image' => vartrue($data['image']) ? $model->get($data['image']) : '', 'fb_imageurl' => $url, 'fb_class' => isset($data['visibility']) && $data['visibility'] !== false ? $model->get($data['visibility']) : e_UC_ADMIN, 'fb_template' => 'default', 'fb_category' => $category, // TODO popup - choose category 'fb_order' => $scount, ); $res = $sql->insert('featurebox', $fbArray); if($res !== FALSE) { e107::getMessage()->addSuccess(LAN_CREATED. ': ' .LAN_PLUGIN_FEATUREBOX_NAME. ': ' .($name ? $name : 'n/a')); $scount++; } else { if($sql->getLastErrorNumber()) { e107::getMessage()->addError(LAN_CREATED_FAILED. ': ' .LAN_PLUGIN_FEATUREBOX_NAME. ': ' .$name. ': ' .LAN_SQL_ERROR); e107::getMessage()->addDebug('SQL Featurebox Creation Error #'.$sql->getLastErrorNumber().': '.$sql->getLastErrorText()); } else { e107::getMessage()->addError(LAN_CREATED_FAILED. ': ' .$name. ': ' .LAN_UNKNOWN_ERROR); } } } if($scount > 0) { e107::getMessage()->addSuccess(LAN_CREATED. ' (' .$scount. ') ' .defset('LAN_PLUGIN_FEATUREBOX_NAME')); e107::getMessage()->addSuccess("'); return $scount; } return false; } /** * Batch boolean trigger * @param array $selected * @return void */ protected function handleListBoolBatch($selected, $field, $value) { $cnt = $this->getTreeModel()->batchUpdate($field, $value, $selected, $value, false); if($cnt) { $caption = e107::getParser()->lanVars(LAN_UI_BATCH_BOOL_SUCCESS, $cnt, true); $this->getTreeModel()->addMessageSuccess($caption); } $this->getTreeModel()->setMessages(); } /** * Batch boolean reverse trigger * @param array $selected * @return void */ protected function handleListBoolreverseBatch($selected, $field) { $tree = $this->getTreeModel(); $cnt = $tree->batchUpdate($field, "1-{$field}", $selected, null, false); if($cnt) { $caption = e107::getParser()->lanVars(LAN_UI_BATCH_REVERSED_SUCCESS, $cnt, true); $tree->addMessageSuccess($caption); //sync models $tree->loadBatch(true); } $this->getTreeModel()->setMessages(); } /** * @param $selected * @param $field * @param $value * @param $type * @return void */ public function handleCommaBatch($selected, $field, $value, $type) { $tree = $this->getTreeModel(); $rcnt = 0; $cnt = $rcnt; $value = e107::getParser()->toDB($value); switch ($type) { case 'attach': case 'deattach': $this->_setModel(); foreach ($selected as $key => $id) { $node = $tree->getNode($id); if(!$node) { continue; } $val = $node->get($field); if(empty($val)) { $val = array(); } elseif(!is_array($val)) { $val = explode(',', $val); } if($type === 'deattach') { $search = array_search($value, $val); if($search === false) { continue; } unset($val[$search]); sort($val); $val = implode(',', $val); $node->set($field, $val); $check = $this->getModel()->setData($node->getData())->save(false, true); if($check === false) { $this->getModel()->setMessages(); } else { $rcnt++; } continue; } // attach it if(in_array($value, $val) === false) { $val[] = $value; sort($val); $val = implode(',', array_unique($val)); $node->set($field, $val); $check = $this->getModel()->setData($node->getData())->save(false, true); if($check === false) { $this->getModel()->setMessages(); } else { $cnt++; } } } $this->_model = null; break; case 'addAll': if(!empty($value)) { if(is_array($value)) { sort($value); $value = implode(',', array_map('trim', $value)); } $cnt = $this->getTreeModel()->batchUpdate($field, $value, $selected, true); } else { $this->getTreeModel()->addMessageWarning(LAN_UPDATED_FAILED)->setMessages();//"Comma list is empty, aborting." $this->getTreeModel()->addMessageDebug(LAN_UPDATED_FAILED. ': Comma list is empty, aborting.')->setMessages(); } break; case 'clearAll': $allowed = !is_array($value) ? explode(',', $value) : $value; if(!$allowed) { $rcnt = $this->getTreeModel()->batchUpdate($field, '', $selected, ''); } else { $this->_setModel(); foreach ($selected as $key => $id) { $node = $tree->getNode($id); if(!$node) { continue; } $val = $node->get($field); // nothing to do if(empty($val)) { break; } elseif(!is_array($val)) { $val = explode(',', $val); } // remove only allowed, see userclass foreach ($val as $_k => $_v) { if(in_array($_v, $allowed)) { unset($val[$_k]); } } sort($val); $val = !empty($val) ? implode(',', $val) : ''; $node->set($field, $val); $check = $this->getModel()->setData($node->getData())->save(false, true); if($check === false) { $this->getModel()->setMessages(); } else { $rcnt++; } } $this->_model = null; } // format for proper message $value = implode(',', $allowed); break; } if($cnt) { $vttl = $this->getUI()->renderValue($field, $value, $this->getFieldAttr($field)); $caption = e107::getParser()->lanVars(LAN_UI_BATCH_UPDATE_SUCCESS, array('x'=>$vttl, 'y'=>$cnt), true); $this->getTreeModel()->addMessageSuccess($caption); } elseif($rcnt) { $vttl = $this->getUI()->renderValue($field, $value, $this->getFieldAttr($field)); $caption = e107::getParser()->lanVars(LAN_UI_BATCH_DEATTACH_SUCCESS, array('x'=>$vttl, 'y'=>$cnt), true); $this->getTreeModel()->addMessageSuccess($caption); } $this->getTreeModel()->setMessages(); } protected function handleGridSearchfieldFilter($selected) { return $this->handleListSearchfieldFilter($selected); } protected function handleGridDeleteBatch($selected) { return $this->handleListDeleteBatch($selected); } /** * Method to generate "Search in Field" query. * @param $selected * @return string */ protected function handleListSearchfieldFilter($selected) { $string = $this->getQuery('searchquery'); if(empty($string)) { return ''; } return $selected. " LIKE '%".e107::getParser()->toDB($string)."%' "; // array($selected, $this->getQuery('searchquery')); } /** * Batch default (field) trigger * @param array $selected * @return int|null */ protected function handleListBatch($selected, $field, $value) { // special exceptions if($value === '#delete') // see admin->users { $val = "''"; $value = '(empty)'; } elseif($value === '#null') { $val = null; $value = '(empty)'; } else { $val = "'".$value."'"; } if($field === 'options') // reserved field type. see: admin -> media-manager - batch rotate image. { return null; } $cnt = $this->getTreeModel()->batchUpdate($field, $val, $selected, true, false); if($cnt) { $vttl = $this->getUI()->renderValue($field, $value, $this->getFieldAttr($field)); $msg = e107::getParser()->lanVars(LAN_UI_BATCH_UPDATE_SUCCESS, array('x' => $vttl, 'y' => $cnt), true); $this->getTreeModel()->addMessageSuccess($msg); // force reload the collection from DB, fix some issues as 'observer' is executed before the batch handler $this->getTreeModel()->setParam('db_query', $this->_modifyListQry(false, false, false, false, $this->listQry))->loadBatch(true); } $this->getTreeModel()->setMessages(); return $cnt; } public function GridDeleteTrigger($posted) { $this->ListDeleteTrigger($posted); } /** * Catch delete submit * @param $posted * @return null */ public function ListDeleteTrigger($posted) { if($this->getPosted('etrigger_cancel')) { $this->setPosted(array()); return; // always break on cancel! } $id = (int) key($posted); if($this->deleteConfirmScreen && !$this->getPosted('etrigger_delete_confirm')) { // forward data to delete confirm screen $this->setPosted('delete_confirm_value', $id); return; // User confirmation expected } $this->setTriggersEnabled(false); $data = array(); $model = $this->getTreeModel()->getNode($id); //FIXME - this has issues with being on a page other than the 1st. if($model) { $data = $model->getData(); if($this->table !== 'admin_history') { $this->backupToHistory($this->table, $this->pid, $id,'delete',$data); } if($this->beforeDelete($data, $id)) { if($triggerName = $this->getEventTriggerName($this->getEventName(),'delete')) // trigger for before. { if($halt = $this->triggerEvent($triggerName, null, $data, $id)) { $this->getTreeModel()->setMessages(); return null; } } $check = $this->getTreeModel()->delete($id); if($this->afterDelete($data, $id, $check)) { if($triggerName = $this->getEventTriggerName($this->getEventName(), 'deleted')) // trigger for after. { $this->triggerEvent($triggerName, null, $data, $id); } $this->getTreeModel()->setMessages(); } } else { $this->getTreeModel()->setMessages();// errors } } else //FIXME - this is a fall-back for the BUG which causes model to fail on all list pages other than the 1st { //echo "Couldn't get Node for ID: ".$id; // exit; e107::getMessage()->addDebug('Model Failure Fallback in use!! ID: '.$id.' file: '.__FILE__. ' line: ' .__LINE__ ,'default',true); $check = $this->getTreeModel()->delete($id); return; } } /** * User defined pre-delete logic */ public function beforeDelete($data, $id) { return true; } /** * User defined after-delete logic */ public function afterDelete($deleted_data, $id, $deleted_check) { return true; } /** * List action header * @return void */ public function ListHeader() { //e107::js('core','core/tabs.js','prototype'); //e107::js('core','core/admin.js','prototype'); } /** * List action observer * @return void */ public function ListObserver() { if($ufieldpref = $this->getUserPref()) { $this->fieldpref = $ufieldpref; } $table = $this->getTableName(); if(empty($table)) { return; } $this->getTreeModel()->setParam('db_query', $this->_modifyListQry(false, false, false, false, $this->listQry))->loadBatch(); $this->addTitle(); } /** * Grid action observer */ public function GridObserver() { $table = $this->getTableName(); if(empty($table)) { return; } $this->getTreeModel()->setParam('db_query', $this->_modifyListQry(false, false, false, false, $this->listQry))->loadBatch(); } /** * Filter response ajax page * @return string */ public function FilterAjaxPage() { return $this->renderAjaxFilterResponse($this->listQry); //listQry will be used only if available } /** * Inline edit action * @return void */ public function InlineAjaxPage() { $this->logajax('Inline Ajax Triggered'); $protocol = (isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0'); if(!vartrue($_POST['name']) || !vartrue($this->fields[$_POST['name']])) { header($protocol.': 404 Not Found', true, 404); header('Status: 404 Not Found', true, 404); echo LAN_FIELD. ': ' .$this->fields[$_POST['name']]. ': ' .LAN_NOT_FOUND; // Field: x: not found! $this->logajax('Field not found'); return; } $_name = $_POST['name']; $_value = $_POST['value']; $_token = $_POST['token']; $parms = $this->fields[$_name]['readParms'] ? $this->fields[$_name]['readParms'] : ''; if(!is_array($parms)) { parse_str($parms, $parms); } if(!empty($parms['editable'])) { $this->fields[$_name]['inline'] = true; } if(!empty($this->fields[$_name]['noedit']) || !empty($this->fields[$_name]['nolist']) || empty($this->fields[$_name]['inline']) || empty($_token) || !password_verify(session_id(),$_token)) { header($protocol.': 403 Forbidden', true, 403); header('Status: 403 Forbidden', true, 403); echo ADLAN_86; //Forbidden $result = var_export($this->fields[$_name], true); $problem = array(); $problem['noedit'] = !empty($this->fields[$_name]['noedit']) ? 'yes' : 'no'; $problem['nolist'] = !empty($this->fields[$_name]['nolist']) ? 'yes' : 'no'; $problem['inline'] = empty($this->fields[$_name]['inline']) ? 'yes' : 'no'; $problem['token'] = empty($_token) ? 'yes' : 'no'; $problem['password'] = !password_verify(session_id(),$_token) ? 'yes' : 'no'; $result .= "\nForbidden Caused by: ".print_r($problem,true); $this->logajax("Forbidden\nAction:".$this->getAction()."\nField (".$_name."):\n".$result); return; } $model = $this->getModel()->load($this->getId()); $_POST = array(); //reset post $_POST[$_name] = $_value; // set current field only $_POST['etrigger_submit'] = 'update'; // needed for event trigger // print_r($_POST); // generic handler - same as regular edit form submit $this->convertToData($_POST); $model->setPostedData($_POST); $model->setParam('validateAvailable', true); // new param to control validate of available data only, reset on validate event // Do not update here! Because $old_data and $new_data will be the same in afterUpdate() methods. // Data will be saved in _manageSubmit() method. // $model->update(true); if($model->hasError()) { // using 400 header($protocol.': 400 Bad Request', true, 400); header('Status: 400 Bad Request', true, 400); $this->logajax('Bad Request'); // DEBUG e107::getMessage()->addError('Error test.', $model->getMessageStackName())->addError('Another error test.', $model->getMessageStackName()); if(E107_DEBUG_LEVEL) { $message = e107::getMessage()->get('debug', $model->getMessageStackName(), true); } else { $message = e107::getMessage()->get('error', $model->getMessageStackName(), true); } if(!empty($message)) { echo implode(' ', $message); } $this->logajax(implode(' ', $message)); return; } //TODO ? afterInline trigger? $res = $this->_manageSubmit('beforeUpdate', 'afterUpdate', 'onUpdateError', 'edit'); } // Temporary - but useful. :-) /** * @param $message * @return void */ public function logajax($message) { if(e_DEBUG !== true) { return; } $message = date('r')."\n".$message."\n"; $message .= "\n_POST\n"; $message .= print_r($_POST,true); $message .= "\n_GET\n"; $message .= print_r($_GET,true); $message .= '---------------'; file_put_contents(e_LOG.'uiAjaxResponseInline.log', $message."\n\n", FILE_APPEND); } /** * Drag-n-Drop sort action * @return void */ public function SortAjaxPage() { if(!isset($_POST['all']) || empty($_POST['all'])) { return; } if(!$this->sortField) { echo 'Missing sort field value'; return; } $this->_log('Executing SortAjaxPage()'); $sql = e107::getDb(); if(!empty($this->sortParent)) // Force 100 positions for child when sorting with parent/child. { $this->orderStep = 100; } else // Reset all the order fields first. { $resetQry = $this->sortField ."= 999 WHERE 1"; // .$this->sortField; $sql->update($this->table, $resetQry ); $this->_log('Sort Qry ('.$this->table.'): '.$resetQry); } $step = $this->orderStep ? (int) $this->orderStep : 1; $from = !empty($_GET['from']) ? (int) $_GET['from'] * $step : $step; $c = $from; $updated = array(); foreach($_POST['all'] as $row) { list($tmp,$id) = explode('-', $row, 2); $id = preg_replace('/[^\w\-:.]/', '', $id); if(!is_numeric($id)) { $id = "'{$id}'"; } $updateQry = $this->sortField." = {$c} WHERE ".$this->pid. ' = ' .$id; if($sql->update($this->table, $updateQry) !==false) { $updated[] = '#' .$id. ' -- ' .$this->sortField. ' = ' .$c; } $this->_log('Sort Qry ('.$this->table.'): '.$updateQry); $c += $step; } if(!empty($this->sortParent) && !empty($this->sortField) ) { return; } // Increment every record after the current page of records. $changed = $c - $step; $qry = 'UPDATE `#' .$this->table. '` e, (SELECT @n := ' .($changed). ') m SET e.' .$this->sortField. ' = @n := @n + ' .$step. ' WHERE ' .$this->sortField. ' > ' .($changed); $result = $sql->gen($qry); $this->_log('Sort Qry: '.$qry); // ------------ Fix Child Order when parent is used. ---------------- /* if(!empty($this->sortParent) && !empty($this->sortField) ) // Make sure there is space for at least 99 { $parent = array(); $data2 = $sql->retrieve($this->table,$this->pid.','.$this->sortField,$this->sortParent .' = 0',true); foreach($data2 as $val) { $id = $val[$this->pid]; $parent[$id] = $val[$this->sortField]; } $previous = 0; $data = $sql->retrieve($this->table,'*',$this->sortParent.' != 0 ORDER BY '.$this->sortField,true); foreach($data as $row) { $p = $row[$this->sortParent]; if($p != $previous) { $c = $parent[$p]; } $c++; $previous = $p; // echo "