sql_r = e107::getDb('sql_r');
$this->isAdmin = FALSE;
$this->fixed_classes = array(e_UC_PUBLIC => UC_LAN_0,
e_UC_GUEST => UC_LAN_1,
e_UC_NOBODY => UC_LAN_2,
e_UC_MEMBER => UC_LAN_3,
e_UC_ADMIN => UC_LAN_5,
e_UC_MAINADMIN => UC_LAN_6,
e_UC_READONLY => UC_LAN_4,
e_UC_NEWUSER => UC_LAN_9
);
$this->text_class_link = array('public' => e_UC_PUBLIC, 'guest' => e_UC_GUEST, 'nobody' => e_UC_NOBODY, 'member' => e_UC_MEMBER,
'admin' => e_UC_ADMIN, 'main' => e_UC_MAINADMIN, 'readonly' => e_UC_READONLY, 'new' => e_UC_NEWUSER);
$this->readTree(TRUE); // Initialise the classes on entry
}
/*
Ensure the tree of userclass data is stored in our object.
Only read if its either not present, or the $force flag is set
*/
protected function readTree($force = FALSE)
{
if (isset($this->class_tree) && count($this->class_tree) && !$force) return $this->class_tree;
global $e107;
$this->class_tree = array();
$this->class_parents = array();
$array = new ArrayData;
if ($temp = $e107->ecache->retrieve_sys(UC_CACHE_TAG))
{
$this->class_tree = $array->ReadArray($temp);
unset($temp);
}
else
{
$this->sql_r->db_Select("userclass_classes", '*', "ORDER BY userclass_parent", 'nowhere'); // The order statement should give a consistent return
while ($row = $this->sql_r->db_Fetch(MYSQL_ASSOC))
{
$this->class_tree[$row['userclass_id']] = $row;
$this->class_tree[$row['userclass_id']]['class_children'] = array(); // Create the child array in case needed
}
// Add in any fixed classes that aren't already defined
foreach ($this->fixed_classes as $c => $d)
{
// if (!isset($this->class_tree[$c]) && ($c != e_UC_PUBLIC))
if (!isset($this->class_tree[$c]))
{
switch ($c)
{
case e_UC_ADMIN :
case e_UC_MAINADMIN :
$this->class_tree[$c]['userclass_parent'] = e_UC_NOBODY;
break;
case e_UC_NEWUSER :
$this->class_tree[$c]['userclass_parent'] = e_UC_MEMBER;
break;
default :
$this->class_tree[$c]['userclass_parent'] = e_UC_PUBLIC;
}
$this->class_tree[$c]['userclass_id'] = $c;
$this->class_tree[$c]['userclass_name'] = $d;
$this->class_tree[$c]['userclass_description'] = 'Fixed class';
$this->class_tree[$c]['userclass_visibility'] = e_UC_PUBLIC;
$this->class_tree[$c]['userclass_editclass'] = e_UC_MAINADMIN;
$this->class_tree[$c]['userclass_accum'] = $c;
$this->class_tree[$c]['userclass_type'] = UC_TYPE_STD;
}
}
$userCache = $array->WriteArray($this->class_tree, FALSE);
$e107->ecache->set_sys(UC_CACHE_TAG,$userCache);
unset($userCache);
}
// Now build the tree.
// There are just two top-level classes - 'Everybody' and 'Nobody'
$this->class_parents[e_UC_PUBLIC] = e_UC_PUBLIC;
$this->class_parents[e_UC_NOBODY] = e_UC_NOBODY;
foreach ($this->class_tree as $uc)
{
/*
if ($uc['userclass_parent'] == e_UC_PUBLIC)
{ // Note parent (top level) classes
$this->class_parents[$uc['userclass_id']] = $uc['userclass_id'];
}
else
*/
if (($uc['userclass_id'] != e_UC_PUBLIC) && ($uc['userclass_id'] != e_UC_NOBODY))
{
// if (!array_key_exists($uc['userclass_parent'],$this->class_tree))
if (!isset($this->class_tree[$uc['userclass_parent']]))
{
echo "Orphaned class record: ID=".$uc['userclass_id']." Name=".$uc['userclass_name']." Parent=".$uc['userclass_parent']." ";
}
else
{ // Add to array
$this->class_tree[$uc['userclass_parent']]['class_children'][] = $uc['userclass_id'];
}
}
}
}
// Given the list of 'base' classes a user belongs to, returns a comma separated list including ancestors. Duplicates stripped
public function get_all_user_classes($start_list)
{
$is = array();
$start_array = explode(',', $start_list);
foreach ($start_array as $sa)
{ // Merge in latest values - should eliminate duplicates as it goes
if (isset($this->class_tree[$sa]))
{
$is = array_merge($is,explode(',',$this->class_tree[$sa]['userclass_accum']));
}
}
return implode(',',array_unique($is));
}
// Returns a list of user classes which can be edited by the specified classlist (defaults to current user's classes)
public function get_editable_classes($class_list = USERCLASS_LIST, $asArray = FALSE)
{
$ret = array();
$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);
$possibles = array_flip(explode(',',$class_list));
unset($possibles[e_UC_READONLY]);
foreach ($this->class_tree as $uc => $uv)
{
if (!isset($blockers[$uc]))
{
$ec = $uv['userclass_editclass'];
if (isset($possibles[$ec]))
{
// echo $uc." {$ec} {$uv['userclass_description']} ";
$ret[] = $uc;
}
}
}
if ($asArray) { return $ret; }
return implode(',',$ret);
}
// Combines the selected editable classes into the main class list for a user.
// $combined - the complete list of current class memberships
// $possible - the classes which are being edited
// $actual - the actual membership of the editable classes
// All classes may be passed as comma-separated lists or arrays
public function mergeClassLists($combined, $possible, $actual, $asArray = FALSE)
{
if (!is_array($combined)) { $combined = explode(',',$combined); }
if (!is_array($possible)) { $possible = explode(',',$possible); }
if (!is_array($actual)) { $actual = explode(',',$actual); }
$combined = array_flip($combined);
foreach ($possible as $p)
{
if (in_array($p,$actual))
{ // Class must be in final array
$combined[$p] = 1;
}
else
{
unset($combined[$p]);
}
}
$combined = array_keys($combined);
if ($asArray) { return $combined; }
return implode(',', $combined);
}
public function stripFixedClasses($inClasses)
{
$asArray = TRUE;
if (!is_array($inClasses))
{
$asArray = FALSE;
$inClasses = explode(',',$inClasses);
}
$inClasses = array_flip($inClasses);
foreach ($this->fixed_classes as $k => $v)
{
if (isset($inClasses[$k])) { unset($inClasses[$k]); }
}
$inClasses = array_keys($inClasses);
if ($asArray) { return ($inClasses); }
return implode(',',$inClasses);
}
// 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)
// Requires the class tree to have been initialised
public function normalise_classes($class_list)
{
$drop_classes = array();
$old_classes = explode(',',$class_list);
foreach ($old_classes as $c)
{ // Look at our parents (which are in 'userclass_accum') - if any of them are contained in old_classes, we can drop them.
$tc = array_flip(explode(',',$this->class_tree[$c]['userclass_accum']));
unset($tc[$c]); // Current class should be in $tc anyway
foreach ($tc as $tc_c => $v)
{
if (in_array($tc_c,$old_classes))
{
$drop_classes[] = $tc_c;
}
}
}
$new_classes = array_diff($old_classes,$drop_classes);
return implode(',',$new_classes);
}
/* Generate a dropdown list of user classes from which to select - virtually as r_userclass()
$optlist allows selection of the classes to be shown in the dropdown. All or none can be included, separated by comma. Valid options are:
public
guest
nobody
member
readonly
admin
main - main admin
new - new users
classes - shows all classes
matchclass - if 'classes' is set, this option will only show the classes that the user is a member of
language - list of languages.
blank - puts an empty option at the top of select dropdowns
filter - only show those classes where member is in a class permitted to view them - i.e. as the new 'visible to' field - added for 0.8
force - show all classes (subject to the other options, including matchclass) - added for 0.8
$extra_js - can add JS handlers (e.g. 'onclick', 'onchange') if required
[ $mode parameter of r_userclass() removed - $optlist is more flexible) ]
*/
public function uc_dropdown($fieldname, $curval = 0, $optlist = "", $extra_js = '')
{
global $pref;
$show_classes = self::uc_required_class_list($optlist);
$text = '';
foreach ($show_classes as $k => $v)
{
if ($k == e_UC_BLANK)
{
$text .= "\n";
}
else
{
$s = ($curval == $k && $curval !== '') ? "selected='selected'" : "";
$text .= "\n";
}
}
// Inverted Classes
if(strpos($optlist, "no-excludes") !== TRUE)
{
$text .= "\n\n";
}
if (strpos($optlist, "language") !== FALSE && $pref['multilanguage'])
{
$text .= "\n";
$tmpl = explode(",",e_LANLIST);
foreach($tmpl as $lang)
{
$s = ($curval == $lang) ? " selected='selected'" : "";
$text .= "\n";
}
}
// Only return the select box if we've ended up with some options
if ($text) $text = "\n\n";
return $text.$curVal;
}
/*
Generate an ordered array classid=>classname - used for dropdown and check box lists
If $just_ids is TRUE, array value is just '1'
*/
public function uc_required_class_list($optlist = '', $just_ids = FALSE)
{
$ret = array();
if (!$optlist) $optlist = 'public,guest,nobody,member,classes'; // Set defaults to simplify ongoing processing
if ($optlist == 'editable')
{
$temp = array_flip(explode(',',$this->get_editable_classes()));
if ($just_ids) return $temp;
foreach ($temp as $c => $t)
{
$temp[$c] = $this->class_tree[$c]['userclass_name'];
}
return $temp;
}
$opt_arr = explode(',',$optlist);
foreach ($opt_arr as $k => $v)
{
$opt_arr[$k] = trim($v);
}
$opt_arr = array_flip($opt_arr); // This also eliminates duplicates which could arise from applying the other options, although shouldn't matter
if (isset($opt_arr['force'])) unset($opt_arr['filter']);
if (isset($opt_arr['blank']))
{
$ret[e_UC_BLANK] = 1;
}
// Do the 'fixed' classes next
foreach ($this->text_class_link as $k => $v)
{
// if (isset($opt_arr[$k]) || isset($opt_arr['force']))
if (isset($opt_arr[$k]))
{
$ret[$v] = $just_ids ? '1' : $this->fixed_classes[$v];
}
}
// Now do the user-defined classes
if (isset($opt_arr['classes']) || isset($opt_arr['force']))
{ // Display those classes the user is allowed to:
// Main admin always sees the lot
// a) Mask the 'fixed' user classes which have already been processed
// b) Apply the visibility option field ('userclass_visibility')
// c) Apply the matchclass option if appropriate
foreach($this->class_tree as $uc_id => $row)
{
if (!array_key_exists($uc_id,$this->fixed_classes)
&& ( getperms("0")
|| (
(!isset($optlist["matchclass"]) || check_class($uc_id))
&& (!isset($optlist["filter"]) || check_class($row['userclass_visibility']))
)
)
)
{
$ret[$uc_id] = $just_ids ? '1' : $this->class_tree[$uc_id]['userclass_name'];
}
}
}
/* Above loop slightly changes the display order of earlier code versions.
If readonly must be last (after language), delete it from the $text_class_link array, and uncomment the following code
if (isset($opt_arr['readonly']))
{
$ret[e_UC_READONLY] = $this->class_tree[e_UC_READONLY]['userclass_description'];
}
*/
return $ret;
}
/*
Very similar to r_userclass, but returns a list of check boxes. Doesn't encapsulate it.
$fieldname is the name for the array of checkboxes
$curval is a comma separated list of class IDs for boxes which are checked.
$optlist as for uc_dropdown
if $showdescription is TRUE, appends the class description in brackets
*/
public function uc_checkboxes($fieldname, $curval='', $optlist = '', $showdescription = FALSE)
{
global $pref;
$show_classes = $this->uc_required_class_list($optlist);
$curArray = explode(",", $curval); // Array of current values
$ret = "";
foreach ($show_classes as $k => $v)
{
if ($k != e_UC_BLANK)
{
$c = (in_array($k,$curArray)) ? " checked='checked'" : "";
if ($showdescription) $v .= " (".$this->uc_get_classdescription($k).")";
$ret .= "
\n";
}
}
if (strpos($optlist, "language") !== FALSE && $pref['multilanguage'])
{
$ret .= "\n";
$tmpl = explode(",",e_LANLIST);
foreach($tmpl as $lang)
{
$c = (in_array($lang, $curArray)) ? " checked='checked' " : "";
$ret .= "";
}
}
return $ret;
}
/*
Next two routines create an indented tree - for example within a select box or a list of check boxes.
For each displayed element, the callback routine is called
$treename is the name given to the elements where required
$callback is a routine used to generate each element; there are two implemented within this class:
select (the default) - generates the option list. Text requires to be encapsulated in a tag set
- can also be used with multi-select boxes
checkbox - generates a set of checkboxes
Alternative callbacks can be used to achieve different layouts/styles
$current_value is a single class number for single-select dropdown; comma separated array of class numbers for checkbox list or multi-select
$optlist works the same as for other class displays
*/
protected function vetted_sub_tree($treename, $callback,$listnum,$nest_level,$current_value, $perms, $opt_options)
{
$ret = '';
$nest_level++;
if(isset($this->class_tree[$listnum]['class_children']))
{
foreach ($this->class_tree[$listnum]['class_children'] as $p)
{
// Looks like we don't need to differentiate between function and class calls
if (isset($perms[$p]))
{
$ret .= call_user_func($callback,$treename, $p,$current_value,$nest_level, $opt_options);
}
$ret .= $this->vetted_sub_tree($treename, $callback,$p,$nest_level,$current_value, $perms, $opt_options);
}
}
return $ret;
}
public function vetted_tree($treename, $callback='', $current_value='', $optlist = '',$opt_options = '')
{
$ret = '';
if (!$callback) $callback=array($this,'select');
$current_value = str_replace(' ','',$current_value); // Simplifies parameter passing for the tidy-minded
$perms = $this->uc_required_class_list($optlist,TRUE); // List of classes which we can display
if (isset($perms[e_UC_BLANK]))
{
$ret .= call_user_func($callback,$treename, e_UC_BLANK, $current_value,0, $opt_options);
}
foreach ($this->class_parents as $p)
{
if (isset($perms[$p]))
{
$ret .= call_user_func($callback,$treename, $p,$current_value,0, $opt_options);
}
$ret .= $this->vetted_sub_tree($treename, $callback,$p,0, $current_value, $perms, $opt_options);
}
return $ret;
}
// Callback for vetted_tree - Creates the option list for a selection box
public function select($treename, $classnum, $current_value, $nest_level)
{
if ($classnum == e_UC_BLANK) return "\n";
// echo "Display: {$classnum}, {$current_value}, {$nest_level} ";
$tmp = explode(',',$current_value);
$sel = in_array($classnum,$tmp) ? " selected='selected'" : '';
if ($nest_level == 0)
{
$prefix = '';
$style = " style='font-weight:bold; font-style: italic;'";
}
elseif ($nest_level == 1)
{
$prefix = ' ';
$style = " style='font-weight:bold'";
}
else
{
$prefix = ' '.str_repeat('--',$nest_level-1).'>';
$style = '';
}
return "\n";
}
// Callback for vetted_tree - displays indented checkboxes with class name only
public function checkbox($treename, $classnum, $current_value, $nest_level)
{
if ($classnum == e_UC_BLANK) return '';
$tmp = explode(',',$current_value);
$chk = in_array($classnum, $tmp) ? " checked='checked'" : '';
if ($nest_level == 0)
{
$style = " style='font-weight:bold'";
}
else
{
$style = " style='text-indent:".(1.2*$nest_level)."em'";
}
return "