1<?php
2/*
3 * e107 website system
4 *
5 * Copyright (C) 2008-2017 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 * User class functions
10 *
11 */
12
13/**
14 *
15 *	@package     e107
16 *	@subpackage	e107_handlers
17 *
18 *	This class handles all user-related user class functions.  Admin functions inherit from it.
19 */
20
21if (!defined('e107_INIT')) { exit; }
22
23
24e107::includeLan(e_LANGUAGEDIR.e_LANGUAGE.'/lan_userclass.php');
25
26
27/*
28Fixed classes occupy a numeric block from e_UC_SPECIAL_BASE to e_UC_SPECIAL_END, plus zero = e_UC_PUBLIC
29(Note that in 0.7/1.0, class numbers stopped at 255. Now they can be up to 65535).
30For info:
31define("e_UC_PUBLIC", 0);
32define("e_UC_MAINADMIN", 250);
33define("e_UC_READONLY", 251);
34define("e_UC_GUEST", 252);
35define("e_UC_MEMBER", 253);
36define("e_UC_ADMIN", 254);
37define("e_UC_NOBODY", 255);
38*/
39define('e_UC_ADMINMOD'		,249);			// Admins (includes main admins)
40define('e_UC_MODS'			,248);			// Moderators (who aren't admins)
41define('e_UC_NEWUSER'		,247);			// Users in 'probationary' period
42define('e_UC_BOTS'			,246);			// Reserved to identify search bots
43											// 243..245 reserved for future predefined user classes
44define('e_UC_SPECIAL_BASE'	,243);			// Assign class IDs 243 and above for fixed/special purposes
45define('e_UC_SPECIAL_END'	,255);			// Highest 'special' class
46
47define('UC_ICON_DIR',		e_IMAGE_ABS.'generic/');		// Directory for the icons used in the admin tree displays
48
49define('e_UC_BLANK'			,'-32767');		// Code for internal use - needs to be large to avoid confusion with 'not a member of...'
50define('UC_TYPE_STD'		, '0');			// User class is 'normal'
51define('UC_TYPE_GROUP'		, '1');			// User class is a group or list of subsidiary classes
52
53define('UC_CACHE_TAG', 'nomd5_classtree');
54
55
56class user_class
57{
58	public $class_tree;						// Simple array, filled with current tree. Additional field class_children is an array of child user classes (by ID)
59	protected $class_parents;				// Array of class IDs of 'parent' (i.e. top level) classes
60
61	public $fixed_classes = array();		// The 'predefined' core classes (constants beginning 'e_UC_')  (would be nice to have this R/O outside)
62	public $text_class_link = array();		// List of 'core' user classes and the related constants
63
64	protected $sql_r;						// We'll use our own DB to avoid interactions
65	protected $isAdmin;						// Set true if we're an instance of user_class_admin
66
67
68	// Constructor
69	public function __construct()
70	{
71		$this->sql_r = e107::getDb('sql_r');
72		$this->isAdmin = FALSE;
73
74		$this->fixed_classes = array(
75							e_UC_PUBLIC => UC_LAN_0,
76							e_UC_GUEST => UC_LAN_1,
77							e_UC_NOBODY => UC_LAN_2,
78							e_UC_MEMBER => UC_LAN_3,
79							e_UC_ADMIN => UC_LAN_5,
80							e_UC_MAINADMIN => UC_LAN_6,
81							e_UC_READONLY => UC_LAN_4,
82							e_UC_NEWUSER => UC_LAN_9,
83							e_UC_BOTS => UC_LAN_10
84							);
85
86
87
88		$this->text_class_link = array('public' => e_UC_PUBLIC, 'guest' => e_UC_GUEST, 'nobody' => e_UC_NOBODY, 'member' => e_UC_MEMBER,
89									'admin' => e_UC_ADMIN, 'main' => e_UC_MAINADMIN, 'new' => e_UC_NEWUSER, 'mods' => e_UC_MODS,
90									'bots' => e_UC_BOTS, 'readonly' => e_UC_READONLY);
91
92
93
94		$this->readTree(TRUE);			// Initialise the classes on entry
95	}
96
97
98	public function getFixedClassDescription($id)
99	{
100		if(isset($this->fixed_classes[$id]))
101		{
102			return $this->fixed_classes[$id];
103		}
104
105		return false;
106	}
107
108
109	/**
110	 * Take a key value such as 'member' and return it's numerical value.
111	 * @param $text
112	 * @return bool
113	 */
114	public function getClassFromKey($text)
115	{
116		if(isset($this->text_class_link[$text]))
117		{
118			return $this->text_class_link[$text];
119		}
120
121		return false;
122	}
123
124
125
126	/**
127 	*  Return value of isAdmin
128 	*/
129	public function isAdmin()
130	{
131		return $this->isAdmin;
132	}
133
134	/**
135	 *	Ensure the tree of userclass data is stored in our object ($this->class_tree).
136	 *	Only read if its either not present, or the $force flag is set.
137	 *	Data is cached if enabled
138	 *
139	 *	@param boolean $force - set to TRUE to force a re-read of the info regardless.
140	 *	@return none
141	*/
142	public function readTree($force = FALSE)
143	{
144		if (isset($this->class_tree) && count($this->class_tree) && !$force) return;
145
146		$e107 = e107::getInstance();
147
148		$this->class_tree = array();
149		$this->class_parents = array();
150
151
152
153		if ($temp = $e107->ecache->retrieve_sys(UC_CACHE_TAG))
154		{
155			$this->class_tree = e107::unserialize($temp);
156			unset($temp);
157		}
158		else
159		{
160			if($this->sql_r->field('userclass_classes','userclass_parent') &&  $this->sql_r->select('userclass_classes', '*', 'ORDER BY userclass_parent,userclass_name', 'nowhere')) // The order statement should give a consistent return
161			{
162				while ($row = $this->sql_r->fetch())
163				{
164					$this->class_tree[$row['userclass_id']] = $row;
165					$this->class_tree[$row['userclass_id']]['class_children'] = array();		// Create the child array in case needed
166				}
167			}
168
169			// Add in any fixed classes that aren't already defined (they historically didn't have a DB entry, although now its facilitated (and necessary for tree structure)
170			foreach ($this->fixed_classes as $c => $d)
171			{
172				if (!isset($this->class_tree[$c]))
173				{
174					switch ($c)
175					{
176						case e_UC_ADMIN :
177						case e_UC_MAINADMIN :
178							$this->class_tree[$c]['userclass_parent'] = e_UC_NOBODY;
179							break;
180						case e_UC_NEWUSER :
181							$this->class_tree[$c]['userclass_parent'] = e_UC_MEMBER;
182							break;
183						default :
184							$this->class_tree[$c]['userclass_parent'] = e_UC_PUBLIC;
185					}
186					$this->class_tree[$c]['userclass_id'] = $c;
187					$this->class_tree[$c]['userclass_name'] = $d;
188					$this->class_tree[$c]['userclass_description'] = 'Fixed class';
189					$this->class_tree[$c]['userclass_visibility'] = e_UC_PUBLIC;
190					$this->class_tree[$c]['userclass_editclass'] = e_UC_MAINADMIN;
191					$this->class_tree[$c]['userclass_accum'] = $c;
192					$this->class_tree[$c]['userclass_type'] = UC_TYPE_STD;
193				}
194			}
195
196			$userCache = e107::serialize($this->class_tree, FALSE);
197			$e107->ecache->set_sys(UC_CACHE_TAG,$userCache);
198			unset($userCache);
199		}
200
201
202		// Now build the tree.
203		// There are just two top-level classes - 'Everybody' and 'Nobody'
204		$this->class_parents[e_UC_PUBLIC] = e_UC_PUBLIC;
205		$this->class_parents[e_UC_NOBODY] = e_UC_NOBODY;
206		foreach ($this->class_tree as $uc)
207		{
208			if (($uc['userclass_id'] != e_UC_PUBLIC) && ($uc['userclass_id'] != e_UC_NOBODY))
209			{
210				if (!isset($this->class_tree[$uc['userclass_parent']]))
211				{
212					echo "Orphaned class record: ID=".$uc['userclass_id']." Name=".$uc['userclass_name']."  Parent=".$uc['userclass_parent']."<br />";
213				}
214				else
215				{	// Add to array
216					$this->class_tree[$uc['userclass_parent']]['class_children'][] = $uc['userclass_id'];
217				}
218			}
219		}
220	}
221
222
223
224	/**
225	 *	Given the list of 'base' classes a user belongs to, returns a comma separated list including ancestors. Duplicates stripped
226	 *
227	 *	@param string $startList - comma-separated list of classes user belongs to
228	 *	@param boolean $asArray - if TRUE, result returned as array; otherwise result returned as string
229	 *	@return string|array of user classes; format determined by $asArray
230	 */
231	public function get_all_user_classes($startList, $asArray = FALSE)
232	{
233		$is = array();
234		$start_array = explode(',', $startList);
235
236
237
238		foreach ($start_array as $sa)
239		{	// Merge in latest values - should eliminate duplicates as it goes
240			$is[] = $sa; // add parent to the flat list first
241			if (isset($this->class_tree[$sa]))
242			{
243				if($this->class_tree[$sa]['userclass_accum'])
244				{
245					$is = array_merge($is,explode(',',$this->class_tree[$sa]['userclass_accum']));
246				}
247			}
248		}
249		if ($asArray)
250		{
251			return array_unique($is);
252		}
253		return implode(',',array_unique($is));
254	}
255
256
257
258	/**
259	 *	Returns a list of user classes which can be edited by the specified classlist
260	 *
261	 *	@param string $classList - comma-separated list of classes to consider - default current user's class list
262	 *	@param boolean $asArray - if TRUE, result returned as array; otherwise result returned as string
263	 *	@return string|array of user classes; format determined by $asArray
264	 */
265	public function get_editable_classes($classList = USERCLASS_LIST, $asArray = FALSE)
266	{
267		$ret = array();
268		$blockers = array(e_UC_PUBLIC => 1, e_UC_READONLY => 1, e_UC_MEMBER => 1, e_UC_NOBODY => 1, e_UC_GUEST => 1, e_UC_NEWUSER => 1, e_UC_BOTS => 1);
269		$possibles = array_flip(explode(',',$classList));
270		unset($possibles[e_UC_READONLY]);
271
272		foreach ($this->class_tree as $uc => $uv)
273		{
274			if (!isset($blockers[$uc]))
275			{
276				$ec = $uv['userclass_editclass'];
277			//	$ec = $uv['userclass_visibility'];
278				if (isset($possibles[$ec]))
279				{
280					$ret[] = $uc;
281				}
282			}
283		}
284		if ($asArray) { return $ret; }
285		return implode(',',$ret);
286	}
287
288
289
290	/**
291	 *	Combines the selected editable classes into the main class list for a user.
292	 *
293	 *	@param array|string $combined - the complete list of current class memberships
294	 *	@param array|string $possible - the classes which are being edited
295	 *	@param array|string $actual - the actual membership of the editable classes
296	 *	@param boolean $asArray - if TRUE, result returned as array; otherwise result returned as string
297	 *	@return string|array of user classes; format determined by $asArray
298	 */
299	public function mergeClassLists($combined, $possible, $actual, $asArray = FALSE)
300	{
301		if (!is_array($combined)) { $combined = explode(',',$combined);  }
302		if (!is_array($possible)) { $possible = explode(',',$possible);  }
303		if (!is_array($actual)) 	{ $actual = explode(',',$actual);  }
304		$combined = array_flip($combined);
305		foreach ($possible as $p)
306		{
307			if (in_array($p,$actual))
308			{	// Class must be in final array
309				$combined[$p] = 1;
310			}
311			else
312			{
313				unset($combined[$p]);
314			}
315		}
316		$combined = array_keys($combined);
317		if ($asArray) { return $combined; }
318		return implode(',', $combined);
319	}
320
321
322
323	/**
324	 *	Remove the fixed classes from a class list
325	 *	Removes all classes in the reserved block, as well as e_UC_PUBLIC
326	 *	@param array|string $inClasses - the complete list of current class memberships
327	 *	@return string|array of user classes; format is the same as $inClasses
328	 */
329	public function stripFixedClasses($inClasses)
330	{
331		$asArray = TRUE;
332		if (!is_array($inClasses))
333		{
334			$asArray = FALSE;
335			$inClasses = explode(',',$inClasses);
336		}
337		/*
338		$inClasses = array_flip($inClasses);
339		foreach ($this->fixed_classes as $k => $v)
340		{
341			if (isset($inClasses[$k])) { unset($inClasses[$k]); }
342		}
343		$inClasses = array_keys($inClasses);
344		*/
345		foreach ($inClasses as $k => $uc)
346		{
347			if ((($uc >= e_UC_SPECIAL_BASE) && ($uc <= e_UC_SPECIAL_END)) || ($uc == e_UC_PUBLIC))
348			{
349				unset($inClasses[$k]);
350			}
351		}
352		if ($asArray) { return ($inClasses); }
353		return implode(',',$inClasses);
354	}
355
356
357
358	/**
359	 *	Given a comma separated list, returns the minimum number of class memberships required to achieve this (i.e. strips classes 'above' another in the tree)
360	 *	Requires the class tree to have been initialised
361	 *
362	 *	@param array|string $classList - the complete list of current class memberships
363	 *
364	 *	@return string|array of user classes; format is the same as $classList
365	 */
366	public function normalise_classes($classList)
367	{
368		if (is_array($classList))
369		{
370			$asArray = TRUE;
371			$oldClasses = $classList;
372		}
373		else
374		{
375			$asArray = FALSE;
376			$oldClasses = explode(',',$classList);
377		}
378		$dropClasses = array();
379		foreach ($oldClasses as $c)
380		{  // Look at our parents (which are in 'userclass_accum') - if any of them are contained in oldClasses, we can drop them.
381			$tc = array_flip(explode(',',$this->class_tree[$c]['userclass_accum']));
382			unset($tc[$c]);		// Current class should be in $tc anyway
383			foreach ($tc as $tc_c => $v)
384			{
385				if (in_array($tc_c,$oldClasses))
386				{
387					$dropClasses[] = $tc_c;
388				}
389			}
390		}
391		$newClasses = array_diff($oldClasses,$dropClasses);
392		if ($asArray) { return $newClasses; }
393		return implode(',',$newClasses);
394	}
395
396
397	/**
398	 * @param string $optlist - comma-separated list of classes/class types to be included in the list
399			It allows selection of the classes to be shown in the dropdown. All or none can be included, separated by comma. Valid options are:
400			public
401			guest
402			nobody
403			member
404			readonly
405			admin
406			main - main admin
407			new - new users
408			bots - search bot class
409			classes - shows all classes
410			matchclass - if 'classes' is set, this option will only show the classes that the user is a member of
411	 * @return array
412	 */
413	public function getClassList($optlist)
414	{
415		return $this->uc_required_class_list($optlist);
416	}
417
418
419	/**
420	 *	Generate a dropdown list of user classes from which to select - virtually as the deprecated r_userclass() function did
421	 *	[ $mode parameter of r_userclass() removed - $optlist is more flexible) ]
422	 *
423	 * 	@param string $fieldname - name of select list
424	 *	@param mixed $curval - current selected value (empty string if no current value)
425	 *	@param string $optlist - comma-separated list of classes/class types to be included in the list
426			It allows selection of the classes to be shown in the dropdown. All or none can be included, separated by comma. Valid options are:
427			public
428			guest
429			nobody
430			member
431			readonly
432			admin
433			main - main admin
434			new - new users
435			bots - search bot class
436			classes - shows all classes
437			matchclass - if 'classes' is set, this option will only show the classes that the user is a member of
438			blank - puts an empty option at the top of select dropdowns
439
440			filter - only show those classes where member is in a class permitted to view them - e.g. as the new 'visible to' field - added for 2.0
441			force  - show all classes (subject to the other options, including matchclass) - added for 2.0
442			all - alias for 'force'
443
444			no-excludes - if present, doesn't show the 'not member of' list
445			is-checkbox - if present, suppresses the <optgroup...> construct round the 'not member of' list
446
447			editable - can only appear on its own - returns list of those classes the user can edit (manage)
448
449	 *	@param string $extra_js - can add JS handlers (e.g. 'onclick', 'onchange') if required
450	*/
451	public function uc_dropdown($fieldname, $curval = 0, $optlist = '', $extra_js = '')
452	{
453		$show_classes = self::uc_required_class_list($optlist);		// Get list of classes which meet criteria
454
455		$text = '';
456		foreach ($show_classes as $k => $v)
457		{
458			if ($k == e_UC_BLANK)
459			{
460				$text .= "<option value=''>&nbsp;</option>\n";
461			}
462			else
463			{
464				$s = ($curval == $k && $curval !== '') ?  "selected='selected'" : '';
465				$text .= "<option class='uc-select'  value='".$k."' ".$s.">".$v."</option>\n";
466			}
467		}
468
469		if(is_array($extra_js))
470		{
471			$options = $extra_js;
472			unset($extra_js);
473		}
474
475		$class = "tbox form-control";
476
477		if(!empty($options['class']))
478		{
479			$class .= " ".$options['class'];
480		}
481
482
483		// Inverted Classes
484		if(strpos($optlist, 'no-excludes') === FALSE)
485		{
486			if (strpos($optlist, 'is-checkbox') !== FALSE)
487			{
488				$text .= "\n".UC_LAN_INVERTLABEL."<br />\n";
489			}
490			else
491			{
492				$text .= "\n";
493				$text .= '<optgroup label=\''.UC_LAN_INVERTLABEL.'\'>';
494				$text .= "\n";
495			}
496			foreach ($show_classes as $k => $v)
497			{
498				if($k != e_UC_PUBLIC && $k != e_UC_NOBODY && $k != e_UC_READONLY)  // remove everyone, nobody and readonly from list.
499				{
500					$s = ($curval == ('-'.$k) && $curval !== '') ?  "selected='selected'" : '';
501					$text .= "<option class='uc-select-inverted' value='-".$k."' ".$s.">".str_replace("[x]", $v, UC_LAN_INVERT)."</option>\n";
502				}
503			}
504			$text .= "</optgroup>\n";
505		}
506
507		// Only return the select box if we've ended up with some options
508		if ($text) $text = "\n<select class='".$class."' name='{$fieldname}' id='{$fieldname}' {$extra_js}>\n".$text."</select>\n";
509		return $text;
510	}
511
512
513
514	/**
515	 *	Generate an ordered array  classid=>classname - used for dropdown and check box lists
516	 *
517	 *	@param string $optlist - comma-separated list of classes/class types to include (see uc_dropdown for details)
518	 *	@param boolean $just_ids - if TRUE, each returned array value is '1'; otherwise it is the class name
519	 *	@return array of user classes; ky is numeric class id, value is '1' or class name according to $just_ids
520	 */
521	public function uc_required_class_list($optlist = '', $just_ids = FALSE)
522	{
523		$ret = array();
524		$opt_arr = array();
525
526		if ($optlist)
527		{
528			$opt_arr = array_map('trim', explode(',',$optlist));
529		}
530
531		$opt_arr = array_flip($opt_arr);		// This also eliminates duplicates which could arise from applying the other options, although shouldn't matter
532
533		if (isset($opt_arr['no-excludes'])) unset($opt_arr['no-excludes']);
534		if (isset($opt_arr['is-checkbox'])) unset($opt_arr['is-checkbox']);
535
536		if (count($opt_arr) == 0)
537		{
538			$opt_arr = array('public' => 1, 'guest' => 1, 'nobody' => 1, 'member' => 1, 'classes' => 1);
539		}
540
541		if (isset($opt_arr['all']))
542		{
543			unset($opt_arr['all']);
544			$opt_arr['force'] = 1;
545		}
546
547		if (isset($opt_arr['editable']))
548		{
549			$temp = array_flip(explode(',',$this->get_editable_classes()));
550			if ($just_ids) return $temp;
551			foreach ($temp as $c => $t)
552			{
553				$temp[$c] = $this->class_tree[$c]['userclass_name'];
554			}
555			return $temp;
556		}
557
558
559
560		if (isset($opt_arr['force'])) unset($opt_arr['filter']);
561
562		if (isset($opt_arr['blank']))
563		{
564			$ret[e_UC_BLANK] = 1;
565		}
566
567		// Do the 'fixed' classes next
568		foreach ($this->text_class_link as $k => $v)
569		{
570			//		if (isset($opt_arr[$k]) || isset($opt_arr['force']))
571			if (isset($opt_arr[$k]))
572			{
573				$ret[$v] = $just_ids ? '1' : $this->fixed_classes[$v];
574			}
575		}
576
577		// Now do the user-defined classes
578		if (isset($opt_arr['classes']) || isset($opt_arr['force']))
579		{	// Display those classes the user is allowed to:
580			//	Main admin always sees the lot
581			//	a) Mask the 'fixed' user classes which have already been processed
582			//  b) Apply the visibility option field ('userclass_visibility')
583			//  c) Apply the matchclass option if appropriate
584			foreach($this->class_tree as $uc_id => $row)
585			{
586				if (!array_key_exists($uc_id,$this->fixed_classes)
587				&& (   getperms('0')
588					|| (
589						(!isset($opt_arr['matchclass']) || check_class($uc_id))
590						&&
591						(!isset($opt_arr['filter']) || check_class($row['userclass_visibility']))
592					   )
593					)
594					)
595				{
596					$ret[$uc_id] = $just_ids ? '1' : $this->class_tree[$uc_id]['userclass_name'];
597				}
598			}
599		}
600		/* Above loop slightly changes the display order of earlier code versions.
601			If readonly must be last, delete it from the $text_class_link array, and uncomment the following code
602
603			if (isset($opt_arr['readonly']))
604			{
605			  $ret[e_UC_READONLY] = $this->class_tree[e_UC_READONLY]['userclass_description'];
606			}
607		*/
608
609		return $ret;
610	}
611
612
613	/**
614	 *    Very similar to self::uc_dropdown, but returns a list of check boxes. Doesn't encapsulate it.
615	 *
616	 * @param string $fieldname is the name for the array of checkboxes
617	 * @param string $curval is a comma separated list of class IDs for boxes which are checked.
618	 * @param string $optlist as for uc_dropdown
619	 * @param boolean $showdescription - if TRUE, appends the class description in brackets
620	 * @param boolean $asArray - if TRUE, result returned as array; otherwise result returned as string
621	 *
622	 *    return string|array according to $asArray
623	 * @return array|string
624	 */
625	public function uc_checkboxes($fieldname, $curval='', $optlist = '', $showdescription = FALSE, $asArray = FALSE)
626	{
627		$show_classes = $this->uc_required_class_list($optlist);
628		$frm = e107::getForm();
629
630		$curArray = explode(',', $curval);				// Array of current values
631		$ret = array();
632
633		foreach ($show_classes as $k => $v)
634		{
635			if ($k != e_UC_BLANK)
636			{
637				// $c = (in_array($k,$curArray)) ?  " checked='checked'" : '';
638				$c = (in_array($k,$curArray)) ?  true : false;
639				if ($showdescription) $v .= ' ('.$this->uc_get_classdescription($k).')';
640				//$ret[] = "<div class='field-spacer'><input type='checkbox' class='checkbox' name='{$fieldname}[{$k}]' id='{$fieldname}-{$k}' value='{$k}'{$c} /><label for='{$fieldname}-{$k}'>".$v."</label></div>\n";
641				$name = $fieldname.'['.$k.']';
642				$ret[] = $frm->checkbox($name,$k,$c,$v);
643				//$ret[] = "<div class='field-spacer'><input type='checkbox' class='checkbox' name='{$fieldname}[{$k}]' id='{$fieldname}-{$k}' value='{$k}'{$c} /><label for='{$fieldname}-{$k}'>".$v."</label></div>\n";
644
645			}
646		}
647		if ($asArray) return $ret;
648		return implode('', $ret);
649	}
650
651
652
653	/**
654	 *	Used by @see{vetted_tree()} to generate lower levels of tree
655	 *
656	 *	@param string $listnum - class number of the parent. Is negative if the class is 'Everyone except...' (Must be a string because 0 == -0)
657	 *	@param integer $nest_level - indicates our level in the tree - 0 is the top level; increases as we descend the tree. Positive value.
658	 *	@param string $current_value - comma-separated list of integers indicating classes selected. (Spaces not permitted)
659	 *	@param array $perms - list of classes we are allowed to display
660	 *	@param string $opt_options - passed to callback function; not otherwise used
661	 */
662	protected function vetted_sub_tree($treename, $callback, $listnum, $nest_level, $current_value, $perms, $opt_options)
663	{
664		$ret = '';
665		$nest_level++;
666		$listIndex = abs($listnum);
667		$classSign = (substr($listnum, 0, 1) == '-') ? '-' : '+';
668		//echo "Subtree: {$listnum}, {$nest_level}, {$current_value}, {$classSign}:{$listIndex}<br />";
669		if(isset($this->class_tree[$listIndex]['class_children']))
670		{
671			foreach ($this->class_tree[$listIndex]['class_children'] as $p)
672			{
673				$classValue = $classSign.$p;
674				// Looks like we don't need to differentiate between function and class calls
675				if (isset($perms[$p]))
676				{
677					$ret .= call_user_func($callback, $treename, $classValue, $current_value, $nest_level, $opt_options);
678				}
679
680				$ret .= $this->vetted_sub_tree($treename, $callback, $classValue, $nest_level, $current_value, $perms, $opt_options);
681			}
682
683
684		}
685		return $ret;
686	}
687
688
689	/**
690	 *	create an indented tree - for example within a select box or a list of check boxes.
691	 *	For each displayed element, the callback routine is called
692	 *	@param string $treename is the name given to the elements where required
693	 *	@param	function|object $callback is a routine used to generate each element; there are three implemented within this class:
694	 *		select (the default) - generates the option list. Text requires to be encapsulated in a <select......./select> tag set
695	 *			- can also be used with multi-select boxes
696	 *		checkbox - generates a set of checkboxes
697	 *		checkbox_desc - generates a set of checkboxes with the class description in brackets
698	 *		Alternative callbacks can be used to achieve different layouts/styles
699	 *	@param integer|string $current_value - single class number for single-select dropdown; comma separated array of class numbers for checkbox list or multi-select
700	 *	@param string $optlist works the same as for @see uc_dropdown()
701	 *	@param string $opt_options - passed to callback function; not otherwise used
702	 *	@return string - formatted HTML for tree
703	*/
704	public function vetted_tree($treename, $callback='', $current_value='', $optlist = '', $opt_options = '')
705	{
706		$ret = '';
707		if (!$callback) $callback=array($this,'select');
708		$current_value = str_replace(' ','',$current_value);				// Simplifies parameter passing for the tidy-minded
709		$notCheckbox = (strpos($optlist, 'is-checkbox') === FALSE);
710
711		$perms = $this->uc_required_class_list($optlist,TRUE);				// List of classes which we can display
712		if (isset($perms[e_UC_BLANK]))
713		{
714			$ret .= call_user_func($callback, $treename, e_UC_BLANK, $current_value, 0, $opt_options);
715		}
716		foreach ($this->class_parents as $p)
717		{
718			if (isset($perms[$p]))
719			{
720				$ret .= call_user_func($callback, $treename, $p, $current_value, 0, $opt_options);
721			}
722			$ret .= $this->vetted_sub_tree($treename, $callback, $p, 0, $current_value, $perms, $opt_options);
723		}
724
725
726		// Inverted classes. (negative values for exclusion).
727		if(strpos($optlist, 'no-excludes') === FALSE)
728		{
729			if ($notCheckbox)
730			{
731				$ret .= "\n";
732				$ret .= '<optgroup label=\''.UC_LAN_INVERTLABEL.'\'>';
733				$ret .= "\n";
734			}
735			else
736			{
737				$ret .= "\n".UC_LAN_INVERTLABEL."<br />\n";
738			}
739			foreach ($this->class_parents as $k => $p)		// Currently key and data are the same
740			{
741			//echo "Class parent: {$k}:{$p}<br />";
742				if($k != e_UC_PUBLIC && $k != e_UC_NOBODY && $k != e_UC_READONLY)  // remove everyone, nobody and readonly from list.
743				{
744					if (isset($perms[$p]))
745					{
746						$ret .= call_user_func($callback, $treename, '-'.$p, $current_value, 0, $opt_options);
747					}
748				}
749				$ret .= $this->vetted_sub_tree($treename, $callback, '-'.$p, 0, $current_value, $perms, $opt_options);
750			}
751			if ($notCheckbox)
752			{
753				$ret .= "</optgroup>\n";
754			}
755		}
756		return $ret;
757	}
758
759
760	/**
761	 *	Callback for vetted_tree - Creates the option list for a selection box
762	 *	It is the caller's responsibility to enclose this list in a <select...../select> structure
763	 *	Can be used as a basis for similar functions
764	 *
765	 *	@param string $treename	- name of tree elements (not used with select; used with checkboxes, for example)
766	 *	@param string $classnum - user class being displayed. This may be negative to indicate 'everyone but...'
767	 *			- special numeric part e_UC_BLANK adds a blank option in the list.
768	 *	@param integer|string $current_value - single class number for single-select dropdown; comma separated array of class numbers for checkbox list or multi-select
769	 *	@param integer $nest_level - 'depth' of this item in the tree. Zero is base level. May be used to indent or highlight dependent on level
770	 *	@param string $opt_options - passed to callback function; not otherwise used
771	 *
772	 *	@return string - option list
773	 */
774	public function select($treename, $classnum, $current_value, $nest_level, $opt_options = '')
775	{
776		$classIndex = abs($classnum);			// Handle negative class values
777		$classSign = (substr($classnum, 0, 1) == '-') ? '-' : '';
778		if ($classIndex == e_UC_BLANK)  return "<option value=''>&nbsp;</option>\n";
779		$tmp = explode(',',$current_value);
780		$sel = in_array($classnum, $tmp) ? " selected='selected'" : '';
781		if ($nest_level == 0)
782		{
783			$prefix = '';
784			$style = " style='font-weight:bold; font-style: italic;'";
785		}
786		elseif ($nest_level == 1)
787		{
788			$prefix = '&nbsp;&nbsp;';
789			$style = " style='font-weight:bold'";
790		}
791		else
792		{
793			$prefix = '&nbsp;&nbsp;'.str_repeat('--',$nest_level-1).'>';
794			$style = '';
795		}
796		$ucString = $this->class_tree[$classIndex]['userclass_name'];
797		if ($classSign == '-')
798		{
799			$ucString = str_replace('[x]', $ucString, UC_LAN_INVERT);
800		}
801		return "<option value='{$classSign}{$classIndex}'{$sel}{$style}>".$prefix.$ucString."</option>\n";
802	}
803
804
805	/**
806	 *	Callback for vetted_tree - displays indented checkboxes with class name only
807	 *	See @link select for parameter details
808	 */
809	public function checkbox($treename, $classnum, $current_value, $nest_level, $opt_options = '')
810	{
811		$frm = e107::getForm();
812
813		$classIndex 		= abs($classnum);			// Handle negative class values
814		$classSign 			= (substr($classnum, 0, 1) == '-') ? '-' : '';
815
816		if ($classIndex == e_UC_BLANK)  return '';
817
818		$tmp 				= explode(',',$current_value);
819		$chk 				= in_array($classnum, $tmp) ? " checked='checked'" : '';
820		$style				= "";
821
822		if ($nest_level == 0)
823		{
824			$style = " style='font-weight:bold'";
825		}
826		elseif($nest_level > 1)
827		{
828			$style = " style='text-indent:".(1.2 * $nest_level)."em'";
829		}
830
831		$ucString = $this->class_tree[$classIndex]['userclass_name'];
832
833		if ($classSign == '-')
834		{
835			$ucString = str_replace('[x]', $ucString, UC_LAN_INVERT);
836		}
837
838		$checked = in_array($classnum, $tmp) ? true : false;
839		return "<div {$style}>".$frm->checkbox($treename.'[]',$classSign.$classIndex,$checked,array('label'=> $ucString))."</div>\n";
840
841
842		return "<div {$style}><input type='checkbox' class='checkbox' name='{$treename}[]' id='{$treename}_{$classSign}{$classIndex}' value='{$classSign}{$classIndex}'{$chk} />".$ucString."</div>\n";
843	}
844
845
846	/**
847	 *	Callback for vetted_tree - displays indented checkboxes with class name, and description in brackets
848	 *	See @link select for parameter details
849	 */
850	public function checkbox_desc($treename, $classnum, $current_value, $nest_level, $opt_options = '')
851	{
852		$classIndex = abs($classnum);			// Handle negative class values
853		$classSign = (substr($classnum, 0, 1) == '-') ? '-' : '';
854
855		if ($classIndex == e_UC_BLANK)  return '';
856
857		$tmp = explode(',',$current_value);
858		$chk = in_array($classnum, $tmp) ? " checked='checked'" : '';
859
860		if ($nest_level == 0)
861		{
862			$style = " style='font-weight:bold'";
863		}
864		else
865		{
866			$style = " style='text-indent:".(0.3 * $nest_level)."em'";
867		}
868
869		$id = "{$treename}_{$classnum}";
870
871		$ucString = $this->class_tree[$classIndex]['userclass_name'];
872
873		if ($classSign == '-')
874		{
875			$ucString = str_replace('[x]', $ucString, UC_LAN_INVERT);
876		}
877
878		$description = $ucString.'  ('.$this->class_tree[$classIndex]['userclass_description'].")";
879
880		$id ="{$treename}_{$classSign}{$classnum}";
881
882		return "<div class='checkbox' {$style}>".e107::getForm()->checkbox($treename.'[]', $classnum , $chk, array("id"=>$id,'label'=>$description))."</div>\n";
883
884	//	return "<div {$style}><input type='checkbox' class='checkbox' name='{$treename}[]' id='{$treename}_{$classSign}{$classnum}' value='{$classSign}{$classnum}'{$chk} />".$this->class_tree[$classIndex]['userclass_name'].'  ('.$this->class_tree[$classIndex]['userclass_description'].")</div>\n";
885	}
886
887
888
889
890	/**
891	 *	Return array of all classes, limited according to membership of the userclass_visibility field if $filter is set.
892	 *	@param string|integer $filter - user class or class list in format acceptable to check_class()
893	 *	@return array of class elements, each itself an array:
894	 *		Index field - userclass_id
895	 *		Data fields - userclass_name, userclass_description, userclass_editclass
896	 */
897	public function uc_get_classlist($filter = FALSE)
898	{
899		$ret = array();
900		$this->readTree(FALSE);				// Make sure we have data
901		foreach ($this->class_tree as $k => $v)
902		{
903			if (!$filter || check_class($filter))
904			{
905				$ret[$k] = array('userclass_name' => $v, 'userclass_description' => $v['userclass_description'], 'userclass_editclass' => $v['userclass_editclass']);
906			}
907		}
908		return $ret;
909	}
910
911
912
913	/**
914	 *	Return class name for given class ID
915	 *	Handles 'not a member of...' construct by replacing '--CLASS--' in UC_LAN_INVERT with the class name
916	 *	@param integer $id - class number. A negative number indicates 'not a member of...'
917	 *	@return string class name
918	 */
919	public function getName($id)
920	{
921		$cn = abs($id);
922		$ucString = 'Class:'.$id;			// Debugging aid - this should be overridden
923
924		if (isset($this->class_tree[$cn]))
925		{
926			$ucString = $this->class_tree[$cn]['userclass_name'];
927		}
928		elseif (isset($this->fixed_classes[$cn]))
929		{
930			$ucString = $this->fixed_classes[$cn];
931		}
932
933		if($id < 0)
934		{
935			//$val = abs($id);
936			//$name = isset($this->class_tree[$val]['userclass_name']) ? $this->class_tree[$val]['userclass_name'] : $this->fixed_classes[$val];
937			$ucString = str_replace('[x]', $ucString, UC_LAN_INVERT);
938		}
939		return $ucString;
940	}
941
942
943
944	/**
945	 *	Return a key-name identifier for given class ID
946	 *	@param integer $id - class number. A negative number indicates 'not a member of...'
947	 *	@return string class name ke
948	 */
949	public function getIdentifier($id)
950	{
951		$cn = abs($id);
952
953		$ucString = '';
954
955		$fixedClasses = array_flip($this->text_class_link);
956
957		if(isset($fixedClasses[$cn]))
958		{
959			return $fixedClasses[$cn];
960		}
961
962		if(isset($this->class_tree[$cn]))
963		{
964			return e107::getForm()->name2id($this->class_tree[$cn]['userclass_name']);
965		}
966
967		return $ucString;
968	}
969
970
971	/**
972	 *	Return class description for given class ID
973	 *	@param integer $id - class number. Must be >= 0
974	 *	@return string class description
975	 */
976	public function getDescription($id)
977	{
978		$id = intval($id);
979
980		if(isset($this->class_tree[$id]))
981		{
982			return $this->class_tree[$id]['userclass_description'];
983		}
984		if (isset($this->fixed_classes[$id]))
985		{
986			return $this->fixed_classes[$id];	// Name and description the same for fixed classes
987		}
988		return '';
989	}
990
991
992
993	/**
994	 * BC Alias. of getName();
995	 * @deprecated
996	 * @param $id
997	 * @return string
998	 */
999	public function uc_get_classname($id)
1000	{
1001		return $this->getName($id);
1002	}
1003
1004
1005
1006
1007	/**
1008	 * BC Alias of getDescription
1009	 * @deprecated
1010	 * @param $id
1011	 * @return mixed
1012	 */
1013	public function uc_get_classdescription($id)
1014	{
1015		return $this->getDescription($id);
1016	}
1017
1018
1019
1020	/**
1021	 *	Return class icon for given class ID
1022	 *	@param integer $id - class number. Must be >= 0
1023	 *	@return string class icon if defined, otherwise empty string
1024	 */
1025	public function uc_get_classicon($id)
1026	{
1027		if (isset($this->class_tree[$id]))
1028		{
1029			return $this->class_tree[$id]['userclass_icon'];
1030		}
1031		return '';
1032	}
1033
1034
1035
1036
1037
1038	/**
1039	 *	Look up class ID for a given class name
1040	 *	@param string $name - class name
1041	 *	@return integer|boolean FALSE if not found, else user class ID
1042	 */
1043	public function getID($name)
1044	{
1045		$this->readTree();
1046		// We have all the info - can just search the array
1047		foreach ($this->class_tree as $uc => $info)
1048		{
1049			if ($info['userclass_name'] == $name)
1050			{
1051				return $uc;
1052			}
1053		}
1054		return FALSE;		// not found
1055	}
1056
1057
1058
1059
1060
1061	/**
1062	 * BC Alias of getID();
1063	 * @param $name
1064	 * @return mixed
1065	 */
1066	public function ucGetClassIDFromName($name)
1067	{
1068		return $this->getID($name);
1069
1070	}
1071
1072	/**
1073	 *	Utility to remove a specified class ID from the default comma-separated list
1074	 *	Optional conversion to array of classes
1075	 *	@param integer $classID - class number. Must be >= 0
1076	 *	@param string $from - comma separated list of class numbers
1077	 *	@param boolean $asArray - if TRUE, result returned as array; otherwise result returned as string
1078	 *	@return string class description
1079	 */
1080	public function ucRemove($classID, $from, $asArray = FALSE)
1081	{
1082		$tmp = array_flip(explode(',',$from));
1083		if (isset($tmp[$classID]))
1084		{
1085			unset($tmp[$classID]);
1086		}
1087		$tmp = array_keys($tmp);
1088		if ($asArray) { return $tmp; }
1089		if (count($tmp) == 0) { return ''; }
1090		return implode(',',$tmp);
1091	}
1092
1093
1094
1095	/**
1096	 *	Utility to add a specified class ID to the default comma-separated list
1097	 *	Optional conversion to array of classes
1098	 *	@param integer $classID - class number. Must be >= 0
1099	 *	@param string $to - comma separated list of class numbers
1100	 *	@param boolean $asArray - if TRUE, result returned as array; otherwise result returned as string
1101	 *	@return string class description
1102	 */
1103	public function ucAdd($classID, $to, $asArray = FALSE)
1104	{
1105		$tmp = array_flip(explode(',',$to));
1106		$tmp[$classID] = 1;
1107		$tmp = array_keys($tmp);
1108		if ($asArray) { return $tmp; }
1109		return implode(',',$tmp);
1110	}
1111
1112
1113
1114	/**
1115	 *	See if a class can be edited (in the sense of the class ID not being fixed)
1116	 *	@param integer $classID - class number. Must be >= 0
1117	 *	@return boolean - TRUE if class can be edited
1118	 */
1119	public function isEditableClass($classID)
1120	{
1121		if (($classID >= e_UC_SPECIAL_BASE) && ($classID <= e_UC_SPECIAL_END)) return FALSE;	// Don't allow deletion of fixed classes
1122		if (isset($this->fixed_classes[$classID])) return FALSE;			// This picks up classes such as e_UC_PUBLIC outside the main range which can't be deleted
1123		return TRUE;
1124	}
1125
1126
1127	/**
1128	 * @deprecated Alias of getUsersInClass()
1129	 * @param        $classes
1130	 * @param string $fieldList
1131	 * @param bool   $includeAncestors
1132	 * @param string $orderBy
1133	 * @return array
1134	 */
1135	public function get_users_in_class($classes, $fieldList = 'user_name, user_loginname', $includeAncestors = FALSE, $orderBy = 'user_id')
1136	{
1137		return $this->getUsersInClass($classes, $fieldList, $includeAncestors, $orderBy);
1138	}
1139
1140
1141	/**
1142	 *	Return all users in a particular class or set of classes.
1143	 *
1144	 *  Could potentially be verrrrryyyy slow - has to scan the whole user database at present.
1145	 *	@param string $$classes - comma separated list of classes
1146	 *	@param string $fields - comma separated list of fields to be returned. `user_id` is always returned as the key of the array entry, prefix with 'ue.' to retrieve extended user fields.
1147	 *	@param boolean $includeAncestors - if TRUE, also looks for classes in the hierarchy; otherwise checks exactly the classes passed
1148	 *	@param string $orderBy - optional field name to define the order of entries in the results array
1149	 *	@return array indexed by user_id, each element is an array (database row) containing the requested fields
1150	 */
1151	public function getUsersInClass($classes, $fields = 'user_name, user_loginname', $includeAncestors = false, $orderBy = 'user_id')
1152	{
1153
1154		$classes = str_replace(' ','', $classes); // clean up white spaces
1155
1156		$classList = explode(",", $classes);
1157
1158		if(empty($classList))
1159		{
1160			return array();
1161		}
1162
1163		if($includeAncestors === true)
1164		{
1165			$classList = $this->get_all_user_classes($classes, true);
1166		}
1167
1168		$classList = array_flip($classList);
1169
1170		$qry = array();
1171
1172		if(isset($classList[e_UC_MEMBER]))
1173		{
1174			$qry[] = "user_ban = 0";
1175			unset($classList[e_UC_MEMBER]);
1176		}
1177
1178		if(isset($classList[e_UC_ADMIN]))
1179		{
1180			$qry[] = "user_admin = 1";
1181			unset($classList[e_UC_ADMIN]);
1182		}
1183
1184		if(isset($classList[e_UC_MAINADMIN]))
1185		{
1186			$qry[] = "user_perms = '0' OR user_perms = '0.'";
1187			unset($classList[e_UC_MAINADMIN]);
1188		}
1189
1190		if(!empty($classList))
1191		{
1192			$class_regex = implode('|', array_flip($classList));
1193			$regex = "(^|,)(".e107::getParser()->toDB($class_regex).")(,|$)";
1194			$qry[] = "user_class REGEXP '{$regex}' ";
1195		}
1196
1197		if(empty($qry))
1198		{
1199			return array();
1200		}
1201
1202		$sql = e107::getDb('sql_r');
1203
1204		$ret = array();
1205
1206		$lj = strpos($fields,'ue.') !== false ? "LEFT JOIN `#user_extended` AS ue ON user_id = ue.user_extended_id " : "";
1207
1208		$query = "SELECT user_id,{$fields} FROM `#user` ".$lj." WHERE ".implode(" OR ",$qry)." ORDER BY ".$orderBy;
1209
1210		if ($sql->gen($query))
1211		{
1212			while ($row = $sql->fetch())
1213			{
1214				$row['user_id'] = (int) $row['user_id'];
1215				$ret[$row['user_id']] = $row;
1216			}
1217
1218		}
1219
1220		return $ret;
1221	}
1222
1223
1224	/**
1225	 *	Clear user class cache
1226	 *	@return void
1227	 */
1228	public function clearCache()
1229	{
1230		e107::getCache()->clear_sys(UC_CACHE_TAG);
1231	}
1232}
1233
1234
1235/**========================================================================
1236 *			Functions from previous userclass_class handler
1237 * ========================================================================
1238 * Implemented for backwards compatibility/convenience.
1239
1240 * ************** DEPRECATED - use new class-based functions **************
1241 *
1242 * Functionality should be sufficiently similar to the original functions to not notice (and will
1243 * be more consistent with the preferred class-based functions).
1244 *
1245 * Refer to the corresponding class-based functions for full details
1246 *
1247 *	Consistent interface:
1248 *
1249 *	@param string $fieldname - name to use for the element
1250 *	@param mixed $curval - current value of field - as CSV if multiple values
1251 *	@param string $mode = (off|admin...) - affects display -
1252 *	@param string $optlist - comma-separated list which specifies the classes to be shown:
1253 *		public
1254 *		guest
1255 *		nobody
1256 *		member
1257 *		readonly
1258 *		admin
1259 *		main - main admin
1260 *		classes - shows all classes
1261 *		matchclass - if 'classes' is set, this option will only show the classes that the user is a member of
1262 *
1263 *		filter - only show those classes where member is in a class permitted to view them - i.e. as the new 'visible to' field
1264 *		force  - show all classes (subject to the other options, including matchclass)
1265 *
1266 *		$mode = 'off' turns off listing of admin/main admin classes unless enabled in $optlist (can probably be deprecated - barely used)
1267 *
1268 */
1269
1270
1271function r_userclass($fieldname, $curval = 0, $mode = "off", $optlist = "")
1272{
1273	//  echo "Call r_userclass{$fieldname}, CV: {$curval}  opts: {$optlist}<br />";
1274	global $e_userclass;
1275	if ($mode != 'off')
1276	{	// Handle legacy code
1277		if ($optlist) $optlist .= ',';
1278		$optlist .= 'admin,main';
1279		if ($mode != 'admin') $optlist .= ',readonly';
1280	}
1281	if (!is_object($e_userclass)) $e_userclass = new user_class;
1282	return $e_userclass->uc_dropdown($fieldname,$curval,$optlist);
1283}
1284
1285
1286// Very similar to r_userclass, but returns a list of check boxes. (currently only used in newspost.php)
1287// $curval is a comma separated list of class IDs for boxes which are checked.
1288function r_userclass_check($fieldname, $curval = '', $optlist = "")
1289{
1290	//  echo "Call r_userclass_check: {$curval}<br />";
1291	global $e_userclass;
1292	if (!is_object($e_userclass)) $e_userclass = new user_class;
1293	$ret = $e_userclass->uc_checkboxes($fieldname,$curval,$optlist);
1294	if ($ret) $ret = "<div class='check-block'>".$ret."</div>";
1295	return $ret;
1296}
1297
1298
1299
1300function get_userclass_list()
1301{
1302//  echo "Call get_userclass_list<br />";
1303	global $e_userclass;
1304	if (!is_object($e_userclass)) $e_userclass = new user_class;
1305	return $e_userclass->uc_get_classlist();
1306}
1307
1308
1309
1310function r_userclass_name($id)
1311{
1312//  echo "Call r_userclass_name<br />";
1313	global $e_userclass;
1314	if (!is_object($e_userclass)) $e_userclass = new user_class;
1315	return $e_userclass->uc_get_classname($id);
1316}
1317
1318
1319
1320
1321
1322// Deprecated functions to hopefully be removed
1323function r_userclass_radio($fieldname, $curval = '')
1324{
1325	echo "Deprecated function r_userclass_radio not used in core - mutter if you'd like it implemented<br />";
1326}
1327
1328//========================================================================
1329//			Admin Class handler - could go into separate file later
1330//========================================================================
1331
1332class user_class_admin extends user_class
1333{
1334	protected $field_list = array('userclass_name' => "varchar(100) NOT NULL default ''",
1335							'userclass_description' => "varchar(250) NOT NULL default ''",
1336							'userclass_editclass' => "tinyint(3) unsigned NOT NULL default '0'",
1337							'userclass_parent' => "tinyint(3) unsigned NOT NULL default '0'",
1338							'userclass_accum' => "varchar(250) NOT NULL default ''",
1339							'userclass_visibility' => "tinyint(3) unsigned NOT NULL default '0'",
1340							'userclass_type'		=>"tinyint(1) unsigned NOT NULL default '0'",
1341							'userclass_icon' => "varchar(250) NOT NULL default ''"
1342							);		// Note - 'userclass_id' intentionally not in this list
1343
1344
1345	protected $graph_debug = FALSE;			// Shows extra info on graphical tree when TRUE
1346
1347	// Icons to use for graphical tree display
1348	// First index - no children, children
1349	// Second index - not last item, last item
1350	// Third index - closed tree, open tree
1351	protected $tree_icons = array(
1352						FALSE => array(			// No children
1353							FALSE => array(			// Not last item
1354							  FALSE => '',		// Closed tree - don't display
1355							  TRUE  => 'branch.gif'
1356							  )
1357							,
1358							TRUE => array(			// Last item
1359							  FALSE => '',		// Closed tree - don't display
1360							  TRUE  => 'branchbottom.gif'
1361						    )
1362						),
1363						TRUE => array(			// children
1364							FALSE => array(			// Not last item
1365							  FALSE => 'plus.gif',		// Closed tree - option to expand
1366							  TRUE  => 'minus.gif'
1367							  )
1368							,
1369							TRUE => array(			// Last item
1370							  FALSE => 'plusbottom.gif',		// Closed tree - option to expand
1371							  TRUE  => 'minusbottom.gif'
1372							))
1373						);
1374
1375	protected $top_icon = null;
1376
1377
1378	/**
1379	 *	Constructor
1380	 */
1381	public function __construct()
1382	{
1383		parent::__construct();
1384		if (!(getperms('4') || getperms('0'))) return;
1385
1386		$this->isAdmin = TRUE;			// We have full class management rights
1387
1388		$pref = e107::getPref();
1389
1390		$style = ($pref['admincss'] == 'admin_dark.css') ? ' icon-white' : '';
1391		$this->top_icon = "<i class='icon-user{$style}'></i> ";
1392	}
1393
1394
1395
1396	/**
1397	 *	Next three routines are used to update the database after adding/deleting a class
1398	 *	calc_tree is the public interface
1399	 *	@return none
1400	*/
1401	public function calc_tree()
1402	{
1403		$this->readTree(TRUE);						// Make sure we have accurate data
1404		foreach ($this->class_parents as $cp)
1405		{
1406			$rights = array();
1407			$this->rebuild_tree($cp,$rights);		// increasing rights going down the tree
1408		}
1409	}
1410
1411
1412
1413	/*
1414	 *	Internal function, called recursively to rebuild the permissions tree where rights increase going down the tree
1415	 *	If the permissions change, sets the 'change_flag' to force rewrite to DB (by other code)
1416	 *	@param integer $parent is the class number being processed.
1417	 *	@param array $rights is the array of rights accumulated so far in the walk down the tree
1418	 */
1419	protected function rebuild_tree($parent, $rights)
1420	{
1421
1422		if($this->class_tree[$parent]['userclass_parent'] == e_UC_NOBODY)
1423		{
1424			$this->topdown_tree($parent);
1425			return null;
1426		}
1427
1428		if($this->class_tree[$parent]['userclass_type'] == UC_TYPE_GROUP)
1429		{
1430			return null;            // Probably just stop here for a group class
1431		}
1432
1433		$rights[] = $parent;
1434		$imp_rights = implode(',', $rights);
1435
1436		if($this->class_tree[$parent]['userclass_accum'] != $imp_rights)
1437		{
1438			$this->class_tree[$parent]['userclass_accum'] = $imp_rights;
1439
1440			if(!isset($this->class_tree[$parent]['change_flag']))
1441			{
1442				$this->class_tree[$parent]['change_flag'] = 'UPDATE';
1443			}
1444		}
1445
1446
1447		if(!empty($this->class_tree[$parent]['class_children']))
1448		{
1449			foreach ($this->class_tree[$parent]['class_children'] as $cc)
1450			{
1451				$this->rebuild_tree($cc,$rights);		// Recursive call
1452			}
1453		}
1454	}
1455
1456
1457	/*
1458	 * Internal function, called recursively to rebuild the permissions tree where rights increase going up the tree
1459	 *	@param integer $our_class - ID of class being processed
1460	 *	@return array of class permissions
1461	 */
1462	protected function topdown_tree($our_class)
1463	{
1464		$rights  = array($our_class);				// Accumulator always has rights to its own class
1465
1466		if ($this->class_tree[$our_class]['userclass_type'] == UC_TYPE_GROUP) return array_merge($rights, explode(',',$this->class_tree[$our_class]['userclass_accum']));					// Stop rights accumulation at a group
1467
1468		foreach ($this->class_tree[$our_class]['class_children'] as $cc)
1469		{
1470			$rights = array_merge($rights,$this->topdown_tree($cc));				// Recursive call
1471		}
1472		$rights = array_unique($rights);
1473		$imp_rights = implode(',',$rights);
1474		if ($this->class_tree[$our_class]['userclass_accum'] != $imp_rights)
1475		{
1476			$this->class_tree[$our_class]['userclass_accum'] = $imp_rights;
1477			$this->class_tree[$our_class]['change_flag'] = 'UPDATE';
1478		}
1479		return $rights;
1480	}
1481
1482
1483
1484	/**
1485	 *	Called after recalculating the tree to save changed records to the database
1486	 *	@return none
1487	 */
1488	public function save_tree()
1489	{
1490		foreach ($this->class_tree as $tree)
1491		{
1492			if (isset($tree['change_flag']))
1493			{
1494				switch ($tree['change_flag'])
1495				{
1496				  case 'INSERT' :
1497					$this->add_new_class($tree);
1498					break;
1499				  case 'UPDATE' :
1500					$this->save_edited_class($tree);
1501					break;
1502
1503
1504				}
1505			}
1506		}
1507	}
1508
1509
1510
1511	/**
1512	 *	Next two routines show a text-based tree with markers to indicate the hierarchy.
1513	 *	Intended primarily for debugging
1514	 */
1515	protected function show_sub_tree($listnum,$marker, $add_class = FALSE)
1516	{
1517		$ret = '';
1518		$marker = '--'.$marker;
1519		foreach ($this->class_tree[$listnum]['class_children'] as $p)
1520		{
1521			$ret .= $marker.$this->class_tree[$p]['userclass_id'].':'.$this->class_tree[$p]['userclass_name'];
1522			if ($add_class) $ret .= " (".$this->class_tree[$p]['userclass_accum'].")";
1523			$ret .= "  Children: ".count($this->class_tree[$p]['class_children']);
1524			$ret .= "<br />";
1525			$ret .= $this->show_sub_tree($p,$marker, $add_class);
1526		}
1527		return $ret;
1528	}
1529
1530
1531	/**
1532	 *	Show a text-based sub-tree, optionally with helpful debug info
1533	 *	@param boolean $add_class - TRUE includes a list of accumulated rights in each line
1534	 *	@return string text for display
1535	 */
1536	public function show_tree($add_class = FALSE)
1537	{
1538		$ret = '';
1539		foreach ($this->class_parents as $p)
1540		{
1541		  $ret .= $this->class_tree[$p]['userclass_id'].':'.$this->class_tree[$p]['userclass_name'];
1542		  if ($add_class) $ret .= " (".$this->class_tree[$p]['userclass_accum'].")";
1543		  $ret .= "  Children: ".count($this->class_tree[$p]['class_children']);
1544		  $ret .= "<br />";
1545		  $ret .= $this->show_sub_tree($p,'>', $add_class);
1546		}
1547		return $ret;
1548	}
1549
1550
1551
1552
1553	/*
1554	 *	Next two routines generate a graphical tree, including option to open/close branches
1555	 *
1556	 *	function show_graphical subtree() is for internal use, called from function show_graphical_tree
1557	 *
1558	 *	@param int $listnum - class number of first element to display, along with its children
1559	 *	@param array $indent_images - array of images with which to start each line
1560	 *	@param boolean $is_last - TRUE if this is the last element on the current branch of the tree
1561	 */
1562	protected function show_graphical_subtree($listnum, $indent_images, $is_last = FALSE)
1563	{
1564		$tree = vartrue($this->class_tree[$listnum]['class_children'], array());
1565		$num_children = count($tree);
1566		$is_open = TRUE;
1567		$tag_name = 'uclass_tree_'.$listnum;
1568
1569		$ret = "<div class='uclass_tree' >\n";
1570
1571		foreach ($indent_images as $im)
1572		{
1573			$ret .= "<img src='".UC_ICON_DIR.$im."' alt='class icon' />";
1574		}
1575		// If this link has children, wrap the next image in a link and an expand/hide option
1576		if ($num_children)
1577		{
1578			$ret .= "<span onclick=\"javascript: expandit('{$tag_name}'); expandit('{$tag_name}_p'); expandit('{$tag_name}_m')\"><img src='".UC_ICON_DIR.$this->tree_icons[TRUE][$is_last][TRUE]."' alt='class icon' id='{$tag_name}_m' />";
1579			$ret .= "<img src='".UC_ICON_DIR.$this->tree_icons[TRUE][$is_last][FALSE]."' style='display:none' id='{$tag_name}_p' alt='class icon' /></span>\n";
1580		}
1581		else
1582		{
1583			$ret .= "<img src='".UC_ICON_DIR.$this->tree_icons[FALSE][$is_last][$is_open]."' alt='class icon' />\n";
1584		}
1585		$name_line = '';
1586		if ($this->graph_debug) { $name_line = $this->class_tree[$listnum]['userclass_id'].":";  }
1587	//	if ($this->graph_debug) { $name_line = varset($this->class_tree[$listnum]['userclass_id'], 'XXX').":";  }
1588
1589		if ($this->class_tree[$listnum]['userclass_type'] == UC_TYPE_GROUP)
1590		{
1591			$name_line .= '<b>'.$this->class_tree[$listnum]['userclass_name'].'</b> ('.UCSLAN_81.').';	// Highlight groups
1592		}
1593		else
1594		{
1595			$name_line .= $this->class_tree[$listnum]['userclass_name'];
1596		}
1597		if ($this->graph_debug) $name_line .= "[vis:".$this->class_tree[$listnum]['userclass_visibility'].", edit:".$this->class_tree[$listnum]['userclass_editclass']."] = ".$this->class_tree[$listnum]['userclass_accum']." Children: ".implode(',',$this->class_tree[$listnum]['class_children']);
1598		// Next (commented out) line gives a 'conventional' link
1599		//$ret .= "<img src='".UC_ICON_DIR."topicon.png' alt='class icon' /><a style='text-decoration: none' class='userclass_edit' href='".e_ADMIN_ABS."userclass2.php?config.edit.{$this->class_tree[$listnum]['userclass_id']}'>".$name_line."</a></div>";
1600		if($this->queryCanEditClass($this->class_tree[$listnum]['userclass_id']))
1601		{
1602			$url = e_SELF.'?mode=main&action=edit&amp;id='.$this->class_tree[$listnum]['userclass_id'];
1603			$onc = '';
1604		}
1605		else
1606		{
1607			$url = '#';
1608			$onc = " onclick=\"alert('".str_replace("'", "\\'", (stripslashes(UCSLAN_90)))."'); return false;\"";
1609		}
1610
1611		$ret .= $this->top_icon."<a style='text-decoration: none' class='userclass_edit'{$onc} href='{$url}'>".$name_line."</a></div>";
1612		//$ret .= "<img src='".UC_ICON_DIR."topicon.png' alt='class icon' />
1613			//<span style='cursor:pointer; vertical-align: bottom' onclick=\"javascript: document.location.href='".e_ADMIN."userclass2.php?config.edit.{$this->class_tree[$listnum]['userclass_id']}'\">".$name_line."</span></div>";
1614		// vertical-align: middle doesn't work! Nor does text-top
1615
1616		if ($num_children)
1617		{
1618			$ret .= "<div class='uclass_tree' id='{$tag_name}'>\n";
1619			$image_level = count($indent_images);
1620			if ($is_last)
1621			{
1622				$indent_images[] = 'linebottom.gif';
1623			}
1624			else
1625			{
1626				$indent_images[] = 'line.gif';
1627			}
1628			if (isset($this->class_tree[$listnum]['class_children'])) foreach ($this->class_tree[$listnum]['class_children'] as $p)
1629			{
1630				$num_children--;
1631				if ($num_children)
1632				{	// Use icon indicating more below
1633					$ret .= $this->show_graphical_subtree($p, $indent_images, FALSE);
1634				}
1635				else
1636				{ // else last entry on this tree
1637					$ret .= $this->show_graphical_subtree($p, $indent_images, TRUE);
1638				}
1639			}
1640			$ret .= "</div>";
1641		}
1642		return $ret;
1643	}
1644
1645
1646
1647	/**
1648	 * Create graphical class tree, including clickable links to expand/contract branches.
1649	 *
1650	 * @param boolean $show_debug - TRUE to display additional information against each class
1651	 * @return string - text for display
1652	 */
1653	public function show_graphical_tree($show_debug=FALSE)
1654	{
1655		$this->graph_debug = $show_debug;
1656		$indent_images = array();
1657
1658		$ret = "
1659		<div class='uclass_tree' style='height:16px'>
1660			".$this->top_icon."
1661			<span style='top:3px'></span>
1662		</div>";		// Just a generic icon here to provide a visual anchor
1663
1664		$num_parents = count($this->class_parents);
1665		foreach ($this->class_parents as $p)
1666		{
1667			$num_parents--;
1668			$ret .= $this->show_graphical_subtree($p, $indent_images, ($num_parents == 0));
1669		}
1670		return $ret;
1671	}
1672
1673
1674
1675	/**
1676	 *	Creates an array which contains only DB fields (i.e. strips any added status)
1677	 *	Copies only those valid fields which are found in the source array
1678	 *	@param array $classrec - array of class-related information
1679	 *	@param boolean $inc_id - TRUE to include the class id field (if present in the original)
1680	 *	@return array of class info suitable for writing to DB
1681	 */
1682	protected function copy_rec($classrec, $inc_id = FALSE)
1683	{
1684		$ret = array();
1685		if ($inc_id && isset($classrec['userclass_id'])) $ret['userclass_id'] = $classrec['userclass_id'];
1686		foreach ($this->field_list as $fl => $val)
1687		{
1688			if (isset($classrec[$fl])) $ret[$fl] = $classrec[$fl];
1689		}
1690		return $ret;
1691	}
1692
1693
1694
1695	/**
1696	 *	Return an unused class ID. Misses the predefined classes.
1697	 *	Initially tries to find an unused class ID less than e_UC_SPECIAL_BASE
1698	 *	Then attempts to find a gap in the lower numbered classes
1699	 *	Finally allocates a class number above e_UC_SPECIAL_END
1700	 *	@return integer|boolean - class ID if available; otherwise FALSE
1701	 */
1702	public function findNewClassID()
1703	{
1704		$i = 1;
1705		// Start by allocating a new class with a number higher than any previously allocated
1706		foreach ($this->class_tree as $id => $r)
1707		{
1708			if ($id < e_UC_SPECIAL_BASE)
1709			{
1710				$i = max($i,$id);
1711			}
1712		}
1713		$i++;
1714		if ($i < e_UC_SPECIAL_BASE) return $i;
1715
1716		// Looks like we've had a lot of activity in classes - try and find a gap.
1717		for ($i = 1; ($i < e_UC_SPECIAL_BASE); $i++)
1718		{
1719			if (!isset($this->class_tree[$i])) return $i;
1720		}
1721		// Big system!! Assign a class in the 2.0-only block above 255
1722		for ($i = e_UC_SPECIAL_END+1; ($i < 32767); $i++)
1723		{
1724			if (!isset($this->class_tree[$i])) return $i;
1725		}
1726
1727		return FALSE;			// Just in case all classes assigned!
1728	}
1729
1730
1731
1732
1733	/**
1734	 *	Add new class. Class ID must be in the passed record.
1735	 *	@param array $classrec - user class data
1736	 *	@return boolean TRUE on success, FALSE on failure
1737	 */
1738	public function add_new_class($classrec)
1739	{
1740		if (!isset($classrec['userclass_id']))
1741		{
1742			return FALSE;
1743		}
1744		if ($classrec['userclass_type'] == UC_TYPE_GROUP)
1745		{	// Need to make sure our ID is in the accumulation array
1746			$temp = explode(',',$classrec['userclass_accum']);
1747			if (!in_array($classrec['userclass_id'], $temp))
1748			{
1749				$temp[] = $classrec['userclass_id'];
1750				$classrec['userclass_accum'] = implode(',',$temp);
1751			}
1752		}
1753		if ($this->sql_r->db_Insert('userclass_classes',$this->copy_rec($classrec, TRUE)) === FALSE)
1754		{
1755			return FALSE;
1756		}
1757		//findNewClassID() always returns an unused ID.
1758		//if a plugin.xml adds more than one <class ...> within <userClasses..> tag
1759		//it will add 1 class only because class_tree never updates itself after adding classes and will return the same unnused ID
1760		$this->class_tree[$classrec['userclass_id']] = $classrec;
1761		$this->clearCache();
1762		return TRUE;
1763	}
1764
1765
1766
1767	/**
1768	 *	Save class data after editing
1769	 *
1770	 *	@param array $classrec - class data
1771	 *	@return boolean TRUE on success, FALSE on failure
1772	 *
1773	 *	Note - only updates those fields which are present in the passed array, and ignores unexpected fields.
1774	 */
1775	public function save_edited_class($classrec)
1776	{
1777		if (!$classrec['userclass_id'])
1778		{
1779		e107::getMessage()->addDebug('Programming bungle on save - no ID field');
1780		//	echo 'Programming bungle on save - no ID field<br />';
1781			return FALSE;
1782		}
1783		$qry = '';
1784		$spacer = '';
1785		if (isset($classrec['userclass_type']) && ($classrec['userclass_type'] == UC_TYPE_GROUP))
1786		{	// Need to make sure our ID is in the accumulation array
1787			$temp = explode(',',$classrec['userclass_accum']);
1788			if (!in_array($classrec['userclass_id'], $temp))
1789			{
1790				$temp[] = $classrec['userclass_id'];
1791				$classrec['userclass_accum'] = implode(',',$temp);
1792			}
1793		}
1794
1795		foreach ($this->field_list as $fl => $val)
1796		{
1797			if (isset($classrec[$fl]))
1798			{
1799				$qry .= $spacer."`".$fl."` = '".$classrec[$fl]."'";
1800				$spacer = ", ";
1801			}
1802		}
1803		if ($this->sql_r->db_Update('userclass_classes', $qry." WHERE `userclass_id`='{$classrec['userclass_id']}'") === FALSE)
1804		{
1805			return FALSE;
1806		}
1807		$this->clearCache();
1808		return TRUE;
1809	}
1810
1811
1812
1813	/**
1814	 *	Check if a user may edit a user class.
1815	 *	@param integer $classID > 0
1816	 *	@param string $classList - comma-separated list of class IDs; defaults to those of current user
1817	 *
1818	 *	@return integer:
1819	 *				0 - if editing not allowed at all
1820	 *				1 - if restricted editing allowed (usually because its a fixed class)
1821	 *				2 - All editing rights allowed
1822	 */
1823	public function queryCanEditClass($classID, $classList = USERCLASS_LIST)
1824	{
1825		if (!isset($this->class_tree[$classID])) return 0;			// Class doesn't exist - no hope of editing!
1826
1827		$blockers = array(e_UC_PUBLIC => 1, e_UC_READONLY => 1, e_UC_NOBODY => 1, e_UC_GUEST => 1);
1828		if (isset($blockers[$classID])) return 0;					// Don't allow edit of some fixed classes
1829
1830		$canEdit = $this->isAdmin;
1831		$possibles = array_flip(explode(',',$classList));
1832		if (isset($possibles[$this->class_tree[$classID]['userclass_editclass']])) $canEdit = TRUE;
1833
1834		if (!$canEdit) return 0;
1835
1836		if (($classID >= e_UC_SPECIAL_BASE) && ($classID <= e_UC_SPECIAL_END)) return  1;	// Restricted edit of fixed classes
1837		if (isset($this->fixed_classes[$classID])) return 1;			// This picks up fixed classes such as e_UC_PUBLIC outside the main range
1838
1839		return 2;						// Full edit rights - a 'normal' class
1840	}
1841
1842
1843
1844
1845	/**
1846	 *	Check if a class may be deleted. (Fixed classes, classes with descendants cannot be deleted)
1847	 *	@param integer $classID > 0
1848	 *	@return boolean TRUE if deletion permissible; false otherwise
1849	 */
1850	public function queryCanDeleteClass($classID)
1851	{
1852		if (($classID >= e_UC_SPECIAL_BASE) && ($classID <= e_UC_SPECIAL_END)) return FALSE;	// Don't allow deletion of fixed classes
1853		if (isset($this->fixed_classes[$classID])) return FALSE;			// This picks up fixed classes such as e_UC_PUBLIC outside the main range which can't be deleted
1854		if (!isset($this->class_tree[$classID])) return FALSE;
1855		if (count($this->class_tree[$classID]['class_children'])) return FALSE;		// Can't delete class with descendants
1856		foreach ($this->class_tree as $c)
1857		{
1858			if ($c['userclass_editclass'] == $classID) return FALSE;				// Class specified as managing or using another class
1859			if ($c['userclass_visibility'] == $classID) return FALSE;
1860		}
1861		return TRUE;
1862	}
1863
1864
1865
1866	/**
1867	 *	Delete a class
1868	 *	@param integer $classID > 0
1869	 *	@return boolean TRUE on success, FALSE on error
1870	 */
1871	public function delete_class($classID)
1872	{
1873		if (self::queryCanDeleteClass($classID) === FALSE) return FALSE;
1874
1875		if ($this->sql_r->db_Delete('userclass_classes', "`userclass_id`='{$classID}'") === FALSE) return FALSE;
1876		$this->clearCache();
1877		$this->readTree(TRUE);			// Re-read the class tree
1878		return TRUE;
1879	}
1880
1881
1882
1883	/**
1884	 *	Delete a class, and update class membership for all users who are members of that class.
1885	 *	@param integer $classID > 0
1886	 *	@return boolean TRUE on success, FALSE on error
1887	 */
1888	public function deleteClassAndUsers($classID)
1889	{
1890		if (self::delete_class($classID) === TRUE)
1891		{
1892			if ($this->sql_r->select('user', 'user_id, user_class', "user_class REGEXP '(^|,){$classID}(,|$)'"))
1893			{
1894				$sql2 = e107::getDb('sql2');
1895				while ($row = $this->sql_r->fetch())
1896				{
1897					$newClass = self::ucRemove($classID, $row['user_class']);
1898					$sql2->update('user', "user_class = '{$newClass}' WHERE user_id = {$row['user_id']} LIMIT 1");
1899				}
1900			}
1901			return TRUE;
1902		}
1903		return FALSE;
1904	}
1905
1906
1907
1908	/**
1909	 *	Adds all users in list to a specified user class
1910	 *	(Moved in from e_userclass class)
1911	 *	@param integer $cid - user class ID
1912	 *	@param $uinfoArray is array(uid=>user_class) - list of affected users
1913	 *	@return none
1914	 */
1915	public function class_add($cid, $uinfoArray)
1916	{
1917		$e107 = e107::getInstance();
1918		$uc_sql = new db;
1919		foreach($uinfoArray as $uid => $curclass)
1920		{
1921			if ($curclass)
1922			{
1923				$newarray = array_unique(array_merge(explode(',', $curclass), array($cid)));
1924				$new_userclass = implode(',', $newarray);
1925			}
1926			else
1927			{
1928				$new_userclass = $cid;
1929			}
1930			$uc_sql->update('user', "user_class='".e107::getParser()->toDB($new_userclass, true)."' WHERE user_id=".intval($uid)." LIMIT 1");
1931		}
1932	}
1933
1934
1935
1936	/**
1937	 *	Removes all users in list from a specified user class
1938	 *	(Moved in from e_userclass class)
1939	 *	@param integer $cid - user class ID
1940	 *	@param $uinfoArray is array(uid=>user_class) - list of affected users
1941	 *	@return none
1942	 */
1943	public function class_remove($cid, $uinfoArray)
1944	{
1945		$uc_sql = e107::getDb();
1946		foreach($uinfoArray as $uid => $curclass)
1947		{
1948			$newarray = array_diff(explode(',', $curclass), array('', $cid));
1949			$new_userclass = implode(',', $newarray);
1950			$uc_sql->update('user', "user_class='".e107::getParser()->toDB($new_userclass, true)."' WHERE user_id=".intval($uid)." LIMIT 1");
1951		}
1952	}
1953
1954
1955
1956	/**
1957	 *	Check class data record for a fixed class - certain fields have constraints on their values.
1958	 *	updates any values which are unacceptable.
1959	 *	@param array $data - user class record (passed by reference)
1960	 *	@param integer $id - class id
1961	 *	@return boolean Returns TRUE if nothing changed, FALSE if changes made
1962	 */
1963	public function checkAdminInfo(&$data, $id)
1964	{
1965		$ret = TRUE;
1966		if (($id < e_UC_SPECIAL_BASE) || ($id > e_UC_SPECIAL_END)) return TRUE;
1967		if (isset($data['userclass_parent']))
1968		{
1969			if (($data['userclass_parent'] < e_UC_SPECIAL_BASE) || ($data['userclass_parent'] > e_UC_SPECIAL_END))
1970			{
1971				$data['userclass_parent'] = e_UC_NOBODY;
1972				$ret = FALSE;
1973			}
1974		}
1975		if (isset($data['userclass_editclass']))
1976		{
1977			if ($id == e_UC_MAINADMIN)
1978			{
1979				if ($data['userclass_editclass'] < e_UC_MAINADMIN)
1980				{
1981					$data['userclass_editclass'] = e_UC_MAINADMIN;
1982					$ret = FALSE;
1983				}
1984			}
1985			elseif (($data['userclass_editclass'] < e_UC_SPECIAL_BASE) || ($data['userclass_editclass'] > e_UC_SPECIAL_END))
1986			{
1987				$data['userclass_editclass'] = e_UC_MAINADMIN;
1988				$ret = FALSE;
1989			}
1990		}
1991		return $ret;
1992	}
1993
1994
1995
1996	/**
1997	 *	Set a simple default tree structure of classes
1998	 *	@return none
1999	 */
2000	public function set_default_structure()
2001	{
2002		// If they don't exist, we need to create class records for the 'standard' user classes
2003		$init_list = array(
2004						array('userclass_id' => e_UC_MEMBER, 'userclass_name' => UC_LAN_3,
2005							'userclass_description' => UCSLAN_75,
2006							'userclass_editclass' => e_UC_MAINADMIN,
2007							'userclass_parent' => e_UC_PUBLIC,
2008							'userclass_visibility' => e_UC_MEMBER
2009							),
2010						array('userclass_id' => e_UC_ADMINMOD, 'userclass_name' => UC_LAN_8,
2011							'userclass_description' => UCSLAN_74,
2012							'userclass_editclass' => e_UC_MAINADMIN,
2013							'userclass_parent' => e_UC_MAINADMIN,
2014							'userclass_visibility' => e_UC_MEMBER
2015							),
2016						array('userclass_id' => e_UC_ADMIN, 'userclass_name' => UC_LAN_5,
2017							'userclass_description' => UCSLAN_76,
2018							'userclass_editclass' => e_UC_MAINADMIN,
2019							'userclass_parent' => e_UC_ADMINMOD,
2020							'userclass_visibility' => e_UC_MEMBER
2021							),
2022						array('userclass_id' => e_UC_MAINADMIN, 'userclass_name' => UC_LAN_6,
2023							'userclass_description' => UCSLAN_77,
2024							'userclass_editclass' => e_UC_MAINADMIN,
2025							'userclass_parent' => e_UC_NOBODY,
2026							'userclass_visibility' => e_UC_MEMBER
2027							),
2028						array('userclass_id' => e_UC_MODS, 'userclass_name' => UC_LAN_7,
2029							'userclass_description' => UCSLAN_78,
2030							'userclass_editclass' => e_UC_MAINADMIN,
2031							'userclass_parent' => e_UC_ADMINMOD,
2032							'userclass_visibility' => e_UC_MEMBER
2033							),
2034						array('userclass_id' => e_UC_NEWUSER, 'userclass_name' => UC_LAN_9,
2035							'userclass_description' => UCSLAN_87,
2036							'userclass_editclass' => e_UC_MAINADMIN,
2037							'userclass_parent' => e_UC_MEMBER,
2038							'userclass_visibility' => e_UC_ADMIN
2039							),
2040						array('userclass_id' => e_UC_BOTS, 'userclass_name' => UC_LAN_10,
2041							'userclass_description' => UCSLAN_88,
2042							'userclass_editclass' => e_UC_MAINADMIN,
2043							'userclass_parent' => e_UC_PUBLIC,
2044							'userclass_visibility' => e_UC_ADMIN
2045							)
2046						);
2047
2048		foreach ($init_list as $entry)
2049		{
2050			if ($this->sql_r->select('userclass_classes','*',"userclass_id='".$entry['userclass_id']."' "))
2051			{
2052				$this->sql_r->update('userclass_classes', "userclass_parent='".$entry['userclass_parent']."', userclass_visibility='".$entry['userclass_visibility']."' WHERE userclass_id='".$entry['userclass_id']."'");
2053			}
2054			else
2055			{
2056				$this->add_new_class($entry);
2057			}
2058		}
2059	}
2060
2061
2062
2063	/**
2064	 *	Write the current userclass tree to the file e_TEMP.'userclasses.xml'
2065	 *
2066	 *	@return TRUE on success, FALSE on fail.
2067	 */
2068	public function makeXMLFile()
2069	{
2070		$xml = "<dbTable name=\"userclass_classes\">\n";
2071		foreach ($this->class_tree as $uc => $d)
2072		{
2073			$xml .= "\t<item>\n";
2074			$xml .= "\t\t<field name=\"userclass_id\">{$uc}</field>\n";
2075			foreach ($this->field_list as $f => $v)
2076			{
2077				$xml .= "\t\t<field name=\"{$f}\">{$d[$f]}</field>\n";
2078			}
2079			$xml .= "\t</item>\n";
2080		}
2081		$xml .= "</dbTable>\n";
2082		return (file_put_contents(e_TEMP.'userclasses.xml', $xml) === FALSE) ? FALSE : TRUE;
2083	}
2084
2085
2086
2087	/**
2088	 *	Clear user class cache
2089	 *	@return none
2090	 */
2091	public function clearCache()
2092	{
2093		e107::getCache()->clear_sys(UC_CACHE_TAG);
2094	}
2095}
2096
2097
2098
2099
2100