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* Extended user field handler
10*
11*/
12
13if (!defined('e107_INIT')) { exit; }
14
15/**
16 *	Extended user field handler
17 *
18 *	@todo: - change some routines to access the cached variables rather than DB
19 *	@todo: Remove setting up of _FIELD_TYPES array (may be necessary, since UEF data structure not fixed)
20 *	@todo: Consider changing field type constants to class constants
21 *	@todo - cache field structure (already done in a different way in e107::user() in class2.php line 1387 or so)
22 *	@todo - class variables - confirm whether public/protected assignments are correct
23 *	@todo - consider whether to split system and non-system fields
24
25Code uses two tables:
26	user_extended_struct - individual field definitions, one record per field
27	user_extended - actual field data, one record per user
28
29@todo: Should user_extended_validate_entry() check DB for DB-type fields?
30
31*/
32
33e107::coreLan('user_extended');
34
35class e107_user_extended
36{
37	public $user_extended_types;	// Text description corresponding to each field type
38	private $extended_xml 		= FALSE;
39	public $typeArray;				// Cross-reference between names of field types, and numeric ID (must be public)
40	private $reserved_names;		// List of field names used in main user DB - not allowed in extended DB
41	public $fieldDefinitions    = array();	// Array initialised from DB by constructor - currently all fields
42	public $catDefinitions;			// Categories
43	private $nameIndex          = array();	// Array for field name lookup - initialised by constructor
44	public $systemCount         = 0;		// Count of system fields - always zero ATM
45	public $userCount           = 0;			// Count of non-system fields
46	private $fieldAttributes   	= array(); // Field Permissionss with field name as key.
47
48	public function __construct()
49	{
50		@define('EUF_CATEGORY', 0);
51		@define('EUF_TEXT',1);
52		@define('EUF_RADIO',2);
53		@define('EUF_DROPDOWN',3);
54		@define('EUF_DB_FIELD',4);
55		@define('EUF_TEXTAREA',5);
56		@define('EUF_INTEGER',6);
57		@define('EUF_DATE',7);
58		@define('EUF_LANGUAGE',8);
59		@define('EUF_PREDEFINED',9); // should be EUF_LIST IMO
60		@define('EUF_CHECKBOX',10);
61		@define('EUF_PREFIELD',11); // should be EUF_PREDEFINED, useful when creating fields from e.g. plugin XML
62		@define('EUF_ADDON', 12);  // defined within e_user.php addon @todo
63		@define('EUF_COUNTRY', 13);  // $frm->country()
64		@define('EUF_RICHTEXTAREA', 14); // $frm->bbarea()
65
66		$this->typeArray = array(
67			'text'          => EUF_TEXT,
68			'radio'         => EUF_RADIO,
69			'dropdown'      => EUF_DROPDOWN,
70			'db field'      => EUF_DB_FIELD,
71			'textarea'      => EUF_TEXTAREA,
72			'integer'       => EUF_INTEGER,
73			'date'          => EUF_DATE,
74			'language'      => EUF_LANGUAGE,
75			'list'          => EUF_PREDEFINED,
76			'checkbox'	    => EUF_CHECKBOX,
77			'predefined'    => EUF_PREFIELD, // DON'T USE IT IN PREDEFINED FIELD XML!!! Used in plugin installation routine.
78			'addon'         => EUF_ADDON,
79			'country'       => EUF_COUNTRY,
80			'richtextarea' 	=> EUF_RICHTEXTAREA,
81		);
82
83		$this->user_extended_types = array(
84			1 	=> UE_LAN_1,
85			2 	=> UE_LAN_2,
86			3 	=> UE_LAN_3,
87			4 	=> UE_LAN_4,
88			5 	=> UE_LAN_5,
89			14 	=> UE_LAN_14,
90			6 	=> UE_LAN_6,
91			7 	=> LAN_DATE,
92			8 	=> UE_LAN_8,
93			9 	=> UE_LAN_9,
94			10 	=> UE_LAN_10,
95			13 	=> UE_LAN_13,
96		//	12=> UE_LAN_10
97
98		);
99
100		//load array with field names from main user table, so we can disallow these
101		// user_new, user_timezone deleted for 0.8
102		$this->reserved_names = array (
103		'id', 'name', 'loginname', 'customtitle', 'password',
104		'sess', 'email', 'signature', 'image', 'hideemail',
105		'join', 'lastvisit', 'currentvisit', 'chats',
106		'comments', 'forums', 'ip', 'ban', 'prefs', 'viewed',
107		'visits', 'admin', 'login', 'class', 'baseclasslist', 'perms', 'pwchange',
108		'xup'
109		);
110
111		$this->init();
112
113	}
114
115	public function init()
116	{
117		$sql = e107::getDb();
118
119		// Read in all the field and category fields
120		// At present we load all fields into common array - may want to split system and non-system
121		$this->catDefinitions = array();		// Categories array
122		$this->nameIndex = array();				// Index of names => field IDs
123		$this->systemCount = 0;
124		$this->userCount = 0;
125
126		if($sql->select('user_extended_struct', '*', "user_extended_struct_text != '_system_' ORDER BY user_extended_struct_order ASC"))
127		{
128			while($row = $sql->fetch())
129			{
130				if ($row['user_extended_struct_type'] == 0)
131				{	// Its a category
132					$this->catDefinitions[$row['user_extended_struct_id']] = $row;
133				}
134				else
135				{	// Its a field definition
136					$this->fieldDefinitions[$row['user_extended_struct_id']] = $row;
137					$id = 'user_' . $row['user_extended_struct_name'];
138
139					$this->fieldAttributes[$id] = array(
140							'read' => $row['user_extended_struct_read'],
141							'write' => $row['user_extended_struct_write'],
142							'type'  => $row['user_extended_struct_type']
143					);
144					$this->nameIndex['user_' . $row['user_extended_struct_name']] = $row['user_extended_struct_id'];            // Create name to ID index
145
146					if($row['user_extended_struct_text'] == '_system_')
147					{
148						$this->systemCount++;
149					}
150					else
151					{
152						$this->userCount++;
153					}
154				}
155			}
156		}
157
158		return null;
159	}
160
161
162
163
164
165	/**
166	 * Check read/write access on extended user-fields
167	 * @param string $field eg. user_something
168	 * @param string $type read|write
169	 * @return boolean true if
170	 */
171	public function hasPermission($field, $type='read')
172	{
173		$class = ($type == 'read') ? $this->fieldAttributes[$field]['read'] : $this->fieldAttributes[$field]['write'];
174		return check_class($class);
175	}
176
177
178
179	/**
180	 *	Check for reserved field names.
181	 *	(Names which clash with the 'normal' user table aren't allowed)
182	 *	@param array $name - name of field bweing checked (no 'user_' prefix)
183	 *	@return boolean TRUE if disallowed name
184	 */
185	public function user_extended_reserved($name)
186	{
187		return (in_array($name, $this->reserved_names));
188	}
189
190
191
192	// Adds the _FIELD_TYPES array to the data, ready for saving in the DB.
193	function addFieldTypes(&$target)
194	{
195		$target['_FIELD_TYPES'] = array();		// We should always want to recreate the array, even if it exists
196		foreach ($target['data'] as $k => $v)
197		{
198			if (isset($this->nameIndex[$k]))
199			{
200				switch ($this->fieldDefinitions[$this->nameIndex[$k]]['user_extended_struct_type'])
201				{
202					case EUF_TEXT :
203					case EUF_DB_FIELD :
204					case EUF_TEXTAREA :
205					case EUF_RICHTEXTAREA :
206					case EUF_DROPDOWN :
207					case EUF_DATE :
208					case EUF_LANGUAGE :
209					case EUF_PREDEFINED :
210
211					case EUF_RADIO :
212						$target['_FIELD_TYPES'][$k] = 'todb';
213						break;
214
215					case EUF_CHECKBOX :
216						$target['_FIELD_TYPES'][$k] = 'array';
217						break;
218
219
220					case EUF_INTEGER :
221						$target['_FIELD_TYPES'][$k] = 'int';
222						break;
223				}
224			}
225		}
226	}
227
228
229
230	/**
231	 * For all UEFs not in the target array, adds the default value
232	 * Also updates the _FIELD_TYPES array, so call this last thing before writing to the DB
233	 *
234	 *	@param $target - pointer to data array
235	 */
236	public function addDefaultFields(&$target)
237	{
238		//$target['_FIELD_TYPES'] = array();		// We should always want to recreate the array, even if it exists
239		foreach ($this->fieldDefinitions as $k => $defs)
240		{
241			$f = 'user_'.$defs['user_extended_struct_name'];
242			if (!isset($target['data'][$f]) && $this->fieldDefinitions[$k]['user_extended_struct_default'])
243			{
244				switch ($this->fieldDefinitions[$k]['user_extended_struct_type'])
245				{
246					case EUF_TEXT :
247					case EUF_DB_FIELD :
248					case EUF_TEXTAREA :
249					case EUF_RICHTEXTAREA :
250					case EUF_DROPDOWN :
251					case EUF_DATE :
252					case EUF_LANGUAGE :
253					case EUF_PREDEFINED :
254
255						$target['data'][$f] = $this->fieldDefinitions[$k]['user_extended_struct_default'];
256						$target['_FIELD_TYPES'][$f] = 'todb';
257						break;
258					case EUF_RADIO :
259					case EUF_INTEGER :
260						$target['data'][$f] = $this->fieldDefinitions[$k]['user_extended_struct_default'];
261						$target['_FIELD_TYPES'][$f] = 'int';
262						break;
263					case EUF_CHECKBOX :
264                    	$target['data'][$f] = $this->fieldDefinitions[$k]['user_extended_struct_default'];
265						$target['_FIELD_TYPES'][$f] = 'array';
266						break;
267				}
268			}
269		}
270	}
271
272
273
274	// Validate a single extended user field
275	// $val is whatever the user entered.
276	// $params is the field definition
277	// Return FALSE if acceptable, TRUE if fail , error message on regex fail if the message is defined
278	function user_extended_validate_entry($val, $params)
279	{
280		$tp = e107::getParser();
281
282		$parms = explode('^,^', $params['user_extended_struct_parms']);
283		$requiredField = $params['user_extended_struct_required'] == 1;
284		$regex = $tp->toText($parms[1]);
285		$regexfail = $tp->toText($parms[2]);
286		if(defined($regexfail))
287		{
288			$regexfail = constant($regexfail);
289		}
290		if($val == '' && $requiredField)
291		{
292			return true;
293		}
294
295		$type = $params['user_extended_struct_type'];
296
297		switch($type)
298		{
299			case EUF_DATE :
300				if($requiredField && ($val == '0000-00-00'))
301				{
302					return true;
303				}
304				break;
305		}
306		if($regex != "" && $val != "")
307		{
308			if(!preg_match($regex, $val))
309			{
310				return $regexfail ? $regexfail : true;
311			}
312		}
313
314		return false;            // Pass by default here
315	}
316
317
318
319	/**
320	 * Validate all user-modifable extended user fields which are presented.
321	 *	Primarily intended to validate data entered by a user or admin
322	 *
323	 * @param array $inArray is the input data (usually from $_POST or $_POST['ue'], although doesn't have to be) - may have 'surplus' values
324	 * @param array $hideArray is a set of possible 'hide' flags
325	 * @param boolean $isSignup TRUE causes required fields to be specifically checked, else only data passed is checked
326	 *
327	 *	@return array with three potential subkeys:
328	 *		'data' - valid data values (key is field name)
329	 *			['data']['user_hidden_fields'] is the hidden fields
330	 *		'errors' - data values in error
331	 *		'errortext' - error message corresponding to erroneous value
332	 *
333	 *	@todo - does $hidden_fields need to be merged with values for fields not processed? (Probably not - should only relate to fields current user can see)
334	 *	@todo - make sure admin can edit fields of other users
335	 */
336	public function userExtendedValidateAll($inArray, $hideArray, $isSignup=FALSE)
337	{
338		$tp = e107::getParser();
339
340		$eufVals = array();		// 'Answer' array
341		$hideFlags = array();
342
343		foreach ($this->fieldDefinitions as $k => $defs)
344		{
345			$category = $defs['user_extended_struct_parent'];
346			if (($category == 0) || ($isSignup && (int) $this->catDefinitions[$category]['user_extended_struct_applicable'] === (int) e_UC_MEMBER && (int) $this->catDefinitions[$category]['user_extended_struct_write'] === (int) e_UC_MEMBER) || (check_class($this->catDefinitions[$category]['user_extended_struct_applicable']) && check_class($this->catDefinitions[$category]['user_extended_struct_write'])))
347			{	// Category applicable to user
348
349				if (($isSignup && (int) $defs['user_extended_struct_applicable'] === (int) e_UC_MEMBER && (int) $defs['user_extended_struct_write'] === (int) e_UC_MEMBER) || (check_class($defs['user_extended_struct_applicable']) && check_class($defs['user_extended_struct_write'])))
350				{	// User can also update field
351					$f = 'user_'.$defs['user_extended_struct_name'];
352					if (isset($inArray[$f]) || ($isSignup && ($defs['user_extended_struct_required'] == 1)))
353					{	// Only allow valid keys
354						$val = varset($inArray[$f], FALSE);
355						$err = $this->user_extended_validate_entry($val, $defs);
356						if ($err === true)
357						{  // General error - usually empty field; could be unacceptable value, or regex fail and no error message defined
358							$eufVals['errortext'][$f] = str_replace('[x]',$tp->toHTML(defset($defs['user_extended_struct_text'], $defs['user_extended_struct_text']),FALSE,'defs'),LAN_USER_75);
359							$eufVals['errors'][$f] = ERR_GENERIC;
360						}
361						elseif ($err)
362						{	// Specific error message returned - usually regex fail
363							$eufVals['errortext'][$f] = $err;
364							$eufVals['errors'][$f] = ERR_GENERIC;
365						}
366						elseif (!$err)
367						{
368							$eufVals['data'][$f] = $tp->toDB($val);
369						}
370						if (isset($hideArray[$f]))
371						{
372							$hideFlags[] = $f;
373						}
374					}
375				}
376			}
377		}
378		$hidden_fields = implode('^', $hideFlags);
379		if ($hidden_fields != '')
380		{
381			$hidden_fields = '^'.$hidden_fields.'^';
382		}
383		$eufVals['data']['user_hidden_fields'] = $hidden_fields;
384
385		return $eufVals;
386	}
387
388
389	/**
390	 * Sanitize User submitted user-extended fields.
391	 * @param $posted
392	 * @return array
393	 */
394	function sanitizeAll($posted)
395	{
396
397		$arr = array();
398
399		foreach($posted as $field => $value)
400		{
401			$type = $this->getFieldType($field);
402
403			switch($type)
404			{
405
406				case EUF_INTEGER :  //integer
407					$arr[$field] = (int) $value;
408			    break;
409
410				case EUF_TEXT :  //textbox
411				case EUF_COUNTRY:
412				case EUF_RADIO : //radio
413				case EUF_CHECKBOX : //checkboxes
414				case EUF_DROPDOWN : //dropdown
415				case EUF_PREDEFINED : // predefined list, shown in dropdown
416				case EUF_DB_FIELD : //db_field
417				case EUF_DATE : //date
418				case EUF_LANGUAGE : // language
419				case EUF_TEXTAREA : //textarea
420				case EUF_PREFIELD:
421				case EUF_ADDON:
422
423					$arr[$field] = filter_var($value,FILTER_SANITIZE_STRING);
424			    break;
425
426				case EUF_RICHTEXTAREA : // rich textarea (using WYSIWYG editor)
427					$arr[$field] = e107::getParser()->cleanHtml($value);
428				break;
429
430				default:
431					e107::getDebug()->log("User extended field: ".$field." is missing a valid field-type.");
432
433			}
434
435
436		}
437
438
439		return $arr;
440
441
442	}
443
444
445
446	/**
447	 * alias of user_extended_get_categories();
448	 *
449	 * @param bool $byID
450	 * @return array
451	 */
452	function getCategories($byID = TRUE)
453	{
454		return $this->user_extended_get_categories($byID);
455	}
456
457
458	function user_extended_get_categories($byID = TRUE)
459	{
460	   	$ret = array();
461		$sql = e107::getDb('ue');
462
463		if($sql->select("user_extended_struct", "*", "user_extended_struct_type = 0 ORDER BY user_extended_struct_order ASC"))
464		{
465			if($byID == TRUE)
466			{
467				while($row = $sql->fetch())
468				{
469					$ret[$row['user_extended_struct_id']][] = $row;
470				}
471			}
472			else
473			{
474				$ret = $sql->db_getList();
475			}
476		}
477		return $ret;
478	}
479
480
481	/**
482	 * BC Alias of getFields();
483	 * @param string $cat
484	 * @return mixed
485	 */
486	public function getFields($cat = "")
487	{
488		return $this->user_extended_get_fieldList($cat);
489	}
490
491
492	// Get the definition of all fields, or those in a specific category, grouped by category ID
493	// Reads non-system fields only
494	function user_extended_get_fields($cat = "")
495	{
496		$sql = e107::getDb('ue');
497		$ret = array();
498		$more = ($cat) ? " AND user_extended_struct_parent = ".intval($cat)." " : "";
499		if($sql->select("user_extended_struct", "*", "user_extended_struct_type > 0 AND user_extended_struct_text != '_system_' {$more} ORDER BY user_extended_struct_order ASC"))
500		{
501			while($row = $sql->fetch())
502			{
503				$ret[$row['user_extended_struct_parent']][] = $row;
504			}
505		}
506		return $ret;
507	}
508
509
510	/**
511	 * Alias of user_extended_get_fieldList().
512	 * @param string $cat
513	 * @param string $indexField
514	 * @return mixed
515	 */
516	function getFieldList($cat = "", $indexField = 'user_extended_struct_id')
517	{
518		return $this->user_extended_get_fieldList($cat, $indexField);
519	}
520
521
522	/**
523	 * Get the definition of all fields, or those in a specific category, indexed by field ID (or some other field by specifying $indexField)
524	 * @param $cat
525	 * @param $indexField;
526	 * @param $system - include system fields.
527	 * @return array
528	 */
529	function user_extended_get_fieldList($cat = "", $indexField = 'user_extended_struct_id', $system = false)
530	{
531		if(!$indexField)
532		{
533			 $indexField = 'user_extended_struct_id';
534		}
535
536		$sql = e107::getDb('ue');
537
538		$ret = array();
539
540		$more = ($cat != '') ? " AND user_extended_struct_parent = ".intval($cat)." " : "";
541		$sys = ($system == false) ? " AND user_extended_struct_text != '_system_' " : "";
542
543		if($sql->select("user_extended_struct", "*", "user_extended_struct_type > 0 {$sys} {$more} ORDER BY user_extended_struct_order ASC"))
544		{
545			while($row = $sql->fetch())
546			{
547				$ret[$row[$indexField]] = $row;
548			}
549		}
550
551		return $ret;
552	}
553
554
555	/**
556	 * Return the list of user_extended fields.
557	 * @return array
558	 */
559	function getFieldNames()
560	{
561		$ret = array();
562
563		$sql = e107::getDb('ue');
564
565		if($sql->select("user_extended_struct", "*", "user_extended_struct_type > 0 ORDER BY user_extended_struct_order ASC"))
566		{
567			while($row = $sql->fetch())
568			{
569				$ret[] = 'user_'.$row['user_extended_struct_name'];
570			}
571		}
572		return $ret;
573
574	}
575
576
577	/**
578	 * Get the field-type of a given field-name.
579	 * @param $field
580	 * @return bool|int
581	 */
582	public function getFieldType($field)
583	{
584
585		if(!empty($this->fieldAttributes[$field]['type']))
586		{
587			return (int) $this->fieldAttributes[$field]['type'];
588		}
589
590		return false;
591	}
592
593
594	/**
595	 * Return a list of all field types.
596	 * @return array
597	 */
598	public function getFieldTypes()
599	{
600		return $this->user_extended_types;
601
602	}
603
604
605	// Return the field creation text for a definition
606	/**
607	 * @param $type
608	 * @param $default
609	 * @return bool|string
610	 */
611	function user_extended_type_text($type, $default)
612	{
613	  $tp = e107::getParser();
614
615	  if(!is_numeric($type))
616	  {
617	  	return false;
618	  }
619
620	  switch ($type)
621	  {
622		  case EUF_COUNTRY :
623		  $db_type = 'VARCHAR(2)';
624		  break;
625
626		case EUF_INTEGER :
627		  $db_type = 'INT(11)';
628		  break;
629
630		case EUF_DATE :
631		  $db_type = 'DATE';
632		  break;
633
634		case EUF_TEXTAREA:
635		case EUF_RICHTEXTAREA :
636		case EUF_CHECKBOX :
637		  $db_type = 'TEXT';
638		 break;
639
640		case EUF_TEXT :
641		case EUF_RADIO :
642		case EUF_DROPDOWN :
643		case EUF_DB_FIELD :
644		case EUF_LANGUAGE :
645		case EUF_PREDEFINED :
646
647
648		  $db_type = 'VARCHAR(255)';
649		 break;
650
651		 case EUF_PREFIELD: // FIXME Predefined field - this should be assignable from XML typically.
652		     $db_type = 'VARCHAR(255)';
653		 break;
654
655		case EUF_CATEGORY:
656			return '';
657		 break;
658
659		 case EUF_ADDON:
660		    return 'JSON';
661		 break;
662
663		default:
664			e107::getMessage()->addDebug("<strong>Unknown type '{$type}' for user extended field.</strong>");
665			return false;
666		break;
667
668	  }
669	  if($type != EUF_DB_FIELD && ($type != EUF_TEXTAREA) && ($type != EUF_RICHTEXTAREA) &&  ($type != EUF_CHECKBOX) && !empty($default))
670	  {
671		$default_text = " DEFAULT '".$tp -> toDB($default, true)."'";
672	  }
673	  else
674	  {
675		$default_text = '';
676	  }
677
678
679	  return $db_type.$default_text;
680	}
681
682
683	function user_extended_field_exist($name)
684	{
685	  	$sql = e107::getDb('sql2');
686		$tp = e107::getParser();
687		return $sql->count('user_extended_struct','(*)', "WHERE user_extended_struct_name = '".$tp -> toDB($name, true)."'");
688	}
689
690	function clear_cache()
691	{
692		e107::getCache()->clear_sys('nomd5_extended_struct');
693	}
694
695	// For use by plugins to add extended user fields and won't be visible anywhere else
696	function user_extended_add_system($name, $type, $default = '', $source = '_system_')
697	{
698	  return $this->user_extended_add($name, '_system_', $type, $source, '', $default, 0, 255, 255, 255, 0, 0);
699	}
700
701	function user_extended_add($name, $text='', $type='', $parms='', $values='', $default='', $required='', $read='', $write='', $applicable='', $order='', $parent='')
702	{
703
704		$sql = e107::getDb('ue');
705		$tp = e107::getParser();
706
707		$this->clear_cache();
708
709		if(is_array($name))
710	  	{
711			extract($name);
712		}
713
714	 	if(!is_numeric($type))
715	  	{
716			$type = $this->typeArray[$type];
717	  	}
718
719		if($this->user_extended_field_exist($name) && $sql->field('user_extended', 'user_'.$name)!==false)
720		{
721			return true;
722		}
723
724		if ($this->user_extended_reserved($name))
725		{
726			e107::getMessage()->addDebug("Reserved Field");
727			return false;
728		}
729
730		$field_info = $this->user_extended_type_text($type, $default);
731
732		// wrong type
733		if(false === $field_info)
734		{
735			e107::getMessage()->addDebug("\$field_info is false ".__METHOD__);
736			return false;
737		}
738
739		if($order === '' && $field_info)
740		{
741		  if($sql->select('user_extended_struct','MAX(user_extended_struct_order) as maxorder','1'))
742		  {
743			$row = $sql->fetch();
744			if(is_numeric($row['maxorder']))
745			{
746			  $order = $row['maxorder']+1;
747			}
748		  }
749		}
750		// field of type category
751		if($field_info)
752		{
753			$sql->gen('ALTER TABLE #user_extended ADD user_'.$tp -> toDB($name, true).' '.$field_info);
754		}
755
756		/*	TODO
757			$extStructInsert = array(
758			'user_extended_struct_id'           => '_NULL_',
759			'user_extended_struct_name'         => '',
760			'user_extended_struct_text'         => '',
761			'user_extended_struct_type'         => '',
762			'user_extended_struct_parms'        => '',
763			'user_extended_struct_values'       => '',
764			'user_extended_struct_default'      => '',
765			'user_extended_struct_read'         => '',
766			'user_extended_struct_write'        => '',
767			'user_extended_struct_required'     => '',
768			'user_extended_struct_signup'       => '',
769			'user_extended_struct_applicable'   => '',
770			'user_extended_struct_order'        => '',
771			'user_extended_struct_parent'       => ''
772			);
773		*/
774
775		if(!$this->user_extended_field_exist($name))
776		{
777			$sql->insert('user_extended_struct',"null,'".$tp -> toDB($name, true)."','".$tp -> toDB($text, true)."','".intval($type)."','".$tp -> toDB($parms, true)."','".$tp -> toDB($values, true)."', '".$tp -> toDB($default, true)."', '".intval($read)."', '".intval($write)."', '".intval($required)."', '0', '".intval($applicable)."', '".intval($order)."', '".intval($parent)."'");
778		}
779
780		if($this->user_extended_field_exist($name))
781		{
782		    return true;
783		}
784
785		return false;
786	}
787
788
789
790	function user_extended_modify($id, $name, $text, $type, $parms, $values, $default, $required, $read, $write, $applicable, $parent)
791	{
792		$sql = e107::getDb('ue');
793		$tp = e107::getParser();
794
795		if ($this->user_extended_field_exist($name))
796		{
797			$field_info = $this->user_extended_type_text($type, $default);
798			// wrong type
799			if(false === $field_info) return false;
800
801			// field of type category
802			if($field_info)
803			{
804				$sql->gen("ALTER TABLE #user_extended MODIFY user_".$tp -> toDB($name, true)." ".$field_info);
805			}
806
807			$newfield_info = "
808				user_extended_struct_text = '".$tp -> toDB($text, true)."',
809				user_extended_struct_type = '".intval($type)."',
810				user_extended_struct_parms = '".$tp -> toDB($parms, true)."',
811				user_extended_struct_values = '".$tp -> toDB($values, true)."',
812				user_extended_struct_default = '".$tp -> toDB($default, true)."',
813				user_extended_struct_required = '".intval($required)."',
814				user_extended_struct_read = '".intval($read)."',
815				user_extended_struct_write = '".intval($write)."',
816				user_extended_struct_applicable = '".intval($applicable)."',
817				user_extended_struct_parent = '".intval($parent)."'
818				WHERE user_extended_struct_id = '".intval($id)."'
819			";
820			return $sql->update("user_extended_struct", $newfield_info);
821		}
822
823		return false;
824	}
825
826	function user_extended_remove($id, $name)
827	{
828		$sql = e107::getDb('ue');
829		$tp = e107::getParser();
830
831		$this->clear_cache();
832		if ($this->user_extended_field_exist($name))
833		{
834			// FIXME - no table structure changes for categories
835			// but no good way to detect it right now - ignore the sql error for now, fix it asap
836			$sql->gen("ALTER TABLE #user_extended DROP user_".$tp -> toDB($name, true));
837
838			if(is_numeric($id))
839			{
840				$sql->delete("user_extended_struct", "user_extended_struct_id = '".intval($id)."' ");
841			}
842			else
843			{
844				$sql->delete("user_extended_struct", "user_extended_struct_name = '".$tp -> toDB($id, true)."' ");
845			}
846			return !($this->user_extended_field_exist($name));
847		}
848
849		return false;
850	}
851
852	function user_extended_hide($struct, $curval)
853	{
854		$chk = ($curval) ? " checked='checked' " : "";
855		$name = "hide[user_".$struct['user_extended_struct_name']."]";
856		return "<input type='checkbox' {$chk} value='1' name='{$name}' />&nbsp;".UE_LAN_HIDE;
857	}
858
859
860	/**
861	 * BC alias of renderElement
862	 *
863	 * @param array $struct
864	 * @param mixed $curval
865	 * @return array|string
866	 */
867	function user_extended_edit($struct, $curval)
868	{
869		return $this->renderElement($struct, $curval);
870	}
871
872
873	/**
874	 * @param array $struct
875	 * @param mixed $curval
876     * @param array $opts
877	 * @return array|string
878	 */
879	public function renderElement($struct, $curval, $opts=array())
880	{
881		$tp = e107::getParser();
882		$frm = e107::getForm();
883
884		if(trim($curval) == "" && $struct['user_extended_struct_default'] != "")
885		{
886			$curval = $struct['user_extended_struct_default'];
887		}
888
889		$choices = explode(",",$struct['user_extended_struct_values']);
890
891		foreach($choices as $k => $v)
892		{
893			$choices[$k] = str_replace("[E_COMMA]", ",", $v);
894		}
895
896		$parms 		= explode("^,^",$struct['user_extended_struct_parms']);
897		$include 	= preg_replace("/\n/", " ", $tp->toHTML($parms[0]));
898		// $regex 		= $tp->toText(varset($parms[1]));
899		// $regexfail 	= $tp->toText(varset($parms[2]));
900		$fname 		= "ue[user_".$struct['user_extended_struct_name']."]";
901		$required	= vartrue($struct['user_extended_struct_required']) == 1 ? "required"  : "";
902		$fid		= $frm->name2id($fname);
903		$placeholder = (!empty($parms[4])) ? "placeholder=\"".$tp->toAttribute($parms[4])."\"" : "";
904
905		$class = !empty($opts['class']) ? $opts['class'] : "form-control tbox";
906		$placeholder = !empty($opts['placeholder']) ? "placeholder=\"".$tp->toAttribute($opts['placeholder'])."\"" : $placeholder;
907
908		if(!empty($parms[5]))
909		{
910			$class .= " e-tip";
911			$title = "title=\"".$tp->toAttribute($parms[5])."\"";
912		}
913		else
914		{
915			$title = '';
916		}
917
918		if(strpos($include, 'class') === FALSE)
919		{
920			$include .= " class='".$class."' ";
921		}
922
923		$ret = null;
924
925		switch($struct['user_extended_struct_type'])
926		{
927
928			case EUF_COUNTRY:
929				return e107::getForm()->country($fname,$curval, $opts);
930			break;
931
932
933			case EUF_TEXT :  //textbox
934			case EUF_INTEGER :  //integer
935		 		$ret = "<input id='{$fid}' type='text' name='{$fname}' {$title} value='{$curval}' {$include} {$required} {$placeholder} />";
936
937		  		return $ret;
938		  	break;
939
940			case EUF_RADIO : //radio
941
942				$ret = '';
943
944				foreach($choices as $choice)
945				{
946					$choice = trim($choice);
947					$choice = $tp->toHTML($choice);
948
949					if(strpos($choice,"|")!==FALSE)
950					{
951		            	list($val,$label) = explode("|",$choice);
952					}
953					elseif(strpos($choice," => ")!==FALSE) // new in v2.x
954					{
955		            	list($val,$label) = explode(" => ",$choice);
956					}
957					else
958					{
959		            	$val = $choice;
960						$label = $choice;
961					}
962
963					$label = deftrue($label, $label);
964
965					if(deftrue('BOOTSTRAP'))
966					{
967						$ret .= $frm->radio($fname,$val,($curval == $val),array('label'=>$label, 'required'=> !empty($required)));
968					}
969					else
970					{
971						$chk = ($curval == $val)? " checked='checked' " : "";
972						$ret .= "<input id='{$fid}' {$include} type='radio' name='{$fname}' value='{$val}' {$chk} {$required} /> {$label}";
973					}
974
975				}
976
977				return $ret;
978
979		    break;
980
981	        case EUF_CHECKBOX : //checkboxes
982
983				if(!is_array($curval))
984				{
985					$curval = e107::unserialize($curval);
986				}
987
988				return e107::getForm()->checkboxes($fname.'[]',$choices, $curval, array('useLabelValues'=>1));
989
990			break;
991
992			case EUF_DROPDOWN : //dropdown
993			  $ret = "<select {$include} id='{$fid}' name='{$fname}' {$required} {$title} >\n";
994			  $ret .= "<option value=''>&nbsp;</option>\n";  // ensures that the user chose it.
995			  foreach($choices as $choice)
996			  {
997				$choice = trim($choice);
998				$choice = deftrue($choice, $choice);
999				$sel = ($curval == $choice) ? " selected='selected' " : "";
1000				$ret .= "<option value='{$choice}' {$sel}>{$choice}</option>\n";
1001			  }
1002			  $ret .= "</select>\n";
1003			  return $ret;
1004			  break;
1005
1006			case EUF_PREDEFINED : // predefined list, shown in dropdown
1007				$listRoot = trim($struct['user_extended_struct_values']);			// Base list name
1008				$filename = e_CORE.'sql/extended_'.$listRoot.'.php';
1009				if (!is_readable($filename)) return 'No file: '.$filename;
1010				require_once($filename);
1011				$className = 'extended_'.$listRoot;
1012				if (!class_exists($className)) return '?????';
1013				/** @var extended_timezones $temp */
1014				$temp = new $className();
1015				if (!method_exists($className, 'getValue')) return '???-???';
1016				$temp->pointerReset();
1017
1018				$ret = "<select id='{$fid}' {$include} name='{$fname}' {$required} >\n";
1019				$ret .= "<option value=''>&nbsp;</option>\n";  // ensures that the user chooses it.
1020				while (FALSE !== ($row = $temp->getValue(0, 'next')))
1021				{
1022					$val = key($row);
1023					$choice = $temp->getValue($val, 'display');
1024					$sel = ($curval == $val) ? " selected='selected' " : '';
1025					$ret .= "<option value='{$val}' {$sel}>{$choice}</option>\n";
1026				}
1027				$ret .= "</select>\n";
1028				return $ret;
1029
1030			case EUF_DB_FIELD : //db_field
1031
1032				if(empty($choices))
1033				{
1034					e107::getDebug()->log("DB Field Choices is empty");
1035				}
1036
1037				$sql = e107::getDb('ue');
1038
1039				$order = ($choices[3]) ? "ORDER BY " . $tp->toDB($choices[3], true) : "";
1040
1041				if($sql->select($tp->toDB($choices[0], true), $tp->toDB($choices[1], true) . "," . $tp->toDB($choices[2], true), "1 $order"))
1042				{
1043					$choiceList = $sql->db_getList('ALL', false);
1044					$ret = "<select id='{$fid}' {$include} name='{$fname}' {$required}  {$title}>\n";
1045					$ret .= "<option value=''>&nbsp;</option>\n";  // ensures that the user chose it.
1046
1047					foreach($choiceList as $cArray)
1048					{
1049						$cID = trim($cArray[$choices[1]]);
1050						$cText = trim($cArray[$choices[2]]);
1051						$sel = ($curval == $cID) ? " selected='selected' " : "";
1052						$ret .= "<option value='{$cID}' {$sel}>{$cText}</option>\n";
1053					}
1054
1055					$ret .= "</select>\n";
1056
1057					return $ret;
1058				}
1059				else
1060				{
1061					return "<span class='label label-danger'>Failed to load</span>";
1062				}
1063
1064				break;
1065
1066			case EUF_TEXTAREA : //textarea
1067					return "<textarea id='{$fid}' {$include} name='{$fname}'  {$required} {$title}>{$curval}</textarea>";
1068					break;
1069
1070			case EUF_RICHTEXTAREA : // rich textarea (using WYSIWYG editor)
1071					return e107::getForm()->bbarea($fname, $curval);
1072
1073			case EUF_DATE : //date
1074
1075					if($curval == '0000-00-00') // Quick datepicker fix.
1076					{
1077						$curval = '';
1078					}
1079
1080					if(THEME_LEGACY === true)
1081					{
1082					    if(empty($opts['placeholder']))
1083                        {
1084                            $opts['placeholder'] = 'yyyy-mm-dd';
1085                        }
1086
1087						return e107::getForm()->text($fname,$curval,10,$opts);
1088					}
1089
1090                    $opts['format'] = 'yyyy-mm-dd';
1091                    $opts['return'] = 'string';
1092
1093                    if(!empty($required))
1094                    {
1095                        $opts['required'] = true;
1096                    }
1097
1098					return e107::getForm()->datepicker($fname,$curval,$opts);
1099					break;
1100
1101			case EUF_LANGUAGE : // language
1102					$lanlist = e107::getLanguage()->installed();
1103					sort($lanlist);
1104
1105	                $ret = "<select {$include} id='{$fid}' name='{$fname}' {$required} >\n";
1106					$ret .= "<option value=''>&nbsp;</option>\n";  // ensures that the user chose it.
1107					foreach($lanlist as $choice)
1108					{
1109						$choice = trim($choice);
1110						$sel = ($curval == $choice || (!USER && $choice == e_LANGUAGE))? " selected='selected' " : "";
1111						$ret .= "<option value='{$choice}' {$sel}>{$choice}</option>\n";
1112					}
1113					$ret .= "</select>\n";
1114
1115	            break;
1116
1117		}
1118
1119		return $ret;
1120	}
1121
1122
1123	/**
1124	 * BC Alias for getStructure()
1125	 * @param string $orderby
1126	 * @return mixed
1127	 */
1128	function user_extended_getStruct($orderby="user_extended_struct_order")
1129	{
1130		return $this->getStructure($orderby);
1131	}
1132
1133
1134	/**
1135	 * Return all extended-field structure information
1136	 * @param string $orderby
1137	 * @return array|mixed
1138	 */
1139	function getStructure($orderby="user_extended_struct_order")
1140	{
1141
1142		$id = 'core/userextended/structure';
1143
1144		if($ueStruct = e107::getRegistry($id))
1145		{
1146			return $ueStruct;
1147		}
1148
1149		$tp 	= e107::getParser();
1150		$sql_ue = e107::getDb('ue'); // new db;		// Use our own db to avoid interference with other objects
1151
1152		$ret = array();
1153		$parms = "";
1154
1155		if($orderby != "")
1156		{
1157			$parms = "1 ORDER BY ".$tp -> toDB($orderby, true);
1158		}
1159
1160		if($sql_ue->select('user_extended_struct','*',$parms))
1161		{
1162			while($row = $sql_ue->fetch())
1163			{
1164				$ret['user_'.$row['user_extended_struct_name']] = $row;
1165			}
1166		}
1167
1168		e107::setRegistry($id, $ret);
1169
1170		return $ret;
1171	}
1172
1173
1174	/**
1175	 * @param bool|false $no_cache
1176	 * @return bool
1177	 */
1178	function parse_extended_xml($no_cache = false)
1179	{
1180		if($no_cache == FALSE && $this->extended_xml)
1181		{
1182			return $this->extended_xml;
1183		}
1184
1185		$xml = e107::getXml();
1186		$data = $xml->loadXMLfile(e_CORE."xml/user_extended.xml", true);
1187		$ret['version'] = $data['@attributes']['version'];
1188		unset($info);
1189		foreach($data['item'] as $item)
1190		{
1191			if(is_array($item['include_text']) && !count($item['include_text']))
1192			{
1193				$item['include_text'] = '';
1194			}
1195			$info = array(
1196						"name" 			=> $item['@attributes']['name'],
1197						"text" 			=> "UE_LAN_".strtoupper($item['@attributes']['name']),
1198						"type" 			=> $item['type'],
1199						"values" 		=> $item['values'],
1200						"default" 		=> $item['default'],
1201						"required" 		=> $item['required'],
1202						"read" 			=> $item['read'],
1203						"write"			=> $item['write'],
1204						"applicable" 	=> $item['applicable'],
1205						"include_text"	=> $item['include_text'],
1206						"parms"			=> $item['include_text'],
1207						"regex" 		=> $item['regex']
1208					);
1209			if(is_array($item['default']) && $item['default'] == '')
1210			{
1211				$info['default'] = 0;
1212			}
1213			if($item['regex'])
1214			{
1215				$info['parms'] .= $item['include_text']."^,^".$item['regex']."^,^LAN_UE_FAIL_".strtoupper($item['@attributes']['name']);
1216			}
1217			$ret[$item['@attributes']['name']] = $info;
1218		}
1219		$this->extended_xml = $ret;
1220		return $this->extended_xml;
1221	}
1222
1223	/**
1224	 * Proxy Method to retrieve the value of an extended field
1225	 * @param int $uid
1226	 * @param string $field_name
1227	 * @param mixed $ifnotset [optional]
1228	 * @return mixed
1229	 */
1230	function get($uid, $field_name, $ifnotset=false)
1231	{
1232		return $this->user_extended_getvalue($uid, $field_name, $ifnotset);
1233	}
1234
1235
1236
1237	/**
1238	 * Proxy method for setting the value of an extended field
1239	 * (inserts or updates)
1240	 *
1241	 * @param integer $uid
1242	 * @param string $field_name eg. location
1243	 * @param string $newvalue eg. USA
1244	 * @param string $fieldType [optional] default 'todb' |
1245	 * @return boolean;
1246	 */
1247	function set($uid, $field_name, $newvalue, $fieldType = 'todb')
1248	{
1249		return $this->user_extended_setvalue($uid, $field_name, $newvalue, $fieldType);
1250	}
1251
1252
1253	/**
1254	 * Set the value of an extended field
1255	 *
1256	 *  $ue = new e107_user_extended;
1257	 *     $result = $ue->user_extended_setvalue(1, 'location', 'Pittsburgh');
1258	 *
1259	 * @param  int  $uid
1260	 * @param  string $field_name
1261	 * @param mixed $newvalue
1262	 * @param string $fieldType
1263	 * @return bool|int
1264	 */
1265	function user_extended_setvalue($uid, $field_name, $newvalue, $fieldType = 'todb')
1266	{
1267		$sql = e107::getDb();
1268		$tp = e107::getParser();
1269
1270		$uid = (int)$uid;
1271		switch($fieldType)
1272		{
1273			case 'int':
1274				$newvalue = (int)$newvalue;
1275				break;
1276
1277			case 'escape':
1278				$newvalue = "'".$sql->escape($newvalue)."'";
1279				break;
1280
1281			default:
1282				$newvalue = "'".$tp->toDB($newvalue)."'";
1283				break;
1284		}
1285		if(substr($field_name, 0, 5) != 'user_')
1286		{
1287			$field_name = 'user_'.$field_name;
1288		}
1289		$qry = "
1290		INSERT INTO `#user_extended` (user_extended_id, {$field_name})
1291		VALUES ({$uid}, {$newvalue})
1292		ON DUPLICATE KEY UPDATE {$field_name} = {$newvalue}
1293		";
1294		return $sql->gen($qry);
1295	}
1296
1297
1298	/**
1299	 * Retrieve the value of an extended field
1300	 *
1301	 *  $ue = new e107_user_extended;
1302	 *  $value = $ue->user_extended_getvalue(2, 'location');
1303	 *
1304	 * @param int     $uid
1305	 * @param string    $field_name
1306	 * @param bool $ifnotset
1307	 * @return bool
1308	 */
1309	function user_extended_getvalue($uid, $field_name, $ifnotset=false)
1310	{
1311		$uid = intval($uid);
1312		if(substr($field_name, 0, 5) != 'user_')
1313		{
1314			$field_name = 'user_'.$field_name;
1315		}
1316		$uinfo = e107::user($uid);
1317		if (!isset($uinfo[$field_name])) return $ifnotset;
1318		return $uinfo[$field_name];
1319	}
1320
1321
1322	/**
1323	 *
1324	 * Given a predefined list field, returns the display text corresponding to the passed value
1325	 *
1326	 * TODO: consider whether to cache the class object@param $table
1327	 * @param $value
1328	 * @return mixed|string
1329	 */
1330	function user_extended_display_text($table, $value)
1331	{
1332		$filename = e_CORE.'sql/extended_'.$table.'.php';
1333		if (!is_readable($filename)) return 'No file: '.$filename;
1334		require_once($filename);
1335		$className = 'extended_'.$table;
1336		if (!class_exists($className)) return '?????';
1337		/** @var extended_timezones $temp */
1338		$temp = new $className();
1339		if (!method_exists($className, 'getValue')) return '???-???';
1340		return $temp->getValue($value);
1341	}
1342
1343
1344	/**
1345	 * Render Extended User Field Data in a read-only fashion.
1346	 * @param $value
1347	 * @param $type
1348	 * @return array|string
1349	 */
1350	public function renderValue($value, $type=null)
1351	{
1352
1353
1354		//TODO FIXME Add more types.
1355
1356		switch($type)
1357		{
1358
1359			case EUF_COUNTRY:
1360				if(!empty($value))
1361				{
1362					return e107::getForm()->getCountry($value);
1363				}
1364
1365				return null;
1366			break;
1367
1368
1369
1370			case EUF_CHECKBOX:
1371					$value = e107::unserialize($value);
1372
1373					if(!empty($value))
1374					{
1375
1376						sort($value);
1377						return implode('<br />',$value);
1378
1379					/*
1380						$text = '<ul>';
1381						foreach($uVal as $v)
1382						{
1383							$text .= "<li>".$v."</li>";
1384
1385						}
1386						$text .= "</ul>";
1387						$ret_data = $text;*/
1388					}
1389				break;
1390
1391			case EUF_DATE :		//check for 0000-00-00 in date field
1392					if($value == '0000-00-00') { $value = ''; }
1393					return $value;
1394					break;
1395
1396			case EUF_RICHTEXTAREA:
1397				return e107::getParser()->toHTML($value, true);
1398				break;
1399
1400			default:
1401				return $value;
1402				// code to be executed if n is different from all labels;
1403		}
1404
1405		return null;
1406	}
1407
1408}
1409
1410
1411