1<?php
2/*
3 * e107 website system
4 *
5 * Copyright (C) 2008-2012 e107 Inc (e107.org)
6 * Released under the terms and conditions of the
7 * GNU General Public License (http://www.gnu.org/licenses/gpl.txt)
8 *
9 * Administration User Interface logic
10 *
11 * $URL$
12 * $Id$
13 */
14
15 /**
16 * @package e107
17 * @subpackage e107_handlers
18 * @version $Id$
19 *
20 * Administration User Interface logic
21 */
22
23
24/**
25 * @todo core request handler (non-admin), core response
26 */
27if (!defined('e107_INIT')){ exit; }
28
29
30class e_admin_request
31{
32	/**
33	 * Current GET request array
34	 * @var array
35	 */
36	protected $_request_qry;
37
38	/**
39	 * Current POST array
40	 * @var array
41	 */
42	protected $_posted_qry;
43
44	/**
45	 * Current Mode
46	 * @var string
47	 */
48	protected $_mode = '';
49
50	/**
51	 * Default Mode
52	 * @var string
53	 */
54	protected $_default_mode = 'main';
55
56	/**
57	 * Key name for mode search
58	 * @var string
59	 */
60	protected $_mode_key = 'mode';
61
62	/**
63	 * Current action
64	 * @var string
65	 */
66	protected $_action = '';
67
68	/**
69	 * Default Action
70	 * @var string
71	 */
72	protected $_default_action = 'index';
73
74	/**
75	 * Key name for action search
76	 * @var string
77	 */
78	protected $_action_key = 'action';
79
80	/**
81	 * Current ID
82	 * @var integer
83	 */
84	protected $_id = 0;
85
86	/**
87	 * Key name for ID search
88	 * @var string
89	 */
90	protected $_id_key = 'id';
91
92	/**
93	 * Constructor
94	 *
95	 * @param string|array $qry [optional]
96	 * @return none
97	 */
98	public function __construct($request_string = null, $parse = true)
99	{
100		if(null === $request_string)
101		{
102			$request_string = str_replace('&amp;', '&', e_QUERY);
103		}
104		if($parse)
105		{
106			$this->parseRequest($request_string);
107		}
108	}
109
110	/**
111	 * Parse request data
112	 * @param string|array $request_data
113	 * @return e_admin_request
114	 */
115	protected function parseRequest($request_data)
116	{
117		if(is_string($request_data))
118		{
119			parse_str($request_data, $request_data);
120		}
121		$this->_request_qry = (array) $request_data;
122
123		// Set current mode
124		if(isset($this->_request_qry[$this->_mode_key]))
125		{
126			$this->_mode = preg_replace('/[^\w]/', '', $this->_request_qry[$this->_mode_key]);
127		}
128
129		// Set current action
130		if(isset($this->_request_qry[$this->_action_key]))
131		{
132			$this->_action = preg_replace('/[^\w]/', '', $this->_request_qry[$this->_action_key]);
133		}
134
135		// Set current id
136		if(isset($this->_request_qry[$this->_id_key]))
137		{
138			$this->_id = preg_replace('/[^\w\-:\.]/', '', $this->_request_qry[$this->_id_key]);
139		}
140
141		$this->_posted_qry =& $_POST; //raw?
142
143		return $this;
144	}
145
146	/**
147	 * Retrieve variable from GET scope
148	 * If $key is null, all GET data will be returned
149	 *
150	 * @param string $key [optional]
151	 * @param mixed $default [optional]
152	 * @return mixed
153	 */
154	public function getQuery($key = null, $default = null)
155	{
156		if(null === $key)
157		{
158			return $this->_request_qry;
159		}
160		return (isset($this->_request_qry[$key]) ? $this->_request_qry[$key] : $default);
161	}
162
163	/**
164	 * Set/Unset GET variable
165	 * If $key is array, $value is not used.
166	 * If $value is null, (string) $key is unset
167	 *
168	 * @param string|array $key
169	 * @param mixed $value [optional]
170	 * @return e_admin_request
171	 */
172	public function setQuery($key, $value = null)
173	{
174		if(is_array($key))
175		{
176			foreach ($key as $k=>$v)
177			{
178				$this->setQuery($k, $v);
179			}
180			return $this;
181		}
182
183		if(null === $value)
184		{
185			unset($this->_request_qry[$key]);
186			unset($_GET[$key]);
187			return $this;
188		}
189
190		$this->_request_qry[$key] = $value;
191		$_GET[$key] = $value;
192		return $this;
193	}
194
195	/**
196	 * Retrieve variable from POST scope
197	 * If $key is null, all POST data will be returned
198	 *
199	 * @param string $key [optional]
200	 * @param mixed $default [optional]
201	 * @return mixed
202	 */
203	public function getPosted($key = null, $default = null)
204	{
205		if(null === $key)
206		{
207			return $this->_posted_qry;
208		}
209		return (isset($this->_posted_qry[$key]) ? $this->_posted_qry[$key] : $default);
210	}
211
212	/**
213	 * Set/Unset POST variable
214	 * If $key is array, $value is not used.
215	 * If $value is null, (string) $key is unset
216	 *
217	 * @param object $key
218	 * @param object $value [optional]
219	 * @return e_admin_request
220	 */
221	public function setPosted($key, $value = null)
222	{
223		if(is_array($key))
224		{
225			if(empty($key))
226			{
227				$this->_posted_qry = array(); //POST reset
228				return $this;
229			}
230			foreach ($key as $k=>$v)
231			{
232				$this->setPosted($k, $v);
233			}
234			return $this;
235		}
236
237		if(null === $value)
238		{
239			unset($this->_posted_qry[$key]);
240			return $this;
241		}
242
243		$tp = e107::getParser();
244		$this->_posted_qry[$tp->post_toForm($key)] = $tp->post_toForm($value);
245		return $this;
246	}
247
248	/**
249	 * Get current mode
250	 * @return string
251	 */
252	public function getMode()
253	{
254		if(!$this->_mode) return $this->getDefaultMode();
255		return $this->_mode;
256	}
257
258	/**
259	 * Get default mode
260	 * @return string
261	 */
262	public function getDefaultMode()
263	{
264		return $this->_default_mode;
265	}
266
267	/**
268	 * Get current mode name
269	 *
270	 * @return string
271	 */
272	public function getModeName()
273	{
274		return strtolower(str_replace('-', '_', $this->getMode()));
275	}
276
277	/**
278	 * Reset current mode
279	 * @param string $mode
280	 * @return e_admin_request
281	 */
282	public function setMode($mode)
283	{
284		$this->_mode = preg_replace('/[^\w]/', '', $mode);
285		$this->setQuery($this->_mode_key, $this->_mode);
286		return $this;
287	}
288
289	/**
290	 * Set default mode
291	 * @param string $mode
292	 * @return e_admin_request
293	 */
294	public function setDefaultMode($mode)
295	{
296		if($mode) $this->_default_mode = $mode;
297		return $this;
298	}
299
300	/**
301	 * Set mode key name
302	 * @param string $key
303	 * @return e_admin_request
304	 */
305	public function setModeKey($key)
306	{
307		$this->_mode_key = $key;
308		return $this;
309	}
310
311	/**
312	 * Get current action
313	 * @return string
314	 */
315	public function getAction()
316	{
317		if(!$this->_action) return $this->getDefaultAction();
318		return $this->_action;
319	}
320
321	/**
322	 * Get default action
323	 * @return string
324	 */
325	public function getDefaultAction()
326	{
327		return $this->_default_action;
328	}
329
330	/**
331	 * Get current action name
332	 * @return string camelized action
333	 */
334	public function getActionName()
335	{
336		return $this->camelize($this->getAction());
337	}
338
339	/**
340	 * Reset current action
341	 *
342	 * @param string $action
343	 * @return e_admin_request
344	 */
345	public function setAction($action)
346	{
347		$this->_action = preg_replace('/[^\w]/', '', $action);
348		$this->setQuery($this->_action_key, $this->_action);
349		return $this;
350	}
351
352	/**
353	 * Set default action
354	 *
355	 * @param string $action
356	 * @return e_admin_request
357	 */
358	public function setDefaultAction($action)
359	{
360		if($action) $this->_default_action = $action;
361		return $this;
362	}
363
364	/**
365	 * Set action key name
366	 * @param string $key
367	 * @return e_admin_request
368	 */
369	public function setActionKey($key)
370	{
371		$this->_action_key = $key;
372		return $this;
373	}
374
375	/**
376	 * Get current ID
377	 * @return integer
378	 */
379	public function getId()
380	{
381		return $this->_id;
382	}
383
384	/**
385	 * Reset current ID
386	 * @param string $id
387	 * @return e_admin_request
388	 */
389	public function setId($id)
390	{
391		$id = intval($id);
392		$this->_id = $id;
393		$this->setQuery($this->_id_key, $id);
394		return $this;
395	}
396
397	/**
398	 * Set id key name
399	 * @param string $key
400	 * @return e_admin_request
401	 */
402	public function setIdKey($key)
403	{
404		$this->_id_key = $key;
405		return $this;
406	}
407
408	/**
409	 * Build query string from current request array
410	 * NOTE: changing url separator to &amp; ($encode==true) (thus URL XHTML compliance) works in PHP 5.1.2+ environment
411	 *
412	 * @param string|array $merge_with [optional] override request values
413	 * @param boolean $encode if true &amp; separator will be used, all values will be http encoded, default true
414	 * @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
415	 * @param boolean $keepSpecial don't exclude special vars as 'mode' and 'action'
416	 * @return string url encoded query string
417	 */
418	public function buildQueryString($merge_with = array(), $encode = true, $exclude_from_query = '', $keepSpecial = true)
419	{
420		$ret = $this->getQuery();
421
422		//special case - exclude all current
423		if(true === $exclude_from_query)
424		{
425			$exclude_from_query = array_keys($ret);
426		}
427		// to array
428		if(is_string($exclude_from_query))
429		{
430			$exclude_from_query = array_map('trim', explode(',', $exclude_from_query));
431		}
432		if($exclude_from_query)
433		{
434			foreach ($exclude_from_query as $var)
435			{
436				if($keepSpecial && $var != $this->_action_key && $var != $this->_mode_key) unset($ret[$var]);
437			}
438		}
439
440		if(is_string($merge_with))
441		{
442			parse_str($merge_with, $merge_with);
443		}
444		$ret = array_merge($ret, (array) $merge_with);
445		$separator = '&';
446		if($encode)
447		{
448			$separator = '&amp;';
449			//$ret = array_map('rawurlencode', $ret);
450		}
451
452		$ret = http_build_query($ret, 'numeric_', $separator);
453		if(!$encode)
454		{
455			return rawurldecode($ret);
456		}
457		return $ret;
458	}
459
460	/**
461	 * Convert string to CamelCase
462	 *
463	 * @param string $str
464	 * @return string
465	 */
466	public function camelize($str)
467	{
468		return implode('', array_map('ucfirst', explode('-', str_replace('_', '-', $str))));
469	}
470}
471
472/**
473 * TODO - front response parent, should do all the header.php work
474 */
475class e_admin_response
476{
477	/**
478	 * Body segments
479	 *
480	 * @var array
481	 */
482	protected $_body = array();
483
484	/**
485	 * Title segments
486	 *
487	 * @var unknown_type
488	 */
489	protected $_title = array();
490
491	/**
492	 * e107 meta title
493	 *
494	 * @var array
495	 */
496	protected $_e_PAGETITLE = array();
497
498	/**
499	 * e107 meta description
500	 *
501	 * @var array
502	 */
503	protected $_META_DESCRIPTION = array();
504
505	/**
506	 * e107 meta keywords
507	 *
508	 * @var array
509	 */
510	protected $_META_KEYWORDS = array();
511
512	/**
513	 * Render mods
514	 *
515	 * @var array
516	 */
517	protected $_render_mod = array();
518
519	/**
520	 * Meta title segment description
521	 *
522	 * @var string
523	 */
524	protected $_meta_title_separator = ' - ';
525
526	/**
527	 * Title segment separator
528	 *
529	 * @var string
530	 */
531	protected $_title_separator = ' &raquo; ';
532
533	/**
534	 * Constructor
535	 *
536	 */
537	public function __construct()
538	{
539		$this->_render_mod['default'] = 'admin_page';
540	}
541
542	/**
543	 * Set body segments for a namespace
544	 *
545	 * @param string $content
546	 * @param string $namespace segment namesapce
547	 * @return e_admin_response
548	 */
549	function setBody($content, $namespace = 'default')
550	{
551		$this->_body[$namespace] = $content;
552		return $this;
553	}
554
555	/**
556	 * Append body segment to a namespace
557	 *
558	 * @param string $content
559	 * @param string $namespace segment namesapce
560	 * @return e_admin_response
561	 */
562	function appendBody($content, $namespace = 'default')
563	{
564		if(!isset($this->_body[$namespace]))
565		{
566			$this->_body[$namespace] = array();
567		}
568		$this->_body[$namespace][] = $content;
569		return $this;
570	}
571
572	/**
573	 * Prepend body segment to a namespace
574	 *
575	 * @param string $content
576	 * @param string $namespace segment namespace
577	 * @return e_admin_response
578	 */
579	function prependBody($content, $namespace = 'default')
580	{
581		if(!isset($this->_body[$namespace]))
582		{
583			$this->_body[$namespace] = array();
584		}
585		$this->_body[$namespace] = array_merge(array($content), $this->_body[$namespace]);
586		return $this;
587	}
588
589	/**
590	 * Get body segments from a namespace
591	 *
592	 * @param string $namespace segment namesapce
593	 * @param boolean $reset reset segment namespace
594	 * @param string|boolean $glue if false return array, else return string
595	 * @return string|array
596	 */
597	function getBody($namespace = 'default', $reset = false, $glue = '')
598	{
599		$content = vartrue($this->_body[$namespace], array());
600		if($reset)
601		{
602			$this->_body[$namespace] = array();
603		}
604		if(is_bool($glue))
605		{
606			return ($glue ? $content : implode('', $content));
607		}
608		return implode($glue, $content);
609	}
610
611	/**
612	 * Set title segments for a namespace
613	 *
614	 * @param string $title
615	 * @param string $namespace
616	 * @return e_admin_response
617	 */
618	function setTitle($title, $namespace = 'default')
619	{
620		$this->_title[$namespace] = array($title);
621		return $this;
622	}
623
624	/**
625	 * Append title segment to a namespace
626	 *
627	 * @param string $title
628	 * @param string $namespace segment namesapce
629	 * @return e_admin_response
630	 */
631	function appendTitle($title, $namespace = 'default')
632	{
633		if(empty($title))
634		{
635			return $this;
636		}
637		if(!isset($this->_title[$namespace]))
638		{
639			$this->_title[$namespace] = array();
640		}
641		$this->_title[$namespace][] = $title;
642		return $this;
643	}
644
645	/**
646	 * Prepend title segment to a namespace
647	 *
648	 * @param string $title
649	 * @param string $namespace segment namespace
650	 * @return e_admin_response
651	 */
652	function prependTitle($title, $namespace = 'default')
653	{
654		if(empty($title))
655		{
656			return $this;
657		}
658		if(!isset($this->_title[$namespace]))
659		{
660			$this->_title[$namespace] = array();
661		}
662		$this->_title[$namespace] = array_merge(array($title), $this->_title[$namespace]);
663		return $this;
664	}
665
666	/**
667	 * Get title segments from namespace
668	 *
669	 * @param string $namespace
670	 * @param boolean $reset
671	 * @param boolean|string $glue
672	 * @return unknown
673	 */
674	function getTitle($namespace = 'default', $reset = false, $glue = '  ')
675	{
676		$content = array();
677
678		if(isset($this->_title[$namespace]) && is_array($this->_title[$namespace]))
679		{
680			$content = $this->_title[$namespace];
681		}
682		if($reset)
683		{
684			unset($this->_title[$namespace]);
685		}
686		if(is_bool($glue) || empty($glue))
687		{
688			return ($glue ? $content : implode($this->_title_separator, $content));
689		}
690
691		$glue = deftrue('SEP',' - '); // Defined by admin theme. // admin-ui used only by bootstrap.
692
693		return implode($glue, $content);
694		// return $head. implode($glue, $content).$foot;
695	}
696
697	/**
698	 * Set render mode for a namespace
699	 *
700	 * @param string $render_mod
701	 * @param string $namespace
702	 * @return e_admin_response
703	 */
704	function setRenderMod($render_mod, $namespace = 'default')
705	{
706		$this->_render_mod[$namespace] = $render_mod;
707		return $this;
708	}
709
710	/**
711	 * Set render mode for namespace
712	 *
713	 * @param string $namespace
714	 * @return string
715	 */
716	function getRenderMod($namespace = 'default')
717	{
718		return varset($this->_render_mod[$namespace], null);
719	}
720
721	/**
722	 * Add meta title, description and keywords segments
723	 *
724	 * @param string $meta property name
725	 * @param string $content meta content
726	 * @return e_admin_response
727	 */
728	function addMetaData($meta, $content)
729	{
730		$tp = e107::getParser();
731		$meta = '_' . $meta;
732		if(isset($this->{$meta}) && !empty($content))
733		{
734			$this->{$meta}[] = strip_tags($content);
735		}
736		return $this;
737	}
738
739	/**
740	 * Add meta title segment
741	 *
742	 * @param string $title
743	 * @return e_admin_response
744	 */
745	function addMetaTitle($title)
746	{
747		$this->addMetaData('e_PAGETITLE', $title);
748		return $this;
749	}
750
751	/**
752	 * Add meta description segment
753	 *
754	 * @param string $description
755	 * @return e_admin_response
756	 */
757	function addMetaDescription($description)
758	{
759		$this->addMetaData('META_DESCRIPTION', $description);
760		return $this;
761	}
762
763	/**
764	 * Add meta keywords segment
765	 *
766	 * @param string $keyword
767	 * @return e_admin_response
768	 */
769	function addMetaKeywords($keyword)
770	{
771		$this->addMetaData('META_KEYWORDS', $keyword);
772		return $this;
773	}
774
775	/**
776	 * Send e107 meta-data
777	 *
778	 * @return e_admin_response
779	 */
780	function sendMeta()
781	{
782		//HEADERF already included or meta content already sent
783		if(e_AJAX_REQUEST || defined('HEADER_INIT') || defined('e_PAGETITLE'))
784			return $this;
785
786		if(!defined('e_PAGETITLE') && !empty($this->_e_PAGETITLE))
787		{
788			define('e_PAGETITLE', implode($this->_meta_title_separator, $this->_e_PAGETITLE));
789		}
790
791		if(!defined('META_DESCRIPTION') && !empty($this->_META_DESCRIPTION))
792		{
793			define('META_DESCRIPTION', implode(' ', $this->_META_DESCRIPTION));
794		}
795		if(!defined('META_KEYWORDS') && !empty($this->_META_KEYWORDS))
796		{
797			define('META_KEYWORDS', implode(', ', $this->_META_KEYWORDS));
798		}
799		return $this;
800	}
801
802	/**
803	 * Add content segment to the header namespace
804	 *
805	 * @param string $content
806	 * @return e_admin_response
807	 */
808	function addHeaderContent($content)
809	{
810		$this->appendBody($content, 'header_content');
811		return $this;
812	}
813
814	/**
815	 * Get page header namespace content segments
816	 *
817	 * @param boolean $reset
818	 * @param boolean $glue
819	 * @return string
820	 */
821	function getHeaderContent($reset = true, $glue = "\n\n")
822	{
823		return $this->getBody('header_content', $reset, $glue);
824	}
825
826	/**
827	 * Switch to iframe mod
828	 * FIXME - implement e_IFRAME to frontend - header_default.php
829	 *
830	 * @return e_admin_response
831	 */
832	function setIframeMod()
833	{
834		global $HEADER, $FOOTER, $CUSTOMHEADER, $CUSTOMFOOTER;
835		$HEADER = $FOOTER = '';
836		$CUSTOMHEADER = $CUSTOMFOOTER = array();
837		//TODO generic $_GET to activate for any page of admin.
838		// New
839		if(!defined('e_IFRAME'))
840		{
841			define('e_IFRAME', true);
842		}
843		return $this;
844	}
845
846	/**
847	 * Send Response Output
848	 *
849	 * @param string $name segment
850	 * @param array $options valid keys are: messages|render|meta|return|raw|ajax
851	 * @return mixed
852	 */
853	function send($name = 'default', $options = array())
854	{
855		if(is_string($options))
856		{
857			parse_str($options, $options);
858		}
859
860		// Merge with all available default options
861		$options = array_merge(array(
862			'messages' => true,
863			'render' => true,
864			'meta' => false,
865			'return' => false,
866			'raw' => false,
867			'ajax' => false
868		), $options);
869
870		$content = $this->getBody($name, true);
871		$title = $this->getTitle($name, true);
872		$return = $options['return'];
873
874		if($options['ajax'] || e_AJAX_REQUEST)
875		{
876			$type = $options['ajax'] && is_string($options['ajax']) ? $options['ajax'] : '';
877			$this->getJsHelper()->sendResponse($type);
878		}
879
880		if($options['messages'])
881		{
882			$content = e107::getMessage()->render().$content;
883		}
884
885		if($options['meta'])
886		{
887			$this->sendMeta();
888		}
889
890		// raw output expected - force return array
891		if($options['raw'])
892		{
893			return array($title, $content, $this->getRenderMod($name));
894		}
895
896		//render disabled by the controller
897		if(!$this->getRenderMod($name))
898		{
899			$options['render'] = false;
900		}
901
902		if($options['render'])
903		{
904			return e107::getRender()->tablerender($title, $content, $this->getRenderMod($name), $return);
905		}
906
907		if($return)
908		{
909			return $content;
910		}
911
912		print($content);
913		return '';
914	}
915
916	/**
917	 * Get JS Helper instance
918	 *
919	 * @return e_jshelper
920	 */
921	public function getJsHelper()
922	{
923		return e107::getSingleton('e_jshelper', true, 'admin_response');
924	}
925}
926
927/**
928 * TODO - request related code should be moved to core
929 * request handler
930 */
931class e_admin_dispatcher
932{
933	/**
934	 * @var e_admin_request
935	 */
936	protected $_request = null;
937
938	/**
939	 * @var e_admin_response
940	 */
941	protected $_response = null;
942
943	/**
944	 * @var e_admin_controller
945	 */
946	protected $_current_controller = null;
947
948	/**
949	 * Required (set by child class).
950	 * Controller map array in format
951	 * 'MODE' => array('controller' =>'CONTROLLER_CLASS_NAME'[, 'path' => 'CONTROLLER SCRIPT PATH', 'ui' => extend of 'comments_admin_form_ui', 'uipath' => 'path/to/ui/']);
952	 *
953	 * @var array
954	 */
955	protected $modes = array();
956
957	/**
958	 * Optional - access restrictions per action
959	 * Access array in format (similar to adminMenu)
960	 * 'MODE/ACTION' => e_UC_* (userclass constant, or custom userclass ID if dynamically set)
961	 *
962	 * @var array
963	 */
964	protected $access = array();
965
966	/**
967	 * Optional - generic entry point access restriction (via getperms())
968	 * Value of this for plugins would be always 'P'.
969	 * When an array is detected, route mode/action = admin perms is used. (similar to $access)
970	 * More detailed access control is granted with $access and $modes[MODE]['perm'] or  $modes[MODE]['userclass'] settings
971	 *
972	 * @var string|array
973	 */
974	protected $perm;
975
976	/**
977	 * @var string
978	 */
979	protected $defaultMode = '';
980
981	/**
982	 * @var string
983	 */
984	protected $defaultAction = '';
985
986	/**
987	 * Optional - map 'mode/action' pair to 'modeAlias/actionAlias'
988	 * @var string
989	 */
990	protected $adminMenuAliases = array();
991
992	/**
993	 * Optional (set by child class).
994	 * Required for admin menu render
995	 * Format: 'mode/action' => array('caption' => 'Link title'[, 'perm' => '0', 'url' => '{e_PLUGIN}plugname/admin_config.php'], ...);
996	 * Note that 'perm' and 'userclass' restrictions are inherited from the $modes, $access and $perm, so you don't have to set that vars if
997	 * you don't need any additional 'visual' control.
998	 * All valid key-value pair (see e107::getNav()->admin function) are accepted.
999	 * @var array
1000	 */
1001	protected $adminMenu = array();
1002
1003
1004	protected $adminMenuIcon = null;
1005	/**
1006	 * Optional (set by child class).
1007	 * Page titles for pages not in adminMenu (e.g. main/edit)
1008	 * Format array(mod/action => Page Title)
1009	 * @var string
1010	 */
1011	protected $pageTitles = array(
1012		'main/edit' => LAN_MANAGE,
1013	);
1014
1015	/**
1016	 * Optional (set by child class).
1017	 * @var string
1018	 */
1019	protected $menuTitle = 'Menu';
1020
1021	/**
1022	 * @var string
1023	 */
1024	protected $pluginTitle = '';
1025
1026	/**
1027	 * Constructor
1028	 *
1029	 * @param string|array|e_admin_request $request [optional]
1030	 * @param e_admin_response $response
1031	 */
1032	public function __construct($auto_observe = true, $request = null, $response = null)
1033	{
1034		// we let know some admin routines we are in UI mod - related with some legacy checks and fixes
1035		if(!defined('e_ADMIN_UI'))
1036		{
1037			define('e_ADMIN_UI', true);
1038		}
1039
1040		if(!empty($_GET['iframe']))
1041		{
1042			define('e_IFRAME', true);
1043		}
1044
1045		require_once(e_ADMIN.'boot.php');
1046
1047		if(null === $request || !is_object($request))
1048		{
1049			$request = new e_admin_request($request);
1050		}
1051
1052		if(null === $response)
1053		{
1054			$response = new e_admin_response();
1055		}
1056
1057		$this->setRequest($request)->setResponse($response)->init();
1058
1059		if(!$this->defaultMode || !$this->defaultAction)
1060		{
1061			$this->setDefaults();
1062		}
1063
1064
1065
1066		// current user does not have access to default route, so find a new one.
1067		if(!$hasAccess = $this->hasRouteAccess($this->defaultMode.'/'.$this->defaultAction))
1068		{
1069			if($newRoute = $this->getApprovedAccessRoute())
1070			{
1071				list($this->defaultMode,$this->defaultAction) = explode('/',$newRoute);
1072			}
1073		}
1074
1075
1076		$request->setDefaultMode($this->defaultMode)->setDefaultAction($this->defaultAction);
1077
1078		// register itself
1079		e107::setRegistry('admin/ui/dispatcher', $this);
1080
1081		// permissions and restrictions
1082		$this->checkAccess();
1083
1084		if($auto_observe)
1085		{
1086			$this->runObservers(true);
1087		}
1088	}
1089
1090	/**
1091	 * User defined constructor - called before _initController() method
1092	 * @return e_admin_dispatcher
1093	 */
1094	public function init()
1095	{
1096	}
1097
1098	public function checkAccess()
1099	{
1100		$request = $this->getRequest();
1101		$currentMode = $request->getMode();
1102
1103		// access based on mode setting - general controller access
1104		if(!$this->hasModeAccess($currentMode))
1105		{
1106			$request->setAction('e403');
1107			e107::getMessage()->addError(LAN_NO_PERMISSIONS)
1108				->addDebug('Mode access restriction triggered.');
1109			return false;
1110		}
1111
1112		// access based on $access settings - access per action
1113		$currentAction = $request->getAction();
1114		$route = $currentMode.'/'.$currentAction;
1115
1116
1117
1118		if(!$this->hasRouteAccess($route))
1119		{
1120			$request->setAction('e403');
1121			e107::getMessage()->addError(LAN_NO_PERMISSIONS)
1122				->addDebug('Route access restriction triggered:'.$route);
1123			return false;
1124		}
1125
1126		return true;
1127	}
1128
1129	public function hasModeAccess($mode)
1130	{
1131		// mode userclass (former check_class())
1132		if(isset($this->modes[$mode]['userclass']) && !e107::getUser()->checkClass($this->modes[$mode]['userclass'], false))
1133		{
1134			return false;
1135		}
1136		// mode admin permission (former getperms())
1137		if(isset($this->modes[$mode]['perm']) && !e107::getUser()->checkAdminPerms($this->modes[$mode]['perm']))
1138		{
1139			return false;
1140		}
1141
1142		// generic dispatcher admin permission  (former getperms())
1143		if(null !== $this->perm && is_string($this->perm) && !e107::getUser()->checkAdminPerms($this->perm))
1144		{
1145			return false;
1146		}
1147
1148		return true;
1149	}
1150
1151	public function hasRouteAccess($route)
1152	{
1153		if(isset($this->access[$route]) && !e107::getUser()->checkClass($this->access[$route], false))
1154		{
1155			return false;
1156		}
1157
1158		if(is_array($this->perm) && isset($this->perm[$route]) && !e107::getUser()->checkAdminPerms($this->perm[$route]))
1159		{
1160			return false;
1161		}
1162
1163
1164		return true;
1165	}
1166
1167	/**
1168	 * Retrieve missing default action/mode
1169	 * @return e_admin_dispatcher
1170	 */
1171	public function setDefaults()
1172	{
1173		// try Admin menu first
1174		if($this->adminMenu)
1175		{
1176			reset($this->adminMenu);
1177			list($mode, $action) = explode('/', key($this->adminMenu), 3);
1178		}
1179		else
1180		{
1181			reset($this->modes);
1182			$mode = key($this->modes);
1183			$action = $this->modes[$mode]['index'];
1184		}
1185
1186
1187
1188
1189		if(!$this->defaultMode) $this->defaultMode = $mode;
1190		if(!$this->defaultAction) $this->defaultAction = $action;
1191
1192		return $this;
1193	}
1194
1195	/**
1196	 * Search through access for an approved route.
1197	 * Returns false if no approved route found.
1198	 *
1199	 * @return string|bool
1200	 */
1201	private function getApprovedAccessRoute()
1202	{
1203		if(empty($this->access))
1204		{
1205			return false;
1206		}
1207
1208		foreach($this->access as $route=>$uclass)
1209		{
1210			if(check_class($uclass))
1211			{
1212				return $route;
1213			}
1214		}
1215
1216		return false;
1217	}
1218
1219	/**
1220	 * Get admin menu array
1221	 * @return array
1222	 */
1223	public function getMenuData()
1224	{
1225		return $this->adminMenu;
1226	}
1227
1228	/**
1229	 * Get admin menu array
1230	 * @return array
1231	 */
1232	public function getPageTitles()
1233	{
1234		return $this->pageTitles;
1235	}
1236
1237	/**
1238	 * Get admin menu array
1239	 * @return array
1240	 */
1241	public function getMenuAliases()
1242	{
1243		return $this->adminMenuAliases;
1244	}
1245
1246	/**
1247	 * Get request object
1248	 * @return e_admin_request
1249	 */
1250	public function getRequest()
1251	{
1252		return $this->_request;
1253	}
1254
1255	/**
1256	 * Set request object
1257	 * @param e_admin_request $request
1258	 * @return e_admin_dispatcher
1259	 */
1260	public function setRequest($request)
1261	{
1262		$this->_request = $request;
1263		return $this;
1264	}
1265
1266	/**
1267	 * Get response object
1268	 * @return e_admin_response
1269	 */
1270	public function getResponse()
1271	{
1272		return $this->_response;
1273	}
1274
1275	/**
1276	 * Set response object
1277	 * @param e_admin_response $response
1278	 * @return e_admin_dispatcher
1279	 */
1280	public function setResponse($response)
1281	{
1282		$this->_response = $response;
1283		return $this;
1284	}
1285
1286	/**
1287	 * Dispatch & render all
1288	 *
1289	 * @param boolean $run_header see runObservers()
1290	 * @param boolean $return see runPage()
1291	 * @return string|array current admin page body
1292	 */
1293	public function run($run_header = true, $return = 'render')
1294	{
1295		return $this->runObservers()->runPage($return);
1296	}
1297
1298	/**
1299	 * Run observers/headers only, should be called before header.php call
1300	 *
1301	 * @return e_admin_dispatcher
1302	 */
1303	public function runObservers($run_header = true)
1304	{
1305		//search for $actionName.'Observer' method. Additional $actionName.$triggerName.'Trigger' methods will be called as well
1306		$this->getController()->dispatchObserver();
1307
1308		//search for $actionName.'Header' method, js manager should be used inside for sending JS to the page,
1309		// meta information should be created there as well
1310		if($run_header)
1311		{
1312			$this->getController()->dispatchHeader();
1313
1314		}
1315		return $this;
1316	}
1317
1318	/**
1319	 * Run page action.
1320	 * If return type is array, it should contain allowed response options (see e_admin_response::send())
1321	 * Available return type string values:
1322	 * - render_return: return rendered content ( see e107::getRender()->tablerender()), add system messages, send meta information
1323	 * - render: outputs rendered content ( see e107::getRender()->tablerender()), add system messages
1324	 * - response: return response object
1325	 * - raw: return array(title, content, render mode)
1326	 * - ajax: force ajax output (and exit)
1327	 *
1328	 * @param string|array $return_type expected string values: render|render_out|response|raw|ajax[_text|_json|_xml]
1329	 * @return mixed
1330	 */
1331	public function runPage($return_type = 'render')
1332	{
1333		$response = $this->getController()->dispatchPage();
1334		if(is_array($return_type))
1335		{
1336			return $response->send('default', $return_type);
1337		}
1338		switch($return_type)
1339		{
1340			case 'render_return':
1341				$options = array(
1342					'messages' => true,
1343					'render' => true,
1344					'meta' => true,
1345					'return' => true,
1346					'raw' => false
1347				);
1348			break;
1349
1350			case 'raw':
1351				$options = array(
1352					'messages' => false,
1353					'render' => false,
1354					'meta' => false,
1355					'return' => true,
1356					'raw' => true
1357				);
1358			break;
1359
1360			case 'ajax':
1361			case 'ajax_text':
1362			case 'ajax_xml';
1363			case 'ajax_json';
1364				$options = array(
1365					'messages' => false,
1366					'render' => false,
1367					'meta' => false,
1368					'return' => false,
1369					'raw' => false,
1370					'ajax' => str_replace(array('ajax_', 'ajax'), array('', 'text'), $return_type)
1371				);
1372			break;
1373
1374			case 'response':
1375				return $response;
1376			break;
1377
1378			case 'render':
1379			default:
1380				$options = array(
1381					'messages' => true,
1382					'render' => true,
1383					'meta' => false,
1384					'return' => false,
1385					'raw' => false
1386				);
1387			break;
1388		}
1389		return $response->send('default', $options);
1390	}
1391
1392
1393	/**
1394	 * Get perms
1395	 * @return array|string
1396	 */
1397	public function getPerm()
1398	{
1399		return $this->perm;
1400	}
1401
1402	/**
1403	 * Proxy method
1404	 *
1405	 * @return string
1406	 */
1407	public function getHeader()
1408	{
1409		return $this->getController()->getHeader();
1410	}
1411
1412	/**
1413	 * Get current controller object
1414	 * @return e_admin_controller
1415	 */
1416	public function getController()
1417	{
1418		if(null === $this->_current_controller)
1419		{
1420			$this->_initController();
1421		}
1422		return $this->_current_controller;
1423	}
1424
1425	/**
1426	 * Try to init Controller from request using current controller map
1427	 *
1428	 * @return e_admin_dispatcher
1429	 */
1430	protected function _initController()
1431	{
1432		$request = $this->getRequest();
1433		$response = $this->getResponse();
1434		if(isset($this->modes[$request->getModeName()]) && isset($this->modes[$request->getModeName()]['controller']))
1435		{
1436			$class_name = $this->modes[$request->getModeName()]['controller'];
1437			$class_path = vartrue($this->modes[$request->getModeName()]['path']);
1438
1439			if($class_path)
1440			{
1441				require_once(e107::getParser()->replaceConstants($class_path));
1442			}
1443			if($class_name && class_exists($class_name))//NOTE: autoload in the play
1444			{
1445				$this->_current_controller = new  $class_name($request, $response);
1446				//give access to current request object, user defined init
1447				$this->_current_controller->setRequest($this->getRequest())->init();
1448			}
1449			// Known controller (found in e_admin_dispatcher::$modes), class not found exception
1450			else
1451			{
1452				// TODO - admin log
1453				// get default controller
1454				$this->_current_controller = $this->getDefaultController();
1455				// add messages
1456				e107::getMessage()->add('Can\'t find class <strong>&quot;'.($class_name ? $class_name : 'n/a').'&quot;</strong> for controller <strong>&quot;'.ucfirst($request->getModeName()).'&quot;</strong>', E_MESSAGE_ERROR)
1457					->add('Requested: '.e_REQUEST_SELF.'?'.$request->buildQueryString(), E_MESSAGE_DEBUG);
1458				//
1459				$request->setMode($this->getDefaultControllerName())->setAction('e404');
1460				$this->_current_controller->setRequest($request)->init();
1461			}
1462
1463			if(vartrue($this->modes[$request->getModeName()]['ui']))
1464			{
1465				$class_name = $this->modes[$request->getModeName()]['ui'];
1466				$class_path = vartrue($this->modes[$request->getModeName()]['uipath']);
1467				if($class_path)
1468				{
1469					require_once(e107::getParser()->replaceConstants($class_path));
1470				}
1471				if(class_exists($class_name))//NOTE: autoload in the play
1472				{
1473					$this->_current_controller->setParam('ui', new $class_name($this->_current_controller));
1474				}
1475			}
1476			$this->_current_controller->setParam('modes', $this->modes);
1477
1478		}
1479		// Not known controller (not found in e_admin_dispatcher::$modes) exception
1480		else
1481		{
1482			// TODO - admin log
1483			$this->_current_controller = $this->getDefaultController();
1484			// add messages
1485			e107::getMessage()->add('Can\'t find class for controller <strong>&quot;'.ucfirst($request->getModeName()).'&quot;</strong>', E_MESSAGE_ERROR)
1486				->add('Requested: '.e_REQUEST_SELF.'?'.$request->buildQueryString(), E_MESSAGE_DEBUG);
1487			// go to not found page
1488			$request->setMode($this->getDefaultControllerName())->setAction('e404');
1489			$this->_current_controller->setRequest($request)->init();
1490		}
1491
1492		return $this;
1493	}
1494
1495	/**
1496	 * Default controller object - needed if controller not found
1497	 * @return e_admin_controller
1498	 */
1499	public function getDefaultController()
1500	{
1501		$class_name = $this->getDefaultControllerName();
1502		return new $class_name($this->getRequest(), $this->getResponse());
1503	}
1504
1505	/**
1506	 *  Default controller name - needed if controller not found
1507	 * @return string name of controller
1508	 */
1509	public function getDefaultControllerName()
1510	{
1511		return 'e_admin_controller';
1512	}
1513
1514	/**
1515	 * Generic Admin Menu Generator
1516	 * @return string
1517	 */
1518	function renderMenu()
1519	{
1520
1521		$tp = e107::getParser();
1522		$var = array();
1523		$selected = false;
1524
1525		foreach($this->adminMenu as $key => $val)
1526		{
1527
1528			if(isset($val['perm']) && $val['perm']!=='' && !getperms($val['perm']))
1529			{
1530				continue;
1531			}
1532
1533			$tmp = explode('/', trim($key, '/'), 3);
1534
1535			// sync with mode/route access
1536			if(!$this->hasModeAccess($tmp[0]) || !$this->hasRouteAccess($tmp[0].'/'.varset($tmp[1])))
1537			{
1538				continue;
1539			}
1540
1541			// custom 'selected' check
1542			if(isset($val['selected']) && $val['selected']) $selected = $val['selected'] === true ? $key : $val['selected'];
1543
1544			foreach ($val as $k=>$v)
1545			{
1546				switch($k)
1547				{
1548					case 'caption':
1549						$k2 = 'text';
1550						$v = defset($v, $v);
1551
1552					break;
1553
1554					case 'url':
1555						$k2 = 'link';
1556							$qry = (isset($val['query'])) ? $val['query'] : '?mode='.$tmp[0].'&amp;action='.$tmp[1];
1557						$v = $tp->replaceConstants($v, 'abs').$qry;
1558					break;
1559
1560					case 'uri':
1561						$k2 = 'link';
1562						$v = $tp->replaceConstants($v, 'abs');
1563
1564						if(!empty($v) && (e_REQUEST_URI === $v))
1565						{
1566							$selected = $key;
1567						}
1568
1569					break;
1570
1571
1572					case 'badge': // array('value'=> int, 'type'=>'warning');
1573						$k2 = 'badge';
1574						$v = (array) $v;
1575					break;
1576
1577					default:
1578						$k2 = $k;
1579
1580					break;
1581				}
1582
1583
1584
1585				// Access check done above
1586				// if($val['perm']!= null) // check perms
1587				// {
1588					// if(getperms($val['perm']))
1589					// {
1590						// $var[$key][$k2] = $v;
1591					// }
1592				// }
1593				// else
1594				{
1595					$var[$key][$k2] = $v;
1596
1597				}
1598
1599			}
1600
1601
1602
1603			// TODO slide down menu options?
1604			if(!vartrue($var[$key]['link']))
1605			{
1606				$var[$key]['link'] = e_REQUEST_SELF.'?mode='.$tmp[0].'&amp;action='.$tmp[1]; // FIXME - URL based on $modes, remove url key
1607			}
1608
1609
1610			if(varset($val['tab']))
1611			{
1612				$var[$key]['link'] .= "&amp;tab=".$val['tab'];
1613			}
1614
1615			/*$var[$key]['text'] = $val['caption'];
1616			$var[$key]['link'] = (vartrue($val['url']) ? $tp->replaceConstants($val['url'], 'abs') : e_SELF).'?mode='.$tmp[0].'&action='.$tmp[1];
1617			$var[$key]['perm'] = $val['perm'];	*/
1618			if(!empty($val['modal']))
1619			{
1620				$var[$key]['link_class'] = ' e-modal';
1621				if(!empty($val['modal-caption']))
1622				{
1623					$var[$key]['link_data'] = array('data-modal-caption' => $val['modal-caption']);
1624				}
1625
1626			}
1627
1628		}
1629
1630
1631		if(empty($var)) return '';
1632
1633		$request = $this->getRequest();
1634		if(!$selected) $selected = $request->getMode().'/'.$request->getAction();
1635		$selected = vartrue($this->adminMenuAliases[$selected], $selected);
1636
1637		$icon = '';
1638
1639		if(!empty($this->adminMenuIcon))
1640		{
1641			$icon = e107::getParser()->toIcon($this->adminMenuIcon);
1642		}
1643		elseif(deftrue('e_CURRENT_PLUGIN'))
1644		{
1645			$icon = e107::getPlug()->load(e_CURRENT_PLUGIN)->getIcon(24);
1646		}
1647
1648		return e107::getNav()->admin($icon."<span>".$this->menuTitle."</span>", $selected, $var);
1649	}
1650
1651
1652	/**
1653	 * Render Help Text in <ul> format. XXX TODO
1654	 */
1655	function renderHelp()
1656	{
1657
1658
1659
1660	}
1661
1662
1663	/**
1664	 * Check for table issues and warn the user. XXX TODO
1665	 * ie. user is using French interface but no french tables found for the current DB tables.
1666	 */
1667	function renderWarnings()
1668	{
1669
1670
1671
1672
1673	}
1674
1675
1676}
1677
1678class e_admin_controller
1679{
1680	/**
1681	 * @var e_admin_request
1682	 */
1683	protected $_request;
1684
1685	/**
1686	 * @var e_admin_response
1687	 */
1688	protected $_response;
1689
1690	/**
1691	 * @var array User defined parameters
1692	 */
1693	protected $_params = array();
1694
1695	/**
1696	 * @var string default action name
1697	 */
1698	protected $_default_action = 'index';
1699
1700
1701	/**
1702	 * @var string default trigger action.
1703	 */
1704	protected $_default_trigger = 'auto';
1705
1706	/**
1707	 * List (numerical array) of only allowed for this controller actions
1708	 * Useful to grant access for certain pre-defined actions only
1709	 * XXX - we may move this in dispatcher (or even having it also there), still searching the most 'friendly' way
1710	 * @var array
1711	 */
1712	protected $allow = array();
1713
1714	/**
1715	 * List (numerical array) of only disallowed for this controller actions
1716	 * Useful to restrict access for certain pre-defined actions only
1717	 * XXX - we may move this in dispatcher (or even having it also there), still searching the most 'friendly' way
1718	 * @var array
1719	 */
1720	protected $disallow = array();
1721
1722	/**
1723	 * Constructor
1724	 * @param e_admin_request $request [optional]
1725	 */
1726	public function __construct($request, $response, $params = array())
1727	{
1728		$this->_params = array_merge(array('enable_triggers' => false), $params);
1729		$this->setRequest($request)
1730			->setResponse($response)
1731			->setParams($params);
1732
1733		$this->checkAccess();
1734
1735		$this->_log(); // clear the log (when debug is enabled)
1736
1737	}
1738
1739	/**
1740	 * Check against allowed/disallowed actions
1741	 * FIXME check plugin admin access (check_class(P)), confirm e-token is verified
1742	 */
1743	public function checkAccess()
1744	{
1745		$request = $this->getRequest();
1746		$currentAction = $request->getAction();
1747
1748		// access based on mode setting - general controller access
1749		if(!empty($this->disallow) && in_array($currentAction, $this->disallow))
1750		{
1751			$request->setAction('e403');
1752			e107::getMessage()->addError(LAN_NO_PERMISSIONS)
1753				->addDebug('Controller action disallowed restriction triggered.');
1754			return false;
1755		}
1756
1757		// access based on $access settings - access per action
1758		if(!empty($this->allow) && !in_array($currentAction, $this->allow))
1759		{
1760			$request->setAction('e403');
1761			e107::getMessage()->addError(LAN_NO_PERMISSIONS)
1762				->addDebug('Controller action not in allowed list restriction triggered.');
1763			return false;
1764		}
1765		return true;
1766	}
1767
1768	/**
1769	 * User defined init
1770	 * Called before dispatch routine
1771	 */
1772	public function init()
1773	{
1774	}
1775
1776	/**
1777	 * Get controller parameter
1778	 * Currently used core parameters:
1779	 * - enable_triggers: don't use it direct, see {@link setTriggersEnabled()}
1780	 * - modes - see dispatcher::$modes
1781	 * - ajax_response - text|xml|json - default is 'text'; this should be set by the action method
1782	 * - TODO - more parameters/add missing to this list
1783	 *
1784	 * @param string $key [optional] if null - get whole array
1785	 * @param mixed $default [optional]
1786	 * @return mixed
1787	 */
1788	public function getParam($key = null, $default = null)
1789	{
1790		if(null === $key)
1791		{
1792			return $this->_params;
1793		}
1794		return (isset($this->_params[$key]) ? $this->_params[$key] : $default);
1795	}
1796
1797	/**
1798	 * Set parameter
1799	 * @param string $key
1800	 * @param mixed $value
1801	 * @return e_admin_controller
1802	 */
1803	public function setParam($key, $value)
1804	{
1805		if(null === $value)
1806		{
1807			unset($this->_params[$key]);
1808			return $this;
1809		}
1810		$this->_params[$key] = $value;
1811		return $this;
1812	}
1813
1814	/**
1815	 * Merge passed parameter array with current parameters
1816	 * @param array $params
1817	 * @return e_admin_controller
1818	 */
1819	public function setParams($params)
1820	{
1821		$this->_params = array_merge($this->_params, $params);
1822		return $this;
1823	}
1824
1825	/**
1826	 * Reset parameter array
1827	 * @param array $params
1828	 * @return e_admin_controller
1829	 */
1830	public function resetParams($params)
1831	{
1832		$this->_params = $params;
1833		return $this;
1834	}
1835
1836	/**
1837	 * Get current request object
1838	 * @return e_admin_request
1839	 */
1840	public function getRequest()
1841	{
1842		return $this->_request;
1843	}
1844
1845	/**
1846	 * Set current request object
1847	 * @param e_admin_request $request
1848	 * @return e_admin_controller
1849	 */
1850	public function setRequest($request)
1851	{
1852		$this->_request = $request;
1853		return $this;
1854	}
1855
1856	/**
1857	 * Get current response object
1858	 * @return e_admin_response
1859	 */
1860	public function getResponse()
1861	{
1862		return $this->_response;
1863	}
1864
1865	/**
1866	 * Set current response object
1867	 * @param e_admin_response $response
1868	 * @return e_admin_controller
1869	 */
1870	public function setResponse($response)
1871	{
1872		$this->_response = $response;
1873		return $this;
1874	}
1875
1876	/**
1877	 * Get current dispatcher object
1878	 * @return e_admin_dispatcher
1879	 */
1880	public function getDispatcher()
1881	{
1882		return e107::getRegistry('admin/ui/dispatcher');
1883	}
1884
1885	/**
1886	 * Request proxy method
1887	 * @param string $key [optional]
1888	 * @param mixed $default [optional]
1889	 * @return mixed
1890	 */
1891	public function getQuery($key = null, $default = null)
1892	{
1893		return $this->getRequest()->getQuery($key, $default);
1894	}
1895
1896	/**
1897	 * Request proxy method
1898	 * @param string|array $key
1899	 * @param mixed $value [optional]
1900	 * @return e_admin_controller
1901	 */
1902	public function setQuery($key, $value = null)
1903	{
1904		$this->getRequest()->setQuery($key, $value);
1905		return $this;
1906	}
1907
1908	/**
1909	 * Request proxy method
1910	 * @param string $key [optional]
1911	 * @param mixed $default [optional]
1912	 * @return mixed
1913	 */
1914	public function getPosted($key = null, $default = null)
1915	{
1916		return $this->getRequest()->getPosted($key, $default);
1917	}
1918
1919	/**
1920	 * Request proxy method
1921	 * @param string $key
1922	 * @param mixed $value [optional]
1923	 * @return e_admin_controller
1924	 */
1925	public function setPosted($key, $value = null)
1926	{
1927		$this->getRequest()->setPosted($key, $value);
1928		return $this;
1929	}
1930
1931	/**
1932	 * Add page title, response proxy method
1933	 *
1934	 * @param string $title if boolean true - current menu caption will be used
1935	 * @param boolean $meta add to meta as well
1936	 * @return object e_admin_controller
1937	 */
1938	public function addTitle($title = true, $meta = true)
1939	{
1940
1941
1942		if(true === $title)
1943		{
1944			$_dispatcher = $this->getDispatcher();
1945			$data = $_dispatcher->getPageTitles();
1946			$search = $this->getMode().'/'.$this->getAction();
1947
1948
1949
1950			if(isset($data[$search]))
1951			{
1952				 $res['caption'] = $data[$search];
1953			}
1954			else
1955			{
1956
1957
1958				$data = $_dispatcher->getMenuData();
1959
1960				if(isset($data[$search]))
1961				{
1962					 $res = $data[$search];
1963				}
1964				else
1965				{
1966					// check for an alias match.
1967					$d = $_dispatcher->getMenuAliases();
1968					if(isset($d[$search]))
1969					{
1970						$search = $d[$search];
1971						$res = $data[$search];
1972
1973					}
1974					else
1975					{
1976						 return $this;
1977					}
1978				//	var_dump($d);
1979				//	var_dump("Couldnt find: ".$search);
1980
1981				}
1982			}
1983			$title = $res['caption'];
1984
1985
1986		}
1987
1988		//	echo "<h3>".__METHOD__." - ".$title."</h3>";
1989
1990	//	print_a($title);
1991		$this->getResponse()->appendTitle($title);
1992		if($meta) $this->addMetaTitle($title);
1993
1994		return $this;
1995	}
1996
1997	/**
1998	 * Add page meta title, response proxy method.
1999	 * Should be called before header.php
2000	 *
2001	 * @param string $title
2002	 * @return e_admin_controller
2003	 */
2004	public function addMetaTitle($title=null)
2005	{
2006		if($title === null)
2007		{
2008			return $this;
2009		}
2010
2011		$this->getResponse()->addMetaTitle($title);
2012		return $this;
2013	}
2014
2015	/**
2016	 * Add header content, response proxy method
2017	 * Should be called before header.php
2018	 *
2019	 * @param string $content
2020	 * @return e_admin_controller
2021	 */
2022	public function addHeader($content=null)
2023	{
2024		if($content === null)
2025		{
2026			return $this;
2027		}
2028
2029		$this->getResponse()->addHeaderContent(vartrue($content));
2030		return $this;
2031	}
2032
2033	/**
2034	 * Get header content, response proxy method
2035	 *
2036	 * @return string
2037	 */
2038	public function getHeader()
2039	{
2040		return $this->getResponse()->getHeaderContent();
2041	}
2042
2043	/**
2044	 * Get current mode, response proxy method
2045	 * @return string
2046	 */
2047	public function getMode()
2048	{
2049		return $this->getRequest()->getMode();
2050	}
2051
2052	/**
2053	 * Get current actin, response proxy method
2054	 * @return string
2055	 */
2056	public function getAction()
2057	{
2058		return $this->getRequest()->getAction();
2059	}
2060
2061	/**
2062	 * Get current ID, response proxy method
2063	 * @return string
2064	 */
2065	public function getId()
2066	{
2067		return $this->getRequest()->getId();
2068	}
2069
2070	/**
2071	 * Get response owned JS Helper instance, response proxy method
2072	 *
2073	 * @return e_jshelper
2074	 */
2075	public function getJsHelper()
2076	{
2077		return $this->getResponse()->getJsHelper();
2078	}
2079
2080	protected function _preDispatch($action = '')
2081	{
2082		if(!$action) $action = $this->getRequest()->getActionName();
2083		$method = $this->toMethodName($action, 'page');
2084		if(!method_exists($this, $method))
2085		{
2086			$this->_log("Skipping ".$method."() (not found)");
2087			$this->getRequest()->setAction($this->getDefaultAction());
2088		}
2089
2090		// switch to 404 if needed
2091		$method = $this->toMethodName($this->getRequest()->getActionName(), 'page');
2092		if(!method_exists($this, $method))
2093		{
2094			$this->_log("Skipping ".$method."() (not found)");
2095			$this->getRequest()->setAction('e404');
2096			$message = e107::getParser()->lanVars(LAN_UI_404_METHOD_ERROR, $method, true);
2097			e107::getMessage()->add($message, E_MESSAGE_ERROR);
2098		}
2099	}
2100
2101	/**
2102	 * Log Controller when e_DEBUG is active.
2103	 * @param string|null $message
2104	 * @return null
2105	 */
2106	protected function _log($message=null)
2107	{
2108		if(!deftrue('e_DEBUG'))
2109		{
2110			return null;
2111		}
2112
2113		if($message === null) // clear the log.
2114		{
2115			file_put_contents(e_LOG."adminUI.log", '');
2116			return null;
2117		}
2118
2119		$date = (!empty($message)) ? date('c') : '';
2120
2121		file_put_contents(e_LOG."adminUI.log",$date."\t".$message."\n",FILE_APPEND);
2122
2123	}
2124
2125
2126	/**
2127	 * Dispatch observer, check for triggers
2128	 *
2129	 * @param string $action [optional]
2130	 * @return e_admin_controller
2131	 */
2132	public function dispatchObserver($action = null)
2133	{
2134		$request = $this->getRequest();
2135		if(null === $request)
2136		{
2137			$request = new e_admin_request();
2138			$this->setRequest($request);
2139		}
2140
2141		$this->_preDispatch($action);
2142		if(null === $action)
2143		{
2144			$action = $request->getActionName();
2145		}
2146
2147		// check for observer
2148		$actionObserverName = $this->toMethodName($action, 'observer', e_AJAX_REQUEST);
2149		if(method_exists($this, $actionObserverName))
2150		{
2151			$this->_log("Executing ".$actionObserverName."()");
2152			$this->$actionObserverName();
2153		}
2154		else
2155		{
2156			$this->_log("Skipping ".$actionObserverName."() (not found)");
2157		}
2158
2159		// check for triggers, not available in Ajax mode
2160		if(!e_AJAX_REQUEST && $this->triggersEnabled())
2161		{
2162			$posted = $request->getPosted();
2163			foreach ($posted as $key => $value)
2164			{
2165				if(strpos($key, 'etrigger_') === 0)
2166				{
2167					$actionTriggerName = $this->toMethodName($action.$request->camelize(substr($key, 9)), 'trigger', false);
2168					if(method_exists($this, $actionTriggerName))
2169					{
2170						$this->$actionTriggerName($value);
2171					}
2172					//Check if triggers are still enabled
2173					if(!$this->triggersEnabled())
2174					{
2175						break;
2176					}
2177				}
2178			}
2179		}
2180
2181		return $this;
2182	}
2183
2184	/**
2185	 * Dispatch header, not allowed in Ajax mode
2186	 * @param string $action [optional]
2187	 * @return e_admin_controller
2188	 */
2189	public function dispatchHeader($action = null)
2190	{
2191		// not available in Ajax mode
2192		if(e_AJAX_REQUEST)
2193		{
2194			return $this;
2195		}
2196
2197		$request = $this->getRequest();
2198		if(null === $request)
2199		{
2200			$request = new e_admin_request();
2201			$this->setRequest($request);
2202		}
2203
2204		$this->_preDispatch($action);
2205		if(null === $action)
2206		{
2207			$action = $request->getActionName();
2208		}
2209
2210		// check for observer
2211		$actionHeaderName = $this->toMethodName($action, 'header', false);
2212		if(method_exists($this, $actionHeaderName))
2213		{
2214			$this->$actionHeaderName();
2215		}
2216
2217		//send meta data
2218		$this->getResponse()->sendMeta();
2219		return $this;
2220	}
2221
2222	/**
2223	 * Dispatch controller action
2224	 *
2225	 * @param string $action [optional]
2226	 * @return e_admin_response
2227	 */
2228	public function dispatchPage($action = null)
2229	{
2230		$request = $this->getRequest();
2231		if(null === $request)
2232		{
2233			$request = new e_admin_request();
2234			$this->setRequest($request);
2235		}
2236		$response = $this->getResponse();
2237	//	print_a($response);
2238		$this->_preDispatch($action);
2239
2240		if(null === $action)
2241		{
2242			$action = $request->getActionName();
2243		}
2244
2245		// check for observer
2246		$actionName = $this->toMethodName($action, 'page');
2247		$ret = '';
2248		if(!method_exists($this, $actionName)) // pre dispatch already switched to default action/not found page if needed
2249		{
2250			$this->_log("Skipping ".$actionName."() (not found)");
2251			e107::getMessage()->add('Action '.$actionName.' no found!', E_MESSAGE_ERROR);
2252			return $response;
2253		}
2254		else
2255		{
2256			$this->_log("Executing ".$actionName."()");
2257		}
2258
2259		if($action != 'Prefs' && $action != 'Create' && $action !='Edit' && $action != 'List') // Custom Page method in use, so add the title.
2260		{
2261			$this->addTitle();
2262		}
2263
2264
2265	//	e107::getDebug()->log("Admin-ui Action: <b>".$action."</b>");
2266
2267
2268
2269
2270
2271
2272		ob_start(); //catch any output
2273		$ret = $this->{$actionName}();
2274
2275
2276		//Ajax XML/JSON communication
2277		if(e_AJAX_REQUEST && is_array($ret))
2278		{
2279			$response_type = $this->getParam('ajax_response', 'xml');
2280			ob_clean();
2281			$js_helper = $response->getJsHelper();
2282			foreach ($ret as $act => $data)
2283			{
2284				$js_helper->addResponse($data, $act);
2285			}
2286			$js_helper->sendResponse($response_type);
2287		}
2288
2289		$ret .= ob_get_clean();
2290
2291		// Ajax text response
2292		if(e_AJAX_REQUEST)
2293		{
2294			$response_type = 'text';
2295			$response->getJsHelper()->addResponse($ret)->sendResponse($response_type);
2296		}
2297		else
2298		{
2299			$response->appendBody($ret);
2300		}
2301
2302		return $response;
2303	}
2304
2305	public function E404Observer()
2306	{
2307		$this->getResponse()->setTitle(LAN_UI_404_TITLE_ERROR);
2308	}
2309
2310	public function E404Page()
2311	{
2312		return '<div class="center">'.LAN_UI_404_BODY_ERROR.'</div>';
2313	}
2314
2315
2316	public function E404AjaxPage()
2317	{
2318		exit;
2319	}
2320
2321
2322	public function E403Observer()
2323	{
2324		$this->getResponse()->setTitle(LAN_UI_403_TITLE_ERROR);
2325	}
2326
2327	public function E403Page()
2328	{
2329		return '<div class="center">'.LAN_UI_403_BODY_ERROR.'</div>';
2330	}
2331
2332
2333	public function E403AjaxPage()
2334	{
2335		exit;
2336	}
2337
2338	/**
2339	 * Generic redirect handler, it handles almost everything we would need.
2340	 * Additionally, it moves currently registered system messages to SESSION message stack
2341	 * In almost every case {@link redirectAction()} and {@link redirectMode()} are better solution
2342	 *
2343	 * @param string $action defaults to current action
2344	 * @param string $mode defaults to current mode
2345	 * @param string|array $exclude_query comma delimited variable names to be excluded from current query OR TRUE to exclude everything
2346	 * @param string|array $merge_query query string (&amp; delimiter) or associative array to be merged with current query
2347	 * @param string $path default to e_SELF
2348	 * @return void
2349	 */
2350	public function redirect($action = null, $mode = null, $exclude_query = '', $merge_query = array(), $path = null)
2351	{
2352		$request = $this->getRequest();
2353
2354		if($mode) $request->setMode($mode);
2355		if($action) $request->setAction($action);
2356		if(!$path) $path = e_REQUEST_SELF;
2357
2358		//prevent cache
2359		header('Cache-Control: private, no-store, no-cache, must-revalidate, post-check=0, pre-check=0');
2360	//	header('Pragma: no-cache');
2361
2362		$url = $path.'?'.$request->buildQueryString($merge_query, false, $exclude_query);
2363		// Transfer all messages to session
2364		e107::getMessage()->moveToSession();
2365		// write session data
2366		session_write_close();
2367
2368		// do redirect
2369		e107::redirect($url);
2370	//	header('Location: '.$url);
2371		exit;
2372	}
2373
2374	/**
2375	 * Convenient redirect() proxy method, make life easier when redirecting between actions
2376	 * in same mode.
2377	 *
2378	 * @param string $action [optional]
2379	 * @param string|array $exclude_query [optional]
2380	 * @param string|array $merge_query [optional]
2381	 * @return none
2382	 */
2383	public function redirectAction($action = null, $exclude_query = '', $merge_query = array())
2384	{
2385		$this->redirect($action, null, $exclude_query, $merge_query);
2386	}
2387
2388	/**
2389	 * Convenient redirect to another mode (doesn't use current Query state)
2390	 * If path is empty, it'll be auto-detected from modes (dispatcher) array
2391	 *
2392	 * @param string $mode
2393	 * @param string $action
2394	 * @param string|array $query [optional]
2395	 * @param string $path
2396	 * @return void
2397	 */
2398	public function redirectMode($mode, $action, $query = array(), $path = null)
2399	{
2400		if(!$path && $this->getParam('modes'))
2401		{
2402			$modes = $this->getParam('modes');
2403			if(vartrue($modes[$mode]) && vartrue($modes[$mode]['url']))
2404			{
2405				$path = e107::getParser()->replaceConstants($modes[$mode]['url'], 'abs');
2406			}
2407		}
2408		$this->redirect($action, $mode, true, $query, $path);
2409	}
2410
2411	/**
2412	 * Convert action name to method name
2413	 *
2414	 * @param string $action_name formatted (e.g. request method getActionName()) action name
2415	 * @param string $type page|observer|header|trigger
2416	 * @param boolean $ajax force with true/false, if null will be auto-resolved
2417	 * @return string
2418	 */
2419	public function toMethodName($action_name, $type= 'page', $ajax = null)
2420	{
2421		if(null === $ajax) $ajax = e_AJAX_REQUEST; //auto-resolving
2422		return $action_name.($ajax ? 'Ajax' : '').ucfirst(strtolower($type));
2423	}
2424
2425	/**
2426	 * Check if there is a trigger available in the posted data
2427	 * @param array $exclude
2428	 * @return boolean
2429	 */
2430	public function hasTrigger($exclude = array())
2431	{
2432		$posted = array_keys($this->getPosted());
2433		foreach ($posted as $key)
2434		{
2435			if(!in_array($key, $exclude) && strpos($key, 'etrigger_') === 0)
2436			{
2437				return true;
2438			}
2439		}
2440		return false;
2441	}
2442
2443	/**
2444	 * Get default action
2445	 * @return string action
2446	 */
2447	public function getDefaultAction()
2448	{
2449		return $this->_default_action;
2450	}
2451
2452
2453
2454	public function getDefaultTrigger()
2455	{
2456		return $this->_default_trigger;
2457
2458	}
2459
2460	/**
2461	 * Set default action
2462	 * @param string $action_name
2463	 * @return e_admin_controller
2464	 */
2465	public function setDefaultAction($action_name)
2466	{
2467		$this->_default_action = $action_name;
2468		return $this;
2469	}
2470
2471
2472		/**
2473	 * Set default trigger
2474	 * @param string|array $triggers 'auto' or array of triggers
2475	 * @example $triggers['submit'] = array(LAN_UPDATE, 'update', $model->getId());
2476				$triggers['submit'] = array(LAN_CREATE, 'create', 0);
2477				$triggers['cancel'] = array(LAN_CANCEL, 'cancel');
2478	 * @return e_admin_controller
2479	 */
2480	public function setDefaultTrigger($triggers)
2481	{
2482		$this->_default_trigger = $triggers;
2483		return $this;
2484	}
2485
2486	/**
2487	 * @return boolean
2488	 */
2489	public function triggersEnabled()
2490	{
2491		return $this->getParam('enable_triggers');
2492	}
2493
2494	/**
2495	 * @param boolean $flag
2496	 * @return e_admin_controller
2497	 */
2498	public function setTriggersEnabled($flag)
2499	{
2500		$this->setParam('enable_triggers', $flag);
2501		return $this;
2502	}
2503}
2504
2505//FIXME - move everything from e_admin_ui except model auto-create related code
2506class e_admin_controller_ui extends e_admin_controller
2507{
2508
2509	protected $table;
2510	/**
2511	 * @var array UI field data
2512	 */
2513
2514	/** @var string  */
2515	protected $listQry;
2516
2517	protected $pid;
2518
2519	protected $fields = array();
2520
2521	/**
2522	 * @var array default fields activated on List view
2523	 */
2524	protected $fieldpref = array();
2525
2526	/**
2527	 * Custom Field (User) Preferences Name. (for viewable columns)
2528	 * @var string
2529	 */
2530	protected $fieldPrefName = '';
2531
2532	/**
2533	 * @var array Plugin Preference description array
2534	 */
2535	protected $prefs = array();
2536
2537	/**
2538	 * Data required for _modifyListQry() to automate
2539	 * db query building
2540	 * @var array
2541	 */
2542	protected $tableJoin = array();
2543
2544	/**
2545	 * Array of table names and their aliases. (detected from listQry)
2546	 * db query building
2547	 * @var array
2548	 */
2549	protected $joinAlias = array();
2550
2551	/**
2552	 * Array of fields detected from listQry which are JOINs
2553	 * @example returns array('user_name'=>'u.user_name'); from $listQry = "SELECT n.*,u.user_name FROM #news...."etc.
2554	 */
2555	protected $joinField = array();
2556
2557
2558	/**
2559	 * Main model table alias
2560	 * @var string
2561	 */
2562	protected $tableAlias;
2563
2564	/**
2565	 * @var string plugin name
2566	 */
2567	protected $pluginName;
2568
2569
2570	/**
2571	 * @var string event name
2572	 * base event trigger name to be used. Leave blank for no trigger.
2573	 */
2574	protected $eventName = null;
2575
2576	/**
2577	 * @var string
2578	 */
2579	protected $defaultOrderField = null;
2580
2581	/**
2582	 * @var string
2583	 */
2584	protected $defaultOrder = 'asc';
2585
2586	/**
2587	 * @var string SQL order, false to disable order, null is default order
2588	 */
2589	protected $listOrder = null;
2590
2591	/**
2592	 * @var string SQL group-by field name (optional)
2593	 */
2594	protected $listGroup = null;
2595
2596	/**
2597	 * @var string field containing the order number
2598	 */
2599	protected $sortField = null;
2600
2601	/**
2602	 * @var string field containing the order number
2603	 */
2604	protected $treePrefix = null;
2605
2606	/**
2607	 * @var string field containing the parent field
2608	 */
2609	protected $sortParent = null;
2610
2611	/**
2612	 * @var int reorder step
2613	 */
2614	protected $orderStep = 1;
2615
2616	/**
2617	 * Example: array('0' => 'Tab label', '1' => 'Another label');
2618	 * Referenced from $field property per field - 'tab => xxx' where xxx is the tab key (identifier)
2619	 * @var array edit/create form tabs
2620	 */
2621	protected $tabs = array();
2622
2623	/**
2624	 * Example: array('0' => 'Tab label', '1' => 'Another label');
2625	 * Referenced from $prefs property per field - 'tab => xxx' where xxx is the tab key (identifier)
2626	 * @var array edit/create form tabs
2627	 */
2628	protected $preftabs = array();
2629
2630	/**
2631	 * TODO Example:
2632	 * Contains required data for auto-assembling URL from every record
2633	 * For greater control - override url() method
2634	 * @var array
2635	 */
2636	protected $url = array();
2637
2638	/**
2639	 * TODO Example:
2640	 * Contains required data for mapping featurebox fields
2641	 * @var array
2642	 */
2643	protected $featurebox = array();
2644
2645	/**
2646	 * Structure same as TreeModel parameters used for building the load() SQL
2647	 * @var additional SQL to be applied when auto-building the list query
2648	 */
2649	protected $listQrySql = array();
2650
2651	/**
2652	 * @var Custom Filter SQL Query override.
2653	 */
2654	protected $filterQry = null;
2655
2656	/**
2657	 * @var boolean
2658	 */
2659	protected $batchDelete = true;
2660
2661	/**
2662	 * @var boolean
2663	 */
2664	protected $batchCopy = false;
2665
2666    /**
2667     * @var boolean
2668     */
2669    protected $batchLink = false;
2670
2671    /**
2672     * @var boolean
2673     */
2674    protected $batchFeaturebox = false;
2675
2676	 /**
2677     * @var boolean
2678     */
2679    protected $batchExport = false;
2680
2681	/**
2682	 * @var array
2683	 */
2684	protected $batchOptions = array();
2685
2686	/**
2687	 * Could be LAN constant (mulit-language support)
2688	 *
2689	 * @var string plugin name
2690	 */
2691	protected $pluginTitle;
2692
2693	/**
2694	 * Default (db) limit value
2695	 * @var integer
2696	 */
2697	protected $perPage = 20;
2698
2699
2700	/**
2701	 * Data for grid layout.
2702	 * @var array
2703	 */
2704	protected $grid = array();
2705
2706		/**
2707	 * @var e_admin_model
2708	 */
2709	protected $formQuery = false; // custom form post query
2710
2711	/**
2712	 * @var e_admin_model
2713	 */
2714	protected $_model = null;
2715
2716	/**
2717	 * @var e_admin_tree_model
2718	 */
2719	protected $_tree_model = null;
2720
2721	/**
2722	 * @var e_admin_tree_model
2723	 */
2724	protected $_ui = null;
2725
2726	/**
2727	 * @var e_plugin_pref|e_core_pref
2728	 */
2729	protected $_pref = null;
2730
2731	/**
2732	 * Prevent parsing table aliases more than once
2733	 * @var boolean
2734	 */
2735	protected $_alias_parsed = false;
2736
2737	/**
2738	 * @var bool
2739	 */
2740	protected $afterSubmitOptions = true;
2741
2742	public function getAfterSubmitOptions()
2743	{
2744		return $this->afterSubmitOptions;
2745	}
2746
2747	public function getBatchDelete()
2748	{
2749		return $this->batchDelete;
2750	}
2751
2752	public function getBatchCopy()
2753	{
2754		return $this->batchCopy;
2755	}
2756
2757
2758    public function getBatchLink()
2759    {
2760        return $this->batchLink;
2761    }
2762
2763
2764    public function getBatchFeaturebox()
2765    {
2766        return $this->batchFeaturebox;
2767    }
2768
2769	public function getBatchExport()
2770    {
2771        return $this->batchExport;
2772    }
2773
2774	public function getBatchOptions()
2775	{
2776		return $this->batchOptions;
2777	}
2778
2779
2780	/**
2781	 * @return string
2782	 */
2783	public function getEventName()
2784	{
2785		return  $this->eventName;
2786	}
2787
2788
2789	/**
2790	 * @return string
2791	 */
2792	public function getPluginName()
2793	{
2794		return $this->pluginName;
2795	}
2796
2797	/**
2798	 * @return string
2799	 */
2800	public function getPluginTitle()
2801	{
2802		return deftrue($this->pluginTitle, $this->pluginTitle);
2803	}
2804
2805	/**
2806	 * Get Sort Field data
2807	 * @return string
2808	 */
2809	public function getSortField()
2810	{
2811		return $this->sortField;
2812	}
2813
2814	/**
2815	 * Get Sort Field data
2816	 * @return string
2817	 */
2818	public function getSortParent()
2819	{
2820		return $this->sortParent;
2821	}
2822
2823
2824
2825		/**
2826	 * Get Sort Field data
2827	 * @return string
2828	 */
2829	public function getTreePrefix()
2830	{
2831		return $this->treePrefix;
2832	}
2833
2834	/**
2835	 * Get Tab data
2836	 * @return array
2837	 */
2838	public function getTabs()
2839	{
2840		return $this->tabs;
2841	}
2842
2843	public function addTab($key,$val)
2844	{
2845		$this->tabs[$key] = (string) $val;
2846	}
2847
2848	/**
2849	 * Get Tab data
2850	 * @return array
2851	 */
2852	public function getPrefTabs()
2853	{
2854		return $this->preftabs;
2855	}
2856
2857    /**
2858     * Get URL profile
2859     * @return array
2860     */
2861    public function getUrl()
2862    {
2863        return $this->url;
2864    }
2865
2866
2867      /**
2868     * Get Featurebox Copy
2869     * @return array
2870     */
2871    public function getFeaturebox()
2872    {
2873        return $this->featurebox;
2874    }
2875
2876
2877	/**
2878	 * Get all field data
2879	 * @return array
2880	 */
2881	public function getFields()
2882	{
2883		return $this->fields;
2884	}
2885
2886	/**
2887	 *
2888	 * @param string $field
2889	 * @param string $key attribute name
2890	 * @param mixed $default default value if not set, default is null
2891	 * @return mixed
2892	 */
2893	public function getFieldAttr($field, $key = null, $default = null)
2894	{
2895		if(isset($this->fields[$field]))
2896		{
2897			if(null !== $key)
2898			{
2899				return isset($this->fields[$field][$key]) ? $this->fields[$field][$key] : $default;
2900			}
2901			return $this->fields[$field];
2902		}
2903		return $default;
2904	}
2905
2906	/**
2907	 *
2908	 * @param string|array $field
2909	 * @param string $key attribute name
2910	 * @param mixed $value default value if not set, default is null
2911	 * @return e_admin_controller_ui
2912	 */
2913	public function setFieldAttr($field, $key = null, $value = null)
2914	{
2915		// add field array
2916		if(is_array($field))
2917		{
2918			foreach ($field as $f => $atts)
2919			{
2920				$this->setFieldAttr($f, $atts);
2921			}
2922			return $this;
2923		}
2924		// remove a field
2925		if(null === $key)
2926		{
2927			unset($this->fields[$field]);
2928			return $this;
2929		}
2930		// add to attribute array of a field
2931		if(is_array($key))
2932		{
2933			foreach ($key as $k => $att)
2934			{
2935				$this->setFieldAttr($field, $k, $att);
2936			}
2937			return $this;
2938		}
2939		// remove attribute from field attribute set
2940		if(null === $value && $key != 'type')
2941		{
2942			unset($this->fields[$field][$key]);
2943			return $this;
2944		}
2945		// set attribute value
2946		$this->fields[$field][$key] = $value;
2947		return $this;
2948	}
2949
2950	/**
2951	 * Get fields stored as user preferences
2952	 * @return array
2953	 */
2954	public function getFieldPref()
2955	{
2956		return $this->fieldpref;
2957	}
2958
2959	/**
2960	 * Get Config data array
2961	 * @return array
2962	 */
2963	public function getPrefs()
2964	{
2965		return $this->prefs;
2966	}
2967
2968	public function getPerPage()
2969	{
2970
2971		if($this->getAction() === 'grid')
2972		{
2973			if($this->getGrid('carousel') === true)
2974			{
2975				return 0;
2976			}
2977
2978			return $this->getGrid('perPage');
2979		}
2980
2981
2982		return $this->perPage;
2983	}
2984
2985	public function getGrid($key=null)
2986	{
2987		if($key !== null)
2988		{
2989			return $this->grid[$key];
2990		}
2991
2992		return $this->grid;
2993	}
2994
2995
2996	public function getFormQuery()
2997	{
2998		return $this->formQuery;
2999	}
3000
3001	public function getPrimaryName()
3002	{
3003		return $this->getModel()->getFieldIdName();
3004	}
3005
3006
3007	public function getDefaultOrderField()
3008	{
3009		return ($this->defaultOrder ? $this->defaultOrderField : $this->getPrimaryName());
3010	}
3011
3012	public function getDefaultOrder()
3013	{
3014		return ($this->defaultOrder ? $this->defaultOrder : 'asc');
3015	}
3016
3017	/**
3018	 * Get column preference array
3019	 * @return array
3020	 */
3021	public function getUserPref()
3022	{
3023		//global $user_pref;
3024		// return vartrue($user_pref['admin_cols_'.$this->getTableName()], array());
3025
3026		$name = (!empty($this->fieldPrefName)) ? strtolower($this->pluginName."_".$this->fieldPrefName) : $this->getTableName();
3027
3028		e107::getMessage()->addDebug("Loading Field Preferences using name: ".$name);
3029		$this->_log("Loading Field Preferences using name: ".$name);
3030		return e107::getUser()->getPref('admin_cols_'.$name, array());
3031	}
3032
3033	/**
3034	 * Set column preference array
3035	 * @return boolean success
3036	 */
3037	public function setUserPref($new, $name='')
3038	{
3039		//global $user_pref;
3040		//e107::getUser()->getConfig()->setData($new);
3041		//$user_pref['admin_cols_'.$this->getTableName()] = $new;
3042		//$this->fieldpref = $new;
3043		//return save_prefs('user');
3044		if(!empty($new))
3045        {
3046		    $this->fieldpref = $new;
3047        }
3048
3049		if(empty($name))
3050		{
3051			$name = $this->getTableName();
3052		}
3053		else
3054		{
3055			$name = strtolower($this->pluginName."_".$name);
3056		}
3057
3058        $msg = "Saving User Field preferences using name: ".$name;
3059		e107::getMessage()->addDebug($msg);
3060		$this->_log($msg);
3061
3062		return e107::getUser()->getConfig()
3063			->set('admin_cols_'.$name, $new)
3064			->save();
3065	}
3066
3067	/**
3068	 * Get current model
3069	 *
3070	 * @return e_admin_model
3071	 */
3072	public function getModel()
3073	{
3074		if(null === $this->_model)
3075		{
3076			$this->_setModel();
3077		}
3078
3079		return $this->_model;
3080	}
3081
3082
3083	/**
3084	 * Alias for getModel()->get and getListModel()->get().
3085	 * May be used inside field-method in read/write mode.
3086	 *
3087	 * @param string $key
3088	 * @return mixed|null - current value of the chosen db field.
3089	 */
3090	public function getFieldVar($key = null)
3091	{
3092		if(empty($key))
3093		{
3094			return null;
3095		}
3096
3097		if($this->getAction() === 'list' || $this->getAction() === 'grid')
3098		{
3099			$obj = $this->getListModel();
3100			if(is_object($obj))
3101			{
3102				return $obj->get($key);
3103			}
3104
3105			return null;
3106		}
3107
3108		return $this->getModel()->get($key);
3109
3110	}
3111
3112
3113	/**
3114	 * Set controller model
3115	 * @param e_admin_model $model
3116	 * @return e_admin_controller_ui
3117	 */
3118	public function setModel($model)
3119	{
3120		$this->_model = $model;
3121		return $this;
3122	}
3123
3124	/**
3125	 * Get model validation array
3126	 * @return array
3127	 */
3128	public function getValidationRules()
3129	{
3130		return $this->getModel()->getValidationRules();
3131	}
3132
3133	/**
3134	 * Get model data field array
3135	 * @return array
3136	 */
3137	public function getDataFields()
3138	{
3139		return $this->getModel()->getDataFields();
3140	}
3141
3142	/**
3143	 * Get model table or alias
3144	 * @param boolean $alias get table alias on true, default false
3145	 * @param object $prefix add e107 special '#' prefix, default false
3146	 * @return string
3147	 */
3148	public function getTableName($alias = false, $prefix = false)
3149	{
3150		if($alias) return ($this->tableAlias ? $this->tableAlias : '');
3151		return ($prefix ? '#' : '').$this->getModel()->getModelTable();
3152	}
3153
3154	public function getIfTableAlias($prefix = false, $quote = false) //XXX May no longer by useful. see joinAlias()
3155	{
3156		$alias = $this->getTableName(true);
3157		if($alias)
3158		{
3159			return $alias;
3160		}
3161		return ( !$quote ? $this->getTableName(false, $prefix) : '`'.$this->getTableName(false, $prefix).'`' );
3162	}
3163
3164	/**
3165	 * Get join table data - XXX DEPRECATE?
3166	 * @param string $table if null all data will be returned
3167	 * @param string $att_name search for specific attribute, default null (no search)
3168	 * @return mixed
3169	 */
3170	public function getJoinData($table = null, $att_name = null, $default_att = null)
3171	{
3172		if(null === $table)
3173		{
3174			return $this->tableJoin;
3175		}
3176		if(null === $att_name)
3177		{
3178			return (isset($this->tableJoin[$table]) ? $this->tableJoin[$table] : array());
3179		}
3180		return (isset($this->tableJoin[$table][$att_name]) ? $this->tableJoin[$table][$att_name] : $default_att);
3181	}
3182
3183	public function setJoinData($table, $data) //XXX - DEPRECATE?
3184	{
3185		if(null === $data)
3186		{
3187			unset($this->tableJoin[$table]);
3188			return $this;
3189		}
3190		$this->tableJoin[$table] = (array) $data;
3191		return $this;
3192	}
3193
3194	/**
3195	 * User defined model setter
3196	 * @return e_admin_controller_ui
3197	 */
3198	protected function _setModel()
3199	{
3200		return $this;
3201	}
3202
3203	/**
3204	 * Get current tree model
3205	 * @return e_admin_tree_model
3206	 */
3207	public function getTreeModel()
3208	{
3209		if(null === $this->_tree_model)
3210		{
3211			$this->_setTreeModel();
3212		}
3213
3214		return $this->_tree_model;
3215	}
3216
3217	/**
3218	 * Get ordered models by their parents
3219	 * add extra
3220	 * @lonalore
3221	 * @return e_admin_tree_model
3222	 */
3223	public function getTreeModelSorted()
3224	{
3225		$tree = $this->getTreeModel();
3226
3227		$parentField = $this->getSortParent();
3228		$orderField = $this->getSortField();
3229
3230		$arr = array();
3231		/**
3232		 * @var  $id
3233		 * @var e_tree_model $model
3234		 */
3235		foreach ($tree->getTree() as $id => $model)
3236		{
3237			$parent = $model->get($parentField);
3238			$order = $model->get($orderField);
3239
3240			$model->set('_depth', '9999'); // include extra field in output, just as the MySQL function did.
3241
3242
3243			$arr[$id] = $model;
3244		}
3245
3246
3247	//	usort($arr); array_multisort() ?
3248
3249		$tree->setTree($arr,true); // set the newly ordered tree.
3250
3251	//	var_dump($arr);
3252
3253		return $this->_tree_model;
3254	}
3255
3256
3257	/**
3258	 * @lonalore - found online.
3259	 * @param string $idField       The item's ID identifier (required)
3260	 * @param string $parentField   The item's parent identifier (required)
3261	 * @param array $els            The array (required)
3262	 * @param int   $parentID       The parent ID for which to sort (internal)
3263	 * @param array $result         The result set (internal)
3264	 * @param int   $depth          The depth (internal)
3265	 * @return array
3266	 */
3267	function parentChildSort_r($idField, $parentField, $els=array(), $parentID = 0, &$result = array(), &$depth = 0)
3268	{
3269	    foreach ($els as $key => $value)
3270	    {
3271	        if ($value[$parentField] == $parentID)
3272	        {
3273	            $value['depth'] = $depth;
3274	            array_push($result, $value);
3275	            unset($els[$key]);
3276	            $oldParent = $parentID;
3277	            $parentID = $value[$idField];
3278	            $depth++;
3279	            $this->parentChildSort_r($idField,$parentField, $els, $parentID, $result, $depth);
3280	            $parentID = $oldParent;
3281	            $depth--;
3282	        }
3283	    }
3284
3285	    return $result;
3286	}
3287
3288
3289
3290
3291	/**
3292	 * Set controller tree model
3293	 * @param e_admin_tree_model $tree_model
3294	 * @return e_admin_controller_ui
3295	 */
3296	public function setTreeModel($tree_model)
3297	{
3298		$this->_tree_model = $tree_model;
3299
3300		return $this;
3301	}
3302
3303	/**
3304	 * Get currently parsed model while in list mode
3305	 * Model instance is registered by e_form::renderListForm()
3306	 *
3307	 * @return e_admin_model
3308	 */
3309	public function getListModel()
3310	{
3311		return e107::getRegistry('core/adminUI/currentListModel');
3312	}
3313
3314	public function setListModel($model)
3315	{
3316		e107::setRegistry('core/adminUI/currentListModel', $model);
3317		return $this;
3318	}
3319
3320	/**
3321	 * User defined tree model setter
3322	 * @return e_admin_controller_ui
3323	 */
3324	protected function _setTreeModel()
3325	{
3326		return $this;
3327	}
3328
3329	/**
3330	 * Get extended (UI) Form instance
3331	 *
3332	 * @return e_admin_form_ui
3333	 */
3334	public function getUI()
3335	{
3336		if(null === $this->_ui)
3337		{
3338			$this->_setUI();
3339		}
3340		return $this->_ui;
3341	}
3342
3343	/**
3344	 * Set controller UI form
3345	 * @param e_admin_form_ui $ui
3346	 * @return e_admin_controller_ui
3347	 */
3348	public function setUI($ui)
3349	{
3350		$this->_ui = $ui;
3351		return $this;
3352	}
3353
3354	/**
3355	 * User defined UI form setter
3356	 * @return e_admin_controller_ui
3357	 */
3358	protected function _setUI()
3359	{
3360		return $this;
3361	}
3362
3363	/**
3364	 * Get Config object
3365	 * @return e_plugin_pref or e_core_pref when used in core areas
3366	 */
3367	public function getConfig()
3368	{
3369		if(null === $this->_pref)
3370		{
3371			$this->_setConfig();
3372		}
3373		return $this->_pref;
3374	}
3375
3376	/**
3377	 * Set Config object
3378	 * @return e_admin_controller_ui
3379	 */
3380	public function setConfig($config)
3381	{
3382		$this->_prefs = $config;
3383		return $this;
3384	}
3385
3386
3387	/**
3388	 * @param $val
3389	 */
3390	public function setBatchDelete($val)
3391	{
3392		$this->batchDelete = $val;
3393		return $this;
3394	}
3395
3396
3397
3398	/**
3399	 * @param $val
3400	 */
3401	public function setBatchCopy($val)
3402	{
3403		$this->batchCopy = $val;
3404		return $this;
3405	}
3406
3407
3408	/**
3409	 * User defined config setter
3410	 * @return e_admin_controller_ui
3411	 */
3412	protected function _setConfig()
3413	{
3414		return $this;
3415	}
3416
3417	/**
3418	 * Manage column visibility
3419	 * @param string $batch_trigger
3420	 * @return null
3421	 */
3422	public function manageColumns()
3423	{
3424		$cols = array();
3425		$posted = $this->getPosted('e-columns', array());
3426		foreach ($this->getFields() as $field => $attr)
3427		{
3428			if((/*vartrue($attr['forced']) || */ in_array($field, $posted)) && !vartrue($attr['nolist']))
3429			{
3430				$cols[] = $field;
3431				continue;
3432			}
3433		}
3434
3435		// Alow for an empty array to be saved also, to reset to default.
3436	    if($this->getPosted('etrigger_ecolumns', false)) // Column Save Button
3437		{
3438			$this->setUserPref($cols, $this->fieldPrefName);
3439			e107::getMessage()->addDebug("User Field Preferences Saved: ".print_a($cols,true));
3440		}
3441	}
3442
3443
3444
3445	/**
3446	 * Handle posted batch options routine
3447	 * @param string $batch_trigger
3448	 * @return e_admin_controller_ui
3449	 */
3450	protected function _handleListBatch($batch_trigger)
3451	{
3452		$tp = e107::getParser();
3453		//$multi_name = vartrue($this->fields['checkboxes']['toggle'], 'multiselect');
3454		$multi_name = $this->getFieldAttr('checkboxes', 'toggle', 'multiselect');
3455		$selected = array_values($this->getPosted($multi_name, array()));
3456		$trigger = $tp->toDB(explode('__', $batch_trigger));
3457
3458		if(!empty($selected))
3459		{
3460			foreach ($selected as $i => $_sel)
3461			{
3462				$selected[$i] = preg_replace('/[^\w\-:.]/', '', $_sel);
3463			}
3464		}
3465
3466		// XXX An empty selection should always be permitted for custom batch methods which may apply changes to all records, not only selected ones.
3467
3468
3469		if(substr($batch_trigger, 0, 6) === 'batch_')
3470		{
3471			list($tmp,$plugin,$command) = explode("_",$batch_trigger,3);
3472			$this->setPosted(array());
3473			$this->getRequest()->setAction('batch');
3474			$cls = e107::getAddon($plugin,'e_admin',true);
3475			e107::callMethod($cls,'process',$this,array('cmd'=>$command,'ids'=>$selected));
3476			return $this;
3477		}
3478
3479
3480		$this->setTriggersEnabled(false); //disable further triggering
3481
3482		$actionName = $this->getRequest()->getActionName();
3483
3484		if($actionName === 'Grid')
3485		{
3486			$actionName = 'List';
3487		}
3488
3489
3490		switch($trigger[0])
3491		{
3492
3493			case 'sefgen':
3494				$field = $trigger[1];
3495				$value = $trigger[2];
3496
3497				//handleListBatch(); for custom handling of all field names
3498				if(empty($selected)) return $this;
3499				$method = 'handle'.$actionName.'SefgenBatch';
3500				if(method_exists($this, $method)) // callback handling
3501				{
3502					$this->$method($selected, $field, $value);
3503				}
3504			break;
3505
3506
3507			case 'export':
3508				if(empty($selected)) return $this;
3509				$method = 'handle'.$actionName.'ExportBatch';
3510				if(method_exists($this, $method)) // callback handling
3511				{
3512					$this->$method($selected);
3513				}
3514
3515			break;
3516
3517			case 'delete':
3518				//method handleListDeleteBatch(); for custom handling of 'delete' batch
3519				// if(empty($selected)) return $this;
3520				// don't check selected data - subclass need to check additional post variables(confirm screen)
3521
3522				if(empty($selected) && !$this->getPosted('etrigger_delete_confirm')) // it's a delete batch, confirm screen
3523				{
3524					$params = $this->getFieldAttr($trigger[1], 'writeParms', array());
3525					if(!is_array($params)) parse_str($params, $params);
3526					if(!vartrue($params['batchNoCheck']))
3527					{
3528						return $this;
3529					}
3530				}
3531
3532				$method = 'handle'.$actionName.'DeleteBatch';
3533				if(method_exists($this, $method)) // callback handling
3534				{
3535					$this->$method($selected);
3536				}
3537			break;
3538
3539			case 'bool':
3540				if(empty($selected)) return $this;
3541				$field = $trigger[1];
3542				$value = $trigger[2] ? 1 : 0;
3543				//something like handleListBoolBatch(); for custom handling of 'bool' batch
3544				$method = 'handle'.$actionName.'BoolBatch';
3545				if(method_exists($this, $method)) // callback handling
3546				{
3547					$this->$method($selected, $field, $value);
3548				}
3549			break;
3550
3551			case 'boolreverse':
3552				if(empty($selected)) return $this;
3553				$field = $trigger[1];
3554				//something like handleListBoolreverseBatch(); for custom handling of 'boolreverse' batch
3555				$method = 'handle'.$actionName.'BoolreverseBatch';
3556				if(method_exists($this, $method)) // callback handling
3557				{
3558					$this->$method($selected, $field);
3559				}
3560			break;
3561
3562			// see commma, userclasses batch options
3563			case 'attach':
3564			case 'deattach':
3565			case 'addAll':
3566			case 'clearAll':
3567				if(empty($selected)) return $this;
3568				$field = $trigger[1];
3569				$value = $trigger[2];
3570
3571				if($trigger[0] === 'addAll')
3572				{
3573					$parms = $this->getFieldAttr($field, 'writeParms', array());
3574					if(!is_array($parms)) parse_str($parms, $parms);
3575					unset($parms['__options']);
3576					$value = $parms;
3577					if(empty($value)) return $this;
3578					if(!is_array($value)) $value = array_map('trim', explode(',', $value));
3579				}
3580
3581				if(method_exists($this, 'handleCommaBatch'))
3582				{
3583					$this->handleCommaBatch($selected, $field, $value, $trigger[0]);
3584				}
3585			break;
3586
3587			// append to userclass list
3588			case 'ucadd':
3589			case 'ucremove':
3590				if(empty($selected)) return $this;
3591				$field = $trigger[1];
3592				$class = $trigger[2];
3593				$user = e107::getUser();
3594				$e_userclass = e107::getUserClass();
3595
3596				// check userclass manager class
3597				if (!isset($e_userclass->class_tree[$class]) || !$user->checkClass($e_userclass->class_tree[$class]))
3598				{
3599					return $this;
3600				}
3601
3602				if(method_exists($this, 'handleCommaBatch'))
3603				{
3604					$trigger[0] = $trigger[0] === 'ucadd' ? 'attach' : 'deattach';
3605					$this->handleCommaBatch($selected, $field, $class, $trigger[0]);
3606				}
3607			break;
3608
3609			// add all to userclass list
3610			// clear userclass list
3611			case 'ucaddall':
3612			case 'ucdelall':
3613				if(empty($selected)) return $this;
3614				$field = $trigger[1];
3615				$user = e107::getUser();
3616				$e_userclass = e107::getUserClass();
3617				$parms = $this->getFieldAttr($field, 'writeParms', array());
3618				if(!is_array($parms)) parse_str($parms, $parms);
3619				if(!vartrue($parms['classlist'])) return $this;
3620
3621				$classes = $e_userclass->uc_required_class_list($parms['classlist']);
3622				foreach ($classes as $id => $label)
3623				{
3624					// check userclass manager class
3625					if (!isset($e_userclass->class_tree[$id]) || !$user->checkClass($e_userclass->class_tree[$id]))
3626					{
3627						$msg = $tp->lanVars(LAN_NO_ADMIN_PERMISSION,$label);
3628						$this->getTreeModel()->addMessageWarning($msg);
3629						unset($classes[$id],$msg);
3630					}
3631				}
3632				if(method_exists($this, 'handleCommaBatch'))
3633				{
3634					$this->handleCommaBatch($selected, $field, array_keys($classes), $trigger[0] === 'ucdelall' ? 'clearAll' : 'addAll');
3635				}
3636			break;
3637
3638			// handleListCopyBatch etc.
3639			default:
3640				$field = $trigger[0];
3641				$value = $trigger[1];
3642
3643				//something like handleListUrlTypeBatch(); for custom handling of 'url_type' field name
3644				$method = 'handle'.$actionName.$this->getRequest()->camelize($field).'Batch';
3645
3646				e107::getMessage()->addDebug("Searching for custom batch method: ".$method."(".$selected.",".$value.")");
3647
3648				if(method_exists($this, $method)) // callback handling
3649				{
3650					$this->$method($selected, $value);
3651					break;
3652				}
3653
3654				//handleListBatch(); for custom handling of all field names
3655				//if(empty($selected)) return $this;
3656				$method = 'handle'.$actionName.'Batch';
3657				e107::getDebug()->log("Checking for batch method: ".$method);
3658				if(method_exists($this, $method))
3659				{
3660					$this->$method($selected, $field, $value);
3661				}
3662
3663
3664
3665			break;
3666		}
3667		return $this;
3668	}
3669
3670	/**
3671	 * Handle requested filter dropdown value
3672	 * @param string $filter_value
3673	 * @return array field -> value
3674	 */
3675	protected function _parseFilterRequest($filter_value)
3676	{
3677		$tp = e107::getParser();
3678		if(!$filter_value || $filter_value === '___reset___')
3679		{
3680			return array();
3681		}
3682		$filter = (array) $tp->toDB(explode('__', $filter_value));
3683		$res = array();
3684		switch($filter[0])
3685		{
3686			case 'bool':
3687				// direct query
3688				$res = array($filter[1], $filter[2]);
3689				$this->_log("listQry Filtered by ".$filter[1]." (".($filter[2] ? 'true': 'false').")");
3690			break;
3691
3692			case 'datestamp':
3693
3694				//XXX DO NOT TRANSLATE THESE VALUES!
3695				$dateConvert = array(
3696					"hour"	=> "1 hour ago",
3697					"day"	=> "24 hours ago",
3698					"week"	=> "1 week ago",
3699					"month"	=> "1 month ago",
3700					"month3"	=> "3 months ago",
3701					"month6"	=> "6 months ago",
3702					"month9"	=> "9 months ago",
3703					"year"	=> "1 year ago",
3704					"nhour"	=> "now + 1 hour",
3705					"nday"	=> "now + 24 hours",
3706					"nweek"	=> "now + 1 week",
3707					"nmonth"	=> "now + 1 month",
3708					"nmonth3"	=> "now + 3 months",
3709					"nmonth6"	=> "now + 6 months",
3710					"nmonth9"	=> "now + 9 months",
3711					"nyear"	=> "now + 1 year",
3712				);
3713
3714				$ky = $filter[2];
3715				$time = vartrue($dateConvert[$ky]);
3716				$timeStamp = strtotime($time);
3717
3718				$res = array($filter[1], $timeStamp);
3719
3720				$this->_log("listQry Filtered by ".$filter[1]." (".$time.")");
3721
3722			break;
3723
3724			default:
3725				//something like handleListUrlTypeFilter(); for custom handling of 'url_type' field name filters
3726				$method = 'handle'.$this->getRequest()->getActionName().$this->getRequest()->camelize($filter[0]).'Filter';
3727				$args = array_slice($filter, 1);
3728
3729				e107::getMessage()->addDebug("Searching for custom filter method: ".$method."(".implode(', ', $args).")");
3730
3731
3732				if(method_exists($this, $method)) // callback handling
3733				{
3734					//return $this->$method($filter[1], $selected); selected?
3735					// better approach - pass all values as method arguments
3736					// NOTE - callbacks are allowed to return QUERY as a string, it'll be added in the WHERE clause
3737
3738					e107::getMessage()->addDebug('Executing filter callback <strong>'.get_class($this).'::'.$method.'('.implode(', ', $args).')</strong>');
3739
3740					return call_user_func_array(array($this, $method), $args);
3741				}
3742				else // default handling
3743				{
3744					$res = array($filter[0], $filter[1]);
3745					$this->_log("listQry Filtered by ".$filter[0]." (".$filter[1].")");
3746				}
3747			break;
3748		}
3749
3750		//print_a($res);
3751		//exit;
3752
3753		return $res;
3754	}
3755
3756
3757	/**
3758	 * Convert posted to model values after submit (based on field type)
3759	 * @param array $data
3760	 * @return void
3761	 */
3762	protected function convertToData(&$data)
3763	{
3764		$model = new e_model($data);
3765
3766		foreach ($this->getFields() as $key => $attributes)
3767		{
3768			$value = vartrue($attributes['dataPath']) ? $model->getData($attributes['dataPath'])  : $model->get($key);
3769
3770			if(null === $value)
3771			{
3772				continue;
3773			}
3774			switch($attributes['type'])
3775			{
3776
3777				case 'password': //TODO more encryption options.
3778					if(strlen($value) < 30) // expect a non-md5 value if less than 32 chars.
3779					{
3780						$value = md5($value);
3781					}
3782
3783				break;
3784
3785
3786				case 'datestamp':
3787					if(!is_numeric($value))
3788					{
3789						if(!empty($attributes['writeParms']))
3790						{
3791							if(is_string($attributes['writeParms']))
3792							{
3793								parse_str($attributes['writeParms'],$opt);
3794							}
3795							elseif(is_array($attributes['writeParms']))
3796							{
3797								$opt = $attributes['writeParms'];
3798							}
3799						}
3800
3801
3802						$format = $opt['type'] ? ('input'.$opt['type']) : 'inputdate';
3803						$value = trim($value) ? e107::getDate()->toTime($value, $format) : 0;
3804					}
3805				break;
3806
3807				case 'ip': // TODO - ask Steve if this check is required
3808					//if(strpos($value, '.') !== FALSE)
3809					{
3810						$value = trim($value) ? e107::getIPHandler()->ipEncode($value) : '';
3811					}
3812				break;
3813
3814				case 'dropdown': // TODO - ask Steve if this check is required
3815				case 'lanlist':
3816				case 'userclasses':
3817				case 'comma':
3818				case 'checkboxes':
3819					if(is_array($value))
3820					{
3821						// no sanitize here - data is added to model posted stack
3822						// and validated & sanitized before sent to db
3823						//$value = array_map(array(e107::getParser(), 'toDB'), $value);
3824						$value = implode(',', $value);
3825					}
3826				break;
3827
3828				case 'images':
3829				case 'files':
3830
3831				//	XXX Cam @ SecretR: didn't work here. See model_class.php line 2046.
3832				// if(!is_array($value))
3833			//		{
3834				//		$value = e107::unserialize($value);
3835				//	}
3836				break;
3837
3838
3839			}
3840/*
3841			if($attributes['serialize'] == true)
3842			{
3843				$attributes['data'] = 'array';
3844			}
3845
3846			if($attributes['data'] != 'array')
3847			{
3848				$value = e107::unserialize($value);
3849			}
3850*/
3851
3852			if(vartrue($attributes['dataPath']))
3853			{
3854				$model->setData($attributes['dataPath'], $value);
3855			}
3856			else
3857			{
3858				$model->set($key, $value);
3859			}
3860
3861		}
3862
3863		$data = $model->getData();
3864		unset($model);
3865		$this->toData($data);
3866	}
3867
3868	/**
3869	 * User defined method for converting POSTED to MODEL data
3870	 * @param array $data posted data
3871	 * @param string $type current action type - edit, create, list or user defined
3872	 * @return void
3873	 */
3874	protected function toData(&$data, $type = '')
3875	{
3876	}
3877
3878	/**
3879	 * Take approproate action after successfull submit
3880	 *
3881	 * @param integer $id optional, needed only if redirect action is 'edit'
3882	 * @param string $noredirect_for don't redirect if action equals to its value
3883	 */
3884	protected function doAfterSubmit($id = 0, $noredirect_for = '')
3885	{
3886		if(e_AJAX_REQUEST) return;
3887
3888		if($noredirect_for && $noredirect_for == $this->getPosted('__after_submit_action') && $noredirect_for == $this->getAction())
3889		{
3890			return;
3891		}
3892
3893		$choice = $this->getPosted('__after_submit_action', 0);
3894		switch ($choice) {
3895			case 'create': // create
3896				$this->redirectAction('create', 'id');
3897			break;
3898
3899			case 'edit': // edit
3900				$this->redirectAction('edit', '', 'id='.$id);
3901			break;
3902
3903			case 'list': // list
3904				$this->redirectAction('list', 'id');
3905			break;
3906
3907			default:
3908				$choice = explode('|', str_replace('{ID}', $id, $choice), 3);
3909				$this->redirectAction(preg_replace('/[^\w\-:.]/', '', $choice[0]), vartrue($choice[1]), vartrue($choice[2]));
3910			break;
3911		}
3912		return;
3913	}
3914
3915	/**
3916	 * Build ajax auto-complete filter response
3917	 * @return string response markup
3918	 */
3919	protected function renderAjaxFilterResponse($listQry = '')
3920	{
3921		$debug = false;
3922		$srch = $this->getPosted('searchquery');
3923		$this->getRequest()->setQuery('searchquery', $srch); //_modifyListQry() is requiring GET String
3924
3925		$ret = '<ul>';
3926		$ret .= '<li>'.$srch.'<span class="informal warning"> '.LAN_FILTER_LABEL_TYPED.'</span></li>'; // fix Enter - search for typed word only
3927
3928		$reswords = array();
3929		if(trim($srch) !== '')
3930		{
3931			// Build query
3932			$qry = $this->_modifyListQry(false, true, 0, 20, $listQry);
3933			$this->_log("Filter ListQry: ".$qry);
3934			//file_put_contents(e_LOG.'uiAjaxResponseSQL.log', $qry."\n\n", FILE_APPEND);
3935
3936			// Make query
3937			$sql = e107::getDb();
3938			if($qry && $sql->gen($qry, $debug))
3939			{
3940				while ($res = $sql->fetch())
3941				{
3942					$tmp1 = array();
3943					$tmp = array_values(preg_grep('#'.$srch.'#i', $res));
3944					foreach ($tmp as $w)
3945					{
3946						if($w == $srch)
3947						{
3948							array_unshift($reswords, $w); //exact match
3949							continue;
3950						}
3951						preg_match('#[\S]*('.$srch.')[\S]*#i', $w, $tmp1);
3952						if($tmp1[0]) $reswords[] = $tmp1[0];
3953					}
3954				}
3955			}
3956
3957			// Build response
3958			$reswords = array_unique($reswords);
3959			if($reswords)
3960			{
3961				$ret .= '<li>'.implode("</li>\n\t<li>", $reswords).'</li>';
3962			}
3963		}
3964
3965		$ret .= '<li><span class="informal warning"> '.LAN_FILTER_LABEL_CLEAR.' </span></li>'; // clear filter option
3966		$ret .= '</ul>';
3967		return $ret;
3968	}
3969
3970	/**
3971	 * Given an alias such as 'u' or 'n.news_datestamp' -  will return the associated table such as 'user' or 'news'
3972	 */
3973	function getTableFromAlias($alias)
3974	{
3975		if(strpos($alias,".")!==false)
3976		{
3977			list($alias,$tmp) = explode(".",$alias,2);
3978		}
3979
3980		$tmp = array_flip($this->joinAlias);
3981		return vartrue($tmp[$alias]);
3982	}
3983
3984	public function getJoinField($field=null)
3985	{
3986		if(empty($field))
3987		{
3988			return $this->joinField;
3989		}
3990
3991		return isset($this->joinField[$field]) ? $this->joinField[$field] : false; // vartrue($this->joinField[$field],false);
3992	}
3993
3994	public function getJoinAlias()
3995	{
3996		return $this->joinAlias;
3997	}
3998
3999	/**
4000	 * Parses all available field data, adds internal attributes for handling join requests
4001	 * @return e_admin_controller_ui
4002	 */
4003	protected function parseAliases()
4004	{
4005		if($this->_alias_parsed) return $this; // already parsed!!!
4006
4007		$this->joinAlias($this->listQry); // generate Table Aliases from listQry
4008
4009		if($this->getJoinData())
4010		{
4011			foreach ($this->getJoinData() as $table => $att)
4012			{
4013				if(strpos($table, '.') !== false)
4014				{
4015					$tmp = explode('.', $table, 2);
4016					$this->setJoinData($table, null);
4017					$att['alias'] = $tmp[0];
4018					$att['table'] = $tmp[1];
4019					$att['__tablePath'] = $att['alias'].'.';
4020					$att['__tableFrom'] = '`#'.$att['table'].'` AS '.$att['alias'];
4021					$this->setJoinData($att['alias'], $att);
4022					unset($tmp);
4023					continue;
4024				}
4025				$att['table'] = $table;
4026				$att['alias'] = '';
4027				$att['__tablePath'] = '`#'.$att['table'].'`.';
4028				$att['__tableFrom'] = '`#'.$att['table'].'`';
4029				$this->setJoinData($table, $att);
4030			}
4031		}
4032
4033
4034		if(empty($this->fields))
4035		{
4036			$this->_alias_parsed = true;
4037			return $this;
4038		}
4039
4040
4041		// check for table & field aliases
4042		$fields = array(); // preserve order
4043		foreach ($this->fields as $field => $att)
4044		{
4045			// fieldAlias.fieldName // table name no longer required as it's included in listQry. (see joinAlias() )
4046			if(strpos($field, '.') !== false) // manually entered alias.
4047			{
4048				$tmp = explode('.', $field, 2);
4049				$table = $this->getTableFromAlias($tmp[0]);
4050				$att['table'] = $table;
4051				$att['alias'] = $tmp[0];
4052				$att['field'] = $tmp[1];
4053				$att['__tableField'] = $field;
4054				$att['__tablePath'] = $att['alias'].'.';
4055				$att['__tableFrom'] = "`#".$table."`.".$tmp[1];//." AS ".$att['alias'];
4056				$field = $att['alias'] ? $tmp[1] : $tmp[0];
4057
4058				$fields[$field] = $att;
4059				unset($tmp);
4060			}
4061			else
4062			{
4063
4064				$att['table'] = $this->getIfTableAlias(false);
4065
4066				if($newField = $this->getJoinField($field)) // Auto-Detect.
4067				{
4068					$table = $this->getTableFromAlias($newField); // Auto-Detect.
4069					$att['table'] = $table;
4070					$att['alias'] = $newField;
4071					$att['__tableField'] = $newField;
4072					// $att['__tablePath'] = $newField; ????!!!!!
4073					$att['__tableFrom'] = "`#".$table."`.".$field;//." AS ".$newField;
4074				}
4075				elseif(isset($this->joinAlias[$this->table]) && $field !='checkboxes' && $field !='options')
4076				{
4077					$att['alias'] = $this->joinAlias[$this->table].".".$field;
4078				}
4079				else
4080				{
4081					$att['alias'] = "";
4082				}
4083				$att['field'] = $field;
4084				$fields[$field] = $att;
4085			}
4086
4087			if($fields[$field]['table'] == $this->getIfTableAlias(false))
4088			{
4089				$fields[$field]['__tableField'] = $att['alias'] ? $att['alias'] : $this->getIfTableAlias(true, true).'.'.$att['field'];
4090				$fields[$field]['__tableFrom'] = $this->getIfTableAlias(true, true).'.'.$att['field'].($att['alias'] ? ' AS '.$att['alias'] : '');
4091			}
4092			else
4093			{
4094		//		$fields[$field]['__tableField'] = $this->getJoinData($fields[$field]['table'], '__tablePath').$field;
4095			}
4096			/*
4097			if($fields[$field]['table'])
4098			{
4099				if($fields[$field]['table'] == $this->getIfTableAlias(false))
4100				{
4101					$fields[$field]['__tableField'] = $att['alias'] ? $att['alias'] : $this->getIfTableAlias(true, true).'.'.$att['field'];
4102					$fields[$field]['__tableFrom'] = $this->getIfTableAlias(true, true).'.'.$att['field'].($att['alias'] ? ' AS '.$att['alias'] : '');
4103				}
4104				else
4105				{
4106					$fields[$field]['__tableField'] = $this->getJoinData($fields[$field]['table'], '__tablePath').$field;
4107				}
4108			}
4109			else
4110			{
4111				$fields[$field]['__tableField'] = '`'.$this->getTableName(false, true).'`.'.$field;
4112			}
4113			*/
4114		}
4115
4116
4117		$this->fields = $fields;
4118
4119		$this->_alias_parsed = true;
4120		return $this;
4121	}
4122
4123	/**
4124	 *  Intuitive LEFT JOIN Qry support. (preferred)
4125	 *  Generate array of table names and their alias - auto-detected from listQry;
4126	 *  eg. $listQry = "SELECT m.*, u.user_id,u.user_name FROM #core_media AS m LEFT JOIN #user AS u ON m.media_author = u.user_id";
4127	 */
4128	public function joinAlias($listQry=null)
4129	{
4130		if(!empty($listQry))
4131		{
4132			preg_match_all("/`?#([\w-]+)`?\s*(as|AS)\s*([\w-]+)/im",$listQry,$matches);
4133			$keys = array();
4134			foreach($matches[1] AS $k=>$v)
4135			{
4136				if(varset($matches[3][$k]) && !array_key_exists($v, $this->joinAlias))
4137				{
4138					$this->joinAlias[$v] = $matches[3][$k]; // array. eg $this->joinAlias['core_media'] = 'm';
4139				}
4140
4141				$keys[] = $matches[3][$k];
4142			}
4143
4144			foreach($keys as $alias)
4145			{
4146				preg_match_all("/".$alias."\.([\w]*)/i",$listQry,$match);
4147				foreach($match[1] as $k=>$m)
4148				{
4149					if(empty($m))
4150					{
4151						continue;
4152					}
4153					$this->joinField[$m] = $match[0][$k];
4154				}
4155			}
4156
4157		}
4158		elseif($this->tableJoin)
4159		{
4160			foreach ($this->tableJoin as $tbl => $data)
4161			{
4162				$matches = explode('.', $tbl, 2);
4163				$this->joinAlias[$matches[1]] = $matches[0]; // array. eg $this->joinAlias['core_media'] = 'm';
4164				//'user_name'=>'u.user_name'
4165				if(isset($data['fields']) && $data['fields'] !== '*')
4166				{
4167					$tmp = explode(',', $data['fields']);
4168					foreach ($tmp as $field)
4169					{
4170						$this->joinField[$field] = $matches[0].'.'.$field;
4171					}
4172				}
4173			}
4174		}
4175
4176
4177	}
4178
4179	/**
4180	 * Quick fix for bad custom $listQry;
4181	 */
4182	protected function parseCustomListQry($qry)
4183	{
4184		if(E107_DEBUG_LEVEL == E107_DBG_SQLQUERIES)
4185		{
4186			e107::getMessage()->addDebug('Using Custom listQry ');
4187		}
4188
4189		if(strpos($qry,'`')===false && strpos($qry, 'JOIN')===false)
4190		{
4191			$ret = preg_replace("/FROM\s*(#[\w]*)/","FROM `$1`", $qry);  // backticks missing, so add them.
4192
4193			if($ret)
4194			{
4195				e107::getMessage()->addDebug('Your $listQry is missing `backticks` around the table name! It should look like this'. print_a($ret,true));
4196				return $ret;
4197			}
4198		}
4199
4200		return $qry;
4201	}
4202
4203	/**
4204	 * Fix search string by replacing the commonly used '*' wildcard
4205	 * with the mysql represenation of it '%' and '?' with '_' (single character)
4206	 *
4207	 * @param string $search
4208	 * @return string
4209	 */
4210	protected function fixSearchWildcards($search)
4211	{
4212		$search = trim($search);
4213		if (empty($search))
4214		{
4215			return '';
4216		}
4217
4218		// strip wildcard on the beginning and the end
4219		while (substr($search, 0, 1) == '*') $search = substr($search, 1);
4220		while (substr($search, -1) == '*') $search = substr($search, 0, -1);
4221
4222		// replace "*" wildcard with mysql wildcard "%"
4223		return str_replace(array('*', '?'), array('%', '_'), $search);
4224	}
4225
4226
4227	// TODO - abstract, array return type, move to parent?
4228	protected function _modifyListQry($raw = false, $isfilter = false, $forceFrom = false, $forceTo = false, $listQry = '')
4229	{
4230		$searchQry = array();
4231		$filterFrom = array();
4232		$request  = $this->getRequest();
4233		$tp = e107::getParser();
4234		$tablePath = $this->getIfTableAlias(true, true).'.';
4235		$tableFrom = '`'.$this->getTableName(false, true).'`'.($this->getTableName(true) ? ' AS '.$this->getTableName(true) : '');
4236		$tableSFieldsArr = array(); // FROM for main table
4237		$tableSJoinArr = array(); // FROM for join tables
4238		$filter = array();
4239
4240		$this->listQry = $listQry;
4241
4242		$filterOptions = $request->getQuery('filter_options', '');
4243
4244		$searchQuery = $this->fixSearchWildcards($tp->toDB($request->getQuery('searchquery', '')));
4245		$searchFilter = $this->_parseFilterRequest($filterOptions);
4246
4247		$listQry = $this->listQry; // check for modification during parseFilterRequest();
4248
4249		if(E107_DEBUG_LEVEL == E107_DBG_SQLQUERIES)
4250		{
4251			e107::getMessage()->addDebug('searchQuery: <b>'.$searchQuery.'</b>');
4252		}
4253
4254		if($searchFilter && is_array($searchFilter))
4255		{
4256
4257			list($filterField, $filterValue) = $searchFilter;
4258
4259			if($filterField && $filterValue !== '' && isset($this->fields[$filterField]))
4260			{
4261				$_dataType = $this->fields[$filterField]['data'];
4262				$_fieldType = $this->fields[$filterField]['type'];
4263
4264				if($_fieldType === 'comma' || $_fieldType === 'checkboxes' || $_fieldType === 'userclasses' || ($_fieldType === 'dropdown' && !empty($this->fields[$filterField]['writeParms']['multiple'])))
4265				{
4266					 $_dataType = 'set';
4267				}
4268
4269				switch ($_dataType)
4270				{
4271					case 'set':
4272						$searchQry[] = "FIND_IN_SET('".$tp->toDB($filterValue)."', ".$this->fields[$filterField]['__tableField'].")";
4273					break;
4274
4275					case 'int':
4276					case 'integer':
4277						if($_fieldType === 'datestamp') // Past Month, Past Year etc.
4278						{
4279							if($filterValue > time())
4280							{
4281								$searchQry[] = $this->fields[$filterField]['__tableField']." > ".time();
4282								$searchQry[] = $this->fields[$filterField]['__tableField']." < ".intval($filterValue);
4283							}
4284							else
4285							{
4286								$searchQry[] = $this->fields[$filterField]['__tableField']." > ".intval($filterValue);
4287								$searchQry[] = $this->fields[$filterField]['__tableField']." < ".time();
4288							}
4289
4290						}
4291						else
4292						{
4293							$searchQry[] = $this->fields[$filterField]['__tableField']." = ".intval($filterValue);
4294						}
4295					break;
4296
4297
4298
4299					default: // string usually.
4300
4301						if($filterValue === '_ISEMPTY_')
4302						{
4303							$searchQry[] = $this->fields[$filterField]['__tableField']." = '' ";
4304						}
4305
4306						else
4307						{
4308
4309							if($_fieldType === 'method') // More flexible filtering.
4310							{
4311
4312								$searchQry[] = $this->fields[$filterField]['__tableField']." LIKE \"%".$tp->toDB($filterValue)."%\"";
4313							}
4314							else
4315							{
4316
4317								$searchQry[] = $this->fields[$filterField]['__tableField']." = '".$tp->toDB($filterValue)."'";
4318							}
4319						}
4320
4321						//exit;
4322					break;
4323				}
4324
4325			}
4326				//echo 'type= '. $this->fields[$filterField]['data'];
4327					//	print_a($this->fields[$filterField]);
4328		}
4329		elseif($searchFilter && is_string($searchFilter))
4330		{
4331
4332			// filter callbacks could add to WHERE clause
4333			$searchQry[] = $searchFilter;
4334		}
4335
4336		if(E107_DEBUG_LEVEL == E107_DBG_SQLQUERIES)
4337		{
4338			e107::getMessage()->addDebug(print_a($searchQry,true));
4339		}
4340
4341		$className = get_class($this);
4342
4343		// main table should select everything
4344		$tableSFieldsArr[] = $tablePath.'*';
4345		foreach($this->getFields() as $key => $var)
4346		{
4347			// disabled or system
4348			if((!empty($var['nolist']) && empty($var['filter'])) || empty($var['type']) || empty($var['data']))
4349			{
4350				continue;
4351			}
4352
4353			// select FROM... for main table
4354			if(!empty($var['alias']) && !empty($var['__tableField']))
4355			{
4356				$tableSFieldsArr[] = $var['__tableField'];
4357			}
4358
4359			// filter for WHERE and FROM clauses
4360			$searchable_types = array('text', 'textarea', 'bbarea', 'url', 'ip', 'tags', 'email', 'int', 'integer', 'str', 'string', 'number'); //method? 'user',
4361
4362			if($var['type'] === 'method' && !empty($var['data']) && ($var['data'] === 'string' || $var['data'] === 'str' || $var['data'] === 'int'))
4363			{
4364				$searchable_types[] = 'method';
4365			}
4366
4367			if(trim($searchQuery) !== '' && in_array($var['type'], $searchable_types) && $var['__tableField'])
4368			{
4369				// Search for customer filter handler.
4370				$cutomerSearchMethod = 'handle'.$this->getRequest()->getActionName().$this->getRequest()->camelize($key).'Search';
4371				$args = array($tp->toDB($request->getQuery('searchquery', '')));
4372
4373				e107::getMessage()->addDebug("Searching for custom search method: ".$className.'::'.$cutomerSearchMethod."(".implode(', ', $args).")");
4374
4375				if(method_exists($this, $cutomerSearchMethod)) // callback handling
4376				{
4377					e107::getMessage()->addDebug('Executing custom search callback <strong>'.$className.'::'.$cutomerSearchMethod.'('.implode(', ', $args).')</strong>');
4378
4379					$filter[] = call_user_func_array(array($this, $cutomerSearchMethod), $args);
4380					continue;
4381				}
4382
4383
4384				if($var['data'] === 'int' || $var['data'] === 'integer' ||  $var['type'] === 'int' || $var['type'] === 'integer')
4385				{
4386					if(is_numeric($searchQuery))
4387					{
4388						$filter[] = $var['__tableField']." = ".$searchQuery;
4389					}
4390					continue;
4391				}
4392
4393				if($var['type'] === 'ip')
4394				{
4395					$ipSearch = e107::getIPHandler()->ipEncode($searchQuery);
4396					if(!empty($ipSearch))
4397					{
4398						$filter[] = $var['__tableField']." LIKE '%".$ipSearch."%'";
4399					}
4400					// Continue below for BC check also.
4401				}
4402
4403
4404				if(strpos($searchQuery, " ") !==false) // search multiple words across fields.
4405				{
4406					$tmp = explode(" ", $searchQuery);
4407
4408					if(count($tmp) < 4) // avoid excessively long query.
4409					{
4410						foreach($tmp as $splitSearchQuery)
4411						{
4412							if(!empty($splitSearchQuery))
4413							{
4414								$filter[] = $var['__tableField']." LIKE '%".$splitSearchQuery."%'";
4415							}
4416						}
4417					}
4418					else
4419					{
4420						$filter[] = $var['__tableField']." LIKE '%".$searchQuery."%'";
4421					}
4422
4423				}
4424				else
4425				{
4426					$filter[] = $var['__tableField']." LIKE '%".$searchQuery."%'";
4427				}
4428
4429
4430				if($isfilter)
4431				{
4432					$filterFrom[] = $var['__tableField'];
4433
4434				}
4435			}
4436		}
4437
4438
4439		if(strpos($filterOptions,'searchfield__') === 0) // search in specific field, so remove the above filters.
4440		{
4441			$filter = array(); // reset filter.
4442		}
4443
4444
4445		if(E107_DEBUG_LEVEL == E107_DBG_SQLQUERIES)
4446		{
4447		//	e107::getDebug()->log(print_a($filter,true));
4448			// e107::getMessage()->addInfo(print_a($filter,true));
4449		}
4450
4451		if($isfilter)
4452		{
4453			if(!$filterFrom) return false;
4454			$tableSFields = implode(', ', $filterFrom);
4455		}
4456		else
4457		{
4458			$tableSFields = $tableSFieldsArr ? implode(', ', $tableSFieldsArr) : $tablePath.'*';
4459		}
4460
4461
4462		$jwhere = array();
4463		$joins = array();
4464		//file_put_contents(e_LOG.'uiAjaxResponseSFields.log', $tableSFields."\n\n", FILE_APPEND);
4465		//file_put_contents(e_LOG.'uiAjaxResponseFields.log', print_r($this->getFields(), true)."\n\n", FILE_APPEND);
4466		if($this->getJoinData())
4467		{
4468			$qry = "SELECT SQL_CALC_FOUND_ROWS ".$tableSFields;
4469			foreach ($this->getJoinData() as $jtable => $tparams)
4470			{
4471				// Select fields
4472				if(!$isfilter)
4473				{
4474					$fields = vartrue($tparams['fields']);
4475					if('*' === $fields)
4476					{
4477						$tableSJoinArr[] = "{$tparams['__tablePath']}*";
4478					}
4479					elseif($fields)
4480					{
4481						$tableSJoinArr[] = $fields;
4482						/*$fields = explode(',', $fields);
4483						foreach ($fields as $field)
4484						{
4485							$qry .= ", {$tparams['__tablePath']}`".trim($field).'`';
4486						}*/
4487					}
4488				}
4489
4490				// Prepare Joins
4491				$joins[] = "
4492					".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'] : '');
4493
4494				// Prepare Where
4495				if(vartrue($tparams['where']))
4496				{
4497					$jwhere[] = $tparams['where'];
4498				}
4499			}
4500
4501
4502			//From
4503			$qry .= $tableSJoinArr ? ', '.implode(', ', $tableSJoinArr)." FROM ".$tableFrom : " FROM ".$tableFrom;
4504
4505			// Joins
4506			if(count($joins) > 0)
4507			{
4508				$qry .=  "\n".implode("\n", $joins);
4509			}
4510		}
4511		else    // default listQry
4512		{
4513			if(!empty($listQry))
4514			{
4515				$qry = $this->parseCustomListQry($listQry);
4516			}
4517			elseif($this->sortField && $this->sortParent && !deftrue('e_DEBUG_TREESORT')) // automated 'tree' sorting.
4518			{
4519			//	$qry = "SELECT SQL_CALC_FOUND_ROWS a. *, CASE WHEN a.".$this->sortParent." = 0 THEN a.".$this->sortField." ELSE b.".$this->sortField." + (( a.".$this->sortField.")/1000) END AS treesort FROM `#".$this->table."` AS a LEFT JOIN `#".$this->table."` AS b ON a.".$this->sortParent." = b.".$this->pid;
4520				$qry                = $this->getParentChildQry(true);
4521				//$this->listOrder	= '_treesort '; // .$this->sortField;
4522			//	$this->orderStep    = ($this->orderStep === 1) ? 100 : $this->orderStep;
4523			}
4524			else
4525			{
4526				$qry = "SELECT SQL_CALC_FOUND_ROWS ".$tableSFields." FROM ".$tableFrom;
4527			}
4528
4529		}
4530
4531		// group field - currently auto-added only if there are joins
4532		$groupField = '';
4533		if($joins && $this->getPrimaryName())
4534		{
4535			$groupField = $tablePath.$this->getPrimaryName();
4536		}
4537
4538		// appended to GROUP BY when true.
4539		if(!empty($this->listGroup))
4540		{
4541			$groupField = $this->listGroup;
4542		}
4543
4544		if($raw)
4545		{
4546			$rawData = array(
4547				'joinWhere' => $jwhere,
4548				'filter' => $filter,
4549				'listQrySql' => $this->listQrySql,
4550				'filterFrom' => $filterFrom,
4551				'search' => $searchQry,
4552				'tableFromName' => $tableFrom,
4553			);
4554
4555			$orderField = $request->getQuery('field', $this->getDefaultOrderField());
4556
4557			$rawData['tableFrom'] = $tableSFieldsArr;
4558			$rawData['joinsFrom'] = $tableSJoinArr;
4559			$rawData['joins'] = $joins;
4560			$rawData['groupField'] = $groupField;
4561			$rawData['orderField'] = isset($this->fields[$orderField]) ? $this->fields[$orderField]['__tableField'] : '';
4562			$rawData['orderType'] = $request->getQuery('asc') === 'desc' ? 'DESC' : 'ASC';
4563			$rawData['limitFrom'] = false === $forceFrom ? intval($request->getQuery('from', 0)) : intval($forceFrom);
4564			$rawData['limitTo'] = false === $forceTo ? intval($this->getPerPage()) : intval($forceTo);
4565			return $rawData;
4566		}
4567
4568
4569		// join where
4570		if(count($jwhere) > 0)
4571		{
4572			$searchQry[] = " (".implode(" AND ",$jwhere)." )";
4573		}
4574		// filter where
4575		if(count($filter) > 0)
4576		{
4577			$searchQry[] = " ( ".implode(" OR ",$filter)." ) ";
4578		}
4579
4580		// more user added sql
4581		if(isset($this->listQrySql['db_where']) && $this->listQrySql['db_where'])
4582		{
4583			if(is_array($this->listQrySql['db_where']))
4584			{
4585				$searchQry[] = implode(" AND ", $this->listQrySql['db_where']);
4586			}
4587			else
4588			{
4589				$searchQry[] = $this->listQrySql['db_where'];
4590			}
4591		}
4592
4593
4594
4595		// where query
4596		if(count($searchQry) > 0)
4597		{
4598			// add more where details on the fly via $this->listQrySql['db_where'];
4599			$qry .= (strripos($qry, 'where')==FALSE) ? " WHERE " : " AND "; // Allow 'where' in custom listqry
4600			$qry .= implode(" AND ", $searchQry);
4601
4602			// Disable tree (use flat list instead) when filters are applied
4603			// Implemented out of necessity under https://github.com/e107inc/e107/issues/3204
4604			// Horrible hack, but only needs this one line of additional code
4605			$this->getTreeModel()->setParam('sort_parent', null);
4606		}
4607
4608		// GROUP BY if needed
4609		if($groupField)
4610		{
4611			$qry .= ' GROUP BY '.$groupField;
4612		}
4613
4614		// only when no custom order is required
4615		if($this->listOrder && !$request->getQuery('field') && !$request->getQuery('asc'))
4616		{
4617			$qry .= ' ORDER BY '.$this->listOrder;
4618		}
4619		elseif(false !== $this->listOrder)
4620		{
4621			$orderField = $request->getQuery('field', $this->getDefaultOrderField());
4622			$orderDef = (null === $request->getQuery('asc', null) ? $this->getDefaultOrder() : $request->getQuery('asc'));
4623			if(isset($this->fields[$orderField]) && strpos($this->listQry,'ORDER BY')==FALSE) //override ORDER using listQry (admin->sitelinks)
4624			{
4625				// no need of sanitize - it's found in field array
4626				$qry .= ' ORDER BY '.$this->fields[$orderField]['__tableField'].' '.(strtolower($orderDef) === 'desc' ? 'DESC' : 'ASC');
4627			}
4628		}
4629
4630		if(isset($this->filterQry)) // custom query on filter. (see downloads plugin)
4631		{
4632			$qry = $this->filterQry;
4633		}
4634
4635		if($this->getPerPage() || false !== $forceTo)
4636		{
4637			$from = false === $forceFrom ? intval($request->getQuery('from', 0)) : intval($forceFrom);
4638			if(false === $forceTo) $forceTo = $this->getPerPage();
4639			$qry .= ' LIMIT '.$from.', '.intval($forceTo);
4640		}
4641
4642		// Debug Filter Query.
4643		if(E107_DEBUG_LEVEL == E107_DBG_SQLQUERIES)
4644		{
4645			e107::getMessage()->addDebug('QRY='.str_replace('#', MPREFIX, $qry));
4646		}
4647	//	 echo $qry.'<br />';
4648	// print_a($this->fields);
4649
4650		$this->_log('listQry: '.str_replace('#', MPREFIX, $qry));
4651
4652		return $qry;
4653	}
4654
4655
4656	/**
4657	 * Return a Parent/Child SQL Query based on sortParent and sortField variables
4658	 *
4659	 * Note: Since 2018-01-28, the queries were replaced with pure PHP sorting. See:
4660	 *       https://github.com/e107inc/e107/issues/3015
4661	 *
4662	 * @param bool|false $orderby - include 'ORDER BY' in the qry.
4663	 * @return string
4664	 */
4665	public function getParentChildQry($orderby=false)
4666	{
4667		return "SELECT SQL_CALC_FOUND_ROWS * FROM `#".$this->getTableName()."` ";
4668	}
4669
4670
4671
4672
4673
4674
4675	/**
4676	 * Manage submit item
4677	 * Note: $callbackBefore will break submission if returns false
4678	 *
4679	 * @param string $callbackBefore existing method from $this scope to be called before submit
4680	 * @param string $callbackAfter existing method from $this scope to be called after successfull submit
4681	 * @param string $noredirectAction passed to doAfterSubmit()
4682	 * @return boolean
4683	 */
4684	protected function _manageSubmit($callbackBefore = '', $callbackAfter = '', $callbackError = '', $noredirectAction = '', $forceSave=false)
4685	{
4686
4687		$model = $this->getModel();
4688		$old_data = $model->getData();
4689
4690		$_posted = $this->getPosted();
4691		$this->convertToData($_posted);
4692
4693		if($callbackBefore && method_exists($this, $callbackBefore))
4694		{
4695			$data = $this->$callbackBefore($_posted, $old_data, $model->getId());
4696			if(false === $data)
4697			{
4698				// we don't wanna loose posted data
4699				$model->setPostedData($_posted, null, false);
4700				return false;
4701			}
4702			if($data && is_array($data))
4703			{
4704				// add to model data fields array if required
4705				foreach ($data as $f => $val)
4706				{
4707					if($this->getFieldAttr($f, 'data'))
4708					{
4709						$model->setDataField($f, $this->getFieldAttr($f, 'data'));
4710					}
4711				}
4712				$_posted = array_merge($_posted, $data);
4713			}
4714		}
4715
4716	//	$model->addMessageDebug(print_a($_posted,true));
4717	//	$model->addMessageDebug(print_a($this,true));
4718
4719		// - Autoincrement sortField on 'Create'.
4720
4721
4722		// Prevent parent being assigned as self.
4723		if(!empty($this->sortParent) && $this->getAction() === 'edit' && ($model->getId() == $_posted[$this->sortParent] ) )
4724		{
4725			$vars = array(
4726				'x'=> $this->getFieldAttr($this->sortParent,'title'),
4727				'y'=> $this->getFieldAttr($this->pid,'title'),
4728			);
4729
4730			$message = e107::getParser()->lanVars(LAN_UI_X_CANT_EQUAL_Y, $vars);
4731			$model->addMessageWarning($message);
4732			$model->setMessages();
4733			$this->getUI()->addWarning($this->sortParent);
4734			return false;
4735		}
4736
4737
4738
4739
4740		if(($this->getAction() === 'create') && !empty($this->sortField) && empty($this->sortParent) && empty($_posted[$this->sortField])  )
4741		{
4742
4743			$incVal = e107::getDb()->max($this->table, $this->sortField) + 1;
4744			$_posted[$this->sortField] = $incVal;
4745		//	$model->addMessageInfo(print_a($_posted,true));
4746		}
4747
4748		// Trigger Admin-ui event.  'pre'
4749		if($triggerName = $this->getEventTriggerName($_posted['etrigger_submit'])) // 'create' or 'update';
4750		{
4751			$id = $model->getId();
4752			$eventData = array('newData'=>$_posted,'oldData'=>$old_data,'id'=> $id);
4753			$model->addMessageDebug('Admin-ui Trigger fired: <b>'.$triggerName.'</b>');
4754			$this->_log('Triggering Event: '.$triggerName. " (before)");
4755			if(E107_DBG_ALLERRORS >0 )
4756			{
4757				$model->addMessageDebug($triggerName.' data: '.print_a($eventData,true));
4758			}
4759
4760			if($halt = e107::getEvent()->trigger($triggerName, $eventData))
4761			{
4762				$model->setMessages();
4763				return false;
4764			}
4765		}
4766
4767
4768		// Scenario I - use request owned POST data - toForm already executed
4769		$model->setPostedData($_posted, null, false) // insert() or update() dbInsert();
4770			->save(true, $forceSave);
4771
4772
4773
4774	//	if(!empty($_POST))
4775		{
4776
4777		}
4778
4779		// Scenario II - inner model sanitize
4780		//$this->getModel()->setPosted($this->convertToData($_POST, null, false, true);
4781
4782		// Take action based on use choice after success
4783		if(!$this->getModel()->hasError())
4784		{
4785			// callback (if any)
4786			$new_data 		= $model->getData();
4787			$id 			= $model->getId();
4788
4789			e107::getAddonConfig('e_admin',null,'process', $this, $id);
4790
4791			// Trigger Admin-ui event. 'post'
4792			if($triggerName = $this->getEventTriggerName($_posted['etrigger_submit'],'after')) // 'created' or 'updated';
4793			{
4794				unset($_posted['etrigger_submit'], $_posted['__after_submit_action'], $_posted['submit_value'], $_posted['e-token']);
4795
4796				$pid = $this->getPrimaryName();
4797				$_posted[$pid] = $id; 	// add in the primary ID field.
4798				$eventData = array('newData'=>$_posted,'oldData'=>$old_data,'id'=> $id); // use $_posted as it may include unsaved data.
4799				$model->addMessageDebug('Admin-ui Trigger fired: <b>'.$triggerName.'</b>');
4800				$this->_log('Triggering Event: '.$triggerName." (after)");
4801				if(E107_DBG_ALLERRORS >0 )
4802				{
4803					$model->addMessageDebug($triggerName.' data: '.print_a($eventData,true));
4804				}
4805				e107::getEvent()->trigger($triggerName, $eventData);
4806			}
4807
4808			if($callbackAfter && method_exists($this, $callbackAfter))
4809			{
4810				$this->$callbackAfter($new_data, $old_data, $id);
4811			}
4812			$model->setMessages(true); //FIX - move messages (and session messages) to the default stack
4813			$this->doAfterSubmit($model->getId(), $noredirectAction);
4814			return true;
4815		}
4816		elseif($callbackError && method_exists($this, $callbackError))
4817		{
4818			// suppress messages if callback returns TRUE
4819			if(true !== $this->$callbackError($_posted, $old_data, $model->getId()))
4820			{
4821				// Copy model messages to the default message stack
4822				$model->setMessages();
4823			}
4824			return false;
4825		}
4826
4827		// Copy model messages to the default message stack
4828		$model->setMessages();
4829		return false;
4830	}
4831
4832
4833	/**
4834	 *  Return a custom event trigger name
4835	 * @param null $type  Usually 'Create' or 'Update'
4836	 * @param string $when ' before or after
4837	 * @return bool|string
4838	 */
4839	public function getEventTriggerName($type=null, $when='before')
4840	{
4841		$plug = $this->getEventName();
4842
4843		if(empty($plug) || empty($type))
4844		{
4845			return false;
4846		}
4847
4848		if($when === 'after')
4849		{
4850			$type .= 'd'; // ie. 'created' or 'updated'.
4851		}
4852
4853		return 'admin_'.strtolower($plug).'_'.strtolower($type);
4854
4855	}
4856}
4857
4858class e_admin_ui extends e_admin_controller_ui
4859{
4860
4861	protected $fieldTypes = array();
4862	protected $dataFields = array();
4863	protected $fieldInputTypes = array();
4864	protected $validationRules = array();
4865
4866	protected $table;
4867	protected $pid;
4868	protected $listQry;
4869	protected $editQry;
4870	protected $sortField;
4871	protected $sortParent;
4872	protected $orderStep;
4873	protected $treePrefix;
4874
4875
4876	/**
4877	 * Markup to be auto-inserted before List filter
4878	 * @var string
4879	 */
4880	public $preFilterMarkup = '';
4881
4882	/**
4883	 * Markup to be auto-inserted after List filter
4884	 * @var string
4885	 */
4886	public $postFilterMarkup = '';
4887
4888	/**
4889	 * Markup to be auto-inserted at the top of Create form
4890	 * @var string
4891	 */
4892	public $headerCreateMarkup = '';
4893
4894	/**
4895	 * Markup to be auto-inserted at the bottom of Create form
4896	 * @var string
4897	 */
4898	public $footerCreateMarkup = '';
4899
4900	/**
4901	 * Markup to be auto-inserted at the top of Update form
4902	 * @var string
4903	 */
4904	public $headerUpdateMarkup = '';
4905
4906	/**
4907	 * Markup to be auto-inserted at the bottom of Update form
4908	 * @var string
4909	 */
4910	public $footerUpdateMarkup = '';
4911
4912	/**
4913	 * Show confirm screen before (batch/single) delete
4914	 * @var boolean
4915	 */
4916	public $deleteConfirmScreen = false;
4917
4918	/**
4919	 * Confirm screen custom message
4920	 * @var string
4921	 */
4922	public $deleteConfirmMessage = null;
4923
4924
4925
4926	/**
4927	 * Constructor
4928	 * @param e_admin_request $request
4929	 * @param e_admin_response $response
4930	 * @param array $params [optional]
4931	 */
4932	public function __construct($request, $response, $params = array())
4933	{
4934		$this->setDefaultAction($request->getDefaultAction());
4935		$params['enable_triggers'] = true; // override
4936
4937		parent::__construct($request, $response, $params);
4938
4939		if(!$this->pluginName)
4940		{
4941			$this->pluginName = 'core';
4942		}
4943
4944	/*	$ufieldpref = $this->getUserPref();
4945		if($ufieldpref)
4946		{
4947			$this->fieldpref = $ufieldpref;
4948		}*/
4949
4950		$this->addTitle($this->pluginTitle, true)->parseAliases();
4951
4952		$this->initAdminAddons();
4953
4954
4955		if($help = $this->renderHelp())
4956		{
4957			if(!empty($help))
4958			{
4959				e107::setRegistry('core/e107/adminui/help',$help);
4960			}
4961		}
4962
4963
4964	}
4965
4966
4967	private function initAdminAddons()
4968	{
4969		$tmp = e107::getAddonConfig('e_admin', null, 'config', $this);
4970
4971		if(empty($tmp))
4972		{
4973			return;
4974		}
4975
4976		$opts = null;
4977
4978		foreach($tmp as $plug=>$config)
4979		{
4980
4981			$form = e107::getAddon($plug, 'e_admin', $plug."_admin_form"); // class | false.
4982
4983			if(!empty($config['fields']))
4984			{
4985				if(!empty($this->fields['options']))
4986				{
4987					$opts = $this->fields['options'];
4988					unset($this->fields['options']);
4989				}
4990
4991				foreach($config['fields'] as $k=>$v)
4992				{
4993					$v['data'] = false; // disable data-saving to db table. .
4994
4995					$fieldName = 'x_'.$plug.'_'.$k;
4996					e107::getDebug()->log($fieldName." initiated by ".$plug);
4997
4998					if($v['type'] === 'method' && method_exists($form,$fieldName))
4999					{
5000						$v['method'] = $plug."_admin_form::".$fieldName;
5001						//echo "Found method ".$fieldName." in ".$plug."_menu_form";
5002						//echo $form->$fieldName();
5003					}
5004
5005
5006					$this->fields[$fieldName] = $v; // ie. x_plugin_key
5007
5008				}
5009
5010				if(!empty($opts)) // move options field to the end.
5011				{
5012					$this->fields['options'] = $opts;
5013				}
5014			}
5015
5016			if(!empty($config['batchOptions']))
5017			{
5018				$opts = array();
5019				foreach($config['batchOptions'] as $k=>$v)
5020				{
5021					$fieldName = 'batch_'.$plug.'_'.$k;
5022
5023					$opts[$fieldName] = $v; // ie. x_plugin_key
5024
5025				}
5026
5027				$batchCat = deftrue('LAN_PLUGIN_'.strtoupper($plug).'_NAME', $plug);
5028				$this->batchOptions[$batchCat] = $opts;
5029
5030			}
5031
5032			if(!empty($config['tabs']))
5033			{
5034				foreach($config['tabs'] as $t=>$tb)
5035				{
5036					$this->tabs[$t] = $tb;
5037				}
5038			}
5039
5040
5041		}
5042
5043
5044
5045
5046	}
5047
5048
5049
5050	/**
5051	 * Catch fieldpref submit
5052	 * @return none
5053	 */
5054	public function ListEcolumnsTrigger()
5055	{
5056		$this->setTriggersEnabled(false); //disable further triggering
5057		parent::manageColumns();
5058	}
5059
5060
5061	/**
5062	 * Detect if a batch function has been fired.
5063	 * @param $batchKey
5064	 * @return bool
5065	 */
5066	public function batchTriggered($batchKey)
5067	{
5068		return (!empty($_POST['e__execute_batch']) && (varset($_POST['etrigger_batch']) == $batchKey));
5069	}
5070
5071
5072
5073	/**
5074	 * Catch batch submit
5075	 * @param string $batch_trigger
5076	 * @return none
5077	 */
5078	public function ListBatchTrigger($batch_trigger)
5079	{
5080		$this->setPosted('etrigger_batch', null);
5081
5082		if($this->getPosted('etrigger_cancel'))
5083		{
5084			$this->setPosted(array());
5085			return; // always break on cancel!
5086		}
5087		$this->deleteConfirmScreen = true; // Confirm screen ALWAYS enabled when multi-deleting!
5088
5089		// proceed ONLY if there is no other trigger, except delete confirmation
5090		if($batch_trigger && !$this->hasTrigger(array('etrigger_delete_confirm'))) $this->_handleListBatch($batch_trigger);
5091	}
5092
5093		/**
5094	 * Catch batch submit
5095	 * @param string $batch_trigger
5096	 * @return none
5097	 */
5098	public function GridBatchTrigger($batch_trigger)
5099	{
5100		$this->setPosted('etrigger_batch', null);
5101
5102		if($this->getPosted('etrigger_cancel'))
5103		{
5104			$this->setPosted(array());
5105			return; // always break on cancel!
5106		}
5107		$this->deleteConfirmScreen = true; // Confirm screen ALWAYS enabled when multi-deleting!
5108
5109		// proceed ONLY if there is no other trigger, except delete confirmation
5110		if($batch_trigger && !$this->hasTrigger(array('etrigger_delete_confirm'))) $this->_handleListBatch($batch_trigger);
5111	}
5112
5113	/**
5114	 * Batch delete trigger
5115	 * @param array $selected
5116	 * @return void
5117	 */
5118	protected function handleListDeleteBatch($selected)
5119	{
5120
5121		$tp = e107::getParser();
5122
5123		if(!$this->getBatchDelete())
5124		{
5125			e107::getMessage()->add(LAN_UI_BATCHDEL_ERROR, E_MESSAGE_WARNING);
5126			return;
5127		}
5128		if($this->deleteConfirmScreen)
5129		{
5130			if(!$this->getPosted('etrigger_delete_confirm'))
5131			{
5132				// ListPage will show up confirmation screen
5133				$this->setPosted('delete_confirm_value', implode(',', $selected));
5134				return;
5135			}
5136			else
5137			{
5138				// already confirmed, resurrect selected values
5139				$selected = explode(',', $this->getPosted('delete_confirm_value'));
5140				foreach ($selected as $i => $_sel)
5141				{
5142					$selected[$i] = preg_replace('/[^\w\-:.]/', '', $_sel);
5143				}
5144			}
5145		}
5146
5147		// delete one by one - more control, less performance
5148		// pass  afterDelete() callback to tree delete method
5149		$set_messages = true;
5150		$delcount = 0;
5151		$nfcount = 0;
5152		foreach ($selected as $id)
5153		{
5154			$data = array();
5155			$model = $this->getTreeModel()->getNode($id);
5156			if($model)
5157			{
5158				$data = $model->getData();
5159				if($this->beforeDelete($data, $id))
5160				{
5161					$check = $this->getTreeModel()->delete($id);
5162					if($check) $delcount++;
5163					if(!$this->afterDelete($data, $id, $check))
5164					{
5165						$set_messages = false;
5166					}
5167				}
5168			}
5169			else
5170			{
5171				$set_messages = true;
5172				$nfcount++;
5173			}
5174		}
5175
5176		//$this->getTreeModel()->delete($selected);
5177		if($set_messages)
5178		{
5179			$this->getTreeModel()->setMessages();
5180			// FIXME lan
5181			if($delcount) e107::getMessage()->addSuccess($tp->lanVars(LAN_UI_DELETED, $delcount, true));
5182			if($nfcount) e107::getMessage()->addError($tp->lanVars(LAN_UI_DELETED_FAILED, $nfcount,true));
5183		}
5184
5185		//$this->redirect();
5186	}
5187
5188	/**
5189	 * Batch copy trigger
5190	 * @param array $selected
5191	 * @return void
5192	 */
5193	protected function handleListCopyBatch($selected)
5194	{
5195		// Batch Copy
5196
5197		$res = $this->getTreeModel()->copy($selected);
5198		// callback
5199		$this->afterCopy($res, $selected);
5200		// move messages to default stack
5201		$this->getTreeModel()->setMessages();
5202		// send messages to session
5203		e107::getMessage()->moveToSession();
5204		// redirect
5205		$this->redirect();
5206	}
5207
5208
5209	/**
5210	 * Batch Export trigger
5211	 * @param array $selected
5212	 * @return void
5213	 */
5214	protected function handleListExportBatch($selected)
5215	{
5216		// Batch Copy
5217		$res = $this->getTreeModel()->export($selected);
5218		// callback
5219	//	$this->afterCopy($res, $selected);
5220		// move messages to default stack
5221		$this->getTreeModel()->setMessages();
5222		// send messages to session
5223		e107::getMessage()->moveToSession();
5224		// redirect
5225		$this->redirect();
5226	}
5227
5228
5229		/**
5230	 * Batch Export trigger
5231	 * @param array $selected
5232	 * @return void
5233	 */
5234	protected function handleListSefgenBatch($selected, $field, $value)
5235	{
5236
5237		$tree = $this->getTreeModel();
5238		$c= 0;
5239		foreach($selected as $id)
5240        {
5241        	if(!$tree->hasNode($id))
5242        	{
5243        		e107::getMessage()->addError('Item #ID '.htmlspecialchars($id).' not found.');
5244        		continue;
5245        	}
5246
5247        	$model = $tree->getNode($id);
5248
5249        	$name = $model->get($value);
5250
5251        	$sef = eHelper::title2sef($name,'dashl');
5252
5253
5254
5255
5256
5257        	$model->set($field, $sef);
5258
5259
5260        	$model->save();
5261
5262        	$data = $model->getData();
5263
5264        	if($model->isModified())
5265	        {
5266	           	$this->getModel()->setData($data)->save(false,true);
5267		        $c++;
5268	        }
5269        }
5270
5271
5272
5273		$caption = e107::getParser()->lanVars(LAN_UI_BATCH_BOOL_SUCCESS, $c, true);
5274		e107::getMessage()->addSuccess($caption);
5275
5276	//	e107::getMessage()->moveToSession();
5277		// redirect
5278	//	$this->redirect();
5279	}
5280
5281
5282
5283    /**
5284     * Batch URL trigger
5285     * @param array $selected
5286     * @return void
5287     */
5288    protected function handleListUrlBatch($selected)
5289    {
5290        if($this->_add2nav($selected))
5291		{
5292			e107::getMessage()->moveToSession();
5293			$this->redirect();
5294		}
5295    }
5296
5297
5298	/** TODO
5299	 * Batch Featurebox Transfer
5300	 * @param array $selected
5301	 * @return void
5302	 */
5303	protected function handleListFeatureboxBatch($selected)
5304	{
5305		 if($this->_add2featurebox($selected))
5306		{
5307			e107::getMessage()->moveToSession();
5308			$this->redirect();
5309		}
5310	}
5311
5312	protected function _add2nav($selected)
5313	{
5314		if(empty($selected)) return false;// TODO warning message
5315
5316		if(!is_array($selected)) $selected  = array($selected);
5317
5318        $sql        = e107::getDb();
5319		$urlData	= $this->getUrl();
5320		$allData 	= $this->getTreeModel()->url($selected, array('sc' => true), true);
5321
5322        e107::getMessage()->addDebug('Using Url Route:'.$urlData['route']);
5323
5324		$scount = 0;
5325        foreach($allData as $id => $data)
5326        {
5327            $name = $data['name'];
5328            $desc = $data['description'];
5329
5330            $link = $data['url'];
5331
5332            $link = str_replace('{e_BASE}', "", $link); // TODO temporary here, discuss
5333
5334            // _FIELD_TYPES auto created inside mysql handler now
5335            $linkArray = array(
5336                'link_name'         => $name,
5337                'link_url'          => $link,
5338                'link_description'  => e107::getParser()->toDB($desc), // retrieved field type is string, we might need todb here
5339                'link_button'       => '',
5340                'link_category'     => 255, // Using an unassigned template rather than inactive link-class, since other inactive links may already exist.
5341                'link_order'        => 0,
5342                'link_parent'       => 0,
5343                'link_open'         => '',
5344                'link_class'        => 0,
5345                'link_sefurl'		=> e107::getParser()->toDB($urlData['route'].'?'.$id),
5346            );
5347
5348            $res = $sql->insert('links', $linkArray);
5349
5350            if($res !== FALSE)
5351            {
5352				e107::getMessage()->addSuccess(LAN_CREATED.": ".LAN_SITELINK.": ".($name ? $name : 'n/a'));
5353				$scount++;
5354            }
5355            else
5356            {
5357                if($sql->getLastErrorNumber())
5358                {
5359					e107::getMessage()->addError(LAN_CREATED_FAILED.": ".LAN_SITELINK.": ".$name.": ".LAN_SQL_ERROR);
5360                    e107::getMessage()->addDebug('SQL Link Creation Error #'.$sql->getLastErrorNumber().': '.$sql->getLastErrorText());
5361                }
5362				else
5363				{
5364					e107::getMessage()->addError(LAN_CREATED_FAILED.": ".LAN_SITELINK.": ".$name.": ".LAN_UNKNOWN_ERROR);//Unknown Error
5365				}
5366            }
5367
5368        }
5369
5370		if($scount > 0)
5371		{
5372			e107::getMessage()->addSuccess(LAN_CREATED." (".$scount.") ".ADLAN_138);
5373			e107::getMessage()->addSuccess("<a class='btn btn-small btn-primary' href='".e_ADMIN_ABS."links.php?searchquery=&filter_options=link_category__255'>".LAN_CONFIGURE." ".ADLAN_138."</a>");
5374			return $scount;
5375		}
5376
5377        return false;
5378
5379	}
5380
5381	protected function _add2featurebox($selected)
5382	{
5383		// FIX - don't allow if plugin not installed
5384		if(!e107::isInstalled('featurebox'))
5385		{
5386			return false;
5387		}
5388
5389		if(empty($selected)) return false;// TODO warning message
5390
5391		if(!is_array($selected)) $selected = array($selected);
5392
5393        $sql        = e107::getDb();
5394		$tree = $this->getTreeModel();
5395		$urlData = $this->getTreeModel()->url($selected, array('sc' => true), false);
5396		$data = $this->featurebox;
5397
5398		$scount = 0;
5399        foreach($selected as $id)
5400        {
5401        	if(!$tree->hasNode($id))
5402        	{
5403        		e107::getMessage()->addError('Item #ID '.htmlspecialchars($id).' not found.');
5404        		continue; // TODO message
5405        	}
5406
5407        	$model = $tree->getNode($id);
5408            if($data['url'] === true)
5409			{
5410				$url = $urlData[$id];
5411			}
5412			else $url = $model->get($data['url']);
5413			$name = $model->get($data['name']);
5414
5415			$category = e107::getDb()->retrieve('featurebox_category', 'fb_category_id', "fb_category_template='unassigned'");
5416
5417            $fbArray = array (
5418                	'fb_title' 		=> $name,
5419     				'fb_text' 		=> $model->get($data['description']),
5420					'fb_image' 		=> vartrue($data['image']) ? $model->get($data['image']) : '',
5421					'fb_imageurl'	=> $url,
5422					'fb_class' 		=> isset($data['visibility']) && $data['visibility'] !== false ? $model->get($data['visibility']) : e_UC_ADMIN,
5423					'fb_template' 	=> 'default',
5424					'fb_category' 	=> $category, // TODO popup - choose category
5425					'fb_order' 		=> $scount,
5426            );
5427
5428            $res = $sql->insert('featurebox', $fbArray);
5429
5430            if($res !== FALSE)
5431            {
5432				e107::getMessage()->addSuccess(LAN_CREATED.": ".LAN_PLUGIN_FEATUREBOX_NAME.": ".($name ? $name : 'n/a'));
5433				$scount++;
5434            }
5435            else
5436            {
5437                if($sql->getLastErrorNumber())
5438                {
5439					e107::getMessage()->addError(LAN_CREATED_FAILED.": ".LAN_PLUGIN_FEATUREBOX_NAME.": ".$name.": ".LAN_SQL_ERROR);
5440					e107::getMessage()->addDebug('SQL Featurebox Creation Error #'.$sql->getLastErrorNumber().': '.$sql->getLastErrorText());
5441                }
5442				else
5443				{
5444					e107::getMessage()->addError(LAN_CREATED_FAILED.": ".$name.": ".LAN_UNKNOWN_ERROR);
5445				}
5446            }
5447        }
5448
5449        if($scount > 0)
5450        {
5451			e107::getMessage()->addSuccess(LAN_CREATED." (".$scount.") ".LAN_PLUGIN_FEATUREBOX_NAME);
5452			e107::getMessage()->addSuccess("<a class='btn btn-small btn-primary' href='".e_PLUGIN_ABS."featurebox/admin_config.php?searchquery=&filter_options=fb_category__{$category}'".LAN_CONFIGURE." ".LAN_PLUGIN_FEATUREBOX_NAME."</a>");
5453			return $scount;
5454        }
5455
5456        return false;
5457
5458	}
5459
5460
5461
5462
5463
5464
5465
5466
5467
5468
5469
5470
5471
5472
5473	/**
5474	 * Batch boolean trigger
5475	 * @param array $selected
5476	 * @return void
5477	 */
5478	protected function handleListBoolBatch($selected, $field, $value)
5479	{
5480		$cnt = $this->getTreeModel()->batchUpdate($field, $value, $selected, $value, false);
5481		if($cnt)
5482		{
5483			$caption = e107::getParser()->lanVars(LAN_UI_BATCH_BOOL_SUCCESS, $cnt, true);
5484			$this->getTreeModel()->addMessageSuccess($caption);
5485		}
5486		$this->getTreeModel()->setMessages();
5487	}
5488
5489	/**
5490	 * Batch boolean reverse trigger
5491	 * @param array $selected
5492	 * @return void
5493	 */
5494	protected function handleListBoolreverseBatch($selected, $field)
5495	{
5496		$tree = $this->getTreeModel();
5497		$cnt = $tree->batchUpdate($field, "1-{$field}", $selected, null, false);
5498		if($cnt)
5499		{
5500			$caption = e107::getParser()->lanVars(LAN_UI_BATCH_REVERSED_SUCCESS, $cnt, true);
5501			$tree->addMessageSuccess($caption);
5502			//sync models
5503			$tree->loadBatch(true);
5504		}
5505		$this->getTreeModel()->setMessages();
5506	}
5507
5508	public function handleCommaBatch($selected, $field, $value, $type)
5509	{
5510		$tree = $this->getTreeModel();
5511		$cnt = $rcnt = 0;
5512		$value = e107::getParser()->toDB($value);
5513
5514		switch ($type)
5515		{
5516			case 'attach':
5517			case 'deattach':
5518				$this->_setModel();
5519				foreach ($selected as $key => $id)
5520				{
5521					$node = $tree->getNode($id);
5522					if(!$node) continue;
5523					$val = $node->get($field);
5524
5525					if(empty($val)) $val = array();
5526					elseif(!is_array($val)) $val = explode(',', $val);
5527
5528					if($type === 'deattach')
5529					{
5530						$search = array_search($value, $val);
5531						if(false === $search) continue;
5532						unset($val[$search]);
5533						sort($val);
5534						$val = implode(',', $val);
5535						$node->set($field, $val);
5536						$check = $this->getModel()->setData($node->getData())->save(false, true);
5537
5538						if(false === $check) $this->getModel()->setMessages();
5539						else $rcnt++;
5540						continue;
5541					}
5542
5543					// attach it
5544					if(false === in_array($value, $val))
5545					{
5546						$val[] = $value;
5547						sort($val);
5548						$val = implode(',', array_unique($val));
5549						$node->set($field, $val);
5550						$check = $this->getModel()->setData($node->getData())->save(false, true);
5551						if(false === $check) $this->getModel()->setMessages();
5552						else $cnt++;
5553					}
5554				}
5555				$this->_model = null;
5556			break;
5557
5558			case 'addAll':
5559				if(!empty($value))
5560				{
5561					if(is_array($value))
5562					{
5563						sort($value);
5564						$value = implode(',', array_map('trim', $value));
5565					}
5566
5567					$cnt = $this->getTreeModel()->batchUpdate($field, $value, $selected, true, true);
5568				}
5569				else
5570				{
5571					$this->getTreeModel()->addMessageWarning(LAN_UPDATED_FAILED)->setMessages();//"Comma list is empty, aborting."
5572					$this->getTreeModel()->addMessageDebug(LAN_UPDATED_FAILED.": Comma list is empty, aborting.")->setMessages();
5573				}
5574			break;
5575
5576			case 'clearAll':
5577				$allowed = !is_array($value) ? explode(',', $value) : $value;
5578				if(!$allowed)
5579				{
5580					$rcnt = $this->getTreeModel()->batchUpdate($field, '', $selected, '', true);
5581				}
5582				else
5583				{
5584					$this->_setModel();
5585					foreach ($selected as $key => $id)
5586					{
5587						$node = $tree->getNode($id);
5588						if(!$node) continue;
5589
5590						$val = $node->get($field);
5591
5592						// nothing to do
5593						if(empty($val)) break;
5594						elseif(!is_array($val)) $val = explode(',', $val);
5595
5596						// remove only allowed, see userclass
5597						foreach ($val as $_k => $_v)
5598						{
5599							if(in_array($_v, $allowed))
5600							{
5601								unset($val[$_k]);
5602							}
5603						}
5604
5605						sort($val);
5606						$val = !empty($val) ? implode(',', $val) : '';
5607						$node->set($field, $val);
5608						$check = $this->getModel()->setData($node->getData())->save(false, true);
5609
5610						if(false === $check) $this->getModel()->setMessages();
5611						else $rcnt++;
5612					}
5613					$this->_model = null;
5614				}
5615
5616				// format for proper message
5617				$value = implode(',', $allowed);
5618			break;
5619		}
5620
5621		if($cnt)
5622		{
5623			$vttl = $this->getUI()->renderValue($field, $value, $this->getFieldAttr($field));
5624			$caption = e107::getParser()->lanVars(LAN_UI_BATCH_UPDATE_SUCCESS, array('x'=>$vttl, 'y'=>$cnt), true);
5625			$this->getTreeModel()->addMessageSuccess($caption);
5626		}
5627		elseif($rcnt)
5628		{
5629			$vttl = $this->getUI()->renderValue($field, $value, $this->getFieldAttr($field));
5630			$caption = e107::getParser()->lanVars(LAN_UI_BATCH_DEATTACH_SUCCESS, array('x'=>$vttl, 'y'=>$cnt), true);
5631			$this->getTreeModel()->addMessageSuccess($caption);
5632		}
5633		$this->getTreeModel()->setMessages();
5634	}
5635
5636
5637	/**
5638	 * Method to generate "Search in Field" query.
5639	 * @param $selected
5640	 * @return string
5641	 */
5642	protected function handleListSearchfieldFilter($selected)
5643	{
5644		$string = $this->getQuery('searchquery');
5645
5646
5647
5648		if(empty($string))
5649		{
5650			return null;
5651		}
5652
5653		return $selected. " LIKE '%".e107::getParser()->toDB($string)."%' "; // array($selected, $this->getQuery('searchquery'));
5654	}
5655
5656	/**
5657	 * Batch default (field) trigger
5658	 * @param array $selected
5659	 * @return void
5660	 */
5661	protected function handleListBatch($selected, $field, $value)
5662	{
5663		// special exceptions
5664
5665		if($value === '#delete') // see admin->users
5666		{
5667			$val = "''";
5668			$value = "(empty)";
5669		}
5670		elseif($value === "#null")
5671		{
5672			$val = null;
5673			$value = "(empty)";
5674		}
5675		else
5676		{
5677			$val = "'".$value."'";
5678		}
5679
5680		if($field === 'options') // reserved field type. see: admin -> media-manager - batch rotate image.
5681		{
5682			return;
5683		}
5684
5685
5686
5687
5688		$cnt = $this->getTreeModel()->batchUpdate($field, $val, $selected, true, false);
5689		if($cnt)
5690		{
5691			$vttl = $this->getUI()->renderValue($field, $value, $this->getFieldAttr($field));
5692			$msg = e107::getParser()->lanVars(LAN_UI_BATCH_UPDATE_SUCCESS, array('x' => $vttl, 'y' => $cnt), true);
5693			$this->getTreeModel()->addMessageSuccess($msg);
5694			// force reload the collection from DB, fix some issues as 'observer' is executed before the batch handler
5695			$this->getTreeModel()->setParam('db_query', $this->_modifyListQry(false, false, false, false, $this->listQry))->loadBatch(true);
5696		}
5697		$this->getTreeModel()->setMessages();
5698		return $cnt;
5699	}
5700
5701	/**
5702	 * Catch delete submit
5703	 * @param string $batch_trigger
5704	 * @return none
5705	 */
5706	public function ListDeleteTrigger($posted)
5707	{
5708		if($this->getPosted('etrigger_cancel'))
5709		{
5710			$this->setPosted(array());
5711			return; // always break on cancel!
5712		}
5713
5714		$id = intval(key($posted));
5715		if($this->deleteConfirmScreen && !$this->getPosted('etrigger_delete_confirm'))
5716		{
5717			// forward data to delete confirm screen
5718			$this->setPosted('delete_confirm_value', $id);
5719			return; // User confirmation expected
5720		}
5721
5722		$this->setTriggersEnabled(false);
5723		$data = array();
5724		$model = $this->getTreeModel()->getNode($id); //FIXME - this has issues with being on a page other than the 1st.
5725		if($model)
5726		{
5727			$data = $model->getData();
5728			if($this->beforeDelete($data, $id))
5729			{
5730
5731				$eventData = array('oldData'=>$data,'id'=> $id);
5732
5733				if($triggerName = $this->getEventTriggerName('delete')) // trigger for before.
5734				{
5735
5736					if(E107_DBG_ALLERRORS >0 )
5737					{
5738						$this->getTreeModel()->addMessageDebug('Admin-ui Trigger fired: <b>'.$triggerName.'</b> with data '.print_a($eventData,true));
5739					}
5740
5741					if($halt = e107::getEvent()->trigger($triggerName, $eventData))
5742					{
5743						$this->getTreeModel()->setMessages();
5744						return;
5745					}
5746				}
5747
5748				$check = $this->getTreeModel()->delete($id);
5749
5750				if($this->afterDelete($data, $id, $check))
5751				{
5752					if($triggerName = $this->getEventTriggerName('deleted')) // trigger for after.
5753					{
5754						if(E107_DBG_ALLERRORS > 0)
5755						{
5756							$this->getTreeModel()->addMessageDebug('Admin-ui Trigger fired: <b>'.$triggerName.'</b>'); //FIXME - Why doesn't this display?
5757						}
5758						e107::getEvent()->trigger($triggerName, $eventData);
5759					}
5760
5761					$this->getTreeModel()->setMessages();
5762				}
5763			}
5764			else
5765			{
5766				$this->getTreeModel()->setMessages();// errors
5767			}
5768		}
5769		else  //FIXME - this is a fall-back for the BUG which causes model to fail on all list pages other than the 1st
5770		{
5771			//echo "Couldn't get Node for ID: ".$id;
5772			// exit;
5773			e107::getMessage()->addDebug('Model Failure Fallback in use!! ID: '.$id.' file: '.__FILE__. " line: ".__LINE__ ,'default',true);
5774			$check = $this->getTreeModel()->delete($id);
5775			return;
5776		}
5777	}
5778
5779	/**
5780	 * User defined pre-delete logic
5781	 */
5782	public function beforeDelete($data, $id)
5783	{
5784		return true;
5785	}
5786
5787	/**
5788	 * User defined after-delete logic
5789	 */
5790	public function afterDelete($deleted_data, $id, $deleted_check)
5791	{
5792		return true;
5793	}
5794
5795	/**
5796	 * List action header
5797	 * @return void
5798	 */
5799	public function ListHeader()
5800	{
5801		//e107::js('core','core/tabs.js','prototype');
5802		//e107::js('core','core/admin.js','prototype');
5803	}
5804
5805	/**
5806	 * List action observer
5807	 * @return void
5808	 */
5809	public function ListObserver()
5810	{
5811		if($ufieldpref = $this->getUserPref())
5812		{
5813			$this->fieldpref = $ufieldpref;
5814		}
5815
5816		$table = $this->getTableName();
5817		if(empty($table))
5818		{
5819			return;
5820		}
5821
5822		$this->getTreeModel()->setParam('db_query', $this->_modifyListQry(false, false, false, false, $this->listQry))->loadBatch();
5823
5824		$this->addTitle();
5825
5826		if($this->getQuery('filter_options'))
5827		{
5828		//	var_dump($this);
5829			// $this->addTitle("to-do"); // display filter option when active.
5830		}
5831
5832	}
5833
5834	/**
5835	 * Grid action observer
5836	 */
5837	public function GridObserver()
5838	{
5839
5840		$table = $this->getTableName();
5841		if(empty($table))
5842		{
5843			return;
5844		}
5845		$this->getTreeModel()->setParam('db_query', $this->_modifyListQry(false, false, false, false, $this->listQry))->loadBatch();
5846	}
5847
5848	/**
5849	 * Filter response ajax page
5850	 * @return string
5851	 */
5852	public function FilterAjaxPage()
5853	{
5854		return $this->renderAjaxFilterResponse($this->listQry); //listQry will be used only if available
5855	}
5856
5857	/**
5858	 * Inline edit action
5859	 * @return void
5860	 */
5861	public function InlineAjaxPage()
5862	{
5863		$this->logajax("Inline Ajax Triggered");
5864
5865		$protocol = (isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0');
5866		if(!vartrue($_POST['name']) || !vartrue($this->fields[$_POST['name']]))
5867		{
5868			header($protocol.': 404 Not Found', true, 404);
5869			header("Status: 404 Not Found", true, 404);
5870			echo LAN_FIELD.": ".$this->fields[$_POST['name']].": ".LAN_NOT_FOUND; // Field: x: not found!
5871			$this->logajax('Field not found');
5872			return;
5873		}
5874
5875		$_name = $_POST['name'];
5876		$_value = $_POST['value'];
5877		$_token = $_POST['token'];
5878
5879		$parms = $this->fields[$_name]['readParms'] ? $this->fields[$_name]['readParms'] : '';
5880		if(!is_array($parms)) parse_str($parms, $parms);
5881		if(!empty($parms['editable'])) $this->fields[$_name]['inline'] = true;
5882
5883		if(!empty($this->fields[$_name]['noedit']) || !empty($this->fields[$_name]['nolist']) || empty($this->fields[$_name]['inline']) || empty($_token) || !password_verify(session_id(),$_token))
5884		{
5885			header($protocol.': 403 Forbidden', true, 403);
5886			header("Status: 403 Forbidden", true, 403);
5887			echo ADLAN_86; //Forbidden
5888
5889			$result = var_export($this->fields[$_name], true);
5890
5891			$problem = array();
5892			$problem['noedit'] = !empty($this->fields[$_name]['noedit']) ? 'yes' : 'no';
5893			$problem['nolist'] = !empty($this->fields[$_name]['nolist']) ? 'yes' : 'no';
5894			$problem['inline'] = empty($this->fields[$_name]['inline']) ? 'yes' : 'no';
5895			$problem['token'] = empty($_token) ? 'yes' : 'no';
5896			$problem['password'] = !password_verify(session_id(),$_token) ? 'yes' : 'no';
5897
5898			$result .= "\nForbidden Caused by: ".print_r($problem,true);
5899			$this->logajax("Forbidden\nAction:".$this->getAction()."\nField (".$_name."):\n".$result);
5900			return;
5901		}
5902
5903
5904
5905
5906
5907		$model = $this->getModel()->load($this->getId());
5908		$_POST = array(); //reset post
5909		$_POST[$_name] = $_value; // set current field only
5910		$_POST['etrigger_submit'] = 'update'; // needed for event trigger
5911
5912	//	print_r($_POST);
5913
5914		// generic handler - same as regular edit form submit
5915
5916		$this->convertToData($_POST);
5917
5918		$model->setPostedData($_POST, null, false);
5919		$model->setParam('validateAvailable', true); // new param to control validate of available data only, reset on validate event
5920		// Do not update here! Because $old_data and $new_data will be the same in afterUpdate() methods.
5921		// Data will be saved in _manageSubmit() method.
5922		// $model->update(true);
5923
5924		if($model->hasError())
5925		{
5926			// using 400
5927			header($protocol.': 400 Bad Request', true, 400);
5928			header("Status: 400 Bad Request", true, 400);
5929			$this->logajax("Bad Request");
5930			// DEBUG e107::getMessage()->addError('Error test.', $model->getMessageStackName())->addError('Another error test.', $model->getMessageStackName());
5931
5932
5933			if(E107_DEBUG_LEVEL) $message = e107::getMessage()->get('debug', $model->getMessageStackName(), true);
5934			else $message = e107::getMessage()->get('error', $model->getMessageStackName(), true);
5935
5936			if(!empty($message)) echo implode(' ', $message);
5937			$this->logajax(implode(' ', $message));
5938			return;
5939		}
5940
5941		//TODO ? afterInline trigger?
5942		$res = $this->_manageSubmit('beforeUpdate', 'afterUpdate', 'onUpdateError', 'edit');
5943	}
5944
5945	// Temporary - but useful. :-)
5946	public function logajax($message)
5947	{
5948		if(e_DEBUG !== true)
5949		{
5950			return;
5951		}
5952
5953		$message = date('r')."\n".$message."\n";
5954		$message .= "\n_POST\n";
5955		$message .= print_r($_POST,true);
5956		$message .= "\n_GET\n";
5957		$message .= print_r($_GET,true);
5958
5959		$message .= "---------------";
5960
5961		file_put_contents(e_LOG.'uiAjaxResponseInline.log', $message."\n\n", FILE_APPEND);
5962	}
5963
5964
5965	/**
5966	 * Drag-n-Drop sort action
5967	 * @return void
5968	 */
5969	public function SortAjaxPage()
5970	{
5971		if(!isset($_POST['all']) || empty($_POST['all']))
5972		{
5973			return;
5974		}
5975		if(!$this->sortField)
5976		{
5977			echo 'Missing sort field value';
5978			return;
5979		}
5980
5981		if(!empty($this->sortParent)) // Force 100 positions for child when sorting with parent/child.
5982		{
5983			$this->orderStep = 100;
5984		}
5985
5986
5987		$sql    = e107::getDb();
5988		$step   = $this->orderStep ? intval($this->orderStep) : 1;
5989		$from   = !empty($_GET['from']) ? (int) $_GET['from'] * $step : $step;
5990
5991		$c = $from;
5992		$updated = array();
5993
5994		foreach($_POST['all'] as $row)
5995		{
5996
5997			list($tmp,$id) = explode("-", $row, 2);
5998			$id = preg_replace('/[^\w\-:.]/', '', $id);
5999			if(!is_numeric($id)) $id = "'{$id}'";
6000			if($sql->update($this->table, $this->sortField." = {$c} WHERE ".$this->pid." = ".$id)!==false)
6001			{
6002				$updated[] = "#".$id."  --  ".$this->sortField." = ".$c;
6003			}
6004
6005			// echo($sql->getLastQuery()."\n");
6006			$c += $step;
6007
6008		}
6009
6010
6011		if(!empty($this->sortParent) && !empty($this->sortField) )
6012		{
6013			return null;
6014		}
6015
6016//	file_put_contents(e_LOG."sortAjax.log", print_r($updated,true));
6017
6018		// Increment every other record after the current page of records.
6019	//	$changed = (intval($_POST['neworder']) * $step) + $from ;
6020		$changed = $c - $step;
6021		$qry = "UPDATE `#".$this->table."` e, (SELECT @n := ".($changed).") m  SET e.".$this->sortField." = @n := @n + ".$step." WHERE ".$this->sortField." > ".($changed);
6022
6023		$result = $sql->gen($qry);
6024
6025
6026		// ------------ Fix Child Order when parent is used. ----------------
6027/*
6028		if(!empty($this->sortParent) && !empty($this->sortField) ) // Make sure there is space for at least 99
6029		{
6030			$parent = array();
6031
6032			$data2 = $sql->retrieve($this->table,$this->pid.','.$this->sortField,$this->sortParent .' = 0',true);
6033			foreach($data2 as $val)
6034			{
6035				$id = $val[$this->pid];
6036				$parent[$id] = $val[$this->sortField];
6037
6038			}
6039
6040			$previous = 0;
6041
6042			$data = $sql->retrieve($this->table,'*',$this->sortParent.' != 0 ORDER BY '.$this->sortField,true);
6043
6044			foreach($data as $row)
6045			{
6046				$p = $row[$this->sortParent];
6047
6048				if($p != $previous)
6049				{
6050					$c = $parent[$p];
6051				}
6052
6053				$c++;
6054				$previous = $p;
6055
6056				//	echo "<br />".$row['forum_name']." with parent: ".$p." old: ".$row['forum_order']."  new: ".$c;
6057				$sql->update($this->table, $this->sortField . ' = '.$c.' WHERE '.$this->pid.' = '.intval($row[$this->pid]).' LIMIT 1');
6058
6059			}
6060
6061
6062
6063
6064
6065		}
6066*/
6067		$this->afterSort($result, $_POST);
6068
6069	//	e107::getLog()->addDebug(print_r($_POST,true))->toFile('SortAjax','Admin-UI Ajax Sort Log', true);
6070	//	 e107::getLog()->addDebug(print_r($updated,true))->toFile('SortAjax','Admin-UI Ajax Sort Log', true);
6071	//	e107::getLog()->addDebug($qry)->toFile('SortAjax','Admin-UI Ajax Sort Log', true);
6072
6073	// eg. 	$qry = "UPDATE e107_faqs e, (SELECT @n := 249) m  SET e.faq_order = @n := @n + 1 WHERE 1";
6074
6075	}
6076
6077	/**
6078	 * Generic List action page
6079	 * @return string
6080	 */
6081	public function ListPage()
6082	{
6083		if($this->deleteConfirmScreen && !$this->getPosted('etrigger_delete_confirm') && $this->getPosted('delete_confirm_value'))
6084		{
6085			// 'edelete_confirm_data' set by single/batch delete trigger
6086			return $this->getUI()->getConfirmDelete($this->getPosted('delete_confirm_value')); // User confirmation expected
6087		}
6088		return $this->getUI()->getList();
6089	}
6090
6091	/**
6092	 * Generic List action page
6093	 * @return string
6094	 */
6095	public function GridPage()
6096	{
6097		if($this->deleteConfirmScreen && !$this->getPosted('etrigger_delete_confirm') && $this->getPosted('delete_confirm_value'))
6098		{
6099			// 'edelete_confirm_data' set by single/batch delete trigger
6100			return $this->getUI()->getConfirmDelete($this->getPosted('delete_confirm_value')); // User confirmation expected
6101		}
6102
6103		return $this->getUI()->getList(null,'grid');
6104	}
6105
6106	/**
6107	 * List action observer
6108	 * @return void
6109	 */
6110	public function ListAjaxObserver()
6111	{
6112	    if($ufieldpref = $this->getUserPref())
6113		{
6114			$this->fieldpref = $ufieldpref;
6115		}
6116
6117		$this->getTreeModel()->setParam('db_query', $this->_modifyListQry(false, false, 0, false, $this->listQry))->loadBatch();
6118	}
6119
6120
6121	/**
6122	 * List action observer
6123	 * @return void
6124	 */
6125	public function GridAjaxObserver()
6126	{
6127		$this->ListAjaxObserver();
6128	}
6129
6130	/**
6131	 * Generic List action page (Ajax)
6132	 * @return string
6133	 */
6134	public function ListAjaxPage()
6135	{
6136		return $this->getUI()->getList(true);
6137	}
6138
6139
6140	public function GridAjaxPage()
6141	{
6142		return $this->getUI()->getList(true,'grid');
6143	}
6144
6145	/**
6146	 * Generic Edit observer
6147	 */
6148	public function EditObserver()
6149	{
6150		$this->getModel()->load($this->getId());
6151		$this->addTitle();
6152		$this->addTitle('#'.$this->getId()); // Inform user of which record is being edited.
6153	}
6154
6155	/**
6156	 * Generic Create submit trigger
6157	 */
6158	public function EditCancelTrigger()
6159	{
6160		$this->redirectAction('list', 'id');
6161	}
6162
6163	/**
6164	 * Generic Edit submit trigger
6165	 */
6166	public function EditSubmitTrigger()
6167	{
6168		$this->_manageSubmit('beforeUpdate', 'afterUpdate', 'onUpdateError', 'edit');
6169	}
6170
6171	/**
6172	 * Edit - send JS to page Header
6173	 * @return none
6174	 */
6175	function EditHeader()
6176	{
6177		// e107::getJs()->requireCoreLib('core/admin.js');
6178		e107::js('core','core/admin.js','prototype');
6179	}
6180
6181	/**
6182	 * Generic Edit page
6183	 * @return string
6184	 */
6185	public function EditPage()
6186	{
6187		return $this->CreatePage();
6188	}
6189
6190	/**
6191	 * Generic Create observer
6192	 * @return string
6193	 */
6194	public function CreateObserver()
6195	{
6196		$this->setTriggersEnabled(true);
6197		$this->addTitle();
6198	}
6199
6200	/**
6201	 * Generic Create submit trigger
6202	 */
6203	public function CreateCancelTrigger()
6204	{
6205		$this->redirectAction('list', 'id');
6206	}
6207
6208	/**
6209	 * Generic Create submit trigger
6210	 */
6211	public function CreateSubmitTrigger()
6212	{
6213		$this->_manageSubmit('beforeCreate', 'afterCreate', 'onCreateError');
6214	}
6215
6216	/**
6217	 * User defined pre-create logic, return false to prevent DB query execution
6218	 * @param $new_data
6219	 * @param $old_data
6220	 */
6221	public function beforeCreate($new_data, $old_data)
6222	{
6223	}
6224
6225	/**
6226	 * User defined after-create logic
6227	 * @param $new_data
6228	 * @param $old_data
6229	 * @param $id
6230	 */
6231	public function afterCreate($new_data, $old_data, $id)
6232	{
6233	}
6234
6235	/**
6236	 * User defined error handling, return true to suppress model messages
6237	 * @param $new_data
6238	 * @param $old_data
6239	 */
6240	public function onCreateError($new_data, $old_data)
6241	{
6242	}
6243
6244	/**
6245	 * User defined pre-update logic, return false to prevent DB query execution
6246	 * @param $new_data
6247	 * @param $old_data
6248	 */
6249	public function beforeUpdate($new_data, $old_data, $id)
6250	{
6251	}
6252
6253
6254
6255	/**
6256	 * User defined after-update logic
6257	 * @param $new_data
6258	 * @param $old_data
6259	 */
6260	public function afterUpdate($new_data, $old_data, $id)
6261	{
6262	}
6263
6264	/**
6265	 * User defined before pref saving logic
6266	 * @param $new_data
6267	 * @param $old_data
6268	 */
6269	public function beforePrefsSave($new_data, $old_data)
6270	{
6271	}
6272
6273	/**
6274	 * User defined before pref saving logic
6275	 */
6276	public function afterPrefsSave()
6277	{
6278
6279	}
6280
6281	/**
6282    * User defined error handling, return true to suppress model messages
6283    */
6284	public function onUpdateError($new_data, $old_data, $id)
6285	{
6286	}
6287
6288	/**
6289	 * User defined after-update logic
6290	 * @param mixed $result
6291	 * @param array $selected
6292	 * @return void
6293	 */
6294	public function afterCopy($result, $selected)
6295	{
6296	}
6297
6298
6299	/**
6300	 * User defined after-sort logic
6301	 * @param mixed $result
6302	 * @param array $selected
6303	 * @return void
6304	 */
6305	public function afterSort($result, $selected)
6306	{
6307	}
6308
6309
6310	/**
6311	 * @return string
6312	 */
6313	public function renderHelp()
6314	{
6315
6316	}
6317
6318	/**
6319	 * Create - send JS to page Header
6320	 * @return none
6321	 */
6322	function CreateHeader()
6323	{
6324		// TODO - invoke it on className (not all textarea elements)
6325		//e107::getJs()->requireCoreLib('core/admin.js');
6326		e107::js('core','core/admin.js','prototype');
6327	}
6328
6329	/**
6330	 *
6331	 * @return string
6332	 */
6333	public function CreatePage()
6334	{
6335		return $this->getUI()->getCreate();
6336	}
6337
6338	public function PrefsSaveTrigger()
6339	{
6340		$data = $this->getPosted();
6341
6342		$beforePref = $data;
6343		unset($beforePref['e-token'],$beforePref['etrigger_save']);
6344
6345		$tmp = $this->beforePrefsSave($beforePref, $this->getConfig()->getPref());
6346
6347		if(!empty($tmp))
6348		{
6349			$data = $tmp;
6350		}
6351
6352		foreach($this->prefs as $k=>$v) // fix for empty checkboxes - need to save a value.
6353		{
6354			if(!isset($data[$k]) && $v['data'] !== false && ($v['type'] === 'checkboxes' || $v['type'] === 'checkbox'))
6355			{
6356				$data[$k] = null;
6357			}
6358		}
6359
6360		foreach($data as $key=>$val)
6361		{
6362
6363			if(!empty($this->prefs[$key]['multilan']))
6364			{
6365
6366				if(is_string($this->getConfig()->get($key))) // most likely upgraded to multilan=>true, so reset to an array structure.
6367				{
6368					$this->getConfig()->setPostedData($key, array(e_LANGUAGE => $val), false);
6369				}
6370				else
6371				{
6372					$lang = key($val);
6373					$value = $val[$lang];
6374					$this->getConfig()->setData($key.'/'.$lang, str_replace("'", '&#39;', $value));
6375				}
6376
6377			}
6378			else
6379			{
6380				$this->getConfig()->setPostedData($key, $val, false);
6381			}
6382
6383		}
6384
6385		$this->getConfig()->save(true);
6386
6387		$this->afterPrefsSave();
6388
6389/*
6390		$this->getConfig()
6391			->setPostedData($this->getPosted(), null, false)
6392			//->setPosted('not_existing_pref_test', 1)
6393			->save(true);
6394*/
6395
6396		$this->getConfig()->setMessages();
6397
6398	}
6399
6400	public function PrefsObserver()
6401	{
6402		$this->addTitle();
6403	}
6404
6405	public function PrefsPage()
6406	{
6407		return $this->getUI()->getSettings();
6408	}
6409
6410	/**
6411	 * Parent overload
6412	 * @return e_admin_ui
6413	 */
6414	protected function parseAliases()
6415	{
6416		// parse table
6417		if(strpos($this->table, '.') !== false)
6418		{
6419			$tmp = explode('.', $this->table, 2);
6420			$this->table = $tmp[1];
6421			$this->tableAlias = $tmp[0];
6422			unset($tmp);
6423		}
6424
6425		parent::parseAliases();
6426
6427		return $this;
6428	}
6429
6430	public function getPrimaryName()
6431	{
6432		// Option for working with tables having no PID
6433		if(!varset($this->pid) && vartrue($this->fields) && false !== $this->pid)
6434		{
6435			$message = e107::getParser()->toHTML(LAN_UI_NOPID_ERROR,true);
6436			e107::getMessage()->add($message, E_MESSAGE_WARNING);
6437		}
6438
6439		return $this->pid;
6440	}
6441
6442	public function getTableName($alias = false, $prefix = false)
6443	{
6444		if($alias) return ($this->tableAlias ? $this->tableAlias : '');
6445		return ($prefix ? '#' : '').$this->table;
6446	}
6447
6448	/**
6449	 * Validation rules retrieved from controller object
6450	 * @return array
6451	 */
6452	public function getValidationRules()
6453	{
6454		return $this->validationRules;
6455	}
6456
6457	/**
6458	 * Data Field array retrieved from controller object
6459	 * @return array
6460	 */
6461	public function getDataFields()
6462	{
6463		return $this->dataFields;
6464	}
6465
6466
6467	/**
6468	 * Set read and write parms with drop-down-list array data (ie. type='dropdown')
6469	 * @param str $field
6470	 * @param array $array [optional]
6471	 * @return none
6472	 */
6473	public function setDropDown($field,$array) //TODO Have Miro check this.
6474	{
6475		$this->fields[$field]['readParms'] = $array;
6476		$this->fields[$field]['writeParms'] = $array;
6477	}
6478
6479
6480	/**
6481	 * Set Config object
6482	 * @return e_admin_ui
6483	 */
6484	protected function _setConfig()
6485	{
6486		$this->_pref = $this->pluginName === 'core' ? e107::getConfig() : e107::getPlugConfig($this->pluginName);
6487
6488		if($this->pluginName !== 'core' && !e107::isInstalled($this->pluginName))
6489		{
6490			$obj = get_class($this);
6491			e107::getMessage()->addWarning($obj."The plugin is not installed or \$pluginName: is not valid. (".$this->pluginName. ")"); // debug only.
6492			return $this;
6493		}
6494
6495		$dataFields = $validateRules = array();
6496		foreach ($this->prefs as $key => $att)
6497		{
6498			// create dataFields array
6499			$dataFields[$key] = vartrue($att['data'], 'string');
6500
6501			// create validation array
6502			if(vartrue($att['validate']))
6503			{
6504				$validateRules[$key] = array((true === $att['validate'] ? 'required' : $att['validate']), varset($att['rule']), $att['title'], varset($att['error'], $att['help']));
6505			}
6506			/* Not implemented in e_model yet
6507			elseif(vartrue($att['check']))
6508			{
6509				$validateRules[$key] = array($att['check'], varset($att['rule']), $att['title'], varset($att['error'], $att['help']));
6510			}*/
6511		}
6512		$this->_pref->setDataFields($dataFields)->setValidationRules($validateRules);
6513
6514		return $this;
6515	}
6516
6517	/**
6518	 * Set current model
6519	 *
6520	 * @return e_admin_ui
6521	 */
6522	public function _setModel()
6523	{
6524		// try to create dataFields array if missing
6525
6526		if(!$this->dataFields)
6527		{
6528			$this->dataFields = array();
6529			foreach ($this->fields as $key => $att)
6530			{
6531				if($key == $this->pid && empty($att['data'])) // Set integer as default for primary ID when not specified. MySQL Strict Fix.
6532				{
6533					$this->dataFields[$key] = 'int';
6534					continue;
6535				}
6536
6537				if(varset($att['type']) === 'comma' && (empty($att['data']) || empty($att['rule'])))
6538				{
6539					$att['data'] = 'set';
6540					$att['validate'] = 'set';
6541					$_parms = vartrue($att['writeParms'], array());
6542					if(is_string($_parms)) parse_str($_parms, $_parms);
6543					unset($_parms['__options']);
6544					$att['rule'] = $_parms;
6545					unset($_parms);
6546				}
6547
6548				if(!empty($att['data']) && $att['data'] === 'array' && ($this->getAction() === 'inline')) // FIX for arrays being saved incorrectly with inline editing.
6549				{
6550					$att['data'] = 'set';
6551				}
6552
6553				if(($key !== 'options' && false !== varset($att['data']) && null !== varset($att['type'],null) && !vartrue($att['noedit'])) || vartrue($att['forceSave']))
6554				{
6555					$this->dataFields[$key] = vartrue($att['data'], 'str');
6556					if(!empty($att['type']))
6557					{
6558						$this->fieldInputTypes[$key] = $att['type'];
6559					}
6560				}
6561
6562
6563
6564			}
6565		}
6566
6567		// TODO - do it in one loop, or better - separate method(s) -> convertFields(validate), convertFields(data),...
6568		if(!$this->validationRules)
6569		{
6570			$this->validationRules = array();
6571			foreach ($this->fields as $key => $att)
6572			{
6573				if(null === varset($att['type'], null) || vartrue($att['noedit']))
6574				{
6575					continue;
6576				}
6577				if(vartrue($att['validate']))
6578				{
6579					$this->validationRules[$key] = array((true === $att['validate'] ? 'required' : $att['validate']), varset($att['rule']), $att['title'], varset($att['error'], vartrue($att['help'])));
6580				}
6581				/*elseif(vartrue($att['check'])) could go?
6582				{
6583					$this->checkRules[$key] = array($att['check'], varset($att['rule']), $att['title'], varset($att['error'], $att['help']));
6584				}*/
6585			}
6586		}
6587
6588		// don't touch it if already exists
6589		if($this->_model) return $this;
6590
6591		// default model
6592
6593
6594		$this->_model = new e_admin_model();
6595		$this->_model->setModelTable($this->table)
6596			->setFieldIdName($this->pid)
6597			->setUrl($this->url)
6598			->setValidationRules($this->validationRules)
6599			->setDbTypes($this->fieldTypes)
6600			->setFieldInputTypes($this->fieldInputTypes)
6601			->setDataFields($this->dataFields)
6602			->setMessageStackName('admin_ui_model_'.$this->table)
6603			->setParam('db_query', $this->editQry);
6604
6605		return $this;
6606	}
6607
6608	/**
6609	 * Set current tree
6610	 * @return e_admin_ui
6611	 */
6612	public function _setTreeModel()
6613	{
6614		// default tree model
6615		$this->_tree_model = new e_admin_tree_model();
6616		$this->_tree_model->setModelTable($this->table)
6617			->setFieldIdName($this->pid)
6618			->setUrl($this->url)
6619			->setMessageStackName('admin_ui_tree_'.$this->table)
6620			->setParams(array('model_class' => 'e_admin_model',
6621			                  'model_message_stack' => 'admin_ui_model_'.$this->table,
6622			                  'db_query' => $this->listQry,
6623			                  // Information necessary for PHP-based tree sort
6624			                  'sort_parent' => $this->getSortParent(),
6625			                  'sort_field' => $this->getSortField(),
6626			                  'primary_field' => $this->getPrimaryName(),
6627			                  ));
6628
6629		return $this;
6630	}
6631
6632	/**
6633	 * Get extended (UI) Form instance
6634	 *
6635	 * @return e_admin_ui
6636	 */
6637	public function _setUI()
6638	{
6639		if($this->getParam('ui'))
6640		{
6641			$this->_ui = $this->getParam('ui');
6642			$this->setParam('ui', null);
6643		}
6644		else// default ui
6645		{
6646			$this->_ui = new e_admin_form_ui($this);
6647		}
6648		return $this;
6649	}
6650
6651}
6652
6653class e_admin_form_ui extends e_form
6654{
6655	/**
6656	 * @var e_admin_ui
6657	 */
6658	protected $_controller = null;
6659	protected $_list_view  = null;
6660
6661
6662
6663	/**
6664	 * Constructor
6665	 * @param e_admin_controller_ui $controller
6666	 * @param boolean $tabindex [optional] enable form element auto tab-indexing
6667	 */
6668	function __construct($controller, $tabindex = false)
6669	{
6670		$this->_controller = $controller;
6671		parent::__construct($tabindex);
6672
6673		// protect current methods from conflict.
6674		$this->preventConflict();
6675		// user constructor
6676		$this->init();
6677	}
6678
6679	protected function preventConflict()
6680	{
6681		$err = false;
6682		$fields = $this->getController()->getFields();
6683
6684		if(empty($fields))
6685		{
6686			return null;
6687		}
6688
6689		foreach($fields as $field => $foptions)
6690		{
6691			// check form custom methods
6692			if(vartrue($foptions['type']) === 'method' && method_exists('e_form', $field)) // check even if type is not method. - just in case of an upgrade later by 3rd-party.
6693			{
6694				$message = e107::getParser()->lanVars(LAN_UI_FORM_METHOD_ERROR, array('x'=>$field), true);
6695				e107::getMessage()->addError($message);
6696				$err = true;
6697			}
6698		}
6699
6700		/*if($err)
6701		{
6702			//echo $err;
6703			//exit;
6704		}*/
6705	}
6706
6707
6708
6709	/**
6710	 * User defined init
6711	 */
6712	public function init()
6713	{
6714	}
6715
6716
6717	/**
6718	 * @todo Get a 'depth/level' field working with mysql and change the 'level' accordingly
6719	 * @param mixed $curVal
6720	 * @param string $mode read|write|inline
6721	 * @param array $parm
6722	 * @return array|string
6723	 */
6724	public function treePrefix($curVal, $mode, $parm)
6725	{
6726		$controller         = $this->getController();
6727		$parentField        = $controller->getSortParent();
6728		$treePrefixField    = $controller->getTreePrefix();
6729		$parent 	        = $controller->getListModel()->get($parentField);
6730		$level              = $controller->getListModel()->get("_depth");
6731
6732
6733		if($mode === 'read')
6734		{
6735
6736			$inline = $this->getController()->getFieldAttr($treePrefixField,'inline');
6737
6738			if($inline === true)
6739			{
6740				return $curVal;
6741			}
6742
6743			$level_image = $parent ? str_replace('level-x','level-'.$level, ADMIN_CHILD_ICON) : '';
6744
6745			return ($parent) ?  $level_image.$curVal : $curVal;
6746
6747		}
6748
6749
6750		if($mode === 'inline')
6751		{
6752			$ret = array('inlineType'=>'text');
6753
6754			if(!empty($parent))
6755			{
6756				$ret['inlineParms'] = array('pre'=> str_replace('level-x','level-'.$level, ADMIN_CHILD_ICON));
6757			}
6758
6759
6760			return $ret;
6761		}
6762
6763
6764/*
6765			if($mode == 'write') //  not used.
6766			{
6767			//	return $frm->text('forum_name',$curVal,255,'size=xxlarge');
6768			}
6769
6770			if($mode == 'filter')
6771			{
6772				return;
6773			}
6774			if($mode == 'batch')
6775			{
6776				return;
6777			}
6778*/
6779
6780
6781
6782
6783	}
6784
6785
6786	/**
6787	 * Generic DB Record Creation Form.
6788	 * @return string
6789	 */
6790	function getCreate()
6791	{
6792		$controller = $this->getController();
6793		$request = $controller->getRequest();
6794		if($controller->getId())
6795		{
6796			$legend = e107::getParser()->lanVars(LAN_UI_EDIT_LABEL, $controller->getId()); // sprintXXX(LAN_UI_EDIT_LABEL, $controller->getId());
6797			$form_start = vartrue($controller->headerUpdateMarkup);
6798			$form_end = vartrue($controller->footerUpdateMarkup);
6799		}
6800		else
6801		{
6802			$legend = LAN_UI_CREATE_LABEL;
6803			$form_start = vartrue($controller->headerCreateMarkup);
6804			$form_end = vartrue($controller->footerCreateMarkup);
6805		}
6806
6807		$tabs = $controller->getTabs();
6808
6809		if($multiLangInfo = $this->renderLanguageTableInfo())
6810		{
6811			if(empty($tabs))
6812			{
6813				$head = "<div id='admin-ui-edit-db-language' class='text-right'>".$multiLangInfo."</div>";
6814			}
6815			else
6816			{
6817				$head = "<div id='admin-ui-edit-db-language' class='text-right tabs'>".$multiLangInfo."</div>";
6818			}
6819		}
6820		else
6821		{
6822			$head = '';
6823		}
6824
6825		$forms = $models = array();
6826		$forms[] = array(
6827				'id'  => $this->getElementId(),
6828				'header' => $head,
6829				'footer' => '',
6830				//'url' => e_SELF,
6831				//'query' => 'self', or custom GET query, self is default
6832				'fieldsets' => array(
6833					'create' => array(
6834						'tabs'	=>  $tabs, //used within a single form.
6835						'legend' => $legend,
6836						'fields' => $controller->getFields(), //see e_admin_ui::$fields
6837						'header' => $form_start, //XXX Unused?
6838						'footer' => $form_end,  //XXX Unused?
6839						'after_submit_options' => $controller->getAfterSubmitOptions(), // or true for default redirect options
6840						'after_submit_default' => $request->getPosted('__after_submit_action', $controller->getDefaultAction()), // or true for default redirect options
6841						'triggers' => $controller->getDefaultTrigger(), // standard create/update-cancel triggers
6842					)
6843				)
6844		);
6845
6846		$models[] = $controller->getModel();
6847
6848		return $this->renderCreateForm($forms, $models, e_AJAX_REQUEST);
6849	}
6850
6851	/**
6852	 * Generic Settings Form.
6853	 * @return string
6854	 */
6855	function getSettings()
6856	{
6857		$controller = $this->getController();
6858	//	$request = $controller->getRequest();
6859		$legend = LAN_UI_PREF_LABEL;
6860		$forms = $models = array();
6861		$forms[] = array(
6862				'id'  => $this->getElementId(),
6863				//'url' => e_SELF,
6864				//'query' => 'self', or custom GET query, self is default
6865				'tabs' => false, // TODO - NOT IMPLEMENTED YET - enable tabs (only if fieldset count is > 1)
6866				'fieldsets' => array(
6867					'settings' => array(
6868						'tabs'	=> $controller->getPrefTabs(), //used within a single form.
6869						'legend' => $legend,
6870						'fields' => $controller->getPrefs(), //see e_admin_ui::$prefs
6871						'after_submit_options' => false,
6872						'after_submit_default' => false, // or true for default redirect options
6873						'triggers' => array('save' => array(LAN_SAVE, 'update')), // standard create/update-cancel triggers
6874					)
6875				)
6876		);
6877		$models[] = $controller->getConfig();
6878
6879	//	print_a($forms);
6880
6881		return $this->renderCreateForm($forms, $models, e_AJAX_REQUEST);
6882	}
6883
6884
6885	/**
6886	 * Integrate e_addon data into the list model.
6887	 * @param e_tree_model $tree
6888	 * @param array $fields
6889	 * @param string $pid
6890	 * @return null
6891	 */
6892	private function setAdminAddonModel(e_tree_model $tree, $fields, $pid)
6893	{
6894
6895		$event= $this->getController()->getEventName();
6896
6897		$arr = array();
6898
6899		/** @var e_tree_model $model */
6900		foreach($tree->getTree() as $model)
6901		{
6902			foreach($fields as $fld)
6903			{
6904
6905				if(strpos($fld,'x_') !== 0)
6906				{
6907					continue;
6908				}
6909
6910				list($prefix,$plug,$field) = explode("_",$fld,3);
6911
6912				if($prefix !== 'x' || empty($field) || empty($plug))
6913				{
6914					continue;
6915				}
6916
6917				$id = $model->get($pid);
6918
6919				if(!empty($id))
6920				{
6921					$arr[$plug][$field][$id] = $model;
6922				}
6923			}
6924
6925
6926		}
6927
6928
6929		foreach($arr as $plug=>$field)
6930		{
6931
6932			if($obj = e107::getAddon($plug, 'e_admin'))
6933			{
6934				foreach($field as $fld=>$var)
6935				{
6936					$ids = implode(",", array_keys($var));
6937
6938					$value = (array) e107::callMethod($obj,'load', $event,$ids);
6939
6940				//	$value = (array) $obj->load($event, $ids);
6941
6942					foreach($var as $id=>$model)
6943					{
6944						$model->set("x_".$plug."_".$fld, varset($value[$id][$fld],null));
6945					}
6946				}
6947			}
6948
6949		}
6950
6951	}
6952
6953
6954	/**
6955	 * Create list view
6956	 * Search for the following GET variables:
6957	 * - from: integer, current page
6958	 *
6959	 * @return string
6960	 */
6961	public function getList($ajax = false, $view='default')
6962	{
6963		$tp = e107::getParser();
6964		$this->_list_view = $view;
6965		$controller = $this->getController();
6966
6967		$request = $controller->getRequest();
6968		$id = $this->getElementId();
6969		$pid = $controller->getPrimaryName();
6970		$tree = $options = array();
6971		$tree[$id] = $controller->getTreeModel();
6972
6973
6974
6975
6976		if(deftrue('e_DEBUG_TREESORT') && $view === 'default')
6977		{
6978			$controller->getTreeModelSorted();
6979		}
6980
6981		// if going through confirm screen - no JS confirm
6982		$controller->setFieldAttr('options', 'noConfirm', $controller->deleteConfirmScreen);
6983
6984		$fields = $controller->getFields();
6985
6986		$this->setAdminAddonModel($tree[$id], array_keys($fields), $pid);
6987
6988		// checks dispatcher acess/perms for create/edit/delete access in list mode.
6989		$mode           = $controller->getMode();
6990		$deleteRoute    = $mode."/delete";
6991		$editRoute      = $mode."/edit";
6992		$createRoute    = $mode."/create";
6993
6994		if(!$controller->getDispatcher()->hasRouteAccess($createRoute)) // disable the batchCopy option.
6995		{
6996			$controller->setBatchCopy(false);
6997		}
6998
6999		if(!$controller->getDispatcher()->hasRouteAccess($deleteRoute)) // disable the delete button and batch delete.
7000		{
7001			$fields['options']['readParms']['deleteClass'] = e_UC_NOBODY;
7002			$controller->setBatchDelete(false);
7003		}
7004
7005		if(!$controller->getDispatcher()->hasRouteAccess($editRoute))
7006		{
7007			$fields['options']['readParms']['editClass'] = e_UC_NOBODY; // display the edit button.
7008			foreach($options[$id]['fields'] as $k=>$v) // disable inline editing.
7009			{
7010				$fields[$k]['inline'] = false;
7011			}
7012		}
7013
7014		if(!$controller->getSortField())
7015		{
7016			$fields['options']['sort'] = false;
7017		}
7018
7019		if($treefld = $controller->getTreePrefix())
7020		{
7021			$fields[$treefld]['type'] = 'method';
7022			$fields[$treefld]['method'] = 'treePrefix'; /* @see e_admin_form_ui::treePrefix(); */
7023
7024			$tr = $controller->getTreeModel()->toArray();
7025
7026			foreach($tr as $row)
7027			{
7028				e107::getDebug()->log($row[$treefld].' >  '.$row['_treesort']);
7029			}
7030
7031		}
7032
7033
7034
7035		// ------------------------------------------
7036
7037		$coreBatchOptions = array(
7038			'delete'        => $controller->getBatchDelete(),
7039			'copy'          => $controller->getBatchCopy(),
7040			'url'           => $controller->getBatchLink(),
7041			'featurebox'    => $controller->getBatchFeaturebox(),
7042			'export'        => $controller->getBatchExport(),
7043
7044		);
7045
7046
7047		$options[$id] = array(
7048			'id'            => $this->getElementId(), // unique string used for building element ids, REQUIRED
7049			'pid'           => $pid, // primary field name, REQUIRED
7050			'query'	        => $controller->getFormQuery(), // work around - see form in newspost.php (submitted news)
7051			'head_query'    => $request->buildQueryString('field=[FIELD]&asc=[ASC]&from=[FROM]', false), // without field, asc and from vars, REQUIRED
7052			'np_query'      => $request->buildQueryString(array(), false, 'from'), // without from var, REQUIRED for next/prev functionality
7053			'legend'        => $controller->getPluginTitle(), // hidden by default
7054			'form_pre'      => !$ajax ? $this->renderFilter($tp->post_toForm(array($controller->getQuery('searchquery'), $controller->getQuery('filter_options'))), $controller->getMode().'/'.$controller->getAction()) : '', // needs to be visible when a search returns nothing
7055			'form_post'     => '', // markup to be added after closing form element
7056			'fields'        => $fields, // see e_admin_ui::$fields
7057			'fieldpref'     => $controller->getFieldPref(), // see e_admin_ui::$fieldpref
7058			'table_pre'     => '', // markup to be added before opening table element
7059		//	'table_post' => !$tree[$id]->isEmpty() ? $this->renderBatch($controller->getBatchDelete(),$controller->getBatchCopy(),$controller->getBatchLink(),$controller->getBatchFeaturebox()) : '',
7060
7061			'table_post'    => $this->renderBatch($coreBatchOptions, $controller->getBatchOptions()),
7062			'fieldset_pre'  => '', // markup to be added before opening fieldset element
7063			'fieldset_post' => '', // markup to be added after closing fieldset element
7064			'grid'          =>  $controller->getGrid(),
7065			'perPage'       => $controller->getPerPage(), // if 0 - no next/prev navigation
7066			'from'          => $controller->getQuery('from', 0), // current page, default 0
7067			'field'         => $controller->getQuery('field'), //current order field name, default - primary field
7068			'asc'           => $controller->getQuery('asc', 'desc'), //current 'order by' rule, default 'asc'
7069		);
7070
7071
7072
7073		if($view === 'grid')
7074		{
7075			return $this->renderGridForm($options, $tree, $ajax);
7076		}
7077
7078		return $this->renderListForm($options, $tree, $ajax);
7079	}
7080
7081
7082
7083
7084
7085
7086	public function getConfirmDelete($ids, $ajax = false)
7087	{
7088		$controller = $this->getController();
7089		$request = $controller->getRequest();
7090		$fieldsets = array();
7091		$forms = array();
7092		$id_array = explode(',', $ids);
7093		$delcount = count($id_array);
7094
7095		if(!empty($controller->deleteConfirmMessage))
7096        {
7097			e107::getMessage()->addWarning(str_replace("[x]","<b>".$delcount."</b>", $controller->deleteConfirmMessage));
7098        }
7099		else
7100        {
7101			e107::getMessage()->addWarning(str_replace("[x]","<b>".$delcount."</b>",LAN_UI_DELETE_WARNING));
7102        }
7103
7104		$fieldsets['confirm'] = array(
7105			'fieldset_pre' => '', // markup to be added before opening fieldset element
7106			'fieldset_post' => '', // markup to be added after closing fieldset element
7107			'table_head' => '', // markup between <thead> tag
7108			// Colgroup Example: array(0 => array('class' => 'label', 'style' => 'text-align: left'), 1 => array('class' => 'control', 'style' => 'text-align: left'));
7109			'table_colgroup' => '', // array to be used for creating markup between  <colgroup> tag (<col> list)
7110			'table_pre' => '', // markup to be added before opening table element
7111			'table_post' => '', // markup to be added after closing table element
7112			'table_rows' => '', // rows array (<td> tags)
7113			'table_body' => '', // string body - used only if rows empty
7114			'pre_triggers' => '',
7115			'triggers' => array('hidden' => $this->hidden('etrigger_delete['.$ids.']', $ids) . $this->token(), 'delete_confirm' => array(LAN_CONFDELETE, 'confirm', $ids), 'cancel' => array(LAN_CANCEL, 'cancel')),
7116		);
7117		if($delcount > 1)
7118		{
7119			$fieldsets['confirm']['triggers']['hidden'] = $this->hidden('etrigger_batch', 'delete');
7120		}
7121
7122		$id = null;
7123		$forms[$id] = array(
7124			'id' => $this->getElementId(), // unique string used for building element ids, REQUIRED
7125			'url' => e_REQUEST_SELF, // default
7126			'query' => $request->buildQueryString(array(), true, 'ajax_used'), // - ajax_used is now removed from QUERY_STRING - class2
7127			'legend' => $controller->addTitle(LAN_UI_DELETE_LABEL), // hidden by default
7128			'form_pre' => '',  // markup to be added before opening form element
7129			'form_post' => '', // markup to be added after closing form element
7130			'header' => '', // markup to be added after opening form element
7131			'footer' => '', // markup to be added before closing form element
7132			'fieldsets' => $fieldsets,
7133		);
7134		return $this->renderForm($forms, $ajax);
7135	}
7136
7137
7138	/**
7139	 * Render pagination
7140	 * @return string
7141	 */
7142	public function renderPagination()
7143	{
7144		if($this->_list_view === 'grid' && $this->getController()->getGrid('carousel') === true)
7145		{
7146			return '<div class="btn-group" >
7147			<a id="admin-ui-carousel-prev" class="btn btn-default btn-secondary" href="#admin-ui-carousel" data-slide="prev"><i class="fa fa-backward"></i></a>
7148			<a id="admin-ui-carousel-index" class="btn btn-default btn-secondary">1</a>
7149			<a id="admin-ui-carousel-next" class="btn btn-default btn-secondary" href="#admin-ui-carousel" data-slide="next"><i class="fa fa-forward"></i></a>
7150			</div>';
7151		}
7152
7153		$tree           = $this->getController()->getTreeModel();
7154		$totalRecords   = $tree->getTotal();
7155		$perPage        = $this->getController()->getPerPage();
7156		$fromPage       = $this->getController()->getQuery('from', 0);
7157
7158		$vars           = $this->getController()->getQuery();
7159		$vars['from']   = '[FROM]';
7160
7161		$paginate       = http_build_query($vars, null, '&amp;');
7162
7163		return $this->pagination(e_REQUEST_SELF.'?'.$paginate,$totalRecords,$fromPage,$perPage,array('template'=>'basic'));
7164
7165	}
7166
7167	function renderFilter($current_query = array(), $location = '', $input_options = array())
7168	{
7169		if(!$input_options) $input_options = array('size' => 20);
7170		if(!$location)
7171		{
7172			$location = 'main/list'; //default location
7173		}
7174		$l = e107::getParser()->post_toForm(explode('/', $location));
7175		if(!is_array($input_options))
7176		{
7177			parse_str($input_options, $input_options);
7178		}
7179		$input_options['id'] = false;
7180		$input_options['class'] = 'tbox input-text filter input-xlarge ';
7181		$controller = $this->getController();
7182		$filter_pre = vartrue($controller->preFilterMarkup);
7183		$filter_post = vartrue($controller->postFilterMarkup);
7184		$filter_preserve_var = array();
7185		// method requires controller - stanalone advanced usage not possible
7186		if($this->getController())
7187		{
7188			$get = $this->getController()->getQuery();
7189			foreach ($get as $key => $value)
7190			{
7191				if($key === 'searchquery' || $key === 'filter_options' || $key === 'etrigger_filter')
7192				{
7193					continue;
7194				}
7195
7196				// Reset pager after filtering.
7197				if ($key === 'from')
7198				{
7199					continue;
7200				}
7201
7202				$key = preg_replace('/[^\w]/', '', $key);
7203				$filter_preserve_var[] = $this->hidden($key, rawurlencode($value));
7204			}
7205		}
7206		else
7207		{
7208			$filter_preserve_var[] = $this->hidden('mode', $l[0]);
7209			$filter_preserve_var[] = $this->hidden('action', $l[1]);
7210		}
7211
7212
7213		//	$tree = $this->getTree();
7214		//	$total = $this->getTotal();
7215		$grid = $this->getController()->getGrid();
7216
7217
7218		$gridToggle = '';
7219
7220		if(!empty($grid) && varset($grid['toggleButton']) !==false)
7221		{
7222			$gridAction = $this->getController()->getAction() === 'grid' ? 'list' : 'grid';
7223			$gridQuery = (array) $_GET;
7224			$gridQuery['action'] = $gridAction;
7225			$toggleUrl = e_REQUEST_SELF."?".http_build_query($gridQuery, null, '&amp;');
7226			$gridIcon = ($gridAction === 'grid') ? ADMIN_GRID_ICON : ADMIN_LIST_ICON;
7227			$gridTitle = ($gridAction === 'grid') ? LAN_UI_VIEW_GRID_LABEL : LAN_UI_VIEW_LIST_LABEL;
7228			$gridToggle = "<a class='btn btn-default' href='".$toggleUrl."' title=\"".$gridTitle."\">".$gridIcon."</a>";
7229		}
7230
7231	// <!--<i class='fa fa-search searchquery form-control-feedback form-control-feedback-left'></i>-->
7232
7233		$text = "
7234			<form method='get' action='".e_REQUEST_SELF."'>
7235				<fieldset id='admin-ui-list-filter' class='e-filter'>
7236					<legend class='e-hideme'>".LAN_LABEL_LABEL_SELECTED."</legend>
7237					".$filter_pre."
7238					<div class='row-fluid'>
7239						<div  class='left form-inline span8 col-md-8' >
7240							<span id='admin-ui-list-search' class='form-group has-feedback has-feedback-left'>
7241								".$this->text('searchquery', $current_query[0], 50, $input_options)."
7242							</span>
7243							".$this->select_open('filter_options', array('class' => 'form-control e-tip tbox select filter', 'id' => false, 'title'=>LAN_FILTER))."
7244								".$this->option(LAN_FILTER_LABEL_DISPLAYALL, '')."
7245								".$this->option(LAN_FILTER_LABEL_CLEAR, '___reset___')."
7246								".$this->renderBatchFilter('filter', $current_query[1])."
7247							".$this->select_close()."
7248							<div class='e-autocomplete'></div>
7249							".implode("\n", $filter_preserve_var)."
7250							".$this->admin_button('etrigger_filter', 'etrigger_filter', 'filter e-hide-if-js', ADMIN_FILTER_ICON, array('id' => false,'title'=>LAN_FILTER))."
7251
7252							".$this->renderPagination()."
7253							<span class='indicator' style='display: none;'>
7254								<img src='".e_IMAGE_ABS."generic/loading_16.gif' class='icon action S16' alt='".LAN_LOADING."' />
7255							</span>
7256							".$gridToggle."
7257						</div>
7258						<div id='admin-ui-list-db-language' class='span4 col-md-4 text-right' >";
7259
7260
7261						// Let Admin know which language table is being saved to. (avoid default table overwrites)
7262						$text .= $this->renderLanguageTableInfo();
7263
7264						$text .= "
7265						</div>
7266					</div>
7267					".$filter_post."
7268				</fieldset>
7269			</form>
7270		";
7271
7272
7273		e107::js('core','scriptaculous/controls.js','prototype', 2);
7274		//TODO - external JS
7275		e107::js('footer-inline',"
7276
7277	            //autocomplete fields
7278	             \$\$('input[name=searchquery]').each(function(el, cnt) {
7279				 	if(!cnt) el.activate();
7280					else return;
7281					new Ajax.Autocompleter(el, el.next('div.e-autocomplete'), '".e_REQUEST_SELF."?mode=".$l[0]."&action=filter', {
7282					  paramName: 'searchquery',
7283					  minChars: 2,
7284					  frequency: 0.5,
7285					  afterUpdateElement: function(txt, li) {
7286					  	var cfrm = el.up('form'), cont = cfrm.next('.e-container');
7287						if(!cont) {
7288							return;
7289						}
7290					  	cfrm.submitForm(cont);
7291					  },
7292					  indicator:  el.next('span.indicator'),
7293					  parameters: 'ajax_used=1'
7294					});
7295					var sel = el.next('select.filter');
7296					if(sel) {
7297						sel.observe('change', function (e) {
7298							var cfrm = e.element().up('form'), cont = cfrm.next('.e-container');
7299							if(cfrm && cont && e.element().value != '___reset___') {
7300								e.stop();
7301								cfrm.submitForm(cont);
7302								return;
7303							}
7304							e107Helper.selectAutoSubmit(e.element());
7305						});
7306					}
7307				});
7308		",'prototype');
7309
7310		// TODO implement ajax queue
7311		// FIXME
7312		// dirty way to register events after ajax update - DO IT RIGHT - see all.jquery, create object and use handler,
7313		// re-register them global after ajax update (context)... use behaviors and call e107.attachBehaviors();
7314		e107::js('footer-inline',"
7315			var filterRunning = false, request;
7316			var applyAfterAjax = function(context) {
7317		      	\$('.e-expandit', context).click(function () {
7318		       		var href = (\$(this).is('a')) ? \$(this).attr('href') : '';
7319		       		if(href == '' && \$(this).attr('data-target'))
7320		       		{
7321		       			href = '#' + \$(this).attr('data-target');
7322		       		}
7323					if(href === '#' || href == '')
7324					{
7325						idt = \$(this).nextAll('div');
7326						\$(idt).toggle('slow');
7327						 return true;
7328					}
7329		       		//var id = $(this).attr('href');
7330					\$(href).toggle('slow');
7331					return false;
7332				});
7333				\$('input.toggle-all', context).click(function(evt) {
7334					var selector = 'input[type=\"checkbox\"].checkbox';
7335					if(\$(this).val().startsWith('jstarget:')) {
7336						selector = 'input[type=\"checkbox\"][name^=\"' + \$(this).val().split(/jstarget\:/)[1] + '\"]';
7337					}
7338
7339					if(\$(this).is(':checked')){
7340						\$(selector).attr('checked', 'checked');
7341					}
7342					else{
7343						\$(selector).removeAttr('checked');
7344					}
7345				});
7346			};
7347			var searchQueryHandler = function (e) {
7348				var el = \$(this), frm = el.parents('form'), cont = frm.nextAll('.e-container');
7349				if(cont.length < 1 || frm.length < 1 || (el.val().length > 0 && el.val().length < 3)) return;
7350				e.preventDefault();
7351
7352				if(filterRunning && request) request.abort();
7353				filterRunning = true;
7354
7355				cont.css({ opacity: 0.5 });
7356
7357				request = \$.get(frm.attr('action'), frm.serialize(), function(data){
7358					filterRunning = false;
7359					setTimeout(function() {
7360						if(filterRunning) {
7361							//cont.css({ opacity: 1 });
7362							return;
7363						}
7364						cont.html(data).css({ opacity: 1 });
7365						// TODO remove applyAfterAjax() and use behaviors!
7366						applyAfterAjax(cont);
7367						// Attach behaviors to the newly loaded contents.
7368						e107.attachBehaviors();
7369					}, 700);
7370				}, 'html')
7371				.error(function() {
7372					filterRunning = false;
7373					cont.css({ opacity: 1 });
7374				});
7375			};
7376			\$('#searchquery').on('keyup', searchQueryHandler);
7377		", 'jquery');
7378
7379		return $text;
7380	}
7381
7382
7383	private function renderLanguageTableInfo()
7384	{
7385
7386		if(!e107::getConfig()->get('multilanguage'))
7387		{
7388			return null;
7389		}
7390
7391		$curTable = $this->getController()->getTableName();
7392		$sitelanguage = e107::getConfig()->get('sitelanguage');
7393
7394		$val = e107::getDb()->hasLanguage($curTable, true);
7395
7396		if($val === false)
7397		{
7398			return null;
7399		}
7400
7401		if($curTable != e107::getDb()->hasLanguage($curTable))
7402		{
7403			$lang = e107::getDb()->mySQLlanguage;
7404		}
7405		else
7406		{
7407			$lang = $sitelanguage;
7408		}
7409
7410		$def = deftrue('LAN_UI_USING_DATABASE_TABLE','Using [x] database table');
7411		$diz  = e107::getParser()->lanVars($def, $lang); // "Using ".$lang." database table";
7412		$class = ($sitelanguage == $lang) ? "default" : "";
7413
7414		$text = "<span class='adminui-language-table-info ".$class." e-tip' title=\"".$diz."\">";
7415		$text .= e107::getParser()->toGlyph('fa-hdd-o'); // '<i class="icon-hdd"></i> ';
7416		$text .= e107::getLanguage()->toNative($lang)."</span>";
7417		return $text;
7418
7419
7420	}
7421
7422
7423
7424	// FIXME - use e_form::batchoptions(), nice way of building batch dropdown - news administration show_batch_options()
7425
7426	/**
7427	 * @param array $options array of flags for copy, delete, url, featurebox, batch
7428	 * @param array $customBatchOptions
7429	 * @return string
7430	 */
7431	function renderBatch($options, $customBatchOptions=array())
7432	{
7433
7434		$fields = $this->getController()->getFields();
7435
7436		if(!varset($fields['checkboxes']))
7437		{
7438			$mes = e107::getMessage();
7439			$mes->add("Cannot display Batch drop-down as 'checkboxes' was not found in \$fields array.", E_MESSAGE_DEBUG);
7440			return '';
7441		}
7442
7443		// FIX - don't show FB option if plugin not installed
7444		if(!e107::isInstalled('featurebox'))
7445		{
7446			$options['featurebox'] = false;
7447		}
7448
7449		// TODO - core ui-batch-option class!!! REMOVE INLINE STYLE!
7450		// XXX Quick Fix for styling - correct.
7451		$text = "
7452			<div id='admin-ui-list-batch' class='navbar navbar-inner left' >
7453				<div class='span6 col-md-6'>";
7454
7455		$selectStart = "<div class='form-inline input-inline'>
7456					".ADMIN_CHILD_ICON."
7457	         		 		<div class='input-group input-append'>
7458						".$this->select_open('etrigger_batch', array('class' => 'tbox form-control input-large select batch e-autosubmit reset', 'id' => false))."
7459						".$this->option(LAN_BATCH_LABEL_SELECTED, '', false);
7460
7461		$selectOpt = '';
7462
7463		if(!$this->getController()->getTreeModel()->isEmpty())
7464		{
7465			$selectOpt .= !empty($options['copy']) ? $this->option(LAN_COPY, 'copy', false, array('class' => 'ui-batch-option class', 'other' => 'style="padding-left: 15px"')) : '';
7466			$selectOpt .= !empty($options['delete']) ? $this->option(LAN_DELETE, 'delete', false, array('class' => 'ui-batch-option class', 'other' => 'style="padding-left: 15px"')) : '';
7467			$selectOpt .= !empty($options['export']) ? $this->option(LAN_UI_BATCH_EXPORT, 'export', false, array('class' => 'ui-batch-option class', 'other' => 'style="padding-left: 15px"')) : '';
7468			$selectOpt .= !empty($options['url']) ? $this->option(LAN_UI_BATCH_CREATELINK, 'url', false, array('class' => 'ui-batch-option class', 'other' => 'style="padding-left: 15px"')) : '';
7469			$selectOpt .= !empty($options['featurebox']) ? $this->option(LAN_PLUGIN_FEATUREBOX_BATCH, 'featurebox', false, array('class' => 'ui-batch-option class', 'other' => 'style="padding-left: 15px"')) : '';
7470
7471		//	if(!empty($parms['sef'])
7472
7473
7474
7475			if(!empty($customBatchOptions))
7476			{
7477				foreach($customBatchOptions as $key=>$val)
7478				{
7479
7480					if(is_array($val))
7481					{
7482						$selectOpt .= $this->optgroup_open($key);
7483
7484						foreach($val as $k=>$v)
7485						{
7486							$selectOpt .= $this->option($v, $k, false, array('class' => 'ui-batch-option class', 'other' => 'style="padding-left: 15px"'));
7487						}
7488
7489						$selectOpt .= $this->optgroup_close();
7490					}
7491					else
7492					{
7493						$selectOpt .= $this->option($val, $key, false, array('class' => 'ui-batch-option class', 'other' => 'style="padding-left: 15px"'));
7494					}
7495
7496
7497				}
7498
7499			}
7500
7501
7502			$selectOpt .= $this->renderBatchFilter('batch');
7503
7504			if(!empty($selectOpt))
7505			{
7506				$text .= $selectStart;
7507
7508				$text .= $selectOpt;
7509
7510				$text .= $this->select_close();
7511
7512				$text .= "<div class='input-group-btn input-append'>
7513				".$this->admin_button('e__execute_batch', 'e__execute_batch', 'batch e-hide-if-js', LAN_GO, array('id' => false))."
7514				</div>";
7515				$text .= "</div></div>";
7516			}
7517
7518			$text .= "</div>";
7519
7520		}
7521
7522
7523		$text .= "
7524
7525				<div id='admin-ui-list-total-records' class='span6 col-md-6 right'><span>".e107::getParser()->lanVars(LAN_UI_TOTAL_RECORDS,number_format($this->getController()->getTreeModel()->getTotal()))."</span></div>
7526			</div>
7527		";
7528
7529
7530		return $text;
7531	}
7532
7533
7534	/**
7535	 * Render Batch and Filter Dropdown options.
7536	 * @param string $type
7537	 * @param string $selected
7538	 * @return string
7539	 */
7540	function renderBatchFilter($type='batch', $selected = '') // Common function used for both batches and filters.
7541	{
7542		$optdiz = array('batch' => LAN_BATCH_LABEL_PREFIX.'&nbsp;', 'filter'=> LAN_FILTER_LABEL_PREFIX.'&nbsp;');
7543		$table = $this->getController()->getTableName();
7544		$text = '';
7545		$textsingle = '';
7546
7547
7548		$searchFieldOpts = array();
7549
7550		$fieldList = $this->getController()->getFields();
7551
7552
7553
7554		foreach($fieldList as $key=>$val)
7555		{
7556			if(!empty($val['search']))
7557			{
7558				$searchFieldOpts["searchfield__".$key] = $val['title'];
7559			}
7560
7561			if(empty($val[$type])) // ie. filter = false or batch = false.
7562			{
7563				continue;
7564			}
7565
7566			$option = array();
7567			$parms = vartrue($val['writeParms'], array());
7568			if(is_string($parms)) parse_str($parms, $parms);
7569
7570			//Basic batch support for dropdown with multiple values. (comma separated)
7571			if(!empty($val['writeParms']['multiple']) && $val['type'] === 'dropdown' && !empty($val['writeParms']['optArray']))
7572			{
7573				$val['type'] = 'comma';
7574				$parms = $val['writeParms']['optArray'];
7575			}
7576
7577
7578
7579			switch($val['type'])
7580			{
7581
7582					case 'text';
7583
7584						if(!empty($parms['sef']))
7585						{
7586							$option['sefgen__'.$key.'__'.$parms['sef']] = LAN_GENERATE;
7587						}
7588
7589						$searchFieldOpts["searchfield__".$key] = $val['title'];
7590
7591					break;
7592
7593
7594					case 'number';
7595						if($type === 'filter')
7596						{
7597							$option[$key.'___ISEMPTY_'] = LAN_UI_FILTER_IS_EMPTY;
7598						}
7599
7600						$searchFieldOpts["searchfield__".$key] = $val['title'];
7601
7602					break;
7603
7604					case 'textarea':
7605					case 'tags':
7606						$searchFieldOpts["searchfield__".$key] = $val['title'];
7607					break;
7608
7609					case 'bool':
7610					case 'boolean': //TODO modify description based on $val['parm]
7611
7612						// defaults
7613						$LAN_TRUE = LAN_ON;
7614						$LAN_FALSE = LAN_OFF;
7615
7616						if(varset($parms['label']) === 'yesno')
7617						{
7618							$LAN_TRUE = LAN_YES;
7619							$LAN_FALSE = LAN_NO;
7620						}
7621
7622						if(!empty($parms['enabled']))
7623						{
7624							$LAN_TRUE = $parms['enabled'];
7625						}
7626						elseif(!empty($parms['true']))
7627						{
7628							$LAN_TRUE = $parms['true'];
7629						}
7630
7631						if(!empty($parms['disabled']))
7632						{
7633							$LAN_FALSE = $parms['disabled'];
7634						}
7635						elseif(!empty($parms['false']))
7636						{
7637							$LAN_FALSE = $parms['false'];
7638						}
7639
7640						if(!empty($parms['reverse'])) // reverse true/false values;
7641						{
7642							$option['bool__'.$key.'__0'] = $LAN_TRUE;	// see newspost.php : news_allow_comments for an example.
7643							$option['bool__'.$key.'__1'] = $LAN_FALSE;
7644						}
7645						else
7646						{
7647							$option['bool__'.$key.'__1'] = $LAN_TRUE;
7648							$option['bool__'.$key.'__0'] = $LAN_FALSE;
7649						}
7650
7651						if($type === 'batch')
7652						{
7653							$option['boolreverse__'.$key] = LAN_BOOL_REVERSE;
7654						}
7655					break;
7656
7657					case 'checkboxes':
7658					case 'comma':
7659						// TODO lan
7660						if(!isset($parms['__options'])) $parms['__options'] = array();
7661						if(!is_array($parms['__options'])) parse_str($parms['__options'], $parms['__options']);
7662						$opts = $parms['__options'];
7663						unset($parms['__options']); //remove element options if any
7664
7665						$options = $parms ? $parms : array();
7666						if(empty($options)) continue 2;
7667
7668
7669						if($type === 'batch')
7670						{
7671							$_option = array();
7672
7673							if(isset($options['addAll']))
7674							{
7675								$option['attach_all__'.$key] = vartrue($options['addAll'], "(".LAN_ADD_ALL.")");
7676								unset($options['addAll']);
7677							}
7678							if(isset($options['clearAll']))
7679							{
7680								$_option['deattach_all__'.$key] = vartrue($options['clearAll'], "(".LAN_CLEAR_ALL.")");
7681								unset($options['clearAll']);
7682							}
7683
7684							if(vartrue($opts['simple']))
7685							{
7686								foreach ($options as $value)
7687								{
7688									$option['attach__'.$key.'__'.$value] = LAN_ADD." ".$value;
7689									$_option['deattach__'.$key.'__'.$value] = LAN_REMOVE." ".$value;
7690								}
7691							}
7692							else
7693							{
7694								foreach ($options as $value => $label)
7695								{
7696									$option['attach__'.$key.'__'.$value] = LAN_ADD." ".$label;
7697									$_option['deattach__'.$key.'__'.$value] = LAN_REMOVE." ".$label;
7698								}
7699							}
7700							$option = array_merge($option, $_option);
7701							unset($_option);
7702						}
7703						else
7704						{
7705							unset($options['addAll'], $options['clearAll']);
7706							if(vartrue($opts['simple']))
7707							{
7708								foreach($options as $k)
7709								{
7710									$option[$key.'__'.$k] = $k;
7711								}
7712							}
7713							else
7714							{
7715								foreach($options as $k => $name)
7716								{
7717									$option[$key.'__'.$k] = $name;
7718								}
7719							}
7720
7721						}
7722					break;
7723
7724					case 'templates':
7725					case 'layouts':
7726						$parms['raw'] = true;
7727						$val['writeParms'] = $parms;
7728						$tmp = $this->renderElement($key, '', $val);
7729						if(is_array($tmp))
7730						{
7731							foreach ($tmp as $k => $name)
7732							{
7733								$option[$key.'__'.$k] = $name;
7734							}
7735						}
7736					break;
7737
7738					case 'dropdown': // use the array $parm;
7739
7740
7741
7742
7743						if(!empty($parms['optArray']))
7744						{
7745							$fopts = $parms;
7746							$parms = $fopts['optArray'];
7747							unset($fopts['optArray']);
7748							$parms['__options'] = $fopts;
7749						}
7750
7751
7752						if(!is_array(varset($parms['__options']))) parse_str($parms['__options'], $parms['__options']);
7753						$opts = $parms['__options'];
7754						if(vartrue($opts['multiple']) && $type === 'batch')
7755						{
7756							// no batch support for multiple, should have some for filters soon
7757							continue 2;
7758						}
7759
7760						unset($parms['__options']); //remove element options if any
7761
7762
7763
7764						foreach($parms as $k => $name)
7765						{
7766							$option[$key.'__'.$k] = $name;
7767						}
7768					break;
7769
7770					case 'language': // full list of
7771					case 'lanlist': // use the array $parm;
7772						if(!is_array(varset($parms['__options']))) parse_str($parms['__options'], $parms['__options']);
7773						$opts = $parms['__options'];
7774						if(vartrue($opts['multiple']))
7775						{
7776							// no batch support for multiple, should have some for filters soon
7777							continue 2;
7778						}
7779						$options = ($val['type'] === 'language') ? e107::getLanguage()->getList() : e107::getLanguage()->getLanSelectArray();
7780						foreach($options as $code => $name)
7781						{
7782							$option[$key.'__'.$code] = $name;
7783						}
7784					break;
7785
7786					case 'datestamp':
7787						$tp = e107::getParser();
7788						$dateFilters = array (
7789							'hour'		=> LAN_UI_FILTER_PAST_HOUR,
7790							"day"		=> LAN_UI_FILTER_PAST_24_HOURS,
7791							"week"		=> LAN_UI_FILTER_PAST_WEEK,
7792							"month"		=> LAN_UI_FILTER_PAST_MONTH,
7793							"month3"	=> $tp->lanVars(LAN_UI_FILTER_PAST_XMONTHS,3),
7794							"month6"	=> $tp->lanVars(LAN_UI_FILTER_PAST_XMONTHS,6),
7795							"month9"	=> $tp->lanVars(LAN_UI_FILTER_PAST_XMONTHS,9),
7796							"year"		=> LAN_UI_FILTER_PAST_YEAR
7797						);
7798
7799						$dateFiltersFuture = array (
7800								'nhour'		=> LAN_UI_FILTER_NEXT_HOUR,
7801								"nday"		=> LAN_UI_FILTER_NEXT_24_HOURS,
7802								"nweek"		=> LAN_UI_FILTER_NEXT_WEEK,
7803								"nmonth"	=> LAN_UI_FILTER_NEXT_MONTH,
7804								"nmonth3"	=> $tp->lanVars(LAN_UI_FILTER_NEXT_XMONTHS,3),
7805								"nmonth6"	=> $tp->lanVars(LAN_UI_FILTER_NEXT_XMONTHS,6),
7806								"nmonth9"	=> $tp->lanVars(LAN_UI_FILTER_NEXT_XMONTHS,9),
7807								"nyear"		=> LAN_UI_FILTER_NEXT_YEAR
7808						);
7809
7810						if($val['filter'] === 'future' )
7811						{
7812							$dateFilters = $dateFiltersFuture;
7813						}
7814
7815						if($val['filter'] === 'both')
7816						{
7817							$dateFilters += $dateFiltersFuture;
7818						}
7819
7820						foreach($dateFilters as $k => $name)
7821						{
7822							$option['datestamp__'.$key.'__'.$k] = $name;
7823						//	$option['bool__'.$key.'__0'] = LAN_NO;
7824						//	$option[$key.'__'.$k] = $name;
7825						}
7826					break;
7827
7828					case 'userclass':
7829						$classes = e107::getUserClass()->uc_required_class_list(vartrue($parms['classlist'], 'public,nobody,guest,member,admin,main,classes'));
7830						foreach($classes as $k => $name)
7831						{
7832							$option[$key.'__'.$k] = $name;
7833						}
7834					break;
7835					case 'userclasses':
7836						$classes = e107::getUserClass()->uc_required_class_list(vartrue($parms['classlist'], 'public,nobody,guest,member,admin,main,classes'));
7837						$_option = array();
7838
7839						if($type === 'batch')
7840						{
7841							foreach ($classes as $k => $v)
7842							{
7843								$option['ucadd__'.$key.'__'.$k] = LAN_ADD.' '.$v;
7844								$_option['ucremove__'.$key.'__'.$k] = LAN_REMOVE." ".$v;
7845							}
7846							$option['ucaddall__'.$key] = "(".LAN_ADD_ALL.")";
7847							$_option['ucdelall__'.$key] = "(".LAN_CLEAR_ALL.")";
7848							$option = array_merge($option, $_option);
7849						}
7850						else
7851						{
7852							foreach ($classes as $k => $v)
7853							{
7854								$option[$key.'__'.$k] = $v;
7855							}
7856						}
7857
7858						unset($_option);
7859					break;
7860
7861					case 'method':
7862						$method = $key;
7863						$list = call_user_func_array(array($this, $method), array('', $type, $parms));
7864
7865						if(is_array($list))
7866						{
7867							//check for single option
7868							if(isset($list['singleOption']))
7869							{
7870								$textsingle .= $list['singleOption'];
7871								continue 2;
7872							}
7873							// non rendered options array
7874							foreach($list as $k => $name)
7875							{
7876								$option[$key.'__'.$k] = $name;
7877							}
7878						}
7879						elseif(!empty($list)) //optgroup, continue
7880						{
7881							$text .= $list;
7882							continue 2;
7883						}
7884					break;
7885
7886					case 'user': // TODO - User Filter
7887
7888						$sql = e107::getDb();
7889						$field = $val['field'];
7890
7891						$query = "SELECT d.".$field.", u.user_name FROM #".$val['table']." AS d LEFT JOIN #user AS u ON d.".$field." = u.user_id  GROUP BY d.".$field." ORDER BY u.user_name";
7892						$row = $sql->retrieve($query,true);
7893						foreach($row as $data)
7894						{
7895							$k = $data[$field];
7896							if($k == 0)
7897							{
7898								$option[$key.'__'.$k] = "(".LAN_ANONYMOUS.")";
7899							}
7900							else
7901							{
7902								$option[$key.'__'.$k] = vartrue($data['user_name'],LAN_UNKNOWN);
7903							}
7904
7905
7906						}
7907					break;
7908			}
7909
7910
7911
7912			if(!empty($option))
7913			{
7914				$text .= "\t".$this->optgroup_open($optdiz[$type].defset($val['title'], $val['title']), varset($disabled))."\n";
7915				foreach($option as $okey=>$oval)
7916				{
7917					$text .= $this->option($oval, $okey, $selected == $okey)."\n";
7918				}
7919				$text .= "\t".$this->optgroup_close()."\n";
7920			}
7921		}
7922
7923
7924		if(!empty($searchFieldOpts))
7925		{
7926			$text .= "\t".$this->optgroup_open(defset("LAN_UI_FILTER_SEARCH_IN_FIELD", "Search in Field"))."\n";
7927
7928			foreach($searchFieldOpts as $key=>$val)
7929			{
7930				$text .= $this->option($val, $key, $selected == $key)."\n";
7931			}
7932
7933			$text .= "\t".$this->optgroup_close()."\n";
7934		}
7935
7936
7937
7938		return $textsingle.$text;
7939
7940	}
7941
7942	public function getElementId()
7943	{
7944		$controller = $this->getController();
7945		$name = str_replace('_', '-', ($controller->getPluginName() === 'core' ? 'core-'.$controller->getTableName() : 'plugin-'.$controller->getPluginName()));
7946		return e107::getForm()->name2id($name); // prevent invalid ids.
7947	}
7948
7949	/**
7950	 * @return e_admin_ui
7951	 */
7952	public function getController()
7953	{
7954
7955		return $this->_controller;
7956	}
7957}
7958
7959
7960
7961
7962
7963include_once(e107::coreTemplatePath('admin_icons'));
7964
7965/**
7966 * TODO:
7967 * 1. [DONE - a good start] move abstract peaces of code to the proper classes
7968 * 2. [DONE - at least for alpha release] remove duplicated code (e_form & e_admin_form_ui), refactoring
7969 * 3. make JS Manager handle Styles (.css files and inline CSS)
7970 * 4. [DONE] e_form is missing some methods used in e_admin_form_ui
7971 * 5. [DONE] date convert needs string-to-datestamp auto parsing, strptime() is the solution but needs support for
7972 * 		Windows and PHP < 5.1.0 - build custom strptime() function (php_compatibility_handler.php) on this -
7973 * 		http://sauron.lionel.free.fr/?page=php_lib_strptime (bad license so no copy/paste is allowed!)
7974 * 6. [DONE - read/writeParms introduced ] $fields[parms] mess - fix it, separate list/edit mode parms somehow
7975 * 7. clean up/document all object vars (e_admin_ui, e_admin_dispatcher)
7976 * 8. [DONE hopefully] clean up/document all parameters (get/setParm()) in controller and model classes
7977 * 9. [DONE] 'ip' field type - convert to human readable format while showing/editing record
7978 * 10. draggable (or not?) ordering (list view)
7979 * 11. [DONE] realtime search filter (typing text) - like downloads currently
7980 * 12. [DONE] autosubmit when 'filter' dropdown is changed (quick fix?)
7981 * 13. tablerender captions
7982 * 14. [DONE] textareas auto-height
7983 * 15. [DONE] multi JOIN table support (optional), aliases
7984 * 16. tabs support (create/edit view)
7985 * 17. tree list view (should handle cases like Site Links admin page)
7986 */
7987
7988
7989