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 * Form Handler
10 *
11*/
12
13if (!defined('e107_INIT')) { exit; }
14
15/**
16 *
17 * @package e107
18 * @subpackage e107_handlers
19 * @version $Id$
20 *
21 *
22 * Automate Form fields creation. Produced markup is following e107 CSS/XHTML standards
23 * If options argument is omitted, default values will be used (which OK most of the time)
24 * Options are intended to handle some very special cases.
25 *
26 * Overall field options format (array or GET string like this one: var1=val1&var2=val2...):
27 *
28 *  - id => (mixed) custom id attribute value
29 *  if numeric value is passed it'll be just appended to the name e.g. {filed-name}-{value}
30 *  if false is passed id will be not created
31 *  if empty string is passed (or no 'id' option is found)
32 *  in all other cases the value will be used as field id
33 * 	default: empty string
34 *
35 *  - class => (string) field class(es)
36 * 	Example: 'tbox select class1 class2 class3'
37 * 	NOTE: this will override core classes, so you have to explicit include them!
38 * 	default: empty string
39 *
40 *  - size => (int) size attribute value (used when needed)
41 *	default: 40
42 *
43 *  - title (string) title attribute
44 *  default: empty string (omitted)
45 *
46 *  - readonly => (bool) readonly attribute
47 * 	default: false
48 *
49 *  - selected => (bool) selected attribute (used when needed)
50 * 	default: false
51 *
52 *  checked => (bool) checked attribute (used when needed)
53 *  default: false
54 *  - disabled => (bool) disabled attribute
55 *  default: false
56 *
57 *  - tabindex => (int) tabindex attribute value
58 *	default: inner tabindex counter
59 *
60 *  - other => (string) additional data
61 *  Example: 'attribute1="value1" attribute2="value2"'
62 *  default: empty string
63 */
64class e_form
65{
66	protected $_tabindex_counter = 0;
67	protected $_tabindex_enabled = true;
68	protected $_cached_attributes = array();
69	protected $_field_warnings = array();
70	private $_inline_token = null;
71
72	/**
73	 * @var user_class
74	 */
75	protected $_uc;
76
77	protected $_required_string;
78
79	function __construct($enable_tabindex = false)
80	{
81		e107_include_once(e_LANGUAGEDIR.e_LANGUAGE."/lan_form_handler.php");
82		$this->_tabindex_enabled = $enable_tabindex;
83		$this->_uc = e107::getUserClass();
84		$this->setRequiredString('<span class="required text-warning">&nbsp;*</span>');
85	}
86
87	/**
88	 * @param $tmp
89	 * @return array
90	 * @see https://github.com/e107inc/e107/issues/3533
91	 */
92	private static function sort_get_files_output($tmp)
93	{
94		usort($tmp, function ($left, $right) {
95			$left_full_path = $left['path'] . $left['fname'];
96			$right_full_path = $right['path'] . $right['fname'];
97			return strcmp($left_full_path, $right_full_path);
98		});
99		return $tmp;
100	}
101
102
103	public function addWarning($field)
104	{
105		$this->_field_warnings[] = $field;
106
107	}
108
109	/**
110	 * Open a new form
111	 * @param string name
112	 * @param $method - post|get  default is post
113	 * @param string target - e_REQUEST_URI by default
114	 * @param array $options
115	 * @return string
116	 */
117	public function open($name, $method=null, $target=null, $options=null)
118	{
119		if($target == null)
120		{
121			$target = e_REQUEST_URI;
122		}
123
124		if($method == null)
125		{
126			$method = "post";
127		}
128
129		$autoComplete 	= "";
130
131		if(is_string($options))
132		{
133			parse_str($options, $options);
134		}
135
136		if(vartrue($options['class']))
137		{
138			$class = "class='".$options['class']."'";
139		}
140		else  // default
141		{
142			$class= "class='form-horizontal'";
143		}
144
145		if(isset($options['autocomplete'])) // leave as isset()
146		{
147			$autoComplete = " autocomplete='".($options['autocomplete'] ? 'on' : 'off')."'";
148		}
149
150
151		if($method == 'get' && strpos($target,'='))
152		{
153			list($url,$qry) = explode("?",$target);
154			$text = "\n<form {$class} action='{$url}' id='".$this->name2id($name)."' method = '{$method}'{$autoComplete}>\n";
155
156			parse_str($qry,$m);
157			foreach($m as $k=>$v)
158			{
159				$text .= $this->hidden($k, $v);
160			}
161
162		}
163		else
164		{
165			$target = str_replace("&", "&amp;", $target);
166			$text = "\n<form {$class} action='{$target}' id='".$this->name2id($name)."' method='{$method}'{$autoComplete}>\n";
167		}
168		return $text;
169	}
170
171	/**
172	 * Close a Form
173	 */
174	public function close()
175	{
176		return "</form>";
177
178	}
179
180
181	/**
182	 * Render a country drop-down list.
183	 * @param string $name
184	 * @param string $value
185	 * @param array $options
186	 * @return string
187	 */
188	public function country($name, $value, $options=array())
189	{
190
191		$arr = $this->getCountry();
192
193		$placeholder = isset($options['placeholder']) ? $options['placeholder'] : ' ';
194
195		return $this->select($name, $arr, $value, $options, $placeholder);
196	}
197
198
199	/**
200	 * Get a list of countries.
201	 * @param null|string $iso ISO code.
202	 * @return array|mixed|string
203	 */
204	public function getCountry($iso=null)  // move to parser?
205	{
206
207		$c = array();
208
209		 $c['af'] = "Afghanistan";
210		 $c['al'] = "Albania";
211		 $c['dz'] = "Algeria";
212		 $c['as'] = "American Samoa";
213		 $c['ad'] = "Andorra";
214		 $c['ao'] = "Angola";
215		 $c['ai'] = "Anguilla";
216		 $c['aq'] = "Antarctica";
217		 $c['ag'] = "Antigua and Barbuda";
218		 $c['ar'] = "Argentina";
219		 $c['am'] = "Armenia";
220		 $c['aw'] = "Aruba";
221		 $c['au'] = "Australia";
222		 $c['at'] = "Austria";
223		 $c['az'] = "Azerbaijan";
224		 $c['bs'] = "Bahamas";
225		 $c['bh'] = "Bahrain";
226		 $c['bd'] = "Bangladesh";
227		 $c['bb'] = "Barbados";
228		 $c['by'] = "Belarus";
229		 $c['be'] = "Belgium";
230		 $c['bz'] = "Belize";
231		 $c['bj'] = "Benin";
232		 $c['bm'] = "Bermuda";
233		 $c['bt'] = "Bhutan";
234		 $c['bo'] = "Bolivia";
235		 $c['ba'] = "Bosnia-Herzegovina";
236		 $c['bw'] = "Botswana";
237		 $c['bv'] = "Bouvet Island";
238		 $c['br'] = "Brazil";
239		 $c['io'] = "British Indian Ocean Territory";
240		 $c['bn'] = "Brunei Darussalam";
241		 $c['bg'] = "Bulgaria";
242		 $c['bf'] = "Burkina Faso";
243		 $c['bi'] = "Burundi";
244		 $c['kh'] = "Cambodia";
245		 $c['cm'] = "Cameroon";
246		 $c['ca'] = "Canada";
247
248		 $c['cv'] = "Cape Verde";
249		 $c['ky'] = "Cayman Islands";
250		 $c['cf'] = "Central African Republic";
251		 $c['td'] = "Chad";
252		 $c['cl'] = "Chile";
253		 $c['cn'] = "China";
254		 $c['cx'] = "Christmas Island";
255		 $c['cc'] = "Cocos (Keeling) Islands";
256		 $c['co'] = "Colombia";
257		 $c['km'] = "Comoros";
258		 $c['cg'] = "Congo";
259		 $c['cd'] = "Congo (Dem.Rep)";
260		 $c['ck'] = "Cook Islands";
261		 $c['cr'] = "Costa Rica";
262		 $c['hr'] = "Croatia";
263		 $c['cu'] = "Cuba";
264		 $c['cy'] = "Cyprus";
265		 $c['cz'] = "Czech Republic";
266		 $c['dk'] = "Denmark";
267		 $c['dj'] = "Djibouti";
268		 $c['dm'] = "Dominica";
269		 $c['do'] = "Dominican Republic";
270		 $c['tp'] = "East Timor";
271		 $c['ec'] = "Ecuador";
272		 $c['eg'] = "Egypt";
273		 $c['sv'] = "El Salvador";
274		 $c['gq'] = "Equatorial Guinea";
275		 $c['er'] = "Eritrea";
276		 $c['ee'] = "Estonia";
277		 $c['et'] = "Ethiopia";
278		 $c['fk'] = "Falkland Islands";
279		 $c['fo'] = "Faroe Islands";
280		 $c['fj'] = "Fiji";
281		 $c['fi'] = "Finland";
282		// $c['cs'] = "Former Czechoslovakia";
283		// $c['su'] = "Former USSR";
284		 $c['fr'] = "France";
285		// $c['fx'] = "France (European Territory)";
286		 $c['gf'] = "French Guyana";
287		 $c['tf'] = "French Southern Territories";
288		 $c['ga'] = "Gabon";
289		 $c['gm'] = "Gambia";
290		 $c['ge'] = "Georgia";
291		 $c['de'] = "Germany";
292		 $c['gh'] = "Ghana";
293		 $c['gi'] = "Gibraltar";
294		 $c['gr'] = "Greece";
295		 $c['gl'] = "Greenland";
296		 $c['gd'] = "Grenada";
297		 $c['gp'] = "Guadeloupe (French)";
298		 $c['gu'] = "Guam (USA)";
299		 $c['gt'] = "Guatemala";
300		 $c['gn'] = "Guinea";
301		 $c['gw'] = "Guinea Bissau";
302		 $c['gy'] = "Guyana";
303		 $c['ht'] = "Haiti";
304		 $c['hm'] = "Heard and McDonald Islands";
305		 $c['hn'] = "Honduras";
306		 $c['hk'] = "Hong Kong";
307		 $c['hu'] = "Hungary";
308		 $c['is'] = "Iceland";
309		 $c['in'] = "India";
310		 $c['id'] = "Indonesia";
311		 $c['ir'] = "Iran";
312		 $c['iq'] = "Iraq";
313		 $c['ie'] = "Ireland";
314		 $c['il'] = "Israel";
315		 $c['it'] = "Italy";
316		 $c['ci'] = "Ivory Coast (Cote D'Ivoire)";
317		 $c['jm'] = "Jamaica";
318		 $c['jp'] = "Japan";
319		 $c['jo'] = "Jordan";
320		 $c['kz'] = "Kazakhstan";
321		 $c['ke'] = "Kenya";
322		 $c['ki'] = "Kiribati";
323		 $c['kp'] = "Korea (North)";
324		 $c['kr'] = "Korea (South)";
325		 $c['kw'] = "Kuwait";
326		 $c['kg'] = "Kyrgyzstan";
327		 $c['la'] = "Laos";
328		 $c['lv'] = "Latvia";
329		 $c['lb'] = "Lebanon";
330		 $c['ls'] = "Lesotho";
331		 $c['lr'] = "Liberia";
332		 $c['ly'] = "Libya";
333		 $c['li'] = "Liechtenstein";
334		 $c['lt'] = "Lithuania";
335		 $c['lu'] = "Luxembourg";
336		 $c['mo'] = "Macau";
337		 $c['mk'] = "Macedonia";
338		 $c['mg'] = "Madagascar";
339		 $c['mw'] = "Malawi";
340		 $c['my'] = "Malaysia";
341		 $c['mv'] = "Maldives";
342		 $c['ml'] = "Mali";
343		 $c['mt'] = "Malta";
344		 $c['mh'] = "Marshall Islands";
345		 $c['mq'] = "Martinique (French)";
346		 $c['mr'] = "Mauritania";
347		 $c['mu'] = "Mauritius";
348		 $c['yt'] = "Mayotte";
349		 $c['mx'] = "Mexico";
350		 $c['fm'] = "Micronesia";
351		 $c['md'] = "Moldavia";
352		 $c['mc'] = "Monaco";
353		 $c['mn'] = "Mongolia";
354		 $c['me'] = "Montenegro";
355		 $c['ms'] = "Montserrat";
356		 $c['ma'] = "Morocco";
357		 $c['mz'] = "Mozambique";
358		 $c['mm'] = "Myanmar";
359		 $c['na'] = "Namibia";
360		 $c['nr'] = "Nauru";
361		 $c['np'] = "Nepal";
362		 $c['nl'] = "Netherlands";
363		 $c['an'] = "Netherlands Antilles";
364		 // $c['net'] = "Network";
365
366		 $c['nc'] = "New Caledonia (French)";
367		 $c['nz'] = "New Zealand";
368		 $c['ni'] = "Nicaragua";
369		 $c['ne'] = "Niger";
370		 $c['ng'] = "Nigeria";
371		 $c['nu'] = "Niue";
372		 $c['nf'] = "Norfolk Island";
373
374		 $c['mp'] = "Northern Mariana Islands";
375		 $c['no'] = "Norway";
376		//  $c['arpa'] = "Old style Arpanet";
377		 $c['om'] = "Oman";
378		 $c['pk'] = "Pakistan";
379		 $c['pw'] = "Palau";
380		 $c['pa'] = "Panama";
381		 $c['pg'] = "Papua New Guinea";
382		 $c['py'] = "Paraguay";
383		 $c['pe'] = "Peru";
384		 $c['ph'] = "Philippines";
385		 $c['pn'] = "Pitcairn Island";
386		 $c['pl'] = "Poland";
387		 $c['pf'] = "Polynesia (French)";
388		 $c['pt'] = "Portugal";
389		 $c['pr'] = "Puerto Rico";
390		 $c['ps'] = "Palestine";
391		 $c['qa'] = "Qatar";
392		 $c['re'] = "Reunion (French)";
393		 $c['ro'] = "Romania";
394		 $c['ru'] = "Russia";
395		 $c['rw'] = "Rwanda";
396		 $c['gs'] = "S. Georgia &amp; S. Sandwich Isls.";
397		 $c['sh'] = "Saint Helena";
398		 $c['kn'] = "Saint Kitts &amp; Nevis";
399		 $c['lc'] = "Saint Lucia";
400		 $c['pm'] = "Saint Pierre and Miquelon";
401		 $c['st'] = "Saint Tome (Sao Tome) and Principe";
402		 $c['vc'] = "Saint Vincent &amp; Grenadines";
403		 $c['ws'] = "Samoa";
404		 $c['sm'] = "San Marino";
405		 $c['sa'] = "Saudi Arabia";
406		 $c['sn'] = "Senegal";
407		 $c['rs'] = "Serbia";
408		 $c['sc'] = "Seychelles";
409		 $c['sl'] = "Sierra Leone";
410		 $c['sg'] = "Singapore";
411		 $c['sk'] = "Slovak Republic";
412		 $c['si'] = "Slovenia";
413		 $c['sb'] = "Solomon Islands";
414		 $c['so'] = "Somalia";
415		 $c['za'] = "South Africa";
416
417		 $c['es'] = "Spain";
418		 $c['lk'] = "Sri Lanka";
419		 $c['sd'] = "Sudan";
420		 $c['sr'] = "Suriname";
421		 $c['sj'] = "Svalbard and Jan Mayen Islands";
422		 $c['sz'] = "Swaziland";
423		 $c['se'] = "Sweden";
424		 $c['ch'] = "Switzerland";
425		 $c['sy'] = "Syria";
426		 $c['tj'] = "Tadjikistan";
427		 $c['tw'] = "Taiwan";
428		 $c['tz'] = "Tanzania";
429		 $c['th'] = "Thailand";
430		 $c['ti'] = "Tibet";
431		 $c['tg'] = "Togo";
432		 $c['tk'] = "Tokelau";
433		 $c['to'] = "Tonga";
434		 $c['tt'] = "Trinidad and Tobago";
435		 $c['tn'] = "Tunisia";
436		 $c['tr'] = "Turkey";
437		 $c['tm'] = "Turkmenistan";
438		 $c['tc'] = "Turks and Caicos Islands";
439		 $c['tv'] = "Tuvalu";
440		 $c['ug'] = "Uganda";
441		 $c['ua'] = "Ukraine";
442		 $c['ae'] = "United Arab Emirates";
443		 $c['gb'] = "United Kingdom";
444		 $c['us'] = "United States";
445		 $c['uy'] = "Uruguay";
446		 $c['um'] = "US Minor Outlying Islands";
447		 $c['uz'] = "Uzbekistan";
448		 $c['vu'] = "Vanuatu";
449		 $c['va'] = "Vatican City State";
450		 $c['ve'] = "Venezuela";
451		 $c['vn'] = "Vietnam";
452		 $c['vg'] = "Virgin Islands (British)";
453		 $c['vi'] = "Virgin Islands (USA)";
454		 $c['wf'] = "Wallis and Futuna Islands";
455		 $c['eh'] = "Western Sahara";
456		 $c['ye'] = "Yemen";
457
458		// $c['zr'] = "(deprecated) Zaire";
459		 $c['zm'] = "Zambia";
460		 $c['zw'] = "Zimbabwe";
461
462
463        if(!empty($iso) && !empty($c[$iso]))
464        {
465            return $c[$iso];
466        }
467
468
469		return ($iso === null) ? $c : '';
470
471	}
472
473
474	/**
475	 * Get required field markup string
476	 * @return string
477	 */
478	public function getRequiredString()
479	{
480		return $this->_required_string;
481	}
482
483	/**
484	 * Set required field markup string
485	 * @param string $string
486	 * @return e_form
487	 */
488	public function setRequiredString($string)
489	{
490		$this->_required_string = $string;
491		return $this;
492	}
493
494	// For Comma separated keyword tags.
495	function tags($name, $value, $maxlength = 200, $options = array())
496	{
497	  if(is_string($options)) parse_str($options, $options);
498
499	  $defaults['selectize'] = array(
500		'create'   => true,
501		'maxItems' => vartrue($options['maxItems'], 7),
502		'mode'     => 'multi',
503		'plugins'  => array('remove_button'),
504	  );
505
506	  $options = array_replace_recursive($defaults, $options);
507
508	  return $this->text($name, $value, $maxlength, $options);
509	}
510
511
512	/**
513	 * Render Bootstrap Tabs
514	 *
515	 * @param $array
516	 * @param $options
517	 * @return string
518	 * @example
519	 *        $array = array(
520	 *        'home' => array('caption' => 'Home', 'text' => 'some tab content' ),
521	 *        'other' => array('caption' => 'Other', 'text' => 'second tab content' )
522	 *        );
523	 */
524	function tabs($array,$options = array())
525	{
526		$initTab = varset($options['active'],false);
527		$id = !empty($options['id']) ? 'id="'.$options['id'].'"' : '';
528		$text  ='
529		<!-- Nav tabs -->
530			<ul '.$id.' class="nav nav-tabs">';
531
532		$c = 0;
533
534		foreach($array as $key=>$tab)
535		{
536
537			if(is_numeric($key))
538			{
539				$key = 'tab-'.$key;
540			}
541
542			if($c == 0 & $initTab == false)
543			{
544				$initTab = $key;
545			}
546
547
548
549			$active = ($key ==$initTab) ? ' class="nav-item active"' : ' class="nav-item"';
550			$text .= '<li'.$active.'><a class="nav-link" href="#'.$key.'" data-toggle="tab">'.$tab['caption'].'</a></li>';
551			$c++;
552		}
553
554		$text .= '</ul>';
555
556		$initTab = varset($options['active'],false);
557		$tabClass = varset($options['class'],null);
558
559		$text .= '
560		<!-- Tab panes -->
561		<div class="tab-content '.$tabClass.'">';
562
563		$c=0;
564		foreach($array as $key=>$tab)
565		{
566
567
568			if(is_numeric($key))
569			{
570				$key = 'tab-'.$key;
571			}
572
573			if($c == 0 & $initTab == false)
574			{
575				$initTab = $key;
576			}
577
578			$active = ($key == $initTab) ? ' active' : '';
579			$text .= '<div class="tab-pane'.$active.'" id="'.$key.'">'.$tab['text'].'</div>';
580			$c++;
581		}
582
583		$text .= '
584		</div>';
585
586		return $text;
587
588	}
589
590
591	/**
592	 * Render Bootstrap Carousel
593	 * @param string $name : A unique name
594	 * @param array $array
595	 * @param array $options : default, interval, pause, wrap, navigation, indicators
596	 * @return string|array
597	 * @example
598	 * $array = array(
599	 *        'slide1' => array('caption' => 'Slide 1', 'text' => 'first slide content' ),
600	 *        'slide2' => array('caption' => 'Slide 2', 'text' => 'second slide content' ),
601	 *        'slide3' => array('caption' => 'Slide 3', 'text' => 'third slide content' )
602	 *    );
603	 */
604	function carousel($name="e-carousel", $array=array(), $options = null)
605	{
606		$interval   = null;
607		$wrap       = null;
608		$pause      = null;
609		$indicators = '';
610		$controls   = '';
611
612		$act = varset($options['default'], 0);
613
614		if(isset($options['wrap']))
615		{
616			$wrap = 'data-wrap="'.$options['wrap'].'"';
617		}
618
619		if(isset($options['interval']))
620		{
621			$interval = 'data-interval="'.$options['interval'].'"';
622		}
623
624		if(isset($options['pause']))
625		{
626			$pause = 'data-pause="'.$options['pause'].'"';
627		}
628
629		$navigation = isset($options['navigation']) ? $options['navigation'] : true;
630		$indicate = isset($options['indicators']) ? $options['indicators'] : true;
631
632
633		$start  ='
634		<!-- Carousel -->
635
636		<div id="'.$name.'" class="carousel slide" data-ride="carousel" '.$interval.' '.$wrap.' '.$pause.'>';
637
638		if($indicate && (count($array) > 1))
639		{
640			$indicators = '
641	        <!-- Indicators -->
642	        <ol class="carousel-indicators">
643			';
644
645			$c = 0;
646			foreach($array as $key=>$tab)
647			{
648				$active = ($c == $act) ? ' class="active"' : '';
649				$indicators .=  '<li data-target="#'.$name.'" data-slide-to="'.$c.'" '.$active.'></li>';
650				$c++;
651			}
652
653			$indicators .= '
654			</ol>';
655		}
656
657		$inner = '
658
659		<div class="carousel-inner">
660		';
661
662
663		$c=0;
664		foreach($array as $key=>$tab)
665		{
666			$active = ($c == $act) ? ' active' : '';
667			$label = !empty($tab['label']) ? ' data-label="'.$tab['label'].'"' : '';
668			$inner .= '<div class="carousel-item item'.$active.'" id="'.$key.'"'.$label.'>';
669			$inner .= $tab['text'];
670
671			if(!empty($tab['caption']))
672			{
673				$inner .= '<div class="carousel-caption">'.$tab['caption'].'</div>';
674			}
675
676			$inner .= '</div>';
677			$c++;
678		}
679
680		$inner .= '
681		</div>';
682
683		if($navigation && (count($array) > 1))
684		{
685			$controls = '
686			<a class="left carousel-control carousel-left" href="#'.$name.'" role="button" data-slide="prev">
687	        <span class="glyphicon glyphicon-chevron-left"></span>
688			</a>
689			<a class="right carousel-control carousel-right" href="#'.$name.'" role="button" data-slide="next">
690			<span class="glyphicon glyphicon-chevron-right"></span>
691			</a>';
692		}
693
694		$end = '</div><!-- End Carousel -->';
695
696		if(!empty($options['data']))
697		{
698			return array(
699				'start'         => $start,
700				'indicators'    => $indicators,
701				'inner'         => $inner,
702				'controls'      => $controls,
703				'end'           => $end
704			);
705		}
706
707		return $start.$indicators.$inner.$controls.$end; // $text;
708
709	}
710
711	/**
712	 * Same as $this->text() except it adds input validation for urls.
713	 * At this stage, checking only for spaces. Should include sef-urls.
714	 *
715	 * @param string  $name
716	 * @param string $value
717	 * @param int    $maxlength
718	 * @param array  $options
719	 * @return string
720	 */
721	function url($name, $value = '', $maxlength = 80, $options= array())
722	{
723		$options['pattern'] = '^\S*$';
724		return $this->text($name, $value, $maxlength, $options);
725	}
726
727	/**
728	 * Text-Field Form Element
729	 * @param $name
730	 * @param $value
731	 * @param $maxlength
732	 * @param $options
733	 *  - size: mini, small, medium, large, xlarge, xxlarge
734	 *  - class:
735	 *  - typeahead: 'users'
736	 *
737	 * @return string
738	 */
739	function text($name, $value = '', $maxlength = 80, $options= array())
740	{
741		if(is_string($options))
742		{
743			parse_str($options,$options);
744		}
745
746		if(!vartrue($options['class']))
747		{
748			$options['class'] = "tbox";
749		}
750
751		if(deftrue('BOOTSTRAP'))
752		{
753			$options['class'] .= ' form-control';
754		}
755
756		/*
757		if(!vartrue($options['class']))
758		{
759			if($maxlength < 10)
760			{
761				$options['class'] = 'tbox input-text span3';
762			}
763
764			elseif($maxlength < 50)
765			{
766				$options['class'] = 'tbox input-text span7';
767			}
768
769			elseif($maxlength > 99)
770			{
771				 $options['class'] = 'tbox input-text span7';
772			}
773			else
774			{
775				$options['class'] = 'tbox input-text';
776			}
777		}
778		*/
779
780		if(!empty($options['selectize']))
781		{
782			e107::js('core', 'selectize/js/selectize.min.js', 'jquery');
783			e107::css('core', 'selectize/css/selectize.css', 'jquery');
784
785			// Load selectize behavior.
786			e107::js('core', 'selectize/js/selectize.init.js', 'jquery');
787
788			$options['selectize']['wrapperClass'] = 'selectize-control';
789			$options['selectize']['inputClass'] = 'form-control selectize-input ';
790			$options['selectize']['dropdownClass'] = 'selectize-dropdown';
791			$options['selectize']['dropdownContentClass'] = 'selectize-dropdown-content';
792			$options['selectize']['copyClassesToDropdown'] = true;
793
794			$jsSettings = array(
795				'id'      => vartrue($options['id'], $this->name2id($name)),
796				'options' => $options['selectize'],
797				// Multilingual support.
798				'strings' => array(
799						'anonymous' => LAN_ANONYMOUS,
800				),
801			);
802
803			// Merge field settings with other selectize field settings.
804			e107::js('settings', array('selectize' => array($jsSettings)));
805
806			$options['class'] = '';
807		}
808
809		// TODO: remove typeahead.
810		if(vartrue($options['typeahead']))
811		{
812			if(vartrue($options['typeahead']) == 'users')
813			{
814				$options['data-source'] = e_BASE."user.php";
815				$options['class'] .= " e-typeahead";
816			}
817		}
818
819		if(vartrue($options['size']) && !is_numeric($options['size']))
820		{
821			$options['class'] .= " input-".$options['size'];
822			unset($options['size']); // don't include in html 'size='.
823		}
824
825		$mlength = vartrue($maxlength) ? "maxlength=".$maxlength : "";
826
827		$type = varset($options['type']) == 'email' ? 'email' : 'text'; // used by $this->email();
828
829		$options = $this->format_options('text', $name, $options);
830
831
832		//never allow id in format name-value for text fields
833		return "<input type='".$type."' name='{$name}' value='{$value}' {$mlength} ".$this->get_attributes($options, $name)." />";
834	}
835
836
837	/**
838	 * Create a input [type number]
839	 *
840	 * Additional options:
841	 *   - decimals: default 0; defines the number of decimals allowed in this field (0 = only integers; 1 = integers & floats with 1 decimal e.g. 4.1, etc.)
842	 *   - step: default 1; defines the step for the spinner and the max. number of decimals. If decimals is given, step will be ignored
843	 *   - min: default 0; minimum value allowed
844	 *   - max: default empty; maximum value allowed
845	 *   - pattern: default empty; allows to define an complex input pattern
846	 *
847	 * @param string $name
848	 * @param integer $value
849	 * @param integer $maxlength
850	 * @param array $options decimals, step, min, max, pattern
851	 * @return string
852	 */
853	function number($name, $value=0, $maxlength = 200, $options = array())
854	{
855		if(is_string($options)) parse_str($options, $options);
856
857		if(!empty($options['maxlength']))
858		{
859			 $maxlength = $options['maxlength'];
860		}
861
862		unset($options['maxlength']);
863
864		if(empty($options['size']))
865		{
866			 $options['size'] = 15;
867		}
868		if(empty($options['class']))
869		{
870			 $options['class'] = 'tbox number e-spinner input-small ';
871		}
872
873		if(!empty($options['size']))
874		{
875			$options['class'] .= ' input-'.$options['size'];
876			unset($options['size']);
877		}
878
879		$options['class'] .= " form-control";
880		$options['type'] ='number';
881
882		// Not used anymore
883		//$mlength = vartrue($maxlength) ? "maxlength=".$maxlength : "";
884
885		// Always define the min. parameter
886		// defaults to 0
887		// setting the min option to a negative value allows negative inputs
888		$min = " min='".vartrue($options['min'], '0')."'";
889		$max = isset($options['max']) ? " max='".$options['max']."'" : '';
890
891
892		if (empty($options['pattern']))
893		{
894			$options['pattern'] = '^';
895			// ^\-?[0-9]*\.?[0-9]{0,2}
896			if (varset($options['min'], 0) < 0)
897			{
898				$options['pattern'] .= '\-?';
899			}
900			$options['pattern'] .= '[0-9]*';
901
902			// Integer & Floaat/Double value handling
903			if (isset($options['decimals']))
904			{
905				if (intval($options['decimals']) > 0)
906				{
907					$options['pattern'] .= '\.?[0-9]{0,'.intval($options['decimals']).'}';
908				}
909
910				// defined the step based on number of decimals
911				// 2 = 0.01 > allows integers and float numbers with up to 2 decimals (3.1 = OK; 3.12 = OK; 3.123 = NOK)
912				// 1 = 0.1 > allows integers and float numbers with up to 2 decimals (3.1 = OK; 3.12 = NOK)
913				// 0 = 1 > allows only integers, no float values
914				if (intval($options['decimals']) <= 0)
915				{
916					$step = "step='1'";
917				}
918				else
919				{
920					$step = "step='0." . str_pad(1, intval($options['decimals']), 0, STR_PAD_LEFT)  . "'";
921				}
922			}
923			else
924			{
925				// decimal option not defined
926				// check for step option (1, 0.1, 0.01, and so on)
927				// or set default step 1 (integers only)
928				$step = "step='" . vartrue($options['step'], '1') . "'";
929			}
930
931		}
932
933		$options = $this->format_options('text', $name, $options);
934
935		//never allow id in format name-value for text fields
936		if(THEME_LEGACY === false)
937		{
938			// return "<input pattern='[0-9]*' type='number' name='{$name}' value='{$value}' {$mlength} {$step} {$min} {$max} ".$this->get_attributes($options, $name)." />";
939			return "<input type='number' name='{$name}' {$min} {$max} {$step} value='{$value}' ".$this->get_attributes($options, $name)." />";
940		}
941
942		return $this->text($name, $value, $maxlength, $options);
943	}
944
945
946
947	function email($name, $value, $maxlength = 200, $options = array())
948	{
949		$options['type'] = 'email';
950		return $this->text($name,$value,$maxlength,$options);
951	}
952
953
954
955	function iconpreview($id, $default, $width='', $height='') // FIXME
956	{
957		unset($width,$height); // quick fix
958		// XXX - $name ?!
959	//	$parms = $name."|".$width."|".$height."|".$id;
960		$sc_parameters = 'mode=preview&default='.$default.'&id='.$id;
961		return e107::getParser()->parseTemplate("{ICONPICKER=".$sc_parameters."}");
962	}
963
964	/**
965	 * @param $name
966	 * @param $default - value
967	 * @param $label
968	 * @param $options - gylphs=1
969	 * @param $ajax
970	 * @return string
971	 */
972	function iconpicker($name, $default, $label, $options = array(), $ajax = true)
973	{
974		//v2.2.0
975		unset($label,$ajax);  // no longer used.
976
977		$options['icon'] = 1;
978		$options['glyph'] = 1;
979		$options['w'] = 64;
980		$options['h'] = 64;
981		$options['media'] = '_icon';
982
983		if(!isset($options['legacyPath']))
984		{
985		       $options['legacyPath'] = "{e_IMAGE}icons";
986		}
987
988		return $this->mediapicker($name, $default, $options);
989
990
991	/*	$options['media'] = '_icon';
992		$options['legacyPath'] = "{e_IMAGE}icons";
993
994		return $this->imagepicker($name, $default, $label, $options);*/
995
996
997	}
998
999
1000	/**
1001	 * Internal Function used by imagepicker, filepicker, mediapicker()
1002	 * @param string $category
1003	 * @param string $label
1004	 * @param string $tagid
1005	 * @param null   $extras
1006	 * @return string
1007	 */
1008	public function mediaUrl($category = '', $label = '', $tagid='', $extras=null)
1009	{
1010		if(is_string($extras))
1011		{
1012			parse_str($extras,$extras);
1013		}
1014
1015		$category = str_replace('+', '^', $category); // Bc Fix.
1016
1017		$cat    = ($category) ? '&amp;for='.urlencode($category) : "";
1018		$mode   = vartrue($extras['mode'],'main');
1019		$action = vartrue($extras['action'],'dialog');
1020
1021
1022
1023		if(empty($label))
1024		{
1025			 $label = ' Upload an image or file';
1026		}
1027
1028		// TODO - option to choose which tabs to display by default.
1029
1030		$url = e_ADMIN_ABS."image.php?mode={$mode}&amp;action={$action}".$cat;
1031
1032		if(!empty($tagid))
1033		{
1034			$url .= '&amp;tagid='.$tagid;
1035		}
1036
1037		if(!empty($extras['bbcode']))
1038		{
1039			$url .= '&amp;bbcode='.$extras['bbcode'];
1040		}
1041
1042		$url .= "&amp;iframe=1";
1043
1044		if(vartrue($extras['w']))
1045		{
1046			$url .= "&amp;w=".$extras['w'];
1047		}
1048
1049		if(!empty($extras['image']))
1050		{
1051			$url .= "&amp;image=1";
1052		}
1053
1054		if(!empty($extras['glyphs']) || !empty($extras['glyph']))
1055		{
1056			$url .= "&amp;glyph=1";
1057		}
1058
1059		if(!empty($extras['icons']) || !empty($extras['icon']))
1060		{
1061			$url .= "&amp;icon=1";
1062		}
1063
1064		if(!empty($extras['youtube']))
1065		{
1066			$url .= "&amp;youtube=1";
1067		}
1068
1069		if(!empty($extras['video']))
1070		{
1071			$url .= ($extras['video'] == 2) ? "&amp;video=2" : "&amp;video=1";
1072		}
1073
1074		if(!empty($extras['audio']))
1075		{
1076			$url .= "&amp;audio=1";
1077		}
1078
1079		if(!empty($extras['path']) && $extras['path'] == 'plugin')
1080		{
1081			$url .= "&amp;path=".deftrue('e_CURRENT_PLUGIN');
1082		}
1083
1084		if(E107_DBG_BASIC)
1085		{
1086
1087			$title = "Media Manager : ".$category;
1088		}
1089		else
1090		{
1091			$title = LAN_EDIT;
1092		}
1093
1094		$class = !empty($extras['class']) ? $extras['class']." " : '';
1095		$title = !empty($extras['title']) ? $extras['title'] : $title;
1096
1097	    $ret = "<a title=\"{$title}\" class='".$class."e-modal' data-modal-submit='true' data-modal-caption='".LAN_EFORM_007."' data-cache='false' data-target='#uiModal' href='".$url."'>".$label."</a>"; // using bootstrap.
1098
1099		if(!e107::getRegistry('core/form/mediaurl'))
1100		{
1101			e107::setRegistry('core/form/mediaurl', true);
1102		}
1103
1104		return $ret;
1105	}
1106
1107
1108	/**
1109	 * Avatar Picker
1110	 * @param string $name - form element name ie. value to be posted.
1111	 * @param string $curVal - current avatar value. ie. the image-file name or URL.
1112	 * @param array $options
1113	 * @todo add a pref for allowing external or internal avatars or both.
1114	 * @return string
1115	 */
1116	function avatarpicker($name, $curVal='',$options=array())
1117	{
1118
1119		$tp 		= e107::getParser();
1120		$pref 		= e107::getPref();
1121
1122		$attr 		= "aw=".$pref['im_width']."&ah=".$pref['im_height'];
1123		$tp->setThumbSize($pref['im_width'],$pref['im_height']);
1124
1125		$blankImg 	= $tp->thumbUrl(e_IMAGE."generic/blank_avatar.jpg",$attr);
1126		$localonly 	= true;
1127		$idinput 	= $this->name2id($name);
1128		$previnput	= $idinput."-preview";
1129		$optioni 	= $idinput."-options";
1130
1131
1132		$path = (substr($curVal,0,8) == '-upload-') ? '{e_AVATAR}upload/' : '{e_AVATAR}default/';
1133		$newVal = str_replace('-upload-','',$curVal);
1134
1135		$img = (strpos($curVal,"://")!==false) ? $curVal : $tp->thumbUrl($path.$newVal);
1136
1137		if(!$curVal)
1138		{
1139			$img = $blankImg;
1140		}
1141
1142		$parm = $options;
1143		$classlocal = (!empty($parm['class'])) ? "class='".$parm['class']." e-expandit  e-tip avatar'" : " class='img-rounded rounded e-expandit e-tip avatar ";
1144		$class = (!empty($parm['class'])) ? "class='".$parm['class']." e-expandit '" : " class='img-rounded rounded btn btn-default btn-secondary button e-expandit ";
1145
1146		if($localonly == true)
1147		{
1148			$text = "<input class='tbox' style='width:80%' id='{$idinput}' type='hidden' name='image' value='{$curVal}'  />";
1149			$text .= "<img src='".$img."' id='{$previnput}' ".$classlocal." style='cursor:pointer; width:".$pref['im_width']."px; height:".$pref['im_height']."px' title='".LAN_EFORM_001."' alt='".LAN_EFORM_001."' />";
1150		}
1151		else
1152		{
1153			$text = "<input class='tbox' style='width:80%' id='{$idinput}' type='text' name='image' size='40' value='$curVal' maxlength='100' title=\"".LAN_SIGNUP_111."\" />";
1154			$text .= "<img src='".$img."' id='{$previnput}' style='display:none' />";
1155			$text .= "<input ".$class." type ='button' style='cursor:pointer' size='30' value=\"".LAN_EFORM_002."\"  />";
1156		}
1157
1158		$avFiles = e107::getFile()->get_files(e_AVATAR_DEFAULT,".jpg|.png|.gif|.jpeg|.JPG|.GIF|.PNG");
1159
1160		$text .= "\n<div id='{$optioni}' style='display:none;padding:10px' >\n"; //TODO unique id.
1161		$count = 0;
1162		if (vartrue($pref['avatar_upload']) && FILE_UPLOADS && vartrue($options['upload']))
1163		{
1164				$diz = LAN_USET_32.($pref['im_width'] || $pref['im_height'] ? "\n".str_replace(array('[x]-','[y]'), array($pref['im_width'], $pref['im_height']), LAN_USER_86) : "");
1165
1166				$text .= "<div style='margin-bottom:10px'>".LAN_USET_26."
1167				<input  class='tbox' name='file_userfile[avatar]' type='file' size='47' title=\"{$diz}\" />
1168				</div>";
1169
1170				if(count($avFiles) > 0)
1171				{
1172					$text .= "<div class='divider'><span>".LAN_EFORM_003."</span></div>";
1173					$count = 1;
1174				}
1175		}
1176
1177
1178		foreach($avFiles as $fi)
1179		{
1180			$img_path = $tp->thumbUrl(e_AVATAR_DEFAULT.$fi['fname']);
1181			$text .= "\n<a class='e-expandit' title='".LAN_EFORM_004."' href='#{$optioni}'><img src='".$img_path."' alt=''  onclick=\"insertext('".$fi['fname']."', '".$idinput."');document.getElementById('".$previnput."').src = this.src;return false\" /></a> ";
1182			$count++;
1183
1184
1185			//TODO javascript CSS selector
1186		}
1187
1188		if($count == 0)
1189		{
1190			$text .= "<div class='row'>";
1191			$text .= "<div class='alert alert-info'>".LAN_EFORM_005."</div>";
1192
1193			if(ADMIN)
1194			{
1195				$EAVATAR = e_AVATAR_DEFAULT;
1196				$text .= "<div class='alert alert-danger'>";
1197				$text .= e107::getParser()->lanVars(e107::getParser()->toHTML(LAN_EFORM_006, true), array('x'=>$EAVATAR));
1198				$text .= "</div>";
1199			}
1200
1201			$text .= "</div>";
1202		}
1203
1204
1205		$text .= "
1206		</div>";
1207
1208		// Used by usersettings.php right now.
1209
1210
1211
1212
1213
1214
1215
1216		return $text;
1217		/*
1218		//TODO discuss and FIXME
1219		    // Intentionally disable uploadable avatar and photos at this stage
1220			if (false && $pref['avatar_upload'] && FILE_UPLOADS)
1221			{
1222				$text .= "<br /><span class='smalltext'>".LAN_SIGNUP_25."</span> <input class='tbox' name='file_userfile[]' type='file' size='40' />
1223				<br /><div class='smalltext'>".LAN_SIGNUP_34."</div>";
1224			}
1225
1226			if (false && $pref['photo_upload'] && FILE_UPLOADS)
1227			{
1228				$text .= "<br /><span class='smalltext'>".LAN_SIGNUP_26."</span> <input class='tbox' name='file_userfile[]' type='file' size='40' />
1229				<br /><div class='smalltext'>".LAN_SIGNUP_34."</div>";
1230			}  */
1231	}
1232
1233
1234	/**
1235	 * Image Picker
1236	 *
1237	 * @param string $name          input name
1238	 * @param string $default       default value
1239	 * @param string $previewURL
1240	 * @param string $sc_parameters shortcode parameters
1241	 *                              --- SC Parameter list ---
1242	 *                              - media: if present - load from media category table
1243	 *                              - w: preview width in pixels
1244	 *                              - h: preview height in pixels
1245	 *                              - help: tooltip
1246	 *                              - video: when set to true, will enable the Youtube  (video) tab.
1247	 * @return string html output
1248	 * @example $frm->imagepicker('banner_image', $_POST['banner_image'], '', 'banner'); // all images from category 'banner_image' + common images.
1249	 * @example $frm->imagepicker('banner_image', $_POST['banner_image'], '', 'media=banner&w=600');
1250	 */
1251	function imagepicker($name, $default, $previewURL = '', $sc_parameters = '')
1252	{
1253
1254	//	$tp = e107::getParser();
1255
1256	//	$name_id = $this->name2id($name);
1257	//	$meta_id = $name_id."-meta";
1258
1259		if(is_string($sc_parameters))
1260		{
1261			if(strpos($sc_parameters, '=') === false) $sc_parameters = 'media='.$sc_parameters;
1262			parse_str($sc_parameters, $sc_parameters);
1263		}
1264		elseif(empty($sc_parameters))
1265		{
1266			$sc_parameters = array();
1267		}
1268
1269	//	$cat = $tp->toDB(vartrue($sc_parameters['media']));
1270
1271		// v2.2.0
1272		unset($previewURL );
1273		$sc_parameters['image'] = 1;
1274		$sc_parameters['dropzone'] = 1;
1275		if(!empty($sc_parameters['video'])) // bc fix
1276		{
1277			$sc_parameters['youtube'] = 1;
1278		}
1279
1280		return $this->mediapicker($name, $default, $sc_parameters);
1281
1282
1283	}
1284
1285
1286/**
1287	 * Media Picker
1288 *
1289
1290	 * @param string $name input name
1291	 * @param string $default default value
1292	 * @param string $parms shortcode parameters
1293	 *  --- $parms list ---
1294	 * - media: if present - load from media category table
1295	 * - w: preview width in pixels
1296	 * - h: preview height in pixels
1297	 * - help: tooltip
1298	 * - youtube=1 (Enables the Youtube tab)
1299     * - image=1 (Enable the Images tab)
1300	 * - video=1 (Enable the Video tab)
1301	 * - audio=1  (Enable the Audio tab)
1302     * - glyph=1 (Enable the Glyphs tab).
1303     * - path=plugin (store in media/plugins/{current-plugin])
1304     * - edit=false (disable media-manager popup button)
1305	 * - rename (string) rename file to this value after upload.  (don't forget the extension)
1306	 * - resize array with numberic x values. (array 'w'=>x, 'h'=>x)  - resize the uploaded image before importing during upload.
1307     * - convert=jpg (override pref and convert uploaded image to jpeg format. )
1308	 * @return string html output
1309	 *@example $frm->imagepicker('banner_image', $_POST['banner_image'], '', 'media=banner&w=600');
1310	 */
1311	function mediapicker($name, $default, $parms = '')
1312	{
1313
1314
1315		$tp = e107::getParser();
1316		$name_id = $this->name2id($name);
1317		$meta_id = $name_id."-meta";
1318
1319		if(is_string($parms))
1320		{
1321			if(strpos($parms, '=') === false) $parms = 'media='.$parms;
1322			parse_str($parms, $parms);
1323		}
1324		elseif(empty($parms))
1325		{
1326			$parms = array();
1327		}
1328
1329
1330		if(empty($parms['media']))
1331		{
1332			$parms['media'] = '_common';
1333		}
1334
1335		$title = !empty($parms['help']) ? "title='".$parms['help']."'" : "";
1336
1337		if(!isset($parms['w']))
1338		{
1339			$parms['w'] = 206;
1340		}
1341
1342		if(!isset($parms['h']))
1343		{
1344			$parms['h'] = 190; // 178
1345		}
1346
1347	//	$width = vartrue($parms['w'], 220);
1348	//	$height = vartrue($parms['h'], 190);
1349	// e107::getDebug()->log($parms);
1350
1351		// Test Files...
1352	//	$default = '{e_MEDIA_VIDEO}2018-07/samplevideo_720x480_2mb.mp4';
1353	//	$default = '{e_MEDIA_FILE}2016-03/Colony_Harry_Gregson_Williams.mp3';
1354	//	$default = '{e_PLUGIN}gallery/images/butterfly.jpg';
1355	//	$default = 'NuIAYHVeFYs.youtube';
1356	//	$default = ''; // empty
1357	//	$default = '{e_MEDIA_IMAGE}2018-07/Jellyfish.jpg';
1358
1359		$class = '';
1360
1361		if(!empty($parms['icon']))
1362		{
1363			$class = 'icon-preview mediaselector-container-icon';
1364			$parms['type'] = 'icon';
1365		}
1366
1367		$preview = e107::getMedia()->previewTag($default,$parms);
1368
1369		$cat = $tp->toDB(vartrue($parms['media']));
1370
1371		$ret = "<div  class='mediaselector-container e-tip well well-small ".$class."' {$title} style='position:relative;vertical-align:top;margin-right:15px; display:inline-block; width:".$parms['w']."px;min-height:".$parms['h']."px;'>";
1372
1373		$parms['class'] = 'btn btn-sm btn-default';
1374
1375		$dropzone = !empty($parms['dropzone']) ? " dropzone" : "";
1376	//	$parms['modal-delete-label'] = LAN_DELETE;
1377
1378		if(empty($preview))
1379		{
1380			$parms['title'] = LAN_ADD;
1381			$editIcon        = $this->mediaUrl($cat, $tp->toGlyph('fa-plus', array('fw'=>1)), $name_id,$parms);
1382			$previewIcon     = '';
1383		}
1384		else
1385		{
1386			$editIcon       = $this->mediaUrl($cat, $tp->toGlyph('fa-edit', array('fw'=>1)), $name_id,$parms);
1387		//	$previewIcon    = "<a title='".LAN_PREVIEW."' class='btn btn-sm btn-default btn-secondary e-modal' data-modal-caption='".LAN_PREVIEW."' href='".$previewURL."'>".$tp->toGlyph('fa-search', array('fw'=>1))."</a>";
1388			$previewIcon    = '';
1389		}
1390
1391		if(isset($parms['edit']) && $parms['edit'] === false) // remove media-manager add/edit button. ie. drag-n-drop only.
1392		{
1393			$editIcon = '';
1394		}
1395
1396
1397		if(!empty($parms['icon'])) // empty overlay without button.
1398		{
1399			$parms['class'] = '';
1400			$editIcon = $this->mediaUrl($cat, "<span><!-- --></span>", $name_id,$parms);
1401		}
1402
1403		$ret .= "<div id='{$name_id}_prev' class='mediaselector-preview".$dropzone."'>";
1404
1405		$ret .= $preview; // image, video. audio tag etc.
1406
1407		$ret .= '</div><div class="overlay">
1408				    <div class="text">'.$editIcon.$previewIcon.'</div>
1409				  </div>';
1410
1411		$ret .= "</div>\n";
1412		$ret .=	"<input type='hidden' name='{$name}' id='{$name_id}' value='{$default}' />";
1413		$ret .=	"<input type='hidden' name='mediameta_{$name}' id='{$meta_id}' value='' />";
1414
1415		if(empty($dropzone))
1416		{
1417			return $ret;
1418		}
1419
1420		if(!isset($parms['label']))
1421		{
1422			$parms['label'] = defset('LAN_UI_DROPZONE_DROP_FILES', "Drop files here to upload");
1423		}
1424
1425		$qry = "for=".$cat;
1426
1427		if(!empty($parms['path']) && $parms['path'] == 'plugin')
1428		{
1429			$qry .= "&path=".deftrue('e_CURRENT_PLUGIN');
1430		}
1431
1432		if(!empty($parms['rename']))
1433		{
1434			$qry .= "&rename=".$parms['rename'];
1435		}
1436
1437		if(!empty($parms['convert']))
1438		{
1439			$qry .= "&convert=".$parms['convert'];
1440		}
1441
1442		if(isset($parms['w']))
1443		{
1444			$qry .= "&w=".(int) $parms['w'];
1445		}
1446
1447		if(isset($parms['h']))
1448		{
1449			$qry .= "&h=".(int) $parms['h'];
1450		}
1451
1452		if(!empty($parms['resize']))
1453		{
1454			$resize = array('resize'=>$parms['resize']);
1455			$qry .= "&".http_build_query($resize);
1456		}
1457
1458
1459		// Drag-n-Drop Upload
1460		// @see https://www.dropzonejs.com/#server-side-implementation
1461
1462		e107::js('footer', e_WEB_ABS."lib/dropzone/dropzone.min.js");
1463		e107::css('url', e_WEB_ABS."lib/dropzone/dropzone.min.css");
1464		e107::css('inline', "
1465			.dropzone { background: transparent; border:0 }
1466		");
1467
1468
1469
1470			$INLINEJS = "
1471				Dropzone.autoDiscover = false;
1472				$(function() {
1473				    $('#".$name_id."_prev').dropzone({
1474				        url: '".e_JS."plupload/upload.php?".$qry."',
1475				        createImageThumbnails: false,
1476				        uploadMultiple :false,
1477						dictDefaultMessage: \"".$parms['label']."\",
1478				        maxFilesize: ".(int) ini_get('upload_max_filesize').",
1479				         success: function (file, response) {
1480
1481				            file.previewElement.classList.add('dz-success');
1482
1483				         //   console.log(response);
1484
1485				            if(response)
1486				            {
1487				                var decoded = jQuery.parseJSON(response);
1488				                console.log(decoded);
1489				                if(decoded.preview && decoded.result)
1490				                {
1491				                    $('#".$name_id."').val(decoded.result);
1492				                    $('#".$name_id."_prev').html(decoded.preview);
1493				                }
1494								else if(decoded.error)
1495								{
1496									file.previewElement.classList.add('dz-error');
1497									$('#".$name_id."_prev').html(decoded.error.message);
1498								}
1499				            }
1500
1501				        },
1502				        error: function (file, response) {
1503				            file.previewElement.classList.add('dz-error');
1504				        }
1505				    });
1506				});
1507
1508			";
1509
1510
1511		e107::js('footer-inline', $INLINEJS);
1512
1513		return $ret;
1514
1515	}
1516
1517
1518
1519	/**
1520	 * File Picker
1521	 *
1522	 * @param string name  eg. 'myfield' or 'myfield[]'
1523	 * @param mixed default
1524	 * @param string label
1525	 * @param mixed sc_parameters
1526	 * @return string
1527	 */
1528	function filepicker($name, $default, $label = '', $sc_parameters = null)
1529	{
1530		$tp = e107::getParser();
1531		$name_id = $this->name2id($name);
1532		unset($label);
1533
1534		if(is_string($sc_parameters))
1535		{
1536			if(strpos($sc_parameters, '=') === false) $sc_parameters = 'media='.$sc_parameters;
1537			parse_str($sc_parameters, $sc_parameters);
1538		}
1539
1540		$cat = vartrue($sc_parameters['media']) ? $tp->toDB($sc_parameters['media']) : "_common_file";
1541
1542		$ret = '';
1543
1544		if($sc_parameters['data'] === 'array')
1545		{
1546			// Do not use $this->hidden() method - as it will break 'id' value.
1547			$ret .=	"<input type='hidden' name='".$name."[path]' id='".$this->name2id($name."[path]")."' value='".varset($default['path'])."'  />";
1548			$ret .=	"<input type='hidden' name='".$name."[name]' id='".$this->name2id($name."[name]")."' value='".varset($default['name'])."'  />";
1549			$ret .=	"<input type='hidden' name='".$name."[id]' id='".$this->name2id($name."[id]")."' value='".varset($default['id'])."'  />";
1550
1551			$default = $default['path'];
1552		}
1553		else
1554		{
1555			$ret .=	"<input type='hidden' name='{$name}' id='{$name_id}' value='{$default}' style='width:400px' />";
1556		}
1557
1558
1559		$default_label 				= ($default) ? $default : LAN_CHOOSE_FILE;
1560		$label 						= "<span id='{$name_id}_prev' class='btn btn-default btn-secondary btn-small'>".basename($default_label)."</span>";
1561
1562		$sc_parameters['mode'] 		= 'main';
1563		$sc_parameters['action'] 	= 'dialog';
1564
1565
1566	//	$ret .= $this->mediaUrl($cat, $label,$name_id,"mode=dialog&action=list");
1567		$ret .= $this->mediaUrl($cat, $label,$name_id,$sc_parameters);
1568
1569
1570
1571
1572		return $ret;
1573
1574
1575	}
1576
1577
1578
1579
1580	/**
1581	 *	Date field with popup calendar // NEW in 0.8/2.0
1582	 * on Submit returns unix timestamp or string value.
1583	 * @param string $name the name of the field
1584	 * @param int|bool $datestamp UNIX timestamp - default value of the field
1585	 * @param array|string {
1586	 *      @type string mode date or datetime
1587	 *      @type string format strftime format eg. '%Y-%m-%d'
1588	 *      @type string timezone eg. 'America/Los_Angeles' - intended timezone of the date/time entered. (offsets UTC value)
1589	 *      }
1590	 * @example $frm->datepicker('my_field',time(),'mode=date');
1591	 * @example $frm->datepicker('my_field',time(),'mode=datetime&inline=1');
1592	 * @example $frm->datepicker('my_field',time(),'mode=date&format=yyyy-mm-dd');
1593	 * @example $frm->datepicker('my_field',time(),'mode=datetime&format=MM, dd, yyyy hh:ii');
1594	 * @example $frm->datepicker('my_field',time(),'mode=datetime&return=string');
1595	 *
1596	 * @url http://trentrichardson.com/examples/timepicker/
1597	 * @return string
1598	 */
1599	function datepicker($name, $datestamp = false, $options = null)
1600	{
1601		if(!empty($options) && is_string($options))
1602		{
1603			parse_str($options,$options);
1604		}
1605
1606		$mode = !empty($options['mode']) ? trim($options['mode']) : "date"; // OR  'datetime'
1607
1608		if(!empty($options['type'])) /** BC Fix. 'type' is @deprecated */
1609		{
1610			$mode = trim($options['type']);
1611		}
1612
1613		$dateFormat  = !empty($options['format']) ? trim($options['format']) :e107::getPref('inputdate', '%Y-%m-%d');
1614		$ampm		 = (preg_match("/%l|%I|%p|%P/",$dateFormat)) ? 'true' : 'false';
1615		$value		 = null;
1616		$hiddenValue = null;
1617		$useUnix     = (isset($options['return']) && ($options['return'] === 'string')) ? 'false' : 'true';
1618		$id          = !empty($options['id']) ? $options['id'] : $this->name2id($name);
1619		$classes     = array('date' => 'tbox e-date', 'datetime' => 'tbox e-datetime');
1620
1621		if($mode == 'datetime' && !varset($options['format']))
1622		{
1623			$dateFormat .= " ".e107::getPref('inputtime', '%H:%M:%S');
1624		}
1625
1626		$dformat = e107::getDate()->toMask($dateFormat);
1627
1628		// If default value is set.
1629		if ($datestamp && $datestamp !='0000-00-00') // date-field support.
1630		{
1631			if(!is_numeric($datestamp))
1632			{
1633				$datestamp = strtotime($datestamp);
1634			}
1635
1636			// Convert date to proper (selected) format.
1637			$hiddenValue = $value = e107::getDate()->convert_date($datestamp, $dformat);
1638
1639			if ($useUnix === 'true')
1640			{
1641				$hiddenValue = $datestamp;
1642			}
1643		}
1644
1645		$class 		= (isset($classes[$mode])) ? $classes[$mode] : "tbox e-date";
1646		$size 		= !empty($options['size']) ? intval($options['size']) : 40;
1647		$required 	= !empty($options['required']) ? "required" : "";
1648		$firstDay	= isset($options['firstDay']) ? $options['firstDay'] : 0;
1649		$xsize		= (!empty($options['size']) && !is_numeric($options['size'])) ? $options['size'] : 'xlarge';
1650		$disabled 	= !empty($options['disabled']) ? "disabled" : "";
1651		$placeholder = !empty($options['placeholder']) ? 'placeholder="'.$options['placeholder'].'"' : '';
1652		$timezone    = '';
1653
1654
1655
1656		if(!empty($options['timezone'])) // since datetimepicker does not support timezones and assumes the browser timezone is the intended timezone.
1657		{
1658			date_default_timezone_set($options['timezone']);
1659			$targetOffset = date('Z');
1660			date_default_timezone_set(USERTIMEZONE);
1661			$timezone = "data-date-timezone-offset='".$targetOffset."'";
1662		}
1663
1664		$text = "";
1665
1666		if(!empty($options['inline']))
1667		{
1668			$text .= "<div class='{$class}' id='inline-{$id}' data-date-format='{$dformat}' data-date-ampm='{$ampm}' data-date-firstday='{$firstDay}'></div>";
1669			$text .= "<input type='hidden' name='{$name}' id='{$id}' value='{$value}' data-date-format='{$dformat}' data-date-ampm='{$ampm}' data-date-firstday='{$firstDay}'  />";
1670		}
1671		else
1672		{
1673			$text .= "<input class='{$class} input-".$xsize." form-control' type='text' size='{$size}' id='e-datepicker-{$id}' value='{$value}' data-date-unix ='{$useUnix}' data-date-format='{$dformat}' data-date-ampm='{$ampm}' data-date-language='".e_LAN."' data-date-firstday='{$firstDay}' {$required} {$disabled} {$placeholder} {$timezone} />";
1674			$ftype = (!empty($options['debug'])) ? 'text' : 'hidden';
1675			$text .= "<input type='{$ftype}' name='{$name}' id='{$id}' value='{$hiddenValue}' />";
1676		}
1677
1678		// TODO use Library Manager...
1679		e107::css('core', 'bootstrap-datetimepicker/css/bootstrap-datetimepicker.min.css', 'jquery');
1680		e107::js('footer', '{e_WEB}js/bootstrap-datetimepicker/js/bootstrap-datetimepicker.min.js', 'jquery', 4);
1681		e107::js('footer', '{e_WEB}js/bootstrap-datetimepicker/js/bootstrap-datetimepicker.init.js', 'jquery', 5);
1682
1683		if(e_LANGUAGE !== 'English')
1684		{
1685			e107::js('footer-inline', e107::getDate()->buildDateLocale());
1686		}
1687
1688		return $text;
1689	}
1690
1691
1692	/**
1693	 * Render a simple user dropdown list.
1694	 * @param string $name - form field name
1695	 * @param null $val - current value
1696	 * @param array $options
1697	 * @param string 'group' if == 'class' then users will be sorted into userclass groups.
1698	 *      @type string 'fields'
1699	 *      @type string 'classes' - single or comma-separated list of user-classes members to include.
1700	 *      @type string 'excludeSelf' = exlude logged in user from list.
1701	 *      @type string 'return' if == 'array' an array is returned.
1702	 *      @type string 'return' if == 'sqlWhere' an sql query is returned.
1703	 * @return string|array select form element.
1704	 */
1705	public function userlist($name, $val=null, $options=array())
1706	{
1707
1708		$fields = (!empty($options['fields']))  ? $options['fields'] :  "user_id,user_name,user_class";
1709		$class =  (!empty($options['classes']))   ? $options['classes'] : e_UC_MEMBER ; // all users sharing the same class as the logged-in user.
1710
1711		$class = str_replace(" ","",$class);
1712
1713		switch ($class)
1714		{
1715			case e_UC_ADMIN:
1716				$where = "user_admin = 1";
1717				$classList = e_UC_ADMIN;
1718				break;
1719
1720			case e_UC_MEMBER:
1721				$where = "user_ban = 0";
1722				$classList = e_UC_MEMBER;
1723				break;
1724
1725			case e_UC_NOBODY:
1726				return "";
1727				break;
1728
1729			case 'matchclass':
1730				$where = "user_class REGEXP '(^|,)(".str_replace(",","|", USERCLASS).")(,|$)'";
1731				$classList = USERCLASS;
1732				$clist = explode(",",USERCLASS);
1733				if(count($clist) > 1 && !isset($options['group'])) // group classes by default if more than one found.
1734				{
1735					$options['group'] = 'class';
1736				}
1737			break;
1738
1739			default:
1740				$where = "user_class REGEXP '(^|,)(".str_replace(",","|", $class).")(,|$)'";
1741				$classList = $class;
1742				break;
1743		}
1744
1745
1746		if(!empty($options['return']) && $options['return'] == 'sqlWhere') // can be used by user.php ajax method..
1747		{
1748			return $where;
1749		}
1750
1751		$users =   e107::getDb()->retrieve("user",$fields, "WHERE ".$where." ORDER BY user_name LIMIT 1000",true);
1752
1753		if(empty($users))
1754		{
1755			return LAN_UNAVAILABLE;
1756		}
1757
1758		$opt = array();
1759
1760		if(!empty($options['group']) && $options['group'] == 'class')
1761		{
1762			$classes = explode(',',$classList);
1763
1764			foreach($classes as $cls)
1765			{
1766				$cname = e107::getUserClass()->getName($cls);
1767
1768				$cname = str_replace('_',' ', trim($cname));
1769				foreach($users as $u)
1770				{
1771					$uclass = explode(',',$u['user_class']);
1772
1773					if(($classList == e_UC_ADMIN) || ($classList == e_UC_MEMBER) || in_array($cls,$uclass))
1774					{
1775						$id = $u['user_id'];
1776
1777						if(!empty($options['excludeSelf']) && ($id == USERID))
1778						{
1779							continue;
1780						}
1781
1782						$opt[$cname][$id] = $u['user_name'];
1783					}
1784				}
1785
1786
1787			}
1788
1789		}
1790		else
1791		{
1792			foreach($users as $u)
1793			{
1794				$id = $u['user_id'];
1795				$opt[$id] = $u['user_name'];
1796			}
1797
1798		}
1799
1800
1801		ksort($opt);
1802
1803
1804		if(!empty($options['return']) && $options['return'] == 'array') // can be used by user.php ajax method..
1805		{
1806			return $opt;
1807		}
1808
1809		return $this->select($name,$opt,$val,$options, varset($options['default'],null));
1810
1811	}
1812
1813
1814
1815	/**
1816	 * User auto-complete search
1817	 * XXX EXPERIMENTAL - subject to change.
1818	 * @param string $name_fld field name for user name
1819	 * @param string $id_fld field name for user id
1820	 * @param string $default_name default user name value
1821	 * @param integer $default_id default user id
1822	 * @param array|string $options [optional] 'readonly' (make field read only), 'name' (db field name, default user_name)
1823	 * @return string HTML text for display
1824	 */
1825	 /*
1826	function userpicker($name_fld, $id_fld='', $default_name, $default_id, $options = array())
1827	{
1828		if(!is_array($options))
1829		{
1830			parse_str($options, $options);
1831		}
1832
1833		$default_name = vartrue($default_name, '');
1834		$default_id = vartrue($default_id, '');
1835
1836		$default_options = array();
1837		if (!empty($default_name))
1838		{
1839			$default_options = array(
1840				array(
1841					'value' => $default_id,
1842					'label' => $default_name,
1843				),
1844			);
1845		}
1846
1847		$defaults['selectize'] = array(
1848			'loadPath' => e_BASE . 'user.php',
1849			'create'   => false,
1850			'maxItems' => 1,
1851			'mode'     => 'multi',
1852			'options'  => $default_options,
1853		);
1854
1855		//TODO FIXME Filter by userclass.  - see $frm->userlist().
1856
1857		$options = array_replace_recursive($defaults, $options);
1858
1859		$ret = $this->text($name_fld, $default_id, 20, $options);
1860
1861		return $ret;
1862	}
1863	*/
1864
1865
1866	/**
1867	 * User Field - auto-complete search
1868	 * @param string $name form element name
1869	 * @param string|array $value comma separated list of user ids or array of userid=>username pairs.
1870	 * @param array|string $options [optional]
1871	 *      @type int 'limit' Maximum number of users
1872	 *      @type string 'id' Custom id
1873	 *      @type string 'inline' Inline ID.
1874	 *
1875	 * @example $frm->userpicker('author', 1);
1876	 * @example $frm->userpicker('authors', "1,2,3");
1877	 * @example $frm->userpicker('author', array('user_id'=>1, 'user_name'=>'Admin');
1878	 * @example $frm->userpicker('authors', array(0=>array('user_id'=>1, 'user_name'=>'Admin', 1=>array('user_id'=>2, 'user_name'=>'John'));
1879	 *
1880	 * @todo    $options['type'] = 'select' - dropdown selections box with data returned as array instead of comma-separated.
1881	 * @return string HTML text for display
1882	 */
1883	function userpicker($name, $value, $options = array())
1884	{
1885		if(!is_array($options))
1886		{
1887			parse_str($options, $options);
1888		}
1889
1890		$defaultItems = array();
1891
1892		if(is_array($value))
1893		{
1894			if(isset($value[0]))// multiple users.
1895			{
1896				foreach($value as $val)
1897				{
1898					$defaultItems[] = array('value'=>$val['user_id'], 'label'=>$val['user_name']);
1899				}
1900
1901			}
1902			else // single user
1903			{
1904				$defaultItems[] = array('value'=>$value['user_id'], 'label'=>$value['user_name']);
1905			}
1906
1907		}
1908		elseif(!empty($value)) /// comma separated with user-id lookup.
1909		{
1910			$tmp = explode(",", $value);
1911			foreach($tmp as $uid)
1912			{
1913				if($user = e107::user($uid))
1914				{
1915					$defaultItems[] = array('value'=>$user['user_id'], 'label'=>$user['user_name']);
1916				}
1917			}
1918		}
1919
1920		$parms = array(
1921			'selectize' => array(
1922				'loadPath' => e_HTTP.'user.php',
1923				'create'   => false,
1924				'maxItems' => 1,
1925				'mode'     => 'multi',
1926				'options'  => $defaultItems
1927			)
1928		);
1929
1930		if(!empty($options['limit']))
1931		{
1932			$parms['selectize']['maxItems'] = intval($options['limit']);
1933		}
1934
1935		if(!empty($options['id']))
1936		{
1937			$parms['id'] = $options['id'];
1938		}
1939
1940		if(!empty($options['inline']))
1941		{
1942			$parms['selectize']['e_editable'] = $options['inline'];
1943		}
1944
1945		//TODO FIXME Filter by userclass.  - see $frm->userlist().
1946
1947		$defValues = array();
1948
1949		foreach($defaultItems as $val)
1950		{
1951			$defValues[] = $val['value'];
1952		}
1953
1954		$parms = array_merge($parms, $options);
1955
1956		return $this->text($name, implode(",",$defValues), 100, $parms);
1957
1958	}
1959
1960
1961	/**
1962	 * A Rating element
1963	 *
1964	 * @param string $table
1965	 * @param int $id
1966	 * @param array $options
1967	 * @return string
1968	 */
1969	function rate($table,$id,$options=array())
1970	{
1971		$table 	= preg_replace('/\W/', '', $table);
1972		$id 	= intval($id);
1973
1974		return e107::getRate()->render($table, $id, $options);
1975	}
1976
1977	function like($table,$id,$options=null)
1978	{
1979		$table 	= preg_replace('/\W/', '', $table);
1980		$id 	= intval($id);
1981
1982		return e107::getRate()->renderLike($table,$id,$options);
1983	}
1984
1985
1986	/**
1987	 * File Upload form element.
1988	 * @param $name
1989	 * @param array $options (optional)  array('multiple'=>1)
1990	 * @return string
1991	 */
1992	function file($name, $options = array())
1993	{
1994		if(e_ADMIN_AREA && empty($options['class']))
1995		{
1996			$options = array('class'=>'tbox well file');
1997		}
1998
1999		$options = $this->format_options('file', $name, $options);
2000
2001
2002
2003		//never allow id in format name-value for text fields
2004		return "<input type='file' name='{$name}'".$this->get_attributes($options, $name)." />";
2005	}
2006
2007	/**
2008	 * Upload Element. (for the future)
2009	 *
2010	 * @param       $name
2011	 * @param array $options
2012	 * @return string
2013	 */
2014	function upload($name, $options = array())
2015	{
2016		unset($name,$options);
2017		return 'Ready to use upload form fields, optional - file list view';
2018	}
2019
2020	function password($name, $value = '', $maxlength = 50, $options = array())
2021	{
2022		if(is_string($options)) parse_str($options, $options);
2023
2024		$addon = "";
2025		$gen = "";
2026
2027		if(vartrue($options['generate']))
2028		{
2029			$gen = '&nbsp;<a href="#" class="btn btn-default btn-secondary btn-small e-tip" id="Spn_PasswordGenerator" title=" '.LAN_GEN_PW.' " >'.LAN_GENERATE.'</a> ';
2030
2031			if(empty($options['nomask']))
2032			{
2033				$gen .= '<a class="btn btn-default btn-secondary btn-small e-tip" href="#" id="showPwd" title=" '.LAN_DISPL_PW.' ">'.LAN_SHOW.'</a><br />';
2034			}
2035		}
2036
2037		if(vartrue($options['strength']))
2038		{
2039			$addon .= "<div style='margin-top:4px'><div  class='progress' style='float:left;display:inline-block;width:218px;margin-bottom:0'><div class='progress-bar bar' id='pwdMeter' style='width:0%' ></div></div> <div id='pwdStatus' class='smalltext' style='float:left;display:inline-block;width:150px;margin-left:5px'></span></div>";
2040		}
2041
2042		$options['pattern'] = vartrue($options['pattern'],'[\S].{2,}[\S]');
2043		$options['required'] = varset($options['required'], 1);
2044		$options['class'] = vartrue($options['class'],'e-password tbox');
2045
2046
2047		e107::js('core', 	'password/jquery.pwdMeter.js', 'jquery', 2);
2048
2049		e107::js('footer-inline', '
2050			$(".e-password").pwdMeter({
2051	            minLength: 6,
2052	            displayGeneratePassword: true,
2053	            generatePassText: "Generate",
2054	            randomPassLength: 12
2055	        });
2056	    ');
2057
2058		if(deftrue('BOOTSTRAP'))
2059		{
2060			$options['class'] .= ' form-control';
2061		}
2062
2063		if(vartrue($options['size']) && !is_numeric($options['size']))
2064		{
2065			$options['class'] .= " input-".$options['size'];
2066			unset($options['size']); // don't include in html 'size='.
2067		}
2068
2069		$type = empty($options['nomask']) ? 'password' : 'text';
2070
2071		$options = $this->format_options('text', $name, $options);
2072
2073
2074		//never allow id in format name-value for text fields
2075		$text = "<input type='".$type."' name='{$name}' value='{$value}' maxlength='{$maxlength}'".$this->get_attributes($options, $name)." />";
2076
2077		if(empty($gen) && empty($addon))
2078		{
2079			return $text;
2080		}
2081		else
2082		{
2083			return "<span class='form-inline'>".$text.$gen."</span>".vartrue($addon);
2084		}
2085
2086	}
2087
2088
2089	/**
2090	 * Render Pagination using 'nextprev' shortcode.
2091	 * @param string $url eg. e_REQUEST_SELF.'?from=[FROM]'
2092	 * @param int $total total records
2093	 * @param int $from value to replace [FROM] with in the URL
2094	 * @param int $perPage number of items per page
2095	 * @param array $options template, type, glyphs
2096	 * @return string
2097	 */
2098	public function pagination($url='', $total=0, $from=0, $perPage=10, $options=array())
2099	{
2100
2101		if(empty($total) || empty($perPage))
2102		{
2103			return '';
2104		}
2105
2106		if(BOOTSTRAP === 4)
2107		{
2108			return '<a class="pager-button btn btn-primary" href="'.$url.'">'.$total.'</a>';
2109		}
2110
2111		if(!is_numeric($total))
2112		{
2113			return '<ul class="pager"><li><a href="'.$url.'">'.$total.'</a></li></ul>';
2114		}
2115
2116
2117
2118		require_once(e_CORE."shortcodes/single/nextprev.php");
2119
2120		$nextprev = array(
2121			'tmpl_prefix'	=> varset($options['template'],'default'),
2122			'total'			=> intval($total),
2123			'amount'		=> intval($perPage),
2124			'current'		=> intval($from),
2125			'url'			=> urldecode($url),
2126			'type'          => varset($options['type'],'record'), // page|record
2127			'glyphs'        => vartrue($options['glyphs'],false) // 1|0
2128		);
2129
2130	//	e107::getDebug()->log($nextprev);
2131
2132		return nextprev_shortcode($nextprev);
2133	}
2134
2135
2136	/**
2137	 * Render a bootStrap ProgressBar.
2138	 *
2139	 * @param string        $name
2140	 * @param number|string $value
2141	 * @param array         $options
2142	 * @return string
2143	 * @example  Use
2144	 */
2145	public function progressBar($name,$value,$options=array())
2146	{
2147		if(!deftrue('BOOTSTRAP')) // Legacy ProgressBar.
2148		{
2149			$barl = (file_exists(THEME.'images/barl.png') ? THEME_ABS.'images/barl.png' : e_PLUGIN_ABS.'poll/images/barl.png');
2150			$barr = (file_exists(THEME.'images/barr.png') ? THEME_ABS.'images/barr.png' : e_PLUGIN_ABS.'poll/images/barr.png');
2151			$bar = (file_exists(THEME.'images/bar.png') ? THEME_ABS.'images/bar.png' : e_PLUGIN_ABS.'poll/images/bar.png');
2152
2153			return "<div style='background-image: url($barl); width: 5px; height: 14px; float: left;'></div>
2154			<div style='background-image: url($bar); width: ".intval($value)."%; height: 14px; float: left;'></div>
2155			<div style='background-image: url($barr); width: 5px; height: 14px; float: left;'></div>";
2156		}
2157
2158		$class = vartrue($options['class'],'');
2159		$target = $this->name2id($name);
2160
2161		$striped = (vartrue($options['btn-label'])) ? ' progress-striped active' : '';
2162
2163		if(strpos($value,'/')!==false)
2164		{
2165			$label = $value;
2166			list($score,$denom) = explode('/',$value);
2167
2168		//	$multiplier = 100 / (int) $denom;
2169
2170			$value = ((int) $score / (int) $denom) * 100;
2171
2172		//	$value = (int) $score * (int) $multiplier;
2173			$percVal = round(floatval($value)).'%';
2174		}
2175		else
2176		{
2177			$percVal = round(floatval($value)).'%';
2178			$label = $percVal;
2179		}
2180
2181		if(!empty($options['label']))
2182		{
2183			$label = $options['label'];
2184		}
2185
2186		$id = !empty($options['id']) ? "id='".$options['id']."'" : '';
2187
2188		$text =	"<div {$id} class='progress {$striped}' >
2189   		 	<div id='".$target."' class='progress-bar bar ".$class."' role='progressbar' aria-valuenow='".intval($value)."' aria-valuemin='0' aria-valuemax='100' style='min-width: 2em;width: ".$percVal."'>";
2190   		$text .= $label;
2191   		 	$text .= "</div>
2192    	</div>";
2193
2194		$loading = vartrue($options['loading'], defset('LAN_LOADING', "Loading"));
2195
2196		$buttonId = $target.'-start';
2197
2198
2199
2200		if(vartrue($options['btn-label']))
2201		{
2202			$interval = vartrue($options['interval'],1000);
2203			$text .= '<a id="'.$buttonId.'" data-loading-text="'.$loading.'" data-progress-interval="'.$interval.'" data-progress-target="'.$target.'" data-progress="' . $options['url'] . '" data-progress-mode="'.varset($options['mode'],0).'" data-progress-show="'.varset($options['show'],0).'" data-progress-hide="'.$buttonId.'" class="btn btn-primary e-progress" >'.$options['btn-label'].'</a>';
2204			$text .= ' <a data-progress-target="'.$target.'" class="btn btn-danger e-progress-cancel" >'.LAN_CANCEL.'</a>';
2205		}
2206
2207
2208		return $text;
2209
2210	}
2211
2212
2213	/**
2214	 * Textarea Element
2215	 * @param string $name
2216	 * @param string $value
2217	 * @param int $rows
2218	 * @param int $cols
2219	 * @param array $options
2220	 * @param int|bool $counter
2221	 * @return string
2222	 */
2223	function textarea($name, $value, $rows = 10, $cols = 80, $options = array(), $counter = false)
2224	{
2225		if(is_string($options)) parse_str($options, $options);
2226		// auto-height support
2227
2228		if(empty($options['class']))
2229		{
2230			$options['class'] = '';
2231		}
2232
2233		if(vartrue($options['size']) && !is_numeric($options['size']))
2234		{
2235			$options['class'] .= " form-control input-".$options['size'];
2236			unset($options['size']); // don't include in html 'size='.
2237		}
2238		elseif(!vartrue($options['noresize']))
2239		{
2240			$options['class'] = (isset($options['class']) && $options['class']) ? $options['class'].' e-autoheight' : 'tbox col-md-7 span7 e-autoheight form-control';
2241		}
2242
2243		$options = $this->format_options('textarea', $name, $options);
2244
2245//		print_a($options);
2246		//never allow id in format name-value for text fields
2247		return "<textarea name='{$name}' rows='{$rows}' cols='{$cols}'".$this->get_attributes($options, $name).">{$value}</textarea>".(false !== $counter ? $this->hidden('__'.$name.'autoheight_opt', $counter) : '');
2248	}
2249
2250	/**
2251	 * Bbcode Area. Name, value, template, media-Cat, size, options array eg. counter
2252	 * IMPORTANT: $$mediaCat is also used is the media-manager category identifier
2253	 *
2254	 * @param string $name
2255	 * @param mixed  $value
2256	 * @param string $template
2257	 * @param string $mediaCat _common
2258	 * @param string $size     : small | medium | large
2259	 * @param array  $options  {
2260	 *  @type bool wysiwyg  when set to false will disable wysiwyg if active.
2261	 *  @type string class override class.
2262	 *                         }
2263
2264	 * @return string
2265	 */
2266	function bbarea($name, $value, $template = '', $mediaCat='_common', $size = 'large', $options = array())
2267	{
2268		if(is_string($options)) parse_str($options, $options);
2269		//size - large|medium|small
2270		//width should be explicit set by current admin theme
2271	//	$size = 'input-large';
2272		$height = '';
2273		$cols = 70;
2274
2275		switch($size)
2276		{
2277			case 'tiny':
2278				$rows = '3';
2279				$cols = 50;
2280			//	$height = "style='height:250px'"; // inline required for wysiwyg
2281			break;
2282
2283
2284			case 'small':
2285				$rows = '7';
2286				$height = "style='height:230px'"; // inline required for wysiwyg
2287				$size = "input-block-level";
2288			break;
2289
2290			case 'medium':
2291				$rows = '10';
2292
2293				$height = "style='height:375px'"; // inline required for wysiwyg
2294				$size = "input-block-level";
2295			break;
2296
2297			case 'large':
2298			default:
2299				$rows = '20';
2300				$size = 'large input-block-level';
2301			//	$height = "style='height:500px;width:1025px'"; // inline required for wysiwyg
2302			break;
2303		}
2304
2305		// auto-height support
2306/*
2307		$bbbar 				= '';
2308		$wysiwyg = null;
2309		$wysiwygClass = ' e-wysiwyg';
2310
2311		if(isset($options['wysiwyg']))
2312		{
2313			$wysiwyg = $options['wysiwyg'];
2314		}
2315
2316		if($wysiwyg === false)
2317		{
2318			$wysiwygClass = '';
2319		}
2320
2321		$options['class'] 	= 'tbox bbarea '.($size ? ' '.$size : '').$wysiwygClass.' e-autoheight form-control';
2322*/
2323		$options['class'] 	= 'tbox bbarea '.($size ? ' '.$size : '').' e-wysiwyg e-autoheight form-control';
2324
2325		if (isset($options['id']) && !empty($options['id']))
2326		{
2327			$help_tagid 		= $this->name2id($options['id'])."--preview";
2328		}
2329		else
2330		{
2331			$help_tagid 		= $this->name2id($name)."--preview";
2332		}
2333
2334		if (!isset($options['wysiwyg']))
2335		{
2336			$options['wysiwyg'] = true;
2337		}
2338
2339		//if(e107::wysiwyg(true) === false || $wysiwyg === false) // bbarea loaded, so activate wysiwyg (if enabled in preferences)
2340		if(e107::wysiwyg($options['wysiwyg'],true) === 'bbcode') // bbarea loaded, so activate wysiwyg (if enabled in preferences)
2341		{
2342			$options['other'] 	= "onselect='storeCaret(this);' onclick='storeCaret(this);' onkeyup='storeCaret(this);' {$height}";
2343		}
2344		else
2345		{
2346			$options['other'] 	= " ".$height;
2347		}
2348
2349
2350		$counter 			= vartrue($options['counter'],false);
2351
2352		$ret = "<div class='bbarea {$size}'>
2353		<div class='field-spacer'><!-- --></div>\n";
2354
2355
2356		if(e107::wysiwyg() === true) // && $wysiwyg !== false)
2357		{
2358			$eParseList = e107::getConfig()->get('e_parse_list');
2359
2360			if(!empty($eParseList))
2361			{
2362				$opts = array(
2363					'field' => $name,
2364				);
2365
2366				foreach($eParseList as $plugin)
2367				{
2368					$hookObj = e107::getAddon($plugin, 'e_parse');
2369
2370					if($tmp = e107::callMethod($hookObj, 'toWYSIWYG', $value, $opts))
2371					{
2372						$value = $tmp;
2373					}
2374				}
2375			}
2376		}
2377
2378
2379		$ret .=	e107::getBB()->renderButtons($template,$help_tagid);
2380		$ret .=	$this->textarea($name, $value, $rows, $cols, $options, $counter); // higher thank 70 will break some layouts.
2381
2382		$ret .= "</div>\n";
2383
2384		$_SESSION['media_category'] = $mediaCat; // used by TinyMce.
2385
2386
2387
2388
2389		return $ret;
2390
2391		// Quick fix - hide TinyMCE links if not installed, dups are handled by JS handler
2392		/*
2393
2394				e107::getJs()->footerInline("
2395						if(typeof tinyMCE === 'undefined')
2396						{
2397							\$$('a.e-wysiwyg-switch').invoke('hide');
2398						}
2399				");
2400		*/
2401
2402
2403	}
2404
2405	/**
2406	* Render a checkbox
2407	* @param string $name
2408	* @param mixed $value
2409	* @param boolean $checked
2410	* @param mixed $options query-string or array or string for a label. eg. label=Hello&foo=bar or array('label'=>Hello') or 'Hello'
2411	* @return string
2412	*/
2413	function checkbox($name, $value, $checked = false, $options = array())
2414	{
2415		if(!is_array($options))
2416		{
2417			if(strpos($options,"=")!==false)
2418			{
2419			 	parse_str($options, $options);
2420			}
2421			elseif(is_array($options))
2422			{
2423				// do nothing.
2424			}
2425			else // Assume it's a label.
2426			{
2427				$options = array('label'=>$options);
2428			}
2429
2430		}
2431
2432		$labelClass = (!empty($options['inline'])) ? 'checkbox-inline' : 'checkbox form-check';
2433		$labelTitle = '';
2434
2435		$options = $this->format_options('checkbox', $name, $options);
2436
2437		$options['checked'] = $checked; //comes as separate argument just for convenience
2438
2439		$text = "";
2440
2441		$active = ($checked === true) ? " active" : ""; // allow for styling if needed.
2442
2443		if(!empty($options['label'])) // add attributes to <label>
2444		{
2445			if(!empty($options['title']))
2446			{
2447				$labelTitle = " title=\"".$options['title']."\"";
2448				unset($options['title']);
2449			}
2450
2451			if(!empty($options['class']))
2452			{
2453				$labelClass .= " ".$options['class'];
2454				unset($options['class']);
2455			}
2456		}
2457
2458		if(!isset($options['class']))
2459        {
2460            $options['class'] = '';
2461        }
2462
2463        $options['class'] .= ' form-check-input';
2464
2465		$pre = (vartrue($options['label'])) ? "<label class='".$labelClass.$active."'{$labelTitle}>" : ""; // Bootstrap compatible markup
2466		$post = (vartrue($options['label'])) ? "<span>".$options['label']."</span></label>" : "";
2467		unset($options['label']); // not to be used as attribute;
2468
2469		$text .= "<input type='checkbox' name='{$name}' value='{$value}'".$this->get_attributes($options, $name, $value)." />";
2470
2471		return $pre.$text.$post;
2472	}
2473
2474
2475	/**
2476	 * Render an array of checkboxes.
2477	 * @param string $name
2478	 * @param array $option_array
2479	 * @param mixed $checked
2480	 * @param array $options [optional useKeyValues]
2481	 */
2482	function checkboxes($name, $option_array=array(), $checked=null, $options=array())
2483	{
2484		$name = (strpos($name, '[') === false) ? $name.'[]' : $name;
2485
2486		if(!is_array($checked)) $checked = explode(",",$checked);
2487
2488		$text = array();
2489
2490		$cname = $name;
2491
2492		foreach($option_array as $k=>$label)
2493		{
2494			if(!empty($options['useKeyValues'])) // ie. auto-generated
2495			{
2496				$key = $k;
2497				$c = in_array($k, $checked) ? true : false;
2498			}
2499			elseif(!empty($options['useLabelValues']))
2500			{
2501				$key = $label;
2502				//print_a($label);
2503				$c = in_array($label, e107::getParser()->toDB($checked)) ? true : false;
2504			}
2505			else
2506			{
2507				$key = 1;
2508				$cname = str_replace('[]','['.$k.']',$name);
2509				$c = vartrue($checked[$k]);
2510			}
2511
2512			/**
2513			 * Label overwrote the other supplied options (if any)
2514			 * and also failed in case it contained a "=" character
2515			 */
2516			$options['label'] = $label;
2517			$text[] = $this->checkbox($cname, $key, $c, $options);
2518		}
2519
2520		$id = empty($options['id']) ? $this->name2id($name).'-container' : $options['id'];
2521
2522	//	return print_a($checked,true);
2523		if(isset($options['list']) && $options['list'])
2524		{
2525			return "<ul id='".$id."' class='checkboxes checkbox'><li>".implode("</li><li>",$text)."</li></ul>";
2526		}
2527
2528
2529		if(!empty($text))
2530		{
2531			return "<div id='".$id."' class='checkboxes checkbox' style='display:inline-block'>".implode("",$text)."</div>";
2532		}
2533
2534		return $text;
2535
2536	}
2537
2538
2539	function checkbox_label($label_title, $name, $value, $checked = false, $options = array())
2540	{
2541		return $this->checkbox($name, $value, $checked, $options).$this->label($label_title, $name, $value);
2542	}
2543
2544	function checkbox_switch($name, $value, $checked = false, $label = '')
2545	{
2546		return $this->checkbox($name, $value, $checked).$this->label($label ? $label : LAN_ENABLED, $name, $value);
2547	}
2548
2549	function checkbox_toggle($name, $selector = 'multitoggle', $id = false, $label='') //TODO Fixme - labels will break this. Don't use checkbox, use html.
2550	{
2551		$selector = 'jstarget:'.$selector;
2552		if($id) $id = $this->name2id($id);
2553
2554		return $this->checkbox($name, $selector, false, array('id' => $id,'class' => 'checkbox checkbox-inline toggle-all','label'=>$label));
2555	}
2556
2557	function uc_checkbox($name, $current_value, $uc_options, $field_options = array())
2558	{
2559		if(!is_array($field_options)) parse_str($field_options, $field_options);
2560		return '
2561			<div class="check-block">
2562				'.$this->_uc->vetted_tree($name, array($this, '_uc_checkbox_cb'), $current_value, $uc_options, $field_options).'
2563			</div>
2564		';
2565	}
2566
2567
2568	/**
2569	 *	Callback function used with $this->uc_checkbox
2570	 *
2571	 *	@see user_class->select() for parameters
2572	 */
2573	function _uc_checkbox_cb($treename, $classnum, $current_value, $nest_level, $field_options)
2574	{
2575		if($classnum == e_UC_BLANK)
2576			return '';
2577
2578		if (!is_array($current_value))
2579		{
2580			$tmp = explode(',', $current_value);
2581		}
2582
2583		$classIndex = abs($classnum);			// Handle negative class values
2584		$classSign = (substr($classnum, 0, 1) == '-') ? '-' : '';
2585
2586		$class = $style = '';
2587		if($nest_level == 0)
2588		{
2589			$class = " strong";
2590		}
2591		else
2592		{
2593			$style = " style='text-indent:" . (1.2 * $nest_level) . "em'";
2594		}
2595		$descr = varset($field_options['description']) ? ' <span class="smalltext">('.$this->_uc->uc_get_classdescription($classnum).')</span>' : '';
2596
2597		return "<div class='field-spacer{$class}'{$style}>".$this->checkbox($treename.'[]', $classnum, in_array($classnum, $tmp), $field_options).$this->label($this->_uc->uc_get_classname($classIndex).$descr, $treename.'[]', $classnum)."</div>\n";
2598	}
2599
2600
2601	function uc_label($classnum)
2602	{
2603		return $this->_uc->uc_get_classname($classnum);
2604	}
2605
2606	/**
2607	 * A Radio Button Form Element
2608	 * @param $name
2609	 * @param @value array pair-values|string - auto-detected.
2610	 * @param $checked boolean
2611	 * @param $options
2612	 */
2613	function radio($name, $value, $checked = false, $options = null)
2614	{
2615
2616		if(!is_array($options)) parse_str($options, $options);
2617
2618		if(is_array($value))
2619		{
2620			return $this->radio_multi($name, $value, $checked, $options);
2621		}
2622
2623		$labelFound = vartrue($options['label']);
2624		unset($options['label']); // label attribute not valid in html5
2625
2626		$options = $this->format_options('radio', $name, $options);
2627		$options['checked'] = $checked; //comes as separate argument just for convenience
2628
2629		if(empty($options['id']))
2630		{
2631			unset($options['id']);
2632		}
2633		// $options['class'] = 'inline';
2634		$text = "";
2635
2636
2637
2638	//	return print_a($options,true);
2639		if($labelFound) // Bootstrap compatible markup
2640		{
2641			$defaultClass = (deftrue('BOOTSTRAP')) ? 'radio-inline form-check-inline' : 'radio inline';
2642			$dis = (!empty($options['disabled'])) ? " disabled" : "";
2643			$text .= "<label class='{$defaultClass}{$dis}'>";
2644
2645		}
2646
2647
2648		$text .= "<input class='form-check-input' type='radio' name='{$name}' value='".$value."'".$this->get_attributes($options, $name, $value)." />";
2649
2650		if(vartrue($options['help']))
2651		{
2652			$text .= "<div class='field-help'>".$options['help']."</div>";
2653		}
2654
2655		if($labelFound)
2656		{
2657			$text .= " <span>".$labelFound."</span></label>";
2658		}
2659
2660		return $text;
2661	}
2662
2663	/**
2664	 * Boolean Radio Buttons / Checkbox (with Bootstrap Switch).
2665	 *
2666	 * @param string $name
2667	 *  Form element name.
2668	 * @param bool $checked_enabled
2669	 *  Use the checked attribute or not.
2670	 * @param string $label_enabled
2671	 *  Default is LAN_ENABLED
2672	 * @param string $label_disabled
2673	 *  Default is LAN_DISABLED
2674	 * @param array $options
2675	 *  - 'inverse' => 1 (invert values)
2676	 *  - 'reverse' => 1 (switch display order)
2677	 *  - 'switch'  => 'normal' (size for Bootstrap Switch... mini, small, normal, large)
2678	 *
2679	 * @return string $text
2680	 */
2681	function radio_switch($name, $checked_enabled = false, $label_enabled = '', $label_disabled = '', $options = array())
2682	{
2683		if(!is_array($options))
2684		{
2685			parse_str($options, $options);
2686		}
2687
2688		$options_on = varset($options['enabled'], array());
2689		$options_off = varset($options['disabled'], array());
2690
2691		unset($options['enabled'], $options['disabled']);
2692
2693		$options_on = array_merge($options_on, $options);
2694		$options_off = array_merge($options_off, $options);
2695
2696
2697		if(vartrue($options['class']) == 'e-expandit' || vartrue($options['expandit'])) // See admin->prefs 'Single Login' for an example.
2698		{
2699			$options_on = array_merge($options, array('class' => 'e-expandit-on'));
2700			$options_off = array_merge($options, array('class' => 'e-expandit-off'));
2701		}
2702
2703		if(e_ADMIN_AREA === true)
2704		{
2705			$options['switch'] = 'small';
2706			$label_enabled = ($label_enabled) ? strtoupper($label_enabled) : strtoupper(LAN_ON);
2707			$label_disabled = ($label_disabled) ?  strtoupper($label_disabled): strtoupper(LAN_OFF);
2708		}
2709
2710
2711		$options_on['label'] = $label_enabled ? defset($label_enabled, $label_enabled) : LAN_ENABLED;
2712		$options_off['label'] = $label_disabled ? defset($label_disabled, $label_disabled) : LAN_DISABLED;
2713
2714		if(!empty($options['switch']))
2715		{
2716			return $this->flipswitch($name,$checked_enabled, array('on'=>$options_on['label'],'off'=>$options_off['label']),$options);
2717		}
2718		elseif(!empty($options['inverse'])) // Same as 'writeParms'=>'reverse=1&enabled=LAN_DISABLED&disabled=LAN_ENABLED'
2719		{
2720			$text = $this->radio($name, 0, !$checked_enabled, $options_on) . " 	" . $this->radio($name, 1, $checked_enabled, $options_off);
2721
2722		}
2723		elseif(!empty($options['reverse'])) // reverse display order.
2724		{
2725			$text = $this->radio($name, 0, !$checked_enabled, $options_off) . " " . $this->radio($name, 1, $checked_enabled, $options_on);
2726		}
2727		else
2728		{
2729			$text = $this->radio($name, 1, $checked_enabled, $options_on) . " 	" . $this->radio($name, 0, !$checked_enabled, $options_off);
2730		}
2731
2732		return $text;
2733	}
2734
2735
2736	/**
2737	 * @param string $name
2738	 * @param bool|false $checked_enabled
2739	 * @param array $labels on & off
2740	 * @param array $options
2741	 * @return string
2742	 */
2743	public function flipswitch($name, $checked_enabled = false, $labels=null, $options = array())
2744	{
2745
2746		if(empty($labels))
2747		{
2748			$labels = array('on' =>strtoupper(LAN_ON), 'off' =>strtoupper(LAN_OFF));
2749		}
2750
2751		$value = $checked_enabled;
2752
2753		if(!empty($options['inverse']))
2754		{
2755			$checked_enabled = !$checked_enabled;
2756		}
2757
2758		if(!empty($options['reverse']))
2759		{
2760			$on = $labels['on'];
2761			$options_on['label'] = $labels['off'];
2762			$options_off['label'] = $on;
2763			unset($on);
2764
2765		}
2766
2767		if(empty($options['switch']))
2768		{
2769			$options['switch'] = 'small';
2770		}
2771
2772
2773		$switchName = $this->name2id($name) . '__switch'; // fixes array names.
2774
2775		$switchAttributes = array(
2776			'data-type'    => 'switch',
2777			'data-name'    => $name,
2778			'data-size'    => $options['switch'],
2779			'data-on'      => $labels['on'],
2780			'data-off'     => $labels['off'],
2781			'data-inverse' => (int) !empty($options['inverse']),
2782		);
2783
2784		$options += $switchAttributes;
2785
2786		if(e_ADMIN_AREA === true)
2787		{
2788			$options['data-wrapper'] = 'wrapper form-control';
2789
2790		}
2791
2792		e107::library('load', 'bootstrap.switch');
2793		e107::js('footer', '{e_WEB}js/bootstrap.switch.init.js', 'jquery', 5);
2794
2795		$text = $this->hidden($name, (int) $value);
2796		$text .= $this->checkbox($switchName, (int) $checked_enabled, $checked_enabled, $options);
2797
2798		return $text;
2799	}
2800
2801
2802
2803
2804
2805	/**
2806	 * XXX INTERNAL ONLY - Use radio() instead. array will automatically trigger this internal method.
2807	 * @param string $name
2808	 * @param array $elements = arrays value => label
2809	 * @param string/integer $checked = current value
2810	 * @param boolean $multi_line
2811	 * @param mixed $help array of field help items or string of field-help (to show on all)
2812	 */
2813	private function radio_multi($name, $elements, $checked, $options=array(), $help = null)
2814	{
2815
2816
2817
2818		/* // Bootstrap Test.
2819		 return'    <label class="checkbox">
2820    <input type="checkbox" value="">
2821    Option one is this and that—be sure to include why its great
2822    </label>
2823
2824    <label class="radio">
2825    <input type="radio" name="optionsRadios" id="optionsRadios1" value="option1" checked>
2826    Option one is this and that—be sure to include why its great
2827    </label>
2828    <label class="radio">
2829    <input type="radio" name="optionsRadios" id="optionsRadios2" value="option2">
2830    Option two can be something else and selecting it will deselect option one
2831    </label>';
2832		*/
2833
2834
2835		$text = array();
2836
2837		if(is_string($elements)) parse_str($elements, $elements);
2838		if(!is_array($options)) parse_str($options, $options);
2839		$help = '';
2840		if(vartrue($options['help']))
2841		{
2842			$help = "<div class='field-help'>".$options['help']."</div>";
2843			unset($options['help']);
2844		}
2845
2846		foreach ($elements as $value => $label)
2847		{
2848			$label = defset($label, $label);
2849
2850			$helpLabel = (is_array($help)) ? vartrue($help[$value]) : $help;
2851
2852		// Bootstrap Style Code - for use later.
2853			$options['label'] = $label;
2854			$options['help'] = $helpLabel;
2855			$text[] = $this->radio($name, $value, (string) $checked === (string) $value, $options);
2856
2857		//	$text[] = $this->radio($name, $value, (string) $checked === (string) $value)."".$this->label($label, $name, $value).(isset($helpLabel) ? "<div class='field-help'>".$helpLabel."</div>" : '');
2858		}
2859
2860	//	if($multi_line === false)
2861	//	{
2862		//	return implode("&nbsp;&nbsp;", $text);
2863	//	}
2864
2865		// support of UI owned 'newline' parameter
2866		if(!varset($options['sep']) && vartrue($options['newline']))  $options['sep'] = '<br />'; // TODO div class=separator?
2867		$separator = varset($options['sep']," ");
2868	//	return print_a($text,true);
2869		return implode($separator, $text).$help;
2870
2871		// return implode("\n", $text);
2872		//XXX Limiting markup.
2873	//	return "<div class='field-spacer' style='width:50%;float:left'>".implode("</div><div class='field-spacer' style='width:50%;float:left'>", $text)."</div>";
2874
2875	}
2876
2877	/**
2878	 * Just for BC - use the $options['label'] instead.
2879	 */
2880	function label($text, $name = '', $value = '')
2881	{
2882	//	$backtrack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS,2);
2883	//	e107::getMessage()->addDebug("Deprecated \$frm->label() used in: ".print_a($backtrack,true));
2884		$for_id = $this->_format_id('', $name, $value, 'for');
2885		return "<label$for_id class='e-tip legacy'>{$text}</label>";
2886	}
2887
2888	function help($text)
2889	{
2890		return !empty($text) ? '<div class="field-help">'.$text.'</div>' : '';
2891	}
2892
2893	function select_open($name, $options = array())
2894	{
2895
2896		if(!is_array($options)) parse_str($options, $options);
2897
2898
2899		if(!empty($options['size']) && !is_numeric($options['size']))
2900		{
2901			if(!empty($options['class']))
2902			{
2903				$options['class'] .= " form-control input-".$options['size'];
2904			}
2905			else
2906			{
2907				$options['class'] = "form-control input-".$options['size'];
2908			}
2909
2910			unset($options['size']); // don't include in html 'size='.
2911		}
2912
2913        if(!empty($options['title']) && is_array($options['title']))
2914        {
2915            unset($options['title']);
2916        }
2917
2918
2919		$options = $this->format_options('select', $name, $options);
2920
2921		return "<select name='{$name}'".$this->get_attributes($options, $name).">";
2922	}
2923
2924
2925	/**
2926	 * @DEPRECATED - use select() instead.
2927	 */
2928	function selectbox($name, $option_array, $selected = false, $options = array(), $defaultBlank= false)
2929	{
2930		return $this->select($name, $option_array, $selected, $options, $defaultBlank);
2931	}
2932
2933
2934
2935	/**
2936	 *
2937	 * @param string        $name
2938	 * @param array         $option_array
2939	 * @param boolean       $selected [optional]
2940	 * @param string|array  $options [optional]
2941	 * @param bool          $options['useValues']   when true uses array values as the key.
2942	 * @param array         $options['disabled'] list of $option_array keys which should be disabled. eg. array('key_1', 'key_2');
2943	 * @param bool|string   $defaultBlank [optional] set to TRUE if the first entry should be blank, or to a string to use it for the blank description.
2944	 * @return string       HTML text for display
2945	 */
2946	function select($name, $option_array, $selected = false, $options = array(), $defaultBlank= false)
2947	{
2948		if(!is_array($options)) parse_str($options, $options);
2949
2950		if($option_array === 'yesno')
2951		{
2952			$option_array = array(1 => LAN_YES, 0 => LAN_NO);
2953		}
2954
2955		if(!empty($options['multiple']))
2956		{
2957			$name = (strpos($name, '[') === false) ? $name.'[]' : $name;
2958			if(!is_array($selected)) $selected = explode(",",$selected);
2959
2960		}
2961
2962		$text = $this->select_open($name, $options)."\n";
2963
2964		if(isset($options['default']))
2965		{
2966			if($options['default'] === 'blank')
2967			{
2968				$options['default'] = '&nbsp;';
2969			}
2970			$text .= $this->option($options['default'], varset($options['defaultValue'],''));
2971		}
2972		elseif($defaultBlank)
2973		{
2974			$diz = is_string($defaultBlank) ? $defaultBlank : '&nbsp;';
2975			$text .= $this->option($diz, '');
2976		}
2977
2978		if(!empty($options['useValues'])) // use values as keys.
2979		{
2980			$new = array();
2981			foreach($option_array as $v)
2982			{
2983				$new[$v] = (string) $v;
2984			}
2985			$option_array = $new;
2986		}
2987
2988		$text .= $this->option_multi($option_array, $selected, $options)."\n".$this->select_close();
2989		return $text;
2990	}
2991
2992
2993
2994
2995
2996
2997	/**
2998	 * Universal Userclass selector - checkboxes, dropdown, everything.
2999	 * @param string $name - form element name
3000	 * @param int $curval - current userclass value(s) as array or comma separated.
3001	 * @param string $type - checkbox|dropdown  default is dropdown.
3002	 * @param string|array $options - classlist or query string or key=value pair.
3003	 * @param string $options['options'] comma-separated list of display options. 'options=admin,mainadmin,classes&vetted=1&exclusions=0' etc.
3004	 *
3005	 * @example $frm->userclass('name', 0, 'dropdown', 'classes'); // display all userclasses
3006	 * @example $frm->userclass('name', 0, 'dropdown', 'classes,matchclass'); // display only classes to which the user belongs.
3007	 * @return string form element(s)
3008	 */
3009	function userclass($name, $curval=255, $type=null, $options=null)
3010	{
3011		if(!empty($options))
3012		{
3013			if(is_array($options))
3014			{
3015				$opt = $options;
3016			}
3017			elseif(strpos($options,'=')!==false)
3018			{
3019				parse_str($options,$opt);
3020			}
3021			else
3022			{
3023				$opt = array('options'=>$options);
3024			}
3025
3026		}
3027		else
3028		{
3029			$opt = array();
3030		}
3031
3032		$optlist = vartrue($opt['options'],null);
3033
3034		switch ($type)
3035		{
3036			case 'checkbox':
3037				return e107::getUserClass()->uc_checkboxes($name, $curval, $optlist, null,false);
3038			break;
3039
3040			case 'dropdown':
3041			default:
3042				return e107::getUserClass()->uc_dropdown($name, $curval, $optlist, $opt);
3043			break;
3044		}
3045
3046	}
3047
3048
3049	/**
3050	 * Renders a generic search box. If $filter has values, a filter box will be included with the options provided.
3051	 *
3052	 */
3053	function search($name, $searchVal, $submitName, $filterName='', $filterArray=false, $filterVal=false)
3054	{
3055		$tp = e107::getParser();
3056
3057		$text = '<span class="input-append input-group e-search">
3058    		'.$this->text($name, $searchVal,20,'class=search-query&placeholder='.LAN_SEARCH.'&hellip;').'
3059   			 <span class="input-group-btn"><button class="btn btn-primary" name="'.$submitName.'" type="submit">'.$tp->toGlyph('fa-search',' ').'</button></span>
3060    	</span>';
3061
3062
3063
3064		if(is_array($filterArray))
3065		{
3066			$text .= $this->selectbox($filterName, $filterArray, $filterVal);
3067		}
3068
3069	//	$text .= $this->admin_button($submitName,LAN_SEARCH,'search');
3070
3071		return $text;
3072
3073		/*
3074		$text .=
3075
3076						<select style="display: none;" data-original-title="Filter the results below" name="filter_options" id="filter-options" class="e-tip tbox select filter" title="">
3077							<option value="">Display All</option>
3078							<option value="___reset___">Clear Filter</option>
3079								<optgroup class="optgroup" label="Filter by&nbsp;Category">
3080<option value="faq_parent__1">General</option>
3081<option value="faq_parent__2">Misc</option>
3082<option value="faq_parent__4">Test 3</option>
3083	</optgroup>
3084
3085						</select><div class="btn-group bootstrap-select e-tip tbox select filter"><button id="filter-options" class="btn dropdown-toggle clearfix" data-toggle="dropdown"><span class="filter-option pull-left">Display All</span>&nbsp;<span class="caret"></span></button><ul style="max-height: none; overflow-y: auto;" class="dropdown-menu" role="menu"><li rel="0"><a tabindex="-1" class="">Display All</a></li><li rel="1"><a tabindex="-1" class="">Clear Filter</a></li><li rel="2"><dt class="optgroup-div">Filter by&nbsp;Category</dt><a tabindex="-1" class="opt ">General</a></li><li rel="3"><a tabindex="-1" class="opt ">Misc</a></li><li rel="4"><a tabindex="-1" class="opt ">Test 3</a></li></ul></div>
3086						<div class="e-autocomplete"></div>
3087
3088
3089			<button type="submit" name="etrigger_filter" value="etrigger_filter" id="etrigger-filter" class="btn filter e-hide-if-js btn-primary"><span>Filter</span></button>
3090
3091						<span class="indicator" style="display: none;">
3092							<img src="/e107_2.0/e107_images/generic/loading_16.gif" class="icon action S16" alt="Loading...">
3093						</span>
3094
3095		*/
3096	}
3097
3098
3099	/**
3100	 * @param       $name
3101	 * @param null  $current_value
3102	 * @param null  $uc_options
3103	 * @param array $select_options multiple, default
3104	 * @param array $opt_options
3105	 * @return string
3106	 */
3107	function uc_select($name, $current_value=null, $uc_options=null, $select_options = array(), $opt_options = array())
3108	{
3109
3110/*	var_dump($name);
3111	var_dump($current_value);
3112var_dump($uc_options);
3113var_dump($select_options);*/
3114
3115
3116		if(!empty($select_options['multiple']) && substr($name,-1) != ']')
3117		{
3118			$name .= '[]';
3119		}
3120
3121		if(($current_value === null || $current_value === '') && !empty($uc_options)) // make the first in the opt list the default value.
3122		{
3123			$tmp = explode(",", $uc_options);
3124			$current_value =  e107::getUserClass()->getClassFromKey($tmp[0]);
3125
3126			if(isset($select_options['default']))
3127			{
3128				$current_value = (int) $select_options['default'];
3129			}
3130		}
3131
3132		if(!empty($current_value) && !is_numeric($current_value)) // convert name to id.
3133		{
3134			//$current_value = $this->_uc->getID($current_value);
3135			// issue #3249 Accept also comma separated values
3136			if (!is_array($current_value))
3137			{
3138				$current_value = explode(',', $current_value);
3139			}
3140			$tmp = array();
3141			foreach($current_value as $val)
3142			{
3143				if (!empty($val))
3144				{
3145					$tmp[] = !is_numeric($val) ? $this->_uc->getID(trim($val)) : (int) $val;
3146				}
3147			}
3148			$current_value = implode(',', $tmp);
3149			unset($tmp);
3150		}
3151
3152		$text = $this->select_open($name, $select_options)."\n";
3153		$text .= $this->_uc->vetted_tree($name, array($this, '_uc_select_cb'), $current_value, $uc_options, $opt_options)."\n";
3154		$text .= $this->select_close();
3155
3156		return $text;
3157	}
3158
3159	// Callback for vetted_tree - Creates the option list for a selection box
3160	function _uc_select_cb($treename, $classnum, $current_value, $nest_level)
3161	{
3162		$classIndex = abs($classnum);			// Handle negative class values
3163		$classSign = (substr($classnum, 0, 1) == '-') ? '-' : '';
3164
3165		if($classnum == e_UC_BLANK)
3166			return $this->option('&nbsp;', '');
3167
3168		$tmp = explode(',', $current_value);
3169		if($nest_level == 0)
3170		{
3171			$prefix = '';
3172			$style = "font-weight:bold; font-style: italic;";
3173		}
3174		elseif($nest_level == 1)
3175		{
3176			$prefix = '&nbsp;&nbsp;';
3177			$style = "font-weight:bold";
3178		}
3179		else
3180		{
3181			$prefix = '&nbsp;&nbsp;'.str_repeat('--', $nest_level - 1).'&gt;';
3182			$style = '';
3183		}
3184		return $this->option($prefix.$this->_uc->uc_get_classname($classnum), $classSign.$classIndex, ($current_value !== '' && in_array($classnum, $tmp)), array("style"=>"{$style}"))."\n";
3185	}
3186
3187
3188	function optgroup_open($label, $disabled = false, $options = null)
3189	{
3190		return "<optgroup class='optgroup ".varset($options['class'])."' label='{$label}'".($disabled ? " disabled='disabled'" : '').">\n";
3191	}
3192
3193    /**
3194     * <option> tag generation.
3195     * @param $option_title
3196     * @param $value
3197     * @param $selected
3198     * @param $options (eg. disabled=1)
3199     */
3200	function option($option_title, $value, $selected = false, $options = '')
3201	{
3202	    if(is_string($options)) parse_str($options, $options);
3203
3204		if(false === $value) $value = '';
3205
3206		$options = $this->format_options('option', '', $options);
3207		$options['selected'] = $selected; //comes as separate argument just for convenience
3208
3209
3210
3211		return "<option value='{$value}'".$this->get_attributes($options).">".defset($option_title, $option_title)."</option>";
3212	}
3213
3214
3215    /**
3216    * Use selectbox() instead.
3217    */
3218	function option_multi($option_array, $selected = false, $options = array())
3219	{
3220		if(is_string($option_array))
3221		{
3222			parse_str($option_array, $option_array);
3223		}
3224
3225		$text = '';
3226
3227		if(empty($option_array))
3228		{
3229			return $this->option('','');
3230		}
3231
3232		$opts = $options;
3233
3234		foreach ((array) $option_array as $value => $label)
3235		{
3236
3237			if(is_array($label))
3238			{
3239				$text .= $this->optgroup($value, $label, $selected, $options, 0);
3240			}
3241			else
3242			{
3243				$sel = is_array($selected) ? in_array($value, $selected) : ($value == $selected);
3244
3245				if(!empty($options['optDisabled']) && is_array($options['optDisabled']))
3246				{
3247					$opts['disabled'] = in_array($value, $options['optDisabled']);
3248				}
3249
3250				if(!empty($options['title'][$value]))
3251				{
3252					$opts['data-title'] = $options['title'][$value];
3253				}
3254				elseif(isset($opts['data-title']))
3255				{
3256					unset($opts['data-title']);
3257				}
3258
3259				$text .= $this->option($label, $value, $sel, $opts)."\n";
3260			}
3261		}
3262
3263		return $text;
3264	}
3265
3266
3267	/**
3268	 * No compliant, but it works.
3269	 * @param $value
3270	 * @param $label
3271	 * @param $selected
3272	 * @param $options
3273	 * @param int $level
3274	 * @return string
3275	 */
3276	private function optgroup($value, $label, $selected, $options, $level=1)
3277	{
3278		$level++;
3279		$text = $this->optgroup_open($value, null, array('class'=>'level-'.$level));
3280
3281		$opts = $options;
3282
3283		foreach($label as $val => $lab)
3284		{
3285			if(is_array($lab))
3286			{
3287				$text .= $this->optgroup($val,$lab,$selected,$options,$level);
3288			}
3289			else
3290			{
3291				if(!empty($options['optDisabled']) && is_array($options['optDisabled']))
3292				{
3293					$opts['disabled'] = in_array($val, $options['optDisabled']);
3294				}
3295
3296				$text .= $this->option($lab, $val, (is_array($selected) ? in_array($val, $selected) : $selected == $val), $opts)."\n";
3297			}
3298
3299		}
3300
3301		$text .= $this->optgroup_close();
3302
3303		return $text;
3304	}
3305
3306
3307
3308
3309
3310	function optgroup_close()
3311	{
3312		return "</optgroup>\n";
3313	}
3314
3315	function select_close()
3316	{
3317		return "</select>";
3318	}
3319
3320	function hidden($name, $value, $options = array())
3321	{
3322		$options = $this->format_options('hidden', $name, $options);
3323
3324		return "<input type='hidden' name='{$name}' value='{$value}'".$this->get_attributes($options, $name, $value)." />";
3325	}
3326
3327	/**
3328	 * Generate hidden security field
3329	 * @return string
3330	 */
3331	function token()
3332	{
3333		return "<input type='hidden' name='e-token' value='".defset('e_TOKEN', '')."' />";
3334	}
3335
3336	function submit($name, $value, $options = array())
3337	{
3338		$options = $this->format_options('submit', $name, $options);
3339		return "<input type='submit' name='{$name}' value='{$value}'".$this->get_attributes($options, $name, $value)." />";
3340	}
3341
3342	function submit_image($name, $value, $image, $title='', $options = array())
3343	{
3344		$tp = e107::getParser();
3345
3346		if(!empty($options['icon']))
3347		{
3348			$customIcon = $options['icon'];
3349			unset($options['icon']);
3350		}
3351
3352		$options = $this->format_options('submit_image', $name, $options);
3353		switch ($image)
3354		{
3355			case 'edit':
3356				$icon = (e_ADMIN_AREA === true) ? ADMIN_EDIT_ICON : $tp->toIcon("e-edit-32");
3357				$options['class'] = $options['class'] == 'action' ? 'btn btn-default btn-secondary action edit' : $options['class'];
3358			break;
3359
3360			case 'delete':
3361				$icon = (e_ADMIN_AREA === true) ? ADMIN_DELETE_ICON : $tp->toIcon('fa-trash.glyph');
3362				$options['class'] = $options['class'] == 'action' ? 'btn btn-default btn-secondary action delete' : $options['class'];
3363				$options['other'] = 'data-confirm="'.LAN_JSCONFIRM.'"';
3364			break;
3365
3366			case 'execute':
3367				$icon = (e_ADMIN_AREA === true) ? ADMIN_EXECUTE_ICON : $tp->toIcon('fa-power-off.glyph');
3368				$options['class'] = $options['class'] == 'action' ? 'btn btn-default btn-secondary action execute' : $options['class'];
3369			break;
3370
3371			case 'view':
3372				$icon = $tp->toIcon("e-view-32");
3373				$options['class'] = $options['class'] == 'action' ? 'btn btn-default btn-secondary action view' : $options['class'];
3374			break;
3375		}
3376
3377		$options['title'] = $title;//shorthand
3378
3379		if(!empty($customIcon))
3380		{
3381			$icon = $customIcon;
3382		}
3383
3384		return  "<button type='submit' name='{$name}' data-placement='left' value='{$value}'".$this->get_attributes($options, $name, $value)."  >".$icon."</button>";
3385
3386
3387	}
3388
3389	/**
3390	 * Alias of admin_button, adds the etrigger_ prefix required for UI triggers
3391	 * @see e_form::admin_button()
3392	 */
3393	function admin_trigger($name, $value, $action = 'submit', $label = '', $options = array())
3394	{
3395		return $this->admin_button('etrigger_'.$name, $value, $action, $label, $options);
3396	}
3397
3398
3399	/**
3400	 * Generic Button Element.
3401	 * @param string $name
3402	 * @param string $value
3403	 * @param string $action [optional] default is submit - use 'dropdown' for a bootstrap dropdown button.
3404	 * @param string $label [optional]
3405	 * @param string|array $options [optional]
3406	 * @return string
3407	 */
3408	public function button($name, $value, $action = 'submit', $label = '', $options = array())
3409	{
3410		if(deftrue('BOOTSTRAP') && $action == 'dropdown' && is_array($value))
3411		{
3412		//	$options = $this->format_options('admin_button', $name, $options);
3413			$options['class'] = vartrue($options['class']);
3414
3415			$align = vartrue($options['align'],'left');
3416
3417			$text = '<div class="btn-group pull-'.$align.'">
3418			    <a class="btn dropdown-toggle '.$options['class'].'" data-toggle="dropdown" href="#">
3419			    '.($label ? $label : LAN_NO_LABEL_PROVIDED).'
3420			    <span class="caret"></span>
3421			    </a>
3422			    <ul class="dropdown-menu">
3423			    ';
3424
3425			foreach($value as $k=>$v)
3426			{
3427				$text .= '<li>'.$v.'</li>';
3428			}
3429
3430			$text .= '
3431			    </ul>
3432			    </div>';
3433
3434			return $text;
3435		}
3436
3437
3438
3439		return $this->admin_button($name, $value, $action, $label, $options);
3440
3441	}
3442
3443	/**
3444	 * Render a Breadcrumb in Bootstrap format.
3445	 * @param $array
3446	 * @param $array[url]
3447	 * @param $array[text]
3448	 */
3449	function breadcrumb($array)
3450	{
3451		if(!is_array($array)){ return; }
3452
3453		$opt = array();
3454
3455		$homeicon = (deftrue('FONTAWESOME')) ? 'fa-home' : 'icon-home.glyph';
3456		$homeIcon = e107::getParser()->toGlyph($homeicon,false);
3457
3458
3459		$opt[] = "<a href='".e_HTTP."'>".$homeIcon."</a>"; // Add Site-Pref to disable?
3460
3461		$text = "\n<ul class=\"breadcrumb\">\n";
3462		$text .= "<li class=\"breadcrumb-item\">";
3463
3464		foreach($array as $val)
3465		{
3466			if($val['url'] === e_REQUEST_URI) // automatic link removal for current page.
3467			{
3468				$val['url']= null;
3469			}
3470
3471			$ret = "";
3472			$ret .= vartrue($val['url']) ? "<a href='".$val['url']."'>" : "";
3473			$ret .= vartrue($val['text'],'');
3474			$ret .= vartrue($val['url']) ? "</a>" : "";
3475
3476			if($ret != '')
3477			{
3478				$opt[] = $ret;
3479			}
3480		}
3481
3482		$sep = (deftrue('BOOTSTRAP')) ? "" : "<span class='divider'>/</span>";
3483
3484		$text .= implode($sep."</li>\n<li class='breadcrumb-item'>",$opt);
3485
3486		$text .= "</li>\n</ul>";
3487
3488	//	return print_a($opt,true);
3489
3490		return $text;
3491
3492	}
3493
3494
3495	/**
3496	 * Render a direct link admin-edit button on the frontend.
3497	 * @param $url
3498	 * @param string $perms
3499	 * @return string
3500	 */
3501	public function instantEditButton($url, $perms='0')
3502	{
3503		if(deftrue("BOOTSTRAP") && getperms($perms))
3504		{
3505			return "<span class='e-instant-edit hidden-print'><a target='_blank' title='".LAN_EDIT."' href='".$url."'>".e107::getParser()->toGlyph('fa-edit')."</a></span>";
3506		}
3507
3508		return '';
3509
3510	}
3511
3512
3513
3514
3515	/**
3516	 * Admin Button - for front-end, use button();
3517	 * @param string $name
3518	 * @param string $value
3519	 * @param string $action [optional] default is submit
3520	 * @param string $label [optional]
3521	 * @param string|array $options [optional]
3522	 * @return string
3523	 */
3524	function admin_button($name, $value, $action = 'submit', $label = '', $options = array())
3525	{
3526		$btype = 'submit';
3527		if(strpos($action, 'action') === 0)
3528		{
3529			$btype = 'button';
3530		}
3531
3532		if(isset($options['loading']) && ($options['loading'] == false))
3533		{
3534			unset($options['loading']);
3535			$include = '';
3536		}
3537		else
3538		{
3539			// $include = (deftrue("FONTAWESOME")) ? "data-loading-icon='fa-spinner' data-disable='true'" : "";
3540			$include = (deftrue("FONTAWESOME")) ? "data-loading-icon='fa-spinner' " : ""; // data-disable breaks db.php charset Fix.
3541		}
3542
3543		$confirmation = LAN_JSCONFIRM;
3544
3545		if(!empty($options['confirm']))
3546		{
3547			$confirmation = $options['confirm'];
3548		}
3549
3550		$options = $this->format_options('admin_button', $name, $options);
3551
3552		$options['class'] = vartrue($options['class']);
3553		$options['class'] .= ' btn ' . $action;
3554
3555		if(empty($label))
3556		{
3557			$label = $value;
3558		}
3559
3560		// Ability to use any kind of button class for the selected action.
3561		if (!$this->defaultButtonClassExists($options['class']))
3562		{
3563			$options['class'] .= ' ' . $this->getDefaultButtonClassByAction($action);
3564		}
3565
3566		switch ($action)
3567		{
3568			case 'checkall':
3569				$options['class'] .= ' btn-mini btn-xs';
3570				break;
3571
3572			case 'delete':
3573			case 'danger':
3574
3575
3576
3577				$options['other'] = 'data-confirm="'.$confirmation.'"';
3578				break;
3579
3580			case 'batch':
3581			case 'batch e-hide-if-js':
3582				// FIXME hide-js shouldn't be here.
3583				break;
3584
3585			case 'filter':
3586			case 'filter e-hide-if-js':
3587				$options['class'] = 'btn btn-default';
3588				break;
3589		}
3590
3591		return "<button " . $include . " type='{$btype}' name='{$name}' value='{$value}'" . $this->get_attributes($options, $name) . "><span>{$label}</span></button>";
3592	}
3593
3594	/**
3595	 * Helper function to check if a (CSS) class already contains a button class?
3596	 *
3597	 * @param string $class
3598	 *  The class we want to check.
3599	 *
3600	 * @return bool
3601	 *  True if $class already contains a button class. Otherwise false.
3602	 */
3603	function defaultButtonClassExists($class = '')
3604	{
3605		// Bootstrap button classes.
3606		// @see http://getbootstrap.com/css/#buttons-options
3607		$btnClasses = array(
3608			'btn-default',
3609			'btn-primary',
3610			'btn-success',
3611			'btn-info',
3612			'btn-warning',
3613			'btn-danger',
3614			'btn-link',
3615		);
3616
3617		foreach($btnClasses as $btnClass) {
3618			if(strpos($class, $btnClass, 0) !== false)
3619			{
3620				return true;
3621			}
3622		}
3623
3624		return false;
3625	}
3626
3627
3628
3629
3630
3631
3632	/**
3633	 * Helper function to get default button class by action.
3634	 *
3635	 * @param string $action
3636	 *  Action for a button. See button().
3637	 *
3638	 * @return string $class
3639	 *  Default button class.
3640	 */
3641	function getDefaultButtonClassByAction($action)
3642	{
3643		switch($action)
3644		{
3645			case 'update':
3646			case 'create':
3647			case 'import':
3648			case 'submit':
3649			case 'success':
3650				$class = 'btn-success';
3651				break;
3652
3653			case 'checkall':
3654				$class = 'btn-default';
3655				break;
3656
3657			case 'cancel':
3658				$class = 'btn-default';
3659				break;
3660
3661			case 'delete':
3662			case 'danger':
3663				$class = 'btn-danger';
3664				break;
3665
3666			case 'execute':
3667				$class = 'btn-success';
3668				break;
3669
3670			case 'other':
3671			case 'login':
3672			case 'primary':
3673				$class = 'btn-primary';
3674				break;
3675
3676			case 'warning':
3677			case 'confirm':
3678				$class = 'btn-warning';
3679				break;
3680
3681			case 'batch':
3682			case 'batch e-hide-if-js':
3683				$class = 'btn-primary';
3684				break;
3685
3686			case 'filter':
3687			case 'filter e-hide-if-js':
3688				$class = 'btn-primary';
3689				break;
3690
3691			case 'default':
3692			default:
3693				$class = 'btn-default';
3694				break;
3695		}
3696
3697		return $class;
3698	}
3699
3700	function getNext()
3701	{
3702		if(!$this->_tabindex_enabled) return 0;
3703		$this->_tabindex_counter += 1;
3704		return $this->_tabindex_counter;
3705	}
3706
3707	function getCurrent()
3708	{
3709		if(!$this->_tabindex_enabled) return 0;
3710		return $this->_tabindex_counter;
3711	}
3712
3713	function resetTabindex($reset = 0)
3714	{
3715		$this->_tabindex_counter = $reset;
3716	}
3717
3718	function get_attributes($options, $name = '', $value = '')
3719	{
3720		$ret = '';
3721		//
3722		foreach ($options as $option => $optval)
3723		{
3724			$optval = trim($optval);
3725			switch ($option)
3726			{
3727
3728				case 'id':
3729					$ret .= $this->_format_id($optval, $name, $value);
3730					break;
3731
3732				case 'class':
3733					if(!empty($optval)) $ret .= " class='{$optval}'";
3734					break;
3735
3736				case 'size':
3737					if($optval) $ret .= " size='{$optval}'";
3738					break;
3739
3740				case 'title':
3741					if($optval) $ret .= " title='{$optval}'";
3742					break;
3743
3744				case 'label':
3745					if($optval) $ret .= " label='{$optval}'";
3746					break;
3747
3748				case 'tabindex':
3749					if($optval) $ret .= " tabindex='{$optval}'";
3750					elseif(false === $optval || !$this->_tabindex_enabled) break;
3751					else
3752					{
3753						$this->_tabindex_counter += 1;
3754						$ret .= " tabindex='".$this->_tabindex_counter."'";
3755					}
3756					break;
3757
3758				case 'readonly':
3759					if($optval) $ret .= " readonly='readonly'";
3760					break;
3761
3762				case 'multiple':
3763					if($optval) $ret .= " multiple='multiple'";
3764					break;
3765
3766				case 'selected':
3767					if($optval) $ret .= " selected='selected'";
3768					break;
3769
3770				case 'maxlength':
3771					if($optval) $ret .= " maxlength='{$optval}'";
3772					break;
3773
3774				case 'checked':
3775					if($optval) $ret .= " checked='checked'";
3776					break;
3777
3778				case 'disabled':
3779					if($optval) $ret .= " disabled='disabled'";
3780					break;
3781
3782				case 'required':
3783					if($optval) $ret .= " required='required'";
3784					break;
3785
3786				case 'autofocus':
3787					if($optval) $ret .= " autofocus='autofocus'";
3788					break;
3789
3790				case 'placeholder':
3791					if($optval) {
3792					  $optval = deftrue($optval, $optval);
3793					  $ret .= " placeholder='{$optval}'";
3794					}
3795					break;
3796
3797				case 'wrap':
3798					if($optval) $ret .= " wrap='{$optval}'";
3799					break;
3800
3801				case 'autocomplete':
3802					if($optval) $ret .= " autocomplete='{$optval}'";
3803					break;
3804
3805				case 'pattern':
3806					if($optval) $ret .= " pattern='{$optval}'";
3807					break;
3808
3809				case 'other':
3810					if($optval) $ret .= " $optval";
3811					break;
3812			}
3813
3814			if(substr($option,0,5) =='data-')
3815			{
3816				$ret .= " ".$option."='{$optval}'";
3817			}
3818
3819		}
3820
3821		return $ret;
3822	}
3823
3824	/**
3825	 * Auto-build field attribute id
3826	 *
3827	 * @param string $id_value value for attribute id passed with the option array
3828	 * @param string $name the name attribute passed to that field
3829	 * @param unknown_type $value the value attribute passed to that field
3830	 * @return string formatted id attribute
3831	 */
3832	function _format_id($id_value, $name, $value = '', $return_attribute = 'id')
3833	{
3834		if($id_value === false) return '';
3835
3836		//format data first
3837		$name = trim($this->name2id($name), '-');
3838		$value = trim(preg_replace('#[^a-zA-Z0-9\-]#','-', $value), '-');
3839		//$value = trim(preg_replace('#[^a-z0-9\-]#/i','-', $value), '-');		// This should work - but didn't for me!
3840		$value = trim(str_replace("/","-",$value), '-');					// Why?
3841		if(!$id_value && is_numeric($value)) $id_value = $value;
3842
3843		// clean - do it better, this could lead to dups
3844		$id_value = trim($id_value, '-');
3845
3846		if(empty($id_value) ) return " {$return_attribute}='{$name}".($value ? "-{$value}" : '')."'";// also useful when name is e.g. name='my_name[some_id]'
3847		elseif(is_numeric($id_value) && $name) return " {$return_attribute}='{$name}-{$id_value}'";// also useful when name is e.g. name='my_name[]'
3848		else return " {$return_attribute}='{$id_value}'";
3849	}
3850
3851	function name2id($name)
3852	{
3853		$name = strtolower($name);
3854		return rtrim(str_replace(array('[]', '[', ']', '_', '/', ' ','.', '(', ')', '::', ':', '?','='), array('-', '-', '', '-', '-', '-', '-','','','-','','-','-'), $name), '-');
3855	}
3856
3857	/**
3858	 * Format options based on the field type,
3859	 * merge with default
3860	 *
3861	 * @param string $type
3862	 * @param string $name form name attribute value
3863	 * @param array|string $user_options
3864	 * @return array merged options
3865	 */
3866	function format_options($type, $name, $user_options=null)
3867	{
3868		if(is_string($user_options))
3869		{
3870			parse_str($user_options, $user_options);
3871		}
3872
3873		$def_options = $this->_default_options($type);
3874
3875
3876		if(is_array($user_options))
3877		{
3878			$user_options['name'] = $name; //required for some of the automated tasks
3879
3880			foreach (array_keys($user_options) as $key)
3881			{
3882				if(!isset($def_options[$key]) && substr($key,0,5)!='data-') unset($user_options[$key]); // data-xxxx exempt //remove it?
3883			}
3884		}
3885		else
3886		{
3887			$user_options = array('name' => $name); //required for some of the automated tasks
3888		}
3889
3890		return array_merge($def_options, $user_options);
3891	}
3892
3893	/**
3894	 * Get default options array based on the field type
3895	 *
3896	 * @param string $type
3897	 * @return array default options
3898	 */
3899	function _default_options($type)
3900	{
3901		if(isset($this->_cached_attributes[$type])) return $this->_cached_attributes[$type];
3902
3903		$def_options = array(
3904			'id' 			=> '',
3905			'class' 		=> '',
3906			'title' 		=> '',
3907			'size' 			=> '',
3908			'readonly' 		=> false,
3909			'selected' 		=> false,
3910			'checked' 		=> false,
3911			'disabled' 		=> false,
3912			'required' 		=> false,
3913			'autofocus'		=> false,
3914			'tabindex' 		=> 0,
3915			'label' 		=> '',
3916			'placeholder' 	=> '',
3917			'pattern'		=> '',
3918			'other' 		=> '',
3919			'autocomplete' 	=> '',
3920			'maxlength'		=> '',
3921			'wrap'          => '',
3922			'multiple'      => '',
3923
3924			//	'multiple' => false, - see case 'select'
3925		);
3926
3927		$form_control = (THEME_LEGACY !== true) ? ' form-control' : '';
3928
3929		switch ($type) {
3930			case 'hidden':
3931				$def_options = array('id' => false, 'disabled' => false, 'other' => '');
3932				break;
3933
3934			case 'text':
3935				$def_options['class'] = 'tbox input-text'.$form_control;
3936				unset($def_options['selected'], $def_options['checked']);
3937				break;
3938
3939			case 'number':
3940				$def_options['class'] = 'tbox '.$form_control;
3941				break;
3942
3943			case 'file':
3944				$def_options['class'] = 'tbox file';
3945				unset($def_options['selected'], $def_options['checked']);
3946				break;
3947
3948			case 'textarea':
3949				$def_options['class'] = 'tbox textarea'.$form_control;
3950				unset($def_options['selected'], $def_options['checked'], $def_options['size']);
3951				break;
3952
3953			case 'select':
3954				$def_options['class'] = 'tbox select'.$form_control;
3955				$def_options['multiple'] = false;
3956				unset($def_options['checked']);
3957				break;
3958
3959			case 'option':
3960				$def_options = array('class' => '', 'selected' => false, 'other' => '', 'disabled' => false, 'label' => '');
3961				break;
3962
3963			case 'radio':
3964				//$def_options['class'] = ' ';
3965				$def_options = array('class' => '', 'id'=>'');
3966				unset($def_options['size'], $def_options['selected']);
3967				break;
3968
3969			case 'checkbox':
3970				unset($def_options['size'],  $def_options['selected']);
3971				break;
3972
3973			case 'submit':
3974				$def_options['class'] = 'button btn btn-primary';
3975				unset($def_options['checked'], $def_options['selected'], $def_options['readonly']);
3976				break;
3977
3978			case 'submit_image':
3979				$def_options['class'] = 'action';
3980				unset($def_options['checked'], $def_options['selected'], $def_options['readonly']);
3981				break;
3982
3983			case 'admin_button':
3984				unset($def_options['checked'],  $def_options['selected'], $def_options['readonly']);
3985				break;
3986
3987		}
3988
3989		$this->_cached_attributes[$type] = $def_options;
3990		return $def_options;
3991	}
3992
3993	function columnSelector($columnsArray, $columnsDefault = array(), $id = 'column_options')
3994	{
3995		$columnsArray = array_filter($columnsArray);
3996
3997	// navbar-header nav-header
3998	// navbar-header nav-header
3999		$text = '<div class="col-selection dropdown e-tip pull-right float-right" data-placement="left">
4000    <a class="dropdown-toggle" title="'.LAN_EFORM_008.'" data-toggle="dropdown" href="#"><b class="caret"></b></a>
4001    <ul class="list-group dropdown-menu  col-selection e-noclick" role="menu" aria-labelledby="dLabel">
4002
4003    <li class="list-group-item "><h5 class="list-group-item-heading">'.LAN_EFORM_009.'</h5></li>
4004    <li class="list-group-item col-selection-list">
4005     <ul class="nav scroll-menu" >';
4006
4007        unset($columnsArray['options'], $columnsArray['checkboxes']);
4008
4009		foreach($columnsArray as $key => $fld)
4010		{
4011			if(!isset($fld['type']) || $fld['type'] === null) // Fixes #4083
4012			{
4013				continue;
4014			}
4015
4016			if (empty($fld['forced']) && empty($fld['nolist']) && vartrue($fld['type'])!='hidden' && vartrue($fld['type'])!='upload')
4017			{
4018				$checked = (in_array($key,$columnsDefault)) ?  TRUE : FALSE;
4019				$ttl = isset($fld['title']) ? defset($fld['title'], $fld['title']) : $key;
4020
4021				$text .= "
4022					<li role='menuitem'><a href='#'>
4023						".$this->checkbox('e-columns[]', $key, $checked,'label='.$ttl)."
4024					</a>
4025					</li>
4026				";
4027			}
4028		}
4029
4030		// has issues with the checkboxes.
4031        $text .= "
4032				</ul>
4033				</li>
4034				 <li class='list-group-item'>
4035				<div id='{$id}-button' class='right'>
4036					".$this->admin_button('etrigger_ecolumns', LAN_SAVE, 'btn btn-primary btn-small')."
4037				</div>
4038				 </li>
4039				</ul>
4040			</div>";
4041
4042	//	$text .= "</div></div>";
4043
4044		$text .= "";
4045
4046
4047	/*
4048	$text = '<div class="dropdown">
4049    <a class="dropdown-toggle" data-toggle="dropdown" href="#"><b class="caret"></b></a>
4050    <ul class="dropdown-menu" role="menu" aria-labelledby="dLabel">
4051    <li>hi</li>
4052    </ul>
4053    </div>';
4054	*/
4055		return $text;
4056	}
4057
4058
4059
4060
4061
4062
4063
4064
4065
4066
4067
4068	function colGroup($fieldarray, $columnPref = '')
4069	{
4070        $text = "";
4071        $count = 0;
4072		foreach($fieldarray as $key=>$val)
4073		{
4074			if ((in_array($key, $columnPref) || $key=='options' || vartrue($val['forced'])) && !vartrue($val['nolist']))
4075			{
4076
4077				$class = vartrue($val['class']) ? 'class="'.$val['class'].'"' : '';
4078				$width = vartrue($val['width']) ? ' style="width:'.$val['width'].'"' : '';
4079				$text .= '<col '.$class.$width.' />
4080				';
4081				$count++;
4082			}
4083		}
4084
4085		return '
4086			<colgroup>
4087				'.$text.'
4088			</colgroup>
4089		';
4090	}
4091
4092	function thead($fieldarray, $columnPref = array(), $querypattern = '', $requeststr = '')
4093	{
4094        $text = "";
4095
4096        $querypattern = filter_var($querypattern, FILTER_SANITIZE_STRING);
4097        if(!$requeststr) $requeststr = rawurldecode(e_QUERY);
4098        $requeststr = filter_var($requeststr, FILTER_SANITIZE_STRING);
4099
4100		// Recommended pattern: mode=list&field=[FIELD]&asc=[ASC]&from=[FROM]
4101		if(strpos($querypattern,'&')!==FALSE)
4102		{
4103			// we can assume it's always $_GET since that's what it will generate
4104			// more flexible (e.g. pass default values for order/field when they can't be found in e_QUERY) & secure
4105			$tmp = str_replace('&amp;', '&', $requeststr ? $requeststr : e_QUERY);
4106			parse_str($tmp, $tmp);
4107
4108			$etmp = array();
4109			parse_str(str_replace('&amp;', '&', $querypattern), $etmp);
4110		}
4111		else // Legacy Queries. eg. main.[FIELD].[ASC].[FROM]
4112		{
4113			$tmp = explode(".", ($requeststr ? $requeststr : e_QUERY));
4114			$etmp = explode(".", $querypattern);
4115		}
4116
4117		foreach($etmp as $key => $val)    // I'm sure there's a more efficient way to do this, but too tired to see it right now!.
4118		{
4119        	if($val == "[FIELD]")
4120			{
4121            	$field = varset($tmp[$key]);
4122			}
4123
4124			if($val == "[ASC]")
4125			{
4126            	$ascdesc = varset($tmp[$key]);
4127			}
4128			if($val == "[FROM]")
4129			{
4130            	$fromval = varset($tmp[$key]);
4131			}
4132		}
4133
4134		if(!varset($fromval)){ $fromval = 0; }
4135
4136		$sorted = varset($ascdesc);
4137        $ascdesc = ($sorted == 'desc') ? 'asc' : 'desc';
4138
4139		foreach($fieldarray as $key=>$val)
4140		{
4141     		if ((in_array($key, $columnPref) || ($key === 'options' && isset($val['title'])) || (vartrue($val['forced']))) && !vartrue($val['nolist']))
4142			{
4143				$cl = (vartrue($val['thclass'])) ? " class='".$val['thclass']."'" : "";
4144
4145				$aClass = ($key === $field) ? "class='sorted-".$sorted."'" : "";
4146
4147				$text .= "
4148					<th id='e-column-".str_replace('_', '-', $key)."'{$cl}>
4149				";
4150
4151                if($querypattern!="" && !vartrue($val['nosort']) && $key != "options" && $key != "checkboxes")
4152				{
4153					$from = ($key == $field) ? $fromval : 0;
4154					$srch = array("[FIELD]","[ASC]","[FROM]");
4155					$repl = array($key,$ascdesc,$from);
4156                	$val['url'] = e_SELF."?".str_replace($srch,$repl,$querypattern);
4157				}
4158
4159
4160
4161				$text .= (vartrue($val['url'])) ? "<a ".$aClass." title=\"".LAN_SORT."\" href='".str_replace(array('&amp;', '&'), array('&', '&amp;'),$val['url'])."'>" : "";  // Really this column-sorting link should be auto-generated, or be autocreated via unobtrusive js.
4162	            $text .= defset($val['title'], $val['title']);
4163				$text .= ($val['url']) ? "</a>" : "";
4164	            $text .= ($key === "options" && !vartrue($val['noselector'])) ? $this->columnSelector($fieldarray, $columnPref) : "";
4165				$text .= ($key === "checkboxes") ? $this->checkbox_toggle('e-column-toggle', vartrue($val['toggle'], 'multiselect')) : "";
4166
4167
4168	 			$text .= "
4169					</th>
4170				";
4171			}
4172		}
4173
4174		return "
4175		<thead>
4176	  		<tr >".$text."</tr>
4177		</thead>
4178		";
4179
4180	}
4181
4182
4183	/**
4184	 * Render Table cells from hooks.
4185	 * @param array $data
4186	 * @return string
4187	 */
4188	function renderHooks($data)
4189	{
4190		$hooks = e107::getEvent()->triggerHook($data);
4191
4192		$text = "";
4193
4194		if(!empty($hooks))
4195		{
4196			foreach($hooks as $plugin => $hk)
4197			{
4198				$text .= "\n\n<!-- Hook : {$plugin} -->\n";
4199
4200				if(!empty($hk))
4201				{
4202					foreach($hk as $hook)
4203					{
4204						$text .= "\t\t\t<tr>\n";
4205						$text .= "\t\t\t<td>".$hook['caption']."</td>\n";
4206						$text .= "\t\t\t<td>".$hook['html']."";
4207						$text .= (varset($hook['help'])) ? "\n<span class='field-help'>".$hook['help']."</span>" : "";
4208						$text .= "</td>\n\t\t\t</tr>\n";
4209					}
4210
4211
4212				}
4213			}
4214		}
4215
4216		return $text;
4217	}
4218
4219
4220
4221
4222	/**
4223	 * Render Related Items for the current page/news-item etc.
4224	 * @param string $type : comma separated list. ie. plugin folder names.
4225	 * @param string $tags : comma separated list of keywords to return related items of.
4226	 * @param array $curVal. eg. array('page'=> current-page-id-value);
4227	 */
4228	function renderRelated($parm,$tags, $curVal, $template=null) //XXX TODO Cache!
4229	{
4230
4231		if(empty($tags))
4232		{
4233			return;
4234		}
4235
4236		$tags = str_replace(', ',',', $tags); //BC Fix, all tags should be comma separated without spaces ie. one,two NOT one, two
4237
4238		e107::setRegistry('core/form/related',$tags); // TODO Move to elsewhere so it works without rendering? e107::related() set and get by plugins?
4239
4240		if(!varset($parm['limit']))
4241		{
4242			$parm = array('limit' => 5);
4243		}
4244
4245		if(!varset($parm['types']))
4246		{
4247			$parm['types'] = 'news';
4248		}
4249
4250		if(empty($template))
4251		{
4252			$TEMPLATE['start'] = "<hr><h4>".defset('LAN_RELATED', 'Related')."</h4><ul class='e-related'>";
4253			$TEMPLATE['item'] = "<li><a href='{RELATED_URL}'>{RELATED_TITLE}</a></li>";
4254			$TEMPLATE['end'] = "</ul>";
4255
4256		}
4257		else
4258		{
4259			$TEMPLATE = $template;
4260		}
4261
4262
4263
4264		$tp = e107::getParser();
4265
4266		$types = explode(',',$parm['types']);
4267		$list = array();
4268
4269		$head = $tp->parseTemplate($TEMPLATE['start'],true);
4270
4271		foreach($types as $plug)
4272		{
4273
4274			if(!$obj = e107::getAddon($plug,'e_related'))
4275			{
4276				continue;
4277			}
4278
4279			$parm['current'] = intval(varset($curVal[$plug]));
4280
4281
4282
4283
4284			$tmp = $obj->compile($tags,$parm);
4285
4286			if(count($tmp))
4287			{
4288				foreach($tmp as $val)
4289				{
4290
4291					$row = array(
4292						'RELATED_URL'       => $tp->replaceConstants($val['url'],'full'),
4293						'RELATED_TITLE'     => $val['title'],
4294						'RELATED_IMAGE'     => $tp->toImage($val['image']),
4295						'RELATED_SUMMARY'   => $tp->toHTML($val['summary'],true,'BODY'),
4296						'RELATED_DATE'		=> $val['date'],
4297					);
4298
4299					$list[] = $tp->simpleParse($TEMPLATE['item'], $row);
4300
4301				}
4302			}
4303		}
4304
4305		if(count($list))
4306		{
4307			return "<div class='e-related clearfix hidden-print'>".$head.implode("\n",$list).$tp->parseTemplate($TEMPLATE['end'], true)."</div>";
4308
4309		//	return "<div class='e-related clearfix hidden-print'><hr><h4>".defset('LAN_RELATED', 'Related')."</h4><ul class='e-related'>".implode("\n",$list)."</div>"; //XXX Tablerender?
4310		}
4311
4312	}
4313
4314
4315
4316	/**
4317	 * Render Table cells from field listing.
4318	 * @param array $fieldarray - eg. $this->fields
4319	 * @param array $currentlist - eg $this->fieldpref
4320	 * @param array $fieldvalues - eg. $row
4321	 * @param string $pid - eg. table_id
4322	 * @return string
4323	 */
4324	function renderTableCells($fieldarray, $currentlist, $fieldvalues, $pid)
4325	{
4326
4327		$cnt = 0;
4328		$text = '';
4329
4330
4331		foreach ($fieldarray as $field => $data)
4332		{
4333
4334
4335			// shouldn't happen... test with Admin->Users with user_xup visible and NULL values in user_xup table column before re-enabling this code.
4336			/*
4337			if(!isset($fieldvalues[$field]) && vartrue($data['alias']))
4338			{
4339				$fieldvalues[$data['alias']] = $fieldvalues[$data['field']];
4340				$field = $data['alias'];
4341			}
4342			*/
4343
4344			//Not found
4345			if((empty($data['forced']) && !in_array($field, $currentlist)) || !empty($data['nolist']))
4346			{
4347				continue;
4348			}
4349			elseif(vartrue($data['type']) != 'method' && empty($data['forced']) && !isset($fieldvalues[$field]) && $fieldvalues[$field] !== null)
4350			{
4351				$text .= "
4352					<td>
4353						Not Found! ($field)
4354					</td>
4355				";
4356
4357				continue;
4358			}
4359
4360			$tdclass = vartrue($data['class']);
4361
4362            if($field == 'checkboxes') $tdclass = $tdclass ? $tdclass.' autocheck e-pointer' : 'autocheck e-pointer';
4363
4364			if($field == 'options') $tdclass = $tdclass ? $tdclass.' options' : 'options';
4365
4366
4367			// there is no other way for now - prepare user data
4368			if('user' == vartrue($data['type']) /* && isset($data['readParms']['idField'])*/)
4369			{
4370				if(varset($data['readParms']) && is_string($data['readParms'])) parse_str($data['readParms'], $data['readParms']);
4371				if(isset($data['readParms']['idField']))
4372				{
4373					$data['readParms']['__idval'] = $fieldvalues[$data['readParms']['idField']];
4374				}
4375				elseif(isset($fieldvalues['user_id'])) // Default
4376				{
4377					$data['readParms']['__idval'] = $fieldvalues['user_id'];
4378				}
4379
4380				if(isset($data['readParms']['nameField']))
4381				{
4382					$data['readParms']['__nameval'] = $fieldvalues[$data['readParms']['nameField']];
4383				}
4384				elseif(isset($fieldvalues['user_name'])) // Default
4385				{
4386					$data['readParms']['__nameval'] = $fieldvalues['user_name'];
4387				}
4388
4389			}
4390
4391			$value = $this->renderValue($field, varset($fieldvalues[$field]), $data, varset($fieldvalues[$pid]));
4392
4393
4394			if($tdclass)
4395			{
4396				$tdclass = ' class="'.$tdclass.'"';
4397			}
4398
4399			$text .= '
4400				<td'.$tdclass.'>
4401					'.$value.'
4402				</td>
4403			';
4404
4405			$cnt++;
4406		}
4407
4408		if($cnt)
4409		{
4410			return $text;
4411		}
4412
4413		return null;
4414
4415	}
4416
4417	/**
4418	 * Render Table row and cells from field listing.
4419	 *
4420	 * @param array  $fieldArray  - eg. $this->fields
4421	 * @param array  $fieldPref   - eg $this->fieldpref
4422	 * @param array  $fieldValues - eg. $row
4423	 * @param string $pid         - eg. table_id
4424	 * @return string
4425	 */
4426	function renderTableRow($fieldArray, $fieldPref, $fieldValues, $pid)
4427	{
4428
4429		if(!$ret = $this->renderTableCells($fieldArray, $fieldPref, $fieldValues, $pid))
4430		{
4431			return '';
4432		}
4433
4434		$trclass = '';
4435		//	$trclass = vartrue($fieldvalues['__trclass']) ?  ' class="'.$trclass.'"' : '';
4436		unset($fieldValues['__trclass']);
4437
4438		return '
4439				<tr'.$trclass.' id="row-' . $fieldValues[$pid].'">
4440					'.$ret.'
4441				</tr>
4442			';
4443	}
4444
4445
4446
4447	/**
4448	 * Inline Token
4449	 * @return string
4450	 */
4451	private function inlineToken()
4452	{
4453		$this->_inline_token = $this->_inline_token ?:
4454			password_hash(session_id(), PASSWORD_DEFAULT, ['cost' => 04]);
4455		return $this->_inline_token;
4456	}
4457
4458	/**
4459	 * Create an Inline Edit link.
4460	 * @param string $dbField : field being edited
4461	 * @param int $pid : primary ID of the row being edited.
4462	 * @param string $fieldName - Description of the field name (caption)
4463	 * @param mixed $curVal : existing value of in the field
4464	 * @param mixed $linkText : existing value displayed
4465	 * @param string $type text|textarea|select|date|checklist
4466	 * @param array $array : array data used in dropdowns etc.
4467	 */
4468	public function renderInline($dbField, $pid, $fieldName, $curVal, $linkText, $type='text', $array=null, $options=array())
4469	{
4470		$jsonArray = array();
4471
4472		if(!empty($array))
4473		{
4474			foreach($array as $k=>$v)
4475			{
4476				$jsonArray[$k] = str_replace("'", "`", $v);
4477			}
4478		}
4479
4480		$source = e107::getParser()->toJSON($jsonArray, true);
4481
4482		$mode = preg_replace('/[^\w]/', '', vartrue($_GET['mode'], ''));
4483
4484		if(!isset($options['url']))
4485		{
4486			$options['url'] = e_SELF."?mode={$mode}&amp;action=inline&amp;id={$pid}&amp;ajax_used=1";
4487		}
4488
4489		if(!empty($pid))
4490		{
4491			$options['pk'] = $pid;
4492		}
4493
4494		$title = varset($options['title'] , (LAN_EDIT." ".$fieldName));
4495		$class = varset($options['class'] ,'');
4496
4497		unset( $options['title']);
4498
4499		$text = "<a class='e-tip e-editable editable-click ".$class."' data-name='".$dbField."' ";
4500		$text .= (is_array($array)) ? "data-source='".$source."'  " : "";
4501		$text .= " title=\"".$title."\" data-type='".$type."' data-inputclass='x-editable-".$this->name2id($dbField)." ".$class."' data-value=\"{$curVal}\"   href='#' ";
4502
4503		$options['token'] = $this->inlineToken();
4504
4505		if(!empty($options))
4506		{
4507			foreach($options as $k=>$opt)
4508			{
4509				if(!empty($opt))
4510				{
4511					$text .= " data-".$k."='".$opt."'";
4512				}
4513			}
4514		}
4515
4516		$text .= ">".$linkText."</a>";
4517
4518		return $text;
4519	}
4520
4521	/**
4522	 * Check if a value should be linked and wrap in <a> tag if required.
4523	 * @todo global pref for the target option?
4524	 * @param mixed $value
4525	 * @param array $parms
4526	 * @param $id
4527	 * @example $frm->renderLink('label', array('link'=>'{e_PLUGIN}myplugin/myurl.php','target'=>'blank')
4528	 * @example $frm->renderLink('label', array('link'=>'{e_PLUGIN}myplugin/myurl.php?id=[id]','target'=>'blank')
4529	 * @example $frm->renderLink('label', array('link'=>'{e_PLUGIN}myplugin/myurl.php?id=[field-name]','target'=>'blank')
4530	 * @example $frm->renderLink('label', array('link'=>'db-field-name','target'=>'blank')
4531	 * @example $frm->renderLink('label', array('url'=>'e_url.php key','title'=>'click here');
4532	 * @return string
4533	 */
4534	public function renderLink($value, $parms, $id=null)
4535	{
4536		if(empty($parms['link']) && empty($parms['url']))
4537		{
4538			return $value;
4539		}
4540
4541		/** @var e_admin_model $model */
4542		if(!$model = e107::getRegistry('core/adminUI/currentListModel')) // Try list model
4543		{
4544			$model = e107::getRegistry('core/adminUI/currentModel'); // try create/edit model.
4545		}
4546
4547		$dialog     = vartrue($parms['target']) =='dialog' ? " e-modal" : ""; // iframe
4548		$ext        = vartrue($parms['target']) =='blank' ? " rel='external' " : ""; // new window
4549		$modal      = vartrue($parms['target']) =='modal' ? " data-toggle='modal' data-cache='false' data-target='#uiModal' " : "";
4550
4551		$link = null;
4552
4553		if(!empty($parms['url']) && !empty($model)) // ie. use e_url.php
4554		{
4555			//$plugin = $this->getController()->getPluginName();
4556			if($plugin = e107::getRegistry('core/adminUI/currentPlugin'))
4557			{
4558				$data = $model->getData();
4559				$link = e107::url($plugin,$parms['url'],$data);
4560			}
4561		}
4562		elseif(!empty($model)) // old way.
4563		{
4564			$tp = e107::getParser();
4565
4566			$data = $model->getData();
4567
4568			$link       = str_replace('[id]',$id,$parms['link']);
4569			$link       = $tp->replaceConstants($link); // SEF URL is not important since we're in admin.
4570
4571			if($parms['link'] === 'sef' )
4572			{
4573				if(!$model->getUrl())
4574				{
4575					/** @var e_admin_controller_ui $controller */
4576					$controller = $this->getController();
4577					$model->setUrl($controller->getUrl());
4578				}
4579
4580				// assemble the url
4581				$link = $model->url(null);
4582			}
4583			elseif(!empty($data[$parms['link']])) // support for a field-name as the link. eg. link_url.
4584			{
4585				$link = $tp->replaceConstants(vartrue($data[$parms['link']]));
4586			}
4587			elseif(strpos($link,'[')!==false && preg_match('/\[(\w+)\]/',$link, $match)) // field-name within [ ] brackets.
4588			{
4589				$field = $match[1];
4590				$link = str_replace($match[0], $data[$field],$link);
4591			}
4592		}
4593					// in case something goes wrong...
4594		if($link)
4595		{
4596			return "<a class='e-tip{$dialog}' {$ext} href='".$link."' {$modal} title='".varset($parms['title'],LAN_EFORM_010)."' >".$value."</a>";
4597		}
4598
4599		return $value;
4600
4601	}
4602
4603	private function renderOptions($parms, $id, $attributes)
4604	{
4605		$tp = e107::getParser();
4606		$cls = false;
4607
4608		$editIconDefault = deftrue('ADMIN_EDIT_ICON', $tp->toGlyph('fa-edit'));
4609		$deleteIconDefault = deftrue('ADMIN_DELETE_ICON', $tp->toGlyph('fa-trash'));
4610/*
4611		if($attributes['grid'])
4612		{
4613			$editIconDefault = $tp->toGlyph('fa-edit');
4614			$deleteIconDefault = $tp->toGlyph('fa-trash');
4615		}
4616*/
4617		/** @var e_admin_controller_ui $controller */
4618		$controller = $this->getController();
4619		$sf = $controller->getSortField();
4620
4621		if(!isset($parms['sort']) && !empty($sf))
4622		{
4623			$parms['sort'] = true;
4624		}
4625
4626		$text = "<div class='btn-group'>";
4627
4628		if(!empty($parms['sort']) && empty($attributes['grid']))
4629		{
4630			$mode = preg_replace('/[^\w]/', '', vartrue($_GET['mode'], ''));
4631			$from = intval(vartrue($_GET['from'],0));
4632			$text .= "<a class='e-sort sort-trigger btn btn-default' style='cursor:move' data-target='".e_SELF."?mode={$mode}&action=sort&ajax_used=1&from={$from}' title='".LAN_RE_ORDER."'>".ADMIN_SORT_ICON."</a> ";
4633		}
4634
4635
4636		if(varset($parms['editClass']))
4637		{
4638			$cls = (deftrue($parms['editClass'])) ? constant($parms['editClass']) : $parms['editClass'];
4639		}
4640
4641		if((false === $cls || check_class($cls)) && varset($parms['edit'],1) == 1)
4642		{
4643
4644			parse_str(str_replace('&amp;', '&', e_QUERY), $query); //FIXME - FIX THIS
4645				// keep other vars in tact
4646			$query['action'] = 'edit';
4647			$query['id'] = $id;
4648
4649
4650			if(!empty($parms['target']) && $parms['target']=='modal')
4651			{
4652				$eModal = " e-modal ";
4653				$eModalCap = !empty($parms['modalCaption']) ? "data-modal-caption='".$parms['modalCaption']."'" : "data-modal-caption='#".$id."'";
4654				$query['iframe'] = 1;
4655			}
4656			else
4657			{
4658				$eModal = "";
4659				$eModalCap = "";
4660			}
4661
4662			if(!empty($parms['modalSubmit']))
4663			{
4664				$eModalCap .= " data-modal-submit='true'";
4665			}
4666
4667			$query = http_build_query($query, null, '&amp;');
4668			$text .= "<a href='".e_SELF."?{$query}' class='btn btn-default btn-secondary".$eModal."' ".$eModalCap." title='".LAN_EDIT."' data-toggle='tooltip' data-placement='left'>
4669				".$editIconDefault."</a>";
4670		}
4671
4672		$delcls = !empty($attributes['noConfirm']) ? ' no-confirm' : '';
4673		if(varset($parms['deleteClass']) && varset($parms['delete'],1) == 1)
4674		{
4675			$cls = (deftrue($parms['deleteClass'])) ? constant($parms['deleteClass']) : $parms['deleteClass'];
4676
4677			if(check_class($cls))
4678			{
4679				$parms['class'] =  'action delete btn btn-default'.$delcls;
4680				unset($parms['deleteClass']);
4681				$parms['icon'] = $deleteIconDefault;
4682				$text .= $this->submit_image('etrigger_delete['.$id.']', $id, 'delete', LAN_DELETE.' [ ID: '.$id.' ]', $parms);
4683			}
4684		}
4685		else
4686		{
4687			$parms['class'] =  'action delete btn btn-default'.$delcls;
4688			$parms['icon'] = $deleteIconDefault;
4689			$text .= $this->submit_image('etrigger_delete['.$id.']', $id, 'delete', LAN_DELETE.' [ ID: '.$id.' ]', $parms);
4690		}
4691
4692				//$attributes['type'] = 'text';
4693		$text .= "</div>";
4694
4695		return $text;
4696
4697	}
4698
4699	/**
4700	 * Render Field Value
4701	 * @param string $field field name
4702	 * @param mixed $value field value
4703	 * @param array $attributes field attributes including render parameters, element options - see e_admin_ui::$fields for required format
4704	 * @return string
4705	 */
4706	function renderValue($field, $value, $attributes, $id = 0)
4707	{
4708
4709
4710		if(!empty($attributes['multilan']) && is_array($value))
4711		{
4712			$value = varset($value[e_LANGUAGE],'');
4713		}
4714
4715		$parms = array();
4716		if(isset($attributes['readParms']))
4717		{
4718			if(!is_array($attributes['readParms'])) parse_str($attributes['readParms'], $attributes['readParms']);
4719			$parms = $attributes['readParms'];
4720		}
4721
4722		// @see custom fields in cpage which accept json params.
4723		if(!empty($attributes['writeParms']) && $tmpOpt = e107::getParser()->isJSON($attributes['writeParms']))
4724		{
4725			$attributes['writeParms'] = $tmpOpt;
4726			unset($tmpOpt);
4727		}
4728
4729
4730
4731		if(!empty($attributes['inline'])) $parms['editable'] = true; // attribute alias
4732		if(!empty($attributes['sort'])) $parms['sort'] = true; // attribute alias
4733
4734		if(!empty($parms['type'])) // Allow the use of a different 'type' in readMode. eg. type=method.
4735		{
4736			$attributes['type'] = $parms['type'];
4737		}
4738
4739		$this->renderValueTrigger($field, $value, $parms, $id);
4740
4741		$tp = e107::getParser();
4742		switch($field) // special fields
4743		{
4744			case 'options':
4745
4746				if(!empty($attributes['type']) && ($attributes['type'] == "method")) // Allow override with 'options' function.
4747				{
4748					$attributes['mode'] = "read";
4749					if(isset($attributes['method']) && $attributes['method'] && method_exists($this, $attributes['method']))
4750					{
4751						$method = $attributes['method'];
4752						return $this->$method($parms, $value, $id, $attributes);
4753
4754					}
4755					elseif(method_exists($this, 'options'))
4756					{
4757						//return  $this->options($field, $value, $attributes, $id);
4758						// consistent method arguments, fixed in admin cron administration
4759						$attributes['type'] = null; // prevent infinite loop.
4760
4761						return $this->options($parms, $value, $id, $attributes);
4762					}
4763				}
4764
4765				if(!$value)
4766				{
4767					$value = $this->renderOptions($parms, $id, $attributes);
4768				}
4769
4770				return $value;
4771			break;
4772
4773			case 'checkboxes':
4774
4775				//$attributes['type'] = 'text';
4776				if(empty($attributes['writeParms'])) // avoid comflicts with a field called 'checkboxes'
4777				{
4778					$value = $this->checkbox(vartrue($attributes['toggle'], 'multiselect').'['.$id.']', $id);
4779					return $value;
4780				}
4781
4782
4783			break;
4784		}
4785
4786		if(!empty($attributes['grid']) && empty($attributes['type']))
4787		{
4788			return null;
4789		}
4790
4791		if(empty($attributes['type']))
4792		{
4793			e107::getDebug()->log("Field '".$field."' is missing a value for 'type'.");
4794		//	e107::getDebug()->log($value);
4795		//	e107::getDebug()->log($attributes);
4796		}
4797
4798
4799		switch($attributes['type'])
4800		{
4801			case 'number':
4802				if(!$value) $value = '0';
4803
4804				if($parms)
4805				{
4806					if(!isset($parms['sep'])) $value = number_format($value, varset($parms['decimals'],0));
4807					else $value = number_format($value, $parms['decimals'], vartrue($parms['point'], '.'), vartrue($parms['sep'], ' '));
4808				}
4809
4810				if(empty($attributes['noedit']) && !empty($parms['editable']) && empty($parms['link'])) // avoid bad markup, better solution coming up
4811				{
4812					$value = $this->renderInline($field,$id,$attributes['title'],$value, $value, 'number');
4813				}
4814				elseif(!empty($parms['link']))
4815				{
4816					$value = $this->renderLink($value,$parms,$id);
4817				}
4818
4819
4820				$value = vartrue($parms['pre']).$value.vartrue($parms['post']);
4821				// else same
4822			break;
4823
4824			case 'country':
4825
4826				$_value = $this->getCountry($value);
4827
4828				if(empty($attributes['noedit']) && !empty($parms['editable']) && empty($parms['link'])) // avoid bad markup, better solution coming up
4829				{
4830					$arr = $this->getCountry();
4831					$value = $this->renderInline($field,$id,$attributes['title'],$value, $_value, 'select', $arr);
4832				}
4833				else
4834				{
4835					$value = $_value;
4836				}
4837
4838			break;
4839
4840			case 'ip':
4841				//$e107 = e107::getInstance();
4842				$value = "<span title='".$value."'>".e107::getIPHandler()->ipDecode($value).'</span>';;
4843				// else same
4844			break;
4845
4846			case 'templates':
4847			case 'layouts':
4848
4849				if(!empty($attributes['writeParms']))
4850				{
4851					if(is_string($attributes['writeParms'])) parse_str($attributes['writeParms'], $attributes['writeParms']);
4852				}
4853
4854				if(empty($attributes['noedit']) && !empty($parms['editable']) && empty($parms['link'])) // avoid bad markup, better solution coming up
4855				{
4856					$wparms     = $attributes['writeParms'];
4857
4858					$location   = vartrue($wparms['plugin']); // empty - core
4859					$ilocation  = vartrue($wparms['id'], $location); // omit if same as plugin name
4860					$where      = vartrue($wparms['area'], 'front'); //default is 'front'
4861					$filter     = varset($wparms['filter']);
4862					$merge      = isset($wparms['merge']) ? (bool) $wparms['merge'] : true;
4863
4864					$layouts    = e107::getLayouts($location, $ilocation, $where, $filter, $merge, false);
4865
4866					$label   = varset($layouts[$value], $value);
4867
4868					$value = $this->renderInline($field, $id, $attributes['title'], $value, $label, 'select', $layouts);
4869				}
4870
4871				$value = vartrue($parms['pre']) . $value . vartrue($parms['post']);
4872			break;
4873
4874			case 'checkboxes':
4875			case 'comma':
4876			case 'dropdown':
4877				// XXX - should we use readParams at all here? see writeParms check below
4878
4879				if($parms && is_array($parms)) // FIXME - add support for multi-level arrays (option groups)
4880				{
4881					//FIXME return no value at all when 'editable=1' is a readParm. See FAQs templates.
4882				//	$value = vartrue($parms['pre']).vartrue($parms[$value]).vartrue($parms['post']);
4883				//	break;
4884				}
4885
4886				// NEW - multiple (array values) support
4887				// FIXME - add support for multi-level arrays (option groups)
4888				if(!is_array($attributes['writeParms'])) parse_str($attributes['writeParms'], $attributes['writeParms']);
4889				$wparms = $attributes['writeParms'];
4890
4891				if(!is_array(varset($wparms['__options']))) parse_str($wparms['__options'], $wparms['__options']);
4892
4893				if(!empty($wparms['optArray']))
4894				{
4895					$fopts = $wparms;
4896					$wparms = $fopts['optArray'];
4897					unset($fopts['optArray']);
4898					$wparms['__options'] = $fopts;
4899				}
4900
4901
4902				$opts = $wparms['__options'];
4903				unset($wparms['__options']);
4904				$_value = $value;
4905
4906				if($attributes['type'] == 'checkboxes' || $attributes['type'] == 'comma')
4907				{
4908					$opts['multiple'] = true;
4909				}
4910
4911				if(!empty($opts['multiple']))
4912				{
4913					$ret = array();
4914					$value = is_array($value) ? $value : explode(',', $value);
4915					foreach ($value as $v)
4916					{
4917						if(isset($wparms[$v])) $ret[] = $wparms[$v];
4918					}
4919					$value = implode(', ', $ret);
4920
4921
4922				}
4923				else
4924				{
4925					$ret = '';
4926					if(isset($wparms[$value])) $ret = $wparms[$value];
4927					$value = $ret;
4928				}
4929
4930				$value = ($value ? vartrue($parms['pre']).defset($value, $value).vartrue($parms['post']) : '');
4931
4932				if(empty($attributes['noedit']) && !empty($parms['editable']) && empty($parms['link'])) // avoid bad markup, better solution coming up
4933				{
4934					$xtype = ($attributes['type'] == 'dropdown') ? 'select' : 'checklist';
4935					$value = $this->renderInline($field, $id, $attributes['title'], $_value, $value, $xtype, $wparms);
4936				}
4937
4938				// return ;
4939			break;
4940
4941			case 'radio':
4942
4943
4944				if($parms && isset($parms[$value])) // FIXME - add support for multi-level arrays (option groups)
4945				{
4946					$value = vartrue($parms['pre']).vartrue($parms[$value]).vartrue($parms['post']);
4947					break;
4948				}
4949
4950				if(!is_array($attributes['writeParms'])) parse_str($attributes['writeParms'], $attributes['writeParms']);
4951
4952				if(!empty($attributes['writeParms']['optArray']))
4953				{
4954					$radioValue = $attributes['writeParms']['optArray'][$value];
4955
4956					if(empty($attributes['noedit']) && !empty($parms['editable']) && empty($parms['link'])) // avoid bad markup, better solution coming up
4957					{
4958						$radioValue = $this->renderInline($field, $id, $attributes['title'], $value, $radioValue, 'select', $attributes['writeParms']['optArray']);
4959					}
4960				}
4961				else
4962				{
4963					$radioValue = vartrue($attributes['writeParms'][$value]);
4964				}
4965
4966
4967				$value = vartrue($attributes['writeParms']['__options']['pre']).$radioValue.vartrue($attributes['writeParms']['__options']['post']);
4968			break;
4969
4970			case 'tags':
4971				if(!empty($parms['constant']))
4972				{
4973					$value = defset($value, $value);
4974				}
4975
4976				if(!empty($parms['truncate']))
4977				{
4978					$value = $tp->text_truncate($value, $parms['truncate'], '...');
4979				}
4980				elseif(!empty($parms['htmltruncate']))
4981				{
4982					$value = $tp->html_truncate($value, $parms['htmltruncate'], '...');
4983				}
4984				if(!empty($parms['wrap']))
4985				{
4986					$value = $tp->htmlwrap($value, (int) $parms['wrap'], varset($parms['wrapChar'], ' '));
4987				}
4988
4989				$value = $this->renderLink($value,$parms,$id);
4990
4991				if(empty($value))
4992				{
4993					$value = '-';
4994					$setValue = "data-value=''";
4995				}
4996				else
4997				{
4998					$setValue = "";
4999
5000					if($attributes['type'] == 'tags' && !empty($value))
5001					{
5002						$setValue = "data-value='" . $value . "'";
5003						$value = str_replace(",", ", ", $value); // add spaces so it wraps, but don't change the actual values.
5004					}
5005				}
5006
5007
5008				if(!vartrue($attributes['noedit']) && vartrue($parms['editable']) && !vartrue($parms['link'])) // avoid bad markup, better solution coming up
5009				{
5010					$options['selectize'] = array(
5011						'create'     => true,
5012						'maxItems'   => vartrue($parms['maxItems'], 7),
5013						'mode'       => 'multi',
5014						'e_editable' => $field . '_' . $id,
5015					);
5016
5017					$tpl = $this->text($field, $value, 80, $options);
5018
5019					$mode = preg_replace('/[^\w]/', '', vartrue($_GET['mode'], ''));
5020					$value = "<a id='" . $field . '_' . $id . "' class='e-tip e-editable editable-click editable-tags' data-emptytext='-' data-tpl='" . str_replace("'", '"', $tpl) . "' data-name='" . $field . "' data-token='".$this->inlineToken()."' title=\"" . LAN_EDIT . " " . $attributes['title'] . "\" data-type='text' data-pk='" . $id . "' " . $setValue . " data-url='" . e_SELF . "?mode={$mode}&amp;action=inline&amp;id={$id}&amp;ajax_used=1' href='#'>" . $value . "</a>";
5021				}
5022
5023				$value = vartrue($parms['pre']) . $value . vartrue($parms['post']);
5024				break;
5025
5026			case 'text':
5027
5028				if(!empty($parms['constant']))
5029				{
5030					$value = defset($value,$value);
5031				}
5032
5033				if(vartrue($parms['truncate']))
5034				{
5035					$value = $tp->text_truncate($value, $parms['truncate'], '...');
5036				}
5037				elseif(vartrue($parms['htmltruncate']))
5038				{
5039					$value = $tp->html_truncate($value, $parms['htmltruncate'], '...');
5040				}
5041				if(vartrue($parms['wrap']))
5042				{
5043					$value = $tp->htmlwrap($value, (int)$parms['wrap'], varset($parms['wrapChar'], ' '));
5044				}
5045
5046				$value = $this->renderLink($value,$parms,$id);
5047
5048
5049				if(empty($value))
5050				{
5051					$value = '-';
5052					$setValue = "data-value=''";
5053				}
5054				else
5055				{
5056					$setValue = "";
5057
5058					if($attributes['type'] == 'tags' && !empty($value))
5059					{
5060						$setValue = "data-value='".$value."'";
5061						$value = str_replace(",", ", ", $value); // add spaces so it wraps, but don't change the actual values.
5062					}
5063				}
5064
5065
5066				if(empty($attributes['noedit']) && !empty($parms['editable']) && empty($parms['link'])) // avoid bad markup, better solution coming up
5067				{
5068					$value = $this->renderInline($field,$id,$attributes['title'],$value, $value);
5069				}
5070
5071				$value = vartrue($parms['pre']).$value.vartrue($parms['post']);
5072			break;
5073
5074
5075
5076			case 'bbarea':
5077			case 'textarea':
5078
5079
5080				if($attributes['type'] == 'textarea' && !vartrue($attributes['noedit']) && vartrue($parms['editable']) && !vartrue($parms['link'])) // avoid bad markup, better solution coming up
5081				{
5082					return $this->renderInline($field,$id,$attributes['title'],$value,substr($value,0,50)."...",'textarea'); //FIXME.
5083				}
5084
5085
5086				$expand = '<span class="e-expandit-ellipsis">...</span>';
5087				$toexpand = false;
5088				if($attributes['type'] == 'bbarea' && !isset($parms['bb'])) $parms['bb'] = true; //force bb parsing for bbareas
5089				$elid = trim(str_replace('_', '-', $field)).'-'.$id;
5090				if(!vartrue($parms['noparse'])) $value = $tp->toHTML($value, (vartrue($parms['bb']) ? true : false), vartrue($parms['parse']));
5091				if(vartrue($parms['expand']) || vartrue($parms['truncate']) || vartrue($parms['htmltruncate']))
5092				{
5093					$ttl = vartrue($parms['expand']);
5094					if($ttl == 1)
5095					{
5096						$dataAttr = "data-text-more='" . LAN_MORE . "' data-text-less='" . LAN_LESS . "'";
5097						$ttl = $expand."<button class='btn btn-default btn-secondary btn-xs btn-mini pull-right' {$dataAttr}>" . LAN_MORE . "</button>";
5098					}
5099
5100					$expands = '<a href="#'.$elid.'-expand" class="e-show-if-js e-expandit e-expandit-inline">'.defset($ttl, $ttl)."</a>";
5101				}
5102
5103				$oldval = $value;
5104				if(vartrue($parms['truncate']))
5105				{
5106					$value = $oldval = strip_tags($value);
5107					$value = $tp->text_truncate($value, $parms['truncate'], '');
5108					$toexpand = $value != $oldval;
5109				}
5110				elseif(vartrue($parms['htmltruncate']))
5111				{
5112					$value = $tp->html_truncate($value, $parms['htmltruncate'], '');
5113					$toexpand = $value != $oldval;
5114				}
5115				if($toexpand)
5116				{
5117					// force hide! TODO - core style .expand-c (expand container)
5118					$value .= '<span class="expand-c" style="display: none" id="'.$elid.'-expand"><span>'.str_replace($value,'',$oldval).'</span></span>';
5119					$value .= varset($expands); 	// 'More..' button. Keep it at the bottom so it does't cut the sentence.
5120				}
5121
5122
5123
5124			break;
5125
5126			case 'icon':
5127
5128				$value = "<span class='icon-preview'>".$tp->toIcon($value,$parms)."</span>";
5129
5130			break;
5131
5132			case 'file':
5133				if(vartrue($parms['base']))
5134				{
5135					$url = $parms['base'].$value;
5136				}
5137				else $url = e107::getParser()->replaceConstants($value, 'full');
5138				$name = basename($value);
5139				$value = '<a href="'.$url.'" title="Direct link to '.$name.'" rel="external">'.$name.'</a>';
5140			break;
5141
5142			case 'image': //js tooltip...
5143
5144				$thparms = array();
5145				$createLink = true;
5146
5147						// Support readParms example: thumb=1&w=200&h=300
5148						// Support readParms example: thumb=1&aw=80&ah=30
5149				if(isset($parms['h']))		{ 	$thparms['h'] 	= intval($parms['h']); 		}
5150				if(isset($parms['ah']))		{ 	$thparms['ah'] 	= intval($parms['ah']); 	}
5151				if(isset($parms['w']))		{ 	$thparms['w'] 	= intval($parms['w']); 		}
5152				if(isset($parms['aw']))		{ 	$thparms['aw'] 	= intval($parms['aw']); 	}
5153				if(isset($parms['crop']))	{ 	$thparms['crop'] = $parms['crop']; 	}
5154
5155
5156
5157				if($value)
5158				{
5159
5160					if(strpos($value,",")!==false)
5161					{
5162						$tmp = explode(",",$value);
5163						$value = $tmp[0];
5164						unset($tmp);
5165					}
5166
5167					if(empty($parms['thumb_aw']) && !empty($parms['thumb']) && strpos($parms['thumb'],'x')!==false)
5168					{
5169						list($parms['thumb_aw'],$parms['thumb_ah']) = explode('x',$parms['thumb']);
5170					}
5171
5172					$vparm = array('thumb'=>'tag','w'=> vartrue($parms['thumb_aw'],'80'));
5173
5174					if($video = e107::getParser()->toVideo($value,$vparm))
5175					{
5176						return $video;
5177					}
5178
5179					$fileOnly = basename($value);
5180					// Not an image but a file.  (media manager)
5181					if(!preg_match("/\.(png|jpg|jpeg|gif|PNG|JPG|JPEG|GIF)$/", $fileOnly) && false !== strpos($fileOnly,'.'))
5182					{
5183						$icon = "{e_IMAGE}filemanager/zip_32.png";
5184						$src = $tp->replaceConstants(vartrue($parms['pre']).$icon, 'abs');
5185					//	return $value;
5186						return e107::getParser()->toGlyph('fa-file','size=2x');
5187				//		return '<img src="'.$src.'" alt="'.$value.'" class="e-thumb" title="'.$value.'" />';
5188					}
5189
5190
5191
5192					if(!empty($parms['thumb']))
5193					{
5194
5195						if(isset($parms['link']) && empty($parms['link']))
5196						{
5197							$createLink = false;
5198						}
5199
5200						// Support readParms example: thumb=200x300 (wxh)
5201						if(strpos($parms['thumb'],'x')!==false)
5202						{
5203							list($thparms['w'],$thparms['h']) = explode('x',$parms['thumb']);
5204						}
5205
5206						// Support readParms example: thumb={width}
5207						if(!isset($parms['w']) && is_numeric($parms['thumb']) && '1' != $parms['thumb'])
5208						{
5209							$thparms['w'] = intval($parms['thumb']);
5210						}
5211						elseif(vartrue($parms['thumb_aw'])) // Legacy v2.
5212						{
5213							$thparms['aw'] = intval($parms['thumb_aw']);
5214						}
5215
5216						if(!empty($parms['legacyPath']))
5217						{
5218							$thparms['legacy'] = $parms['legacyPath'];
5219							$parms['pre'] = rtrim($parms['legacyPath'],'/').'/';
5220						}
5221					//	return print_a($thparms,true);
5222
5223						if(!empty($value[0]) && $value[0] === '{') // full path to convert.
5224						{
5225							$src = $tp->replaceConstants($value, 'abs');
5226						}
5227						else // legacy link without {e_XXX} path. eg. downloads thumbs.
5228						{
5229							$src = $tp->replaceConstants(vartrue($parms['pre']).$value, 'abs');
5230						}
5231
5232						$alt = basename($src);
5233
5234
5235						$thparms['alt'] = $alt;
5236						$thparms['class'] = "thumbnail e-thumb";
5237
5238					//	e107::getDebug()->log($value);
5239
5240						$ttl = $tp->toImage($value, $thparms);
5241
5242						if($createLink === false)
5243						{
5244							return $ttl;
5245						}
5246
5247
5248						$value = '<a href="'.$src.'" data-modal-caption="'.$alt.'" data-target="#uiModal" class="e-modal e-image-preview" title="'.$alt.'" rel="external">'.$ttl.'</a>';
5249					}
5250					else
5251					{
5252						$src = $tp->replaceConstants(vartrue($parms['pre']).$value, 'abs');
5253						$alt = $src; //basename($value);
5254						$ttl = vartrue($parms['title'], 'LAN_PREVIEW');
5255						$value = '<a href="'.$src.'" class="e-image-preview" title="'.$alt.'" rel="external">'.defset($ttl, $ttl).'</a>';
5256					}
5257				}
5258				elseif(!empty($parms['fallback']))
5259				{
5260					$value = $parms['fallback'];
5261					$thparms['class'] = "thumbnail e-thumb fallback";
5262					return $tp->toImage($value, $thparms);
5263				}
5264			break;
5265
5266
5267			case 'media':
5268
5269				if(!empty($value) && $attributes['data'] === 'json')
5270				{
5271					$tmp = e107::unserialize($value);
5272					$value = !empty($tmp[0]['path']) ? $tmp[0]['path'] : null; // display first item.
5273				}
5274
5275				return e107::getMedia()->previewTag($value, $parms);
5276			break;
5277
5278			case 'files':
5279				$ret = '<ol>';
5280				for ($i=0; $i < 5; $i++)
5281				{
5282					//$k 		= $key.'['.$i.'][path]';
5283					$ival 	= $value[$i]['path'];
5284					$ret .=  '<li>'.$ival.'</li>';
5285				}
5286				$ret .= '</ol>';
5287				$value = $ret;
5288			break;
5289
5290			case 'datestamp':
5291				$value = $value ? e107::getDate()->convert_date($value, vartrue($parms['mask'], 'short')) : '';
5292			break;
5293
5294			case 'date':
5295
5296				if(empty($attributes['noedit']) && !empty($parms['editable']) && empty($parms['link'])) // avoid bad markup, better solution coming up
5297				{
5298					$value = $this->renderInline($field,$id,$attributes['title'],$value, $value);
5299				}
5300
5301				// just show original value
5302			break;
5303
5304			case 'userclass':
5305				$dispvalue = $this->_uc->getName($value);
5306					// Inline Editing.
5307				if(empty($attributes['noedit']) && !empty($parms['editable']) && empty($parms['link'])) // avoid bad markup, better solution coming up
5308				{
5309					// $mode = preg_replace('/[^\w]/', '', vartrue($_GET['mode'], ''));
5310
5311					$uc_options = vartrue($parms['classlist'], 'public,guest,nobody,member,admin,main,classes'); // defaults to 'public,guest,nobody,member,classes' (userclass handler)
5312					unset($parms['classlist']);
5313
5314					$array = e107::getUserClass()->uc_required_class_list($uc_options); //XXX Ugly looking (non-standard) function naming - TODO discuss name change.
5315
5316					$value = $this->renderInline($field, $id, $attributes['title'], $value, $dispvalue, 'select', $array, array('placement'=>'left'));
5317				}
5318				else
5319				{
5320					$value = $dispvalue;
5321				}
5322			break;
5323
5324			case 'userclasses':
5325			//	return $value;
5326				$classes = explode(',', $value);
5327
5328				$uv = array();
5329				foreach ($classes as $cid)
5330				{
5331					if(!empty($parms['defaultLabel']) && $cid === '')
5332					{
5333						$uv[] = $parms['defaultLabel'];
5334						continue;
5335					}
5336
5337					$uv[] = $this->_uc->getName($cid);
5338				}
5339
5340
5341
5342				$dispvalue = implode(vartrue($parms['separator'],"<br />"), $uv);
5343
5344				// Inline Editing.
5345				if(!vartrue($attributes['noedit']) && vartrue($parms['editable']) && !vartrue($parms['link'])) // avoid bad markup, better solution coming up
5346				{
5347					$uc_options = vartrue($parms['classlist'], 'public,guest, nobody,member,admin,main,classes'); // defaults to 'public,guest,nobody,member,classes' (userclass handler)
5348					$array      = e107::getUserClass()->uc_required_class_list($uc_options); //XXX Ugly looking (non-standard) function naming - TODO discuss name change.
5349
5350					//$mode = preg_replace('/[^\w]/', '', vartrue($_GET['mode'], ''));
5351					$mode       = $tp->filter(vartrue($_GET['mode'], ''),'w');
5352					$source     = str_replace('"',"'",json_encode($array, JSON_FORCE_OBJECT));
5353
5354					//NOTE Leading ',' required on $value; so it picks up existing value.
5355					$value = "<a class='e-tip e-editable editable-click' data-placement='bottom' data-value=',".$value."' data-name='".$field."' data-source=\"".$source."\" title=\"".LAN_EDIT." ".$attributes['title']."\" data-type='checklist' data-pk='".$id."' data-token='".$this->inlineToken()."' data-url='".e_SELF."?mode={$mode}&amp;action=inline&amp;id={$id}&amp;ajax_used=1' href='#'>".$dispvalue."</a>";
5356				}
5357				else
5358				{
5359					$value = $dispvalue;
5360				}
5361
5362				unset($parms['classlist']);
5363
5364			break;
5365
5366			/*case 'user_name':
5367			case 'user_loginname':
5368			case 'user_login':
5369			case 'user_customtitle':
5370			case 'user_email':*/
5371			case 'user':
5372
5373				/*if(is_numeric($value))
5374				{
5375					$value = e107::user($value);
5376					if($value)
5377					{
5378						$value = $value[$attributes['type']] ? $value[$attributes['type']] : $value['user_name'];
5379					}
5380					else
5381					{
5382						$value = 'not found';
5383					}
5384				}*/
5385				$row_id = $id;
5386				// Dirty, but the only way for now
5387				$id = 0;
5388				$ttl = LAN_ANONYMOUS;
5389
5390				//Defaults to user_id and user_name (when present) and when idField and nameField are not present.
5391
5392
5393				// previously set - real parameters are idField && nameField
5394				$id = vartrue($parms['__idval']);
5395				if($value && !is_numeric($value))
5396				{
5397					$id = vartrue($parms['__idval']);
5398					$ttl = $value;
5399				}
5400				elseif($value && is_numeric($value))
5401				{
5402					$id = $value;
5403
5404					if (vartrue($parms['__nameval']))
5405					{
5406						$ttl = $parms['__nameval'];
5407					}
5408					else
5409					{
5410						$user = e107::user($value);
5411						if (vartrue($user['user_name']))
5412						{
5413							$ttl = $user['user_name'];
5414						}
5415					}
5416				}
5417
5418
5419				if(!empty($parms['link']) && $id && $ttl && is_numeric($id))
5420				{
5421					// Stay in admin area.
5422					$link = e_ADMIN."users.php?mode=main&action=edit&id=".$id."&readonly=1&iframe=1"; // e107::getUrl()->create('user/profile/view', array('id' => $id, 'name' => $ttl))
5423
5424					$value = '<a class="e-modal" data-modal-caption="User #'.$id.' : '.$ttl.'" href="'.$link.'" title="'.LAN_EFORM_011.'">'.$ttl.'</a>';
5425				}
5426				else
5427				{
5428					$value = $ttl;
5429				}
5430
5431				// Inline Editing.
5432				if(!vartrue($attributes['noedit']) && vartrue($parms['editable']) && !vartrue($parms['link'])) // avoid bad markup, better solution coming up
5433				{
5434					// Need a Unique Field ID to store field settings using e107::js('settings').
5435					$fieldID = $this->name2id($field . '_' . microtime(true));
5436					// Unique ID for each rows.
5437					$eEditableID = $this->name2id($fieldID . '_' . $row_id);
5438				//	$tpl = $this->userpicker($field, '', $ttl, $id, array('id' => $fieldID, 'selectize' => array('e_editable' => $eEditableID)));
5439
5440					$tpl = $this->userpicker($fieldID, array('user_id'=>$id, 'user_name'=>$ttl),  array('id' => $fieldID, 'inline' => $eEditableID));
5441					$mode = preg_replace('/[^\w]/', '', vartrue($_GET['mode'], ''));
5442					$value = "<a id='" . $eEditableID . "' class='e-tip e-editable editable-click editable-userpicker' data-clear='false' data-token='".$this->inlineToken()."' data-tpl='" . str_replace("'", '"', $tpl) . "' data-name='" . $field . "' title=\"" . LAN_EDIT . " " . $attributes['title'] . "\" data-type='text' data-pk='" . $row_id . "' data-value='" . $id . "' data-url='" . e_SELF . "?mode={$mode}&amp;action=inline&amp;id={$row_id}&amp;ajax_used=1' href='#'>" . $ttl . "</a>";
5443				}
5444
5445			break;
5446
5447			/**
5448			 * $parms['true']  - label to use for true
5449			 * $parms['false'] - label to use for false
5450			 * $parms['enabled'] - alias of $parms['true']
5451			 * $parms['disabled'] - alias of $parms['false']
5452			 * $parms['reverse'] - use 0 for true and 1 for false.
5453			 */
5454			case 'bool':
5455			case 'boolean':
5456				$false = vartrue($parms['trueonly']) ? "" : ADMIN_FALSE_ICON;
5457
5458				if(!empty($parms['enabled']))
5459				{
5460					$parms['true'] = $parms['enabled'];
5461				}
5462
5463				if(!empty($parms['disabled']))
5464				{
5465					$parms['false'] = $parms['disabled'];
5466				}
5467
5468				if(!vartrue($attributes['noedit']) && vartrue($parms['editable']) && !vartrue($parms['link'])) // avoid bad markup, better solution coming up
5469				{
5470					if(isset($parms['false'])) // custom representation for 'false'. (supports font-awesome when set by css)
5471					{
5472						$false = $parms['false'];
5473					}
5474					else
5475					{
5476						// https://stackoverflow.com/questions/2965971/how-to-add-images-in-select-list
5477
5478						$false = ($value === '') ? "&square;" : "&#10799;"; // "&cross;";
5479					}
5480
5481					$true = varset($parms['true'], "&#10004;" /*'&check;'*/); // custom representation for 'true'. (supports font-awesome when set by css)
5482
5483			//		$true = '&#xf00c';
5484			//		$false = '\f00d';
5485
5486					$value = intval($value);
5487
5488					$wparms = (vartrue($parms['reverse'])) ? array(0=>$true, 1=>$false) : array(0=>$false, 1=>$true);
5489					$dispValue = $wparms[$value];
5490					$styleClass = '';
5491
5492                    if($true ==='&#10004;')
5493                    {
5494					    $styleClass = ($value === 1) ? 'admin-true-icon' : 'admin-false-icon';
5495                    }
5496
5497
5498					return $this->renderInline($field, $id, $attributes['title'], $value, $dispValue, 'select', $wparms, array('class'=>'e-editable-boolean '.$styleClass));
5499				}
5500
5501				if(vartrue($parms['reverse']))
5502				{
5503					$value = ($value) ? $false : ADMIN_TRUE_ICON;
5504				}
5505				else
5506				{
5507					$value = $value ? ADMIN_TRUE_ICON : $false;
5508				}
5509
5510			break;
5511
5512			case 'url':
5513				if(!$value) break;
5514				$ttl = $value;
5515				if(!empty($parms['href']))
5516				{
5517					return $tp->replaceConstants(vartrue($parms['pre']).$value, varset($parms['replace_mod'],'abs'));
5518				}
5519				if(!empty($parms['truncate']))
5520				{
5521					$ttl = $tp->text_truncate($value, $parms['truncate'], '...');
5522				}
5523
5524				$target = (!empty($parms['target'])) ? " target='".$parms['target']."' " : "";
5525				$class = (!empty($parms['class'])) ? " class='".$parms['class']."' " : "";
5526
5527				$value = "<a ".$target.$class."href='".$tp->replaceConstants(vartrue($parms['pre']).$value, 'abs')."' title='{$value}'>".$ttl."</a>";
5528			break;
5529
5530			case 'email':
5531				if(!$value) break;
5532				$ttl = $value;
5533				if(vartrue($parms['truncate']))
5534				{
5535					$ttl = $tp->text_truncate($value, $parms['truncate'], '...');
5536				}
5537
5538				$target = (!empty($parms['target'])) ? " target='".$parms['target']."' " : "";
5539				$class = (!empty($parms['class'])) ? " class='".$parms['class']."' " : "";
5540
5541				$value = "<a ".$target.$class."href='mailto:".$value."' title='{$value}'>".$ttl."</a>";
5542			break;
5543
5544			case 'method': // Custom Function
5545				$method = varset($attributes['field']); // prevents table alias in method names. ie. u.my_method.
5546				$_value = $value;
5547
5548				if(!empty($attributes['data']) && $attributes['data'] == 'array') // FIXME @SecretR - please move this to where it should be.
5549				{
5550					$value = e107::unserialize($value); // (saved as array, return it as an array)
5551				}
5552
5553				$meth = (!empty($attributes['method'])) ? $attributes['method'] : $method;
5554
5555				if(strpos($meth,'::')!==false)
5556				{
5557					list($className,$meth) = explode('::', $meth);
5558					$cls = new $className();
5559				}
5560				else
5561				{
5562					$cls = $this;
5563				}
5564
5565				if(method_exists($cls,$meth))
5566				{
5567					$parms['field'] = $field;
5568					$mode = (!empty($attributes['mode'])) ? $attributes['mode'] :'read';
5569					$value = call_user_func_array(array($cls, $meth), array($value, $mode, $parms));
5570				}
5571				else
5572				{
5573					$className = get_class($cls);
5574					e107::getDebug()->log("Missing Method: ".$className."::".$meth." ".print_a($attributes,true));
5575					return "<span class='label label-important label-danger' title='".$className."::".$meth."'>Missing Method</span>";
5576				}
5577			//	 print_a($attributes);
5578					// Inline Editing.
5579				if(empty($attributes['noedit']) && !empty($parms['editable'])) // avoid bad markup, better solution coming up
5580				{
5581
5582					$mode = preg_replace('/[^\w]/', '', vartrue($_GET['mode'], ''));
5583					$methodParms = call_user_func_array(array($this, $meth), array($_value, 'inline', $parms));
5584
5585					$inlineParms = (!empty($methodParms['inlineParms'])) ? $methodParms['inlineParms'] : null;
5586
5587					if(!empty($methodParms['inlineType']))
5588					{
5589						$attributes['inline'] = $methodParms['inlineType'];
5590						$methodParms = (!empty($methodParms['inlineData'])) ? $methodParms['inlineData'] : null;
5591					}
5592
5593
5594
5595					if(is_string($attributes['inline'])) // text, textarea, select, checklist.
5596					{
5597						switch ($attributes['inline'])
5598						{
5599
5600							case 'checklist':
5601								$xtype = 'checklist';
5602							break;
5603
5604							case 'select':
5605							case 'dropdown':
5606								$xtype = 'select';
5607							break;
5608
5609							case 'textarea':
5610								$xtype = 'textarea';
5611							break;
5612
5613
5614							default:
5615								$xtype = 'text';
5616								 $methodParms = null;
5617							break;
5618						}
5619					}
5620
5621					if(!empty($xtype))
5622					{
5623						$value = varset($inlineParms['pre'],'').$this->renderInline($field, $id, $attributes['title'], $_value, $value, $xtype, $methodParms,$inlineParms).varset($inlineParms['post'],'');
5624					}
5625
5626				}
5627
5628			break;
5629
5630			case 'hidden':
5631				return (vartrue($parms['show']) ? ($value ? $value : vartrue($parms['empty'])) : '');
5632			break;
5633
5634			case 'language': // All Known Languages.
5635
5636				if(!empty($value))
5637				{
5638					$_value = $value;
5639					if(strlen($value) === 2)
5640					{
5641						$value = e107::getLanguage()->convert($value);
5642					}
5643				}
5644
5645				if(!vartrue($attributes['noedit']) && vartrue($parms['editable']))
5646				{
5647					$wparms = e107::getLanguage()->getList();
5648					return $this->renderInline($field, $id, $attributes['title'], $_value, $value, 'select', $wparms);
5649				}
5650
5651				return $value;
5652
5653			break;
5654
5655			case 'lanlist': // installed languages.
5656				$options = e107::getLanguage()->getLanSelectArray();
5657
5658				if($options) // FIXME - add support for multi-level arrays (option groups)
5659				{
5660					if(!is_array($attributes['writeParms'])) parse_str($attributes['writeParms'], $attributes['writeParms']);
5661					$wparms = $attributes['writeParms'];
5662					if(!is_array(varset($wparms['__options']))) parse_str($wparms['__options'], $wparms['__options']);
5663					$opts = $wparms['__options'];
5664					if($opts['multiple'])
5665					{
5666						$ret = array();
5667						$value = is_array($value) ? $value : explode(',', $value);
5668						foreach ($value as $v)
5669						{
5670							if(isset($options[$v])) $ret[] = $options[$v];
5671						}
5672						$value = implode(', ', $ret);
5673					}
5674					else
5675					{
5676						$ret = '';
5677						if(isset($options[$value])) $ret = $options[$value];
5678						$value = $ret;
5679					}
5680					$value = ($value ? vartrue($parms['pre']).$value.vartrue($parms['post']) : '');
5681				}
5682				else
5683				{
5684					$value = '';
5685				}
5686			break;
5687
5688			//TODO - order
5689
5690			default:
5691				$value = $this->renderLink($value,$parms,$id);
5692				//unknown type
5693			break;
5694		}
5695
5696		return $value;
5697	}
5698
5699	/**
5700	 * Auto-render Form Element
5701	 * @param string $key
5702	 * @param mixed $value
5703	 * @param array $attributes field attributes including render parameters, element options - see e_admin_ui::$fields for required format
5704	 * #param array (under construction) $required_data required array as defined in e_model/validator
5705	 * @param mixed $attributes['writeParms']['default'] default value when empty (or default option when type='dropdown')
5706	 * @param mixed $attributes['writeParms']['defaultValue'] default option value when type='dropdown'
5707	 * @param mixed $attributes['writeParms']['empty'] default value when value is empty (dropdown and hidden only right now)
5708	 * @return string
5709	 */
5710	function renderElement($key, $value, $attributes, $required_data = array(), $id = 0)
5711	{
5712		$tp = e107::getParser();
5713
5714		$parms = vartrue($attributes['writeParms'], array());
5715
5716		if($tmpOpt = $tp->isJSON($parms))
5717		{
5718			$parms = $tmpOpt;
5719			unset($tmpOpt);
5720		}
5721
5722		if(is_string($parms)) parse_str($parms, $parms);
5723
5724		$ajaxParms = array();
5725
5726		if(!empty($parms['ajax']))
5727		{
5728			$ajaxParms['data-src'] = varset($parms['ajax']['src']);
5729			$ajaxParms['data-target'] = varset($parms['ajax']['target']);
5730			$ajaxParms['data-method'] = varset($parms['ajax']['method'], 'html');
5731			$ajaxParms['data-loading'] = varset($parms['ajax']['loading'], 'fa-spinner'); //$tp->toGlyph('fa-spinner', array('spin'=>1))
5732
5733			unset($attributes['writeParms']['ajax']);
5734
5735		//	e107::getDebug()->log($parms['ajax']);
5736		}
5737
5738		if(!empty($attributes['multilan']))
5739		{
5740			$value = is_array($value) ? varset($value[e_LANGUAGE],'') : $value;
5741			$parms['post'] = "<small class='e-tip admin-multilanguage-field input-group-addon' style='cursor:help; padding-left:10px' title='".LAN_EFORM_012." (".e_LANGUAGE.")'>".$tp->toGlyph('fa-language')."</small>".varset($parms['post']);
5742			$key = $key.'['.e_LANGUAGE.']';
5743		}
5744
5745		if(empty($value) && !empty($parms['default']) && $attributes['type'] !== 'dropdown') // Allow writeParms to set default value.
5746		{
5747			$value = $parms['default'];
5748		}
5749
5750		// Two modes of read-only. 1 = read-only, but only when there is a value, 2 = read-only regardless.
5751		if(vartrue($attributes['readonly']) && (vartrue($value) || vartrue($attributes['readonly'])===2)) // quick fix (maybe 'noedit'=>'readonly'?)
5752		{
5753			if(vartrue($attributes['writeParms'])) // eg. different size thumbnail on the edit page.
5754			{
5755				$attributes['readParms'] = $attributes['writeParms'];
5756			}
5757
5758			return $this->renderValue($key, $value, $attributes).$this->hidden($key, $value); //
5759		}
5760
5761		// FIXME standard - writeParams['__options'] is introduced for list elements, bundle adding to writeParms is non reliable way
5762		$writeParamsOptionable =  array('dropdown', 'comma', 'radio', 'lanlist', 'language', 'user');
5763		$writeParamsDisabled =  array('layouts', 'templates', 'userclass', 'userclasses');
5764
5765		// FIXME it breaks all list like elements - dropdowns, radio, etc
5766		if(vartrue($required_data[0]) || vartrue($attributes['required'])) // HTML5 'required' attribute
5767		{
5768			// FIXME - another approach, raise standards, remove checks
5769			if(in_array($attributes['type'], $writeParamsOptionable))
5770			{
5771				$parms['__options']['required'] = 1;
5772			}
5773			elseif(!in_array($attributes['type'], $writeParamsDisabled))
5774			{
5775				$parms['required'] = 1;
5776			}
5777		}
5778
5779		// FIXME it breaks all list like elements - dropdowns, radio, etc
5780		if(vartrue($required_data[3]) || vartrue($attributes['pattern'])) // HTML5 'pattern' attribute
5781		{
5782			// FIXME - another approach, raise standards, remove checks
5783			if(in_array($attributes['type'], $writeParamsOptionable))
5784			{
5785				$parms['__options']['pattern'] = vartrue($attributes['pattern'], $required_data[3]);
5786			}
5787			elseif(!in_array($attributes['type'], $writeParamsDisabled))
5788			{
5789				$parms['pattern'] = vartrue($attributes['pattern'], $required_data[3]);
5790			}
5791		}
5792
5793
5794
5795		// XXX Fixes For the above.  - use optArray variable. eg. $field['key']['writeParms']['optArray'] = array('one','two','three');
5796		if(($attributes['type'] == 'dropdown' || $attributes['type'] == 'radio' || $attributes['type'] == 'checkboxes') && isset($parms['optArray']))
5797		{
5798			$fopts = $parms;
5799			$parms = $fopts['optArray'];
5800			unset($fopts['optArray']);
5801			$parms['__options'] = $fopts;
5802		}
5803
5804		$this->renderElementTrigger($key, $value, $parms, $required_data, $id);
5805
5806		switch($attributes['type'])
5807		{
5808			case 'number':
5809				$maxlength = vartrue($parms['maxlength'], 255);
5810				unset($parms['maxlength']);
5811				if(!vartrue($parms['size'])) $parms['size'] = 'small';
5812				if(!vartrue($parms['class'])) $parms['class'] = 'tbox number e-spinner ';
5813				if(!$value) $value = '0';
5814				$ret =  vartrue($parms['pre']).$this->number($key, $value, $maxlength, $parms).vartrue($parms['post']);
5815			break;
5816
5817			case 'country':
5818				$ret = vartrue($parms['pre']).$this->country($key, $value, $parms).vartrue($parms['post']);
5819			break;
5820
5821			case 'ip':
5822				$ret = vartrue($parms['pre']).$this->text($key, e107::getIPHandler()->ipDecode($value), 32, $parms).vartrue($parms['post']);
5823			break;
5824
5825			case 'email':
5826				$maxlength = vartrue($parms['maxlength'], 255);
5827				unset($parms['maxlength']);
5828				$ret =  vartrue($parms['pre']).$this->email($key, $value, $maxlength, $parms).vartrue($parms['post']); // vartrue($parms['__options']) is limited. See 'required'=>true
5829			break;
5830
5831			case 'url':
5832				$maxlength = vartrue($parms['maxlength'], 255);
5833				unset($parms['maxlength']);
5834				$ret =  vartrue($parms['pre']).$this->url($key, $value, $maxlength, $parms).vartrue($parms['post']); // vartrue($parms['__options']) is limited. See 'required'=>true
5835
5836			break;
5837		//	case 'email':
5838
5839			case 'password': // encrypts to md5 when saved.
5840				$maxlength = vartrue($parms['maxlength'], 255);
5841				unset($parms['maxlength']);
5842				if(!isset($parms['required']))
5843				{
5844
5845					$parms['required'] = false;
5846				}
5847				$ret =  vartrue($parms['pre']).$this->password($key, $value, $maxlength, $parms).vartrue($parms['post']); // vartrue($parms['__options']) is limited. See 'required'=>true
5848
5849			break;
5850
5851			case 'text':
5852			case 'progressbar':
5853
5854				$maxlength = vartrue($parms['maxlength'], 255);
5855				unset($parms['maxlength']);
5856
5857				if(!empty($parms['sef']) && e_LANGUAGE != "Japanese" && e_LANGUAGE != "Korean" && e_LANGUAGE != "Hebrew") // unsupported languages.(FIXME there are more)
5858				{
5859					$sefSource = $this->name2id($parms['sef']);
5860					$sefTarget = $this->name2id($key);
5861					if(!empty($parms['tdClassRight']))
5862					{
5863						$parms['tdClassRight'] .= 'input-group';
5864					}
5865					else
5866					{
5867						$parms['tdClassRight'] = 'input-group';
5868					}
5869
5870					$parms['post'] = "<span class='form-inline input-group-btn pull-left'><a class='e-sef-generate btn btn-default' data-src='".$sefSource."' data-target='".$sefTarget."' data-sef-generate-confirm=\"".LAN_WILL_OVERWRITE_SEF." ".LAN_JSCONFIRM."\">".LAN_GENERATE."</a></span>";
5871				}
5872
5873				if(!empty($parms['password'])) // password mechanism without the md5 storage.
5874				{
5875					$ret =  vartrue($parms['pre']).$this->password($key, $value, $maxlength, $parms).vartrue($parms['post']);
5876				}
5877
5878				else
5879				{
5880					$ret =  vartrue($parms['pre']).$this->text($key, $value, $maxlength, $parms).vartrue($parms['post']); // vartrue($parms['__options']) is limited. See 'required'=>true
5881				}
5882
5883
5884				if(!empty($attributes['multilan']))
5885				{
5886					$msize = vartrue($parms['size'], 'xxlarge');
5887					$ret = "<span class='input-group input-".$msize."'>".$ret."</span>";
5888				}
5889
5890			break;
5891
5892			case 'tags':
5893				$maxlength = vartrue($parms['maxlength'], 255);
5894				$ret =  vartrue($parms['pre']).$this->tags($key, $value, $maxlength, $parms).vartrue($parms['post']); // vartrue($parms['__options']) is limited. See 'required'=>true
5895			break;
5896
5897			case 'textarea':
5898				$text = "";
5899				if(vartrue($parms['append']) && vartrue($value)) // similar to comments - TODO TBD. a 'comment' field type may be better.
5900				{
5901					$attributes['readParms'] = 'bb=1';
5902
5903					$text = $this->renderValue($key, $value, $attributes);
5904					$text .= '<br />';
5905					$value = "";
5906
5907					// Appending needs is  performed and customized using function: beforeUpdate($new_data, $old_data, $id)
5908				}
5909
5910				if(empty($parms['size']))
5911				{
5912					$parms['size'] = 'xxlarge';
5913				}
5914
5915
5916				$text .= vartrue($parms['pre']).$this->textarea($key, $value, vartrue($parms['rows'], 5), vartrue($parms['cols'], 40), vartrue($parms['__options'],$parms), varset($parms['counter'], false)).vartrue($parms['post']);
5917				$ret =  $text;
5918			break;
5919
5920			case 'bbarea':
5921				$options = array('counter' => varset($parms['counter'], false));
5922				// Media = media-category owner used by media-manager.
5923				$ret =  vartrue($parms['pre']).$this->bbarea($key, $value, vartrue($parms['template']), vartrue($parms['media'],'_common_image'), vartrue($parms['size'], 'medium'),$options ).vartrue($parms['post']);
5924			break;
5925
5926			case 'video':
5927			case 'image': //TODO - thumb, image list shortcode, js tooltip...
5928				$label = varset($parms['label'], 'LAN_EDIT');
5929
5930				if(!empty($parms['optArray']))
5931				{
5932
5933					return $this->imageradio($key,$value,$parms);
5934				}
5935
5936
5937				unset($parms['label']);
5938
5939				if($attributes['type'] === 'video')
5940				{
5941					$parms['video'] = 2; // ie. video only.
5942					$parms['w'] = 280;
5943				}
5944
5945
5946				$ret =  $this->imagepicker($key, $value, defset($label, $label), $parms);
5947			break;
5948
5949			case 'images':
5950			//	return print_a($value, true);
5951				$ret = "";
5952				$label = varset($parms['label'], 'LAN_EDIT');
5953				$max = varset($parms['max'],5);
5954
5955				$ret .= "<div class='mediaselector-multi field-element-images'>";
5956
5957				for ($i=0; $i < $max; $i++)
5958				{
5959					$k 		= $key.'['.$i.'][path]';
5960					$ival 	= $value[$i]['path'];
5961
5962					$ret .=  $this->imagepicker($k, $ival, defset($label, $label), $parms);
5963				}
5964
5965				$ret .= "</div>";
5966			break;
5967
5968			/** Generic Media Pick for combinations of images, audio, video, glyphs, files, etc. Field Type = json */
5969			case 'media':
5970
5971				$max = varset($parms['max'],1);
5972
5973				if(!empty($value) && $attributes['data'] === 'json')
5974				{
5975					$value = e107::unserialize($value);
5976				}
5977
5978				$ret = "<div class='mediaselector-multi field-element-media'>";
5979				for ($i=0; $i < $max; $i++)
5980				{
5981					$k 		= $key.'['.$i.'][path]';
5982					$ival 	= $value[$i]['path'];
5983
5984					$ret .=  $this->mediapicker($k, $ival, $parms);
5985				}
5986
5987				$ret .= "</div>";
5988
5989				return $ret;
5990			break;
5991
5992			case 'files':
5993				$label = varset($parms['label'], 'LAN_EDIT');
5994				if($attributes['data'] == 'array')
5995				{
5996					$parms['data'] = 'array';
5997				}
5998
5999				$ret = '<ol>';
6000				for ($i=0; $i < 5; $i++)
6001				{
6002				//	$k 		= $key.'['.$i.'][path]';
6003				//	$ival 	= $value[$i]['path'];
6004					$k 		= $key.'['.$i.']';
6005					$ival 	= $value[$i];
6006					$ret .=  '<li>'.$this->filepicker($k, $ival, defset($label, $label), $parms).'</li>';
6007				}
6008				$ret .= '</ol>';
6009			break;
6010
6011			case 'file': //TODO - thumb, image list shortcode, js tooltip...
6012				$label = varset($parms['label'], 'LAN_EDIT');
6013				unset($parms['label']);
6014				$ret =  $this->filepicker($key, $value, defset($label, $label), $parms);
6015			break;
6016
6017			case 'icon':
6018				$label = varset($parms['label'], 'LAN_EDIT');
6019				$ajax = varset($parms['ajax'], true) ? true : false;
6020				unset($parms['label'], $parms['ajax']);
6021				$ret =  $this->iconpicker($key, $value, defset($label, $label), $parms, $ajax);
6022			break;
6023
6024			case 'date': // date will show the datepicker but won't convert the value to unix. ie. string value will be saved. (or may be processed manually with beforeCreate() etc. Format may be determined by $parm.
6025			case 'datestamp':
6026				// If hidden, value is updated regardless. eg. a 'last updated' field.
6027				// If not hidden, and there is a value, it is retained. eg. during the update of an existing record.
6028				// otherwise it is added. eg. during the creation of a new record.
6029				if(vartrue($parms['auto']) && (($value == null) || vartrue($parms['hidden'])))
6030				{
6031					$value = time();
6032				}
6033
6034				if(vartrue($parms['readonly'])) // different to 'attribute-readonly' since the value may be auto-generated.
6035				{
6036					$ret =  $this->renderValue($key, $value, $attributes).$this->hidden($key, $value);
6037				}
6038				elseif(vartrue($parms['hidden']))
6039				{
6040					$ret =  $this->hidden($key, $value);
6041				}
6042				else
6043				{
6044					$ret =  $this->datepicker($key, $value, $parms);
6045				}
6046			break;
6047
6048			case 'layouts': //to do - exclude param (exact match)
6049
6050				$location   = varset($parms['plugin']); // empty - core
6051				$ilocation  = vartrue($parms['id'], $location); // omit if same as plugin name
6052				$where      = vartrue($parms['area'], 'front'); //default is 'front'
6053				$filter     = varset($parms['filter']);
6054				$merge      = isset($parms['merge']) ? (bool) $parms['merge'] : true;
6055
6056				$layouts = e107::getLayouts($location, $ilocation, $where, $filter, $merge, false);
6057
6058				return vartrue($parms['pre'],'').$this->select($key, $layouts,$value,$parms).vartrue($parms['post'],'');
6059
6060			/*	if($tmp = e107::getTemplateInfo($location,$ilocation, null,true,$merge)) // read xxxx_INFO array from template file.
6061				{
6062					$opt = array();
6063					foreach($tmp as $k=>$inf)
6064					{
6065						$opt[$k] = $inf['title'];
6066					}
6067
6068					return vartrue($parms['pre'],'').$this->select($key,$opt,$value,$parms).vartrue($parms['post'],'');
6069				}*/
6070
6071
6072
6073/*
6074				if(varset($parms['default']) && !isset($layouts[0]['default']))
6075				{
6076					$layouts[0] = array('default' => $parms['default']) + $layouts[0];
6077				}
6078				$info = array();
6079				if($layouts[1])
6080				{
6081					foreach ($layouts[1] as $k => $info_array)
6082					{
6083						if(isset($info_array['description']))
6084						$info[$k] = defset($info_array['description'], $info_array['description']);
6085					}
6086				}
6087
6088				*/
6089
6090				//$this->selectbox($key, $layouts, $value)
6091			//	$ret =  (vartrue($parms['raw']) ? $layouts[0] : $this->radio_multi($key, $layouts[0], $value,array('sep'=>"<br />"), $info));
6092			break;
6093
6094			case 'templates': //to do - exclude param (exact match)
6095				$templates = array();
6096				if(varset($parms['default']))
6097				{
6098					$templates['default'] = defset($parms['default'], $parms['default']);
6099				}
6100				$location = vartrue($parms['plugin']) ? e_PLUGIN.$parms['plugin'].'/' : e_THEME;
6101				$ilocation = vartrue($parms['location']);
6102				$tmp = e107::getFile()->get_files($location.'templates/'.$ilocation, vartrue($parms['fmask'], '_template\.php$'), vartrue($parms['omit'], 'standard'), vartrue($parms['recurse_level'], 0));
6103				foreach(self::sort_get_files_output($tmp) as $files)
6104				{
6105					$k = str_replace('_template.php', '', $files['fname']);
6106					$templates[$k] = implode(' ', array_map('ucfirst', explode('_', $k))); //TODO add LANS?
6107				}
6108
6109				// override
6110				$where = vartrue($parms['area'], 'front');
6111				$location = vartrue($parms['plugin']) ? $parms['plugin'].'/' : '';
6112				$tmp = e107::getFile()->get_files(e107::getThemeInfo($where, 'rel').'templates/'.$location.$ilocation, vartrue($parms['fmask']), vartrue($parms['omit'], 'standard'), vartrue($parms['recurse_level'], 0));
6113				foreach(self::sort_get_files_output($tmp) as $files)
6114				{
6115					$k = str_replace('_template.php', '', $files['fname']);
6116					$templates[$k] = implode(' ', array_map('ucfirst', explode('_', $k))); //TODO add LANS?
6117				}
6118				$ret =  (vartrue($parms['raw']) ? $templates : $this->selectbox($key, $templates, $value));
6119			break;
6120
6121			case 'checkboxes':
6122
6123				if(is_array($parms))
6124				{
6125					$eloptions  = vartrue($parms['__options'], array());
6126					if(is_string($eloptions)) parse_str($eloptions, $eloptions);
6127					if($attributes['type'] === 'comma') $eloptions['multiple'] = true;
6128					unset($parms['__options']);
6129
6130					if(!is_array($value) && !empty($value))
6131					{
6132						$value = explode(",",$value);
6133					}
6134
6135
6136					$ret =  vartrue($eloptions['pre']).$this->checkboxes($key, (array) $parms, $value, $eloptions).vartrue($eloptions['post']);
6137
6138
6139				}
6140				return $ret;
6141			break;
6142
6143
6144			case 'dropdown':
6145			case 'comma':
6146
6147
6148				if(!empty($attributes['writeParms']['optArray']))
6149				{
6150					$eloptions = $attributes['writeParms'];
6151					unset($eloptions['optArray']);
6152				}
6153				else
6154				{
6155					$eloptions  = vartrue($parms['__options'], array());
6156				}
6157
6158				$value = (isset($eloptions['empty']) && ($value === null)) ? $eloptions['empty'] : $value;
6159
6160				if(is_string($eloptions)) parse_str($eloptions, $eloptions);
6161				if($attributes['type'] === 'comma') $eloptions['multiple'] = true;
6162				unset($parms['__options']);
6163				if(vartrue($eloptions['multiple']) && !is_array($value)) $value = explode(',', $value);
6164
6165				// Allow Ajax API.
6166				if(!empty($ajaxParms))
6167				{
6168					$eloptions = array_merge_recursive($eloptions, $ajaxParms);
6169					$eloptions['class'] = 'e-ajax ' . varset($eloptions['class']);
6170				}
6171
6172				$ret =  vartrue($eloptions['pre']).$this->select($key, $parms, $value, $eloptions).vartrue($eloptions['post']);
6173			break;
6174
6175			case 'radio':
6176				// TODO - more options (multi-line, help)
6177				$eloptions  = vartrue($parms['__options'], array());
6178				if(is_string($eloptions)) parse_str($eloptions, $eloptions);
6179				unset($parms['__options']);
6180				$ret =  vartrue($eloptions['pre']).$this->radio_multi($key, $parms, $value, $eloptions, false).vartrue($eloptions['post']);
6181			break;
6182
6183			case 'userclass':
6184			case 'userclasses':
6185
6186
6187				$uc_options = vartrue($parms['classlist'], 'public,guest,nobody,member,admin,main,classes'); // defaults to 'public,guest,nobody,member,classes' (userclass handler)
6188				unset($parms['classlist']);
6189
6190
6191			//	$method = ($attributes['type'] == 'userclass') ? 'uc_select' : 'uc_select';
6192				if(vartrue($attributes['type']) == 'userclasses'){ $parms['multiple'] = true; }
6193
6194				$ret =   vartrue($parms['pre']).$this->uc_select($key, $value, $uc_options, vartrue($parms, array())). vartrue($parms['post']);
6195			break;
6196
6197			/*case 'user_name':
6198			case 'user_loginname':
6199			case 'user_login':
6200			case 'user_customtitle':
6201			case 'user_email':*/
6202			case 'user':
6203				//user_id expected
6204				// Just temporary solution, could be changed soon
6205
6206
6207				if(!isset($parms['__options'])) $parms['__options'] = array();
6208				if(!is_array($parms['__options'])) parse_str($parms['__options'], $parms['__options']);
6209
6210				if((empty($value) || !empty($parms['currentInit']) && empty($parms['default']) ) || !empty($parms['current']) || (vartrue($parms['default']) == 'USERID')) // include current user by default.
6211				{
6212					$value = array('user_id'=>USERID, 'user_name'=>USERNAME);
6213					if(vartrue($parms['current']))
6214					{
6215						$parms['__options']['readonly'] = true;
6216					}
6217
6218				}
6219
6220
6221
6222
6223
6224		//		if(!is_array($value))
6225		//		{
6226			//		$value = $value ? e107::getSystemUser($value, true)->getUserData() : array();// e107::user($value);
6227		//		}
6228
6229				$colname = vartrue($parms['nameType'], 'user_name');
6230				$parms['__options']['name'] = $colname;
6231
6232		//		if(!$value) $value = array();
6233		//		$uname = varset($value[$colname]);
6234		//		$value = varset($value['user_id'], 0);
6235
6236				if(!empty($parms['limit']))
6237				{
6238					$parms['__options']['limit'] = intval($parms['limit']);
6239				}
6240
6241				$ret =  $this->userpicker(vartrue($parms['nameField'], $key), $value, vartrue($parms['__options']));
6242
6243			//	$ret =  $this->userpicker(vartrue($parms['nameField'], $key), $key, $uname, $value, vartrue($parms['__options']));
6244			break;
6245
6246
6247			/**
6248			 * $parms['true']  - label to use for true
6249			 * $parms['false'] - label to use for false
6250			 * $parms['enabled'] - alias of $parms['true']
6251			 * $parms['disabled'] - alias of $parms['false']
6252			 * $parms['label'] - when set to 'yesno' uses yes/no instead of enabled/disabled
6253			 */
6254			case 'bool':
6255			case 'boolean':
6256
6257				if(varset($parms['label']) === 'yesno')
6258				{
6259					$lenabled = 'LAN_YES';
6260					$ldisabled = 'LAN_NO';
6261				}
6262				else
6263				{
6264					$lenabled = vartrue($parms['enabled'], 'LAN_ON');
6265					$ldisabled = (!empty($parms['disabled']) && is_string($parms['disabled'])) ?  $parms['disabled'] : 'LAN_OFF';
6266				}
6267
6268				if(!empty($parms['true']))
6269				{
6270					$lenabled = $parms['true'];
6271				}
6272
6273				if(!empty($parms['false']))
6274				{
6275					$ldisabled = $parms['false'];
6276				}
6277
6278
6279				unset($parms['enabled'], $parms['disabled'], $parms['label']);
6280				$ret =  vartrue($parms['pre']).$this->radio_switch($key, $value, defset($lenabled, $lenabled), defset($ldisabled, $ldisabled),$parms).vartrue($parms['post']);
6281			break;
6282
6283			case "checkbox":
6284
6285				$value = (isset($parms['value'])) ? $parms['value'] : $value;
6286				$ret =  vartrue($parms['pre']).$this->checkbox($key, 1, $value,$parms).vartrue($parms['post']);
6287			break;
6288
6289			case 'method': // Custom Function
6290				$meth = (!empty($attributes['method'])) ? $attributes['method'] : $key;
6291				$parms['field'] = $key;
6292
6293				if(strpos($meth,'::')!==false)
6294				{
6295					list($className,$meth) = explode('::', $meth);
6296					$cls = new $className;
6297				}
6298				else
6299				{
6300					$cls = $this;
6301				}
6302
6303				$ret =  call_user_func_array(array($cls, $meth), array($value, 'write', $parms));
6304			break;
6305
6306			case 'upload': //TODO - from method
6307				// TODO uploadfile SC is now processing uploads as well (add it to admin UI), write/readParms have to be added (see uploadfile.php parms)
6308				$disbut = varset($parms['disable_button'], '0');
6309				$ret =  $tp->parseTemplate("{UPLOADFILE=".(vartrue($parms['path']) ? e107::getParser()->replaceConstants($parms['path']) : e_UPLOAD)."|nowarn&trigger=etrigger_uploadfiles&disable_button={$disbut}}");
6310			break;
6311
6312			case 'hidden':
6313
6314				$value = (isset($parms['value'])) ? $parms['value'] : $value;
6315				$ret = (vartrue($parms['show']) ? ($value ? $value : varset($parms['empty'], $value)) : '');
6316				$ret =  $ret.$this->hidden($key, $value);
6317			break;
6318
6319			case 'lanlist': // installed languages
6320			case 'language': // all languages
6321
6322				$options = ($attributes['type'] === 'language') ? e107::getLanguage()->getList() : e107::getLanguage()->getLanSelectArray();
6323
6324				$eloptions  = vartrue($parms['__options'], array());
6325				if(!is_array($eloptions)) parse_str($eloptions, $eloptions);
6326				unset($parms['__options']);
6327				if(vartrue($eloptions['multiple']) && !is_array($value)) $value = explode(',', $value);
6328				$ret =  vartrue($eloptions['pre']).$this->selectbox($key, $options, $value, $eloptions).vartrue($eloptions['post']);
6329			break;
6330
6331			case null:
6332			//	Possibly used in db but should not be submitted in form. @see news_extended.
6333			break;
6334
6335			default:// No LAN necessary, debug only.
6336				$ret =  (ADMIN) ? "<span class='alert alert-error alert-danger'>".LAN_ERROR." Unknown 'type' : ".$attributes['type'] ."</span>" : $value;
6337			break;
6338		}
6339
6340		if(vartrue($parms['expand']))
6341		{
6342			$k = "exp-".$this->name2id($key);
6343			$text = "<a class='e-expandit e-tip' href='#{$k}'>".$parms['expand']."</a>";
6344			$text .= vartrue($parms['help']) ? '<div class="field-help">'.$parms['help'].'</div>' : '';
6345			$text .= "<div id='{$k}' class='e-hideme'>".$ret."</div>";
6346			return $text;
6347		}
6348		else
6349		{
6350			/** @deprecated usage @see renderCreateFieldset() should be attributes['help'] */
6351			$ret .= vartrue($parms['help']) ? '<div class="field-help">'.$tp->toHTML($parms['help'],false,'defs').'</div>' : '';
6352		}
6353
6354		return $ret;
6355	}
6356
6357
6358	private function imageradio($name,$value,$parms)
6359	{
6360
6361		if(!empty($parms['path']))
6362		{
6363			$parms['legacy'] = $parms['path'];
6364		}
6365
6366
6367		$text = '<div class="clearfix">';
6368
6369
6370
6371		foreach($parms['optArray'] as $key=>$val)
6372		{
6373			$thumbnail    = e107::getParser()->toImage($val,$parms);
6374
6375		//	$thumbnail = "<img class='img-responsive img-fluid thumbnail'  src='".$preview ."' alt='".$val."' />";
6376
6377
6378					$selected = ($val == $value) ? " checked" : "";
6379
6380					$text .= "
6381									<div class='col-md-2 e-image-radio' >
6382										<label class='theme-selection' title=\"".$key."\"><input type='radio' name='".$name."' value='{$val}' required='required' $selected />
6383										<div>".$thumbnail."</div>
6384										</label>
6385									</div>";
6386
6387		}
6388
6389		$text .= "</div>";
6390
6391		return $text;
6392
6393	}
6394
6395
6396
6397	/**
6398	 * Generic List Form, used internally by admin UI
6399	 * Expected options array format:
6400	 * <code>
6401	 * <?php
6402	 * $form_options['myplugin'] = array(
6403	 * 		'id' => 'myplugin', // unique string used for building element ids, REQUIRED
6404	 * 		'pid' => 'primary_id', // primary field name, REQUIRED
6405	 * 		'url' => '{e_PLUGIN}myplug/admin_config.php', // if not set, e_SELF is used
6406	 * 		'query' => 'mode=main&amp;action=list', // or e_QUERY if not set
6407	 * 		'head_query' => 'mode=main&amp;action=list', // without field, asc and from vars, REQUIRED
6408	 * 		'np_query' => 'mode=main&amp;action=list', // without from var, REQUIRED for next/prev functionality
6409	 * 		'legend' => 'Fieldset Legend', // hidden by default
6410	 * 		'form_pre' => '', // markup to be added before opening form element (e.g. Filter form)
6411	 * 		'form_post' => '', // markup to be added after closing form element
6412	 * 		'fields' => array(...), // see e_admin_ui::$fields
6413	 * 		'fieldpref' => array(...), // see e_admin_ui::$fieldpref
6414	 * 		'table_pre' => '', // markup to be added before opening table element
6415	 * 		'table_post' => '', // markup to be added after closing table element (e.g. Batch actions)
6416	 * 		'fieldset_pre' => '', // markup to be added before opening fieldset element
6417	 * 		'fieldset_post' => '', // markup to be added after closing fieldset element
6418	 * 		'perPage' => 15, // if 0 - no next/prev navigation
6419	 * 		'from' => 0, // current page, default 0
6420	 * 		'field' => 'field_name', //current order field name, default - primary field
6421	 * 		'asc' => 'desc', //current 'order by' rule, default 'asc'
6422	 * );
6423	 * $tree_models['myplugin'] = new e_admin_tree_model($data);
6424	 * </code>
6425	 * TODO - move fieldset & table generation in separate methods, needed for ajax calls
6426	 * @param array $form_options
6427	 * @param e_admin_tree_model $tree_model
6428	 * @param boolean $nocontainer don't enclose form in div container
6429	 * @return string
6430	 */
6431	public function renderListForm($form_options, $tree_models, $nocontainer = false)
6432	{
6433		$tp = e107::getParser();
6434		$text = '';
6435
6436
6437
6438		foreach ($form_options as $fid => $options)
6439		{
6440			list($type,$plugin) = explode('-',$fid,2);
6441
6442			$plugin = str_replace('-','_',$plugin);
6443
6444			e107::setRegistry('core/adminUI/currentPlugin', $plugin);
6445
6446			/** @var e_tree_model $tree_model */
6447			$tree_model = $tree_models[$fid];
6448			$tree = $tree_model->getTree();
6449			$total = $tree_model->getTotal();
6450
6451
6452			$amount = $options['perPage'];
6453			$from = vartrue($options['from'], 0);
6454			$field = vartrue($options['field'], $options['pid']);
6455			$asc = strtoupper(vartrue($options['asc'], 'asc'));
6456			$elid = $fid;//$options['id'];
6457			$query = vartrue($options['query'],e_QUERY); //  ? $options['query'] :  ;
6458			if(vartrue($_GET['action']) == 'list')
6459			{
6460				$query = e_QUERY; //XXX Quick fix for loss of pagination after 'delete'.
6461			}
6462			$url = (isset($options['url']) ? $tp->replaceConstants($options['url'], 'abs') : e_SELF);
6463			$formurl = $url.($query ? '?'.$query : '');
6464			$fields = $options['fields'];
6465			$current_fields = varset($options['fieldpref']) ? $options['fieldpref'] : array_keys($options['fields']);
6466			$legend_class = vartrue($options['legend_class'], 'e-hideme');
6467
6468
6469
6470	        $text .= "
6471				<form method='post' action='{$formurl}' id='{$elid}-list-form'>
6472				<div>".$this->token()."
6473					".vartrue($options['fieldset_pre'])."
6474					<fieldset id='{$elid}-list'>
6475						<legend class='{$legend_class}'>".$options['legend']."</legend>
6476						".vartrue($options['table_pre'])."
6477						<table class='table adminlist table-striped' id='{$elid}-list-table'>
6478							".$this->colGroup($fields, $current_fields)."
6479							".$this->thead($fields, $current_fields, varset($options['head_query']), varset($options['query']))."
6480							<tbody id='e-sort'>
6481			";
6482
6483			if(!$tree)
6484			{
6485				$text .= "
6486							</tbody>
6487						</table>";
6488
6489				$text .= "<div id='admin-ui-list-no-records-found' class=' alert alert-block alert-info center middle'>".LAN_NO_RECORDS_FOUND."</div>"; // not prone to column-count issues.
6490			}
6491			else
6492			{
6493				/** @var e_model $model */
6494				foreach($tree as $model)
6495				{
6496				//	$model->set('x_canonical_url', 'whatever');
6497
6498					e107::setRegistry('core/adminUI/currentListModel', $model);
6499					$text .= $this->renderTableRow($fields, $current_fields, $model->getData(), $options['pid']);
6500				}
6501
6502
6503				e107::setRegistry('core/adminUI/currentListModel', null);
6504
6505				$text .= "</tbody>
6506						</table>";
6507			}
6508
6509
6510			$text .= vartrue($options['table_post']);
6511
6512
6513			if($tree && $amount)
6514			{
6515				// New nextprev SC parameters
6516				$parms = 'total='.$total;
6517				$parms .= '&amount='.$amount;
6518				$parms .= '&current='.$from;
6519				if(ADMIN_AREA)
6520				{
6521					$parms .= '&tmpl_prefix=admin';
6522				}
6523
6524				// NOTE - the whole url is double encoded - reason is to not break parms query string
6525				// 'np_query' should be proper (urlencode'd) url query string
6526				$url = rawurlencode($url.'?'.(varset($options['np_query']) ? str_replace(array('&amp;', '&'), array('&', '&amp;'),  $options['np_query']).'&amp;' : '').'from=[FROM]');
6527				$parms .= '&url='.$url;
6528				//$parms = $total.",".$amount.",".$from.",".$url.'?'.($options['np_query'] ? $options['np_query'].'&amp;' : '').'from=[FROM]';
6529		    	//$text .= $tp->parseTemplate("{NEXTPREV={$parms}}");
6530				$nextprev = $tp->parseTemplate("{NEXTPREV={$parms}}");
6531				if ($nextprev)
6532				{
6533					$text .= "<div class='nextprev-bar'>".$nextprev."</div>";
6534				}
6535			}
6536
6537			$text .= "
6538					</fieldset>
6539					".vartrue($options['fieldset_post'])."
6540				</div>
6541				</form>
6542			";
6543
6544			e107::setRegistry('core/adminUI/currentPlugin', null);
6545		}
6546		if(!$nocontainer)
6547		{
6548			$class = deftrue('e_IFRAME') ? 'e-container e-container-modal' : 'e-container';
6549			$text = '<div class="'.$class.'">'.$text.'</div>';
6550		}
6551		return (vartrue($options['form_pre']).$text.vartrue($options['form_post']));
6552	}
6553
6554
6555	/**
6556	 * Used with 'carousel' generates slides with X number of cells/blocks per slide.
6557	 * @param $cells
6558	 * @param int $perPage
6559	 * @return array
6560	 */
6561	private function slides($cells, $perPage=12)
6562	{
6563		$tmp = '';
6564		$s = 0;
6565		$slides = array();
6566		foreach($cells as $cell)
6567		{
6568			$tmp .= $cell;
6569
6570			$s++;
6571			if($s == $perPage)
6572			{
6573				$slides[] = array('text'=>$tmp);
6574				$tmp = '';
6575				$s = 0;
6576			}
6577		}
6578
6579		if($s != $perPage && $s != 0)
6580		{
6581			$slides[] = array('text'=>$tmp);
6582		}
6583
6584		return $slides;
6585
6586
6587	}
6588
6589
6590	/**
6591	 * Render Grid-list layout.  used internally by admin UI
6592	 * @param $form_options
6593	 * @param $tree_models
6594	 * @param bool|false $nocontainer
6595	 * @return string
6596	 */
6597	public function renderGridForm($form_options, $tree_models, $nocontainer = false)
6598	{
6599		$tp = e107::getParser();
6600		$text = '';
6601
6602
6603		// print_a($form_options);
6604
6605		foreach ($form_options as $fid => $options)
6606		{
6607			$tree_model = $tree_models[$fid];
6608			$tree = $tree_model->getTree();
6609			$total = $tree_model->getTotal();
6610
6611			$amount = $options['perPage'];
6612			$from = vartrue($options['from'], 0);
6613			$field = vartrue($options['field'], $options['pid']);
6614			$asc = strtoupper(vartrue($options['asc'], 'asc'));
6615			$elid = $fid;//$options['id'];
6616			$query = vartrue($options['query'],e_QUERY); //  ? $options['query'] :  ;
6617			if(vartrue($_GET['action']) == 'list')
6618			{
6619				$query = e_QUERY; //XXX Quick fix for loss of pagination after 'delete'.
6620			}
6621			$url = (isset($options['url']) ? $tp->replaceConstants($options['url'], 'abs') : e_SELF);
6622			$formurl = $url.($query ? '?'.$query : '');
6623			$fields = $options['fields'];
6624			$current_fields = varset($options['fieldpref']) ? $options['fieldpref'] : array_keys($options['fields']);
6625			$legend_class = vartrue($options['legend_class'], 'e-hideme');
6626
6627
6628
6629	        $text .= "
6630				<form method='post' action='{$formurl}' id='{$elid}-list-form'>
6631				<div>".$this->token()."
6632					".vartrue($options['fieldset_pre']);
6633
6634					$text .= "
6635
6636					<fieldset id='{$elid}-list'>
6637						<legend class='{$legend_class}'>".$options['legend']."</legend>
6638						".vartrue($options['table_pre'])."
6639						<div class='row admingrid ' id='{$elid}-list-grid'>
6640						";
6641
6642
6643			if(!$tree)
6644			{
6645				$text .= "</div>";
6646				$text .= "<div id='admin-ui-list-no-records-found' class=' alert alert-block alert-info center middle'>".LAN_NO_RECORDS_FOUND."</div>"; // not prone to column-count issues.
6647			}
6648			else
6649			{
6650
6651
6652				if(empty($options['grid']['template']))
6653				{
6654					$template = '<div class="panel panel-default">
6655					<div class="e-overlay" >{IMAGE}
6656						<div class="e-overlay-content">
6657						{OPTIONS}
6658						</div>
6659					</div>
6660					<div class="panel-footer">{TITLE}<span class="pull-right">{CHECKBOX}</span></div>
6661					</div>';
6662				}
6663				else
6664				{
6665					$template = $options['grid']['template'];
6666				}
6667
6668
6669				$cls        = !empty($options['grid']['class']) ? $options['grid']['class'] : 'col-md-2';
6670				$pid        = $options['pid'];
6671				$perPage    = $options['grid']['perPage'];
6672
6673
6674
6675				$gridFields =  $options['grid'];
6676				$gridFields['options'] = 'options';
6677				$gridFields['checkbox'] = 'checkboxes';
6678
6679				unset($gridFields['class'],$gridFields['perPage'], $gridFields['carousel']);
6680
6681				$cells = array();
6682				foreach($tree as $model)
6683				{
6684					e107::setRegistry('core/adminUI/currentListModel', $model);
6685
6686					$data = $model->getData();
6687
6688					$id   = $data[$pid];
6689					$vars = array();
6690
6691					foreach($gridFields as $k=>$v)
6692					{
6693						$key = strtoupper($k);
6694						$fields[$v]['grid'] = true;
6695						$vars[$key] = $this->renderValue($v,$data[$v],$fields[$v],$id);
6696					}
6697
6698					$cells[] = "<div class='".$cls." admin-ui-grid'>". $tp->simpleParse($template,$vars). "</div>";
6699
6700				}
6701
6702
6703				if($options['grid']['carousel'] === true)
6704				{
6705					$slides         = $this->slides($cells, $perPage);
6706					$carouselData   = $this->carousel('admin-ui-carousel',$slides, array('wrap'=>false, 'interval'=>false, 'data'=>true));
6707
6708					$text .= $carouselData['start'].$carouselData['inner'].$carouselData['end'];
6709
6710				}
6711				else
6712				{
6713					$text .= implode("\n",$cells);
6714				}
6715
6716
6717				e107::setRegistry('core/adminUI/currentListModel', null);
6718
6719				$text .= "</div>
6720				<div class='clearfix'></div>";
6721			}
6722
6723
6724			$text .= vartrue($options['table_post']);
6725
6726
6727			if($tree && $amount)
6728			{
6729				// New nextprev SC parameters
6730				$parms = 'total='.$total;
6731				$parms .= '&amount='.$amount;
6732				$parms .= '&current='.$from;
6733
6734				if(ADMIN_AREA)
6735				{
6736					$parms .= '&tmpl_prefix=admin';
6737				}
6738
6739				// NOTE - the whole url is double encoded - reason is to not break parms query string
6740				// 'np_query' should be proper (urlencode'd) url query string
6741				$url = rawurlencode($url.'?'.(varset($options['np_query']) ? str_replace(array('&amp;', '&'), array('&', '&amp;'),  $options['np_query']).'&amp;' : '').'from=[FROM]');
6742				$parms .= '&url='.$url;
6743				//$parms = $total.",".$amount.",".$from.",".$url.'?'.($options['np_query'] ? $options['np_query'].'&amp;' : '').'from=[FROM]';
6744		    	//$text .= $tp->parseTemplate("{NEXTPREV={$parms}}");
6745				$nextprev = $tp->parseTemplate("{NEXTPREV={$parms}}");
6746				if ($nextprev)
6747				{
6748					$text .= "<div class='nextprev-bar'>".$nextprev."</div>";
6749				}
6750			}
6751
6752			$text .= "
6753					</fieldset>
6754					".vartrue($options['fieldset_post'])."
6755				</div>
6756				</form>
6757			";
6758		}
6759		if(!$nocontainer)
6760		{
6761			$class = deftrue('e_IFRAME') ? 'e-container e-container-modal' : 'e-container';
6762			$text = '<div class="'.$class.'">'.$text.'</div>';
6763		}
6764		return (vartrue($options['form_pre']).$text.vartrue($options['form_post']));
6765	}
6766
6767	/**
6768	 * Generic DB Record Management Form.
6769	 * TODO - lans
6770	 * TODO - move fieldset & table generation in separate methods, needed for ajax calls
6771	 * Expected arrays format:
6772	 * <code>
6773	 * <?php
6774	 * $forms[0] = array(
6775	 * 		'id'  => 'myplugin',
6776	 * 		'url' => '{e_PLUGIN}myplug/admin_config.php', //if not set, e_SELF is used
6777	 * 		'query' => 'mode=main&amp;action=edit&id=1', //or e_QUERY if not set
6778	 * 		'tabs' => true, 	 *
6779	 *      'fieldsets' => array(
6780	 * 			'general' => array(
6781	 * 				'legend' => 'Fieldset Legend',
6782	 * 				'fields' => array(...), //see e_admin_ui::$fields
6783	 * 				'after_submit_options' => array('action' => 'Label'[,...]), // or true for default redirect options
6784	 * 				'after_submit_default' => 'action_name',
6785	 * 				'triggers' => 'auto', // standard create/update-cancel triggers
6786	 * 				//or custom trigger array in format array('sibmit' => array('Title', 'create', '1'), 'cancel') - trigger name - title, action, optional hidden value (in this case named sibmit_value)
6787	 * 			),
6788	 *
6789	 * 			'advanced' => array(
6790	 * 				'legend' => 'Fieldset Legend',
6791	 * 				'fields' => array(...), //see e_admin_ui::$fields
6792	 * 				'after_submit_options' => array('__default' => 'action_name' 'action' => 'Label'[,...]), // or true for default redirect options
6793	 * 				'triggers' => 'auto', // standard create/update-cancel triggers
6794	 * 				//or custom trigger array in format array('submit' => array('Title', 'create', '1'), 'cancel' => array('cancel', 'cancel')) - trigger name - title, action, optional hidden value (in this case named sibmit_value)
6795	 * 			)
6796	 * 		)
6797	 * );
6798	 * $models[0] = new e_admin_model($data);
6799	 * $models[0]->setFieldIdName('primary_id'); // you need to do it if you don't use your own admin model extension
6800	 * </code>
6801	 * @param array $forms numerical array
6802	 * @param array $models numerical array with values instance of e_admin_model
6803	 * @param boolean $nocontainer don't enclose in div container
6804	 * @return string
6805	 */
6806	function renderCreateForm($forms, $models, $nocontainer = false)
6807	{
6808		$text = '';
6809		foreach ($forms as $fid => $form)
6810		{
6811			$model = $models[$fid];
6812
6813			e107::setRegistry('core/adminUI/currentModel', $model);
6814
6815			if(!is_object($model))
6816			{
6817				e107::getDebug()->log("No model object found with key ".$fid);
6818			}
6819
6820			$query = isset($form['query']) ? $form['query'] : e_QUERY ;
6821			$url = (isset($form['url']) ? e107::getParser()->replaceConstants($form['url'], 'abs') : e_SELF).($query ? '?'.$query : '');
6822			$curTab = strval(varset($_GET['tab'],'0'));
6823
6824			$text .= "
6825				<form method='post' action='".$url."' id='{$form['id']}-form' enctype='multipart/form-data' autocomplete='off' >
6826				<div style='display:none'><input type='text' name='lastname_74758209201093747' autocomplete='off' id='_no_autocomplete_' /></div>
6827				<div id='admin-ui-edit'>
6828				".vartrue($form['header'])."
6829				".$this->token()."
6830			";
6831
6832			foreach ($form['fieldsets'] as $elid => $data)
6833			{
6834				$elid = $form['id'].'-'.$elid;
6835
6836				if(vartrue($data['tabs'])) // Tabs Present
6837				{
6838					$text .= '<ul class="nav nav-tabs">';
6839					foreach($data['tabs'] as $i=>$label)
6840					{
6841						$class = (strval($i) === $curTab) ? 'class="active" ' : '';
6842						$text .= '<li '.$class.'><a href="#tab'.$i.'" data-toggle="tab">'.$label.'</a></li>';
6843					}
6844					$text .= ' </ul><div class="tab-content">';
6845
6846					foreach($data['tabs'] as $tabId=>$label)
6847					{
6848						$active = (strval($tabId) === $curTab) ? 'active' : '';
6849						$text .= '<div class="tab-pane '.$active.'" id="tab'.$tabId.'">';
6850
6851					//	e107::getDebug()->log('elid: '.$elid. " tabid: ".$tabId);
6852					//	e107::getDebug()->log($data);
6853					//	e107::getDebug()->log($model);
6854
6855
6856						$text .= $this->renderCreateFieldset($elid, $data, $model, $tabId);
6857						$text .= "</div>";
6858					}
6859
6860					$text .= "</div>";
6861					$text .= $this->renderCreateButtonsBar( $data, $model->getId());	// Create/Update Buttons etc.
6862
6863				}
6864				else   // No Tabs Present
6865				{
6866					$text .= $this->renderCreateFieldset($elid, $data, $model, false);
6867					$text .= $this->renderCreateButtonsBar( $data, $model->getId());	// Create/Update Buttons etc.
6868				}
6869
6870
6871			}
6872
6873			$text .= "
6874			".vartrue($form['footer'])."
6875			</div>
6876			</form>
6877			";
6878
6879			// e107::js('footer-inline',"Form.focusFirstElement('{$form['id']}-form');",'prototype');
6880			// e107::getJs()->footerInline("Form.focusFirstElement('{$form['id']}-form');");
6881		}
6882		if(!$nocontainer)
6883		{
6884			$class = deftrue('e_IFRAME') ? 'e-container e-container-modal' : 'e-container';
6885			$text = '<div class="'.$class.'">'.$text.'</div>';
6886		}
6887		return $text;
6888	}
6889
6890	/**
6891	 * Create form fieldset, called internal by {@link renderCreateForm())
6892	 *
6893	 * @param string $id field id
6894	 * @param array $fdata fieldset data
6895	 * @param object $model
6896	 * @return string | false
6897	 */
6898	function renderCreateFieldset($id, $fdata, $model, $tab=0)
6899	{
6900
6901
6902		$start = vartrue($fdata['fieldset_pre'])."
6903			<fieldset id='{$id}-".$tab."'>
6904				<legend>".vartrue($fdata['legend'])."</legend>
6905				".vartrue($fdata['table_pre'])."
6906				<table class='table adminform'>
6907					<colgroup>
6908						<col class='col-label' />
6909						<col class='col-control' />
6910					</colgroup>
6911					<tbody>
6912		";
6913
6914		$text = '';
6915
6916		// required fields - model definition
6917		$model_required = $model->getValidationRules();
6918		$required_help = false;
6919		$hidden_fields = array();
6920
6921		foreach($fdata['fields'] as $key => $att)
6922		{
6923			if($tab !== false && varset($att['tab'], 0) !== $tab)
6924			{
6925				continue;
6926			}
6927
6928			// convert aliases - not supported in edit mod
6929			if(vartrue($att['alias']) && !$model->hasData($key))
6930			{
6931				$key = $att['field'];
6932			}
6933
6934			if($key == 'checkboxes' || $key == 'options' || ($att['type'] === null) || ($att['type'] === false))
6935			{
6936				continue;
6937			}
6938
6939			$parms = vartrue($att['formparms'], array());
6940			if(!is_array($parms)) parse_str($parms, $parms);
6941			$label = !empty($att['note']) ? '<div class="label-note">'.deftrue($att['note'], $att['note']).'</div>' : '';
6942			$help = !empty($att['help']) ? '<div class="field-help" data-placement="left">'.deftrue($att['help'], $att['help']).'</div>' : '';
6943
6944
6945			$valPath = trim(vartrue($att['dataPath'], $key), '/');
6946			$keyName = $key;
6947			if(strpos($valPath, '/')) //not TRUE, cause string doesn't start with /
6948			{
6949				$tmp = explode('/', $valPath);
6950				$keyName = array_shift($tmp);
6951				foreach ($tmp as $path)
6952				{
6953					$keyName .= '['.$path.']';
6954				}
6955			}
6956
6957			if(!empty($att['writeParms']) && !is_array($att['writeParms']))
6958			{
6959				 parse_str(varset($att['writeParms']), $writeParms);
6960			}
6961			else
6962			{
6963				 $writeParms = varset($att['writeParms']);
6964			}
6965
6966			if(!empty($writeParms['sef'])) // group sef generate button with input element.
6967			{
6968				if(empty($writeParms['tdClassRight']))
6969				{
6970					$writeParms['tdClassRight'] = 'input-group';
6971
6972				}
6973				else
6974				{
6975					$writeParms['tdClassRight'] .= ' input-group';
6976				}
6977
6978			}
6979
6980			if('hidden' === $att['type'])
6981			{
6982
6983				if(empty($writeParms['show'])) // hidden field and not displayed. Render element after the field-set.
6984				{
6985					$hidden_fields[] = $this->renderElement($keyName, $model->getIfPosted($valPath), $att, varset($model_required[$key], array()));
6986
6987					continue;
6988				}
6989				unset($tmp);
6990			}
6991
6992			// type null - system (special) fields
6993			if(vartrue($att['type']) !== null && !vartrue($att['noedit']) && $key != $model->getFieldIdName())
6994			{
6995				$required = '';
6996				$required_class = '';
6997				if(isset($model_required[$key]) || vartrue($att['validate']) || !empty($att['writeParms']['required']))
6998				{
6999
7000					$required = $this->getRequiredString();
7001					$required_class = ' class="required-label" title="'.LAN_REQUIRED.'"';
7002					$required_help = true;
7003
7004					if(!empty($att['validate']))
7005					{
7006						// override
7007						$model_required[$key] = array();
7008						$model_required[$key][] = true === $att['validate'] ? 'required' : $att['validate'];
7009						$model_required[$key][] = varset($att['rule']);
7010						$model_required[$key][] = $att['title'];
7011						$model_required[$key][] = varset($att['error']);
7012					}
7013				}
7014
7015
7016				if(in_array($key,$this->_field_warnings))
7017				{
7018					if(is_string($writeParms))
7019					{
7020						parse_str($writeParms,$writeParms);
7021					}
7022
7023					$writeParms['tdClassRight'] .=   ' has-warning';
7024
7025				}
7026
7027				$leftCell = "<span{$required_class}>".defset(vartrue($att['title']), vartrue($att['title']))."</span>".$required.$label;
7028				$rightCell = $this->renderElement($keyName, $model->getIfPosted($valPath), $att, varset($model_required[$key], array()), $model->getId())." ".$help;
7029
7030				$att['writeParms'] = $writeParms;
7031
7032				$text .= $this->renderCreateFieldRow($leftCell, $rightCell, $att);
7033
7034
7035
7036			}
7037
7038
7039		}
7040
7041
7042		if(!empty($text) || !empty($hidden_fields))
7043		{
7044			$text = $start.$text;
7045
7046			$text .= "
7047					</tbody>
7048				</table>";
7049
7050			$text .= vartrue($fdata['table_post']);
7051
7052			$text .= implode("\n", $hidden_fields);
7053
7054			$text .= "</fieldset>";
7055
7056			$text .= vartrue($fdata['fieldset_post']);
7057
7058			return $text;
7059		}
7060
7061
7062
7063		return false;
7064
7065
7066	}
7067
7068
7069
7070	/**
7071	 * Render Create/Edit Fieldset Row.
7072	 * @param string $label
7073	 * @param string $control
7074	 * @param array $att
7075	 * @return string
7076	 */
7077	public function renderCreateFieldRow($label, $control, $att = array())
7078	{
7079
7080		$writeParms = $att['writeParms'];
7081
7082		if(vartrue($att['type']) == 'bbarea' || !empty($writeParms['nolabel']))
7083		{
7084			$text = "
7085			<tr>
7086			<td colspan='2'>";
7087
7088			$text .= (isset($writeParms['nolabel']) && $writeParms['nolabel'] == 2) ? '' : "<div style='padding-bottom:8px'>".$label."</div>" ;
7089			$text .= $control."
7090			</td>
7091			</tr>
7092			";
7093
7094			return $text;
7095
7096		}
7097
7098		$leftCellClass  = (!empty($writeParms['tdClassLeft'])) ? " class='".$writeParms['tdClassLeft']."'" : "";
7099		$rightCellClass = (!empty($writeParms['tdClassRight'])) ? " class='".$writeParms['tdClassRight']."'" : "";
7100		$trClass        = (!empty($writeParms['trClass'])) ? " class='".$writeParms['trClass']."'" : "";
7101
7102		$text = "
7103				<tr{$trClass}>
7104					<td{$leftCellClass}>
7105						".$label."
7106					</td>
7107					<td{$rightCellClass}>
7108						".$control."
7109					</td>
7110				</tr>
7111				";
7112
7113		return $text;
7114
7115	}
7116
7117
7118
7119
7120	/**
7121	 * Render the submit buttons in the Create/Edit Form.
7122	 * @param array $fdata - admin-ui data such as $fields, $tabs, $after_submit_options etc.
7123	 * @param int $id Primary ID of the record being edited (only in edit-mode)
7124	 * @return string
7125	 */
7126	public function renderCreateButtonsBar($fdata, $id=null) // XXX Note model and $tab removed as of v2.3
7127	{
7128		$text = "
7129				<div class='buttons-bar center'>
7130		";
7131					// After submit options
7132					$defsubmitopt = array('list' => LAN_EFORM_013, 'create' => LAN_EFORM_014, 'edit' => LAN_EFORM_015);
7133					$submitopt = isset($fdata['after_submit_options']) ? $fdata['after_submit_options'] : true;
7134
7135					if(true === $submitopt)
7136					{
7137						$submitopt = $defsubmitopt;
7138					}
7139
7140					if($submitopt)
7141					{
7142						$selected = isset($fdata['after_submit_default']) && array_key_exists($fdata['after_submit_default'], $submitopt) ? $fdata['after_submit_default'] : 'list';
7143					}
7144
7145					$triggers = (empty($fdata['triggers']) && $fdata['triggers'] !== false) ? 'auto' : $fdata['triggers']; // vartrue($fdata['triggers'], 'auto');
7146
7147					if(is_string($triggers) && 'auto' === $triggers)
7148					{
7149						$triggers = array();
7150						if(!empty($id))
7151						{
7152							$triggers['submit'] = array(LAN_UPDATE, 'update', $id);
7153						}
7154						else
7155						{
7156							$triggers['submit'] = array(LAN_CREATE, 'create', 0);
7157						}
7158
7159						$triggers['cancel'] = array(LAN_CANCEL, 'cancel');
7160					}
7161
7162					if(!empty($triggers))
7163					{
7164						foreach ($triggers as $trigger => $tdata)
7165						{
7166							$text .= ($trigger == 'submit') ? "<div class='etrigger-submit-group btn-group'>" : "";
7167							$text .= $this->admin_button('etrigger_'.$trigger, $tdata[1], $tdata[1], $tdata[0]);
7168
7169							if($trigger == 'submit' && $submitopt)
7170							{
7171
7172								$text .=
7173								'<button class="btn btn-success dropdown-toggle left" data-toggle="dropdown">
7174										<span class="caret"></span>
7175										</button>
7176										<ul class="dropdown-menu col-selection">
7177										<li class="dropdown-header nav-header">'.LAN_EFORM_016.'</li>
7178								';
7179
7180								foreach($submitopt as $k=>$v)
7181								{
7182									$text .= "<li class='after-submit'>".$this->radio('__after_submit_action', $k, $selected == $k, "label=".$v)."</li>";
7183								}
7184
7185								$text .= '</ul>';
7186							}
7187
7188							$text .= ($trigger == 'submit') ?"</div>" : "";
7189
7190							if(isset($tdata[2]))
7191							{
7192								$text .= $this->hidden($trigger.'_value', $tdata[2]);
7193							}
7194						}
7195					}
7196
7197		$text .= "
7198				</div>
7199
7200		";
7201
7202		return $text;
7203	}
7204
7205
7206	/**
7207	 * Generic renderForm solution
7208	 * @param @forms
7209	 * @param @nocontainer
7210	 * @return string
7211	 */
7212	function renderForm($forms, $nocontainer = false)
7213	{
7214		$text = '';
7215		foreach ($forms as $fid => $form)
7216		{
7217			$query = isset($form['query']) ? $form['query'] : e_QUERY ;
7218			$url = (isset($form['url']) ? e107::getParser()->replaceConstants($form['url'], 'abs') : e_SELF).($query ? '?'.$query : '');
7219
7220			$text .= "
7221				".vartrue($form['form_pre'])."
7222				<form method='post' action='".$url."' id='{$form['id']}-form' enctype='multipart/form-data'>
7223				<div>
7224				".vartrue($form['header'])."
7225				".$this->token()."
7226			";
7227
7228			foreach ($form['fieldsets'] as $elid => $fieldset_data)
7229			{
7230				$elid = $form['id'].'-'.$elid;
7231				$text .= $this->renderFieldset($elid, $fieldset_data);
7232			}
7233
7234			$text .= "
7235			".vartrue($form['footer'])."
7236			</div>
7237			</form>
7238			".vartrue($form['form_post'])."
7239			";
7240		}
7241		if(!$nocontainer)
7242		{
7243			$class = deftrue('e_IFRAME') ? 'e-container e-container-modal' : 'e-container';
7244			$text = '<div class="'.$class.'">'.$text.'</div>';
7245		}
7246		return $text;
7247	}
7248
7249    /**
7250     * Generic renderFieldset solution, will be split to renderTable, renderCol/Row/Box etc - Still in use.
7251     */
7252	function renderFieldset($id, $fdata)
7253	{
7254		$colgroup = '';
7255		if(vartrue($fdata['table_colgroup']))
7256		{
7257			$colgroup = "
7258				<colgroup span='".count($fdata['table_colgroup'])."'>
7259			";
7260			foreach ($fdata['table_colgroup'] as $i => $colgr)
7261			{
7262				$colgroup .= "<col ";
7263				foreach ($colgr as $attr => $v)
7264				{
7265					$colgroup .= "{$attr}='{$v}'";
7266				}
7267				$colgroup .= " />
7268				";
7269			}
7270
7271			$colgroup = "</colgroup>
7272			";
7273		}
7274		$text = vartrue($fdata['fieldset_pre'])."
7275			<fieldset id='{$id}'>
7276				<legend>".vartrue($fdata['legend'])."</legend>
7277				".vartrue($fdata['table_pre'])."
7278
7279		";
7280
7281		if(vartrue($fdata['table_rows']) || vartrue($fdata['table_body']))
7282		{
7283			$text .= "
7284				<table class='table adminform'>
7285					{$colgroup}
7286					<thead>
7287						".vartrue($fdata['table_head'])."
7288					</thead>
7289					<tbody>
7290			";
7291
7292			if(vartrue($fdata['table_rows']))
7293			{
7294				foreach($fdata['table_rows'] as $index => $row)
7295				{
7296					$text .= "
7297						<tr id='{$id}-{$index}'>
7298							$row
7299						</tr>
7300					";
7301				}
7302			}
7303			elseif(vartrue($fdata['table_body']))
7304			{
7305				$text .= $fdata['table_body'];
7306			}
7307
7308			if(vartrue($fdata['table_note']))
7309			{
7310				$note = '<div class="form-note">'.$fdata['table_note'].'</div>';
7311			}
7312
7313			$text .= "
7314						</tbody>
7315					</table>
7316					".$note."
7317					".vartrue($fdata['table_post'])."
7318			";
7319		}
7320
7321		$triggers = vartrue($fdata['triggers'], array());
7322		if($triggers)
7323		{
7324			$text .= "<div class='buttons-bar center'>
7325				".vartrue($fdata['pre_triggers'], '')."
7326			";
7327			foreach ($triggers as $trigger => $tdata)
7328			{
7329				if(is_string($tdata))
7330				{
7331					$text .= $tdata;
7332					continue;
7333				}
7334				$text .= $this->admin_button('etrigger_'.$trigger, $tdata[0], $tdata[1]);
7335				if(isset($tdata[2]))
7336				{
7337					$text .= $this->hidden($trigger.'_value', $tdata[2]);
7338				}
7339			}
7340			$text .= "</div>";
7341		}
7342
7343		$text .= "
7344			</fieldset>
7345			".vartrue($fdata['fieldset_post'])."
7346		";
7347		return $text;
7348	}
7349
7350	/**
7351	 * Render Value Trigger - override to modify field/value/parameters
7352	 * @param string $field field name
7353	 * @param mixed $value field value
7354	 * @param array $params 'writeParams' key (see $controller->fields array)
7355	 * @param int $id record ID
7356	 */
7357	public function renderValueTrigger(&$field, &$value, &$params, $id)
7358	{
7359
7360	}
7361
7362	/**
7363	 * Render Element Trigger - override to modify field/value/parameters/validation data
7364	 * @param string $field field name
7365	 * @param mixed $value field value
7366	 * @param array $params 'writeParams' key (see $controller->fields array)
7367	 * @param array $required_data validation data
7368	 * @param int $id record ID
7369	 */
7370	public function renderElementTrigger(&$field, &$value, &$params, &$required_data, $id)
7371	{
7372
7373	}
7374}
7375
7376// DEPRECATED - use above methods instead ($frm)
7377class form
7378{
7379	function form_open($form_method, $form_action, $form_name = "", $form_target = "", $form_enctype = "", $form_js = "")
7380	{
7381		$method = ($form_method ? "method='".$form_method."'" : "");
7382		$target = ($form_target ? " target='".$form_target."'" : "");
7383		$name = ($form_name ? " id='".$form_name."' " : " id='myform'");
7384		return "\n<form action='".$form_action."' ".$method.$target.$name.$form_enctype.$form_js."><div>".e107::getForm()->token()."</div>";
7385	}
7386
7387	function form_text($form_name, $form_size, $form_value, $form_maxlength = FALSE, $form_class = "tbox form-control", $form_readonly = "", $form_tooltip = "", $form_js = "") {
7388		$name = ($form_name ? " id='".$form_name."' name='".$form_name."'" : "");
7389		$value = (isset($form_value) ? " value='".$form_value."'" : "");
7390		$size = ($form_size ? " size='".$form_size."'" : "");
7391		$maxlength = ($form_maxlength ? " maxlength='".$form_maxlength."'" : "");
7392		$readonly = ($form_readonly ? " readonly='readonly'" : "");
7393		$tooltip = ($form_tooltip ? " title='".$form_tooltip."'" : "");
7394		return "\n<input class='".$form_class."' type='text' ".$name.$value.$size.$maxlength.$readonly.$tooltip.$form_js." />";
7395	}
7396
7397	function form_password($form_name, $form_size, $form_value, $form_maxlength = FALSE, $form_class = "tbox form-control", $form_readonly = "", $form_tooltip = "", $form_js = "") {
7398		$name = ($form_name ? " id='".$form_name."' name='".$form_name."'" : "");
7399		$value = (isset($form_value) ? " value='".$form_value."'" : "");
7400		$size = ($form_size ? " size='".$form_size."'" : "");
7401		$maxlength = ($form_maxlength ? " maxlength='".$form_maxlength."'" : "");
7402		$readonly = ($form_readonly ? " readonly='readonly'" : "");
7403		$tooltip = ($form_tooltip ? " title='".$form_tooltip."'" : "");
7404		return "\n<input class='".$form_class."' type='password' ".$name.$value.$size.$maxlength.$readonly.$tooltip.$form_js." />";
7405	}
7406
7407	function form_button($form_type, $form_name, $form_value, $form_js = "", $form_image = "", $form_tooltip = "") {
7408		$name = ($form_name ? " id='".$form_name."' name='".$form_name."'" : "");
7409		$image = ($form_image ? " src='".$form_image."' " : "");
7410		$tooltip = ($form_tooltip ? " title='".$form_tooltip."' " : "");
7411		return "\n<input class='btn btn-default btn-secondary button' type='".$form_type."' ".$form_js." value='".$form_value."'".$name.$image.$tooltip." />";
7412	}
7413
7414	function form_textarea($form_name, $form_columns, $form_rows, $form_value, $form_js = "", $form_style = "", $form_wrap = "", $form_readonly = "", $form_tooltip = "") {
7415		$name = ($form_name ? " id='".$form_name."' name='".$form_name."'" : "");
7416		$readonly = ($form_readonly ? " readonly='readonly'" : "");
7417		$tooltip = ($form_tooltip ? " title='".$form_tooltip."'" : "");
7418		$wrap = ($form_wrap ? " wrap='".$form_wrap."'" : "");
7419		$style = ($form_style ? " style='".$form_style."'" : "");
7420		return "\n<textarea class='tbox form-control' cols='".$form_columns."' rows='".$form_rows."' ".$name.$form_js.$style.$wrap.$readonly.$tooltip.">".$form_value."</textarea>";
7421	}
7422
7423	function form_checkbox($form_name, $form_value, $form_checked = 0, $form_tooltip = "", $form_js = "") {
7424		$name = ($form_name ? " id='".$form_name.$form_value."' name='".$form_name."'" : "");
7425		$checked = ($form_checked ? " checked='checked'" : "");
7426		$tooltip = ($form_tooltip ? " title='".$form_tooltip."'" : "");
7427		return "\n<input type='checkbox' value='".$form_value."'".$name.$checked.$tooltip.$form_js." />";
7428
7429	}
7430
7431	function form_radio($form_name, $form_value, $form_checked = 0, $form_tooltip = "", $form_js = "") {
7432		$name = ($form_name ? " id='".$form_name.$form_value."' name='".$form_name."'" : "");
7433		$checked = ($form_checked ? " checked='checked'" : "");
7434		$tooltip = ($form_tooltip ? " title='".$form_tooltip."'" : "");
7435		return "\n<input type='radio' value='".$form_value."'".$name.$checked.$tooltip.$form_js." />";
7436
7437	}
7438
7439	function form_file($form_name, $form_size, $form_tooltip = "", $form_js = "") {
7440		$name = ($form_name ? " id='".$form_name."' name='".$form_name."'" : "");
7441		$tooltip = ($form_tooltip ? " title='".$form_tooltip."'" : "");
7442		return "<input type='file' class='tbox' size='".$form_size."'".$name.$tooltip.$form_js." />";
7443	}
7444
7445	function form_select_open($form_name, $form_js = "") {
7446		return "\n<select id='".$form_name."' name='".$form_name."' class='tbox form-control' ".$form_js." >";
7447	}
7448
7449	function form_select_close() {
7450		return "\n</select>";
7451	}
7452
7453	function form_option($form_option, $form_selected = "", $form_value = "", $form_js = "") {
7454		$value = ($form_value !== FALSE ? " value='".$form_value."'" : "");
7455		$selected = ($form_selected ? " selected='selected'" : "");
7456		return "\n<option".$value.$selected." ".$form_js.">".$form_option."</option>";
7457	}
7458
7459	function form_hidden($form_name, $form_value) {
7460		return "\n<input type='hidden' id='".$form_name."' name='".$form_name."' value='".$form_value."' />";
7461	}
7462
7463	function form_close() {
7464		return "\n</form>";
7465	}
7466}
7467
7468/*
7469Usage
7470echo $rs->form_open("post", e_SELF, "_blank");
7471echo $rs->form_text("testname", 100, "this is the value", 100, 0, "tooltip");
7472echo $rs->form_button("submit", "testsubmit", "SUBMIT!", "", "Click to submit");
7473echo $rs->form_button("reset", "testreset", "RESET!", "", "Click to reset");
7474echo $rs->form_textarea("textareaname", 10, 10, "Value", "overflow:hidden");
7475echo $rs->form_checkbox("testcheckbox", 1, 1);
7476echo $rs->form_checkbox("testcheckbox2", 2);
7477echo $rs->form_hidden("hiddenname", "hiddenvalue");
7478echo $rs->form_radio("testcheckbox", 1, 1);
7479echo $rs->form_radio("testcheckbox", 1);
7480echo $rs->form_file("testfile", "20");
7481echo $rs->form_select_open("testselect");
7482echo $rs->form_option("Option 1");
7483echo $rs->form_option("Option 2");
7484echo $rs->form_option("Option 3", 1, "defaultvalue");
7485echo $rs->form_option("Option 4");
7486echo $rs->form_select_close();
7487echo $rs->form_close();
7488*/
7489
7490
7491
7492